HeartBeast Action RPG

The canonical Godot tutorial port, sword + roll + enemies.

▶ Run in browser

Upstream: https://github.com/uheartbeast/godot-action-rpg-tutorial

Tags: port tier-2

HeartBeast Action RPG: SimVX Port

Tier 2 port #18 of uheartbeast/godot-action-rpg-tutorial.

What it is

A faithful port of the canonical HeartBeast Godot Action RPG tutorial to SimVX. Implements the complete combat loop: player movement with acceleration/friction, directional sword attacks, roll/dodge with invincibility frames, enemy AI with Idle→Wander→Chase state machine, soft collision between enemies, destructible grass, and a heart-based HUD.

Running

# From /home/fezzik/dev/simvx:
uv run python ../ported_games/heartbeast_rpg/simvx_port/main.py              # desktop
uv run python ../ported_games/heartbeast_rpg/simvx_port/main.py --test       # headless smoke
uv run python ../ported_games/heartbeast_rpg/simvx_port/capture.py           # screenshots

Controls

Action

Keys

Move

W/A/S/D or Arrow Keys

Attack

J, Z, or Left Mouse

Roll/Dodge

K or X

Quit

Escape

Architecture

nodes/
├── camera.py      Bounded Camera2D with world clamping
├── effects.py     HitEffect, EnemyDeathEffect, GrassEffect (auto-destroy)
├── enemy.py       Bat enemy with Idle/Wander/Chase FSM + soft collision
├── grass.py       Destructible grass patches
├── hud.py         Heart-based health bar overlay
├── player.py      Player: movement physics, sword, roll, invulnerability
├── stats.py       Signal-based health tracker (mirrors upstream Stats.tscn)
└── world.py       Main scene: tilemap, entities, collision dispatcher

Feature parity with upstream

Feature

Status

Notes

Player movement (accel/friction)

Matches upstream speed values

8-directional movement

Normalised diagonal

Sword attack with rotation

Hitbox follows movement direction

Roll/dodge with i-frames

0.3s duration, 250 px/s

Enemy AI (Idle/Wander/Chase)

3-state FSM with detection radius

Soft collision

Push-apart between overlapping enemies

Destructible grass

20 patches, effect on destruction

Invincibility frames + flash

Blink effect for both player and enemies

Health system (signals)

Stats node with health_changed/no_health

Heart-based HUD

6 hearts, updates via signals

Bounded camera

Clamped to world edges

Death/respawn

2s delay, full health restore

Hit/death/grass effects

Particle burst, auto-destroy

Tree/Bush decorations

Procedural draw

Audio

SFX not included (licensing)

Animation (sprite flip)

Wing flap simulation

Tilemap terrain

Procedural grass + dirt path

Web Export

cd /home/fezzik/dev/simvx
uv run simvx export web ../ported_games/heartbeast_rpg/simvx_port/main.py \
    -o ../ported_games/heartbeast_rpg/simvx_port/web/index.html

Output: web/index.html (~2613 KB), auto-detects GameRoot, no extra flags needed.

License

Upstream tutorial code by uheartbeast (Godot co-creator) is CC0/MIT. Assets (HeartBeast pixel art, OpenGameArt music) are NOT bundled; this port uses procedural rendering for all visuals. Audio is stubbed for licensing compliance.

See also

  • NOTES.md: porting friction, engine gaps, BUGS.md cross-refs

  • ../PORTS.md: master port list

  • ../../simvx/CLAUDE.md: engine rules

Source

 1#!/usr/bin/env python3
 2"""HeartBeast Action RPG: The canonical Godot tutorial port, sword + roll + enemies.
 3
 4# /// simvx
 5# tags = ["port", "tier-2"]
 6# upstream = "https://github.com/uheartbeast/godot-action-rpg-tutorial"
 7# web = { width = 1280, height = 720, responsive = true }
 8# ///
 9
10Canonical Godot tutorial port: top-down ARPG with player movement,
11sword combat, roll/dodge, enemy AI with state machines, health system,
12invincibility frames, soft collisions, and grass destruction.
13
14Run:
15    uv run python ported_games/heartbeast_rpg/simvx_port/main.py
16    uv run python ported_games/heartbeast_rpg/simvx_port/main.py --test
17"""
18from __future__ import annotations
19
20import argparse
21import sys
22from pathlib import Path
23
24# Allow flat sibling imports (settings, nodes.*)
25_PORT_DIR = Path(__file__).resolve().parent
26if str(_PORT_DIR) not in sys.path:
27    sys.path.insert(0, str(_PORT_DIR))
28
29from simvx.core import InputMap, Key, MouseButton, Node2D
30from simvx.graphics import App
31
32from nodes.world import World
33from settings import HEIGHT, WIDTH, INPUT_ACTIONS
34
35
36# Key name → Key enum mapping (avoids reflection)
37_KEY_MAP = {
38    "w": "W", "a": "A", "s": "S", "d": "D",
39    "j": "J", "k": "K", "z": "Z", "x": "X",
40    "up": "UP", "down": "DOWN", "left": "LEFT", "right": "RIGHT",
41    "escape": "ESCAPE",
42}
43
44
45class GameRoot(Node2D):
46    """Root scene node: registers input actions then loads the world."""
47
48    def on_ready(self):
49        from simvx.core import Key, MouseButton
50
51        for action, keys in INPUT_ACTIONS.items():
52            bindings = []
53            for k in keys:
54                if k == "mouse_left":
55                    bindings.append(MouseButton.LEFT)
56                else:
57                    enum_name = _KEY_MAP.get(k.lower(), k.upper())
58                    bindings.append(getattr(Key, enum_name))
59            InputMap.add_action(action, bindings)
60
61        self.add_child(World())
62
63    def on_process(self, dt: float):
64        from simvx.core import Input
65        if Input.is_action_just_pressed("escape"):
66            self.app.quit()
67
68
69def main():
70    parser = argparse.ArgumentParser(description="HeartBeast Action RPG")
71    parser.add_argument(
72        "--test", action="store_true",
73        help="Headless smoke run, exit after a few frames."
74    )
75    args = parser.parse_args()
76
77    if args.test:
78        app = App(width=WIDTH, height=HEIGHT, title="HeartBeast RPG (test)", visible=False)
79        frames = app.run_headless(GameRoot(), frames=30)
80        print(f"OK: rendered {len(frames)} frames headlessly")
81        return
82
83    App(width=WIDTH, height=HEIGHT, title="HeartBeast Action RPG").run(GameRoot())
84
85
86if __name__ == "__main__":
87    main()