从零开发游戏需要学习的c#模块,第二十六章(多种敌人与基础 AI)

本节课目标

  1. 创建三种敌人:史莱姆(慢)、骷髅(中速追人)、蝙蝠(快速随机移动)

  2. 敌人有独立的行为逻辑

  3. 不同敌人有不同外观和属性

  4. 敌人在地图上自主移动


第一步:创建敌人基类

右键项目 → 添加 ,文件名 Enemy.cs

csharp

复制代码
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;

namespace MY_FIRST_GAME
{
    public enum EnemyType
    {
        Slime,    // 史莱姆:慢速随机移动
        Skeleton, // 骷髅:追踪玩家
        Bat       // 蝙蝠:快速随机移动
    }

    public class Enemy
    {
        public Vector2 Position;
        public EnemyType Type;
        public int Hp;
        public int MaxHp;
        public int Attack;
        public int ScoreValue;
        public float Speed;
        public bool IsAlive;

        protected Texture2D texture;
        protected Color color;
        protected Random rng;
        protected float changeTimer;
        protected float changeInterval;
        protected Vector2 moveDirection;

        public Enemy(EnemyType type, Vector2 position, GraphicsDevice graphicsDevice)
        {
            Type = type;
            Position = position;
            IsAlive = true;
            rng = new Random();
            changeTimer = 0f;
            changeInterval = (float)(rng.NextDouble() * 2 + 1);

            // 根据类型设置属性
            switch (type)
            {
                case EnemyType.Slime:
                    Hp = 30; MaxHp = 30; Attack = 8;
                    ScoreValue = 30; Speed = 40f;
                    color = Color.Green;
                    break;
                case EnemyType.Skeleton:
                    Hp = 60; MaxHp = 60; Attack = 15;
                    ScoreValue = 80; Speed = 80f;
                    color = Color.White;
                    break;
                case EnemyType.Bat:
                    Hp = 15; MaxHp = 15; Attack = 5;
                    ScoreValue = 20; Speed = 150f;
                    color = Color.Purple;
                    break;
            }

            // 创建纹理
            int size = (type == EnemyType.Bat) ? 24 : 40;
            texture = new Texture2D(graphicsDevice, size, size);
            Color[] data = new Color[size * size];

            if (type == EnemyType.Bat)
            {
                // 蝙蝠是三角形
                for (int y = 0; y < size; y++)
                    for (int x = 0; x < size; x++)
                    {
                        if (Math.Abs(x - size / 2) < y * 0.8f && y < size * 0.7f)
                            data[y * size + x] = color;
                        else
                            data[y * size + x] = Color.Transparent;
                    }
            }
            else if (type == EnemyType.Skeleton)
            {
                // 骷髅是菱形
                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;
                        else
                            data[y * size + x] = Color.Transparent;
                    }
            }
            else
            {
                // 史莱姆是圆形
                for (int y = 0; y < size; y++)
                    for (int x = 0; x < size; x++)
                    {
                        float dx = x - size / 2f;
                        float dy = y - size / 2f;
                        if (dx * dx + dy * dy < (size / 2f) * (size / 2f))
                            data[y * size + x] = color;
                        else
                            data[y * size + x] = Color.Transparent;
                    }
            }
            texture.SetData(data);

            // 初始随机方向
            float angle = (float)(rng.NextDouble() * Math.PI * 2);
            moveDirection = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
        }

        public virtual void Update(float deltaTime, Vector2 playerPosition, TileMap tileMap)
        {
            changeTimer += deltaTime;

            switch (Type)
            {
                case EnemyType.Slime:
                    UpdateSlime(deltaTime, tileMap);
                    break;
                case EnemyType.Skeleton:
                    UpdateSkeleton(deltaTime, playerPosition, tileMap);
                    break;
                case EnemyType.Bat:
                    UpdateBat(deltaTime, tileMap);
                    break;
            }
        }

        // ★ 史莱姆:慢速随机移动
        private void UpdateSlime(float deltaTime, TileMap tileMap)
        {
            if (changeTimer >= changeInterval)
            {
                changeTimer = 0f;
                changeInterval = (float)(rng.NextDouble() * 3 + 1);
                float angle = (float)(rng.NextDouble() * Math.PI * 2);
                moveDirection = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
            }

            Vector2 newPos = Position + moveDirection * Speed * deltaTime;
            if (!tileMap.IsWall(newPos))
                Position = newPos;
            else
                changeTimer = changeInterval; // 撞墙就换方向
        }

