追逐算法之--牛鞭的子弹是怎样练成的(2)--简单追逐


        那是8岁的一个夏天,我刚刚完成了一天的工作,赚了50块钱,那个时候赚钱之后,唯一的消费场所当然就是游戏机厅了,从家里往游戏机室冲的感觉真是美好。乐极生悲,那天可能过于兴奋,穿着一双拖鞋,往游戏机室跳奔,谁知到途中的地上竖着一根大铁钉,我以百米冲刺的速度,将脚插进铁钉,顿时血流如注,当我把脚拔出来时,我发现脚底被扎出了一个直径1厘米的洞。我知道我要是再纠结我的脚,那么游戏机室的机子就肯定就被人占了,于是我立刻单脚跳到了游戏机室。一个小时后,尽管游戏机室的地面已经被我的鲜血染红,我仍然坐在游戏机上,拼命的砸着按键,大喊“大血给我吃!大血给我吃!我没血了!”老板过来拍拍我,问我是不是撒了什么染料在地上。我才意识到,我有可能先于游戏里的角色game over,这时我的整条腿已经失去了知觉,我赶紧又往家蹦,一离开游戏厅我才感觉到剧痛,说游戏是海洛因真的一点也不假。

 

       到家之后,我肯定是不敢告诉父母的了,因为之前也听说过,有伤口之后要在一定cd内打破伤风针,不然就会死。突然我看到了桌子上的风油精,我拿起来一看说明,“用于蚊虫叮咬及伤风感冒引起的头痛,头晕,晕车不适”,我优秀的语文阅读能力,让我认为,这个东西一定是治破伤风的,只是没有写明是内服还是外服,只好内外双服。然后在伤口上涂满了风油精,之后当然我很快就晕过去了,幸好抢救的及时,不然就要从下半身截肢了。

  

今天,我们要开始进入正题了,我们首先要搭一个简单射击游戏的框架,并实现一个简单追逐的效果,为后面实现牛鞭子弹做准备。

 

    首先,我们先造一架自己的飞机,并且能够控制它移动,新建一个MyPlanet类,在draw函数中,我们用画笔画一个长50,宽50的正方形,表示我的飞机,你们一定会惊讶你的飞机竟然是正方形的,我知道你们的飞机都是火腿肠形的,只是用画笔画一个火腿肠实在不容易,只好先代替下,后面会修饰。

 

  画完图形之后,我们实现了飞机向4个方向移动的方法,和4个动作状态属性,一旦相应的动作状态被设为true,我的飞机就会执行某一项动作。

 

public class MyPlanet {
	//位置坐标
	private double  x,y;
	//动作状态
	public boolean  isLeft,isRight,isUp,isDown,isFire;
	//移动速度
	private int speed =10;
		
	public MyPlanet(double x,double y){
		//构造函数,用于传入初始位置
		this.x =x;
		this.y = y;
	}
	
	public void draw(Graphics g){
		//画个正方形表示自己的飞机,这里的画矩形函数只能接受int类型的数,所以这里将
		//坐标的double类型转为int型
		g.drawRect((int)this.x, (int)this.y, 50, 50);
	}
		
	public void update(){
		//处理各方向的移动
		if(this.isLeft)this.move_left(speed);
		if(this.isRight)this.move_right(speed);
		if(this.isUp)this.move_up(speed);
		if(this.isDown)this.move_down(speed);
	}
	
	private void move_left(int speed) {
		this.x-=speed;	
	}

	private void move_right(int speed) {
		this.x+=speed;
	}

	private void move_up(int speed) {
		this.y-=speed;
	}

	private void move_down(int speed) {
		this.y+=speed;
	}	
}


  那么这里的动作状态是从哪里改变的呢?我们希望是按下w,a,s,d键分别让飞机,上下左右的运动,这个时候,我们只需要在GameHandler中的keypress中把相应的状态改成true就可以了

 

