世风日下啊,大家现在动不动就打飞机。。。
上个周末实在是无聊,就写了个Java版的微信打飞机游戏。。拿上来和大家交流交流,不喜勿喷(网络上这个资源已经很多了很多了)
目前实现的功能:
1、敌方有两类飞机:小飞机和大BOSS飞机(小飞机1滴血;BOSS有10滴血,我方我只设置一滴血,可以修改blood参数)
2、药丸:有蓝色药丸和红色药丸
3:蓝色药丸能双发子弹 (有时间限制)
4:红色药丸全屏爆炸 屏幕下方显示已有的红色药丸数目
5:按键说明:游戏暂停p 游戏继续s 游戏重开始 r 方向键控制上下左右 红色药丸使用空格
先上几副图看看吧(那个。。。飞机爆炸有点土。。。用一陀屎黄色的东西代替哈。。)
(注意)游戏制作中细节的地方:
(1)图一中左下角的显示
(2)双发子弹的设计,如果有一发击中敌机,另一发也应该继续往前走
那个图形美化实在是不善长,就借用微信上的飞机截图做素材哈,,,版权归原作者所有。
------------------------------------------------------------------------
1 简单入门
其实做这个游戏主要你规划好有什么角色,每帧他们都在做什么就好了,然后用什么数据结构去表示(应该说压根就不设计什么算法,数据结构的东西)
在进行冗长的说明之前,我先举个例子,比如如何实现一个物体在界面中随着时间移动(暂时不考虑边界问题)
实现: 1:实时绘制背景 为黑色 2:一红色小球从界面顶部下落 3:随着时间增长,速度增快 4:为了不出先卡吨,实现双缓冲机制 |
首先,我们需要一个JFrame窗口,这个继承就好,然后实现paint()方法,来实现我们的绘制,制作背景简单,可是为什么要实时更新呢?
因为这里我们所看见的动画就是每帧图像快速拼接起来的,而每幅图像都需要我们重新绘制,否则都是在原有图像上再次绘制,所以,如果每次都不刷新界面,那么会有上一次的滞留图像。
paint方法如下:
@Override
public void paint(Graphics g) {
//super.paint(arg0);
//before draw, move first
time++;
y += velocity + time;
//draw
Color c = g.getColor();
g.setColor(Color.BLACK);
g.fillRect(0, 0, 480, 800);
g.setColor(Color.RED);
g.fillOval(x, y, 10, 10);
g.setColor(c);
}
可是,如果绘制物体过多,我们会发现,存在卡顿现象,这也是所有显示都会遇到的问题,一般采用双缓冲方式解决,我们这里不是什么大型游戏,就简单用下面方法实现双缓冲机制,即我们先绘制好下一帧图像,再显示,而不是边显示边绘制。
我们在先申明一个公共变量Image buffer;专门存储下一帧图像,并且在update()函数里实现绘制
@Override
public void update(Graphics g) {
//super.update(g);
if(buffer == null)
buffer = this.createImage(480, 800);
Graphics gBuffer = buffer.getGraphics();
paint(gBuffer);//先绘制在缓冲中
g.drawImage(buffer, 0, 0, null);
}
当然,这里update()传入的g参数就是我们这个窗口的画笔。
随着时间增加,速度增快,这个在paint()函数里已经简单实现了。。
然后就是怎么实现实时绘制,当然是需要开一个定时器啦。。那就开一个线程专门负责更新图像不久好了?
这里关键的更新就是利用mf.update(mf.getGraphics());然后就调用我们刚才实现的update(),然后里面再调用paint()
class MainThread implements Runnable{
MainFrame mf = null;
public MainThread(MainFrame mf) {
this.mf = mf;
}
@Override
public void run() {
while(true){
try{
Thread.sleep(50);
mf.update(mf.getGraphics());
}catch(Exception e){
e.printStackTrace();
}
}
}
}
上面这个流程很清晰了,就是让线程负责时间刷新,然后通知窗口,要更新啦!!窗口对象调用更新函数,更新函数通知逻辑绘制函数paint(),要更新啥,你说了算。。。所以把这个程序分为两部分:
UI更新:逻辑更新(目前都是在paint()里面具体实现的)
所以这个程序一封装,以后我们要先游戏,不久只需要在paint()函数里做手脚不就行了么?
下面给出这个小demo的代码
package ylf.graphics;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
/**
* @author ylf
*/
public class MainFrame extends JFrame{
int time ;
int velocity ;
int x ;
int y;
Image buffer = null;
public MainFrame() {
init();
this.setSize(480, 800);
this.setVisible(true);
new Thread(new MainThread(this)).start();
}
public void init(){
time = 0;
velocity = 0;
x = 240;
y = 0;
}
@Override
public void paint(Graphics g) {
//super.paint(arg0);
//before draw, move first
time++;
y += velocity + time;
//draw
Color c = g.getColor();
g.setColor(Color.BLACK);
g.fillRect(0, 0, 480, 800);
g.setColor(Color.RED);
g.fillOval(x, y, 10, 10);
g.setColor(c);
}
@Override
public void update(Graphics g) {
//super.update(g);
if(buffer == null)
buffer = this.createImage(480, 800);
Graphics gBuffer = buffer.getGraphics();
paint(gBuffer);//先绘制在缓冲中
g.drawImage(buffer, 0, 0, null);
}
class MainThread implements Runnable{
MainFrame mf = null;
public MainThread(MainFrame mf) {
this.mf = mf;
}
@Override
public void run() {
while(true){
try{
Thread.sleep(50);
mf.update(mf.getGraphics());
}catch(Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MainFrame m = new MainFrame();
}
}
2 打飞机逻辑
好了,能自己实现上面这段就说明:你已经会打飞机啦!
下面教你怎么打更comfortable
继续刚才的,既然我们可以把飞机的逻辑业务都抽取出来,那么这个Frame类我们就不去打扰了,我们独立出一个Controller的类,该类负责游戏业务控制,比如我方飞机的对象持有,敌方一群飞机对象持有(用什么数据结构?当然就用链表呗。。)该逻辑也就是控制分数,还有游戏开始,暂停以及重新开始一些外围以及游戏运行的整体逻辑。
我把Controller的onDraw()函数贴出来,大家就清楚这个游戏逻辑
定期安排敌人飞机出现
定期安排药丸出现
刷新我们飞机位置 状态
刷新敌人飞机位置 状态
刷新爆炸动画
刷新药丸个数显示
刷新分数
public void onDraw(Graphics g) {
///游戏逻辑/
//schedular produce plane-other
//定期安排出飞机,出飞机的类型有飞机工厂来生成
if((++readyOther)%10==0){
readyOther = 0;
Random rand = new Random();
OtherPlaneFactory.getPlanes(rand.nextInt(2), others, this);
}
//schedular produce equipment
//定期安排药丸出现 其实这里也可以弄一个药丸工厂
if((++readyPowerful)%600==0){
Random rand = new Random();
if(rand.nextBoolean())
equipments.add(new PowerEquipment(rand.nextInt(MainFrame.FRAME_WIDIH), 0, this));
else
equipments.add(new BombEquipment(rand.nextInt(MainFrame.FRAME_WIDIH), 0, this));
}
/下面就是把逻辑分配给这些游戏角色了
Color oldColor = g.getColor();
myPlane.onDraw(g); //我方飞机逻辑控制
for(int i=0;i<others.size();i++)
others.get(i).onDraw(g); //敌方飞机逻辑控制
for(int i=0;i<explosions.size();i++)
explosions.get(i).onDraw(g); //爆炸的绘制
// 下面这段是为了实现界面左下角的药丸个数提醒的绘制,实现方法有很多,我这里粗略实现了
int bombNum = 0;
for(int i=0;i<equipments.size();i++){
Equipment e = equipments.get(i);
e.onDraw(g);
if(e.getType() == Equipment.TYPE_BOMB){
if(!((BombEquipment)e).isLive()){
bombNum++;
}
}
}
if(bombNum != 0){
g.drawImage(bombNumImg, 10, MainFrame.FRAME_HEIGHT-50, null);
g.drawString(""+bombNum, 85, MainFrame.FRAME_HEIGHT-22);
}
//绘制分数
g.setColor(Color.BLACK);
g.drawImage(pauseFace, 10, 30, null);
g.drawString(""+score, 60, 50);
g.setColor(oldColor);
}
3 打飞机的角色
主要的还是角色的设置才是学习java的途径
如何把java的一些设计原则使用的好就在这里。
接口的封装和字符类的继承,我觉得最好能用接口就使用接口,还有功能不能定的太死,最好能用插拔接口的形式。
例如某个物体是需要能运动的。我们不需要对所有角色都继承一个有onMove()的父类,而是可以用实现了Moveable这个接口。这种灵活性比使用类好的多。当然我实现上也有许多缺陷,仅供参考:下载地址
http://download.youkuaiyun.com/detail/ylf13/6870955
还在弄git。。不太会用,传上去后再给出地址下载哈