        // ★ 骷髅:追踪玩家
        private void UpdateSkeleton(float deltaTime, Vector2 playerPosition, TileMap tileMap)
        {
            Vector2 toPlayer = playerPosition - Position;
            if (toPlayer.Length() > 0)
            {
                moveDirection = Vector2.Normalize(toPlayer);
            }

            Vector2 newPos = Position + moveDirection * Speed * deltaTime;
            if (!tileMap.IsWall(newPos))
                Position = newPos;
        }

        // ★ 蝙蝠:快速随机移动,频繁换向
        private void UpdateBat(float deltaTime, TileMap tileMap)
        {
            if (changeTimer >= changeInterval)
            {
                changeTimer = 0f;
                changeInterval = (float)(rng.NextDouble() * 0.5 + 0.3);
                float angle = (float)(rng.NextDouble() * Math.PI * 2);
                moveDirection = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
            }

            Vector2 newPos = Position + moveDirection * Speed * deltaTime;
            if (!tileMap.IsWall(newPos))
                Position = newPos;
            else
                changeTimer = changeInterval;
        }

        public virtual void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture, Position, null, Color.White,
                0f, new Vector2(texture.Width / 2, texture.Height / 2),
                1f, SpriteEffects.None, 0f);
        }

        public Rectangle GetBounds()
        {
            return new Rectangle(
                (int)(Position.X - texture.Width / 2),
                (int)(Position.Y - texture.Height / 2),
                texture.Width, texture.Height);
        }
    }
}

第二步:改造 Game1.cs

以下是需要修改的部分。为了节省篇幅,我只列出改动的地方,你按位置替换即可。

1. 替换敌人列表类型:

// 旧的
private List<Vector2> enemies = default!;

// 新的
private List<Enemy> enemies = default!;

2. 替换 SpawnEnemies 方法:

cs 复制代码
private void SpawnEnemies(int count)
{
    for (int i = 0; i < count; i++)
    {
        Vector2 pos = tileMap.GetRandomEmptyPosition(rng);
        EnemyType type = (EnemyType)(rng.Next(3)); // 随机类型
        enemies.Add(new Enemy(type, pos, GraphicsDevice));
    }
}

3. 在 UpdateGame 状态下更新敌人 AI:

cs 复制代码
// 在 CheckEnemyCollision 之前加
foreach (Enemy enemy in enemies)
    enemy.Update(deltaTime, player.Position, tileMap);

4. 替换 CheckEnemyCollision 方法:

cs 复制代码
private void CheckEnemyCollision()
{
    Rectangle playerRect = player.GetBounds();
    for (int i = enemies.Count - 1; i >= 0; i--)
    {
        if (enemies[i].GetBounds().Intersects(playerRect))
        {
            Enemy enemy = enemies[i];
            enemies.RemoveAt(i);
            score += enemy.ScoreValue;
            enemiesDefeatedThisGame++;
            player.Hp -= enemy.Attack;
            particleSystem.EmitHitParticles(enemy.Position);
            try { hitSound?.Play(); } catch { }
        }
    }
}

5. 替换 DrawGameWorld 里的敌人绘制:

cs 复制代码
foreach (Enemy enemy in enemies)
    enemy.Draw(_spriteBatch);

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

相关推荐
lzhdim37 分钟前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
那年窗外下的雪.2 小时前
AIDC 学习日志|第 15 天|EVPN 多归属故障收敛与撤销机制
网络·学习·php
惊鸿醉2 小时前
Unity 实战:用讯飞 WebAPI 做一个“说普通话、播方言“的语音程序
unity·c#·游戏引擎·语音识别
电脑玩家柒柒3 小时前
FreeToken:在游戏 PC 上跑 290B+ 前沿 MoE——带宽自适应执行的本地推理新范式
游戏
M78佐菲3 小时前
Linux学习笔记:进程
linux·笔记·学习·算法
略略略咯咯3 小时前
C#Unity
开发语言·unity·c#
我命由我123455 小时前
人脸识别 - 勒克斯(光线照射到物体表面上的明亮程度)
android·java·学习·java-ee·人脸识别·学习方法·android runtime
Behavior5 小时前
Unity文字显示带竖线问题
c#·unity3d
Logintern096 小时前
【每天学习一点点】MPI是什么?
学习·mpi·多机训练
fengci.6 小时前
提示词注入学习小结
学习