//按下键盘事件
	public void keyPressed(KeyEvent e) {
	//获得按键编号
        int keyCode = e.getKeyCode();
        //后面会添加具体按键匹配
        switch (keyCode) {
        case KeyEvent.VK_A:
        	myPlane.isLeft=true;
        	break;
        case KeyEvent.VK_D:
        	myPlane.isRight=true;
        	break;
        case KeyEvent.VK_W:
        	myPlane.isUp=true;
        	break;
        case  KeyEvent.VK_S:
        	myPlane.isDown=true;
        	break;
        }
	}

     

    这样当你按下s键时,myPlanet的向下移动判断成立并执行,飞机的位移就向下移动了,之后我们要在keyReleaesd 即按键释放函数中,将按键状态重置为false

 

//释放键盘事件
	public void keyReleased(KeyEvent e) {
		//获得按键编号
        int keyCode = e.getKeyCode();
        //后面会添加具体按键匹配
        switch (keyCode) {
        case KeyEvent.VK_A:
        	myPlane.isLeft=false;
        	break;
        case KeyEvent.VK_D:
        	myPlane.isRight=false;
        	break;
        case KeyEvent.VK_W:
        	myPlane.isUp=false;
        	break;
        case  KeyEvent.VK_S:
        	myPlane.isDown=false;
        	break;
        }
	}

    

 看到这里,有的码农朋友肯定要骂了,你这不闲的蛋疼嘛,你直接在keypress中这样写

 

        case  KeyEvent.VK_S:
        	myPlane.y+=myPlane.speed;
        	break;

     这样不就行了?连KeyReleased都不用写,简单快捷!

 

     其实这里我主要是出于,游戏操作的手感才这样写的,你们可以打开记事本,然后在输入区域,按住一个键输入字母,你会发现,当你刚刚按住一个键的时候,会出一个字母,但是要等接近0.5s之后,才会接着出现字母,这对飞机游戏的手感有着毁灭性的打击。
  

      这个时候我们还不能让飞机运动,因为我们还没在逻辑循环,和绘图循环中加入myPlanet的处理。

public class GameHandler implements Runnable {
	//游戏帧率
	public static final int FPS =40 ;
    //定义全局常量,游戏窗口的宽度和高度
    public static final int FRAME_WIDTH = 600;
    public static final int FRAME_HEIGHT = 700;
    //我的飞机
    public static MyPlanet myPlane = new MyPlanet(200,630) ;
		
	//渲染主循环,处理每一帧的画图动作
	public void draw(Graphics g){
		g.setColor(Color.WHITE);
		//绘制我的飞机
		myPlane.draw(g);
	}
	
	//逻辑主循环,处理每一帧的逻辑处理
	public void logical(){
		myPlane.update();
	}


    如上添加后,你可以运行程序了,此时你应该可以用按键操控飞机的四方向移动了,飞机移动的是挺流畅,但是世界上最悲剧的事情就是拥有飞机,却打不了炮,我们赶紧给我们的飞机添加上发射子弹的功能。

 

   我们希望我们的飞机在按下空格的情况下一秒钟内,能够发射5发子弹,因此我们这里需要一个计时器,也就是每隔200ms才允许飞机打出一发子弹。

 

要打炮,首先得有子弹,我们创建一个子弹类,我们画一个圆形的子弹,由于子弹在游戏中不止一个,我们用一个List集合来统一管理,并在子弹创建时,将自己加入到集合。

package planet;

import java.awt.Graphics;
public class Bullet {
	private double x,y;
	private int speed =15;
	//子弹直径
	private int rdius =15;
	public Bullet(double x,double y){
		this.x =x;
		this.y = y;
		//将此类加入GameHandler的子弹集合
		GameHandler.bulletList.add(this);
	}
	
	public void draw(Graphics g){
		//绘制一个圆形 
		g.fillOval((int)this.x,(int)this.y, rdius, rdius);
	}
	
	public void update(){
		//每帧让子弹上升
		this.y-=speed;
	}

}


这里的画圆方法是以正方形的最大内切圆来计算的,所以这里的直径长宽相同时,画出来的就是圆。

接下来我们在GameHandler中创立子弹集合,并在主循环中遍历调用每一个子弹的draw()和logical()

 

