如何在Pygame中用一个物体(玩家)推动另一个物体(箱子)?

huangapple go评论126阅读模式
英文:

How do I "Push" an object (crate) with another object (player) in Pygame?

问题

我正在尝试使用Pygame制作可推动的箱子。

我能够将箱子向所有方向(上、下、左、右)推动,这很正常。我还可以同时将箱子推向上或下并左右移动。问题是,当我同时将箱子向左或右推动并上下移动时,箱子会弹到我的玩家对象的顶部或底部。

我已经创建了一个GIF来演示我的意思:

如何在Pygame中用一个物体(玩家)推动另一个物体(箱子)?

我在我的Player类中使用一个方法/函数来处理推动:

def push(self, other):
    if self.this.colliderect(other):
        self.speed = 1
        if self.dx < 0:
            other.x = self.this.left - other.this.width
        elif self.dx > 0:
            other.x = self.this.right
        elif self.dy < 0:
            other.y = self.this.top - other.this.height
        elif self.dy > 0:
            other.y = self.this.bottom
    else:
        self.speed = 3

self.dxself.dy 是在 update 方法中分配的变量,如下所示:

def update(self, x, y):
    self.dx = x
    self.dy = y
    self.rect.x += x
    self.rect.y += y

以下是我在游戏循环中处理移动的方式:

# 处理与移动相关的按键按下事件
if event.type == pygame.KEYDOWN:
    if event.key == pygame.K_w:
        up = True
    if event.key == pygame.K_s:
        down = True
    if event.key == pygame.K_a:
        left = True
    if event.key == pygame.K_d:
        right = True

if event.type == pygame.KEYUP:
    if event.key == pygame.K_w:
        dy = 0
        up = False
    if event.key == pygame.K_s:
        dy = 0
        down = False
    if event.key == pygame.K_a:
        dx = 0
        left = False
    if event.key == pygame.K_d:
        dx = 0
        right = False

# 将速度应用于玩家
if up:
    dy = -player.speed
if down:
    dy = player.speed
if left:
    dx = -player.speed
if right:
    dx = player.speed

# 移动玩家并检查墙壁/门的碰撞
if dx != 0:
    player.update(dx, 0)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)
if dy != 0:
    player.update(0, dy)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)

从逻辑上看,查看我的 push() 方法,我可以理解为什么在将箱子向左或右推动时会弹起,因为我正在设置箱子的 y 位置,以玩家的方向为准。然而,我不明白为什么当我将箱子向上或向下推动并同时向左或向右移动时,箱子不会弹起...

我最终只想让玩家能够推动箱子而不让它弹来弹去。

我已经尝试了多种解决方案,比如检查 self.dxself.dy 是否都激活,并在游戏循环中处理箱子的行为,而不是将其附加到Player对象的方法中。

无论我尝试什么,都无法使推动动作按我所希望的方式行为。

如果查看我的整个游戏代码有助于解决问题,可以在这里找到。

您对如何正确处理这种互动有什么建议吗?

英文:

I'm trying to make a pushable crate using Pygame.

I'm able to push the crate in all directions (up, down, left and right) which works fine. I can also push the crate up or down and move left and right at the same time. The issue is when I push the crate left or right and move up or down at the same time, the crate will ping to the top or bottom of my Player object.

I've created a GIF to show you what I mean:

如何在Pygame中用一个物体(玩家)推动另一个物体(箱子)?

I'm using a method/function in my Player class to handle the pushing:

def push(self, other):
    if self.this.colliderect(other):
        self.speed = 1
        if self.dx &lt; 0:
            other.x = self.this.left - other.this.width
        elif self.dx &gt; 0:
            other.x = self.this.right
        elif self.dy &lt; 0:
            other.y = self.this.top - other.this.height
        elif self.dy &gt; 0:
            other.y = self.this.bottom
    else:
        self.speed = 3

self.dx and self.dy are variables assigned in the update method, as follows:

def update(self, x, y):
    self.dx = x
    self.dy = y
    self.rect.x += x
    self.rect.y += y

