第十一天、十二天、十三天
2D游戏开发
基础
这几天一直在完善自己的基于java的简单2D游戏引擎。
由于我也不知道真正的2D游戏引擎都包含哪些模块,只能按照自己的理解和以前的一些经验去编写。
首先,结合之前的线程知识,我们的2D引擎至少需要一个线程,用来逐帧绘图。这里可以使用sleep人为控制每秒帧率为多少,当cpu无法满足时,便时刻不停的计算,不使用sleep。不过该实现有些复杂,于当前程序用处不大,所以在线程中使用固定时间sleep。
结合之前的知识,要把游戏内所有的物体全部绘制到画面上,需要遍历每一个游戏物体,这里可以用到面向对象思想,即构建一个父类GameObject,将其他的对象继承父类的属性,并重写其抽象方法。不过我个人不喜欢面向对象法,事实上,将所有类别的游戏对象按照其基本数据类型可以存储为多个列表,而不是将每一个种对象存入列表,这样做使程序效率更更高,只不过编程时会略增麻烦(当属性和方法足够多时,一般人类的大脑会很难处理)。
现在,我们有了一个绘制图像的线程类,和一个父类GameObject,在父类中定义一个抽象方法,paint,将每一个新生成的游戏对象都存入主游戏对象列表中,绘制线程将遍历这个列表,调取每一个游戏对象的paint方法进行绘制。
但是此时我们的游戏对象还无法移动,我们在GameObject类中为对象加入x,y,speedx,speedy,dspeedx,dspeedy等属性,为对象添加位置坐标,速度,加速度等信息。(注意最好都使用double,不然当增量很小时容易丢失数据信息,我这里额外增加了一对xx,yy属性)。
有了这些属性之后,我们可以在绘制帧时,调用函数来为每一个游戏对象更新这些属性,貌似有一些游戏引擎是这么做的。
不过我选择了新建一个线程类CalThread,使用与绘制线程不同的时间间隔来计算更新这些属性。在该线程中,遍历调用所有可移动游戏对象的move函数来更新位置。
然而这时我们会发现,游戏对象可以移动了,但是它们都是拖着一条尾巴走的,原因是旧的绘制数据没有被擦除。所以需要在每一帧绘制时,额外绘制一个白色的矩形覆盖掉整张地图。
GameObject类
package com.zht0119.ThreadGame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public abstract class GameObject implements Const
{
static final int BOARD=0;
static final int BALL=1;
static final int KILLER=2;
static final int CROSSSTAR=3;
static final int BLOOD=4;
static final int GRASS=5;
static TGameProcess tgp;
public int x,y;
public double xx,yy;
public double speedx,speedy;
public double dspeedx,dspeedy;
public double mass=0.01;
public Color color;
boolean canMove=true;
boolean isDraw=true;
public int type=0;
int layerint=1; //所属的图层数
int listint=0; //在主游戏空间列表中的位置
int layerListint=0; //在所属图层游戏空间列表中的位置
Pve selfPve=new Pve(0,0); //当前正面法向量
public abstract void move();
public abstract void paint(Graphics2D g);
public abstract void paintMap(Graphics2D mg);
public abstract void destroy();
public void usuallyDestroy()
{
for(int i=listint+1;i<tgp.objects.size();++i)
{
tgp.objects.get(i).listint--;
}
for(int i=layerListint+1;i<tgp.layers.get(layerint).objects.size();++i)
{
tgp.layers.get(layerint).objects.get(i).layerListint--;
}
tgp.objects.remove(listint);
tgp.layers.get(layerint).objects.remove(layerListint);
}
public void speed2Pve()
{
selfPve.xx=speedx;
selfPve.yy=speedy;
}
public void dspeed2Pve()
{
selfPve.xx=dspeedx;
selfPve.yy=dspeedy;
}
public void maxSpeed(double maxSx,double maxSy)
{
if(Math.abs(speedx)>maxSx)
{
speedx=(speedx>0?1:-1)*maxSx;
}
if(Math.abs(speedy)>maxSy)
{
speedy=(speedy>0?1:-1)*maxSy;
}
}
}
在GameObject类中
DeawThread类
package com.zht0119.ThreadGame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Objects;
public class DrawThread extends Thread implements Const
{
static TGameProcess tgp;
BufferedImage buffer=new BufferedImage((int)(MAP_X*quality), (int)(MAP_Y*quality), BufferedImage.TYPE_INT_RGB);
BufferedImage mapBuffer=new BufferedImage((int)(SMALL_MAP_X*quality), (int)(SMALL_MAP_Y*quality), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d;
Graphics2D mg2d;
public void run()
{
g2d=(Graphics2D)buffer.getGraphics();
g2d.scale(quality, quality);
mg2d=(Graphics2D)mapBuffer.getGraphics();
mg2d.scale(quality, quality);
tgp.g.scale(1/quality, 1/quality);
tgp.mg.scale(1/quality, 1/quality);
while(true)
{
//clear(tgp.g);
//paintObjects(tgp.g);
for(int i=0;i<tgp.layers.size();++i)
{
Layer tempLayer=tgp.layers.get(i);
//System.out.println("layer"+i+":"+tgp.layers.get(i).objects.size());
if(tempLayer.vis==true)
{
drawBuffer(tempLayer.objects);
}
}
showBuffer();
paintMap();
//((Graphics2D)tgp.g).drawImage(buffer,10,10,null, null);
try {Thread.sleep(drawTime);}
catch(InterruptedException e) {}
}
}
private void paintObjects(Graphics2D g,ArrayList<GameObject> objects)
{
for(int i=0;i<objects.size();++i)
{
//System.out.println(objects.size());
if(objects.get(i).isDraw==true)
{
(objects.get(i)).paint(g);
}
}
}
private void drawBuffer(ArrayList<GameObject> objects)
{
paintObjects(g2d,objects);
}
private void showBuffer()
{
tgp.g.drawImage(buffer, 10-tgp.mainCamera.potx,10-tgp.mainCamera.poty,null, null);
}
private void paintMap()
{
mg2d.setColor(Color.white);
mg2d.fillRect(0,0,120,110);
for(int i=0;i<tgp.objects.size();++i)
{
tgp.objects.get(i).paintMap(mg2d);
}
tgp.mg.drawImage(mapBuffer, 0, 0, null, null);
tgp.mg.setColor(Color.blue);
tgp.mg.drawRect((int)(SMALL_MAP_X*((double)tgp.mainCamera.potx/(double)MAP_X)), (int)(SMALL_MAP_Y*((double)tgp.mainCamera.poty/(double)MAP_Y)), (int)(WINDOW_X/MDSX), (int)(WINDOW_Y/MDSY));
}
}
CalThread类
package com.zht0119.ThreadGame;
import java.util.ArrayList;
public class CalThread extends Thread implements Const
{
static TGameProcess tgp;
public void run()
{
while(true)
{
for(int i=0;i<tgp.objects.size();++i)
{
if(tgp.objects.get(i).canMove==true)
{
(tgp.objects.get(i)).move();
}
}
calStatic();
try {Thread.sleep(calTime);}catch(Exception e) {}
}
}
private void growGrass() {
// TODO Auto-generated method stub
}
private void calStatic()
{
Ball.center();
Grass.growGrass();
}
}
双缓冲绘图
这些代码是完整代码,出了绘制和移动还有许多功能,例如,双