	//子弹集合
	public static ArrayList<Bullet> bulletList = new ArrayList<Bullet>();
	
	//渲染主循环,处理每一帧的画图动作
	public void draw(Graphics g){
		g.setColor(Color.WHITE);
		//绘制我的飞机
		myPlane.draw(g);
		//遍历绘制子弹集合
		for(Bullet e :GameHandler.bulletList){
			e.draw(g);
		}
	}
	
	//逻辑主循环,处理每一帧的逻辑处理
	public void logical(){
		//更新我的飞机的逻辑函数
		myPlane.update();
		//遍历更新子弹集合的逻辑函数
		for(Bullet e :GameHandler.bulletList){
			e.update();
		}
	}

 

  接下来,我们写一个极其简单的计时器类Timer

package planet;

public class Timer {
	//用于计时
	private int currentIndex;
	//计时的长度
	private int endIndex;
	
	public Timer(int frame){
		//构造函数,传入需要计时的时间
		this.endIndex = frame;
	}
	
	//开始计时,让计数帧加1,如果达到设定的帧数,就返回true
	public boolean act(){
		this.currentIndex++;
		if(this.currentIndex==this.endIndex){
			//一旦到达计数时间,就把当前计数帧重置,这样就可以达到重复计时的效果了
			this.reset();
			return true;
		}
		return false;
	}
	
	//重置计时器
	private void reset(){
		this.currentIndex =0;
	}
}

        

下面我们看一下怎么来用这个计时器

 我们首先在MyPlanet类中创建一个计时器和一个开火状态

 

          //动作状态
         public boolean  isLeft,isRight,isUp,isDown,isFire;
         //子弹发射的cd时间,这里设置一秒钟产生5颗子弹
	private Timer fireCDTimer =new Timer(GameHandler.FPS/5);

         

然后我们增加相应的开火方法,之后在GameHandler中的keypress和keyreleased中添加相应的状态

 

         public void fire(){
		//产生一颗子弹,位置就在自己飞机的正前方
		if(this.fireCDTimer.act())
			//这里的+20和-5用来调整子弹的初始位置,让它从飞机的正前方打出来
			GameHandler.bulletList.add(new Bullet(this.x+20,this.y-5));
	}
	
	public void update(){
		//处理各方向的移动
		if(this.isLeft)this.move_left(speed);
		if(this.isRight)this.move_right(speed);
		if(this.isUp)this.move_up(speed);
		if(this.isDown)this.move_down(speed);
		//按下space键时处理开火
		if(this.isFire)this.fire();
	}

              现在我们在运行一下,当你按住空格键时,你应该可以随意的打出子弹了!!

              接下来就是我们今天的重中之重,实现自杀式敌机,撞击我的飞机,并实现简单追逐算法!

              我们希望的是每隔2秒钟,在窗口上方随机产生一架敌机,并超着我飞机的位置飞过来!      

 

           首先我们先创建一个Enemy敌机类,由于敌机也不止一个,我们参照子弹类的方法,也在GameHandler中创建一个敌机类的集合,并创建一个每隔2s触发一次的计时器,用来产生敌机,这里我们还使用到了随机生成函数Random类,产生一个从0到窗口宽度之间的整数,作为敌机创建时的横坐标。

package planet;

import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Random;

public class Enemy {
	private double  x,y;
	private int speed =3;
	public Enemy(){
		//创建一个随机数生成器
		Random r = new Random();
		//随机产生一个从0到窗口宽度之间的整数
		double x=r.nextInt(GameHandler.FRAME_WIDTH);
		this.x =x;
		this.y =-10;
		//将此类加入GameHandler的敌机集合
		GameHandler.enemyList.add(this);
	}
	
	public void draw(Graphics g){
		//画一个矩形表示敌机
		g.drawRect((int)this.x, (int)this.y, 50, 50);
	}
	
	public void update(){
		//每帧追踪敌机
		this.trackMyPanel();
	}
	
