Platformer 2.0

This commit is contained in:
2026-07-07 19:03:53 -07:00
commit db83628775
23 changed files with 323 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8
+2
View File
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
+3
View File
@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/
+13
View File
@@ -0,0 +1,13 @@
extends Resource
class_name State
@export var id: StringName
func enter(_data: Object) -> void:
pass
func exit(_data: Object) -> void:
pass
func process(_data: Object, _delta: float) -> void:
pass
+1
View File
@@ -0,0 +1 @@
uid://b2htg6oquvdho
+71
View File
@@ -0,0 +1,71 @@
extends RefCounted
class_name StateMachine
const MIN_PRIORITY = -2147483648
signal state_changed(f_id: StringName, t_id: StringName)
var states: Dictionary[StringName, State] = {}
var transitions: Array[StateTransition] = []
var current_state: State
var _pending_id: StringName = &""
var _pending_priority = MIN_PRIORITY
func setup(state_list: Array[State], transition_list: Array[StateTransition], initial_id: StringName) -> void:
states.clear()
for s in state_list:
if s != null:
states[s.id] = s
transitions = transition_list
current_state = states.get(initial_id)
if current_state == null and not states.is_empty():
current_state = states.values()[0]
push_warning("State machine initial state '%s' not found, falling back to '%s'" % [initial_id, current_state.id])
func process(data: Object, delta: float) -> void:
if current_state == null:
return
current_state.process(data, delta)
_evaluate_transitions(data)
# Escape hatch for an immediate transition
func request_transition(to_id: StringName, priority: int = 999) -> void:
if priority >= _pending_priority:
_pending_id = to_id
_pending_priority = priority
func _evaluate_transitions(data: Object) -> void:
var best_id: StringName = &""
var best_priority: int = MIN_PRIORITY
# Is there a next state pending that isn't our current state?
if _pending_id != &"" and _pending_id != current_state.id and states.has(_pending_id):
best_id = _pending_id
best_priority = _pending_priority
for t in transitions:
# Skip the current state
if not t.matches_current(current_state.id) or t.to == current_state.id:
continue
if not states.has(t.to):
push_warning("StateMachine: transition targets unknown state '%s'" % t.to)
continue
if t.priority < best_priority:
continue
if t.is_satisfied(data):
best_id = t.to
best_priority = t.priority
_pending_id = &""
_pending_priority = MIN_PRIORITY
if best_id != &"" and best_id != current_state.id:
var from_id = current_state.id
current_state.exit(data)
current_state = states[best_id]
current_state.enter(data)
state_changed.emit(from_id, current_state.id)
+1
View File
@@ -0,0 +1 @@
uid://cp2okhs1ekwad
+15
View File
@@ -0,0 +1,15 @@
extends Resource
class_name StateTransition
const ANY_STATE: StringName = &"*"
@export var from: StringName = ANY_STATE
@export var to: StringName
@export var priority: int = 0
@export var condition: TransitionCondition
func matches_current(current_id: StringName) -> bool:
return from == ANY_STATE or from == current_id
func is_satisfied(data: Object) -> bool:
return condition == null or condition.evaluate(data)
+1
View File
@@ -0,0 +1 @@
uid://e2c8iukkr01l
+18
View File
@@ -0,0 +1,18 @@
extends Resource
class_name TransitionCondition
var _callable: Callable
func _init(callable: Callable = Callable()) -> void:
_callable = callable
func evaluate(data: Object) -> bool:
if _callable.is_valid():
var result = _callable.call(data)
if result is bool:
return result
else:
push_warning("Transition callable did not return boolean! Returning false.")
return false
return true
+1
View File
@@ -0,0 +1 @@
uid://bi3vhketkwgp6
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

