从零开发游戏需要学习的c#模块,第三十二章(Boss 战系统)

本节课学习目标

  1. 每消灭 20 个敌人,出现一个 Boss

  2. Boss 有独立的大血条显示在屏幕顶部

  3. Boss 有两个技能:冲刺和发射弹幕

  4. 击败 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);
}

本节课学习到此结束,关注我,我叫魔法阵维护师,下期更精彩!

相关推荐
洵有兮1 小时前
Shell 脚本编程学习总结(基础 + 变量 + 条件 + 流程控制 + 函数数组)
linux·学习
2501_940041741 小时前
A Curated Archive of Tech & Culture / 科技与文化精选档案
游戏
吃好睡好便好1 小时前
矩阵的左乘和右乘
人工智能·学习·线性代数·算法·matlab·矩阵
我命由我123451 小时前
SEO 与 GEO 极简理解
java·linux·运维·开发语言·学习·算法·运维开发
段一凡-华北理工大学2 小时前
工业领域的Hadoop架构学习~系列文章04:YARN资源调度架构
人工智能·hadoop·学习·架构·系统架构·高炉炼铁·高炉炼铁智能化
爱上好庆祝2 小时前
学习JS第十二天
学习
Upsy-Daisy2 小时前
IOTA 学习笔记(一):IOTA 是什么?从区块链到 Tangle
笔记·学习·区块链
魔法阵维护师2 小时前
从零开发游戏需要学习的c#模块,第三十三章(暂停菜单)
学习·游戏·c#
Upsy-Daisy2 小时前
IOTA 学习笔记(二):DAG 与 Tangle 到底是什么?
笔记·学习