本节课目标
-
用二维数组定义地图
-
根据数组绘制墙壁和地面
-
玩家碰到墙壁会停下来
-
金币和敌人不生成在墙壁上
第一步:创建地图类
右键项目 → 添加 → 类 ,文件名 TileMap.cs:
csharp
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace MY_FIRST_GAME
{
public class TileMap
{
// 瓦片类型
public enum TileType
{
Empty = 0, // 空地
Wall = 1, // 墙壁
}
public int TileSize { get; private set; } = 40;
public int Width { get; private set; }
public int Height { get; private set; }
public TileType[,] Tiles;
private Texture2D wallTexture;
private Texture2D floorTexture;
// ★ 地图数据:0=空地, 1=墙壁
// 可以手动编辑这个数组来设计地图
private int[,] mapData = new int[,]
{
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
public TileMap(GraphicsDevice graphicsDevice)
{
Height = mapData.GetLength(0);
Width = mapData.GetLength(1);
Tiles = new TileType[Width, Height];
// 转换地图数据
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
Tiles[x, y] = (TileType)mapData[y, x];
// 创建墙壁纹理
wallTexture = new Texture2D(graphicsDevice, TileSize, TileSize);
Color[] wallData = new Color[TileSize * TileSize];
for (int i = 0; i < wallData.Length; i++)
wallData[i] = Color.DarkSlateGray;
wallTexture.SetData(wallData);
// 创建地板纹理
floorTexture = new Texture2D(graphicsDevice, TileSize, TileSize);
Color[] floorData = new Color[TileSize * TileSize];
for (int i = 0; i < floorData.Length; i++)
floorData[i] = new Color(30, 30, 30);
floorTexture.SetData(floorData);
}
public void Draw(SpriteBatch spriteBatch)
{
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
Vector2 position = new Vector2(x * TileSize, y * TileSize);
if (Tiles[x, y] == TileType.Wall)
spriteBatch.Draw(wallTexture, position, Color.White);
else
spriteBatch.Draw(floorTexture, position, Color.White);
}
}
}
// 检查某个像素位置是否是墙壁
public bool IsWall(Vector2 position)
{
int tileX = (int)(position.X / TileSize);
int tileY = (int)(position.Y / TileSize);
if (tileX < 0 || tileX >= Width || tileY < 0 || tileY >= Height)
return true; // 地图外也算墙
return Tiles[tileX, tileY] == TileType.Wall;
}
// 检查矩形是否碰到墙壁
public bool CollidesWithWall(Rectangle bounds)
{
int left = bounds.Left / TileSize;
int right = bounds.Right / TileSize;
int top = bounds.Top / TileSize;
int bottom = bounds.Bottom / TileSize;
for (int x = left; x <= right; x++)
{
for (int y = top; y <= bottom; y++)
{
if (x < 0 || x >= Width || y < 0 || y >= Height)
return true;
if (Tiles[x, y] == TileType.Wall)
return true;
}
}
return false;
}
// 获取地图上的随机空地
public Vector2 GetRandomEmptyPosition(Random rng, int margin = 1)
{
while (true)
{
int x = rng.Next(margin, Width - margin);
int y = rng.Next(margin, Height - margin);
if (Tiles[x, y] == TileType.Empty)
return new Vector2(x * TileSize + TileSize / 2, y * TileSize + TileSize / 2);
}
}
}
}
第二步:改造 Player 类以支持墙壁碰撞
把 Player.cs 的 Update 方法替换为:
cs
// 在 Player 类顶部添加字段
private TileMap tileMap;
// 修改构造函数,接收 TileMap
public Player(Texture2D spriteSheet, Vector2 startPosition, TileMap map)
{
Position = startPosition;
texture = spriteSheet;
tileMap = map;
idleAnimation = new Animation(texture, 1, 0f, true);
walkAnimation = new Animation(texture, 4, 0.15f, true);
currentAnimation = idleAnimation;
}
// 替换 Update 方法
public void Update(float deltaTime)
{
KeyboardState keyboard = Keyboard.GetState();
Vector2 newPosition = Position;
bool isMovingNow = false;
if (keyboard.IsKeyDown(Keys.W) || keyboard.IsKeyDown(Keys.Up))
{ newPosition.Y -= Speed * deltaTime; isMovingNow = true; }
if (keyboard.IsKeyDown(Keys.S) || keyboard.IsKeyDown(Keys.Down))
{ newPosition.Y += Speed * deltaTime; isMovingNow = true; }
if (keyboard.IsKeyDown(Keys.A) || keyboard.IsKeyDown(Keys.Left))
{ newPosition.X -= Speed * deltaTime; isMovingNow = true; }
if (keyboard.IsKeyDown(Keys.D) || keyboard.IsKeyDown(Keys.Right))
{ newPosition.X += Speed * deltaTime; isMovingNow = true; }
isMoving = isMovingNow;
// ★ 碰撞检测:分别检测 X 和 Y 轴
Rectangle newBounds = GetBoundsAt(newPosition);
// 先试 X 轴
Rectangle xBounds = new Rectangle(
newBounds.X,
GetBounds().Y,
newBounds.Width,
GetBounds().Height
);
if (!tileMap.CollidesWithWall(xBounds))
Position = new Vector2(newPosition.X, Position.Y);
// 再试 Y 轴
Rectangle yBounds = new Rectangle(
GetBounds().X,
newBounds.Y,
GetBounds().Width,
newBounds.Height
);
if (!tileMap.CollidesWithWall(yBounds))
Position = new Vector2(Position.X, newPosition.Y);
// 切换动画
if (isMoving)
{
if (currentAnimation != walkAnimation)
{
walkAnimation.Reset();
currentAnimation = walkAnimation;
}
}
else
{
currentAnimation = idleAnimation;
}
currentAnimation.Update(deltaTime);
}
// 添加辅助方法
private Rectangle GetBoundsAt(Vector2 pos)
{
Rectangle sourceRect = currentAnimation.GetSourceRectangle();
return new Rectangle(
(int)(pos.X - sourceRect.Width / 2),
(int)(pos.Y - sourceRect.Height / 2),
sourceRect.Width,
sourceRect.Height
);
}
第三步:改造 Game1.cs
把 Game1.cs 里关于 TileMap 的部分加上。只改以下三个地方:
1. 添加字段:
csharp
private TileMap tileMap = default!;
2. 在 InitializeGame() 里初始化地图:
csharp
tileMap = new TileMap(GraphicsDevice);
3. 修改玩家创建(两处):
csharp
// LoadContent 里
player = new Player(playerSpriteSheet, tileMap.GetRandomEmptyPosition(rng), tileMap);
// 标题画面开始新游戏时
player = new Player(playerSpriteSheet, tileMap.GetRandomEmptyPosition(rng), tileMap);
4. 在 SpawnCoins 和 SpawnEnemies 里使用地图随机空地:
csharp
private void SpawnCoins(int count)
{
for (int i = 0; i < count; i++)
coins.Add(tileMap.GetRandomEmptyPosition(rng));
}
private void SpawnEnemies(int count)
{
for (int i = 0; i < count; i++)
enemies.Add(tileMap.GetRandomEmptyPosition(rng));
}
5. 在 DrawGame() 最前面画地图:
csharp
tileMap.Draw(_spriteBatch);
本节课学习到此结束,我是魔法阵维护师,关注我,下期更精彩