and here is how I deal with movement within the game loop:

    # Deal with movement related key presses
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_w:
            up = True
        if event.key == pygame.K_s:
            down = True
        if event.key == pygame.K_a:
            left = True
        if event.key == pygame.K_d:
            right = True

    if event.type == pygame.KEYUP:
        if event.key == pygame.K_w:
            dy = 0
            up = False
        if event.key == pygame.K_s:
            dy = 0
            down = False
        if event.key == pygame.K_a:
            dx = 0
            left = False
        if event.key == pygame.K_d:
            dx = 0
            right = False

# Apply velocity to Player
if up:
    dy = -player.speed
if down: 
    dy = player.speed
if left:
    dx = -player.speed
if right:
    dx = player.speed

# Move the Player and check for wall/door collisions
if dx != 0:
    player.update(dx, 0)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)
if dy != 0:
    player.update(0, dy)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)

Logically, looking at my push() method, I can see why the crate would ping up while I'm pushing it left or right, because I am setting the crates y position respective of the player's direction. However, I don't understand why the crate doesn't ping when I'm pushing it up or down and moving left or right...

I ultimately just want the player to be able to push the crate without it pinging about.

I've attempted multiple solutions, like checking if both self.dx and self.dy are active and dealing with the behaviour of the crate within the game loop, rather than a method attached to the Player object.

Nothing I try seems to get the pushing action to behave how I want it to.

If it helps to check out my entire game code so far, it's here

Do you have any advise on how I can handle this interaction correctly?

答案1

得分: 2

这个问题是由全局变量dxdy(在main中)与属性player.dxplayer.dy不同步引起的。

原因在于main中的这段代码:

if dx != 0:
    player.update(dx, 0)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)
if dy != 0:
    player.update(0, dy)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)

objects中的这段代码配合使用:

def update(self, x, y):
    self.dx = x
    self.dy = y
    self.rect.x += x
    self.rect.y += y

如果单独看其中任何一个部分,都会认为它们执行的是一些合理的操作。第一段代码通过单独处理两个轴的移动来简化了墙壁和门的碰撞代码。这看起来应该是没问题的。问题在于player.update会保存传递给它的两个值,即使其中一个是硬编码的0

因此,如果你在对角线上移动,只有垂直推动是可能的,因为第二次调用player.update会将player.dx清零,即使在main中全局变量dx是非零的。

我留给你去找出如何最好地修复这个问题(你可以更改墙壁碰撞代码或更新逻辑)。我会建议的一个一般性想法是尽量让代码中存在所谓的“单一数据源”关于任何给定的数据。在你目前的代码中,有很多值似乎包含非常相似但可能并不完全相同的数据(例如dxplayer.dx,或player.rectplayer.this),这很容易导致这种微妙的错误。有时冗余有用,但过多的冗余肯定是不好的。

英文:

This looks caused by the global dx and dy variables (in main) getting get out of sync with the attributes player.dx and player.dy.

The reason is in this code, in main:

if dx != 0:
    player.update(dx, 0)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)
if dy != 0:
    player.update(0, dy)
    player.collision(level.wall_tiles)
    player.collision(level.door_tiles)

paired with this code in objects:

def update(self, x, y):
    self.dx = x
    self.dy = y
    self.rect.x += x
    self.rect.y += y

If you look at either of these independently, you'd think they were doing mostly some reasonable stuff. The first block of code simplifies the wall and door collision code by handling the two axes of movement separately. That seems like it should be fine. The trouble is that player.update saves both of the values it gets passed, even when one is a hard-coded 0!

So, if you're moving diagonally, only vertical pushes are ever possible, because the second call to player.update zeros out player.dx even though the global dx variable in main is non-zero.

I'll leave it to you to figure out how best to fix this (you could change the wall-collision code, or the update logic). One general idea I will suggest is to try to get your code so that there's a so called "single source of truth" about any given piece of data. In your current code, there are a lot of values that seem to have very similar, but maybe not quite identical data in them (e.g. dx and player.dx, or player.rect and player.this), and that can very easily lead to subtle bugs like this one. Sometimes a bit of redundancy is useful, but too much is definitely bad.

huangapple
  • 本文由 发表于 2023年3月7日 09:16:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75657239.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定