• 周三. 4 月 22nd, 2026

物嫩软件资讯网

软件资讯来物嫩

Android游戏开发之飞行射击类游戏原理

admin@wunen

5 月 1, 2025

Android游戏开发之飞行射击类游戏原理实现

1.地图滚动的原理实现

举个简单的例子吧,同学们都坐过火车吧,坐火车的时候都遇到过自己的火车明明是停止的但是旁边铁轨的火车在向后行驶,会有一种错觉感觉自己的火车是在向前行驶吧,呵呵。飞行射击类游戏的地图原理和这个完全一样。玩家在控制飞机在屏幕中飞行的位置,背景图片一直向后滚动从而给玩家一种错觉自己控制的飞机在向前飞行,如下图所示两张地图图片在屏幕背后交替滚动,这样就会给玩家产生向前移动的错觉。

2.触摸屏幕控制主角飞机的移动范围

用手触摸屏幕中的任意一个点, 程序可以得到当前点的X , Y坐标   。 以当前飞机的X Y坐标为中心计算出当前飞机X,Y坐标点与目标X,Y点的距离 。因为飞机不可能直接就飞到目标点所以分别依次对当前坐标X,Y相加一次飞机移动的步长。 这里我们须要考虑飞机移动过程中玩家停止触摸屏幕,如果停止触摸飞机将原地停住不在向目标点移动,直到新的目标点出线即玩家新触摸屏幕的X,Y坐标。

4、主角飞机子弹的实现原理与敌机的碰撞

因为子弹的数量会有很多,敌机的数量也会很多, 所以每一颗子弹须要用一个对象来记录这当前子弹的X,Y坐标 与在屏幕中的绘制区域,每一架敌机也是一个对象,也记录着它的X,Y坐标与在屏幕中的绘制区域,这样在处理碰撞的时候其实就是每一颗子弹的矩形区域 与每一架敌机的矩形区域的碰撞。通过遍历子弹对象与敌机对象就可以计算出碰撞的结果,从而拿到碰撞的敌机对象播放死亡爆炸动画。

说到这里有些同学可能会想如果按照这样的思路将会频繁的创建子弹对象与敌机对象这样会造成内存泄漏等严重的问题。仔细想一下屏幕中须要绘制的子弹数量与敌机数量肯定是有限的,我们可以初始化固定的子弹对象与敌机对象 只对这些对象进行更新逻辑与绘制 ,举个例子 当前游戏屏幕中我最多须要5架敌机,代码中我就只分配5个敌机对象,分别检测这些对象 如果被子弹打中 或者向下超过屏幕底边,这时候可以对这个对象进行属性的重置,让这架飞机从新出现在上方的战场上,这样就实现在不增加飞机对象的情况下让玩家感觉有打不完的飞机,子弹对象同理。

简单敌机类实现 敌人有两个状态 一个是生存状态 一个是死亡状态 代码中根据当前状态播放动画。

  1. public class Enemy {
  2. /**敌人存活状态**/
  3. public static final int ENEMY_ALIVE_STATE = 0;
  4. /**敌人死亡状态**/
  5. public static final int ENEMY_DEATH_STATE = 1;
  6. /**敌人行走的Y轴速度**/
  7. static final int ENEMY_STEP_Y = 5;
  8. /**敌人图片的宽度**/
  9. static final int BULLET_WIDTH = 40;
  10. /** 敌人的XY坐标 **/
  11. public int m_posX = 0;
  12. public int m_posY = 0;
  13. /** 敌人行走的动画 **/
  14. private Animation mAnimation0 = null;
  15. /** 敌人死亡的动画 **/
  16. private Animation mAnimation1 = null;
  17. /**播放动画状态**/
  18. public int mAnimState = 0;
  19. /**是否更新绘制敌人**/
  20. boolean mFacus = false;
  21. /**敌人状态**/
  22. int mState =0;
  23. Context mContext = null;
  24. public Enemy(Context context, Bitmap[] frameBitmap,Bitmap[] deadBitmap) {
  25. mContext = context;
  26. mAnimation0 = new Animation(mContext, frameBitmap, true);
  27. mAnimation1 = new Animation(mContext, deadBitmap, false);
  28. }
  29. /**初始化坐标**/
  30. public void init(int x, int y) {
  31. m_posX = x;
  32. m_posY = y;
  33. mFacus = true;
  34. mAnimState = ENEMY_ALIVE_STATE;
  35. mState = ENEMY_ALIVE_STATE;
  36. mAnimation0.reset();
  37. mAnimation1.reset();
  38. }
  39. /**绘制敌人动画**/
  40. public void DrawEnemy(Canvas Canvas, Paint paint) {
  41. if (mFacus) {
  42. if(mAnimState == ENEMY_ALIVE_STATE) {
  43. mAnimation0.DrawAnimation(Canvas, paint, m_posX, m_posY);
  44. }else if(mAnimState == ENEMY_DEATH_STATE) {
  45. mAnimation1.DrawAnimation(Canvas, paint, m_posX, m_posY);
  46. }
  47. }
  48. }
  49. /**更新敌人状态**/
  50. public void UpdateEnemy() {
  51. if (mFacus) {
  52. m_posY += ENEMY_STEP_Y;
  53. //当敌人状态为死亡并且死亡动画播放完毕 不在绘制敌人
  54. if(mAnimState == ENEMY_DEATH_STATE) {
  55. if(mAnimation1.mIsend) {
  56. mFacus = false;
  57. mState = ENEMY_DEATH_STATE;
  58. }
  59. }
  60. }
  61. }
  62. }


