65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 简单 2D 游戏物理 (重力、跳跃、移动 & 块碰撞)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.39/5 (11投票s)

2015年2月28日

CPOL

3分钟阅读

viewsIcon

117199

downloadIcon

6594

我制作 2D 游戏,一个马里奥平台类型游戏的经历。我在互联网上搜索了大部分这些内容,但很难找到。尤其是碰撞检测,我最终自己创建了一个简单的解决方案。

引言

这是一个简单的人为了乐趣而做的工作。

当我用 C# 制作我的第一个 2D 游戏时,我在添加新块以及让碰撞物理正常工作方面遇到了一些问题,这样玩家就不会穿过块或被卡住。

然后我搜索了网络上的一些指针,或者任何我可以学习的有效代码。但我从未找到任何不使用 XNA 框架的代码。我觉得 XNA 很难使用。

所以我做了我自己的,一个简单的碰撞检测,我想为像我一样的人解释一下。

这是一张我制作的游戏的图片。

游戏说明

你扮演马里奥,对抗 2 个追逐你的食人花,还有掉落的炸弹,以及从侧面发射给你的火箭。(但是图中没有显示)

有 2 个平台,1 个管道(不允许你穿过它 :P)
当一切发生时,你的目标是收集尽可能多的硬币(随机从天而降)并尽可能长时间地活下去。

键盘输入

键盘输入,我用方向键(或(WASD)与 forms 的 KeyDown/KeyUp 事件一起使用来移动角色。

private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    Player_Left = true;
                    break;
                case Keys.Right:
                    Player_Right = true;
                    break;
            }
        }

这只是我如何做的例子,正如你可以阅读的那样,有一个开关,取决于哪个键被按下,然后它会根据键做什么。

如果我按下左箭头键,它将我的 "向左移动玩家" 变量更改为 true

重力

对于这个游戏中的 重力,我使用了一个计时器,根据以下变量上下移动角色。

int Force
int Gravity
Boolean Player_Jump

这是 Space keyDown 的设置方式

if (!Player_Jump && !InAirNoCollision(pb_Player))
                        {
                            Force = Gravity;
                            Player_Jump = true;
                        }

这意味着,如果 玩家 还没有跳跃,并且玩家不在空中,并且没有与任何块发生碰撞(也检查玩家是否在世界范围内)。

我在这款游戏中的物理工作方式是,如果 Force 中有任何值,则玩家向上移动(除非头部与块发生碰撞)。

如果 Force 降至 0,则玩家开始下降。

我用 2 个计时器制作了这个(可以用 1 个完成,但我喜欢把它们分开)。

这是我编写下降代码的方式

private void timer_Gravity_Tick(object sender, EventArgs e)
        {
            if (!Player_Jump && pb_Player.Location.Y + 
        pb_Player.Height < WorldFrame.Height - 2 && !Collision_Top(pb_Player))
            {
                pb_Player.Top += Speed_Fall;
            }

if (!Player_Jump && pb_Player.Location.Y + pb_Player.Height > WorldFrame.Height - 1)
            {
                pb_Player.Top--;
            }
        }

这是跳跃代码

  private void timer_Jump_Tick(object sender, EventArgs e)
        {
            if (Force > 0)
            {
                if (Collision_Bottom(pb_Player))
                {
                    Force = 0;
                }
                else
                {
                    Force--;
                    pb_Player.Top -= Speed_Jump;
                }
            }
            else
            {
                Player_Jump = false;
            }
        }

移动

就像我在 Keyboard 输入部分中的例子一样,我使用以下代码开始移动

private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Left:
                    Player_Left = true;
                    break;
                case Keys.Right:
                    Player_Right = true;
                    break;
            }
        } 

一个用于 Player_Left, Player_Right 的布尔值

我还有一个计时器,它根据布尔值是 true 还是 false 来移动角色。

