# Canvas3D + 2D Engine rpg2d is a top-down action game built with the engine 2D layer. It uses generated pixel art, a packed atlas, animated sprites, chunked tilemaps, collision queries, triggers, point lights, hard-edged dungeon shadows, an integer-scale camera, UI, touch controls, keys, and controller buttons. See the [3D Engine reference](/doc/llm-doc/engine3d/llms.txt#2d-overview) for the full API. ![rpg2d dungeon scene](/doc/assets/engine2d-rpg2d-preview.png) ## Controls and states Movement merges the input axes with the virtual joystick. Attack accepts Space, J, Z, Enter, controller button 0 or 2, or the virtual A button. Confirm accepts taps, Space, Enter, controller button 0 or 9, or the virtual A button. The game uses the states `title`, `play`, `dialog`, `dying`, `dead`, and `win`. ```python atk = (inp.get_key_down("space") or inp.get_key_down("j") or inp.get_key_down("z") or inp.get_key_down("enter") or inp.get_button_down(0) or inp.get_button_down(2) or (self.btn_a is not None and self.btn_a.pressed)) ``` ## Entry point and package layout | File | Responsibility | |---|---| | `rpg2d.py` | Creates Canvas3D, sets the app directory, frame rate, multisampling, and window. | | `rpg/window.py` | Creates `Game` from `EngineWindow.setup`. | | `rpg/game.py` | Builds scenes, maps, lights, camera, input, game states, and HUD. | | `rpg/actors.py` | Player, enemies, projectiles, pickups, doors, animation, combat, and flicker components. | | `rpg/art.py` | Generates sprite and tile pixels. | | `rpg/maps.py` | Builds overworld and dungeon cell data. | | `rpg/sfx.py` | Creates and plays game sounds. | ## Atlas and tile creation The atlas uses nearest sampling and 16 pixels per world unit. Pivots place characters at their feet and center items. The water tile alternates two frames at two frames per second. ```python at = SpriteAtlas(512, True, 16.0) S = {} def add(name, im, pivot=(0.5, 0.0), ppu=16.0, flip=False): S[name] = at.add(name, im.w, im.h, bytes(im.d), ppu, pivot) ``` ```python Tile(frames=[S["water0"], S["water1"]], fps=2.0, collider="solid", name="water") ``` References: [SpriteAtlas](/doc/#engine3d-api-spriteatlas), [Sprite](/doc/#engine3d-api-sprite), [Tile](/doc/#engine3d-api-tile). ## Overworld and dungeon Both areas use a tilemap, lit chunk renderer, and merged tilemap collider. The dungeon adds a tilemap shadow caster. ```python def make_map_node(self, name, ox): node = self.scene.create(name, None, (ox, 0, 0), Tilemap(), TilemapRenderer("Ground", 0, True), TilemapCollider2D()) return node, node.get_component(Tilemap) ``` ```python node, tm = self.make_map_node("Dungeon", DX) node.add_component(TilemapShadowCaster2D()) ``` References: [Tilemap](/doc/#engine3d-api-tilemap), [TilemapRenderer](/doc/#engine3d-api-tilemaprenderer), [TilemapCollider2D](/doc/#engine3d-api-tilemapcollider2d), [TilemapShadowCaster2D](/doc/#engine3d-api-tilemapshadowcaster2d). ## Player, animation, and combat The player node combines a lit SpriteRenderer, SpriteAnimator, Rigidbody2D, BoxCollider2D, and a shadowing lantern. Movement writes body velocity. Sword attacks use an overlap box and ignore triggers. Pickups use `on_trigger_enter`. Locked doors use `on_collision_stay`. Trigger stay is not emitted. ```python cols = g.physics.overlap_box((cx, cy), size, 0.0, -1, False) for c in cols: e = c.node.get_component(Enemy) if e is not None and e not in self.swing_hit: self.swing_hit.add(e) e.hurt(1, (p.x, p.y)) ``` ```python def on_trigger_enter(self, other): pl = other.get_component(Player) if pl is None: return ``` References: [SpriteAnimator](/doc/#engine3d-api-spriteanimator), [Rigidbody2D](/doc/#engine3d-api-rigidbody2d), [Physics2D](/doc/#engine3d-api-physics2d), [Collision2D](/doc/#engine3d-api-collision2d). ## Dungeon lights and shadows The dungeon tilemap supplies merged caster edges. Torch lights sit on open floor beside wall torches and enable shadows. `Flicker` varies intensity. The player lantern follows the actor. With shadows disabled, the same lights keep their range and attenuation but no longer hide light behind caster edges. ```python ln = self.scene.create("torch", self.dun_root, (x + 0.5, ty - 0.35, 0.0), Light2D("point", (1.0, 0.65, 0.3), 1.3, 5.5, 0.15, 1.2, shadows=True, shadow_strength=0.9), Flicker(1.3)) ``` Reference: [Light2D](/doc/#engine3d-api-light2d), [World2D](/doc/#engine3d-api-world2d). ## Camera scaling and pixel snapping The camera chooses an integer scale from viewport size. It rounds camera positions to one source pixel, which is `1 / 16` world unit for this atlas. ```python scale = max(1, int(round(min(w, h) / (16.0 * 11.0)))) self.camera.ortho_size = h / (2.0 * 16.0 * scale) ``` References: [ortho_for_pixels](/doc/#engine3d-api-ortho-for-pixels), [snap_to_pixel](/doc/#engine3d-api-snap-to-pixel). ## HUD and virtual controls The HUD uses pixel coordinates from the top-left. A virtual joystick occupies the lower-left region. A virtual A button occupies the lower-right region. ```python self.joy = inp.add_joystick(VirtualJoystick(m * 0.2, h - m * 0.22, m * 0.13, 0.15, region=(0, h * 0.3, w * 0.55, h * 0.7))) self.btn_a = inp.add_button(VirtualButton(w - s * 1.5, h - s * 1.6, s, s)) ``` ## Feature map | Feature | rpg2d location | Reference | |---|---|---| | Atlas and generated art | `rpg/game.py`, `make_atlas`; `rpg/art.py` | [SpriteAtlas](/doc/#engine3d-api-spriteatlas) | | Tile definitions and animated water | `rpg/game.py`, `make_tiles` | [Tile](/doc/#engine3d-api-tile) | | Overworld and dungeon data | `rpg/maps.py`; `rpg/game.py`, `build_overworld`, `build_dungeon` | [Tilemap](/doc/#engine3d-api-tilemap) | | Player and enemy motion | `rpg/actors.py`, `Player`, `Enemy` | [Rigidbody2D](/doc/#engine3d-api-rigidbody2d) | | Sword hit query | `rpg/actors.py`, `Player.sword_hits` | [Physics2D](/doc/#engine3d-api-physics2d) | | Pickup and projectile triggers | `rpg/actors.py`, `Pickup` and projectile components | [Collider2D](/doc/#engine3d-api-collider2d) | | Locked door contacts | `rpg/actors.py`, `LockedDoor` | [Collision2D](/doc/#engine3d-api-collision2d) | | Dungeon lights and flicker | `rpg/game.py`, `build_dungeon`; `rpg/actors.py`, `Flicker` | [Light2D](/doc/#engine3d-api-light2d) | | Dungeon shadow edges | `rpg/game.py`, `build_dungeon` | [TilemapShadowCaster2D](/doc/#engine3d-api-tilemapshadowcaster2d) | | Camera and pixel alignment | `rpg/game.py`, `apply_camera` | [Pixel camera utilities](/doc/#engine3d-category-2d-tilemaps) | | HUD and virtual controls | `rpg/game.py`, `draw_hud` and `layout_controls` | [Input](/doc/#engine3d-api-input) | | Sounds | `rpg/sfx.py`, `Sfx` | [Audio](/doc/#engine3d-api-audiooutput) |