Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
339 lines (277 sloc) 10.7 KB
"""Showcase of a very basic 2d platformer
The red girl sprite is taken from Sithjester's RMXP Resources:
http://untamed.wild-refuge.net/rmxpresources.php?characters
.. note:: The code of this example is a bit messy. If you adapt this to your
own code you might want to structure it a bit differently.
"""
__docformat__ = "reStructuredText"
import math
import sys
import pygame
import pymunk
import pymunk.pygame_util
from pymunk.vec2d import Vec2d
def cpfclamp(f, min_, max_):
"""Clamp f between min and max"""
return min(max(f, min_), max_)
def cpflerpconst(f1, f2, d):
"""Linearly interpolate from f1 to f2 by no more than d."""
return f1 + cpfclamp(f2 - f1, -d, d)
width, height = 690, 400
fps = 60
dt = 1.0 / fps
PLAYER_VELOCITY = 100.0 * 2.0
PLAYER_GROUND_ACCEL_TIME = 0.05
PLAYER_GROUND_ACCEL = PLAYER_VELOCITY / PLAYER_GROUND_ACCEL_TIME
PLAYER_AIR_ACCEL_TIME = 0.25
PLAYER_AIR_ACCEL = PLAYER_VELOCITY / PLAYER_AIR_ACCEL_TIME
JUMP_HEIGHT = 16.0 * 3
JUMP_BOOST_HEIGHT = 24.0
JUMP_CUTOFF_VELOCITY = 100
FALL_VELOCITY = 250.0
JUMP_LENIENCY = 0.05
HEAD_FRICTION = 0.7
PLATFORM_SPEED = 1
def main():
### PyGame init
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
running = True
font = pygame.font.SysFont("Arial", 16)
sound = pygame.mixer.Sound("sfx.wav")
img = pygame.image.load("xmasgirl1.png")
### Physics stuff
space = pymunk.Space()
space.gravity = Vec2d(0, -1000)
pymunk.pygame_util.positive_y_is_up = True
draw_options = pymunk.pygame_util.DrawOptions(screen)
# box walls
static = [
pymunk.Segment(space.static_body, (10, 50), (300, 50), 3),
pymunk.Segment(space.static_body, (300, 50), (325, 50), 3),
pymunk.Segment(space.static_body, (325, 50), (350, 50), 3),
pymunk.Segment(space.static_body, (350, 50), (375, 50), 3),
pymunk.Segment(space.static_body, (375, 50), (680, 50), 3),
pymunk.Segment(space.static_body, (680, 50), (680, 370), 3),
pymunk.Segment(space.static_body, (680, 370), (10, 370), 3),
pymunk.Segment(space.static_body, (10, 370), (10, 50), 3),
]
static[1].color = pygame.Color("red")
static[2].color = pygame.Color("green")
static[3].color = pygame.Color("red")
# rounded shape
rounded = [
pymunk.Segment(space.static_body, (500, 50), (520, 60), 3),
pymunk.Segment(space.static_body, (520, 60), (540, 80), 3),
pymunk.Segment(space.static_body, (540, 80), (550, 100), 3),
pymunk.Segment(space.static_body, (550, 100), (550, 150), 3),
]
# static platforms
platforms = [
pymunk.Segment(space.static_body, (170, 50), (270, 150), 3)
# , pymunk.Segment(space.static_body, (270, 100), (300, 100), 5)
,
pymunk.Segment(space.static_body, (400, 150), (450, 150), 3),
pymunk.Segment(space.static_body, (400, 200), (450, 200), 3),
pymunk.Segment(space.static_body, (220, 200), (300, 200), 3),
pymunk.Segment(space.static_body, (50, 250), (200, 250), 3),
pymunk.Segment(space.static_body, (10, 370), (50, 250), 3),
]
for s in static + platforms + rounded:
s.friction = 1.0
s.group = 1
space.add(*static, *platforms, *rounded)
# moving platform
platform_path = [(650, 100), (600, 200), (650, 300)]
platform_path_index = 0
platform_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
platform_body.position = 650, 100
s = pymunk.Segment(platform_body, (-25, 0), (25, 0), 5)
s.friction = 1.0
s.group = 1
s.color = pygame.Color("blue")
space.add(platform_body, s)
# pass through platform
passthrough = pymunk.Segment(space.static_body, (270, 100), (320, 100), 5)
passthrough.color = pygame.Color("yellow")
passthrough.friction = 1.0
passthrough.collision_type = 2
passthrough.filter = pymunk.ShapeFilter(categories=0b1000)
space.add(passthrough)
def passthrough_handler(arbiter, space, data):
if arbiter.shapes[0].body.velocity.y < 0:
return True
else:
return False
space.add_collision_handler(1, 2).begin = passthrough_handler
# player
body = pymunk.Body(5, float("inf"))
body.position = 100, 100
head = pymunk.Circle(body, 10, (0, 5))
head2 = pymunk.Circle(body, 10, (0, 13))
feet = pymunk.Circle(body, 10, (0, -5))
# Since we use the debug draw we need to hide these circles. To make it
# easy we just set their color to black.
feet.color = 0, 0, 0, 0
head.color = 0, 0, 0, 0
head2.color = 0, 0, 0, 0
mask = pymunk.ShapeFilter.ALL_MASKS() ^ passthrough.filter.categories
sf = pymunk.ShapeFilter(mask=mask)
head.filter = sf
head2.filter = sf
feet.collision_type = 1
feet.ignore_draw = head.ignore_draw = head2.ignore_draw = True
space.add(body, head, feet, head2)
direction = 1
remaining_jumps = 2
landing = {"p": Vec2d.zero(), "n": 0}
frame_number = 0
landed_previous = False
while running:
grounding = {
"normal": Vec2d.zero(),
"penetration": Vec2d.zero(),
"impulse": Vec2d.zero(),
"position": Vec2d.zero(),
"body": None,
}
# find out if player is standing on ground
def f(arbiter):
n = -arbiter.contact_point_set.normal
if n.y > grounding["normal"].y:
grounding["normal"] = n
grounding["penetration"] = -arbiter.contact_point_set.points[0].distance
grounding["body"] = arbiter.shapes[1].body
grounding["impulse"] = arbiter.total_impulse
grounding["position"] = arbiter.contact_point_set.points[0].point_b
body.each_arbiter(f)
well_grounded = False
if (
grounding["body"] != None
and abs(grounding["normal"].x / grounding["normal"].y) < feet.friction
):
well_grounded = True
remaining_jumps = 2
ground_velocity = Vec2d.zero()
if well_grounded:
ground_velocity = grounding["body"].velocity
for event in pygame.event.get():
if (
event.type == pygame.QUIT
or event.type == pygame.KEYDOWN
and (event.key in [pygame.K_ESCAPE, pygame.K_q])
):
running = False
elif event.type == pygame.KEYDOWN and event.key == pygame.K_p:
pygame.image.save(screen, "platformer.png")
elif event.type == pygame.KEYDOWN and event.key == pygame.K_UP:
if well_grounded or remaining_jumps > 0:
jump_v = math.sqrt(2.0 * JUMP_HEIGHT * abs(space.gravity.y))
impulse = (0, body.mass * (ground_velocity.y + jump_v))
body.apply_impulse_at_local_point(impulse)
remaining_jumps -= 1
elif event.type == pygame.KEYUP and event.key == pygame.K_UP:
body.velocity = body.velocity.x, min(
body.velocity.y, JUMP_CUTOFF_VELOCITY
)
# Target horizontal velocity of player
target_vx = 0
if body.velocity.x > 0.01:
direction = 1
elif body.velocity.x < -0.01:
direction = -1
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
direction = -1
target_vx -= PLAYER_VELOCITY
if keys[pygame.K_RIGHT]:
direction = 1
target_vx += PLAYER_VELOCITY
if keys[pygame.K_DOWN]:
direction = -3
feet.surface_velocity = -target_vx, 0
if grounding["body"] != None:
feet.friction = -PLAYER_GROUND_ACCEL / space.gravity.y
head.friction = HEAD_FRICTION
else:
feet.friction, head.friction = 0, 0
# Air control
if grounding["body"] == None:
body.velocity = Vec2d(
cpflerpconst(
body.velocity.x,
target_vx + ground_velocity.x,
PLAYER_AIR_ACCEL * dt,
),
body.velocity.y,
)
body.velocity = body.velocity.x, max(
body.velocity.y, -FALL_VELOCITY
) # clamp upwards as well?
# Move the moving platform
destination = platform_path[platform_path_index]
current = Vec2d(*platform_body.position)
distance = current.get_distance(destination)
if distance < PLATFORM_SPEED:
platform_path_index += 1
platform_path_index = platform_path_index % len(platform_path)
t = 1
else:
t = PLATFORM_SPEED / distance
new = current.interpolate_to(destination, t)
platform_body.position = new
platform_body.velocity = (new - current) / dt
### Clear screen
screen.fill(pygame.Color("black"))
### Helper lines
for y in [50, 100, 150, 200, 250, 300]:
color = pygame.Color("green")
pygame.draw.line(screen, color, (10, y), (680, y), 1)
### Draw stuff
space.debug_draw(draw_options)
direction_offset = 48 + (1 * direction + 1) // 2 * 48
if grounding["body"] != None and abs(target_vx) > 1:
animation_offset = 32 * (frame_number // 8 % 4)
elif grounding["body"] is None:
animation_offset = 32 * 1
else:
animation_offset = 32 * 0
position = body.position + (-16, 28)
p = pymunk.pygame_util.to_pygame(position, screen)
screen.blit(img, p, (animation_offset, direction_offset, 32, 48))
# Did we land?
if abs(grounding["impulse"].y) / body.mass > 200 and not landed_previous:
sound.play()
landing = {"p": grounding["position"], "n": 5}
landed_previous = True
else:
landed_previous = False
if landing["n"] > 0:
p = pymunk.pygame_util.to_pygame(landing["p"], screen)
pygame.draw.circle(screen, pygame.Color("yellow"), p, 5)
landing["n"] -= 1
# Info and flip screen
screen.blit(
font.render("fps: " + str(clock.get_fps()), 1, pygame.Color("white")),
(0, 0),
)
screen.blit(
font.render(
"Move with Left/Right, jump with Up, press again to double jump",
1,
pygame.Color("darkgrey"),
),
(5, height - 35),
)
screen.blit(
font.render("Press ESC or Q to quit", 1, pygame.Color("darkgrey")),
(5, height - 20),
)
pygame.display.flip()
frame_number += 1
### Update physics
space.step(dt)
clock.tick(fps)
if __name__ == "__main__":
sys.exit(main())