从零开发游戏需要学习的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);

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

相关推荐
吴可可12311 小时前
C#中括号报错“应输入标识符”原因解析
c#
文阿花12 小时前
Pc端大屏地图实现方案分析
学习
我的xiaodoujiao12 小时前
API 接口自动化测试详细图文教程学习系列21--结合Pytest框架使用2--断言和插件
python·学习·测试工具·pytest
炽烈小老头12 小时前
【每天学习一点算法 2026/05/22】课程表 II
学习·算法
wuxinyan12313 小时前
工业级大模型学习之路026:LangGraph 入门与基础 Agent 开发
人工智能·python·学习·langsmith
lingxiao1688813 小时前
智慧停车场(SmartParking)
c#·自动化·wpf
weixin1997010801613 小时前
[特殊字符] 从1688接口设计,学习高可用API的最佳实践(附Python源码)
python·学习·spring
叶子野格13 小时前
《C语言学习:编程例题》B
c语言·开发语言·c++·学习
吃好睡好便好14 小时前
创建上三角矩阵和下三角矩阵
开发语言·学习·线性代数·matlab·矩阵