从零开发游戏需要学习的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 方法替换为:

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

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

相关推荐
万岳科技32 分钟前
教育培训系统开发流程详解:平台建设关键环节解析
数据库·后端·学习
fanged33 分钟前
高通学习14--RB5(TODO)
学习
z落落39 分钟前
C#ToolStrip+StatusStrip 状态栏实时显示系统时间+NotifyIcon系统托盘
开发语言·c#
ctrl_v助手2 小时前
VisionPro (R) QuickBuild相机的连接
服务器·笔记·数码相机·c#
Tbisnic2 小时前
AI大模型学习第十四天:Coze项目实战中的分治智慧
人工智能·python·学习·大模型·工作流·智能体·coze
小风吹啊吹~2 小时前
通过时态图学习意图驱动识别足球控球比赛阶段 论文详解
学习·transformer·论文笔记·gan·足球战术·战术分析系统
阿i索3 小时前
【C++学习笔记】【基础】4.string类(2)——模拟实现
c++·笔记·学习
北域码匠3 小时前
奇偶归并排序:并行计算的排序利器
数据结构·算法·c#·排序算法
袁小皮皮不皮3 小时前
6.HCIP OSPF域间防环机制与虚链路
服务器·网络·笔记·网络协议·学习·智能路由器