计算机游戏程序设计

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

一、游戏背景介绍
随着手持式终端的日渐强大,移动手持设备在模拟现实方面的技术日趋成熟。

人们在移动设备上可以体验到比以往更加真实的视觉冲击和立体效果,同时伴随着人们对模拟现实类游戏的青睐,使得此类手机休闲游戏得到了迅速的发展。

现在快节奏的生活让人们无时无刻不存在着紧张感,无论是生活还是工作,上班还是下班,学习还是工作,烦恼紧张无处不在。

这个时代下车辆已经相当普及,所以老司机也不在少数,这就催生了很多模拟驾驶汽车游戏的诞生,这些游戏既放松了心情又锻炼了技术因此深受大众欢迎。

也正因为如此,市场上大多数的驾驶类游戏都是驾驶着机车运送货物或者搭载人。

这些游戏极其考验技术,在保证正常行驶的情况下也必须保证所要运送货物或者搭载乘客的安全,像3D巴士驾驶员、3D学校驾驶如图-1、图-2所示都是此类游戏中很不错的典型。

图-1 3D 巴士驾驶员图-2 3D 学校驾驶
“泥路卡车”是使用OpenGL ES 3.0开发的一款基于Android平台的休闲类游戏,本游戏利用了JBullet物理引擎,下面将对本游戏进行详细的介绍。

上面简单地介绍了本游戏的开发背景,现在将对该游戏的主要功能进行简单的介绍。

包括游戏UI界面的展示,按钮的功能详细介绍以及游戏场景的展示。

(1)运行游戏,首先进入的是欢迎界面,如图-3所示。

经过欢迎界面后进入游戏的加载界面,如图-4所示,这里是游戏中所有资源加载的界面,在这里进度条将随着资源加载的进度而进行读条,资源加载完毕同时读条也完毕。

图-3 欢迎界面图-4 加载界面
(2)读条完毕后进入本游戏的主菜单界面,如图-5所示,从这里可以通过单击不同的功能按钮进入到不同的界面。

点击主菜单界面的设置按钮进入设置界面,如图-6所示,设置界面包含音乐与音效两个开关,点击该界面ON/OFF部分即可实现音乐与音效的开关。

图-5主菜单界面图-6 设置界面(3)点击主菜单界面的帮助按钮进入帮助界面,如图-7所示,帮助界面显示了本游戏的基本操作以及需要注意的事项。

点击关于按钮进入游戏的关于界面,如图-8所示,关于界面显示了我们的开发团队的具体信息以及警示信息用以保护版权归属。

图-7 帮助界面图-8关于界面(4)点击退出游戏按钮时会弹出一个toast,如图-9所示,这是为了防止玩家无意按中退出键来提示玩家是否真的退出游戏。

接下来便是游戏的主体部分,点击开始游戏后,如图-10所示,进入选关界面,玩家可以点击第一关或者第二关开始游戏。

图-9 退出界面toast 图-10 选关界面(5)进入游戏以后玩家就可以正式开启游戏之旅了,如图-11所示,游戏过程中玩家会遇到各种障碍或者凹凸不平、高低起伏的路面,这些阻碍会使车上箱子掉下车去。

一旦车上箱子数目少于俩个便会结束游戏,如图-12所示。

图-11 游戏第一关界面图-12 游戏输之后界面
(6)有时候玩家小心翼翼地开车为了不使箱子从车上掉下去从而忽视了游戏界面顶端的剩余时间,这个时间是限定玩家必须在规定时间内将货物送达终点,玩家若是单方面注意箱子数量而不注意时间的话在时间快结束便会出现警报并伴随着警报声,如图-13所示。

图-13 警报界面 图-14 暂停界面 (7)游戏过程中玩家可以点击右上角的按钮暂停,如图-14所示,这是为了游戏途中玩家在现实中有事而设计的。

玩家在规定时间内完成了任务,终于能进入到下一关时便会弹出如图-15所示的图片,此时玩家根据自己情况进而选择下一关或者重新进行。

图-15 第一关结束界面 图-16 游戏第二关界面 (8)每一关的游戏难度不同,规定时间也有所不同,但是最终目标都是将货物在规定时间内送到规定地点,若玩家完成第一关游戏后想要继续挑战更难的则可以点击进入下一关,如图-16所示,本关将会有较上一关更难的地形与,趣味与紧张并存。

二、核心算法思想
在游戏中,经常需要进行碰撞检测的实现,例如判断前面是否有障碍以及判断子弹是否击中飞机,都是检测两个物体是否发生碰撞,然后根据检测的结果做出不同的处理。

进行碰撞检测的物体可能有些的形状和复杂,这些需要进行组合碰撞检测,就是将复杂的物体处理成一个一个的基本形状的组合,然后分别进行不同的检测。

碰撞问题包括碰撞检测和碰撞响应两个方面的内容。

碰撞检测用来检测不同对象之间是否发生了碰撞,碰撞响应是在碰撞发生后,根据碰撞点和其他参数促使发生碰撞的对象作出正确的动作,以反映真实的动态效果。

碰撞检测方法:按照对象所处的空间分:二维平面碰撞检测和三维空间碰撞检测。

