C# 简单 2D 游戏物理 (重力、跳跃、移动 & 块碰撞)
我制作 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.