I have a box2d pygame platforming example. You can move the player around and spawn platforms. I cannot perform the jump action on the player unless the player is colliding with all of the platforms in the level. This is not intended behavior, and what is desired is to be able to jump when you are touching one platform and not all the others. I am using a for loop to loop through all the collisions for the player, and setting can_jump as needed.
main.py
import pygame
from draw import Draw
from Box2D import (b2World,b2Vec2)
from box import Box
from player import Player
def Run():
PPM = 20
TARGET_FPS = 60
TIME_STEP = 1.0 / TARGET_FPS
SCREEN_WIDTH,SCREEN_HEIGHT = 640,480
CAPTION = ""
BGCOLOR = ((255,255,255))
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption(CAPTION)
clock = pygame.time.Clock()
world = b2World(gravity=(0, 30), doSleep=True)
closed = False
global player, hold_left, hold_right, can_jump, speed, deceleration
player = Player(world,200,200,PPM)
speed = 20
deceleration = 0.95
hold_left = False
hold_right = False
can_jump = False
while not closed:
def left():
player.body.ApplyForce(b2Vec2(-1*speed*speed,0),point=player.body.worldCenter,wake=True)
def right():
player.body.ApplyForce(b2Vec2(speed*speed,0),point=player.body.worldCenter,wake=True)
if hold_left:
left()
elif hold_right:
right()
if len(player.body.contacts) == 0:
can_jump = False
else:
for contact in player.body.contacts:
contact = contact.contact
print(contact)
if contact.manifold.localPoint == b2Vec2(0,1):
can_jump=True
elif contact.manifold.localPoint != b2Vec2(0,1):
can_jump=False
for event in pygame.event.get():
if event.type == pygame.QUIT:
closed = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_d:
hold_right = True
elif event.key == pygame.K_a:
hold_left = True
elif event.key == pygame.K_SPACE:
if can_jump:
player.body.linearVelocity.y = 0
player.body.ApplyLinearImpulse(b2Vec2(0,-150),point=player.body.worldCenter,wake=True)
if event.type == pygame.KEYUP:
if event.key == pygame.K_d:
hold_right = False
elif event.key == pygame.K_a:
hold_left = False
click = pygame.mouse.get_pressed()
if click[0] == 1:
x,y = pygame.mouse.get_pos()
Box(world, x, y, PPM)
player.body.linearVelocity.x *= deceleration
screen.fill(BGCOLOR)
Draw(screen,world.bodies,PPM)
world.Step(TIME_STEP, 10, 10)
pygame.display.flip()
clock.tick(TARGET_FPS)
pygame.quit()
if __name__ == '__main__':
Run()
player.py
from Box2D import (b2FixtureDef, b2PolygonShape)
class Player:
def __init__(self, world, x, y, PPM):
self.x = x / PPM
self.y = y / PPM
self.w = 1
self.h = 1
self.gh = 0.1
self.world = world
self.body = self.world.CreateDynamicBody(
position=(self.x, self.y),
fixtures=b2FixtureDef(
shape=b2PolygonShape(box=(self.w, self.h)), density=2.0, friction = 0.1))
self.body.fixedRotation = True
box.py
from Box2D import (b2FixtureDef, b2PolygonShape)
class Box:
def __init__(self, world, x, y, PPM):
self.x = x / PPM
self.y = y / PPM
self.w = 10
self.h = 1
self.world = world
self.body = self.world.CreateStaticBody(
position=(self.x, self.y),
fixtures=b2FixtureDef(
shape=b2PolygonShape(box=(self.w, self.h)), density=2.0, friction = 0.1))
draw.py
import pygame
from Box2D import b2PolygonShape
def Poly(screen,body,fixture,PPM):
shape = fixture.shape
vertices = [(body.transform * v) * PPM for v in shape.vertices]
pygame.draw.polygon(screen, (0, 255, 187), vertices)
pygame.draw.polygon(screen, (0,0,0), vertices,2)
def Draw(screen,bodies,PPM):
for body in bodies:
for fixture in body.fixtures:
if isinstance(fixture.shape, b2PolygonShape):
Poly(screen,body,fixture,PPM)
You almost certainly have a logic error in this section of code, which appears to control your boolean can_jump
variable:
else:
for contact in player.body.contacts:
contact = contact.contact
print(contact)
if contact.manifold.localPoint == b2Vec2(0,1):
can_jump=True
elif contact.manifold.localPoint != b2Vec2(0,1):
can_jump=False
One error, one cleanup, and a couple tips...
you are looping through the contacts. When you fall out of this loop, what has determined the T/F value of can_jump
?
Ans: Only the last contact in the data container will affect the value of can_jump. Does that make sense?
you are testing for a T/F condition here, the proper way to do it is do the test once and let that determine the result. Right now you are testing "if true" then testing "if false." This is redundant and error-prone. So something like this is better:
if contact.blah.blah == b2vec():
can_jump = True
else:
can_jump = False
Now, that doesn't solve your problem. (This is getting as long as your code...lol). What you probably want to do is check for "all" or "any" of the contacts to determine the condition, depending on the logic of your game. You could either do this with a loop, as you are doing now, and, if you hit a "fail" condition, set the variable, and break out of the loop or such. Or you could be slick and look at Python's any()
and all()
built in commands.
For example... (you would replace the logic test here with your own, but this shows the structure)
contacts = {2, 5, 7}
can_jump = any(contact > 3 for contact in contacts)
print(can_jump) # True
can_jump = all(contact > 3 for contact in contacts)
print(can_jump) # False
So, you could either re-write your loop logic correctly or you could replace it all with a 1-liner, which should work even if contacts is empty, which will generate a False
as well!