按检测的方法长区分为:空间分解法,层次包围盒法。

一般规则的物体碰撞都可以处理成矩形碰撞,实现的原理就是检测两个矩形是否重叠。

我们假设矩形1的参数是:左上角的坐标是(x1,y1),宽度是w1,高度是h1;矩形2的参数是:左上角的坐标是(x2,y2),宽度是w2,高度是h2。

在检测时,数学上可以处理成比较中心点的坐标在x 和y 方向上的距离和宽度的关系。

即两个矩形中心点在x 方向的距离的绝对值小于等于矩形宽度和的二分之一,同时y 方向的距离的绝对值小于等于矩形高度和的二分之一。

下面是数学表达式:
x 方向:|(x1+w1/2)–(x2+w2/2)|<|(w1+w2)/2|
y方向:|(y1+h1/2)–(y2+h2/2)|<|(h1+h2)/2|
在程序中,只需要将上面的条件转换成代码就可以实现了,但是矩形碰撞只是一种比较粗糙的碰撞检测方法。

例如本游戏中的树、石头等物体的碰撞形状并没有采用本身的形状,因为采用采用本身形状顶点太多,计算量过大影响性能,便用胶囊状代替,在视觉上没有什么太大区别。

除了碰撞检测的实现外本游戏中还开发了多线程架构,游戏中多线程架构首先需要开一个物理计算线程,将游戏中需要进行物理计算的部分写到该线程中,进行完一轮物理计算后将车身刚体信息、车轮刚体信息、箱子的刚体信息的计算结果存入到本地的变量组中。

然后加锁将本地变量组中的这些数据送入到全局数据中,此时一轮物理计算线程完毕。

物理线程一轮一轮的运行,在每轮运行之前会判断是否继续运行,每一轮运行结束后也会视情况休息一段时间。

物理线程计算完之后还需要开一个绘制线程,本游戏中的绘制线程就是游戏界面中的绘制方法,在每轮绘制线程开始时都会加锁从全局数据中读取数据进本地数据,读取结束后绘制线程根据本地数据中的各数据来绘制,因为绘制线程中绘制的物体是根据物理计算后得到物体,全局数据中的数据为物理线程经过物理计算后得到的物理数据结果数据,所以需要每轮都向全局数据中取得数据进行绘制,绘制完毕一轮绘制线程结束。

三、核心算法流程图
在游戏界面的绘制方法开始我们就在一直说本地数据、全局数据、物理计算线程等这些名词,这些贯穿游戏开发始终的东西都是多线程架构下的一部分内容,接下来我们来看一下此部分的架构图,如图-18所示为多线程的架构图。

图-18 游戏多线程架构图
如架构图所示,物理线程中运行过程先进行物理计算,计算完毕后计算的结果会存到物理线程自身的本地变量组中,作为临时存放处,之后便会将这些数据加锁送到全局数据中去。

全局数据中声明了所有的物理计算所相关的数据对象数组,在每轮物理计算线程运行完毕时全局数据中都会储存着当前一轮物理计算的结果,之后绘制线程会加锁从全局数据中取出数据放到绘制线程中的本地数组中,之后绘制线程会取得这些数据,并根据这些数据进行绘制。

这就是一轮多线程异步架构的进程。

在不同界面切换时为了实现无延时切换也开发了一套算法,在接下来的代码部分会详细介绍这部分,如图-19所示为切换界面示意图。

图-19 切换界面架构图
游戏中GLsurfacView类是程序的主绘制类,其中包含了整个游戏的架构,游戏中所有的显示界面都是通过主绘制类的不同逻辑调用所实现的,其通过声明所有界面的父类对象,用这个对象指向不同对象实现不同界面的切换,其中在加载界面进行绘制时将游戏中所有资源都会加载完毕,之后的界面切换的延时肉眼是看不出来的。

四、源代码
GLSurfaceView类是整个游戏的绘制类,为整个游戏的呈现类。

整个游戏除欢迎界面外其余所有界面均通过此类显示,包括加载界面、菜单界面、关于界面、设置界面、游戏界面、帮助界面等界面。

开发该类的详细步骤如下。

(1)首先开发的是GLSurfaceView类,GLSurfaceView类是主绘制类,其中包含了整个游戏的架构,游戏中所有的显示界面都是通过主绘制类的不同逻辑调用所实现的,其通过声明所有界面的父类对象,用这个对象指向不同对象实现不同界面的切换,具体代码如下。

