本节课学习目标
-
每消灭 20 个敌人,出现一个 Boss
-
Boss 有独立的大血条显示在屏幕顶部
-
Boss 有两个技能:冲刺和发射弹幕
-
击败 Boss 掉落大量奖励
第一步:创建 Boss 类
右键项目 → 添加 → 类 ,文件名 Boss.cs:
cs
using 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 List<EnemyBullet> Bullets = new List<EnemyBullet>();
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.cs
1. 添加字段:
csharp
private Boss? currentBoss = null;
private int enemiesKilledThisWave;
private int enemiesPerBoss = 20;
private int bossWave;
2. 在 InitializeGame 里重置:
csharp
currentBoss = null;
enemiesKilledThisWave = 0;
bossWave = 0;
3. 修改击杀敌人后的逻辑:
在 CheckBulletEnemyCollision 的击杀分支里加:
csharp
enemiesKilledThisWave++;
// ★ 检测是否该出 Boss
if (enemiesKilledThisWave >= enemiesPerBoss && currentBoss == null)
{
SpawnBoss();
enemiesKilledThisWave = 0;
}
4. 添加生成 Boss 方法:
csharp
private 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 状态里更新 Boss:
csharp
// 更新 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 里画 Boss:
csharp
if (currentBoss != null && currentBoss.IsAlive)
currentBoss.Draw(_spriteBatch);
7. 在 DrawGameUI 上面画 Boss 血条(在 UI 的 Begin 里):
csharp
// ★ Boss 血条
if (currentBoss != null && currentBoss.IsAlive)
{
currentBoss.DrawHealthBar(_spriteBatch, GraphicsDevice, font);
}
本节课学习到此结束,关注我,我叫魔法阵维护师,下期更精彩!