-
Notifications
You must be signed in to change notification settings - Fork 0
Fix Godot rewrite: controls swapped, movement near-zero, no sprites #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
136d0ef
534bc0b
474fc53
65a1e5c
de9baa6
afffb21
77e920f
39c8ae4
17c58c6
e12d302
0828579
7c5a74a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,86 @@ | ||
| # sluggers | ||
| # sluggers | ||
|
|
||
| A construction-based 2D platformer originally built in GameMaker. | ||
| The `copilot/rewrite-project-in-godot` branch contains a full rewrite of the | ||
| project in **Godot 4**. | ||
|
|
||
| --- | ||
|
|
||
| ## Original GameMaker project | ||
|
|
||
| The root of this repository contains the GameMaker project files (`.yyp`, | ||
| `objects/`, `scripts/`, `rooms/`, `sprites/`, etc.). These remain as a | ||
| reference during the Godot rewrite. | ||
|
|
||
| --- | ||
|
|
||
| ## Godot rewrite (`godot/`) | ||
|
|
||
| All Godot 4 source lives in the [`godot/`](godot/) subdirectory. | ||
|
|
||
| ### Project structure | ||
|
|
||
| ``` | ||
| godot/ | ||
| ├── project.godot # Godot 4 project configuration | ||
| ├── autoloads/ | ||
| │ ├── GameState.gd # Global singleton – timer, diamonds, creations, pause | ||
| │ └── InputManager.gd # Runtime key-rebinding helper | ||
| ├── scenes/ | ||
| │ ├── player/ | ||
| │ │ ├── Player.gd # State-machine player (GROUND / AIR / WALL) | ||
| │ │ └── Player.tscn | ||
| │ ├── objects/ | ||
| │ │ ├── Solid.tscn # Static terrain block | ||
| │ │ ├── SolidCreation.* # Player-placed solid block (Z key) | ||
| │ │ ├── BouncyCreation.* # Player-placed bouncy block (X key) | ||
| │ │ ├── Diamond.* # Collectible gem | ||
| │ │ ├── MovingBlock.* # Horizontally-shuttling platform | ||
| │ │ ├── Enemy.* # Damages/knocks back player; can be stomped | ||
| │ │ ├── DeathArea.* # Kill-zone (pits, spikes) | ||
| │ │ └── RoomWarp.* # Teleport trigger between rooms | ||
| │ ├── ui/ | ||
| │ │ ├── HUD.* # Timer / diamond / creations display | ||
| │ │ └── PauseMenu.* # Multi-page pause menu (audio/difficulty/graphics/controls) | ||
| │ ├── camera/ | ||
| │ │ └── GameCamera.* # Lerp-follow camera | ||
| │ └── rooms/ | ||
| │ ├── Room0.tscn # Level 1 | ||
| │ ├── Room1.tscn # Level 2 | ||
| │ ├── Room2.tscn # Level 3 | ||
| │ ├── Room3.tscn # Level 4 | ||
| │ └── Room4.tscn # Level 5 | ||
| └── assets/ | ||
| └── README.md # Sprite / audio asset list (to be imported) | ||
| ``` | ||
|
|
||
| ### How to open | ||
|
|
||
| 1. Install [Godot 4.3+](https://godotengine.org/download) | ||
| 2. Open Godot → **Import** → select `godot/project.godot` | ||
| 3. Press **F5** to run from Room 0 | ||
|
|
||
| ### Default controls | ||
|
|
||
| | Action | Key | | ||
| |---------------------|--------------| | ||
| | Move left | ← Left arrow | | ||
| | Move right | → Right arrow | | ||
| | Jump | ↑ Up arrow | | ||
| | Place solid block | Z | | ||
| | Place bouncy block | X | | ||
| | Pause | Escape | | ||
| | Restart level | R | | ||
|
|
||
| Controls can be rebound from the **Pause → Settings → Controls** menu. | ||
|
|
||
| ### Game mechanics | ||
|
|
||
| | Mechanic | Description | | ||
| |---|---| | ||
| | Ground / Air / Wall states | Full state-machine movement with friction, variable jump height, wall-slide, and wall-jump | | ||
| | Block creation | Limited budget of placeable blocks per level (solid or bouncy) | | ||
| | Diamonds | Collectibles that accumulate across the run | | ||
| | Moving platforms | Horizontal platforms that carry the player | | ||
| | Room warps | Teleporters that transition between levels | | ||
| | Pause menu | Audio, difficulty, graphics, and rebindable-controls settings | | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Godot 4 editor and cache directories | ||
| .godot/ | ||
|
|
||
| # Export templates and builds | ||
| *.exe | ||
| *.x86_64 | ||
| *.x86_32 | ||
| *.arm64 | ||
| *.app | ||
| *.apk | ||
| *.aab | ||
| *.html | ||
| *.pck | ||
| *.zip |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # Sluggers – Assets Placeholder | ||
|
|
||
| This directory is reserved for Sluggers' game assets in the Godot rewrite. | ||
|
|
||
| ## Expected contents | ||
|
|
||
| | Subdirectory | Contents | | ||
| |----------------|--------------------------------------------------------| | ||
| | `sprites/` | Player, enemy, block, diamond, background sprites | | ||
| | `sounds/` | Sound-effect audio files (`.ogg` or `.wav`) | | ||
| | `music/` | Background music tracks (`.ogg`) | | ||
| | `fonts/` | Bitmap / pixel fonts | | ||
|
|
||
| ## Sprite list (to be ported from the GameMaker project) | ||
|
|
||
| - `s_player` – idle standing sprite | ||
| - `s_player_right` – walking animation | ||
| - `s_player_jump` – airborne sprite | ||
| - `s_player_slide` – wall-slide sprite | ||
| - `s_player_attack` – attack animation | ||
| - `s_enemy` – enemy sprite | ||
| - `s_solid` – static terrain tile | ||
| - `s_diamond` – collectible gem sprite | ||
| - `s_hitbox` – debug hitbox overlay | ||
| - `s_room_warp` – warp zone indicator | ||
| - `s_creation_block` – player-placed solid block | ||
| - `s_bg` / `s_bg_sky` / `s_bg_clouds_behind` / `s_bg_clouds_front` – parallax layers | ||
| - `s_fg` / `s_mg` / `s_mg_city` / `s_mg_silhoette` – foreground / midground layers | ||
|
|
||
| Some scenes may still use simple `ColorRect` placeholder visuals where sprites | ||
| have not yet been imported or wired up. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| extends Node | ||
|
|
||
| ## Sluggers – GameState (Autoload) | ||
| ## | ||
| ## Global singleton that holds all persistent game data: | ||
| ## pause state, elapsed timer, diamond counter, player-creation resources, | ||
| ## and stamina. Emits signals so the HUD and other nodes can react reactively | ||
| ## without polling. | ||
|
|
||
| signal paused | ||
| signal resumed | ||
| signal diamonds_changed(count: int) | ||
| signal creations_changed(remaining: int) | ||
| signal timer_updated(minutes: int, seconds: int) | ||
| signal health_changed(current: int, maximum: int) | ||
| signal player_died | ||
| signal player_respawned | ||
|
|
||
| # ── Pause ────────────────────────────────────────────────────────────────── | ||
| var is_paused: bool = false | ||
|
|
||
| # ── Timer ────────────────────────────────────────────────────────────────── | ||
| var minutes: int = 0 | ||
| var seconds: float = 0.0 | ||
| var _last_timer_minute: int = -1 | ||
| var _last_timer_second: int = -1 | ||
|
|
||
| # ── Collectibles ─────────────────────────────────────────────────────────── | ||
| var diamonds: int = 0: | ||
| set(value): | ||
| field = max(value, 0) | ||
| diamonds_changed.emit(field) | ||
|
|
||
|
Comment on lines
+29
to
+33
|
||
| # ── Block-creation resources ─────────────────────────────────────────────── | ||
| var creations_allowed: int = 5 | ||
| var creations_remaining: int = 5: | ||
| set(value): | ||
| field = clampi(value, 0, creations_allowed) | ||
| creations_changed.emit(field) | ||
|
|
||
|
Comment on lines
+36
to
+40
|
||
| # ── Stamina / health ─────────────────────────────────────────────────────── | ||
| var player_stamina: int = 4: | ||
| set(value): | ||
| field = clampi(value, 0, max_player_stamina) | ||
| health_changed.emit(field, max_player_stamina) | ||
| var max_player_stamina: int = 4 | ||
|
Comment on lines
+42
to
+46
|
||
|
|
||
| # ── Difficulty (0 = Easy, 1 = Medium, 2 = Extreme) ──────────────────────── | ||
| var enemy_difficulty: int = 1 | ||
|
|
||
| # ── Audio volumes (linear 0–1) ───────────────────────────────────────────── | ||
| var master_volume: float = 1.0 | ||
| var sound_volume: float = 1.0 | ||
| var music_volume: float = 1.0 | ||
|
|
||
|
|
||
| func _process(delta: float) -> void: | ||
| if is_paused: | ||
| return | ||
| seconds += delta | ||
| if seconds >= 60.0: | ||
| seconds -= 60.0 | ||
| minutes += 1 | ||
| var displayed_second: int = int(seconds) | ||
| if minutes != _last_timer_minute or displayed_second != _last_timer_second: | ||
| _last_timer_minute = minutes | ||
| _last_timer_second = displayed_second | ||
| timer_updated.emit(minutes, displayed_second) | ||
|
|
||
|
Comment on lines
+57
to
+69
|
||
|
|
||
| # ── Pause control ────────────────────────────────────────────────────────── | ||
|
|
||
| func toggle_pause() -> void: | ||
| if is_paused: | ||
| resume() | ||
| else: | ||
| pause() | ||
|
|
||
|
|
||
| func pause() -> void: | ||
| is_paused = true | ||
| get_tree().paused = true | ||
| paused.emit() | ||
|
|
||
|
|
||
| func resume() -> void: | ||
| is_paused = false | ||
| get_tree().paused = false | ||
| resumed.emit() | ||
|
Comment on lines
+80
to
+89
|
||
|
|
||
|
|
||
| # ── Level reset ──────────────────────────────────────────────────────────── | ||
|
|
||
| func reset_level() -> void: | ||
| """Call when restarting the current level to restore per-level state.""" | ||
| for node in get_tree().get_nodes_in_group("player_created"): | ||
| node.queue_free() | ||
| creations_remaining = creations_allowed | ||
| player_stamina = max_player_stamina | ||
|
|
||
|
|
||
| func reset_game() -> void: | ||
| """Call when starting a full new game.""" | ||
| if is_paused: | ||
| resume() | ||
| minutes = 0 | ||
| seconds = 0.0 | ||
| _last_timer_minute = -1 | ||
| _last_timer_second = -1 | ||
| diamonds = 0 | ||
| timer_updated.emit(0, 0) | ||
| reset_level() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| extends Node | ||
|
|
||
| ## Sluggers – InputManager (Autoload) | ||
| ## | ||
| ## Wraps Godot's built-in InputMap to expose human-readable action labels and | ||
| ## support runtime key-rebinding (used by the Controls page of the pause menu). | ||
|
|
||
| # ── Action registry ──────────────────────────────────────────────────────── | ||
|
|
||
| ## Maps action name → display label shown in the Controls menu. | ||
| const ACTION_LABELS: Dictionary = { | ||
| "move_left": "Move Left", | ||
| "move_right": "Move Right", | ||
| "jump": "Jump", | ||
| "create_solid": "Create Solid Block", | ||
| "create_bouncy":"Create Bouncy Block", | ||
| "pause": "Pause", | ||
| "restart": "Restart Level", | ||
| } | ||
|
|
||
|
|
||
| # ── Helpers ──────────────────────────────────────────────────────────────── | ||
|
|
||
| func get_action_key_text(action: String) -> String: | ||
| """Returns a human-readable label for the first keyboard binding of action.""" | ||
| for event in InputMap.action_get_events(action): | ||
| if event is InputEventKey: | ||
| return event.as_text_keycode() | ||
| return "(unbound)" | ||
|
|
||
|
|
||
| func rebind_action(action: String, new_event: InputEventKey) -> void: | ||
| """Replaces the first keyboard event bound to action with new_event.""" | ||
| for event in InputMap.action_get_events(action): | ||
| if event is InputEventKey: | ||
| InputMap.action_erase_event(action, event) | ||
| break | ||
| InputMap.action_add_event(action, new_event) | ||
|
|
||
|
|
||
| func get_all_action_bindings() -> Dictionary: | ||
| """Returns a dictionary of action → current key label for all tracked actions.""" | ||
| var result: Dictionary = {} | ||
| for action in ACTION_LABELS: | ||
| result[action] = get_action_key_text(action) | ||
| return result |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| ; Engine configuration file. | ||
| ; It's best edited using the editor UI and not directly, | ||
| ; but the file format allows using it in version control. | ||
| ; | ||
| ; Format: | ||
| ; [section] ; section goes between [] | ||
| ; param=value ; assign values to parameters | ||
|
|
||
| config_version=5 | ||
|
|
||
| [application] | ||
|
|
||
| config/name="Sluggers" | ||
| config/description="A construction-based 2D platformer" | ||
| config/version="0.1.0" | ||
| run/main_scene="res://scenes/rooms/Room0.tscn" | ||
| config/features=PackedStringArray("4.3", "GL Compatibility") | ||
|
|
||
| [autoload] | ||
|
|
||
| GameState="*res://autoloads/GameState.gd" | ||
| InputManager="*res://autoloads/InputManager.gd" | ||
|
|
||
| [display] | ||
|
|
||
| window/size/viewport_width=384 | ||
| window/size/viewport_height=216 | ||
| window/size/window_width_override=1152 | ||
| window/size/window_height_override=648 | ||
| window/stretch/mode="canvas_items" | ||
| window/stretch/aspect="keep" | ||
|
|
||
| [input] | ||
|
|
||
| move_left={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| move_right={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| jump={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| create_solid={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":90,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| create_bouncy={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":88,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| pause={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
| restart={ | ||
| "deadzone": 0.2, | ||
| "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":82,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)] | ||
| } | ||
|
|
||
| [rendering] | ||
|
|
||
| textures/canvas_textures/default_texture_filter=0 | ||
| renderer/rendering_method="gl_compatibility" | ||
| renderer/rendering_method.mobile="gl_compatibility" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| extends Camera2D | ||
|
|
||
| ## Sluggers – GameCamera | ||
| ## | ||
| ## Smoothly follows a target node using linear interpolation, preserving the | ||
| ## same feel as the original GML camera that lerped at 0.1 per frame. | ||
| ## The target is normally the Player node in the current room. | ||
| ## Camera2D limit_* properties are respected: the lerp destination is clamped | ||
| ## so the view never drifts outside the room boundaries. | ||
|
|
||
| ## NodePath to the node this camera should follow (set per-room in the editor). | ||
| @export var target_path: NodePath = NodePath("") | ||
| ## Interpolation speed (0 = no movement, 1 = instant snap). | ||
| @export_range(0.0, 1.0, 0.01) var lerp_speed: float = 0.1 | ||
|
|
||
| var _target: Node2D = null | ||
|
|
||
|
|
||
| func _ready() -> void: | ||
| if not target_path.is_empty(): | ||
| _target = get_node_or_null(target_path) | ||
|
|
||
|
|
||
| func _process(_delta: float) -> void: | ||
| if _target == null: | ||
| return | ||
| var half := get_viewport_rect().size * 0.5 | ||
| var tx := clampf(_target.global_position.x, limit_left + half.x, limit_right - half.x) | ||
| var ty := clampf(_target.global_position.y, limit_top + half.y, limit_bottom - half.y) | ||
| global_position = global_position.lerp(Vector2(tx, ty), lerp_speed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README’s project structure comments are now out of date: it says
Enemy.*“restarts the level on contact”, but the newEnemy.gdapplies damage/knockback and supports stomping. Also, the room list shows only Room0/Room1 even though Room2–Room4 are added.