复制代码

简单子弹类实现

  1. public class Bullet {
  2. /**子弹的X轴速度**/
  3. static final int BULLET_STEP_X = 3;
  4. /**子弹的Y轴速度**/
  5. static final int BULLET_STEP_Y = 15;
  6. /**子弹图片的宽度**/
  7. static final int BULLET_WIDTH = 40;
  8. /** 子弹的XY坐标 **/
  9. public int m_posX = 0;
  10. public int m_posY = 0;
  11. /** 子弹的动画 **/
  12. private Animation mAnimation = null;
  13. /**是否更新绘制子弹**/
  14. boolean mFacus = false;
  15. Context mContext = null;
  16. public Bullet(Context context, Bitmap[] frameBitmap) {
  17. mContext = context;
  18. mAnimation = new Animation(mContext, frameBitmap, true);
  19. }
  20. /**初始化坐标**/
  21. public void init(int x, int y) {
  22. m_posX = x;
  23. m_posY = y;
  24. mFacus = true;
  25. }
  26. /**绘制子弹**/
  27. public void DrawBullet(Canvas Canvas, Paint paint) {
  28. if (mFacus) {
  29. mAnimation.DrawAnimation(Canvas, paint, m_posX, m_posY);
  30. }
  31. }
  32. /**更新子弹的坐标点**/
  33. public void UpdateBullet() {
  34. if (mFacus) {
  35. m_posY -= BULLET_STEP_Y;
  36. }
  37. }
  38. }


复制代码

这里推展一下知识,游戏中会有不同的敌人与不同的子弹,我们可以拓展一下对象类,那敌机来说 在对象类中可以声明一个飞机类型的成员变量 在绘制与更新敌机的时候可以根据敌机类型做相应的处理,比如敌机的绘制图片、飞行轨迹 、弹道AI、都可以根据它来实现。

这里在说一下代码设计模式中的工厂模式,工厂模式初学者会可能会觉得一头雾水,但不可否认它的抽象原理 可以让我们的代码变的拓展性更好。举个例子 比如游戏开发中我们需要3个玩家职业 A 弓箭手 B 斧头手 C魔法师 ,首先我们分析一下这3个职业的特点 它们的共同点为都需要控制人物行走点击攻击按钮播放攻击特效等, 它们的不同点为 弓箭手 攻击为射箭 、斧头手攻击为近身砍怪 、魔法师则为远程魔法攻击。工厂模式的原理为将他们的共同点拿出来 写在一个类中,这个类就是一个工厂类, 然后让他们三个分别去继承这个类 ,分别在自己的类中实现自己特殊的方法。 这样设计代码的话会使代码拓展性更佳,因为可以非常方便的添加和删除一个职业。不会因为可变因素修改大量代码导致项目Delay。

当然好东西可定会有它的弊端,因为使用工厂模式我们会写很多拓展类,这样无疑会增加大量的对象与调用方法等等,从运行效率上会大打折扣,所以开发者在设计代码的时候就要好好斟酌自己的代码如何来设计。