if (Player_Right && pb_Player.Right <= WorldFrame.Width - 3 && !Collision_Left(pb_Player))
                { 
                    pb_Player.Left += Speed_Movement;
                }
                if (Player_Left && pb_Player.Location.X >= 3 && !Collision_Right(pb_Player))
                { 
                    pb_Player.Left -= Speed_Movement;
                }
            }
            else
            { 
                Player_Right = false;
                Player_Left = false;
            }

这只是我如何使用代码的一个例子,比如 Speed_movement 只是一个 Int,在我这里设置为 3

块碰撞

在我的代码中,我使用 PictureBoxes 作为 玩家 以及 WorldObjects

在这个例子中,我们只需要有以下变量

PictureBox Player

PictureBox WorldObjects Block_1

我使用 4 个布尔函数来检查玩家是否与块的侧面相交,但为了实现这一点,我不得不创建块每侧的一些临时矩形。

例如,顶部碰撞

PictureBox temp1 = new PictureBox();
temp1.Bounds = Block_1.Bounds;
temp1.SetBounds(temp1.Location.X, temp1.Location.Y - 1, temp1.Width, 1);
  • 第 1 行 这就是创建一个新的 picturebox 的作用
  • 第 2 行 复制边界(源块的大小,在本例中为 Block_1
  • 第 3 行 设置新的边界,在相同的 X 轴上,但在 Block_1 上方 1 个点,并且与 Block_1 一样宽

请参阅“使用代码”部分以获取完整的代码,或下载源代码并亲自尝试。

Using the Code

这是 InAirNoCollision 代码以及所有 4 个 Collision 函数

public Boolean InAirNoCollision(PictureBox tar)
        {
            if (!OutsideWorldFrame(tar))
            {
                foreach (PictureBox Obj in WorldObjects)
                {
                    if (!tar.Bounds.IntersectsWith(Obj.Bounds))
                    {
                        if (tar.Location.Y < WorldFrame.Width)
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }


public Boolean Collision_Top(PictureBox tar)
        {
            foreach (PictureBox ob in WorldObjects)
            {
                if (ob != null)
                {
                    PictureBox temp1 = new PictureBox();
                    temp1.Bounds = ob.Bounds;
                    temp1.SetBounds(temp1.Location.X - 3, temp1.Location.Y - 1, temp1.Width + 6, 1);
                    if (tar.Bounds.IntersectsWith(temp1.Bounds))
                        return true;
                }
            }
            return false;
        }

public Boolean Collision_Bottom(PictureBox tar)
        {
            foreach (PictureBox ob in WorldObjects)
            {
                if (ob != null)
                {
                    PictureBox temp1 = new PictureBox();
                    temp1.Bounds = ob.Bounds;
                    temp1.SetBounds(temp1.Location.X, temp1.Location.Y + temp1.Height, temp1.Width, 1);
                    if (tar.Bounds.IntersectsWith(temp1.Bounds))
                        return true;
                }
            }
            return false;
        }

        public Boolean Collision_Left(PictureBox tar)
        {
            foreach (PictureBox ob in WorldObjects)
            {
                if (ob != null)
                {
                    PictureBox temp1 = new PictureBox();
                    temp1.Bounds = ob.Bounds;
                    temp1.SetBounds(temp1.Location.X - 1, temp1.Location.Y + 1, 1, temp1.Height - 1);
                    if (tar.Bounds.IntersectsWith(temp1.Bounds))
                        return true;
                }
            }
            return false;
        }
        public Boolean Collision_Right(PictureBox tar)
        {
            foreach (PictureBox ob in WorldObjects)
            {
                if (ob != null)
                {
                    PictureBox temp1 = new PictureBox();
                    temp1.Bounds = ob.Bounds;
                    temp1.SetBounds(temp1.Location.X + temp1.Width, temp1.Location.Y + 1, 1, temp2.Height - 1);
                    if (tar.Bounds.IntersectsWith(temp2.Bounds))
                        return true;
                }
            }
            return false;
        } 

我已经上传了一个 .zip 文件,其中包含一个可执行文件、资源文件(所有图像)和完整的已记录源代码。

如果您有任何问题,请随时通过电子邮件与我联系 robert@hydeen.se.

© . All rights reserved.