Platformer

CharacterBody2D with gravity, jump, and platforms.

▶ Run in browser

Tags: game character-body gravity input-actions

Demonstrates: CharacterBody2D, CollisionShape2D, move_and_slide, is_on_floor, gravity, input actions, platform collision.

Controls: A/D or Left/Right = move, Space = jump.

Source

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