精华内容
下载资源
问答
  • 本次实例实现了一个小球的控制移动,键盘上的上下左右四个键控制小球移动方向,这里只是一个小小的键盘监听器的雏形,希望会为以后的学习有些好的提示。  程序运行时要用鼠标点击一下窗体,从而激活窗体,这样...

          本次实例实现了一个小球的控制移动,键盘上的上下左右四个键控制小球移动的方向,这里只是一个小小的键盘监听器的雏形,希望会为以后的学习有些好的提示。

          程序运行时要用鼠标点击一下窗体,从而激活窗体,这样才能通过上下左右键来控制小球的移动

    第一个类:

     

    package keylistenermove;
    
    import java.awt.BorderLayout;
    import java.awt.Color;
    
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    public class mainTest extends JFrame {
    	private keyListenerTest klTest = new keyListenerTest();
    
    	public void showUI() {
    		this.setTitle("梅竹寒香");
    		this.setSize(500, 500);
    		this.setLocation(200, 200);
    		this.setDefaultCloseOperation(3);
    		// 窗口打开时默认最大化
    		this.setExtendedState(JFrame.MAXIMIZED_BOTH);
    		this.setResizable(true);
    		this.setVisible(true);
    		this.setLayout(new BorderLayout());
    		this.getContentPane().add(klTest, BorderLayout.CENTER);
    		// 启动上下左右方向键盘按键控制四个线程
    		directionThread directionthread = new directionThread(klTest);
    		directionthread.start();
    	}
    
    	public static void main(String args[]) {
    		mainTest maintest = new mainTest();
    		maintest.showUI();
    	}
    
    }
    

     

    第二个类:

    package keylistenermove;
    import java.awt.Canvas;
    import java.awt.Graphics;
    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    
    public class keyListenerTest extends Canvas implements
    		java.awt.event.KeyListener {
    	//小球的初始位置
    	public static int x = 250, y = 250;
    	//用来记录键盘按下时的键盘键所对应的ASCII码
    	public static int i;
    	//用来记录键盘松开时的键盘键所对应的ASCII码
    	public static int j;
    
    	public void keyTyped(KeyEvent e) {
    	}
    //得到每次按下的键盘键的ASCII码
    	public void keyPressed(KeyEvent e) {
    		i = e.getKeyCode();
    	}
    
    	public void keyReleased(KeyEvent e) {
    		//得到每次松开的键盘键的ASCII码
    		j = e.getKeyCode();
    		//这个是用来上下左右四个方向线程的while中的if判断,如果没有下面的代码,当按一下键盘键的时候,就无法停止小球移动
    		if (j == 37 || j == 38 || j == 39 || j == 40) {
    			i = -1;
    		}
    		
    
    	}
    
    	public keyListenerTest() {
    		this.addKeyListener(this);
    	}
    	public void paint(Graphics g) {
    		//画出小球,
    		g.fillOval(x - 10, y - 10, 20, 20);
    	}
    }
    

     

     

    第三个类:

    package keylistenermove;
    public class directionThread extends Thread {
    	private keyListenerTest klTest = new keyListenerTest();
    
    	public directionThread(keyListenerTest klTest) {
    		this.klTest = klTest;
    	}
    
    	public void run() {
    		while (true) {
    			try {
    				Thread.sleep(5);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			//实现小球的移动
    			if (keyListenerTest.i == 38) {
    				keyListenerTest.y -= 1;
    				klTest.repaint();
    
    			}
    			if (keyListenerTest.i == 39) {
    				keyListenerTest.x += 1;
    				klTest.repaint();
    
    			}
    			if (keyListenerTest.i == 37) {
    				keyListenerTest.x -= 1;
    				klTest.repaint();
    
    			}
    			if (keyListenerTest.i == 40) {
    				keyListenerTest.y+= 1;
    				klTest.repaint();
    
    			}
    		}
    	}
    }
    

     

     

     

    展开全文
  • eclipse打开java包,运行该函数出现开始界面窗体,单击开始,F1为暂停,F5为复活,左右方向键控制小人移动。小人落在钉齿板上将会被扣血,吃到樱桃会加血,落在滚动条上会随着滚动条移动并缓慢加血,落在弹簧上会小...
  • SevenColorPicker

    2009-02-26 22:46:19
    方向键 控制鼠标移动 F1 帮助 Esc 退出 (若热键无响应,可尝试加 Ctrl 键) ===== 说明 ========== 1. 双击窗体空白区域可 打开/关闭 "颜色面板" 2. 单击 "颜色块" 可将颜色代码复制到剪贴板, 左键复制16进制值, 右键...
  • 图片首先你要找到图片,可以百度一张,然后用ps切开,再找一张背景图片就搞定了(一张为活动角色,就是你方向键控制的角色,一个自动的角色,也就是“热狗”)活动角色得分 你还需要一个加载图片的工具类,可以使用get...

    一、整体思路

    1. 继承窗体类,复写窗体刷新
    2. 添加按键监听
    3. 游戏结束界面
    4. 王思聪类和热狗类 移动轨迹计算
    5. 判断碰撞 音乐播放
    6. 不断刷新窗体 处理细节

    二、编码

    1.图片

    首先你要找到图片,可以百度一张,然后用ps切开,再找一张背景图片就搞定了(一张为活动角色,就是你方向键控制的角色,一个自动的角色,也就是“热狗”)

    ee77ecfb29e49198c25814095bcce591.png

    活动角色

    2491af95e35b30f8f5c97098cb8fee1a.png

    得分

    你还需要一个加载图片的工具类,可以使用getResource得到图片的URL路径,然后使用java自带的工具类ImageIO来读取图片

    public class GameUtil { public static Image getImage(String path){URL url=GameUtil.class.getClassLoader().getResource(path);BufferedImage img=null;try {img = ImageIO.read(url);} catch (IOException e) {e.printStackTrace();}return img;}}

    2.窗体

    写一个类来继承窗体,为其设置宽高,位置,关闭设置,可否缩放大小

    复写它的更新和绘画方法,以后要在里面写绘画思聪和热狗和碰撞判断逻辑

    为其添加按键监听事件,在这里处理按键的点击和释放,以此来实现思聪的移动

    public void launchFrame(){setSize(width,height);setResizable(false);setLocation(200,20);setVisible(true);addKeyListener(new KeyMoniter());addWindowListener(new WindowAdapter(){@Overridepublic void windowClosing(WindowEvent e) {System.exit(0);}});}         @Overridepublic void paint(Graphics graphics) {}@Overridepublic void update(Graphics g){} class KeyMoniter extends KeyAdapter{@Overridepublic void keyPressed(KeyEvent e) {            //按键按下}@Overridepublic void keyReleased(KeyEvent e) {    //按键抬起}}

    3.思聪和热狗

    首先要有一个位置,还要能移动,其实也就是不断的计算位置和把图片画上去的过程

    定义一些思聪的属性,并在构造方法中初始化这些属性

    boolean left,right,down,up;public int x,y,width,height;Image img ; public Plane(String img_path,int x, int y){this.img = GameUtil.getImage(img_path);this.x = x;this.y = y;width = img.getWidth(null);height = img.getWidth(null);live = true;}

    把思聪画上去

    public void draw(Graphics g){if(live){g.drawImage(img, x, y, null);move();}}

    接下来最重要的就是计算思聪的位置了,为什么要把位置计算写到画思聪的方法里呢?主要是考虑这样的一种情况,如果你把方法计算写到按键按下或者释放里面,那么你想移动思聪的位置就得疯狂按键盘,要想长按按键来控制移动就要吧移动方法写到画思聪的方法里面,然后用按键的按下和释放来控制是否移动。

    移动的时候还要考虑到边界的情况

    public void move(){if(left&&x>=10){x -= 10;}if(up&&y>=30){y -= 10;}if(right&&x<=FeiJiGame.width-60){x += 10;}if(down&&y<=FeiJiGame.height -60){y += 10;}} public void KeyPressedControlDirection(KeyEvent e){int key_code = e.getKeyCode();if(key_code == 37){left = true;}if(key_code == 38){up = true;}if(key_code == 39){right = true;}if(key_code == 40){down = true;}} public void KeyRelasedControlDirection(KeyEvent e){int key_code = e.getKeyCode();if(key_code == 37){left = false;}if(key_code == 38){up =false;}if(key_code == 39){right = false;}if(key_code == 40){down = false;}}

    最后提供两个方法来改写思聪的生存

    对于热狗类就简单多了,就是让它不断的跑,不需要响应按键事件

    定义一些属性和在构造方法中初始化

    double speed=15;double degree; public double x,y;public int width,height;Image img; public Bullet(String img_path){img=GameUtil.getImage(img_path);degree = Math.random()*Math.PI*2;x=FeiJiGame.width/2;y=FeiJiGame.height/2;width = 10;height = 10;}

    随机的degree的用途是让它们的初始方向不同,使用sin和cos函数来控制热狗的移动,同时也要注意边界

    public void draw(Graphics g){g.drawImage(img, (int)x, (int)y, null);x += speed*Math.cos(degree);y += speed*Math.sin(degree); if(x>FeiJiGame.width-width||xFeiJiGame.height-height||y

    4.碰撞检测和音乐播放

    在java的awt里面有一个矩形类可以判断两个矩形是否有重叠部分,有重叠说明就碰撞上了

    Rectangle bulletRectangle = new Rectangle((int)bullet.x,(int)bullet.y,bullet.width,bullet.height);Rectangle planeRectangle = new Rectangle(plane.x,plane.y,plane.width,plane.height);boolean collide= bulletRectangle.intersects(planeRectangle);

    音乐播放可以使用第三方的库,jl-1.0.1.jar,因为可能连续吃到热狗,所以需要多线程播放音乐

    class MusicPlayer implements Runnable{@Overridepublic void run() {try {new Player(new FileInputStream(FeiJiGame.class.getClassLoader().getResource("raw/music.mp3").getPath().substring(1))).play();} catch (Exception e) {e.printStackTrace();}}}

    5.完成

    剩下的就是窗体的刷新,这个可以开一个线程来做

    class PaintThread extends Thread {@Overridepublic void run() {while(!Thread.currentThread().isInterrupted()){repaint();try {Thread.sleep(40);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}

    游戏右上方要显示现在的游戏时间,结束也要显示一些文字信息,写了一个工具类

    使用font控制字体,graphics.setFont设置字体,drawString画上去

    public void printInfo(Graphics g,String message,int size,int x,int y){g.setColor(Color.white);Font f = new Font("宋体",Font.BOLD,size);g.setFont(f);g.drawString(message, x,y);}

    游戏结束时显示一些信息,根据时间判断给出等级(可以随意设置,自定义分数和等级)

    private void gameOver(Graphics graphics) {printInfo(graphics,"GAME OVER",80,270,300);int survivalTime = (int)(endTime.getTime()-starTime.getTime())/1000;printInfo(graphics,"吃热狗时间:"+survivalTime+"秒",40,300,400); switch(survivalTime/10){case 1:printInfo(graphics,"独孤求败",50,350,500);break;case 2:printInfo(graphics,"登堂入室",50,350,500);break;case 3:printInfo(graphics,"小有成就",50,350,500);break;default:printInfo(graphics,"初入江湖",50,350,500);break;}paintThread.interrupt();}

    在初始化窗体时要添加热狗和开始重绘线程,开始计时

    for(int i=0;i<15;i++){Bullet bullet = new Bullet("images/hotdog.png");bulletList.add(bullet); }starTime = new Date();endTime = new Date();paintThread=new PaintThread();paintThread.start();

    复写绘画和更新

            @Overridepublic void paint(Graphics graphics) {graphics.drawImage(bg, 0, 0, null);plane.draw(graphics);endTime = new Date();if(gameState){for(int i=0;i

    在按键监听里面调用思聪的按键按下和释放方法

    class KeyMoniter extends KeyAdapter{@Overridepublic void keyPressed(KeyEvent e) {plane.KeyPressedControlDirection(e);}@Overridepublic void keyReleased(KeyEvent e) {plane.KeyRelasedControlDirection(e);}}

    最后启动游戏

    public static void main(String[] args) {FeiJiGame game = new FeiJiGame();game.loadGame();}

    6.注意事项

    6.1 在加载lib下依赖包的时候,不同开发工具的引入方式不同,这里主要区别是eclipse和idea

    6.2 在程序加载素材文件(背景和音乐等)需要对路径进行中文编码处理,否则路径会编程url编码,导致加载失败,如果路径是纯英文,就不需要考虑这些。

    7.需要源码的可以私信或者留言

    展开全文
  • 在分论(一)讨论了贪吃蛇和随机点...第一,移动控制,即外界通过什么方式来控制贪吃蛇的移动方向,常用的是键盘或者鼠标。第二,贪吃蛇移动的速度,贪吃蛇在键盘控制下,以怎么样的速度来移动。 通过鼠标控制或者...

    在分论(一)讨论了贪吃蛇和随机点的设计,挡在窗体中定义好贪吃蛇和随机点,那么,最关键的问题是如何定义贪吃蛇的移动,以及当碰到随机点以后会发生什么?
    贪吃蛇的移动说白了是坐标的变化,如何控制坐标变化呢?有两个关键点需要考虑,方向和速度。第一,移动的控制,即外界通过什么方式来控制贪吃蛇的移动方向,常用的是键盘或者鼠标。第二,贪吃蛇移动的速度,贪吃蛇在键盘控制下,以怎么样的速度来移动。 通过鼠标控制或者键盘控制贪吃蛇的移动,移动方式的时间监听也有两种方式,通过Timer类或者线程类监听,在总论二中介绍两种移动方式,由Timer()类控制速度键盘控制方向的移动方式和多线程控制速度和鼠标控制方向的移动方式.在源代码中我利用的是多线程控制速度和鼠标控制方向的移动方式,不过可以在窗体中加入按钮,用于选择键盘操作或者鼠标操作,这算一个彩蛋吧,后面介绍华容道小游戏就有这个功能,即通过鼠标或者键盘操作的选择,可以参考。

    (一)Timer()类控制速度键盘控制方向的移动方式

    蛇的移动进行控制 ,键盘上的“上下左右”来控制蛇身的移动变化。 对于整个面板加入监听器事件,对整个面板增加一个键盘监听器,用来监听自己在键盘上的动作。这里我们统一一下,用”↑↓←→”来控制方向。当我们使用键盘捕捉到相应的动作后,该如何继续呢?该如何编写事件的处理?

    我们来翻阅一下API。查看API中的KeyListener,我们可以查到KeyEvent,他有静态的常量用来表示键盘上相应的点触。VK_UP代表上箭头,VK_DOWN代表下箭头,VK_LEFT代表左箭头,VK_RIGHT代表右箭头。我们马上可以联想到:通过getKeyCode方法获取到键盘事件,和四个常量进行比较,如果符合,就可以按照对应的方向调用方法,来移动蛇身。我们可以定义一个Move()方法,并且定义一个变量direction代表方向,通过对direction不同的赋值传递给Move(),来对蛇身产生不同的移动效果。接下来贴代码:

    采用键盘控制的控制模式,利用键盘的上下左右键,来实现让·direction的变化,从而使贪吃蛇能够按照键盘的控制来实现移动
            this.addKeyListener(new KeyAdapter() {//捕捉键盘的按键事件 设置监听器
                public void keyPressed(KeyEvent e) {
                	 
                    switch(e.getKeyCode()) {
                        case KeyEvent.VK_UP://按下向上,返回1
                            direction = 1;
                            break;
                        case KeyEvent.VK_DOWN://按下向下,返回-1
                            direction = -1;
                            break;
                        case KeyEvent.VK_LEFT://按下相左,返回2
                            direction = 2;
                            break;
                        case KeyEvent.VK_RIGHT://按下向右,返回-2
                            direction = -2;
                            break;
                        default:
                            break;
                    }
                    if(direction + Direction !=0) {//不能反向运动
                        Direction = direction;
                        Move(direction);
                        repaint();
                    }
                }
            });
    

    在键盘上按钮上下左右键以后,通过键盘监听器获得传入的方向,然后将不同的方向定义为不同的Diretion值,不同的Direction值反映不同的坐标值的变化,按照不同的方向移动的过程中,需要考虑的问题有:撞到随机点,撞到墙,撞到自己,贪吃蛇不断移动的定义方式 。
    当输入不同的方向时,需要贪吃蛇蛇头的坐标,蛇头方向的变化就是贪吃蛇移动方向的变化,向上,向下,向左,向右,每一个方向的移动过程中x y值坐标的变化不同,

      int FirstX = snake.get(0).getX();            //获取蛇第一个点的横坐标
            int FirstY = snake.get(0).getY();            //获取蛇第一个点的纵坐标                  
            if(!startFlag) 
                return ; 
            //方向控制
            switch(direction) {
                case 1:     //向上
                    FirstY--;
                    break;
                case -1:     //向下
                    FirstY++;
                    break;
                case 2:     //向左
                    FirstX--;
                    break;
                case -2:
                    FirstX++;    //向右
                    break;
                default:
                    break;
            }
    

    当贪吃蛇撞到随机点时,定义一个撞到随机点的方法。

     //当碰到随机点时
            if(FirstX == newNode.getX()&&FirstY == newNode.getY()) { 
            	new  eatFoodMusic();
                getNode();
                return;
            }
    

    撞到随机点的方法,贪吃蛇的长度增加一,贪吃蛇从前向后遍历,然后重画随机点。

        //获取随机点
    	public void getNode() {                            
            snake.add(new SnakeNode());
            Length++;
            
            for(int x = Length-1; x >0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
                snake.get(x).setColor(snake.get(x-1).getColor());
            }
            snakeScore.setText( ""+( Length ));         //定义蛇的长度
            snake.get(0).setX(newNode.getX());
            snake.get(0).setY(newNode.getY());
            snake.get(0).setColor(newNode.getColor());
            CreateNode1();//产生随机点
           // CreateNode2();
            repaint();
            //当长度超过10的时候,产生鼓掌声
            if(Length==10) {
            	new  applauseMusic();
            }
        }
    

    当贪吃蛇 撞到墙,撞到自己时,游戏结束,转入到重启界面。

     //当碰到蛇身自己时
            for(int x = 0; x < Length; x++) { 
                if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) {
                	startFlag=false;
                	new  DeadMusic();
                	 new  Restart();   
                	 christmas.stop ();
                }
            }
             //当贪吃蛇撞到边界
            if(FirstX < 1  || FirstX >29  || FirstY < 1 || FirstY >18) {        	
            	startFlag=false;
            	new  DeadMusic();
            	new  Restart();
            	 christmas.stop ();
            //	new  Test();
            } 
    

    贪吃蛇不断移动的定义方式 ,从前向后的移动是蛇身的遍历,也就是在方向和时间监听器的控制下,贪吃蛇从前向后移动,实现方式就是从蛇头向蛇尾从前向后向前赋值,逐次遍历。

    //定义循环,使得贪吃蛇从前向后移动
            for(int x = Length - 1; x > 0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
            }
            snake.get(0).setX(FirstX);
            snake.get(0).setY(FirstY);
            repaint();
        }    
    

    定义一个Move()方法,来实现在贪吃蛇移动过程中这些问题处理。
    有一个自有的固定的DIRECTION,之后随着我们的操控Direction也不断发生改变,借此来改变它自身不断移动的方向。用代码来体现,就是在成员变量处定义一个Direction,我们将其初始化为1,这样在Timer的事件触发后,Move()的参数为1,就会不断的向上移动。在键盘的监听事件中,将direction的值赋值给Direction,那么随着我们上下左右的控制,Direction的值也不断发生改变,贪吃蛇的自身移动方向就会发生变化。 贪吃蛇在移动的过程中会发生的问题有:撞到随机点,撞到墙,撞到自己,调用相应的方法即可。

       /*定义蛇移动的方法  
        *   贪吃蛇的移动方法主要包括方向控制,碰到随机点,碰到自己,碰到边界以及设计贪吃蛇从前向后的移动
        * 
        */
          public void Move(int direction) {                 	 
            int FirstX = snake.get(0).getX();            //获取蛇第一个点的横坐标
            int FirstY = snake.get(0).getY();            //获取蛇第一个点的纵坐标                  
            if(!startFlag) 
                return ; 
            //方向控制
            switch(direction) {
                case 1:
                    FirstY--;
                    break;
                case -1:
                    FirstY++;
                    break;
                case 2: 
                    FirstX--;
                    break;
                case -2:
                    FirstX++;
                    break;
                default:
                    break;
            }
          //当碰到随机点时
            if(FirstX == newNode.getX()&&FirstY == newNode.getY()) { 
            	new  eatFoodMusic();
                getNode();
                return;
            }
          //当碰到蛇身自己时
            for(int x = 0; x < Length; x++) { 
                if((FirstX==snake.get(x).getX())&&(FirstY == snake.get(x).getY())) {
                	startFlag=false;
                	new  DeadMusic();
                	 new  Restart();   
                	 christmas.stop ();
                }
            }
             //当贪吃蛇撞到边界
            if(FirstX < 1  || FirstX >29  || FirstY < 1 || FirstY >18) {        	
            	startFlag=false;
            	new  DeadMusic();
            	new  Restart();
            	 christmas.stop ();
            //	new  Test();
            } 
            //定义循环,使得贪吃蛇从前向后移动
            for(int x = Length - 1; x > 0; x--) {
                snake.get(x).setX(snake.get(x-1).getX());
                snake.get(x).setY(snake.get(x-1).getY());
            }
            snake.get(0).setX(FirstX);
            snake.get(0).setY(FirstY);
            repaint();
        }  
    

    那么,上面的分析解决了贪吃蛇移动中的方向问题,那么,如何解决速度问题呢·?也就是说,当采用时间监听器来让贪吃蛇每隔一定的时间来移动,如何如何实现呢?
    查阅API,我们发现了一个TIMER类。API中的描述是:在指定时间间隔触发一个或多个ActionEvent,一个实例用法就是动画对象,它将Timer用作绘制其帧 的触发器。Timer的构造方法是Timer(int delay, ActionListner listener)通俗的说就是创建一个每 delay秒触发一次动作的计时器,每隔特定的时间就会触发特定的事件。可以使用start方法启动计时器。这个Timer类可以完全满足我们的需要。我们只要定义一个Timer类,设置好间隔时间与触发事件就可以了。这里要注意,我们要定义的触发事件是蛇自身的移动。

        //蛇的移动控制,利用线程来实现用鼠标控制,利用计时器来实现用键盘控制。 
          Timer time = new Timer(1000, new ThingsListener1());//定义一个定时器对象,这里我们还要创建一个ThingsListener事件
    

    其中,1000是1000ms的意思,也就是贪吃蛇每隔一秒收到时间监听,判断是否移动。new ThingsListener1()是定义的一个内部类。

    	//定义内部类,贪吃蛇不断移动	
    	public class ThingsListener1 implements ActionListener {
    	    public void actionPerformed(ActionEvent e) {
    	        Move(direction);
    	        }
    	    }// 
    

    实现移动的方式就是在构造方法中加入Time.start();
    (二)多线程控制速度和鼠标控制方向的移动方式.
    采用多线程和鼠标控制主要原因在于时间的控制,也就是如何实现贪吃蛇移动速度的可控,Timer()我尝试了很多次没有成功,最后采用多线程的方法实现了速度的可控,对于贪吃蛇速度的控制是利用时间监听器来实现。
    鼠标控制方向和键盘控制方向的原理基本上相同,不同的在于前者需要对鼠标进行监听,后者需要对于鼠标监听器时间监听,实现的Move()方法是一样的,主要差别在于监听器方式的设计。
    在设计中,将贪吃蛇的移动区域按钮对角线划分为上下左右四个部分,如下图:
    在这里插入图片描述
    那么,鼠标控制的监听在于监听鼠标在窗体中的位置,点击鼠标返回鼠标在窗体中的坐标,在监听鼠标事件的时候需要考虑点击鼠标的坐标在上下左右哪个区域,然后将方向值direction改掉,并定义一个标签,在窗体的左上角,用来说明点击鼠标是上下左右那个方向,源代码如下:

            //采用 鼠标控制的控制模式     通过监听鼠标在容器中的位置,点击上下左右区域,改变direction的值,即可实现贪吃蛇的移动,
              this.addMouseListener(new MouseAdapter(){  //匿名内部类,鼠标事件
                  public void  mousePressed(MouseEvent e){ 
                	  int a=0;//鼠标完成点击事件
                         //e.getButton就会返回点鼠标的那个键,左键还是右健,3代表右键
                           mousex = e.getX();  //得到鼠标x坐标
                           mousey = e.getY();  //得到鼠标y坐标
                           double  k=0.6;            //直线斜率
                           double  Y1=0.6*mousex;
                           double  Y2=-0.6*mousex+810;
                           double   X1=1.6*mousey;
                           double   X2=-1.6*mousey+1350;
                           if(mousex > X1&&mousex<X2&&mousey>0&&mousey<405) {   //第一象限  		向上
                        	   label4.setText( "向上" );
                           	a=1;   	 
                           }
                           if(mousex>X2&&mousex<X1&&mousey>405&&mousey<810) {  // 第二象限             向下
                        	   label4.setText( "  向下" );
                           	 a=2;
                               }
                     if(mousex>0&&mousex<675&&mousey>Y1&&mousey<Y2) {    //第三象限     向左
                        	   label4.setText( " 向左" );
                           	 a=3;
                                }   
                           if(mousex>675&&mousex<1350&&mousey>Y2&&mousey<Y1) {   //第四象限    向右
                        	   label4.setText( "  向右" );
                               a=4;
                               } 
                    //将不同的a值定义为不同的direction值,
                         switch( a) {
                          case  1://按下向上,返回1
                              direction = 1;
                              break;
                          case 2://按下向下,返回-1
                              direction = -1;
                              break;
                          case 3://按下相左,返回2
                              direction = 2;
                              break;
                          case 4://按下向右,返回-2
                              direction = -2;
                              break;
                          default:
                              break;
                      }
                          
                          if(direction + Direction !=0) {//不能反向运动
                              Direction = direction;
                              Move(direction);
                                repaint();
                      }
                    }
                   
              });         
          }
    

    贪吃蛇移动速度的控制是利用多线程来实现的。 在构造方法中加入tr.start();即可实现贪吃蛇移动的控制,

      //蛇的移动控制,利用线程来实现用鼠标控制,利用计时器来实现用键盘控制。
          Thread  tr= new Thread(new ThingsListener());
    
    

    new ThingsListener()是新定义的一个内部类,用来实现速度的可控,

    /*
    	 * 当startflag为真的时候,贪吃蛇在线程时间的脉冲下继续移动,这个过程包含在if语句块中,当程序启动时,每隔1.2s就有一个响应, 
    	 *上一个方法采用Timer, Timer的构造方法是Timer(int delay, ActionListner listener)通俗的说就是创建一个每 delay秒触发一次动作的计时器,
    	 * 每隔特定的时间就会触发特定的事件。可以使用start方法启动计时器。
    	 * 优点在于形式简单,缺点在于当采用速度控制的时候不易控制,而同样作为时间触发作用的线程控制可以实现这个目的,即通过控制时间来控制贪吃蛇的移动速度
    	 * 之所以之前的设计有错误,在于while后面没有用if进行startflag的检验,即startflag只有在真的条件下才可以移动,时间脉冲触发下才可以移动	 
    	*/
    	//定义线程类,使得贪吃蛇能够在线程的控制下不断移动
        class ThingsListener  implements  Runnable   {
    		@Override
    		public void run() {
    			// TODO 自动生成的方法存根
    			while( true) {
    				if(startFlag) {      
    				Move(Direction);
    				repaint();
    				}
    				try {					
    					Thread.sleep(1200/Difficult_Degree);
    				}catch(InterruptedException  e){
    					e.printStackTrace();
    				}
    			}	 		
    		}//设置一个监听器事件,用来控制蛇的不断移动       
        }
    

    Difficult_Degree是速度控制的实现,在不同的按钮的监听器作用下输入不同的值,就可以到达速度控制的目的。

    //按速度一键
            if(e.getSource() == FspeedButton) {
            	new speedButtonMusic ();
            	  Difficult_Degree= 2;                      	
         }//按速度二键
            if(e.getSource() == SspeedButton) {
            	new speedButtonMusic ();
                 
            	 Difficult_Degree=  3;        	
         }//按速度三键
            if(e.getSource() == TspeedButton) {
            	new speedButtonMusic ();
            	 Difficult_Degree= 4;        	        	
         }//按速度四键
            if(e.getSource() == THspeedButton) {
            	new  speedButtonMusic ();
            	 Difficult_Degree= 5;      	
         }
    

    这样的话Timer()类控制时间键盘控制方向和多线程类控制时间鼠标控制速度两种移动方式说完了,那么,如何实现在窗体中两种方式的可选择呢?我想可以定义两个按钮,然后在按钮的监听器事件分别加入键盘监听和鼠标监听的部分,即可实现移动方式的可控。

    展开全文
  • 本软件是针对超级玛丽小游戏的JAVA程序,进入游戏后首先按空格键开始,利用方向键控制的马里奥的移动,同时检测马里奥与场景中的障碍物和敌人的碰撞,并判断马里奥的可移动性和马里奥的生命值。当马里奥通过最后一...

    项目展示:

    在这里插入图片描述

    请添加图片描述
    在这里插入图片描述

    本软件是针对超级玛丽小游戏的JAVA程序,进入游戏后首先按空格键开始,利用方向键来控制的马里奥的移动,同时检测马里奥与场景中的障碍物和敌人的碰撞,并判断马里奥的可移动性和马里奥的生命值。当马里奥通过最后一个场景后游戏结束。

    系统模块设计:

    窗体类

    该类主要用于存放游戏的场景以及其他各类,并且实现KeyListener接口,用于从键盘的按键中读取信息。该类中的一些属性主要包括了用于存放所有场景的list集合 allBG,马里奥类 mario,当前的场景 nowBG以及其他一些游戏中需要的标记等。而且在该类中,运用双缓存的技术使得游戏的流畅度更高,解决了游戏中出现的闪屏问题。
    将该类的名字定义为MyFrame,并且要在该类中实现KeyListener接口和Runnable接口。然后首先要在该类中定义一个List集合,集合的泛型为背景类BackGround,集合的名字定义为allBG,用于存放所有的背景。接着定义一个Mario类属性,名字为mario,这个就是游戏运行时候的所需要的mario。接下来还要在类中定义一个BackGround属性,nowBG,默认值应当为空,会在构造方法中赋予该属性初值,这个属性主要是用来存放当前游戏运行时马里奥所处的游戏场景。另外该类中还应该有一个Thread类属性t,这个属性主要是为了在游戏运行的时候控制游戏的线程。然后就可以在类中定义main()方法,将该类实现就可以了。值得一提的是该类的构造方法相对来说是比较复杂的。
    在该类的构造方法中,应当首先绘制窗体类的标题,以及窗体类的大小,并且要对窗体类在初始化的时候的位置,也就是在屏幕中显示的位置,最好是显示的时候居中,这样的话在游戏运行时会比较美观一些。其次还要对窗体的一个是否可拉升属性进行一下设置,这个设置的主要目的是因为游戏的界面都是开发者经过深思熟虑考虑出来的比较美观的界面,玩家随意改变游戏的窗口大小可能会对游戏的体验造成影响,所以在这里应该设置游戏的窗体默认不可以被拉伸。

    public MyFrame(){
    		this.setTitle("玛丽奥");
    		this.setSize(900, 600);
               //这里是为了获得电脑屏幕的整体大小,以便于下面确定窗体的位置
    		int width = Toolkit.getDefaultToolkit().getScreenSize().width;
    		int height = Toolkit.getDefaultToolkit().getScreenSize().height;
    		this.setLocation((width-900)/2, (height-600)/2);
           //设置窗体默认不可以被拉伸
    		this.setResizable(false);
    		//初始化图片
    		StaticValue.init();
    

    当这些都设置好以后,接下来就应当在构造方法中绘制了,当然最先应当将游戏的场景绘制到窗体类中,然后在窗体类中还应当绘制马里奥类,这是游戏中必不可少的。当然在绘制场景类的时候因为不知一个场景,所以可以使用循环,将所有的场景全部绘制。然后在将所需要的所有监视设置好以后就可以开启该类的线程了。

    //使用循环创建全部场景
    		for(int i=1;i<=7;i++){
    			this.allBG.add(new BackGround(i, i==7?true:false));
    		}
    		//将第一个场景设置为当前场景
    		this.nowBG = this.allBG.get(0);
    		//初始化玛丽奥
    		this.mario = new Mario(0, 480);
    		//将玛丽奥放入场景中
    		this.mario.setBg(nowBG);
    		this.repaint();
    		this.addKeyListener(this);
    		this.t = new Thread(this);
    		t.start();
          //使窗口在关闭的时候,程序也同时停止。
    		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		this.setVisible(true);
    	}
    

    在这些最基本的东西设置完以后,还需要一个方法来解决游戏中经常会出现的闪屏问题。这个方法就是双缓存方法,现在类中定义一个BufferedImage的图片,然后从该图片中获取到图片的Graphics g2,然后利用画笔g2将所要绘制的东西绘制到这个空的图片中,然后在利用窗体类中的paint方法中的画笔g将这个已经绘制好的图片绘制到窗体类中,这样利用空白图片作为程序运行中的中转,就可以很好的解决游戏运行过程中出现的闪屏问题。

    public void paint(Graphics g) {
    		//先定义一个图片,然后利用双缓存解决闪屏问题
    		BufferedImage image = new BufferedImage(900, 600, BufferedImage.TYPE_3BYTE_BGR);
    		Graphics g2 = image.getGraphics();
          //利用上面图片中得到的画笔g2,将所需绘制到图片中
    		if(this.isStart){
    			//绘制背景
    			g2.drawImage(this.nowBG.getBgImage(), 0, 0, this);
    			//绘制生命
    			g2.drawString("生命:    "+this.mario.getLife(), 800, 50);
    			//绘制怪物敌人
    			Iterator<Enemy> iterEnemy = this.nowBG.getAllEnemy().iterator();
    			while(iterEnemy.hasNext()){
    				Enemy e = iterEnemy.next();
    				g2.drawImage(e.getShowImage(), e.getX(), e.getY(), this);
    			}
    		//把缓存图片绘制进去
    		g.drawImage(image, 0, 0, this);
    	}
    

    当然游戏的宗旨是让玩家和电脑之间的互动,那么就又涉及到一个问题,就是玩家对游戏中的马里奥的控制。我们前面已经说过了该类中必须要实现KeyListener接口,这个接口的作用就是使该类中实现一些方法,以便于达到玩家在游戏进行时可以对游戏中的马里奥进行控制。我们这里拟定对于马里奥的控制可以使用我们常见的四个方向键,即我们说的上下左右。并且通过控制台打印,可以知道上对应的是38,右对应的是39,左对应的是37。并且游戏的设定是开始后游戏不会直接运行,而是要使用空格键以后游戏才会真正开始,所以还要加入当按空格键的时候游戏正式开始,空格键对应的是32。

    public void keyPressed(KeyEvent e) {
    		if(this.isStart){
    			//玛丽奥的移动控制
    			if(e.getKeyCode()==39){
    				this.mario.rightMove();
    			}
    			if(e.getKeyCode()==37){
    				this.mario.leftMove();
    			}
    			//跳跃控制
    			if(e.getKeyCode()==38){
    				this.mario.jump();
    			}
    		}else if(e.getKeyCode()==32){
    			this.isStart = true;
    		}
    	}
    

    对于按键,那么相对应的就是当抬起建的时候。因为你向右移动的时候,如果这时候突然停止,那么很可能马里奥会保持一个运动的状态停下来,那么就必须在玛丽奥停止的时候给他一个指令,让他的移动图片变为静止。相对于运动的时候是类似的,这里不做累述。

    public void keyReleased(KeyEvent e) {
    		if(this.isStart){
    			//控制玛丽奥的停止
    			if(e.getKeyCode()==39){
    				this.mario.rightStop();;
    			}
    			if(e.getKeyCode()==37){
    				this.mario.leftStop();;
    			}
    		}
    

    当这一切都做好以后,那么最后就应该在类中重写一下run方法了,在这个方法中应当提一下游戏的通关和死亡后的状态。即游戏通关,或者马里奥死亡时应当弹出一个窗口,说明游戏通关或者马里奥死亡,并且点击了这个窗口以后,游戏应当结束,而且整个游戏也应当关闭。

    if(this.mario.isDead()){
    		JOptionPane.showMessageDialog(this, "游戏结束");
    		System.exit(0);
    	}
    	if(this.mario.isClear()){
    		JOptionPane.showMessageDialog(this, "恭喜游戏通关!");
    		System.exit(0);
    		}
    

    初始化类

    用于存放游戏所需要的所有静态文件,在游戏开始的时候将所有文件导入,提高游戏的运行速度。并且在该类中将所有需要用到的图片进行分类,分为障碍物类,马里奥类,敌人类以及背景图片。当游戏运行时可以直接调用这些集合中的图片进行遍历,在调用的时候更加方便,而且可以使马里奥或者敌人在移动的时候产生动态效果。
    首先在类中应当定义一个静态的List,泛型为BufferedImage,属性名字为allMarioImage,这个属性的作用在于存放所有的马里奥图片,里面包括了马里奥的移动图片,站立图片以及马里奥跳跃的图片。这样在程序运行的时候就可以从该类中的这个属性里面将所需要的马里奥图片直接调用出来,并且还可以在马里奥移动时不断遍历里面的图片,这样就可以使马里奥产生移动的动态效果。接下来要在该类中定义开始图片,结束图片以及背景图片,默认的初始值都为null。注意这些所有的属性都是静态的,包括下面要提到的所有的属性,这样做的目的是为了在程序运行时先加载这些图片。然后应当定义存放食人花的List集合allFlowerImage,这个集合将食人花的不同形态,张嘴、闭嘴图片存放进去,这样在运行的时候进行遍历就可以打到动态效果。同理存放蘑菇怪的集合allTrangleImage,以及存放所有障碍物的集合allObstructionImage。

    public class StaticValue {
    	public static List<BufferedImage> allMarioImage = new ArrayList<BufferedImage>();
    	public static BufferedImage startImage = null;
    	public static BufferedImage endImage = null;
    	public static BufferedImage bgImage = null;
    	public static List<BufferedImage> allFlowerImage = new ArrayList<BufferedImage>();
    	public static List<BufferedImage> allTriangleImage = new ArrayList<BufferedImage>();
    	public static List<BufferedImage> allObstructionImage = new ArrayList<BufferedImage>();
    定义完这些属性之后,剩下的就是初始化了,在该类中定义一个init()方法,这个方法在执行的时候会将所需的所有图片放入到之前定义好的各个集合中。因为图片存放的路径都是一样的,所以为了减少代码量会定义一个公共路径ImagePath。然后就可以利用循环,将存放的图片全部导入进去。
       //介绍代码量,定义公共路径
    	public static String ImagePath = System.getProperty("user.dir")+"/bin/";
    	//定义方法init(),将图片初始化
    	public static void init(){
    		//利用循环将玛丽奥图片初始化
    		for(int i=1;i<=10;i++){
    			try {
    				allMarioImage.add(ImageIO.read(new File(ImagePath+i+".png")));
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		//导入背景图片
    		try {
    			startImage = ImageIO.read(new File(ImagePath+"start.jpg"));
    			bgImage = ImageIO.read(new File(ImagePath+"firststage.jpg"));
    			endImage = ImageIO.read(new File(ImagePath+"firststageend.jpg"));
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		//导入玛丽奥死亡图片
    		try {
    			mariDeadImage = ImageIO.read(new File(ImagePath+"over.png"));
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    

    背景类

    该类表示马里奥及障碍物和敌人所处的场景,并且将障碍物和敌人绘制到场景中。在该类中包括用于存放敌人和障碍物的list集合,以及当敌人或者障碍物被消灭后用于存放已经消失的敌人和障碍物的集合,这样做是为了在马里奥死亡时重置场景所用的。其次在该类中还使用了控制敌人移动的方法,是为了在程序之初控制敌人静止,然后在玩家点击空格以后在使得敌人开始移动。并且在第六个关卡处设置了一个隐形通关要点,只有当马里奥顶到这个隐形砖块时才会出现,马里奥就可以借助这个砖块通过关卡。
    首先背景类中肯定要有一个标记来表示现在是第几个场景,因为不同的背景中所绘制的场景,障碍物等也不同,所以该类中要有一个int类型的场景顺序sort。并且在游戏的设定中,如果玩家玩到最有一关的时候马里奥会失去玩家的控制,自己走向城堡。那么这里就要这几一个标记,是否为最后的场景,类型为boolean类型。如果马里奥失去所有生命值,或者游戏通关的话,那么游戏就会结束,这里还应当加一个boolean的标记isOver判断游戏是否结束。

    public class BackGround {
    	//当前场景图片
    	private BufferedImage bgImage = null;
    	//场景顺序
    	private int sort;
    	//是否为最后的场景
    	private boolean flag;
    	//游戏结束标记
    	private boolean isOver = false;
    

    在最后一个关卡中,马里奥到达旗杆的位置后就会失去控制,同时旗子将会开始下降,只有等旗子下降完毕后,马里奥才能开始移动,所以这里还要定义一个旗子是否下降完毕的boolean类型的属性isDown,用于判断马里奥什么时候移动。

    //定义降旗结束
    	private boolean isDown = false;
    

    当马里奥失去生命的时候,但是并没有失去所有的生命,那么这个时候应当重置这个场景,将所有消灭掉的障碍物和敌人全部还原。因此除了在该类中除了要定义存放敌人和障碍物的List集合以外,还应当有存放被消灭的敌人或者障碍物的List,当敌人或者障碍物被消灭的时候先放入到这个List中,这样在充值的时候就可以直接将这个集合中的数据在还原到原先的集合里面。

    //用集合保存敌人
    	private List<Enemy> allEnemy = new ArrayList<Enemy>();
    	//用集合保存障碍物
    	private List<Obstruction> allObstruction = new ArrayList<Obstruction>();
    	//被消灭的敌人
    	private List<Enemy> removeEnemy = new ArrayList<Enemy>();
    	//被消灭的障碍物
    	private List<Obstruction> removeObstruction = new ArrayList<Obstruction>();
    

    在游戏的设定中,应当是游戏开始的时候,所有的敌人其实是静止的,而且玩家也不能控制马里奥,必须要等到玩家按空格键开始以后游戏才会进行,那么这里就应当在定义一个方法,即当玩家空格键的时候会调用这个方法,同时游戏中的敌人开始移动,游戏正式开始。这个方法也就是相当于控制敌人开始移动的方法,所以命名为enemyStartMove()方法。

    //敌人开始移动
    	public void enemyStartMove(){
           //遍历当前场景中的敌人,使之开始移动
    		for(int i=0;i<this.allEnemy.size();i++){
    			this.allEnemy.get(i).startMove();
    		}
    	}
    

    接下来就应当定义背景类的构造方法了,通过获取场景的顺序,即场景的sort,来判断是哪一个场景,同时将场景绘制好。

    //构造方法
    	public BackGround(int sort,boolean flag){
    		//第一个场景
    		if(sort==1){
    			for(int i=0;i<15;i++){
    				this.allObstruction.add(new Obstruction(i*60, 540, 9,this));
    			}
    			//绘制砖块和问号
    			this.allObstruction.add(new Obstruction(120, 360, 4,this));
    			this.allObstruction.add(new Obstruction(300, 360, 0,this));
    			......
    	}
    

    前面提到,如果马里奥死亡,但是却没有失去所有的生命值,那么游戏应当重置,当前场景中的所有敌人和障碍物,也包括马里奥都应当回到初始位置。为了达到这个效果,那么我们的场景类中就必须要定义一个reset()方法,来调用障碍物和场景还有马里奥的各自的重置方法,来使当前的场景还原。并且在这之前我们还要将消灭掉的敌人和障碍物从消灭掉的存放的List中提出来,放回到原来的List中。然后遍历障碍物和敌人的List,使用循环调用他们的重置方法。

    //重置方法,重置障碍物和敌人
    	public void reset(){
    		//将移除的障碍物和敌人还原
    		this.allEnemy.addAll(this.removeEnemy);
    		this.allObstruction.addAll(this.removeObstruction);
    		//调用障碍物和敌人的重置方法
    		for(int i=0;i<this.allEnemy.size();i++){
    			this.allEnemy.get(i).reset();
    		}
    		for(int i=0;i<this.allObstruction.size();i++){
    			this.allObstruction.get(i).reset();
    		}
    	}	
    

    马里奥类

    用来控制马里奥的行动,并且在该类中加入碰撞检测,判断马里奥是否与障碍物或者敌人发生碰撞。该类中的属性主要定义了马里奥所在的场景,马里奥的移动和跳跃的速度,以及马里奥在移动过程中需要显示的图片。另外该类中还定义了玩家的生命值和所获得的分数。并且在run()方法中还定义了当马里奥到达最后一关的旗子时,玩家将失去对马里奥的控制,剩下的由程序控制走到城堡,完整全部游戏。
    在游戏中,玛丽奥要在玩家的控制下完成移动、跳跃等动作,那么这些动作首先肯定要涉及到坐标,那么我们在该类中首先要定义两个属性,这两个属性即为马里奥的坐标x和y。并且该类还要实现Runnable接口,在run()方法中写马里奥的移动规则。

    public class Mario implements Runnable{
    	//坐标
    	private int x;
    	private int y;
    	//定义玛丽奥所在场景
    	private BackGround bg;
    	//加入线程
    	private Thread t = null;
    

    为了玩家在游戏过程中的良好体验,那么对于马里奥的移动速度和跳跃速度就必须要定义好。所以该类里面还应当定义马里奥的移动速度和跳跃速度,其本质就是马里奥在移动过程中坐标加减的值。当然初始值为零,必须等到马里奥构造的时候,再将这些属性赋予相对应的值。在本类中还要定义游戏的分数以及马里奥的生命数,这些都是必不可少的。

    //移动速度
    	private int xmove = 0;
    	//跳跃速度
    	private int ymove = 0;
    	//状态
    	private String status;
    	//显示图片
    	private BufferedImage showImage;
    	//生命和分数
    	private int score;
    	private int life;
    

    在马里奥这个类中,还要定义马里奥的移动和跳跃方法,以便玩家在按下方向键后调用这些方法,来达到控制马里奥的移动。下面是马里奥向左移动的方法,其他方法同理。

    	public void leftMove(){
    		//移动速度
    		xmove = -5;
    		//改变状态
    		//如果当前已经是跳跃,应该保持原有状态,不能再改变
    		if(this.status.indexOf("jumping") != -1){
    			this.status = "left-jumping";
    		}else{
    			this.status = "left-moving";
    		}
    	}
     ......
    

    在定义马里奥的跳跃方法的时候,不单单定义一个方法就行,而且还要判断马里奥的状态。如果马里奥是在地面或者是在障碍物的上方,那么马里奥可以进行跳跃,如果马里奥处于空中,那么马里奥就不可以继续跳跃。

    public void jump(){
    //判断马里奥是否可以进行跳跃
    		if(this.status.indexOf("jumping") == -1){
    			if(this.status.indexOf("left") != -1){
    				this.status = "left-jumping";
    			}else{
    				this.status = "right-jumping";
    			}
    			ymove = -5;
    			upTime = 36;
    		}
    	}
    

    接下来就要写马里奥中的run()方法了,这个方法中的内容相对来说比较麻烦,因为要在这个方法中对马里奥和障碍物或者敌人之间进行逻辑判断,即所谓的碰撞检测。首先在这个类中对马里奥是否处于最后一个场景进行判断,如果马里奥处于最后一个场景,并且坐标大于520,那么说明马里奥已经撞到的旗杆,这个时候马里奥将不会由玩家控制。并且同时调用旗子的移动方法,使旗子进行下落,当旗子下落完毕后给马里奥一个标记,马里奥开始移动到城堡。当马里奥的坐标大于780,即马里奥到达城堡的门口的时候,这个时候游戏结束。

    public void run() {
    		while(true){
    			//判断是否与障碍物碰撞
    			//定义标记
    			if(this.bg.isFlag() && this.x >= 520){
    				this.bg.setOver(true);
    				if(this.bg.isDown()){
    					//降旗后玛丽奥开始移
    					this.status = "right-moving";
    					if(this.x < 580){
    						//向右
    						this.x += 5;
    					}
    						if(this.x >= 780){
    							//游戏结束
    							this.setClear(true);
    						}
    

    然后对当前马里奥所处的场景中的所有障碍物进行遍历,获取到所有障碍物的坐标,通过障碍物的坐标和马里奥的坐标的之间的关系的判断,来决定马里奥是否与障碍物发生了碰撞,并且通过判断的结果来对马里奥和障碍物的状态进行相应的变化。

    for(int i=0;i<this.bg.getAllObstruction().size();i++){
    		Obstruction ob = this.bg.getAllObstruction().get(i);
    		//不能向右移动
    	if(ob.getX()==this.x+60&&(ob.getY()+50>this.y&&ob.getY()-50<this.y)){
    			if(ob.getType() != 3){
    				canRight = false;
    			}
    		}
    		......
    

    当马里奥撞到障碍物的时候,那么就要根据障碍物的类型进行接下来的判断,如果是砖块或者是问号的话,那么障碍物消失,马里奥被弹回,即马里奥的状态由上升状态变为下落状态,并且将消失掉的障碍物放入相对应的消失的List集合当中。如果障碍物的类型为其他,比如说是石头的话,那么障碍物不变,马里奥直接被弹回。

    //判断玛丽奥跳跃时是否撞到障碍物
    	if(ob.getY()==this.y-60&&(ob.getX()+50>this.x && ob.getX()-50<this.x)){
    		//如果是砖块
    		if(ob.getType()==0){
    			//移除砖块
    			this.bg.getAllObstruction().remove(ob);
    			//保存到移除的障碍物中
    			this.bg.getRemoveObstruction().add(ob);
    		}
    

    为了游戏的可玩性,将会在游戏中加入一个隐藏的陷阱,或者是隐藏的通关点。这个隐藏的障碍物在游戏进行的时候不会显示出来,当然马里奥从他的左右两边过去的时候也不会触发这个隐藏的障碍物,必须是从下方撞到这个障碍物时才会显示出来。同时马里奥由上升状态变为下落状态。而且他和砖块障碍物相同,被顶到后会变为石头,改变类型。

    //如果是问号||隐藏的砖块
    		if((ob.getType()==4 || ob.getType()==3) && upTime > 0){
    			ob.setType(2);
    			ob.setImage();
    		}
          //马里奥开始下落
    		upTime = 0;
    

    在游戏中敌人大致可以分为两类。一类是蘑菇怪,这种敌人是可以被杀死的,当马里奥从蘑菇怪的正上方踩到蘑菇怪时,那么蘑菇怪就会被消灭,同时马里奥向上跳起一小段距离。而消失掉的蘑菇怪就会被放到消失掉的敌人的List集合中,等到重置的时候在调用出来。但是如果马里奥从蘑菇怪的左右两边碰到蘑菇怪的话就会失去一条生命,并且重置游戏。第二类是食人花,这种敌人不会被马里奥消灭掉,不论马里奥从哪个方向去碰撞食人花,食人花都不会消失,而且如果马里奥碰到了食人花,自身还会失去一条生命,并且游戏重置,当然前提是马里奥没有失去所有的生命值,否则的话游戏就结束。
    首先马里奥对于所有的敌人,如果从左右两边碰撞到敌人,那么马里奥死亡,失去一条生命,游戏重置。

    ```java
    //对敌人的判断
    	for(int i=0;i<this.bg.getAllEnemy().size();i++){
    		Enemy e = this.bg.getAllEnemy().get(i);
          //对于所有的敌人都适用
    		if((e.getX()+50>this.x && e.getX()-50<this.x) && (e.getY()+60>this.y && e.getY()-60<this.y)){
    			//玛丽奥死亡
    			this.dead();
    		}
    
          //这里开始区分敌人的类别,对于不同的敌人做出不同的反应
    		if(e.getY()==this.y+60 && (e.getX()+60>this.x && e.getX()-60<this.x)){
    			if(e.getType() == 1){
    				e.dead();
    				this.upTime = 10;
    				this.ymove = -5;
    			}else if(e.getType() == 2){
    				this.dead();
    			}
    		}					
    

    障碍物类

    绘制场景中所需要的障碍物,例如地面、砖块、水管等等。该类中的属性包括了障碍物的坐标,障碍物所需要显示的图片等。并且在该类中也定义了障碍物类的重置方法,当马里奥死亡时,场景类会调用该方法。
    游戏中的场景是由背景中的障碍物绘制而成的,不同的障碍物所在的位置肯定也不相同,那么对于障碍物而言,就必须要有坐标属性来使绘制的时候将不同的障碍物绘制到不同的位置,所以必须要有两个int属性x和y来表示障碍物的坐标。同时该类也必须要实现Runnable接口,实现这个接口的作用主要是为了在最有一个场景中控制旗子的运动,当然同时还要为该类加入线程。

    public class Obstruction implements Runnable{
    	//坐标
    	private int x;
    	private int y;
    	//控制旗子
    	private Thread t = new Thread(this);
    

    前面说过,当马里奥顶到问好或者是隐藏的砖块时,那么这个障碍物的类型就会改变,变为石头。那么在障碍物这个类里面就必须要定义一个属性stype,这个属性用于表示当前障碍物的类型,以便于变化形态的时候调用。这个类型的值就可以用初始化类中的相对应的List集合里面的下标表示。既然有改变,就要有恢复,所以还要定义一个不变的type,命名为starttype,这个属性是为了当游戏重置的时候,障碍物可以通过调用这个属性恢复到最初始的状态。而且不同的状态对应不同的显示图片,所以还要有showImage属性。

    //类型
    	private int type;
    	//初始类型
    	private int starttype;
    	//显示图片
    	private BufferedImage showImage = null;
    	//取得场景
    	private BackGround bg;
    

    在该类中还要写入reset()方法,这个方法是为了当马里奥死的时候调用重置方法,对已经被消灭掉的障碍物进行重置。因为有的障碍物被顶掉以后会给变类型和图片,所有还要定义一个setImage()方法,用来改变障碍物的显示图片。

    //重置方法
    public void reset(){
    	this.type = starttype;
    	this.setImage();
    }
    //根据状态改变显示图片
    public void setImage(){
    	showImage = StaticValue.allObstructionImage.get(type);
    }
    

    最后该类中的run方法主要是为了控制最后一个场景中的旗子的移动,并且在旗子移动完毕后要设置一个标记,并且将该标记表示给马里奥类,这样马里奥就可以开始自主移动了。

    if(this.bg.isOver()){
    				if(this.y < 420){
    					this.y += 5;
    				}else{
                      //设计标记为true,即表示马里奥可以开始移动了
    					this.bg.setDown(true);
    				}
    

    敌人类

    该类中主要设置了两种敌人,一种是蘑菇怪,可以被马里奥踩死,另一种是食人花,不能被踩死。该类中的属性包括了敌人的坐标,敌人的初始坐标,需要显示的图片,以及敌人的移动方向和移动范围等。敌人的初始坐标主要是为了当敌人执行重置方法后将敌人的位置还原。
    在该类中首先要实现Runnable接口,因为在游戏中的敌人是可以移动的,所以一定要通过重写run()方法来达到敌人可以移动的效果。当然还要在该类中定义一个Thread属性,用于控制线程。然后说说到移动,必然少不了坐标问题,那么在该类中就要定义两个int属性x和y,用于控制敌人的位置以及敌人的移动。

    public class Enemy implements Runnable{
    	//坐标
    	private int x;
    	private int y;
       //加入线程
    	private Thread t = null;
    

    当马里奥失去一条生命值的时候,游戏会被重置,敌人回回到初始的位置,所以还要定义另外两个int属性startx和starty,用来当游戏进行重置的时候,可以根据这个初始坐标回复敌人的位置。

    //初始坐标
    	private int startx;
    	private int starty;
    

    对于不同的敌人,所显示的图片肯定是不同的,所以要定义一个现实的图片属性showImage,并且在马里奥中,马里奥要通过判断敌人的类型,来决定是马里奥死亡,还是敌人死亡,对于不同的敌人有不同的反应,所以还要在该类中定义一个type属性,用来表示敌人的类型。

    //怪物类型
    private int type;
    //显示图片
    private BufferedImage showImage;
    

    对于敌人里面的食人花而言,他是在水管中直上直下的,所以他的上下移动应当有一个界限,不论是向上移动还是向下移动,都不能超过这个界限,否则的话食人花就会从水管中飞出来或者是移动到MyFrame外面了。

    //移动范围
    	private int upMax = 0;
    	private int downMax = 0;
    

    在这个类中应当有两个构造方法,对于不同的敌人,所需要的属性都是不同的。并且在两个类中都有一个共同的代码,那就是要在开启线程后应当先将线程挂起。这是为了配合游戏在开始的时候敌人不移动,必须要等到玩家按空格键的时候才会开始,所以先将线程挂起来,当点击空格键以后在将线程开启。

    //蘑菇怪的构造方法
    	public Enemy(int x,int y,boolean isLeft,int type,BackGround bg){
    		... ...
    		this.t = new Thread(this);
    		t.start();
    		t.suspend();
    	}
    

    接下来是写敌人类中的run()方法了,该方法主要是为了控制蘑菇怪以及食人花敌人的移动的。因为不同的敌人在不同的场景中有不同的移动方法,所以对于敌人的移动而且,首先要判断敌人的类型和敌人所处的场景。

    public void run() {
    	while(true){
    		//判断怪物类型
    		if(type==1){
    			if(this.isLeftOrUp){
    				this.x -= 5;
    			}else{
    				this.x += 5;
    			}
    	    
    

    在游戏中,当马里奥死亡的时候会对整个场景中的障碍物进行重置,当然敌人也不例外。当马里奥死亡的时候,不仅要将所有被消灭的敌人全部显示出来,即从消灭的List中还原到原来的敌人List中,并且敌人的状态和坐标也要进行重置。要将敌人的坐标还原到最开始的坐标,而且把图片进行还原。并且在重置方法中也要对敌人的类型进行判断,使得敌人的类型和他的显示图片相对应。

    public void reset(){
    	//还原坐标
    	this.x = this.startx;
    	this.y = this.starty;
    	//还原图片
    	if(this.type == 1){
    		this.showImage = StaticValue.allTriangleImage.get(0);
    	}else if(this.type == 2){
    		this.showImage = StaticValue.allFlowerImage.get(0);
    	}
    }
    

    最后在该类中定义一个死亡方法,主要是针对蘑菇怪被消灭的时候所调用的方法。在这个方法中要定义蘑菇怪死亡的时候的显示图片,也就是蘑菇怪被踩扁的图片。并且要将这个敌人从相对应的场景的敌人集合中除去,放入别消灭的敌人的List集合。

    public void dead(){
    		//死亡图片
    		this.showImage = StaticValue.allTriangleImage.get(2);
    		//从原来的List集合中删除,让入被消灭的List集合中
    		this.bg.getAllEnemy().remove(this);
    		this.bg.getRemoveEnemy().add(this);
    	}	
    }
    

    素材+源码+论文+学习视频)下载

    链接:打包下载点这里
    提取码:vypf

    展开全文
  • 我方坦克可以通过上下左右控制方向,敌方坦克自动改变方向。在游戏窗体中有障碍物,包括砖墙、钢墙、水墙和草地。坦克可以穿过草地,在遇到其他障碍物时,我方坦克停止移动,并通过操纵改变方向移动,敌方坦克...
  • 项目总结

    2020-05-14 09:04:36
    项目总结 1、马里奥项目 首先小编来介绍一下游戏的基本功能 ...1.人物的多键控制 1)给人物设定方向boolean变量:向左、向右、向上、向下 2)通过键盘监听来修改方向的变量值 按下某个键的时候,我们把相应方向
  • [Java] 贪吃蛇 V1.0

    2016-05-14 21:12:38
    可以通过键盘来控制蛇的移动方向 可以吃东西,实现蛇身增长 源代码//Mainfrm.java package com.snake.ui;import java.awt.Color; import java.awt.Graphics; import java.awt.event.KeyAdapter; import java.awt....
  • 一、游戏基本功能1、能够向左向右行走(键盘监听)2、能够跳跃3、能够发射×××4、能够检测和障碍物之间的碰撞5、背景图片的移动二、游戏运行界面三...人物的多键控制1)给人物设定方向boolean变量:向左、向右、向...
  • 在主程序运行的线程中,用户控制的超级玛丽随方向键的操作而移动,并且可以实现马里奥游戏中的一些基本功能,例如踩死敌人或者顶掉砖块。本游戏采用双缓存技术,解决了游戏过程中的闪屏问题。通过本游戏的开发,达到...
  • 一、游戏基本功能1、能够向左向右行走(键盘监听)2、能够跳跃3、能够发射子弹4、能够检测和障碍物之间的碰撞5、背景图片的移动...人物的多键控制1)给人物设定方向boolean变量:向左、向右、向上、向下2)通过键盘监听...
  • 绘出一辆坦克(一圆形代替),可以响应键盘事件(方向键可以控制坦克朝 八个方向移动) 开发步骤: 1.首先建立工程TankWar0.1,新建一个TankClient类继承自Frame类,添加 lauchFrame()方法, 方法内设置窗体基本...
  • C#编程经验技巧宝典

    热门讨论 2008-06-01 08:59:33
    16 <br>0033 Return语句的使用 17 <br>0034 如何实现无限循环 17 <br>0035 巧用foreach语句控制控件 18 <br>0036 有效使用switch case语句 18 <br>2.3 运算符 19 <br>0037 如何使用...
  • 4.根据电脑按下的方向键,确定出坦克的方向,panel重画时根据坦克的方向修改坦克的X,Y轴坐标,来实现坦克的移动 5.通过Rectangle的IntersectsWith函数来进行碰撞检测,实现子弹打击坦克 具体实现代码 1.在项目里面...
  • Ctrl+P 历史命令,建议使用方向键。 Ctrl+z 回根, 建议使用end命令 Ctrl+Break 路由器进入ROM监控状态。交换机清除特权密码。 双击设备进入终端操作 单击设备的接口可以实现画线 单击画有线的接口可以删除连线 在...
  • Excel VBA实用技巧大全 附书源码

    热门讨论 2010-10-08 18:59:24
    01036设置按【Enter】后单元格的移动方向 01037设置最近使用的文件清单中的最多文件数 01038设置新工作簿中的工作表个数 01039设置文件的默认位置 01040设置保存自动恢复文件的时间间隔和保存位置 01041停止屏幕...
  • 10.控制坦克移动射击的部分代码 public override void ccTouchEnded(CCTouch touch, CCEvent event_) { myTank.KeyReleased(Microsoft.Xna.Framework.Input.Keys.Down); } public void hitCallback(CCObject...
  • 如何控制鼠标的移动范围 如何使窗体右上角的系统按钮无效 如何改变窗口的图标 屏幕保护程序的原理是什么 应该如何制作屏幕保护程序 如何在Visual C++ 6.0中用回车切换输入焦点 如何实现类似QQ程序的界面 如何一步...
  • 一款非常好的WPF编程宝典2010 源代码

    热门讨论 2014-04-14 09:57:41
    1.2.3 Windows窗体将继续保留 11 1.2.4 DirectX也将继续保留 12 1.2.5 Silverlight 12 1.3 WPF体系结构 13 1.4 结束语 17 第2章 XAML 19 2.1 理解XAML 20 2.1.1 WPF之前的图形用户界面 20 2.1.2 XAML变体 21...
  • 其中功能窗体显示区(NoTaskbar)功能可设置本工具启用的窗体图标是否在状态栏显示,并允许切换功能。 扩 展 工 具 【屏幕截图】 可以截取屏幕任意一区域并复制或另存为图片,可随意更改大小,操作灵活性特强。 ...
  • 其中功能窗体显示区(NoTaskbar)功能可设置本工具启用的窗体图标是否在状态栏显示,并允许切换功能。 扩 展 工 具 【屏幕截图】 可以截取屏幕任意一区域并复制或另存为图片,可随意更改大小,操作灵活性特强。 【屏幕...
  • EXCEL集成工具箱V6.0

    2010-09-11 01:44:37
    其中功能窗体显示区(NoTaskbar)功能可设置本工具启用的窗体图标是否在状态栏显示,并允许切换功能。 扩 展 工 具 【屏幕截图】 可以截取屏幕任意一区域并复制或另存为图片,可随意更改大小,操作灵活性特强。 ...
  • 其中功能窗体显示区(NoTaskbar)功能可设置本工具启用的窗体图标是否在状态栏显示,并允许切换功能。 扩 展 工 具 【屏幕截图】 可以截取屏幕任意一区域并复制或另存为图片,可随意更改大小,操作灵活性特强。 ...
  • 其中功能窗体显示区(NoTaskbar)功能可设置本工具启用的窗体图标是否在状态栏显示,并允许切换功能。 扩 展 工 具 【屏幕截图】 可以截取屏幕任意一区域并复制或另存为图片,可随意更改大小,操作灵活性特强。 ...
  • 光驱控制模块.ec 全局事件模块.ec 全 程API模块.ec 全面操作SQL Server 2000.ec 公农历转换.ec 公农历转换1.2.ec 公农历转换 1.ec 公农历转换35.ec 公农历转换_2.ec 公历转农历模块 1.0.ec 公历转换为农历.ec 六六...
  • 1350多个精品易语言模块提供下载

    热门讨论 2011-06-06 17:51:09
    光驱控制模块.ec 全局事件模块.ec 全 程API模块.ec 全面操作SQL Server 2000.ec 公农历转换.ec 公农历转换1.2.ec 公农历转换 1.ec 公农历转换35.ec 公农历转换_2.ec 公历转农历模块 1.0.ec 公历转换为农历.ec 六六...
  • 1345个易语言模块

    2012-01-27 19:41:59
    光驱控制模块.ec 全局事件模块.ec 全 程API模块.ec 全面操作SQL Server 2000.ec 公农历转换.ec 公农历转换1.2.ec 公农历转换 1.ec 公农历转换35.ec 公农历转换_2.ec 公历转农历模块 1.0.ec 公历转换为农历.ec 六六...
  • Goto goto 语句将程序控制直接传递给标记语句。 for (int i = 0; i ; i++) for (int j = 0; j ; j++) if (myArray[i,j].Equals(myNumber)) goto Found; Console.WriteLine("The number {0} was not found.", my...
  • VB课程设计俄罗斯方块

    热门讨论 2011-02-25 10:46:55
    利用随机函数在一个预览窗体中提前展示形状供用户参考,然后将展示的形状复制到游戏窗体中进行摆放,在游戏窗体中用户就可以使用键盘的方向键控制方块的运动,然后利用递归语句对每一行进行判断,如果有某行的方块...

空空如也

空空如也

1 2
收藏数 39
精华内容 15
关键字:

方向键控制窗体移动