public class GlSurfaceView extends GLSurfaceView {
public static SelectViewBN selectview;//初始化界面
public static GameViewBN gameview1;//第一关界面
public static GameViewBN gameview2;//第二关界面
public static BNAbstractView currView;//初始化游戏加载界面的父类
public static LoadViewBN loadview;//加载界面
GameActivity activity;
SenceRenderer mRenderer;
DiscreteDynamicsWorld dynamicsWorld;
CarShadowData shadowdata = new CarShadowData();
public float ratio;
public GlSurfaceView(GameActivity activity,
DiscreteDynamicsWorld dynamicsWorld) {
super(activity);
this.activity = activity;
this.dynamicsWorld = dynamicsWorld;
this.setEGLContextClientVersion(3); // 设置使用OPENGL ES3.0
this.setEGLConfigChooser(8, 8, 8, 8, 16, 8);
mRenderer = new SenceRenderer();// 创建场景渲染器
this.setRenderer(mRenderer);// 设置渲染器
// 设置渲染模式为主动渲染
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (currView == null) {
return false;
}
return currView.onTouchEvent(e);
}
class SenceRenderer implements GLSurfaceView.Renderer {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置屏幕背景色RGBA
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// 打开深度检测
GLES30.glEnable(GLES30.GL_DEPTH_TEST);
// 打开背面剪裁
GLES30.glEnable(GLES30.GL_CULL_FACE);
GLES30.glBlendFunc(GLES30.GL_SRC_ALPHA,
GLES30.GL_ONE_MINUS_SRC_ALPHA);
// 初始化变换矩阵
MatrixState.setInitStack();
// 初始化光源方向
MatrixState.setLightDirection(-2f,1,0);
ShaderManager.loadShaderScriptAndCompiled(GlSurfaceView.this
.getResources());
loadview=new LoadViewBN(dynamicsWorld, activity, GlSurfaceView.this);
currView=loadview;
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport((int) Constant.sx, (int) Constant.sy,
(int) (1920 * Constant.ratio), (int) (1080 * Constant.ratio));
// 计算GLSurfaceView的宽高比
ratio = (float) 1920/1080;
}
@Override
public void onDrawFrame(GL10 gl) {
if (currView != null) {
currView.drawView(gl);// 绘制界面信息
}}}}
(2)接下来开发的是除欢迎界面之外所有界面的父类界面类BNAbstractView,该类有三个抽象方法需要每个继承该类的子类重写,进而在主界面GLSurfaceView中实现不同界面的方法到该类去具体调用不同的方法以完成不同的目的。

public abstract class BNAbstractView
{
public abstract void initView();//初始化资源
public abstract boolean onTouchEvent(MotionEvent e);//触控
public abstract void drawView(GL10 gl);//绘制界面
}
(3)加下来开发的是欢迎界面类,欢迎界面是进入游戏的第一个界面,该界面显示了游戏的出处,进入后会发现欢迎图片的透明度会发生改变,代码中是通过控制图片的透明度来达到欢迎动画的效果,具体开发如下。

