├── .gitattributes ├── .gitignore ├── Addons └── StateMachine_system │ ├── State.gd │ └── StateMachine.gd ├── Assets ├── Characters │ ├── Player │ │ ├── Player-sheet.png │ │ └── Player-sheet.png.import │ └── Zombie │ │ ├── Zombie_1.png │ │ └── Zombie_1.png.import └── Tiles │ ├── BG.png │ ├── BG.png.import │ └── TileSet.tres ├── BaseClases └── Actor.gd ├── LICENSE ├── Level.gd ├── Level.tscn ├── Player ├── Player.gd ├── Player.tscn ├── PlayerState.gd ├── PlayerStateMachine.gd └── States │ ├── PlayerIdle.gd │ ├── PlayerJump.gd │ └── PlayerWalk.gd ├── README.md ├── default_env.tres ├── icon.png ├── icon.png.import └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /Addons/StateMachine_system/State.gd: -------------------------------------------------------------------------------- 1 | class_name State 2 | 3 | var name:String #Used when adding state into StateMachine to identify state 4 | var sm #StateMachine 5 | 6 | func _init(_sm)->void: #on .new() receives reference to it's StateMachine 7 | sm = _sm 8 | 9 | 10 | #defined virtual methods that might be called in states 11 | func enter(_msg:Dictionary = {})->void: 12 | pass 13 | 14 | func exit()->void: 15 | pass 16 | 17 | func unhandled_input(_event:InputEvent)->void: 18 | pass 19 | 20 | func physics_process(_delta:float)->void: 21 | pass 22 | 23 | func process(_delta:float)->void: 24 | pass 25 | 26 | func state_check()->void: 27 | pass 28 | -------------------------------------------------------------------------------- /Addons/StateMachine_system/StateMachine.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name StateMachine 3 | 4 | 5 | export (Array, GDScript) var script_array:Array 6 | 7 | var states: = {} #Dictionary to hold all states with String key to acces them 8 | var state #Will hold reference to active State 9 | var current_state: = "" #String that will hold name of active state in case it is needed 10 | 11 | 12 | func _ready()->void: #Set the first state 13 | yield(owner, "ready") #wait when scene root is ready because states need to fetch nodes in the scene 14 | 15 | for st in script_array: 16 | var inst = st.new(self) #Create new instance of State 17 | var n:String = inst.name #All States should set their names to create their keys for Dictionary 18 | states[n] = inst #Put the state into Dictionary and use their name as key 19 | 20 | #Base class doesn't implement starting state, should be implemented by inherited script (Example PlayerStateMachine) 21 | 22 | 23 | func _unhandled_input(event: InputEvent)->void: #because it's defined in script the engine will call it 24 | state.unhandled_input(event) #let the state to do what it needs to do with input 25 | 26 | func _physics_process(delta:float)->void: 27 | state.physics_process(delta) #state decides how to act during physics process 28 | 29 | func _process(delta:float)->void: 30 | state.process(delta) 31 | 32 | func transition_to(next_state: String, msg:Dictionary = {})->void: #states call this when states need to be changed 33 | if !states.has(next_state): #check if state is in dictionary 34 | print("No state: ", next_state) 35 | return 36 | 37 | var next = states[next_state] #reference the next state 38 | state.exit() #Allow old state to take care on it's exit method 39 | state = next #set new state 40 | state.enter(msg) #call entering state 41 | current_state = next_state #if later need reference which is current state 42 | 43 | -------------------------------------------------------------------------------- /Assets/Characters/Player/Player-sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/StateMachine_system_for_Godot/0750dde7091fbabf3928919ee2f402fe2df6283e/Assets/Characters/Player/Player-sheet.png -------------------------------------------------------------------------------- /Assets/Characters/Player/Player-sheet.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Player-sheet.png-4583b14f674a6c6d9b2083e56d28a428.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Assets/Characters/Player/Player-sheet.png" 13 | dest_files=[ "res://.import/Player-sheet.png-4583b14f674a6c6d9b2083e56d28a428.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Assets/Characters/Zombie/Zombie_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/StateMachine_system_for_Godot/0750dde7091fbabf3928919ee2f402fe2df6283e/Assets/Characters/Zombie/Zombie_1.png -------------------------------------------------------------------------------- /Assets/Characters/Zombie/Zombie_1.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Zombie_1.png-0c4f243b1288898e4feb80d060118bf6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Assets/Characters/Zombie/Zombie_1.png" 13 | dest_files=[ "res://.import/Zombie_1.png-0c4f243b1288898e4feb80d060118bf6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Assets/Tiles/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/StateMachine_system_for_Godot/0750dde7091fbabf3928919ee2f402fe2df6283e/Assets/Tiles/BG.png -------------------------------------------------------------------------------- /Assets/Tiles/BG.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/BG.png-656de879545c6a13c488ea4aa0c5caef.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Assets/Tiles/BG.png" 13 | dest_files=[ "res://.import/BG.png-656de879545c6a13c488ea4aa0c5caef.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=false 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /Assets/Tiles/TileSet.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="TileSet" load_steps=4 format=2] 2 | 3 | [ext_resource path="res://Assets/Tiles/BG.png" type="Texture" id=1] 4 | 5 | [sub_resource type="ConvexPolygonShape2D" id=1] 6 | points = PoolVector2Array( 16, 16, 0, 16, 0, 0, 16, 0 ) 7 | 8 | [sub_resource type="ConvexPolygonShape2D" id=2] 9 | points = PoolVector2Array( 16, 16, 0, 16, 0, 0, 16, 0 ) 10 | 11 | [resource] 12 | 0/name = "BG.png 0" 13 | 0/texture = ExtResource( 1 ) 14 | 0/tex_offset = Vector2( 0, 0 ) 15 | 0/modulate = Color( 1, 1, 1, 1 ) 16 | 0/region = Rect2( 0, 0, 16, 16 ) 17 | 0/tile_mode = 0 18 | 0/occluder_offset = Vector2( 0, 0 ) 19 | 0/navigation_offset = Vector2( 0, 0 ) 20 | 0/shape_offset = Vector2( 0, 0 ) 21 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 22 | 0/shape_one_way = false 23 | 0/shape_one_way_margin = 0.0 24 | 0/shapes = [ ] 25 | 0/z_index = 0 26 | 1/name = "BG.png 1" 27 | 1/texture = ExtResource( 1 ) 28 | 1/tex_offset = Vector2( 0, 0 ) 29 | 1/modulate = Color( 1, 1, 1, 1 ) 30 | 1/region = Rect2( 16, 0, 16, 16 ) 31 | 1/tile_mode = 0 32 | 1/occluder_offset = Vector2( 0, 0 ) 33 | 1/navigation_offset = Vector2( 0, 0 ) 34 | 1/shape_offset = Vector2( 0, 0 ) 35 | 1/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 36 | 1/shape_one_way = false 37 | 1/shape_one_way_margin = 0.0 38 | 1/shapes = [ ] 39 | 1/z_index = 0 40 | 2/name = "BG.png 2" 41 | 2/texture = ExtResource( 1 ) 42 | 2/tex_offset = Vector2( 0, 0 ) 43 | 2/modulate = Color( 1, 1, 1, 1 ) 44 | 2/region = Rect2( 32, 0, 16, 16 ) 45 | 2/tile_mode = 0 46 | 2/occluder_offset = Vector2( 0, 0 ) 47 | 2/navigation_offset = Vector2( 0, 0 ) 48 | 2/shape_offset = Vector2( 0, 0 ) 49 | 2/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 50 | 2/shape = SubResource( 1 ) 51 | 2/shape_one_way = false 52 | 2/shape_one_way_margin = 1.0 53 | 2/shapes = [ { 54 | "autotile_coord": Vector2( 0, 0 ), 55 | "one_way": false, 56 | "one_way_margin": 1.0, 57 | "shape": SubResource( 1 ), 58 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 59 | } ] 60 | 2/z_index = 0 61 | 3/name = "BG.png 3" 62 | 3/texture = ExtResource( 1 ) 63 | 3/tex_offset = Vector2( 0, 0 ) 64 | 3/modulate = Color( 1, 1, 1, 1 ) 65 | 3/region = Rect2( 0, 16, 16, 16 ) 66 | 3/tile_mode = 0 67 | 3/occluder_offset = Vector2( 0, 0 ) 68 | 3/navigation_offset = Vector2( 0, 0 ) 69 | 3/shape_offset = Vector2( 0, 0 ) 70 | 3/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 71 | 3/shape_one_way = false 72 | 3/shape_one_way_margin = 0.0 73 | 3/shapes = [ ] 74 | 3/z_index = 0 75 | 4/name = "BG.png 4" 76 | 4/texture = ExtResource( 1 ) 77 | 4/tex_offset = Vector2( 0, 0 ) 78 | 4/modulate = Color( 1, 1, 1, 1 ) 79 | 4/region = Rect2( 16, 16, 16, 16 ) 80 | 4/tile_mode = 0 81 | 4/occluder_offset = Vector2( 0, 0 ) 82 | 4/navigation_offset = Vector2( 0, 0 ) 83 | 4/shape_offset = Vector2( 0, 0 ) 84 | 4/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 85 | 4/shape = SubResource( 2 ) 86 | 4/shape_one_way = false 87 | 4/shape_one_way_margin = 1.0 88 | 4/shapes = [ { 89 | "autotile_coord": Vector2( 0, 0 ), 90 | "one_way": false, 91 | "one_way_margin": 1.0, 92 | "shape": SubResource( 2 ), 93 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 94 | } ] 95 | 4/z_index = 0 96 | -------------------------------------------------------------------------------- /BaseClases/Actor.gd: -------------------------------------------------------------------------------- 1 | extends KinematicBody2D 2 | class_name Actor 3 | 4 | export (float) var speed: = 1.0 * 60.0 5 | export (float) var acceleration: = 300.0 6 | export (float) var gravity: = 4.8 * 60 7 | export (float) var jump_impulse: = -180.0 8 | 9 | var direction: = Vector2.ZERO 10 | var velocity: = Vector2.ZERO 11 | var jump_release: = jump_impulse * 0.2 12 | var jump: = false 13 | var is_jumping: = false 14 | var is_grounded: = false 15 | const SNAP: = Vector2.DOWN * 1 16 | var snap: = Vector2.ZERO 17 | 18 | var max_jump:int = 0 19 | var jump_count:int = 0 20 | 21 | onready var body: = $Body #Parent node for Sprite and RayCast2D 22 | onready var JumpBuffer:Timer = $JumpBuffer #timer 23 | 24 | 25 | func direction_logic()->void: 26 | pass 27 | 28 | func velocity_logic(delta:float)->void: 29 | velocity = velocity.move_toward(Vector2(direction.x * speed, velocity.y), acceleration * delta) 30 | 31 | func gravity_logic(delta:float)->void: 32 | if is_grounded: 33 | if is_jumping: #landed the jump 34 | jump = false #force release jump button 35 | is_jumping = false 36 | snap = SNAP 37 | elif !is_jumping && jump: #works also when re-pressed before ground for jump buffer (pre-landing) 38 | velocity.y = jump_impulse 39 | is_jumping = true 40 | is_grounded = false 41 | snap = Vector2.ZERO 42 | jumping() 43 | JumpBuffer.stop() 44 | else: 45 | if is_jumping: 46 | if !jump: #released jump button mid-air 47 | is_jumping = false 48 | if velocity.y < jump_release: 49 | velocity.y = jump_release 50 | else: 51 | if jump: 52 | if !JumpBuffer.is_stopped(): 53 | JumpBuffer.stop() 54 | velocity.y = jump_impulse 55 | is_jumping = true 56 | is_grounded = false 57 | snap = Vector2.ZERO 58 | jumping() 59 | 60 | #----------This works for moving platforms but slides down the slopes 61 | # else: 62 | # velocity.y += gravity * delta 63 | # else: 64 | # velocity.y += gravity * delta 65 | 66 | #----------This stops sliding down the slopes but doesn't stick to moving platforms 67 | velocity.y += gravity * delta # <--- 68 | 69 | velocity.y = max(velocity.y, jump_impulse) #Limit fall speed to same as Jumping, but allow get faster to go up 70 | 71 | func ground_gravity_logic(delta:float)->void: 72 | if is_grounded: 73 | if is_jumping: #landed the jump 74 | jump = false #force release jump button 75 | is_jumping = false 76 | snap = SNAP 77 | elif !is_jumping && jump: #works also when re-pressed before ground for jump buffer (pre-landing) 78 | velocity.y = jump_impulse 79 | is_jumping = true 80 | is_grounded = false 81 | snap = Vector2.ZERO 82 | jumping() 83 | velocity.y += gravity * delta 84 | velocity.y = max(velocity.y, jump_impulse) 85 | 86 | func air_gravity_logic(delta:float)->void: 87 | if is_jumping: 88 | if !jump: #released jump button mid-air 89 | is_jumping = false 90 | if velocity.y < jump_release: 91 | velocity.y = jump_release 92 | elif jump && !JumpBuffer.is_stopped(): 93 | JumpBuffer.stop() 94 | velocity.y = jump_impulse 95 | is_jumping = true 96 | is_grounded = false 97 | jumping() 98 | velocity.y += gravity * delta 99 | velocity.y = max(velocity.y, jump_impulse) 100 | 101 | func collision_logic()->void: 102 | velocity = move_and_slide_with_snap(velocity, snap, Vector2.UP, true) 103 | 104 | func ground_update_logic()->void: 105 | var temp_grounded: = is_on_floor() 106 | if is_grounded && !temp_grounded: #just lost ground 107 | snap = Vector2.ZERO 108 | if !jump: 109 | JumpBuffer.start() 110 | elif !is_grounded && temp_grounded: #just landed 111 | snap = SNAP 112 | jump_count = 0 #resets double jump count 113 | landed() 114 | 115 | is_grounded = temp_grounded 116 | 117 | func physics_process(delta:float)->void: 118 | direction_logic() 119 | velocity_logic(delta) 120 | gravity_logic(delta) 121 | collision_logic() 122 | ground_update_logic() 123 | 124 | func ground_physics_process(delta:float)->void: 125 | direction_logic() 126 | velocity_logic(delta) 127 | gravity_logic(delta) 128 | collision_logic() 129 | ground_update_logic() 130 | 131 | func air_physics_process(delta:float)->void: 132 | direction_logic() 133 | velocity_logic(delta) 134 | gravity_logic(delta) 135 | collision_logic() 136 | ground_update_logic() 137 | 138 | func process(_delta:float)->void: 139 | if abs(direction.x)>= 0.001: 140 | body.scale.x = direction.x 141 | 142 | func damage()->void: 143 | pass 144 | 145 | func jumping()->void: 146 | pass 147 | 148 | func landed()->void: 149 | pass 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nezvers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Level.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | 4 | -------------------------------------------------------------------------------- /Level.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://Assets/Tiles/TileSet.tres" type="TileSet" id=1] 4 | [ext_resource path="res://Player/Player.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://Level.gd" type="Script" id=3] 6 | 7 | [node name="Level" type="Node2D"] 8 | script = ExtResource( 3 ) 9 | 10 | [node name="TileMap" type="TileMap" parent="."] 11 | tile_set = ExtResource( 1 ) 12 | cell_size = Vector2( 16, 16 ) 13 | format = 1 14 | tile_data = PoolIntArray( -1, 2, 0, -65536, 2, 0, -65535, 2, 0, -65534, 2, 0, -65533, 2, 0, -65532, 2, 0, -65531, 2, 0, -65530, 2, 0, -65529, 2, 0, -65528, 2, 0, -65527, 2, 0, -65526, 2, 0, -65525, 2, 0, -65524, 2, 0, -65523, 2, 0, -65522, 2, 0, -65521, 2, 0, -65520, 2, 0, -65519, 2, 0, -65518, 2, 0, -65517, 2, 0, -65516, 2, 0, 65535, 2, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0, 10, 0, 0, 11, 0, 0, 12, 0, 0, 13, 0, 0, 14, 0, 0, 15, 0, 0, 16, 0, 0, 17, 0, 0, 18, 0, 0, 19, 0, 0, 20, 2, 0, 131071, 2, 0, 65536, 0, 0, 65537, 0, 0, 65538, 0, 0, 65539, 0, 0, 65540, 0, 0, 65541, 0, 0, 65542, 0, 0, 65543, 0, 0, 65544, 0, 0, 65545, 0, 0, 65546, 0, 0, 65547, 0, 0, 65548, 0, 0, 65549, 0, 0, 65550, 0, 0, 65551, 0, 0, 65552, 0, 0, 65553, 0, 0, 65554, 0, 0, 65555, 0, 0, 65556, 2, 0, 196607, 2, 0, 131072, 0, 0, 131073, 0, 0, 131074, 0, 0, 131075, 0, 0, 131076, 0, 0, 131077, 0, 0, 131078, 0, 0, 131079, 0, 0, 131080, 0, 0, 131081, 0, 0, 131082, 0, 0, 131083, 0, 0, 131084, 0, 0, 131085, 0, 0, 131086, 0, 0, 131087, 0, 0, 131088, 0, 0, 131089, 0, 0, 131090, 0, 0, 131091, 0, 0, 131092, 2, 0, 262143, 2, 0, 196608, 0, 0, 196609, 0, 0, 196610, 0, 0, 196611, 0, 0, 196612, 0, 0, 196613, 0, 0, 196614, 0, 0, 196615, 0, 0, 196616, 0, 0, 196617, 0, 0, 196618, 0, 0, 196619, 0, 0, 196620, 0, 0, 196621, 0, 0, 196622, 0, 0, 196623, 0, 0, 196624, 0, 0, 196625, 0, 0, 196626, 0, 0, 196627, 0, 0, 196628, 2, 0, 327679, 2, 0, 262144, 0, 0, 262145, 0, 0, 262146, 0, 0, 262147, 0, 0, 262148, 0, 0, 262149, 0, 0, 262150, 0, 0, 262151, 0, 0, 262152, 0, 0, 262153, 0, 0, 262154, 0, 0, 262155, 0, 0, 262156, 0, 0, 262157, 0, 0, 262158, 0, 0, 262159, 0, 0, 262160, 0, 0, 262161, 0, 0, 262162, 0, 0, 262163, 0, 0, 262164, 2, 0, 393215, 2, 0, 327680, 0, 0, 327681, 0, 0, 327682, 0, 0, 327683, 0, 0, 327684, 0, 0, 327685, 4, 0, 327686, 4, 0, 327687, 4, 0, 327688, 4, 0, 327689, 0, 0, 327690, 0, 0, 327691, 0, 0, 327692, 0, 0, 327693, 0, 0, 327694, 0, 0, 327695, 0, 0, 327696, 0, 0, 327697, 0, 0, 327698, 0, 0, 327699, 0, 0, 327700, 2, 0, 458751, 2, 0, 393216, 0, 0, 393217, 0, 0, 393218, 0, 0, 393219, 0, 0, 393220, 0, 0, 393221, 0, 0, 393222, 0, 0, 393223, 0, 0, 393224, 0, 0, 393225, 0, 0, 393226, 0, 0, 393227, 0, 0, 393228, 4, 0, 393229, 4, 0, 393230, 4, 0, 393231, 4, 0, 393232, 4, 0, 393233, 0, 0, 393234, 0, 0, 393235, 0, 0, 393236, 2, 0, 524287, 2, 0, 458752, 0, 0, 458753, 0, 0, 458754, 0, 0, 458755, 0, 0, 458756, 0, 0, 458757, 0, 0, 458758, 0, 0, 458759, 0, 0, 458760, 0, 0, 458761, 0, 0, 458762, 0, 0, 458763, 0, 0, 458764, 0, 0, 458765, 0, 0, 458766, 0, 0, 458767, 0, 0, 458768, 0, 0, 458769, 0, 0, 458770, 0, 0, 458771, 0, 0, 458772, 2, 0, 589823, 2, 0, 524288, 4, 0, 524289, 4, 0, 524290, 4, 0, 524291, 0, 0, 524292, 0, 0, 524293, 0, 0, 524294, 0, 0, 524295, 0, 0, 524296, 0, 0, 524297, 0, 0, 524298, 0, 0, 524299, 0, 0, 524300, 0, 0, 524301, 0, 0, 524302, 0, 0, 524303, 0, 0, 524304, 0, 0, 524305, 0, 0, 524306, 0, 0, 524307, 0, 0, 524308, 2, 0, 655359, 2, 0, 589824, 0, 0, 589825, 0, 0, 589826, 0, 0, 589827, 0, 0, 589828, 0, 0, 589829, 0, 0, 589830, 0, 0, 589831, 0, 0, 589832, 0, 0, 589833, 0, 0, 589834, 0, 0, 589835, 0, 0, 589836, 0, 0, 589837, 0, 0, 589838, 0, 0, 589839, 0, 0, 589840, 0, 0, 589841, 0, 0, 589842, 0, 0, 589843, 0, 0, 589844, 2, 0, 720895, 2, 0, 655360, 0, 0, 655361, 0, 0, 655362, 0, 0, 655363, 0, 0, 655364, 0, 0, 655365, 0, 0, 655366, 0, 0, 655367, 0, 0, 655368, 0, 0, 655369, 0, 0, 655370, 0, 0, 655371, 0, 0, 655372, 0, 0, 655373, 0, 0, 655374, 0, 0, 655375, 0, 0, 655376, 0, 0, 655377, 0, 0, 655378, 0, 0, 655379, 0, 0, 655380, 2, 0, 786431, 2, 0, 720896, 2, 0, 720897, 2, 0, 720898, 2, 0, 720899, 2, 0, 720900, 2, 0, 720901, 2, 0, 720902, 2, 0, 720903, 2, 0, 720904, 2, 0, 720905, 2, 0, 720906, 2, 0, 720907, 2, 0, 720908, 2, 0, 720909, 2, 0, 720910, 2, 0, 720911, 2, 0, 720912, 2, 0, 720913, 2, 0, 720914, 2, 0, 720915, 2, 0, 720916, 2, 0 ) 15 | 16 | [node name="Player" parent="." instance=ExtResource( 2 )] 17 | position = Vector2( 122.121, 169.673 ) 18 | -------------------------------------------------------------------------------- /Player/Player.gd: -------------------------------------------------------------------------------- 1 | extends Actor 2 | class_name Player 3 | 4 | onready var anim:AnimationPlayer = $AnimationPlayer 5 | 6 | var scaler: = Vector2(1.0, 1.0) 7 | 8 | var velocity_previous: = Vector2.ZERO 9 | 10 | func unhandled_input(event)->void: 11 | if event.is_action_pressed("jump"): 12 | jump = true 13 | elif event.is_action_released("jump"): 14 | jump = false 15 | elif event.is_action_pressed("reset"): 16 | get_tree().reload_current_scene() 17 | elif event.is_action_pressed("exit"): 18 | get_tree().quit() 19 | 20 | func direction_logic()->void: 21 | direction.x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") 22 | direction.y = Input.get_action_strength("move_down") - Input.get_action_strength("move_up") 23 | 24 | func visual_process(delta:float): 25 | if abs(direction.x)>= 0.001: 26 | body.scale.x = sign(direction.x) 27 | 28 | if !is_grounded: #Not on the ground 29 | scaler.y = range_lerp(abs(velocity.y), 0.0, abs(jump_impulse), 0.85, 1.15) 30 | scaler.x = range_lerp(abs(velocity.y), 0.0, abs(jump_impulse), 1.0, 0.85) 31 | 32 | scaler.x = lerp(scaler.x, 1.0, 1.0 - pow(0.01, delta)) 33 | scaler.y = lerp(scaler.y, 1.0, 1.0 - pow(0.01, delta)) 34 | 35 | body.scale = scaler * Vector2(sign(body.scale.x), 1.0) 36 | velocity_previous = velocity 37 | 38 | func landed()->void: #triggered in ground logic when just landed 39 | scaler.x = range_lerp(abs(velocity_previous.y), 0.0, abs(jump_impulse), 1.2, 1.25) 40 | scaler.y = range_lerp(abs(velocity_previous.y), 0.0, abs(jump_impulse), 0.8, 0.5) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Player/Player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=2] 2 | 3 | [ext_resource path="res://Assets/Characters/Player/Player-sheet.png" type="Texture" id=1] 4 | [ext_resource path="res://Player/Player.gd" type="Script" id=2] 5 | [ext_resource path="res://Player/PlayerStateMachine.gd" type="Script" id=3] 6 | [ext_resource path="res://Player/States/PlayerIdle.gd" type="Script" id=4] 7 | [ext_resource path="res://Player/States/PlayerJump.gd" type="Script" id=5] 8 | [ext_resource path="res://Player/States/PlayerWalk.gd" type="Script" id=6] 9 | 10 | [sub_resource type="RectangleShape2D" id=1] 11 | extents = Vector2( 4, 7 ) 12 | 13 | [sub_resource type="Animation" id=2] 14 | resource_name = "Idle" 15 | length = 0.1 16 | tracks/0/type = "value" 17 | tracks/0/path = NodePath("Body/Sprite:frame") 18 | tracks/0/interp = 1 19 | tracks/0/loop_wrap = true 20 | tracks/0/imported = false 21 | tracks/0/enabled = true 22 | tracks/0/keys = { 23 | "times": PoolRealArray( 0 ), 24 | "transitions": PoolRealArray( 1 ), 25 | "update": 1, 26 | "values": [ 0 ] 27 | } 28 | 29 | [sub_resource type="Animation" id=3] 30 | resource_name = "Jump" 31 | length = 0.1 32 | tracks/0/type = "value" 33 | tracks/0/path = NodePath("Body/Sprite:frame") 34 | tracks/0/interp = 1 35 | tracks/0/loop_wrap = true 36 | tracks/0/imported = false 37 | tracks/0/enabled = true 38 | tracks/0/keys = { 39 | "times": PoolRealArray( 0 ), 40 | "transitions": PoolRealArray( 1 ), 41 | "update": 1, 42 | "values": [ 4 ] 43 | } 44 | 45 | [sub_resource type="Animation" id=4] 46 | length = 0.6 47 | loop = true 48 | tracks/0/type = "value" 49 | tracks/0/path = NodePath("Body/Sprite:frame") 50 | tracks/0/interp = 1 51 | tracks/0/loop_wrap = true 52 | tracks/0/imported = false 53 | tracks/0/enabled = true 54 | tracks/0/keys = { 55 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4, 0.5 ), 56 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1 ), 57 | "update": 1, 58 | "values": [ 1, 2, 3, 4, 5, 6 ] 59 | } 60 | 61 | [node name="Player" type="KinematicBody2D"] 62 | script = ExtResource( 2 ) 63 | 64 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 65 | position = Vector2( 0, -7 ) 66 | shape = SubResource( 1 ) 67 | 68 | [node name="Body" type="Node2D" parent="."] 69 | 70 | [node name="Sprite" type="Sprite" parent="Body"] 71 | position = Vector2( 0, -8 ) 72 | texture = ExtResource( 1 ) 73 | hframes = 7 74 | frame = 4 75 | 76 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 77 | autoplay = "Idle" 78 | anims/Idle = SubResource( 2 ) 79 | anims/Jump = SubResource( 3 ) 80 | anims/Walk = SubResource( 4 ) 81 | 82 | [node name="PlayerStateMachine" type="Node" parent="."] 83 | script = ExtResource( 3 ) 84 | script_array = [ ExtResource( 4 ), ExtResource( 6 ), ExtResource( 5 ) ] 85 | start_state = "Idle" 86 | 87 | [node name="Camera2D" type="Camera2D" parent="."] 88 | current = true 89 | 90 | [node name="JumpBuffer" type="Timer" parent="."] 91 | wait_time = 0.17 92 | one_shot = true 93 | -------------------------------------------------------------------------------- /Player/PlayerState.gd: -------------------------------------------------------------------------------- 1 | extends State 2 | class_name PlayerState 3 | 4 | var player: KinematicBody2D 5 | 6 | func _init(_sm).(_sm)->void: #inheriting script needs to call .(argument) from inherited scripts 7 | player = sm.owner #to make easier referencing later for player methods and nodes 8 | 9 | -------------------------------------------------------------------------------- /Player/PlayerStateMachine.gd: -------------------------------------------------------------------------------- 1 | extends StateMachine 2 | class_name PlayerStateMachine 3 | 4 | export (String) var start_state 5 | 6 | func _ready()->void: #Set the first state 7 | yield(._ready(), "completed") #Base class uses yield so need to wait when it is done 8 | 9 | if !states.has(start_state): #check if state is in dictionary 10 | print("No state: ", start_state) #Debug info when writing wrong state 11 | return 12 | state = states[start_state] #set the first state 13 | state.enter() #call enter method for the state in case it need some setup 14 | current_state = start_state #Sets string with the name od state 15 | -------------------------------------------------------------------------------- /Player/States/PlayerIdle.gd: -------------------------------------------------------------------------------- 1 | extends PlayerState 2 | class_name PlayerIdle 3 | 4 | 5 | func _init(_sm).(_sm)->void: #inheriting script needs to call .(argument) from inherited scripts 6 | name = "Idle" 7 | 8 | func enter(_msg:Dictionary = {})->void: #Called by StateMachine when transition_to("State") 9 | player.anim.play("Idle") #call AnimationPlayer to play Idle animation 10 | 11 | func exit()->void: 12 | pass 13 | 14 | func unhandled_input(event:InputEvent)->void: 15 | player.unhandled_input(event) #Player holds all global methods that is the same for most of the states 16 | 17 | func physics_process(delta:float)->void: 18 | player.ground_physics_process(delta) 19 | 20 | func process(delta:float)->void: 21 | player.visual_process(delta) #Handle player turning + stretch and squash 22 | state_check() #call check method if state need to be changed 23 | 24 | func state_check()->void: 25 | if player.is_grounded: #player has bool variable for reading if it is on ground 26 | if abs(player.direction.x) > 0.01: #players movement is above treshold 27 | sm.transition_to("Walk") #call StateMachine to change states 28 | else: 29 | sm.transition_to("Jump") 30 | 31 | 32 | -------------------------------------------------------------------------------- /Player/States/PlayerJump.gd: -------------------------------------------------------------------------------- 1 | extends PlayerState 2 | class_name PlayerJump 3 | 4 | 5 | func _init(_sm).(_sm)->void: 6 | name = "Jump" 7 | 8 | func enter(_msg:Dictionary = {})->void: 9 | player.anim.play("Jump") 10 | 11 | func exit()->void: 12 | pass 13 | 14 | func unhandled_input(event:InputEvent)->void: 15 | player.unhandled_input(event) 16 | 17 | func physics_process(delta:float)->void: 18 | player.air_physics_process(delta) 19 | 20 | func process(delta:float)->void: 21 | player.visual_process(delta) 22 | state_check() 23 | 24 | func state_check()->void: 25 | if player.is_grounded: 26 | if abs(player.direction.x) > 0.01: 27 | sm.transition_to("Walk") 28 | else: 29 | sm.transition_to("Idle") 30 | 31 | -------------------------------------------------------------------------------- /Player/States/PlayerWalk.gd: -------------------------------------------------------------------------------- 1 | extends PlayerState 2 | class_name PlayerWalk 3 | 4 | 5 | func _init(_sm).(_sm)->void: 6 | name = "Walk" 7 | 8 | func enter(_msg:Dictionary = {})->void: 9 | player.anim.play("Walk") 10 | 11 | func exit()->void: 12 | pass 13 | 14 | func unhandled_input(event:InputEvent)->void: 15 | player.unhandled_input(event) 16 | 17 | func physics_process(delta:float)->void: 18 | player.ground_physics_process(delta) 19 | 20 | func process(delta:float)->void: 21 | player.visual_process(delta) 22 | state_check() 23 | 24 | func state_check()->void: 25 | if player.is_grounded: 26 | if abs(player.direction.x) < 0.01: 27 | sm.transition_to("Idle") 28 | else: 29 | sm.transition_to("Jump") 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # State Machine System for Godot 2 | Flexible and lightweight StateMachine for Godot and each system related code row is commented. 3 | States are plain scripts without extending any class except default Reference what GDScript is. States are added to StateMachine through exported script Array. Decision on default State choice must be implemented per use case (Player, Camera, Pickup, Enemy). 4 | 5 | Project is also simple use case for platformer (Idle, Walk, Jump) 6 | 7 | ## Usage 8 | * Create StateMachine inherited script for different types of use cases (example - PlayerStateMachine); 9 | * Create State inherited state scripts (example - PlayerState -> PlayerIdle); 10 | * Use virtual functions inheritted from State (unhandled_input, physics_process, process, state_check); 11 | * Add State scripts into StateMachine exported script_array. PlayerStateMachine implements default state choice, you can change it to script_array[0] instead of String key. 12 | * For getting references to nodes or get_tree() use sm (state machine) - yield(sm.get_tree().create_timer(1.0), "timeout") 13 | 14 | ## IMPORTANT 15 | * Each new state needs to implement: 16 | ``` 17 | func _init(_sm).(_sm)->void: 18 | name = "Idle" 19 | ``` 20 | ## Walkthrough 21 |