+43
View File
@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://qmbdc8qsq5x3"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
+24
View File
@@ -0,0 +1,24 @@
[gd_scene format=3 uid="uid://6xemlk773ni5"]
[ext_resource type="Script" uid="uid://bcipombqbxffa" path="res://player/player_controller.gd" id="1_06t4h"]
[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_lbwmp"]
[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_lbwmp"]
[node name="Node2D" type="Node2D" unique_id=38411652]
[node name="CharacterBody2D" type="CharacterBody2D" parent="." unique_id=1863609037]
position = Vector2(499, 159)
script = ExtResource("1_06t4h")
[node name="CollisionShape2D" type="CollisionShape2D" parent="CharacterBody2D" unique_id=1734081974]
shape = SubResource("CapsuleShape2D_lbwmp")
[node name="AnimationPlayer" type="AnimationPlayer" parent="CharacterBody2D" unique_id=356868445]
[node name="StaticBody2D" type="StaticBody2D" parent="." unique_id=1508606128]
[node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D" unique_id=889471660]
position = Vector2(0, 576)
shape = SubResource("WorldBoundaryShape2D_lbwmp")
+37
View File
@@ -0,0 +1,37 @@
extends CharacterBody2D
@export var gravity = 900.0
var movement_fsm: StateMachine
@onready var animation_player: AnimationPlayer = $AnimationPlayer
var FSMSetup = preload("res://player/setup_state.gd")
var movement_states: Array[State] = []
var movement_transitions: Array[StateTransition] = []
var movement_init_state: StringName = &"idle"
func _ready() -> void:
movement_states = FSMSetup.build_states()
movement_transitions = FSMSetup.build_transitions()
movement_fsm = StateMachine.new()
movement_fsm.setup(movement_states, movement_transitions, movement_init_state)
if movement_fsm.current_state:
movement_fsm.current_state.enter(self)
func _physics_process(delta: float) -> void:
if(movement_fsm):
movement_fsm.process(self, delta)
print("Current state: %s" % movement_fsm.current_state.id)
func play_animation(anim_name: String) -> void:
if animation_player == null:
push_warning("State '%s': player_anims is null, cannot play '%s'" % [movement_fsm.current_state.id, anim_name])
return
# TODO:
# null checks: no sprite frames
# no animation by name
if animation_player.current_animation != anim_name:
animation_player.play(anim_name)
+1
View File
@@ -0,0 +1 @@
uid://bcipombqbxffa
+37
View File
@@ -0,0 +1,37 @@
extends Resource
static var IdleState = preload("res://player/states/idle.gd")
static var FallState = preload("res://player/states/fall.gd")
static func build_states() -> Array[State]:
var list: Array[State] = []
list.append(_named(IdleState.new(), &"idle"))
list.append(_named(FallState.new(), &"fall"))
return list
static func build_transitions() -> Array[StateTransition]:
var list: Array[StateTransition] = []
var t_is_on_floor = TransitionCondition.new(func(data):
return data.is_on_floor()
)
var t_n_is_on_floor = TransitionCondition.new(func(data):
return !data.is_on_floor()
)
list.append(_edge(&"idle", &"fall", t_n_is_on_floor, 10))
list.append(_edge(&"fall", &"idle", t_is_on_floor, 10))
return list
static func _named(state: State, id: StringName) -> State:
state.id = id
return state
static func _edge(from: StringName, to: StringName, condition: TransitionCondition, priority: int) -> StateTransition:
var t:= StateTransition.new()
t.from = from
t.to = to
t.condition = condition
t.priority = priority
return t
+1
View File
@@ -0,0 +1 @@
uid://d3vpouroevnpp
+8
View File
@@ -0,0 +1,8 @@
extends State
func enter(char: Object) -> void:
char.play_animation("fall")
func process(char: Object, delta: float) -> void:
char.velocity.y += 900.0 * delta
char.move_and_slide()
+1
View File
@@ -0,0 +1 @@
uid://c1dkyfrnd4mcu
+8
View File
@@ -0,0 +1,8 @@
extends State
func enter(char: Object) -> void:
char.play_animation("idle")
char.velocity.x = 0.0
func process(char: Object, delta: float) -> void:
char.move_and_slide()
+1
View File
@@ -0,0 +1 @@
uid://bq7nhigvlxpq5
+31
View File
@@ -0,0 +1,31 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="New Game Project 2"
run/main_scene="uid://6xemlk773ni5"
config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://icon.svg"
[display]
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[physics]
3d/physics_engine="Jolt Physics"
[rendering]
rendering_device/driver.windows="d3d12"
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"