初始化游戏 在这里将代码中须要的所有资源所有对象全部初始化,也就是说游戏中不会在分配新对象内存。

  1. private void init() {
  2. /**游戏背景**/
  3. mBitMenuBG0 = ReadBitMap(mContext,R.drawable.map_0);
  4. mBitMenuBG1 = ReadBitMap(mContext,R.drawable.map_1);
  5. /**创建主角飞机动画对象**/
  6. mAircraft = new Animation(mContext,new int[] {R.drawable.plan_0,R.drawable.plan_1,R.drawable.plan_2,R.drawable.plan_3,R.drawable.plan_4,R.drawable.plan_5},true);
  7. /**第一张图片津贴在屏幕00点,第二张图片在第一张图片上方**/
  8. mBitposY0 = 0;
  9. mBitposY1 =-mScreenHeight;
  10. /**初始化飞机的坐标**/
  11. mAirPosX = 150;
  12. mAirPosY = 400;
  13. /**这里敌人行走动画就1帧**/
  14. Bitmap []bitmap0 = new Bitmap[ENEMY_ALIVE_COUNT];
  15. bitmap0[0] = ReadBitMap(mContext,R.drawable.e1_0);
  16. /**敌人死亡动画**/
  17. Bitmap []bitmap1 = new Bitmap[ENEMY_DEATH_COUNT];
  18. for(int i =0; i< ENEMY_DEATH_COUNT; i++) {
  19. bitmap1[i] = ReadBitMap(mContext,R.drawable.bomb_enemy_0 + i);
  20. }
  21. /**创建敌人对象**/
  22. mEnemy = new Enemy[ENEMY_POOL_COUNT];
  23. for(int i =0; i< ENEMY_POOL_COUNT; i++) {
  24. mEnemy[i] = new Enemy(mContext,bitmap0,bitmap1);
  25. mEnemy[i].init(i * ENEMY_POS_OFF, 0);
  26. }
  27. /**创建子弹类对象**/
  28. mBuilet = new Bullet[BULLET_POOL_COUNT];
  29. mBitbullet = new Bitmap[BULLET_ANIM_COUNT];
  30. for(int i=0; i<BULLET_ANIM_COUNT;i++) {
  31. mBitbullet[i] = ReadBitMap(mContext,i+R.drawable.bullet_0);
  32. }
  33. for (int i =0; i< BULLET_POOL_COUNT;i++) {
  34. mBuilet[i] = new Bullet(mContext,mBitbullet);
  35. }
  36. mSendTime = System.currentTimeMillis();
  37. }


复制代码

更新游戏 更新2张背景地图坐标点,分别遍历子弹与敌机对象更新它们的逻辑

  1. private void updateBg() {
  2. /** 更新游戏背景图片实现向下滚动效果 **/
  3. mBitposY0 += 10;
  4. mBitposY1 += 10;
  5. if (mBitposY0 == mScreenHeight) {
  6. mBitposY0 = -mScreenHeight;
  7. }
  8. if (mBitposY1 == mScreenHeight) {
  9. mBitposY1 = -mScreenHeight;
  10. }
  11. /** 手指触摸屏幕更新飞机坐标 **/
  12. if (mTouching) {
  13. if (mAirPosX < mTouchPosX) {
  14. mAirPosX += PLAN_STEP;
  15. } else {
  16. mAirPosX -= PLAN_STEP;
  17. }
  18. if (mAirPosY < mTouchPosY) {
  19. mAirPosY += PLAN_STEP;
  20. } else {
  21. mAirPosY -= PLAN_STEP;
  22. }
  23. if (Math.abs(mAirPosX – mTouchPosX) <= PLAN_STEP) {
  24. mAirPosX = mTouchPosX;
  25. }
  26. if (Math.abs(mAirPosY – mTouchPosY) <= PLAN_STEP) {
  27. mAirPosY = mTouchPosY;
  28. }
  29. }
  30. /** 更新子弹动画 **/
  31. for (int i = 0; i < BULLET_POOL_COUNT; i++) {
  32. /** 子弹出屏后重新赋值**/
  33. mBuilet[i].UpdateBullet();
  34. }
  35. /**绘制敌人动画**/
  36. for(int i =0; i< ENEMY_POOL_COUNT; i++) {
  37. mEnemy[i].UpdateEnemy();
  38. /**敌机死亡 或者 敌机超过屏幕还未死亡重置坐标**/
  39. if(mEnemy[i].mState == Enemy.ENEMY_DEATH_STATE || mEnemy[i].m_posY >=mScreenHeight) {
  40. mEnemy[i].init(UtilRandom(0,ENEMY_POOL_COUNT) *ENEMY_POS_OFF, 0);
  41. }
  42. }
  43. /**根据时间初始化为发射的子弹**/
  44. if (mSendId < BULLET_POOL_COUNT) {
  45. long now = System.currentTimeMillis();
  46. if (now – mSendTime >= PLAN_TIME) {
  47. mBuilet[mSendId].init(mAirPosX – BULLET_LEFT_OFFSET, mAirPosY – BULLET_UP_OFFSET);
  48. mSendTime = now;
  49. mSendId++;
  50. }
  51. }else {
  52. mSendId = 0;
  53. }
  54. //更新子弹与敌人的碰撞
  55. Collision();
  56. }


复制代码

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注