	//自杀式追踪敌机
	private void trackMyPanel(){
		double mx =GameHandler.myPlane.getX();
		double my =GameHandler.myPlane.getY();
		//这里就是简单追踪的核心
		if(this.x<mx){
			this.x+=speed;
		}else{
			this.x-=speed;
		}
		this.y =this.y+speed;
	}
}

      所谓简单追踪的算法,就是当目标在我的左边的时候,我就将横坐标向左移,当目标在我右边的时候,我就将横坐标向右移,虽然够简单,但是效果还是挺智能的!

   

package planet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.util.ArrayList;


public class GameHandler implements Runnable {
	//游戏帧率
	public static final int FPS =40 ;
    //定义全局常量,游戏窗口的宽度和高度
    public static final int FRAME_WIDTH = 600;
    public static final int FRAME_HEIGHT = 700;
    //我的飞机
	public static MyPlanet myPlane = new MyPlanet(200,630) ;
	//敌机集合
	public static ArrayList<Enemy> enemyList = new ArrayList<Enemy>();
	//子弹集合
	public static ArrayList<Bullet> bulletList = new ArrayList<Bullet>();
	//产生敌机计时器
	private static Timer makeEnemyTimer = new Timer(GameHandler.FPS*2);
	
	//渲染主循环,处理每一帧的画图动作
	public void draw(Graphics g){
		g.setColor(Color.WHITE);
		//绘制我的飞机
		myPlane.draw(g);
		//遍历绘制敌机集合
		for(Enemy e :GameHandler.enemyList){
			e.draw(g);
		}
		//遍历绘制子弹集合
		for(Bullet e :GameHandler.bulletList){
			e.draw(g);
		}
	}
	
	//逻辑主循环,处理每一帧的逻辑处理
	public void logical(){
		//产生敌机函数
		makeEnemy();
		//更新我的飞机的逻辑函数
		myPlane.update();
		//遍历更新敌机集合的逻辑函数
		for(Enemy e :GameHandler.enemyList){
			e.update();
		}
		//遍历更新子弹集合的逻辑函数
		for(Bullet e :GameHandler.bulletList){
			e.update();
		}
	}
	
	
	//产生敌人
	public static void makeEnemy(){
		//每隔2秒产生一个敌人
		if(makeEnemyTimer.act())
			new Enemy();
	}
	
	//按下键盘事件
	public void keyPressed(KeyEvent e) {
		//获得按键编号
        int keyCode = e.getKeyCode();
        //后面会添加具体按键匹配
        switch (keyCode) {
        case KeyEvent.VK_SPACE:
        	myPlane.isFire=true;
        	break;
        case KeyEvent.VK_A:
        	myPlane.isLeft=true;
        	break;
        case KeyEvent.VK_D:
        	myPlane.isRight=true;
        	break;
        case KeyEvent.VK_W:
        	myPlane.isUp=true;
        	break;
        case  KeyEvent.VK_S:
        	myPlane.isDown=true;
        	break;
        }
	}
	
	//释放键盘事件
	public void keyReleased(KeyEvent e) {
		//获得按键编号
        int keyCode = e.getKeyCode();
        //后面会添加具体按键匹配
        switch (keyCode) {
        case KeyEvent.VK_SPACE:
        	myPlane.isFire=false;
        	break;
        case KeyEvent.VK_A:
        	myPlane.isLeft=false;
        	break;
        case KeyEvent.VK_D:
        	myPlane.isRight=false;
        	break;
        case KeyEvent.VK_W:
        	myPlane.isUp=false;
        	break;
        case  KeyEvent.VK_S:
        	myPlane.isDown=false;
        	break;
        }
	}
	
	@Override
	public void run() {
		while(true){
			//执行一次逻辑处理函数
			this.logical();
			
			//使面板重绘一次,repaint方法会调用GamePanel中的paintComponent方法
			GameFrame.gamePanel.repaint();
			
			try {
				//线程每隔25ms休眠一次,即每秒执行40次此函数,即FPS=40
				Thread.sleep(1000/FPS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

   此时运行你应该可以看到如下效果了



 源码地址:http://download.csdn.net/detail/azhangzhengtong/5221655

 

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值