从零开发游戏需要学习的c#模块,第二十六章(多种敌人与基础 AI)
·
本节课目标
-
创建三种敌人:史莱姆(慢)、骷髅(中速追人)、蝙蝠(快速随机移动)
-
敌人有独立的行为逻辑
-
不同敌人有不同外观和属性
-
敌人在地图上自主移动
第一步:创建敌人基类
右键项目 → 添加 → 类,文件名 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 方法:
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. 在 Update 的 Game 状态下更新敌人 AI:
// 在 CheckEnemyCollision 之前加
foreach (Enemy enemy in enemies)
enemy.Update(deltaTime, player.Position, tileMap);
4. 替换 CheckEnemyCollision 方法:
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 里的敌人绘制:
foreach (Enemy enemy in enemies)
enemy.Draw(_spriteBatch);
本节课学习任务到此结束,我是魔法阵维护师,关注我,下期更精彩!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)