public class WelcomeView extends SurfaceView implements SurfaceHolder.Callback{//实现生命周期回调接口static Bitmap[] logos;//logo图片数组
static boolean loadFlag=false;//是否加载图片的标志位
GameActivity activity; //activity的引用
Bitmap currentLogo; //当前logo图片引用
Paint paint; //画笔
int currentAlpha=0; //当前的不透明值
int sleepSpan=50; //动画的时延ms
float currentX; //图片位置
float currentY;
ScreenScaleResult ssr;
public WelcomeView(GameActivity activity){
super(activity);
this.activity = activity;
this.loadBitmap();//加载图片
this.getHolder().addCallback(this); //设置生命周期回调接口的实现者
paint = new Paint(); //创建画笔
paint.setAntiAlias(true); //打开抗锯齿
}
//将图片加载进内存的方法
public void loadBitmap() {
currentLogo=BitmapFactory.decodeResource(this.getResources(),R.drawable.flash);
}
//重写onDraw方法
public void onDraw(Canvas canvas){
if(canvas!=null){
canvas.save();
canvas.scale(Constant.ratio,Constant.ratio);
canvas.translate(Constant.sx,Constant.sy);
//绘制黑填充矩形清背景
paint.setColor(Color.BLACK);//设置画笔颜色
paint.setAlpha(255);
canvas.drawRect(0, 0,1920, 1080, paint);
//进行平面贴图
if(currentLogo==null)return;
paint.setAlpha(currentAlpha); //设置当前不透明度
canvas.drawBitmap(currentLogo, currentX, currentY, paint);//绘制当前的logo
canvas.restore();
}
}
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
{//画布改变时调用
}
public void surfaceCreated(SurfaceHolder holder) //创建时被调用
{
new Thread()
{
@SuppressLint("WrongCall")
public void run()
{
//计算图片位置
currentX=1920/2-currentLogo.getWidth()/2;
currentY=1080/2-currentLogo.getHeight()/2;
System.out.println("////currentX:"+currentX+"/////currentY"+currentY);
for(int i=0;i<255;i=i+10)
{//动态更改图片的透明度值并不断重绘
currentAlpha=i;
if(currentAlpha<0)
{
currentAlpha=0;
}
SurfaceHolder myholder=WelcomeView.this.getHolder();
Canvas canvas = myholder.lockCanvas();//获取画布
try{
synchronized(myholder){
onDraw(canvas);//绘制
}}
catch(Exception e){
e.printStackTrace();
}
finally{
if(canvas != null){
myholder.unlockCanvasAndPost(canvas);
}}
try
{
Thread.sleep(sleepSpan);
}
catch(Exception e)
{
e.printStackTrace();
}}
for(int i=255;i>-10;i=i-10)
{//动态更改图片的透明度值并不断重绘
currentAlpha=i;
if(currentAlpha<0)
{
currentAlpha=0;
}
SurfaceHolder myholder=WelcomeView.this.getHolder();
Canvas canvas = myholder.lockCanvas();//获取画布
try{
synchronized(myholder){
onDraw(canvas);//绘制
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
if(canvas != null){
myholder.unlockCanvasAndPost(canvas);
}}
try
{
if(i==255)
{//若是新图片,多等待一会
Thread.sleep(1000);
}
Thread.sleep(sleepSpan);
}
catch(Exception e)
{
e.printStackTrace();
}}
activity.handler.sendEmptyMessage(0);//闪屏结束则向Activity发送消息
}
}.start();
}
public void surfaceDestroyed(SurfaceHolder arg0)
{//销毁时被调用
}}
(4)接下来开发的为加载界面类,该类中将游戏中所需的资源全部加载,绘制时根据加载的进度的不同来变换进度条的位置,从而实现进度条的进度与资源加载的进度是一致的,当进度条时走完时资源也相应的加载完成。

public class LoadViewBN extends BNAbstractView{
GameActivity activity;
DiscreteDynamicsWorld dynamicsWorld;//物理世界
GlSurfaceView gsv; //surfaceView的成员变量
TreeData treedata=new TreeData(); //树的位置旋转数据
CarShadowData shadowdata = new CarShadowData();//阴影体的顶点数据
float ratio;//宽高比
int load_step = 0;//加载进度,将此数传给加载界面用于绘制加载进度
public LoadViewBN(DiscreteDynamicsWorld dynamicsWorld,GameActivity activity,GlSurfaceView gsv){ this.dynamicsWorld=dynamicsWorld;
this.activity=activity;
this.gsv=gsv;
TextureManager.loadingTexture(this.gsv, 0, 49);// 加载游戏所需的所有图片资源
loadingBackGround = new TextureRectangle(this.gsv, 630f,
1150f);
loadingBackGroundTextureId = TextureManager //加载界面背景图
.getTextures("loading.png");
loadingSeek = new TextureRectangle(this.gsv, 90f, 600f); //游戏加载进度条
loadingSeekTextureId = TextureManager
.getTextures("loadingseek.png");
ratio = (float) 1920 / 1080;
}
@Override
public void initView() {
}
@Override
public boolean onTouchEvent(MotionEvent e) {
return false;
}
@Override
public void drawView(GL10 gl) {
//清除颜色缓冲和深度缓冲
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT
| GLES30.GL_COLOR_BUFFER_BIT);
MatrixState.pushMatrix();//保护场景
if (isLoadedOk == false) {
drawOrthLoadingView(); // 读条界面
}
MatrixState.popMatrix();//恢复场景
}
public void drawOrthLoadingView() {
// 调用此方法计算产生正交投影矩阵
MatrixState.setProjectOrtho(-ratio, ratio, -1, 1, 1f, 50);
MatrixState.scale(0.055f, 0.055f, 0.055f);
MatrixState.setCamera(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f,
1.0f, 0.0f);
GLES30.glEnable(GLES30.GL_BLEND);//开启混合
// 清除深度缓冲与颜色缓冲
GLES30.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
MatrixState.pushMatrix();//保护现场
MatrixState.translate(load_step * 0.95f - 37f, 8f, -40f);//平移
loadingSeek.drawSelf(loadingSeekTextureId);//绘制
MatrixState.popMatrix();//恢复现场
MatrixState.pushMatrix();//保护现场
MatrixState.translate(2f, 0f, -39f);//平移
loadingBackGround.drawSelf(loadingBackGroundTextureId);//绘制
MatrixState.popMatrix();//恢复现场
GLES30.glDisable(GLES30.GL_BLEND);//关闭混合
loadResource();
}
public void init_All_Texture(int index) {//该方法将游戏所用到所有资源加载完成,以后直接调用switch (index) {
case 1:
aboutViewId = TextureManager.getTextures("aboutView.png");
helpViewWinId = TextureManager.getTextures("helpwin.png");
helpViewLoseId = TextureManager.getTextures("helplose.png");
helpViewleftId = TextureManager.getTextures("left.png");
helpViewRightId = TextureManager.getTextures("right.png");
vehicleBoxTextureId = TextureManager.getTextures("car.jpg");
vehicleWheelTextureId = TextureManager.getTextures("wheel.jpg");
vehicleBrakeLightId = TextureManager
.getTextures("carLight.jpg");
vehicleTurnLightId = TextureManager
.getTextures("carTurnLight.jpg");
break;
case 2:
break;
case 3:
case 4:
break;
case 5:
map2 = new MapLevel2(gsv, // 地面类
ShaderManager.getShaderProgram(0));
carStartPoint = new Vector3f(carstartpoint[0][0],
carstartpoint[0][1], carstartpoint[0][2]);
carStartPoint1 = new Vector3f(carstartpoint[1][0],
carstartpoint[1][1], carstartpoint[1][2]);
carStartPoint2 = new Vector3f(carstartpoint[2][0],
carstartpoint[2][1], carstartpoint[2][2]);
carEndPoint1 = new Vector3f(carendpoint[0][0],
carendpoint[0][1], carendpoint[0][2]);
carEndPoint2 = new Vector3f(carendpoint[2][0],
carendpoint[2][1], carendpoint[2][2]);
weapon_number=new
NumberForDraw(11,0.05f,0.15f,ShaderManager.getShaderProgram(3),this.gsv);
break;
case 6:
break;
case 7:
sceneLevel1 = new SceneLevel1(gsv,// 场景类
ShaderManager.getShaderProgram(4),
ShaderManager.getShaderProgram(0)
);
steeringTextureId = TextureManager.getTextures("sh.png");
labelTextureId1 = TextureManager.getTextures("label1.png");
labelTextureId2 = TextureManager.getTextures("label2.png");
labelTextureId3 = TextureManager.getTextures("label3.png");
labelTextureId4 = TextureManager.getTextures("label4.png");
loseDialogTextureId = TextureManager.getTextures("lose.png");
winDialogTextureId = TextureManager.getTextures("win.png");
exitToMainMenuId = TextureManager.getTextures("back.png");
exitViewId = TextureManager.getTextures("exitView.png");
skyId = TextureManager.getTextures("sky.jpg");
car = new Car(dynamicsWorld, vehicleBoxTextureId,
vehicleWheelTextureId, vehicleBrakeLightId,
vehicleTurnLightId, carStartPoint, this.gsv,
ShaderManager.getShaderProgram(0),// 车身绘制用着色器编号
ShaderManager.getShaderProgram(0) // 车灯绘制用着色器编号);
case 8:
tg1 = new TreeGroup(gsv, // 树的管理类
ShaderManager.getShaderProgram(2),
treedata.TreeDataLevel1,this.dynamicsWorld);
boxTextureId = TextureManager.getTextures("box.jpg");
liangbianId = TextureManager.getTextures("liangbian.png");
qiangmianId = TextureManager.getTextures("qiang.jpg");
menId = TextureManager.getTextures("men.jpg");
mapId = TextureManager.getTextures("dimian.jpg");
stoneId = TextureManager.getTextures("shitou.png");
fangziId1 = TextureManager.getTextures("fangzi1.png");
fangziId2 = TextureManager.getTextures("fangzi2.png");
treeId = TextureManager.getTextures("tree1.png");
treeId3 = TextureManager.getTextures("tree2.png");
advanceTextureId = TextureManager.getTextures("advup.png");
downId = TextureManager.getTextures("down.png");
break;
case 9:
break;
case 10:
allscenelevel1 = new AllSceneLevel1(this.gsv,
dynamicsWorld);
break;
case 11:
break;
case 12:
sceneLevel2 =new SceneLevel2(gsv,// 场景类
ShaderManager.getShaderProgram(4),//两边场景的着色器编号
ShaderManager.getShaderProgram(0) //路障的着色器编号
);
tg2 = new TreeGroup(gsv, // 树的管理类
ShaderManager.getShaderProgram(2),treedata.TreeDataLevel2,this.dynamicsWorld);
tex_lefttimeId=TextureManager.getTextures("lefttime.png");
numberId=TextureManager.getTextures("timenumber.png");
exitButtonId = TextureManager.getTextures("exitbutton.png");
startButtonId = TextureManager.getTextures("startbutton.png");
helpButtonId = TextureManager.getTextures("helpbutton.png");
setButtonId = TextureManager.getTextures("setbutton.png");
aboutButtonId = TextureManager.getTextures("aboutbutton.png");
selectLevel1Id = TextureManager.getTextures("selectlevel1.png");
selectLevel2Id = TextureManager.getTextures("selectlevel2.png");
BGMONId = TextureManager.getTextures("bgmon.png");
BGMOFFId = TextureManager.getTextures("bgmoff.png");
EQONId = TextureManager.getTextures("eqon.png");
EQOFFId = TextureManager.getTextures("eqoff.png");
helpViewId = TextureManager.getTextures("helpView.png");
backId = TextureManager.getTextures("back.png");
ctrl = new Control(this.gsv); // 游戏界面仪表板类
SelectLevelViewData.initLocalData();
break;
case 13:
break;
case 14:
boxGroup = new BoxGroup(carStartPoint, dynamicsWorld,
this.gsv, car, boxTextureId); // 箱子管理类map1 = new MapLevel1(gsv, // 地面类
ShaderManager.getShaderProgram(0));
cs = new CarShadow(shadowdata.VertexData,
shadowdata.TriangleVertexData,
ShaderManager.getShaderProgram(3));
break;
case 15:
break;
case 16:
allscenelevel2 = new AllSceneLevel2(this.gsv,
dynamicsWorld);
break;
case 17:
sky = new Sky(this.gsv); // 天空穹类
break;
case 18:
menuButton = new MenuButton(this.gsv);
selectview = new SelectViewBN(dynamicsWorld, activity,
this.gsv);
gameview1 = new GameViewBN(dynamicsWorld, activity,
this.gsv);
gameview2 = new GameViewBN(dynamicsWorld, activity,
this.gsv);
break;
case 19:
break;
case 20:
currView = selectview;
isLoadedOk = true;// 切换
break;
case 21:
break;
}}
public void loadResource() {//控制资源加载,给加载进度条传参数
……//省略了部分代码
}}}
(5)接下来开发的便是主菜单场景类SelectViewBN,该类采用单点触控进行选择,所看见的背景虽然是游戏场景,但是这个场景只是传入数据画出来的,并没有开物理线程这是为了节省内存空间,这个场景中没有碰撞等物理事件发生。

