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

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

相关推荐
andrsted22 分钟前
用练题簿小程序 - 让错题不再白错
学习·微信小程序·小程序·学习方法
2601_9499506329 分钟前
练题簿,把培训从“走过场”变成“真落地”
人工智能·学习·小程序·刷题·小程序推荐
北域码匠4 小时前
高通滤波算法深度解析(High-Pass Filter)
stm32·算法·c#·数字信号处理·嵌入式开发·滤波算法·高通滤波
问天_观心4 小时前
大模型微调学习(二)
人工智能·python·深度学习·学习·语言模型·transformer
指掀涛澜天下惊4 小时前
强化学习进阶篇八 策略梯度算法
深度学习·学习·算法·强化学习
star learning white4 小时前
STM32入门学习5
stm32·嵌入式硬件·学习
传奇开心果编程4 小时前
【Rust入门知识点学与练】第4课:条件判断 if / else
开发语言·学习·rust
lifallen5 小时前
Skill 的生命周期:问题消失以后
人工智能·学习·ai·ai编程
千谦阙听5 小时前
C++类和对象(中):默认成员函数、构造与析构、拷贝构造、运算符重载
开发语言·c++·学习
Agudamu11616 小时前
AI 视频学习笔记工具拆解:4款工具谁能把视频变成复习资料
人工智能·学习·音视频