news 2026/5/25 23:13:33

从零开发游戏需要学习的c#模块,第二十四章(瓦片地图 —— 让世界有墙)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开发游戏需要学习的c#模块,第二十四章(瓦片地图 —— 让世界有墙)

本节课目标

  1. 用二维数组定义地图

  2. 根据数组绘制墙壁和地面

  3. 玩家碰到墙壁会停下来

  4. 金币和敌人不生成在墙壁上

第一步:创建地图类

右键项目 →添加,文件名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.csUpdate方法替换为:

// 在 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. 在SpawnCoinsSpawnEnemies里使用地图随机空地:

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);

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 23:13:15

鸿蒙非遗博览页面构建:传承人物、展陈日程与科普知识格模块详解

鸿蒙非遗博览页面构建&#xff1a;传承人物、展陈日程与科普知识格模块详解 前言 在 HarmonyOS 6.0 应用开发中&#xff0c;文化科普类页面的传承人介绍、展陈日程安排和知识卡片是完善用户体验的重要模块。本文将以“非遗博览”应用中的“传承人物”垂直列表模块、“展陈日程”…

作者头像 李华
网站建设 2026/5/25 23:11:58

用了ChatGPT写论文初稿,如何降低AI率并同步减少文字重复率?

用 ChatGPT 写完论文初稿&#xff0c;却卡在重复率飘红、AIGC 疑似率超标&#xff1f;2026 年高校与期刊已进入 “查重 AI 检测” 双严时代&#xff0c;知网、维普、Turnitin 同步收紧标准&#xff0c;AI 率超 20% 直接打回&#xff0c;重复率超 15% 无法定稿。别再盲目逐字改…

作者头像 李华
网站建设 2026/5/25 23:11:01

CatBoost模型在银河系恒星年龄测定中的应用与天体物理发现

1. 项目概述&#xff1a;当机器学习遇见银河考古给几十万甚至上百万颗恒星“测年龄”&#xff0c;听起来像是天方夜谭&#xff0c;但这正是当前银河系考古学&#xff08;Galactic Archaeology&#xff09;最核心也最棘手的挑战之一。恒星年龄是解读星系形成历史的“化石记录”&…

作者头像 李华
网站建设 2026/5/25 23:10:08

Cursor Pro激活工具深度解析:三步解锁AI编程助手完整功能

Cursor Pro激活工具深度解析&#xff1a;三步解锁AI编程助手完整功能 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached your …

作者头像 李华
网站建设 2026/5/25 23:09:41

LLM推理优化:路由与层次推理技术详解

1. LLM推理优化技术概述大型语言模型(LLM)的推理过程面临着显著的资源消耗挑战。以GPT-3为例&#xff0c;单次推理需要约350GB显存和数千亿次浮点运算&#xff0c;导致高昂的计算成本和延迟问题。路由(Routing)和层次推理(Hierarchical Inference)技术通过构建多模型协同的推理…

作者头像 李华
网站建设 2026/5/25 23:08:26

告别元素变动导致的报错:探索自动化测试脚本的 AI“自愈”能力

前言:一个所有测试人都经历过的噩梦 周三晚上十一点,CI/CD流水线再次亮起红灯。 你打开日志,满屏的NoSuchElementException扑面而来。仔细一看——前端团队在昨天的版本中重构了登录页面的DOM结构,原本的#login-btn变成了#signin-button-v2,30个测试用例因此全军覆没。 …

作者头像 李华