具体代码如下。

public class SelectViewBN extends BNAbstractView{
DiscreteDynamicsWorld dynamicsWorld;
GameActivity activity;
GlSurfaceView mv;
boolean bgmon=true;//背景音乐标志位,判断是否背景音乐开
boolean yxon=true;//音效标志位,判断是否音效开
public int isback=0;//退出键弹出toast的标志位
//主界面,用于显示加载界面过后的界面
public SelectViewBN(DiscreteDynamicsWorld dynamicsWorld,GameActivity activity,GlSurfaceView mysurfaceview){
this.mv=mysurfaceview;
this.activity=activity;
this.dynamicsWorld=dynamicsWorld;
initView();
}
@Override
public void initView() {//初始化界面
if(allscenelevel1.isadd)
{
allscenelevel1.removebody();
}if(allscenelevel2.isadd){
allscenelevel2.removebody();
}
allscenelevel1.initdynamicsworld(dynamicsWorld);
tg1.initdynamicsWorld();
car.clientResetScene(2.17f,5.61f,0.278f);
}
@Override
public boolean onTouchEvent(MotionEvent event) {//主界面的触控(单点触控)
System.out.println("sx"+Constant.sx+"|"+"sy"+Constant.sy+"|"+"ratio"+Constant.ratio);
if (isLoadedOk) {//资源加载完成以后触控生效
try {
float x = event.getX();//触控的x坐标
float y = event.getY();//触控的y坐标
if(event.getAction()==MotionEvent.ACTION_UP){//触控抬起时的逻辑
if (menuButton.currentView == INITIALVIEW) // 如果现在是在初始化的界面
{
if (x > Start_Button_Left // 点击到了开始游戏
&& x < Start_Button_Right && y > Start_Button_Top && y <
Start_Button_Buttom) {
System.out.println("点击到了开始游戏");
activity.playSound(1, 0);
menuButton.currentView = STARTVIEW; //将currentView的值变成选关界面的值
}
if (x > Set_Button_Left // 点击到了设置
&& x < Set_Button_Right && y > Set_Button_Top && y <
Set_Button_Buttom) {
System.out.println("点击到了设置游戏");
activity.playSound(1, 0);
menuButton.currentView = SETVIEW; //设置为设置界面的值
}
if (x > Help_Button_Left // 点击到了帮助
&& x < Help_Button_Right && y > Help_Button_Top && y <
Help_Button_Buttom) {
System.out.println("点击到了帮助");
activity.playSound(1, 0);
menuButton.currentView = HELPVIEW; //设置为帮助界面的值
}
if (x > About_Button_Left // 点击到了关于
&& x < About_Button_Right && y > About_Button_Top && y < About_Button_Buttom) {
System.out.println("点击到了关于");
activity.playSound(1, 0);
menuButton.currentView = ABOUTVIEW; //设置为关于界面的值
}
if (x > Exit_Button_Left // 点击到了退出游戏
&& x < Exit_Button_Right && y > Exit_Button_Top && y <
Exit_Button_Buttom) {
System.exit(0); //退出游戏
}
} else if (x > Select_Back_Left // 在其他界面点到了返回按钮
&& x < Select_Back_Right && y > Select_Back_Top && y <
Select_Back_Buttom) {
menuButton.currentView = INITIALVIEW; // 切换到初始界面
}
if (menuButton.currentView == STARTVIEW) // 如果现在是处在选关页面
{
if (x > Level1_Button_Left // 点击到了第一关
&& x < Level1_Button_Right && y > Level1_Button_Top && y < Level1_Button_Buttom) {
System.out.println("点击到了第一关");
activity.playSound(1, 0); //播放音效,设置标志位,将车,箱子刚体移动到相应的位置,开线程
sview=false;
gview1=true;
gview2=false;
gameview1=new GameViewBN(dynamicsWorld, activity,mv);
currView=gameview1;
menuButton.currentView = INITIALVIEW;
car.clientResetScene(-60.754f,-0.4648f,-449.716f);//-203.567f,0.2f,368.457f -60.754f,-0.4648f,-449.716f
boxGroup.clientResetScene(carStartPoint1.x,carStartPoint1.y-0.5f,carStartPoint1.z,-5.0f);
simulationThread=new SimulationThread(dynamicsWorld,
car,boxGroup,carEndPoint1,activity);
simulationThread.start();
if(isBGMON)
{
activity.pauseBGM();
isBGMON=false;
}
if(isBGMON==false)
{
isBGMON=true;
activity.nowBGM=activity.gameBGM;
activity.resumeBGM();
}
}
if (x > Level2_Button_Left // 点击到了第二关
&& x < Level2_Button_Right && y > Level2_Button_Top && y < Level2_Button_Buttom) {
System.out.println("点击到了第二关");
activity.playSound(1, 0);
sview=false;
gview1=false;
gview2=true;
gameview2=new GameViewBN(dynamicsWorld, activity,mv);
currView=gameview2;
menuButton.currentView = INITIALVIEW;
car.clientResetScene(-203.45f,-0.505f,368.525f);//-203.45f,-0.505f,368.525f
boxGroup.clientResetScene(carStartPoint2.x,carStartPoint2.y-0.5f,carStartPoint2.z,-5.0f);
simulationThread=new SimulationThread(dynamicsWorld,
car,boxGroup,carEndPoint2,activity);
simulationThread.start();
if(isBGMON)
{
activity.pauseBGM();
isBGMON=false;
}
if(isBGMON==false)
{
isBGMON=true;
activity.nowBGM=activity.gameBGM;
activity.resumeBGM();
}}
System.out.println(x+" *** "+y);
}
if(menuButton.currentView == SETVIEW) //如果现在为设置界面
{
System.out.println(x+" *** "+y);
if (x > Set_BGMON_Left // 点击到了音乐
&& x < Set_BGMOFF_Right && y > Set_BGMON_Top && y <
Set_BGMOFF_Buttom)
{ //根据背景音乐标志位来设置背景音乐的开关
if(bgmon)
{
isBGMON=false;
activity.pauseBGM();
bgmon=false;
}
else{
isBGMON=true;
activity.resumeBGM();
bgmon=true;
}
}
if (x > Set_EQON_Left // 点击到了音效
&& x < Set_EQOFF_Right && y > Set_EQON_Top && y <
Set_EQON_Buttom)
{//根据音效的标志位来设置音效的开关
if(yxon)
{
isEQON=false;
yxon=false;
}
else{
isEQON=true;
yxon=true;
}}}
if(menuButton.currentView ==HELPVIEW) //点到帮助界面
{
System.out.println(x+" help界面坐标"+y);
if(helpview1==true&&helpview2==false&&helpview3==false)//第一幅帮助图片,只有向右键的触控逻辑
{
if(x>Help_GORight_Left&&x<Help_GORight_Right&&y>Help_GORight_Top&&y<Help_GORight_Righ t)
{
helpview1=false;
helpview2=true;
helpview3=false;
}
}
else if(helpview1==false&&helpview2==true&&helpview3==false)//第二幅帮助的图片,有左右的触控逻辑
{
if(x>Help_GOLeft_Left&&x<Help_GOLeft_Right&&y>Help_GOLeft_Top&&y<Help_GOLeft_Buttom)//左
{
helpview1=true;
helpview2=false;
helpview3=false;
}
if(x>Help_GORight_Left&&x<Help_GORight_Right&&y>Help_GORight_Top&&y<Help_GORight_Righ t)//右
{
helpview1=false;
helpview2=false;
helpview3=true;
}}
else if(helpview1==false&&helpview2==false&&helpview3==true)//第三幅帮助的图片有向左的触控逻辑{
if(x>Help_GOLeft_Left&&x<Help_GOLeft_Right&&y>Help_GOLeft_Top&&y<Help_GOLeft_Buttom)//左{
helpview1=false;
helpview2=true;
helpview3=false;
}
}System.out.println("1-"+helpview1+"2-"+helpview2+"3-"+helpview3);
}}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}}
return true;
}
public void drawScene(float isLight,float warnlight) //绘制有无光照与报警的场景{
boxGroup.drawSelf(isLight,warnlight); //绘制箱子类
car.drawSelf(isLight,warnlight); //绘制车
allscenelevel1.drawself(isLight,warnlight); //绘制场景
}
@Override
public void drawView(GL10 gl) {
// 清除深度缓冲与颜色缓冲
GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT
| GLES30.GL_COLOR_BUFFER_BIT);
//设置摄像机位置
MatrixState.setCamera(0, 12, -11, 0, 10, -6, 0, 1, 0);
MatrixState.pushMatrix();
calculateRotateAngle();//计算摄像机旋转角度
MatrixState.pushMatrix();
MatrixState.translate(LocalData.carTransform[0], -5, LocalData.carTransform[2]);//让天空跟随车移动
sky.drawSelf(skyId,0.6f); // 天空穹绘制
MatrixState.popMatrix();
//绘制场景(不带光照)
drawScene(0.0f,0.6f);
GLES30.glDepthFunc(GLES30.GL_LESS);
//清除模板缓冲区
GLES30.glClear(GLES30.GL_STENCIL_BUFFER_BIT);
//不写入颜色
GLES30.glColorMask(false,false,false,false);
//不写入深度值
GLES30.glDepthMask(false);
//启用模板测试
GLES30.glEnable(GLES30.GL_STENCIL_TEST);
//设置剪裁为剪裁后只会制正面
GLES30.glCullFace(GLES30.GL_BACK);
//设置模板测试参数
GLES30.glStencilFunc(GLES30.GL_ALWAYS, 0x0, 0xff);
//设置模板测试的动作
GLES30.glStencilOp(GLES30.GL_KEEP,GLES30.GL_KEEP,GLES30.GL_INCR);
//绘制阴影体
cs.drawSelf();
//设置剪裁为剪裁后只会制反面
GLES30.glCullFace(GLES30.GL_FRONT);
//设置模板测试参数
GLES30.glStencilFunc(GLES30.GL_ALWAYS, 0x0, 0xff);
//设置模板测试的动作
GLES30.glStencilOp(GLES30.GL_KEEP,GLES30.GL_KEEP,GLES30.GL_DECR);
//绘制阴影体
cs.drawSelf();
//设置剪裁为剪裁后只会制正面
GLES30.glCullFace(GLES30.GL_BACK);
//设置深度检测带等于避免有光照与无光照绘制深度检测冲突
GLES30.glDepthFunc(GLES30.GL_LEQUAL);
//写入颜色
GLES30.glColorMask(true, true, true, true);
//写入深度
GLES30.glDepthMask(true);
//设置模板测试参数
GLES30.glStencilFunc(GLES30.GL_EQUAL, 0x0, 0xff);
//设置模板测试的动作
GLES30.glStencilOp(GLES30.GL_KEEP, GLES30.GL_KEEP, GLES30.GL_KEEP);
//绘制带光照场景
drawScene(1.0f,0.6f);
//禁用模板测试
GLES30.glDisable(GLES30.GL_STENCIL_TEST);
tg1.drawSelf(1.0f,0.6f);//绘制树
MatrixState.popMatrix();
menuButton.drawSelf();//绘制所有按钮
}}
(6)接下来开发的是本游戏中很重要的游戏界面类GameViewBN,该类采用多点触控方式,绘制时每帧画面是根据本地数据localData中车、箱子的位置旋转数据来画的。

