本节课目标
用二维数组定义地图
根据数组绘制墙壁和地面
玩家碰到墙壁会停下来
金币和敌人不生成在墙壁上
第一步:创建地图类
右键项目 →添加→类,文件名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方法替换为:
// 在 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);
本节课学习到此结束,我是魔法阵维护师,关注我,下期更精彩