本节课学习目标每消灭 20 个敌人出现一个 BossBoss 有独立的大血条显示在屏幕顶部Boss 有两个技能冲刺和发射弹幕击败 Boss 掉落大量奖励第一步创建 Boss 类右键项目 →添加→类文件名Boss.csusing Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; namespace MY_FIRST_GAME { public class Boss { public Vector2 Position; public int Hp; public int MaxHp; public int Attack; public int ScoreValue 500; public bool IsAlive true; public float Speed 100f; private Texture2D texture; private float skillTimer; private float skillInterval 2f; private Random rng; private Vector2 moveDirection; private float changeTimer; // Boss 子弹 public ListEnemyBullet Bullets new ListEnemyBullet(); public Boss(Vector2 position, int wave, GraphicsDevice graphicsDevice) { Position position; MaxHp 200 wave * 50; Hp MaxHp; Attack 20 wave * 5; rng new Random(); // 创建 Boss 纹理大型红色菱形 int size 64; texture new Texture2D(graphicsDevice, size, size); Color[] data new Color[size * size]; for (int y 0; y size; y) { for (int x 0; x size; x) { float dx Math.Abs(x - size / 2f); float dy Math.Abs(y - size / 2f); if (dx dy size / 2f) data[y * size x] Color.Red; else if (dx dy size / 2f 4) data[y * size x] Color.DarkRed; else data[y * size x] Color.Transparent; } } texture.SetData(data); moveDirection new Vector2(1, 0); } public void Update(float deltaTime, Vector2 playerPosition, TileMap tileMap) { skillTimer deltaTime; changeTimer deltaTime; // 追踪玩家 Vector2 toPlayer playerPosition - Position; if (toPlayer.Length() 0) moveDirection Vector2.Normalize(toPlayer); Vector2 newPos Position moveDirection * Speed * deltaTime; if (!tileMap.IsWall(newPos)) Position newPos; // ★ 技能1冲刺每3秒 if (skillTimer 3f skillTimer 3.3f) { Speed 300f; } else { Speed 100f; } // ★ 技能2弹幕每2秒 if (skillTimer skillInterval) { skillTimer 0f; skillInterval 1.5f (float)rng.NextDouble(); ShootBullets(playerPosition); } // 更新 Boss 子弹 foreach (EnemyBullet bullet in Bullets) bullet.Update(deltaTime, tileMap); Bullets.RemoveAll(b !b.IsAlive); } private void ShootBullets(Vector2 playerPosition) { int bulletCount 8; float baseAngle MathF.Atan2( playerPosition.Y - Position.Y, playerPosition.X - Position.X); for (int i 0; i bulletCount; i) { float angle baseAngle (i - bulletCount / 2f) * 0.3f; Vector2 direction new Vector2(MathF.Cos(angle), MathF.Sin(angle)); Bullets.Add(new EnemyBullet(Position, direction)); } } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, Position, null, Color.White, 0f, new Vector2(texture.Width / 2, texture.Height / 2), 1f, SpriteEffects.None, 0f); // 画 Boss 子弹 foreach (EnemyBullet bullet in Bullets) bullet.Draw(spriteBatch); } public Rectangle GetBounds() { return new Rectangle( (int)(Position.X - texture.Width / 2), (int)(Position.Y - texture.Height / 2), texture.Width, texture.Height); } // 画 Boss 大血条 public void DrawHealthBar(SpriteBatch spriteBatch, GraphicsDevice device, SpriteFontBase font) { int barWidth 400; int barHeight 20; int barX 200; int barY 5; Texture2D pixel new Texture2D(device, 1, 1); pixel.SetData(new[] { Color.White }); // 背景 spriteBatch.Draw(pixel, new Rectangle(barX, barY, barWidth, barHeight), Color.DarkSlateGray); // 血量 float percent (float)Hp / MaxHp; int currentWidth (int)(barWidth * percent); spriteBatch.Draw(pixel, new Rectangle(barX, barY, currentWidth, barHeight), Color.Red); // 边框 spriteBatch.Draw(pixel, new Rectangle(barX, barY, barWidth, 2), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX, barY barHeight - 2, barWidth, 2), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX, barY, 2, barHeight), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX barWidth - 2, barY, 2, barHeight), Color.White); // Boss 名字 spriteBatch.DrawString(font, BOSS, new Vector2(barX, barY - 20), Color.Red); spriteBatch.DrawString(font, ${Hp}/{MaxHp}, new Vector2(barX barWidth 10, barY 2), Color.White); } } // ★ Boss 的子弹 public class EnemyBullet { public Vector2 Position; public Vector2 Velocity; public float Speed 150f; public bool IsAlive true; public float Lifetime 3f; private float timer; private Texture2D texture; public EnemyBullet(Vector2 startPosition, Vector2 direction) { Position startPosition; Velocity direction * Speed; // 创建纹理懒加载 } // 初始化纹理需要 GraphicsDevice public void CreateTexture(GraphicsDevice device) { if (texture null) { texture new Texture2D(device, 10, 10); Color[] data new Color[100]; for (int y 0; y 10; y) for (int x 0; x 10; x) { float dx x - 4.5f; float dy y - 4.5f; if (dx * dx dy * dy 25) data[y * 10 x] Color.Orange; else data[y * 10 x] Color.Transparent; } texture.SetData(data); } } public void Update(float deltaTime, TileMap tileMap) { Position Velocity * deltaTime; timer deltaTime; if (timer Lifetime || tileMap.IsWall(Position)) IsAlive false; } public void Draw(SpriteBatch spriteBatch) { if (texture ! null) spriteBatch.Draw(texture, Position, null, Color.White, 0f, new Vector2(5, 5), 1f, SpriteEffects.None, 0f); } public Rectangle GetBounds() { return new Rectangle((int)Position.X - 5, (int)Position.Y - 5, 10, 10); } } }第二步改造Game1.cs1. 添加字段csharpprivate Boss? currentBoss null; private int enemiesKilledThisWave; private int enemiesPerBoss 20; private int bossWave;2. 在InitializeGame里重置csharpcurrentBoss null; enemiesKilledThisWave 0; bossWave 0;3. 修改击杀敌人后的逻辑在CheckBulletEnemyCollision的击杀分支里加csharpenemiesKilledThisWave; // ★ 检测是否该出 Boss if (enemiesKilledThisWave enemiesPerBoss currentBoss null) { SpawnBoss(); enemiesKilledThisWave 0; }4. 添加生成 Boss 方法csharpprivate void SpawnBoss() { bossWave; Vector2 bossPos tileMap.GetRandomEmptyPosition(rng); currentBoss new Boss(bossPos, bossWave, GraphicsDevice); floatingTexts.Add(new FloatingText(bossPos, $BOSS Lv.{bossWave} 出现了, Color.Red, 2.5f)); }5. 在Update的 Game 状态里更新 Bosscsharp// 更新 Boss if (currentBoss ! null currentBoss.IsAlive) { currentBoss.Update(deltaTime, player.Position, tileMap); // 初始化 Boss 子弹纹理 foreach (EnemyBullet bullet in currentBoss.Bullets) bullet.CreateTexture(GraphicsDevice); // Boss 子弹打中玩家 Rectangle playerRect player.GetBounds(); for (int i currentBoss.Bullets.Count - 1; i 0; i--) { if (currentBoss.Bullets[i].GetBounds().Intersects(playerRect)) { player.Hp - currentBoss.Attack / 2; currentBoss.Bullets[i].IsAlive false; } } // 玩家子弹打中 Boss for (int i bullets.Count - 1; i 0; i--) { if (bullets[i].GetBounds().Intersects(currentBoss.GetBounds())) { currentBoss.Hp - bullets[i].Damage; bullets[i].IsAlive false; if (currentBoss.Hp 0) { currentBoss.IsAlive false; score currentBoss.ScoreValue; enemiesDefeatedThisGame; enemiesKilledThisWave 0; // 大量掉落 for (int d 0; d 8; d) pickups.Add(new Pickup( currentBoss.Position new Vector2( rng.Next(-50, 50), rng.Next(-50, 50)), rng.Next(2) 0 ? PickupType.Health : PickupType.Power, GraphicsDevice)); floatingTexts.Add(new FloatingText(currentBoss.Position, $BOSS 被击败{currentBoss.ScoreValue}分, Color.Gold, 2f)); currentBoss null; } } } }6. 在DrawGameWorld里画 Bosscsharpif (currentBoss ! null currentBoss.IsAlive) currentBoss.Draw(_spriteBatch);7. 在DrawGameUI上面画 Boss 血条在 UI 的 Begin 里csharp// ★ Boss 血条 if (currentBoss ! null currentBoss.IsAlive) { currentBoss.DrawHealthBar(_spriteBatch, GraphicsDevice, font); }本节课学习到此结束关注我我叫魔法阵维护师下期更精彩
从零开发游戏需要学习的c#模块,第三十二章(Boss 战系统)
本节课学习目标每消灭 20 个敌人出现一个 BossBoss 有独立的大血条显示在屏幕顶部Boss 有两个技能冲刺和发射弹幕击败 Boss 掉落大量奖励第一步创建 Boss 类右键项目 →添加→类文件名Boss.csusing Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; namespace MY_FIRST_GAME { public class Boss { public Vector2 Position; public int Hp; public int MaxHp; public int Attack; public int ScoreValue 500; public bool IsAlive true; public float Speed 100f; private Texture2D texture; private float skillTimer; private float skillInterval 2f; private Random rng; private Vector2 moveDirection; private float changeTimer; // Boss 子弹 public ListEnemyBullet Bullets new ListEnemyBullet(); public Boss(Vector2 position, int wave, GraphicsDevice graphicsDevice) { Position position; MaxHp 200 wave * 50; Hp MaxHp; Attack 20 wave * 5; rng new Random(); // 创建 Boss 纹理大型红色菱形 int size 64; texture new Texture2D(graphicsDevice, size, size); Color[] data new Color[size * size]; for (int y 0; y size; y) { for (int x 0; x size; x) { float dx Math.Abs(x - size / 2f); float dy Math.Abs(y - size / 2f); if (dx dy size / 2f) data[y * size x] Color.Red; else if (dx dy size / 2f 4) data[y * size x] Color.DarkRed; else data[y * size x] Color.Transparent; } } texture.SetData(data); moveDirection new Vector2(1, 0); } public void Update(float deltaTime, Vector2 playerPosition, TileMap tileMap) { skillTimer deltaTime; changeTimer deltaTime; // 追踪玩家 Vector2 toPlayer playerPosition - Position; if (toPlayer.Length() 0) moveDirection Vector2.Normalize(toPlayer); Vector2 newPos Position moveDirection * Speed * deltaTime; if (!tileMap.IsWall(newPos)) Position newPos; // ★ 技能1冲刺每3秒 if (skillTimer 3f skillTimer 3.3f) { Speed 300f; } else { Speed 100f; } // ★ 技能2弹幕每2秒 if (skillTimer skillInterval) { skillTimer 0f; skillInterval 1.5f (float)rng.NextDouble(); ShootBullets(playerPosition); } // 更新 Boss 子弹 foreach (EnemyBullet bullet in Bullets) bullet.Update(deltaTime, tileMap); Bullets.RemoveAll(b !b.IsAlive); } private void ShootBullets(Vector2 playerPosition) { int bulletCount 8; float baseAngle MathF.Atan2( playerPosition.Y - Position.Y, playerPosition.X - Position.X); for (int i 0; i bulletCount; i) { float angle baseAngle (i - bulletCount / 2f) * 0.3f; Vector2 direction new Vector2(MathF.Cos(angle), MathF.Sin(angle)); Bullets.Add(new EnemyBullet(Position, direction)); } } public void Draw(SpriteBatch spriteBatch) { spriteBatch.Draw(texture, Position, null, Color.White, 0f, new Vector2(texture.Width / 2, texture.Height / 2), 1f, SpriteEffects.None, 0f); // 画 Boss 子弹 foreach (EnemyBullet bullet in Bullets) bullet.Draw(spriteBatch); } public Rectangle GetBounds() { return new Rectangle( (int)(Position.X - texture.Width / 2), (int)(Position.Y - texture.Height / 2), texture.Width, texture.Height); } // 画 Boss 大血条 public void DrawHealthBar(SpriteBatch spriteBatch, GraphicsDevice device, SpriteFontBase font) { int barWidth 400; int barHeight 20; int barX 200; int barY 5; Texture2D pixel new Texture2D(device, 1, 1); pixel.SetData(new[] { Color.White }); // 背景 spriteBatch.Draw(pixel, new Rectangle(barX, barY, barWidth, barHeight), Color.DarkSlateGray); // 血量 float percent (float)Hp / MaxHp; int currentWidth (int)(barWidth * percent); spriteBatch.Draw(pixel, new Rectangle(barX, barY, currentWidth, barHeight), Color.Red); // 边框 spriteBatch.Draw(pixel, new Rectangle(barX, barY, barWidth, 2), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX, barY barHeight - 2, barWidth, 2), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX, barY, 2, barHeight), Color.White); spriteBatch.Draw(pixel, new Rectangle(barX barWidth - 2, barY, 2, barHeight), Color.White); // Boss 名字 spriteBatch.DrawString(font, BOSS, new Vector2(barX, barY - 20), Color.Red); spriteBatch.DrawString(font, ${Hp}/{MaxHp}, new Vector2(barX barWidth 10, barY 2), Color.White); } } // ★ Boss 的子弹 public class EnemyBullet { public Vector2 Position; public Vector2 Velocity; public float Speed 150f; public bool IsAlive true; public float Lifetime 3f; private float timer; private Texture2D texture; public EnemyBullet(Vector2 startPosition, Vector2 direction) { Position startPosition; Velocity direction * Speed; // 创建纹理懒加载 } // 初始化纹理需要 GraphicsDevice public void CreateTexture(GraphicsDevice device) { if (texture null) { texture new Texture2D(device, 10, 10); Color[] data new Color[100]; for (int y 0; y 10; y) for (int x 0; x 10; x) { float dx x - 4.5f; float dy y - 4.5f; if (dx * dx dy * dy 25) data[y * 10 x] Color.Orange; else data[y * 10 x] Color.Transparent; } texture.SetData(data); } } public void Update(float deltaTime, TileMap tileMap) { Position Velocity * deltaTime; timer deltaTime; if (timer Lifetime || tileMap.IsWall(Position)) IsAlive false; } public void Draw(SpriteBatch spriteBatch) { if (texture ! null) spriteBatch.Draw(texture, Position, null, Color.White, 0f, new Vector2(5, 5), 1f, SpriteEffects.None, 0f); } public Rectangle GetBounds() { return new Rectangle((int)Position.X - 5, (int)Position.Y - 5, 10, 10); } } }第二步改造Game1.cs1. 添加字段csharpprivate Boss? currentBoss null; private int enemiesKilledThisWave; private int enemiesPerBoss 20; private int bossWave;2. 在InitializeGame里重置csharpcurrentBoss null; enemiesKilledThisWave 0; bossWave 0;3. 修改击杀敌人后的逻辑在CheckBulletEnemyCollision的击杀分支里加csharpenemiesKilledThisWave; // ★ 检测是否该出 Boss if (enemiesKilledThisWave enemiesPerBoss currentBoss null) { SpawnBoss(); enemiesKilledThisWave 0; }4. 添加生成 Boss 方法csharpprivate void SpawnBoss() { bossWave; Vector2 bossPos tileMap.GetRandomEmptyPosition(rng); currentBoss new Boss(bossPos, bossWave, GraphicsDevice); floatingTexts.Add(new FloatingText(bossPos, $BOSS Lv.{bossWave} 出现了, Color.Red, 2.5f)); }5. 在Update的 Game 状态里更新 Bosscsharp// 更新 Boss if (currentBoss ! null currentBoss.IsAlive) { currentBoss.Update(deltaTime, player.Position, tileMap); // 初始化 Boss 子弹纹理 foreach (EnemyBullet bullet in currentBoss.Bullets) bullet.CreateTexture(GraphicsDevice); // Boss 子弹打中玩家 Rectangle playerRect player.GetBounds(); for (int i currentBoss.Bullets.Count - 1; i 0; i--) { if (currentBoss.Bullets[i].GetBounds().Intersects(playerRect)) { player.Hp - currentBoss.Attack / 2; currentBoss.Bullets[i].IsAlive false; } } // 玩家子弹打中 Boss for (int i bullets.Count - 1; i 0; i--) { if (bullets[i].GetBounds().Intersects(currentBoss.GetBounds())) { currentBoss.Hp - bullets[i].Damage; bullets[i].IsAlive false; if (currentBoss.Hp 0) { currentBoss.IsAlive false; score currentBoss.ScoreValue; enemiesDefeatedThisGame; enemiesKilledThisWave 0; // 大量掉落 for (int d 0; d 8; d) pickups.Add(new Pickup( currentBoss.Position new Vector2( rng.Next(-50, 50), rng.Next(-50, 50)), rng.Next(2) 0 ? PickupType.Health : PickupType.Power, GraphicsDevice)); floatingTexts.Add(new FloatingText(currentBoss.Position, $BOSS 被击败{currentBoss.ScoreValue}分, Color.Gold, 2f)); currentBoss null; } } } }6. 在DrawGameWorld里画 Bosscsharpif (currentBoss ! null currentBoss.IsAlive) currentBoss.Draw(_spriteBatch);7. 在DrawGameUI上面画 Boss 血条在 UI 的 Begin 里csharp// ★ Boss 血条 if (currentBoss ! null currentBoss.IsAlive) { currentBoss.DrawHealthBar(_spriteBatch, GraphicsDevice, font); }本节课学习到此结束关注我我叫魔法阵维护师下期更精彩