每帧绘画都会从全局数据GlobalData中将数据取到localData中,以便后边使用,具体开发代码如下。

public class GameViewBN extends BNAbstractView
{//游戏界面类继承自BN抽象类
DiscreteDynamicsWorld dynamicsWorld; //物理世界
GameActivity activity;
GlSurfaceView mv;
Car Car;
public static int gotime; //记录游戏时间
public static float oldtime=0; //计算游戏时间
public boolean isview=true; //标志位是否为本界面
float screenWidth = Constant.screenWidth; //屏幕的宽
float screenHigth = Constant.screenHigth; //屏幕的高
Vector3f old_touch = new Vector3f(0, 0, 0); // 旧的触控点的向量起始点在屏幕左上
Vector3f new_touch = new Vector3f(0, 0, 0); // 新的触控点的向量起始点在屏幕左上
Vector3f center = new Vector3f((float) (0.78 * screenWidth),
(float) (1 * screenHigth), 0); // 方向盘中心的向量int steTouchId = 0;// 方向盘触摸点id
Vector3f old_point = new Vector3f(0, 0, 0); // 旧的向量起始点在方向盘的中心
Vector3f new_point = new Vector3f(0, 0, 0); // 新的向量起始点在方向盘的中心
int i=0; //0与1切换给着色器传警
报时红色通道的值
int j=0; //刷帧次数
int a=0; //与i作用一样控制树的逻辑
public GameViewBN(DiscreteDynamicsWorld dynamicsWorld,GameActivity activity,GlSurfaceView mv)
{
this.mv=mv;
this.activity=activity;
this.dynamicsWorld=dynamicsWorld;
initView();
activity.nowBGM=activity.gameBGM;
isBGMON=true;
isstopview=false;
isFalse=false;
changeDrawTimeThread = new ChangeDrawTimeThread();//声明结束时需调用线程的对象}
@Override
public void initView()//每关开始的时候将所有刚体清除,本关需要的刚体加进来
{
if(allscenelevel1.isadd)//判断场景刚体是否存在,若存在则删掉刚体
{
allscenelevel1.removebody();
tg1.removebody();
}if(allscenelevel2.isadd){
allscenelevel2.removebody();
tg2.removebody();
}
if(sview==true&&gview1==false&&gview2==false)//删除刚体后判断现在为第几关,进而加载该关卡的刚体内容
{
allscenelevel1.initdynamicsworld(dynamicsWorld);
}if(gview1==true&&sview==false&&gview2==false)
{
allscenelevel1.initdynamicsworld(dynamicsWorld);
tg1.initdynamicsWorld();
}if(gview2==true&&sview==false&&gview1==false)
{
allscenelevel2.initdynamicsworld(dynamicsWorld);
tg2.initdynamicsWorld();
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (isLoadedOk) {//资源加载完成后触控生效
try {
//获取触控的动作编号
int action=event.getAction()&MotionEvent.ACTION_MASK;
//获取主、辅点id(down时主辅点id皆正确,up时辅点id正确,主点id要查询Map 中剩下的一个点的id)
int index=(event.getAction()&MotionEvent.ACTION_POINTER_INDEX_MASK)
>>>MotionEvent.ACTION_POINTER_INDEX_SHIFT;
int id=event.getPointerId(index);
switch (action) {
case MotionEvent.ACTION_DOWN: // 主点down
case MotionEvent.ACTION_POINTER_DOWN: // 辅点down{
keyPress(event.getX(id), event.getY(id),id,car);// 当按下时,调用此方法
break;
}。

相关文档
最新文档