Platformer

Demonstrates: CharacterBody2D, CollisionShape2D, move_and_slide, is_on_floor, gravity, input actions, platform collision. Controls: A/D or Left/Right = move, Space = jump. Run: python packages/graphics/examples/platformer_demo.py

Source Code

 1#!/usr/bin/env python3
 2"""Platformer demo -- CharacterBody2D with gravity, jump, and platforms.
 3
 4Demonstrates: CharacterBody2D, CollisionShape2D, move_and_slide, is_on_floor,
 5              gravity, input actions, platform collision.
 6Controls: A/D or Left/Right = move, Space = jump.
 7Run: python packages/graphics/examples/platformer_demo.py
 8"""
 9
10from simvx.core import CharacterBody2D, Input, InputMap, Key, Node2D, Property, Vec2
11from simvx.graphics import App
12
13WIDTH, HEIGHT = 800, 600
14GRAVITY = 800.0
15
16
17class Platform(CharacterBody2D):
18    """Static platform that other bodies collide with."""
19
20    w = Property(120.0, range=(20, 400))
21    h = Property(16.0, range=(8, 60))
22
23    def __init__(self, w: float = 120, h: float = 16, **kwargs):
24        super().__init__(collision=Vec2(w / 2, h / 2), **kwargs)
25        self.w = w
26        self.h = h
27
28    def draw(self, renderer):
29        x = self.position.x - self.w / 2
30        y = self.position.y - self.h / 2
31        renderer.draw_rect(x, y, self.w, self.h, colour=(0.39, 0.71, 0.31, 1.0))
32
33
34class Player(CharacterBody2D):
35    speed = Property(250.0, range=(100, 500))
36    jump_force = Property(450.0, range=(200, 800))
37
38    def __init__(self, **kwargs):
39        super().__init__(collision=12, **kwargs)
40        self.can_jump = True
41
42    def process(self, dt: float):
43        # Horizontal movement
44        self.velocity.x = (Input.get_strength("right") - Input.get_strength("left")) * self.speed
45
46        # Gravity
47        self.velocity.y += GRAVITY * dt
48
49        # Jump
50        if Input.is_action_just_pressed("jump") and self.is_on_floor():
51            self.velocity.y = -self.jump_force
52
53        self.move_and_slide(dt)
54
55        # Reset if fallen off screen
56        if self.position.y > HEIGHT + 50:
57            self.position = Vec2(WIDTH / 2, 100)
58            self.velocity = Vec2()
59
60    def draw(self, renderer):
61        renderer.draw_rect(self.position.x - 10, self.position.y - 14, 20, 28, colour=(0.24, 0.55, 1.0, 1.0))
62        # Eyes
63        renderer.draw_rect(self.position.x - 5, self.position.y - 10, 4, 4, colour=(1.0, 1.0, 1.0, 1.0))
64        renderer.draw_rect(self.position.x + 1, self.position.y - 10, 4, 4, colour=(1.0, 1.0, 1.0, 1.0))
65
66
67class PlatformerDemo(Node2D):
68    def ready(self):
69        InputMap.add_action("left", [Key.A, Key.LEFT])
70        InputMap.add_action("right", [Key.D, Key.RIGHT])
71        InputMap.add_action("jump", [Key.SPACE])
72
73        self.add_child(Player(name="Player", position=Vec2(WIDTH / 2, 100)))
74
75        # Ground
76        self.add_child(Platform(name="Ground", w=WIDTH, h=20, position=Vec2(WIDTH / 2, HEIGHT - 10)))
77
78        # Platforms
79        platforms = [
80            (200, 450, 150),
81            (400, 350, 120),
82            (600, 450, 150),
83            (300, 250, 100),
84            (500, 250, 100),
85            (150, 150, 130),
86            (650, 150, 130),
87        ]
88        for i, (x, y, w) in enumerate(platforms):
89            self.add_child(Platform(name=f"Plat{i}", w=w, position=Vec2(x, y)))
90
91    def draw(self, renderer):
92        renderer.draw_text("Platformer Demo", (10, 10), scale=2, colour=(1.0, 1.0, 1.0))
93        renderer.draw_text("A/D: move  SPACE: jump", (10, 35), scale=1, colour=(0.71, 0.71, 0.71))
94
95
96if __name__ == "__main__":
97    App(title="Platformer Demo", width=WIDTH, height=HEIGHT).run(PlatformerDemo())