打造你的首款2D游戏:Fish Food游戏开发全解析
1. 游戏敌人类型的创建
1.1 基于PatrolEnemy类创建具体敌人类型
PatrolEnemy类经过修改后,能够支持多种行为,可作为游戏中各类敌人的基类。在游戏设计中,有三种不同类型的敌人,每种都有其独特的行为。通过改变PatrolEnemy类中
mCurrentEnemyType
变量的值,就能轻松表示这三种敌人类型。
1.1.1 JellyFish类的创建
public class JellyFish : PatrolEnemy
{
public JellyFish() :
base("ENEMY_3", Vector2.Zero,
new Vector2(kInitFishSize * kEnemyWidth + kEnemyWidth,
kInitFishSize * kEnemyWidth + kEnemyWidth), 2, 1, 0)
{
mAllowRotate = true;
mInitFrontDir = Vector2.UnitY;
mCurrentPatrolType = PatrolType.FreeRoam;
FishSize = kInitFishSize;
mCurrentEnemyType = EnemyType.JellyFish;
}
}
在创建JellyFish类时,继承自PatrolEnemy类。通过调用基类构造函数,使用正确的精灵图和相应设置。精灵图由两个精灵组成,所以行计数设为2,列计数设为1。同时,初始化了一些变量,赋予JellyFish所需的行为。
1.1.2 BlowFish类的创建
public class BlowFish : PatrolEnemy
{
public BlowFish() :
base("ENEMY_1", Vector2.Zero,
new Vector2(kInitFishSize * kEnemyWidth + kEnemyWidth,
kInitFishSize * kEnemyWidth + kEnemyWidth), 2, 2, 0)
{
mAllowRotate = false;
mInitFrontDir = Vector2.UnitX;
mCurrentPatrolType = PatrolType.UpDown;
FishSize = kInitFishSize;
mCurrentEnemyType = EnemyType.BlowFish;
}
}
BlowFish类同样继承自PatrolEnemy类。在基类构造函数中,由于BlowFish的精灵图由四个精灵组成,所以列计数设为2。初始化行为变量,使BlowFish具有特定的行为。
1.1.3 FightingFish类的创建
public class FightingFish : PatrolEnemy
{
public FightingFish() :
base("ENEMY_2", Vector2.Zero,
new Vector2(kInitFishSize * kEnemyWidth + kEnemyWidth,
kInitFishSize * kEnemyWidth + kEnemyWidth), 2, 2, 0)
{
mAllowRotate = false;
mInitFrontDir = Vector2.UnitX;
mCurrentPatrolType = PatrolType.LeftRight;
FishSize = kInitFishSize;
mCurrentEnemyType = EnemyType.FightingFish;
}
}
FightingFish类也是继承自PatrolEnemy类。传入正确的精灵图到基类构造函数,并初始化其行为变量,让FightingFish具备相应的行为。
1.2 PatrolEnemySet类的创建
为了组织敌人,控制它们的创建和销毁,需要创建PatrolEnemySet类。
1.2.1 类的基本结构
public enum EnemyType
{
BlowFish = 0,
JellyFish = 1,
FightingFish = 2
}
public class PatrolEnemySet
{
#region Particles
private const float kCollideParticleSize = 3f;
private const int kCollideParticleLife = 80;
private static ParticleSystem sCollisionEffect = new ParticleSystem();
// to support particle system
static private ParticlePrimitive CreateRedParticle(Vector2 pos)
{
return new ParticlePrimitive(pos, kCollideParticleSize, kCollideParticleLife);
}
static private ParticlePrimitive CreateDarkParticle(Vector2 pos)
{
return new DarkParticlePrimitive(pos,
kCollideParticleSize, kCollideParticleLife);
}
#endregion
private List<PatrolEnemy> mTheSet = new List<PatrolEnemy>();
private float mAddEnemyDistance = 100f;
//Constants
private const int kNumEnemies = 5;
...
}
在PatrolEnemySet类上方添加了
EnemyType
枚举,用于提供全局访问敌人类型。类中添加了静态变量和函数来支持粒子系统,还定义了一个常量
kNumEnemies
表示初始生成的敌人数量,以及一个列表
mTheSet
来存储所有敌人,还有一个变量
mAddEnemyDistance
表示添加新敌人的距离间隔。
1.2.2 构造函数
public PatrolEnemySet()
{
// Create many ...
for (int i = 0; i < kNumEnemies; i++)
{
PatrolEnemy e = SpawnRandomPatrolEnemy();
mTheSet.Add(e);
}
}
在构造函数中,通过循环调用
SpawnRandomPatrolEnemy()
函数创建敌人,并将它们添加到列表
mTheSet
中。
1.2.3 SpawnRandomPatrolEnemy()函数
public PatrolEnemy SpawnRandomPatrolEnemy()
{
int randNum = (int)(Game1.sRan.NextDouble() * 3);
PatrolEnemy enemy = null;
switch (randNum)
{
case (int)EnemyType.BlowFish:
enemy = new BlowFish();
break;
case (int)EnemyType.JellyFish:
enemy = new JellyFish();
break;
case (int)EnemyType.FightingFish:
enemy = new FightingFish();
break;
default:
break;
}
return enemy;
}
该函数通过生成一个随机数,根据随机数的值实例化相应类型的敌人。
1.2.4 UpdateSet()函数
public int UpdateSet(Hero hero)
{
int count = 0;
Vector2 touchPos;
//Add an enemy at 100m and every 50 after
//Should an additional enemy be added?
if (hero.PositionX / 20 > mAddEnemyDistance)
{
PatrolEnemy e = SpawnRandomPatrolEnemy();
mTheSet.Add(e);
mAddEnemyDistance += 50;
}
// destroy and respawn, update and collide with bubbles
for (int i = mTheSet.Count - 1; i >= 0; i--)
{
if (mTheSet[i].DestoryFlag)
{
mTheSet.Remove(mTheSet[i]);
mTheSet.Add(SpawnRandomPatrolEnemy());
continue;
}
if (mTheSet[i].UpdatePatrol(hero, out touchPos))
{
sCollisionEffect.AddEmitterAt(CreateRedParticle, touchPos);
count++;
}
List<BubbleShot> allBubbleShots = hero.AllBubbleShots();
int numBubbleShots = allBubbleShots.Count;
for (int j = numBubbleShots - 1; j >= 0; j--)
{
if (allBubbleShots[j].PixelTouches(mTheSet[i], out touchPos))
{
mTheSet[i].SetToStuntState();
allBubbleShots.RemoveAt(j);
sCollisionEffect.AddEmitterAt(CreateRedParticle, touchPos);
}
}
}
sCollisionEffect.UpdateParticles();
RespawnEnemies();
return count;
}
在
UpdateSet()
函数中,首先判断是否需要添加新敌人。然后遍历敌人列表,处理敌人的销毁和重生,更新敌人状态,检查敌人与英雄发射的气泡的碰撞。最后更新粒子系统并重生离开相机左侧的敌人。
1.2.5 RespawnEnemies()函数
// Respawn enemies that are off to the left side of the camera
public void RespawnEnemies()
{
for (int i = mTheSet.Count - 1; i >= 0; i--)
{
if (mTheSet[i].PositionX < (Camera.CameraWindowLowerLeftPosition.X - mTheSet[i].Width))
{
mTheSet.Remove(mTheSet[i]);
mTheSet.Add(SpawnRandomPatrolEnemy());
}
}
}
该函数用于重生离开相机左侧的敌人,通过检查敌人的X位置与相机左侧位置的关系,将离开相机左侧的敌人移除并重新生成。
1.2.6 DrawSet()函数
public void DrawSet()
{
foreach (var e in mTheSet)
e.Draw();
sCollisionEffect.DrawParticleSystem();
}
DrawSet()
函数用于绘制所有敌人和粒子系统。
1.3 总结
通过以上步骤,成功创建了三种不同类型的敌人(JellyFish、BlowFish、FightingFish),并使用PatrolEnemySet类对敌人进行管理,包括敌人的创建、更新、销毁、重生和绘制等操作。这些操作构成了游戏中敌人系统的基本框架,为游戏的玩法提供了重要的支持。
2. 游戏食物类的创建
2.1 FishFood类的基本结构
public class FishFood : SpritePrimitive
{
private const float kFoodSize = 8;
private bool mCanMove;
...
}
创建FishFood类,继承自SpritePrimitive类。添加了一个常量
kFoodSize
表示食物的大小,以及一个布尔标志
mCanMove
来跟踪食物的移动状态。
2.2 构造函数
public FishFood() :
base("WORM_1", Vector2.Zero, new Vector2(kFoodSize, kFoodSize), 2, 1, 0)
{
Position = RandomPosition(true);
SetSpriteAnimation(0, 0, 1, 1, 10);
mCanMove = true;
Speed = 0.2f;
}
在构造函数中,调用基类构造函数,使用提供的蠕虫精灵图初始化食物。设置食物的初始位置为随机位置,设置精灵动画的起始帧和滴答计时器。将
mCanMove
标志设置为
true
,表示食物可以移动,并设置食物的速度为0.2f。
2.3 Update()函数
public void Update(Hero hero, List<Platform> floor, List<Platform> seaweed)
{
if (Camera.CameraWindowUpperRightPosition.X > PositionX && mCanMove)
{
VelocityDirection = new Vector2(0, -1);
Speed = 0.2f;
base.Update();
}
if (Camera.CameraWindowUpperLeftPosition.X > PositionX)
{
Position = RandomPosition(true);
mCanMove = true;
}
Vector2 vec;
if (hero.PixelTouches(this, out vec))
{
Stop();
hero.Feed();
Position = RandomPosition(true);
mCanMove = true;
}
for (int i = 0; i < floor.Count; i++)
{
if (floor[i].PixelTouches(this, out vec))
{
Stop();
}
}
for (int i = 0; i < seaweed.Count; i++)
{
if (seaweed[i].PixelTouches(this, out vec))
{
Stop();
}
}
}
在
Update()
函数中,首先检查食物是否从屏幕右侧进入相机视野,如果是,则使食物向下移动。然后检查食物是否从屏幕左侧离开相机视野,如果是,则将食物移动到相机外的新位置。接着检查食物与英雄、海底和海藻的碰撞情况,如果碰撞则停止食物移动,若与英雄碰撞还会喂养英雄并将食物重新定位到相机外。
2.4 RandomPosition()函数
private Vector2 RandomPosition(bool offCamera)
{
Vector2 position;
float posX = (float)Game1.sRan.NextDouble() * Camera.Width * 0.80f + Camera.Width * 0.10f;
float posY = Camera.CameraWindowUpperRightPosition.Y;
if (offCamera)
posX += Camera.CameraWindowUpperRightPosition.X + Camera.Width*2;
position = new Vector2(posX, posY);
return position;
}
该函数用于计算食物在相机外的随机位置。由于希望食物从屏幕顶部落下,所以Y位置始终设置为相机的最大Y值。如果
offCamera
参数为
true
,则将X位置设置在相机右侧一定距离之外。
2.5 Stop()函数
private void Stop()
{
mCanMove = false;
Velocity = Vector2.Zero;
Speed = 0;
}
Stop()
函数用于停止食物的移动,将
mCanMove
标志设置为
false
,速度设置为0。
2.6 总结
通过创建FishFood类,实现了游戏中食物的基本功能,包括食物的生成、移动、碰撞检测和停止等操作。食物可以从屏幕顶部随机位置落下,当与英雄碰撞时会喂养英雄,与海底或海藻碰撞时会停止移动。这为游戏增加了一定的趣味性和交互性。
3. 游戏环境生成器类的创建
3.1 EnvironmentGenerator类的基本结构
public class EnvironmentGenerator
{
private List<Platform> mTheFloorSet;
private List<Platform> mTheSeaweedTallSet;
private List<Platform> mTheSeaweedSmallSet;
private Platform mTheSign;
private PatrolEnemySet mEnemies;
private FishFood mFishFood;
private int mOffsetCounter;
private const int kSectionSize = 800;
private const int kFloorAndRoofSize = 40;
private const int kSignSize = 30;
private const int kInitialSignPosX = 100;
private const int kSignUnitScaler = 20;
...
}
创建EnvironmentGenerator类,用于生成游戏环境。添加了几个列表来存储地板、高大海藻和小海藻的平台对象,以及一个标志对象、敌人集合和食物对象。同时定义了一些常量,用于控制环境的生成和布局。
3.2 初始化环境
public void InitializeEnvironment()
{
Camera.SetCameraWindow(Vector2.Zero, 300);
mOffsetCounter = -20;
mTheFloorSet = new List<Platform>();
mTheSeaweedTallSet = new List<Platform>();
mTheSeaweedSmallSet = new List<Platform>();
mEnemies = new PatrolEnemySet();
for (int i = 0; i < kSectionSize / kFloorAndRoofSize; i++)
{
mOffsetCounter += kFloorAndRoofSize;
mTheFloorSet.Add(new
Platform("GROUND_1",
new Vector2(mOffsetCounter, 20),
new Vector2(kFloorAndRoofSize, kFloorAndRoofSize)));
}
mTheSign = new Platform("SIGN_1",
new Vector2(kInitialSignPosX, kFloorAndRoofSize / 2 + kSignSize / 2),
new Vector2(kSignSize, kSignSize));
float randNum;
for (int i = 0; i < 5; i++)
{
randNum = (float)Game1.sRan.NextDouble() *
mTheFloorSet[mTheFloorSet.Count - 1].PositionX + kInitialSignPosX * 2;
mTheSeaweedTallSet.Add(new Platform("SEAWEEDTALL_1",
new Vector2(randNum, kFloorAndRoofSize),
new Vector2(kFloorAndRoofSize / 1.5f, kFloorAndRoofSize * 1.5f)));
}
for (int i = 0; i < 5; i++)
{
randNum = (float)Game1.sRan.NextDouble() *
mTheFloorSet[mTheFloorSet.Count - 1].PositionX;
mTheSeaweedSmallSet.Add(new Platform("SEAWEEDSMALL_1",
new Vector2(randNum, kFloorAndRoofSize / 2 - 5),
new Vector2(kFloorAndRoofSize / 2, kFloorAndRoofSize / 2)));
}
mFishFood = new FishFood();
}
InitializeEnvironment()
函数用于将环境设置为初始状态。设置相机窗口,创建地板、海藻、敌人、标志和食物对象。地板铺设在屏幕底部,高大海藻和小海藻随机分布在地板上。
3.3 更新环境
public void Update(Hero theHero)
{
mFishFood.Update(theHero, mTheFloorSet, mTheSeaweedTallSet);
mEnemies.UpdateSet(theHero);
if (Camera.CameraWindowLowerRightPosition.X >
mTheFloorSet[mTheFloorSet.Count - 1].Position.X)
{
for (int i = 0; i < mTheFloorSet.Count; i++)
{
mTheFloorSet[i].PositionX += kFloorAndRoofSize * 10;
}
float randNum;
for (int i = 0; i < mTheSeaweedTallSet.Count; i++)
{
if (mTheSeaweedTallSet[i].PositionX <
Camera.CameraWindowLowerLeftPosition.X - mTheSeaweedTallSet[i].Width)
{
randNum = (float)Game1.sRan.NextDouble() * kSectionSize / 2
+ Camera.CameraWindowLowerRightPosition.X;
mTheSeaweedTallSet[i].PositionX = randNum;
}
}
for (int i = 0; i < mTheSeaweedSmallSet.Count; i++)
{
if (mTheSeaweedSmallSet[i].PositionX <
Camera.CameraWindowLowerLeftPosition.X - mTheSeaweedTallSet[i].Width)
{
randNum = (float)Game1.sRan.NextDouble() * kSectionSize / 2
+ Camera.CameraWindowLowerRightPosition.X;
mTheSeaweedSmallSet[i].PositionX = randNum;
}
}
}
if ((Camera.CameraWindowLowerLeftPosition.X - mTheSign.Width) > mTheSign.PositionX)
{
if (mTheSign.PositionX == kInitialSignPosX)
mTheSign.PositionX = 0;
mTheSign.PositionX += 500;
}
}
Update()
函数用于支持地形的无限再生。当相机右侧到达最后一个地板瓷砖时,将整个地板数组向前移动,同时将离开相机左侧的海藻重新定位到相机右侧。标志也会在一定间隔后重新定位。
3.4 绘制环境
public void Draw()
{
for (int i = 0; i < mTheFloorSet.Count; i++)
mTheFloorSet[i].Draw();
mTheSign.Draw();
String msg = (mTheSign.Position.X / 20).ToString() + "m";
FontSupport.PrintStatusAt(mTheSign.Position, msg, Color.Black);
for (int i = 0; i < mTheSeaweedTallSet.Count; i++)
mTheSeaweedTallSet[i].Draw();
for (int i = 0; i < mTheSeaweedSmallSet.Count; i++)
mTheSeaweedSmallSet[i].Draw();
mEnemies.DrawSet();
mFishFood.Draw();
}
Draw()
函数用于绘制环境中的所有对象,包括地板、标志、海藻、敌人和食物。同时,通过标志的标签打印标志的位置信息。
3.5 总结
EnvironmentGenerator类实现了游戏环境的生成和管理,包括环境的初始化、更新和绘制。通过不断更新环境对象的位置,实现了地形的无限再生,为游戏提供了一个持续的游戏场景。
4. 游戏状态类的修改
4.1 GameState类的基本结构
public class GameState
{
public enum GameStates
{
StartScreen,
Playing,
Dead
}
private int mDistantTraveled = 0;
private GameStates mCurrentGameState;
private TexturedPrimitive mSplashScreen;
private TexturedPrimitive mGameOverScreen;
private EnvironmentGenerator mEnvironment;
private Hero mHero;
...
}
创建GameState类,添加了一个枚举
GameStates
表示游戏的三种不同状态(开始屏幕、游戏中、死亡)。同时添加了一些实例变量,用于存储游戏的各种状态和对象,如距离旅行、当前游戏状态、启动屏幕、游戏结束屏幕、环境生成器和英雄等。
4.2 构造函数
public GameState()
{
mCurrentGameState = GameStates.StartScreen;
AudioSupport.PlayBackgroundAudio("Mind_Meld", 0.5f);
InitializeStartMenu();
}
public void InitializeStartMenu()
{
float centerX = Camera.CameraWindowUpperRightPosition.X - Camera.Width/2;
float centerY = Camera.CameraWindowUpperRightPosition.Y- Camera.Height/2;
mSplashScreen = new TexturedPrimitive("SPLASHSCREEN_1",
new Vector2(centerX, centerY), new Vector2(Camera.Width, Camera.Height));
String msg = "Press the 'K' key to start.";
mSplashScreen.Label = msg;
mSplashScreen.LabelColor = Color.Black;
}
在构造函数中,将当前游戏状态设置为开始屏幕,播放背景音乐,并初始化开始菜单。开始菜单中设置了启动屏幕的位置和大小,并显示提示信息,告知用户如何开始游戏。
4.3 初始化游戏和游戏结束屏幕
public void InitializeGamePlay()
{
mHero = new Hero(new Vector2(20f, 30f));
mEnvironment = new EnvironmentGenerator();
}
public void InitializeGameOverScreen()
{
float centerX = Camera.CameraWindowUpperRightPosition.X - Camera.Width/2;
float centerY = Camera.CameraWindowUpperRightPosition.Y- Camera.Height/2;
mGameOverScreen = new TexturedPrimitive("GAMEOVERSCREEN_1",
new Vector2(centerX, centerY),
new Vector2(Camera.Width, Camera.Height));
String msg = mDistantTraveled + "m traveled. Press the 'K' key to try agian.";
mGameOverScreen.Label = msg;
mGameOverScreen.LabelColor = Color.Black;
}
InitializeGamePlay()
函数用于初始化游戏,实例化英雄和环境生成器。
InitializeGameOverScreen()
函数用于初始化游戏结束屏幕,显示玩家旅行的距离,并提示玩家如何重新开始游戏。
4.4 更新游戏状态
public void UpdateGame(GameTime gameTime)
{
switch(mCurrentGameState)
{
case GameStates.StartScreen:
UpdateStartScreen();
break;
case GameStates.Playing:
UpdateGamePlay(gameTime);
break;
case GameStates.Dead:
UpdateGameOverScreen();
break;
}
}
public void UpdateStartScreen()
{
if (InputWrapper.Buttons.A == ButtonState.Pressed)
{
mSplashScreen = null;
mCurrentGameState = GameStates.Playing;
InitializeGamePlay();
}
}
public void UpdateGameOverScreen()
{
if (InputWrapper.Buttons.A == ButtonState.Pressed)
{
mGameOverScreen = null;
mCurrentGameState = GameStates.Playing;
InitializeGamePlay();
}
}
public void UpdateGamePlay(GameTime gameTime)
{
mDistantTraveled = (int)mHero.PositionX / 20;
if (mHero.HasLost())
{
mCurrentGameState = GameStates.Dead;
AudioSupport.PlayACue("Break");
InitializeGameOverScreen();
return;
}
bool shootBubbleShot = (InputWrapper.Buttons.A == ButtonState.Pressed);
mHero.Update(gameTime, InputWrapper.ThumbSticks.Left, shootBubbleShot);
mEnvironment.Update(mHero);
#region hero moving the camera window
float kBuffer = mHero.Width * 5f;
float kHalfCameraSize = Camera.Width * 0.5f;
Vector2 delta = Vector2.Zero;
Vector2 cameraLL = Camera.CameraWindowLowerLeftPosition;
Vector2 cameraUR = Camera.CameraWindowUpperRightPosition;
const float kChaseRate = 0.05f;
if (mHero.PositionX > (cameraUR.X - kHalfCameraSize))
{
delta.X = (mHero.PositionX + kHalfCameraSize - cameraUR.X) * kChaseRate;
}
Camera.MoveCameraBy(delta);
#endregion
}
UpdateGame()
函数根据当前游戏状态调用相应的更新函数。
UpdateStartScreen()
和
UpdateGameOverScreen()
函数在检测到A按钮按下时,将游戏状态切换到游戏中,并初始化游戏。
UpdateGamePlay()
函数用于更新游戏中的各种状态,包括记录旅行距离、检查英雄是否失败、处理英雄的移动和气泡发射、更新环境和相机位置等。
4.5 绘制游戏状态
public void DrawGame()
{
switch (mCurrentGameState)
{
case GameStates.StartScreen:
if(mSplashScreen != null)
mSplashScreen.Draw();
break;
case GameStates.Playing:
mEnvironment.Draw();
mHero.Draw();
FontSupport.PrintStatus("Distance: " + mDistantTraveled
+ " Size: " + mHero.HeroSize, null);
break;
case GameStates.Dead:
if (mGameOverScreen != null)
mGameOverScreen.Draw();
break;
}
}
DrawGame()
函数根据当前游戏状态绘制相应的屏幕,包括开始屏幕、游戏中屏幕和游戏结束屏幕。在游戏中屏幕,还会显示玩家旅行的距离和英雄的大小。
4.6 总结
通过修改GameState类,实现了游戏状态的管理,包括游戏的启动、进行和结束。根据不同的游戏状态,处理相应的逻辑和绘制操作,为玩家提供了一个完整的游戏体验。
5. 游戏总结与扩展建议
5.1 游戏完成
经过以上步骤,成功完成了整个Fish Food游戏。这个游戏虽然具有一定的复杂度,但并不是非常困难。游戏中实现了英雄的自我保护机制(气泡射击)、英雄、敌人行为、敌人类型、敌人集合、英雄食物、游戏环境和游戏状态等功能。
5.2 游戏测试与反馈
在游戏实现后,需要进行测试,尝试看看能走多远,并尝试打破自己的记录。思考游戏给人的印象,如是否过于简单、难度如何、趣味性怎样等。这些反馈对于游戏的改进非常重要。
5.3 游戏扩展建议
游戏有很多可以改进的地方,以下是一些扩展建议:
-
内容方面
:可以增加更多的敌人类型、食物类型、英雄角色类型或环境类型,丰富游戏的内容。
-
游戏玩法和功能方面
:例如添加相机缩放功能、英雄或敌人的特殊能力、食物奖励等,提升游戏的可玩性。
-
效果方面
:优秀的游戏通常有很棒的效果,如令人满意的音效或粒子行为,可以在这些方面进行改进。
5.4 总结
通过不断地改进和扩展游戏,可以让游戏更加完善,为玩家带来更好的游戏体验。希望以上的开发过程和扩展建议能为游戏开发者提供一些参考和启发。
6. 流程图总结
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
A([开始]):::startend --> B(创建敌人类型):::process
B --> C(创建PatrolEnemySet类):::process
C --> D(创建FishFood类):::process
D --> E(创建EnvironmentGenerator类):::process
E --> F(修改GameState类):::process
F --> G(完成游戏):::process
G --> H{测试游戏}:::decision
H -->|不满意| I(根据建议扩展游戏):::process
H -->|满意| J([结束]):::startend
I --> H
这个流程图展示了游戏开发的主要步骤,从创建敌人类型开始,逐步完成游戏的各个部分,最后进行游戏测试。如果对游戏不满意,可以根据扩展建议进行改进,直到达到满意的效果。
7. 表格总结
| 类名 | 功能 | 主要方法 |
|---|---|---|
| JellyFish | 游戏中的水母敌人 | 构造函数初始化行为 |
| BlowFish | 游戏中的河豚敌人 | 构造函数初始化行为 |
| FightingFish | 游戏中的斗鱼敌人 | 构造函数初始化行为 |
| PatrolEnemySet | 管理敌人的集合 | SpawnRandomPatrolEnemy()、UpdateSet()、RespawnEnemies()、DrawSet() |
| FishFood | 游戏中的食物 | 构造函数、Update()、RandomPosition()、Stop() |
| EnvironmentGenerator | 生成游戏环境 | InitializeEnvironment()、Update()、Draw() |
| GameState | 管理游戏状态 | InitializeGamePlay()、InitializeGameOverScreen()、UpdateGame()、DrawGame() |
这个表格总结了游戏中主要类的功能和主要方法,方便开发者快速了解各个类的作用和使用方式。
通过以上的开发步骤和总结,我们完成了Fish Food游戏的开发,并对游戏的扩展和优化提出了一些建议。希望这些内容能帮助开发者更好地理解游戏开发的过程和方法。
8. 详细代码分析
8.1 敌人类型代码分析
8.1.1 基类与派生类关系
在游戏中,
PatrolEnemy
作为基类,
JellyFish
、
BlowFish
和
FightingFish
作为派生类继承自它。这种继承关系使得代码具有良好的可扩展性和可维护性。例如,当需要添加新的敌人类型时,只需要创建一个新的派生类并继承
PatrolEnemy
即可。
8.1.2 不同敌人的行为差异
不同的敌人类型通过在构造函数中设置不同的变量值来实现不同的行为。以下是不同敌人类型的行为差异对比表格:
| 敌人类型 | 允许旋转 | 初始方向 | 巡逻类型 |
| ---- | ---- | ---- | ---- |
| JellyFish | 是 | Vector2.UnitY | FreeRoam |
| BlowFish | 否 | Vector2.UnitX | UpDown |
| FightingFish | 否 | Vector2.UnitX | LeftRight |
8.2 PatrolEnemySet类代码分析
8.2.1 敌人管理逻辑
PatrolEnemySet
类负责管理敌人的集合,包括敌人的创建、更新、销毁和重生等操作。以下是该类的主要功能和操作步骤:
1.
初始化敌人集合
:在构造函数中,通过循环调用
SpawnRandomPatrolEnemy()
函数创建指定数量的敌人,并将它们添加到列表中。
2.
随机生成敌人
:
SpawnRandomPatrolEnemy()
函数通过生成随机数,根据随机数的值实例化相应类型的敌人。
3.
更新敌人集合
:
UpdateSet()
函数在每次更新时,判断是否需要添加新敌人,处理敌人的销毁和重生,更新敌人状态,检查敌人与英雄发射的气泡的碰撞,并更新粒子系统。
4.
重生离开相机的敌人
:
RespawnEnemies()
函数检查敌人的位置,将离开相机左侧的敌人移除并重新生成。
5.
绘制敌人和粒子系统
:
DrawSet()
函数用于绘制所有敌人和粒子系统。
8.2.2 粒子系统支持
PatrolEnemySet
类还支持粒子系统,通过静态变量和函数来创建和管理碰撞效果的粒子。例如,
CreateRedParticle()
和
CreateDarkParticle()
函数分别用于创建红色粒子和深色粒子。
8.3 FishFood类代码分析
8.3.1 食物的生成与移动
FishFood
类继承自
SpritePrimitive
类,实现了食物的生成、移动和碰撞检测等功能。以下是该类的主要操作步骤:
1.
构造函数初始化
:在构造函数中,调用基类构造函数,使用提供的蠕虫精灵图初始化食物。设置食物的初始位置为随机位置,设置精灵动画的起始帧和滴答计时器,并将食物的移动标志设置为
true
。
2.
食物移动逻辑
:
Update()
函数在每次更新时,检查食物是否从屏幕右侧进入相机视野,如果是,则使食物向下移动。如果食物从屏幕左侧离开相机视野,则将食物移动到相机外的新位置。
3.
碰撞检测
:在
Update()
函数中,检查食物与英雄、海底和海藻的碰撞情况。如果与英雄碰撞,则喂养英雄并将食物重新定位到相机外;如果与海底或海藻碰撞,则停止食物的移动。
4.
随机位置生成
:
RandomPosition()
函数用于计算食物在相机外的随机位置,确保食物从屏幕顶部落下。
5.
停止食物移动
:
Stop()
函数用于停止食物的移动,将移动标志设置为
false
,速度设置为 0。
8.4 EnvironmentGenerator类代码分析
8.4.1 环境初始化
EnvironmentGenerator
类负责生成和管理游戏环境,包括地板、海藻、标志、敌人和食物等对象。以下是该类的主要操作步骤:
1.
初始化环境对象
:在
InitializeEnvironment()
函数中,设置相机窗口,创建地板、海藻、敌人、标志和食物对象。地板铺设在屏幕底部,高大海藻和小海藻随机分布在地板上。
2.
环境更新逻辑
:
Update()
函数在每次更新时,更新食物和敌人的状态。当相机右侧到达最后一个地板瓷砖时,将整个地板数组向前移动,同时将离开相机左侧的海藻重新定位到相机右侧。标志也会在一定间隔后重新定位。
3.
环境绘制
:
Draw()
函数用于绘制环境中的所有对象,包括地板、标志、海藻、敌人和食物。同时,通过标志的标签打印标志的位置信息。
8.5 GameState类代码分析
8.5.1 游戏状态管理
GameState
类负责管理游戏的状态,包括游戏的启动、进行和结束。以下是该类的主要操作步骤:
1.
构造函数初始化
:在构造函数中,将当前游戏状态设置为开始屏幕,播放背景音乐,并初始化开始菜单。
2.
游戏初始化
:
InitializeGamePlay()
函数用于初始化游戏,实例化英雄和环境生成器。
3.
游戏结束屏幕初始化
:
InitializeGameOverScreen()
函数用于初始化游戏结束屏幕,显示玩家旅行的距离,并提示玩家如何重新开始游戏。
4.
游戏状态更新
:
UpdateGame()
函数根据当前游戏状态调用相应的更新函数。在游戏进行状态下,
UpdateGamePlay()
函数负责更新游戏中的各种状态,包括记录旅行距离、检查英雄是否失败、处理英雄的移动和气泡发射、更新环境和相机位置等。
5.
游戏状态绘制
:
DrawGame()
函数根据当前游戏状态绘制相应的屏幕,包括开始屏幕、游戏中屏幕和游戏结束屏幕。在游戏中屏幕,还会显示玩家旅行的距离和英雄的大小。
9. 开发过程中的注意事项
9.1 代码复用与扩展
在开发过程中,通过继承和组合的方式实现了代码的复用和扩展。例如,
PatrolEnemy
作为基类,
JellyFish
、
BlowFish
和
FightingFish
作为派生类继承自它,方便添加新的敌人类型。
PatrolEnemySet
类通过组合多个
PatrolEnemy
对象,实现了敌人集合的管理。
9.2 碰撞检测与处理
碰撞检测是游戏开发中的重要环节,在游戏中,通过
PixelTouches()
方法实现了食物与英雄、敌人与气泡等对象的碰撞检测。在碰撞发生时,需要及时处理相应的逻辑,如喂养英雄、停止食物移动、使敌人进入眩晕状态等。
9.3 性能优化
在处理大量对象的更新和绘制时,需要考虑性能优化。例如,在
UpdateSet()
函数中,使用逆序遍历敌人列表,避免在移除元素时出现索引错误。同时,合理使用随机数生成和位置计算,避免不必要的计算开销。
9.4 游戏状态管理
游戏状态的管理对于游戏的流畅运行至关重要。通过
GameState
类,将游戏分为开始屏幕、游戏中、死亡三种状态,并根据不同的状态处理相应的逻辑和绘制操作,确保游戏的正常进行。
10. 总结与展望
10.1 总结
通过以上的开发过程,我们完成了 Fish Food 游戏的开发,实现了英雄的自我保护机制、敌人行为、敌人类型、敌人集合、英雄食物、游戏环境和游戏状态等功能。通过对游戏的测试和反馈,我们可以发现游戏中存在的问题,并根据扩展建议对游戏进行改进和优化。
10.2 展望
未来可以进一步扩展游戏的功能和内容,例如:
-
增加关卡和难度级别
:设计不同的关卡,每个关卡具有不同的敌人类型和难度,增加游戏的挑战性。
-
添加更多的交互元素
:除了现有的食物和敌人,还可以添加更多的交互元素,如道具、陷阱等,丰富游戏的玩法。
-
优化游戏画面和音效
:提升游戏的画面质量和音效效果,为玩家带来更好的视觉和听觉体验。
-
支持多人模式
:实现多人合作或对战模式,增加游戏的社交性和趣味性。
总之,游戏开发是一个不断迭代和优化的过程,通过持续的改进和创新,可以让游戏更加完善,吸引更多的玩家。
Fish Food 2D游戏开发详解
超级会员免费看

被折叠的 条评论
为什么被折叠?



