那是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