diff --git a/Main.tscn b/Main.tscn index dbfd82f..8d296ea 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1,10 +1,8 @@ -[gd_scene load_steps=19 format=2] +[gd_scene load_steps=17 format=2] -[ext_resource path="res://src/playerB/Player.tscn" type="PackedScene" id=1] [ext_resource path="res://src/playerC/Player.tscn" type="PackedScene" id=2] [ext_resource path="res://assets/Tiles/Assets/Assets.png" type="Texture" id=3] [ext_resource path="res://src/CameraControl-Position2D.gd" type="Script" id=5] -[ext_resource path="res://src/enemyB/Enemy2.tscn" type="PackedScene" id=6] [sub_resource type="ConvexPolygonShape2D" id=2] points = PoolVector2Array( 16, 16, 0, 16, 0, 0, 16, 0 ) @@ -145,12 +143,8 @@ __meta__ = { "_edit_lock_": true } -[node name="Player" parent="." instance=ExtResource( 1 )] -position = Vector2( 154, 27 ) - -[node name="Player2" parent="." instance=ExtResource( 2 )] +[node name="Player" parent="." instance=ExtResource( 2 )] position = Vector2( 81, 27 ) -player_number = 2 [node name="CameraControl" type="Position2D" parent="."] process_priority = 1 @@ -194,6 +188,3 @@ shape = SubResource( 13 ) [node name="CollisionShape2D2" type="CollisionShape2D" parent="CameraBoundaries"] position = Vector2( 1281, 68 ) shape = SubResource( 13 ) - -[node name="NewEnemy" parent="." instance=ExtResource( 6 )] -position = Vector2( 400, 81 ) diff --git a/project.godot b/project.godot index 1b693a8..15de801 100644 --- a/project.godot +++ b/project.godot @@ -20,14 +20,32 @@ _global_script_classes=[ { "path": "res://src/playerB/player_move_component.gd" }, { "base": "Node", +"class": "PushdownStateMachine", +"language": "GDScript", +"path": "res://src/pushdown_state_machine.gd" +}, { +"base": "Node", "class": "State", "language": "GDScript", "path": "res://src/state.gd" +}, { +"base": "Node", +"class": "StateMachine", +"language": "GDScript", +"path": "res://src/state_machine.gd" +}, { +"base": "Node", +"class": "StateModifier", +"language": "GDScript", +"path": "res://src/state_modifier.gd" } ] _global_script_class_icons={ "Enemy": "", "Movement": "", -"State": "" +"PushdownStateMachine": "", +"State": "", +"StateMachine": "", +"StateModifier": "" } [application] diff --git a/src/CameraControl-Position2D.gd b/src/CameraControl-Position2D.gd index 5d66a83..b7c592b 100644 --- a/src/CameraControl-Position2D.gd +++ b/src/CameraControl-Position2D.gd @@ -80,7 +80,7 @@ func jump_to_grid_position(): else: $Camera2D.limit_left = -10000000 if $Right.is_colliding(): - print("Oh God! No! ", $Right.get_collision_point().x, $Right.global_position) + #print("Oh God! No! ", $Right.get_collision_point().x, $Right.global_position) $Camera2D.limit_right = $Right.get_collision_point().x else: $Camera2D.limit_right = 10000000 diff --git a/src/components/Hurtbox_Component.tscn b/src/components/Hurtbox_Component.tscn new file mode 100644 index 0000000..ab88124 --- /dev/null +++ b/src/components/Hurtbox_Component.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=2 format=2] + +[sub_resource type="RectangleShape2D" id=148] +extents = Vector2( 11.5, 14.5 ) + +[node name="Hurtbox_Component" type="Area2D"] +collision_layer = 16 +collision_mask = 128 + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +self_modulate = Color( 1, 0, 0, 1 ) +position = Vector2( 0, -4 ) +shape = SubResource( 148 ) diff --git a/src/enemyB/states/attack.gd b/src/enemyB/states/attack.gd index 6d34d45..124f031 100644 --- a/src/enemyB/states/attack.gd +++ b/src/enemyB/states/attack.gd @@ -5,7 +5,7 @@ export (NodePath) var idle_node onready var idle_state: State = get_node(idle_node) var can_fire = true -func enter() -> void: +func enter(state_modifiers: Array = []) -> void: .enter() parent.velocity.x = 0 parent.set_hitbox(false) diff --git a/src/enemyB/states/hurt.gd b/src/enemyB/states/hurt.gd index a0ac275..871d846 100644 --- a/src/enemyB/states/hurt.gd +++ b/src/enemyB/states/hurt.gd @@ -4,7 +4,7 @@ export (NodePath) var idle_node onready var idle_state: State = get_node(idle_node) -func enter() -> void: +func enter(state_modifiers: Array = []) -> void: .enter() parent.velocity.x = 0 parent.set_hurtbox(false) diff --git a/src/enemyB/states/idle.gd b/src/enemyB/states/idle.gd index 397bfac..a5dfd11 100644 --- a/src/enemyB/states/idle.gd +++ b/src/enemyB/states/idle.gd @@ -14,7 +14,7 @@ onready var fire_state: State = get_node(fire_node) onready var wander_state: State = get_node(wander_node) onready var attack_state: State = get_node(attack_node) -func enter() -> void: +func enter(state_modifiers: Array = []) -> void: .enter() parent.set_hurtbox(true) parent.velocity.x = 0 diff --git a/src/enemyB/states/jump.gd b/src/enemyB/states/jump.gd index 0742ba0..d7c2857 100644 --- a/src/enemyB/states/jump.gd +++ b/src/enemyB/states/jump.gd @@ -11,7 +11,7 @@ onready var move_state: State = get_node(move_node) export var jump_force: float = 900.0 -func enter() -> void: +func enter(state_modifiers: Array = []) -> void: .enter() parent.velocity.y = -jump_force diff --git a/src/enemyB/states/wander.gd b/src/enemyB/states/wander.gd index b1d2f68..3b13b86 100644 --- a/src/enemyB/states/wander.gd +++ b/src/enemyB/states/wander.gd @@ -13,7 +13,7 @@ onready var attack_state: State = get_node(attack_node) var flip_flop = false var move_direction_x = 1.0 -func enter() -> void: +func enter(state_modifiers: Array = []) -> void: .enter() parent.set_hurtbox(true) state_timeout.start() diff --git a/src/playerC/Player-KinematicBody2D.gd b/src/playerC/Player-KinematicBody2D.gd index 79df14b..f5403a4 100644 --- a/src/playerC/Player-KinematicBody2D.gd +++ b/src/playerC/Player-KinematicBody2D.gd @@ -5,13 +5,15 @@ var direction = Vector2(0,0) export var player_number: int = 1 +export var sprite_facing_right: bool = true + onready var movement_animations: AnimatedSprite = $AnimatedSprite onready var movement_state_machine: Node = $Controllers/state_machine onready var player_move_component = $Controllers/move_component -onready var player_hurtbox = $Hurtbox/CollisionShape2D +onready var player_hurtbox = $Hurtbox_Component/CollisionShape2D onready var gun = $Gun @@ -62,118 +64,3 @@ func shoot_projectile(): var is_shooting = false is_shooting = gun.shoot(direction.x) -#func _ready(): -# # Static types are necessary here to avoid warnings. -# var camera: Camera2D = $Camera2D -# camera.custom_viewport = $"../.." -# yield(get_tree(), "idle_frame") -# camera.make_current() - -# -#func _physics_process(delta): -# # Horizontal movement -# velocity.x = (Input.get_axis("ui_left", "ui_right") * WALK_MAX_SPEED) -# -# # Vertical movement code. Apply gravity. -# velocity.y += gravity * delta -# -# # Move based on the velocity and snap to the ground. -# #velocity = move_and_slide_with_snap(velocity, Vector2.DOWN, Vector2.UP) -# velocity = move_and_slide(velocity, Vector2(0, -1)) -# -# # Check for jumping. is_on_floor() must be called after movement code. -# # Apply Jump velocity -# if is_on_floor() and Input.is_action_just_pressed("ui_accept"): -# velocity.y = -JUMP_SPEED -# -# # Animate based on last state and velocity -# if velocity.x > 0 and _animated_sprite.flip_h != true: -# _animated_sprite.flip_h = true -# if velocity.x < 0 and _animated_sprite.flip_h != false: -# _animated_sprite.flip_h = false -# -# # forced state changes like falling an gitting hurt -# if velocity.y > 0 and player_state != STATE.JUMP and player_state != STATE.FALL: -# print("STATE_TRANSITION: Fall") -# player_state = STATE.FALL -# -# -# # Each state should trigger its own animation -# match player_state: -# STATE.IDLE: -# # Transitions -# if velocity.y == -JUMP_SPEED: -# print("STATE_TRANSITION: Jump") -# player_last_state = STATE.IDLE -# player_state = STATE.JUMP -# elif velocity.x != 0: -# print("STATE_TRANSITION: Run") -# player_last_state = STATE.IDLE -# player_state = STATE.RUN -# # Animation -# if player_last_state != STATE.IDLE: -# $IdleTimeout.start() -# _animated_sprite.play("idle") -# player_last_state = STATE.IDLE -# if $IdleTimeout.time_left == 0 and _animated_sprite.animation != 'blink': -# _animated_sprite.play("blink") -# animation_play_finished = false -# if $IdleTimeout.time_left == 0 and animation_play_finished == true: -# $IdleTimeout.start() -# _animated_sprite.play("idle") -# print("blink over") -# -# STATE.JUMP: -# # Transitions -# if is_on_floor(): -# print("STATE_TRANSITION: Idle") -# _animated_sprite.play("idle") -# player_last_state = STATE.JUMP -# player_state = STATE.IDLE -# # Animation -# if player_last_state != STATE.JUMP: -# _animated_sprite.play("jump") -# player_last_state = STATE.JUMP -# -# STATE.FALL: -# # Transitions -# if is_on_floor(): -# print("STATE_TRANSITION: Idle") -# _animated_sprite.play("idle") -# player_last_state = STATE.FALL -# player_state = STATE.IDLE -# # Animation -# if player_last_state != STATE.FALL: -# _animated_sprite.animation = "jump" -# _animated_sprite.frame = 8 -# _animated_sprite.stop() -# player_last_state = STATE.FALL -# -# STATE.RUN: -# # print (_animated_sprite.animation ) # apparentl playing doesn't stop when loop is disabled. -# # Transitions -# if velocity.y == -JUMP_SPEED: -# print("STATE_TRANSITION: Jump") -# _animated_sprite.play("jump") -# player_last_state = STATE.RUN -# player_state = STATE.JUMP -# elif velocity.x == 0: -# print("STATE_TRANSITION: Idle") -# _animated_sprite.play("idle") -# player_last_state = STATE.RUN -# player_state = STATE.IDLE -# # Animation -# if player_last_state == STATE.IDLE: -# _animated_sprite.play("step") -# animation_play_finished = false -# player_last_state = STATE.RUN -# elif animation_play_finished == true: -# _animated_sprite.play("run") -# animation_play_finished = false -# -# -#func _on_AnimatedSprite_animation_finished(): -# animation_play_finished = true -# print($AnimatedSprite.animation," anim over ", $IdleTimeout.time_left, animation_play_finished) - - diff --git a/src/playerC/Player.tscn b/src/playerC/Player.tscn index 9806bbf..d58131c 100644 --- a/src/playerC/Player.tscn +++ b/src/playerC/Player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=86 format=2] +[gd_scene load_steps=88 format=2] [ext_resource path="res://src/playerC/Player-KinematicBody2D.gd" type="Script" id=1] [ext_resource path="res://src/playerC/states/idle.gd" type="Script" id=2] @@ -10,6 +10,9 @@ [ext_resource path="res://src/playerC/states/hurt.gd" type="Script" id=8] [ext_resource path="res://src/playerC/Gun.gd" type="Script" id=9] [ext_resource path="res://src/playerC/states/fire.gd" type="Script" id=10] +[ext_resource path="res://src/playerC/states/none.gd" type="Script" id=11] +[ext_resource path="res://src/components/Hurtbox_Component.tscn" type="PackedScene" id=12] +[ext_resource path="res://src/state_modifier.gd" type="Script" id=13] [sub_resource type="StreamTexture" id=75] load_path = "res://.import/MegaMan.png-dc2c293e5a0f5d183ee961942ac90e4a.stex" @@ -399,9 +402,6 @@ animations = [ { [sub_resource type="RectangleShape2D" id=74] extents = Vector2( 14, 18.5 ) -[sub_resource type="RectangleShape2D" id=148] -extents = Vector2( 11.5, 14.5 ) - [node name="Player" type="KinematicBody2D"] collision_layer = 2 script = ExtResource( 1 ) @@ -409,7 +409,7 @@ script = ExtResource( 1 ) [node name="AnimatedSprite" type="AnimatedSprite" parent="."] frames = SubResource( 147 ) animation = "idle" -frame = 26 +frame = 1 playing = true flip_h = true __meta__ = { @@ -431,6 +431,9 @@ shape = SubResource( 74 ) [node name="Controllers" type="Node" parent="."] +[node name="move_component" type="Node" parent="Controllers"] +script = ExtResource( 6 ) + [node name="state_machine" type="Node" parent="Controllers"] script = ExtResource( 3 ) starting_state = NodePath("idle") @@ -439,6 +442,7 @@ starting_state = NodePath("idle") script = ExtResource( 2 ) animation_name = "idle" move_speed = 90.0 +transition_state_nodes = [ NodePath("../jump") ] jump_node = NodePath("../jump") fall_node = NodePath("../fall") move_node = NodePath("../move") @@ -482,17 +486,17 @@ animation_name = "fire" timeout_seconds = 1.0 idle_node = NodePath("../idle") -[node name="move_component" type="Node" parent="Controllers"] -script = ExtResource( 6 ) +[node name="attack" type="Node" parent="Controllers/state_machine"] +script = ExtResource( 13 ) +timeout_seconds = 2.0 -[node name="Hurtbox" type="Area2D" parent="."] -collision_layer = 16 -collision_mask = 128 +[node name="AttackStateMachine" type="Node" parent="Controllers"] +script = ExtResource( 3 ) -[node name="CollisionShape2D" type="CollisionShape2D" parent="Hurtbox"] -self_modulate = Color( 1, 0, 0, 1 ) -position = Vector2( 0, -4 ) -shape = SubResource( 148 ) +[node name="none" type="Node" parent="Controllers/AttackStateMachine"] +script = ExtResource( 11 ) + +[node name="Hurtbox_Component" parent="." instance=ExtResource( 12 )] [node name="Gun" type="Position2D" parent="."] physics_interpolation_mode = 1 @@ -509,4 +513,3 @@ width = 2.0 default_color = Color( 1, 0.607843, 0, 1 ) [connection signal="animation_finished" from="AnimatedSprite" to="." method="_on_AnimatedSprite_animation_finished"] -[connection signal="area_entered" from="Hurtbox" to="." method="_on_Hurtbox_area_entered"] diff --git a/src/playerC/states/fire.gd b/src/playerC/states/fire.gd index 4f24349..ab35306 100644 --- a/src/playerC/states/fire.gd +++ b/src/playerC/states/fire.gd @@ -10,6 +10,9 @@ func enter() -> void: parent.set_hurtbox(false) state_timeout.start() can_fire = true + if modifier_stack_ref.has($"../attack") == false: + $"../attack".state_timeout.start() + modifier_stack_ref.push_front($"../attack") func process_input(_event: InputEvent) -> State: # if move_component.get_movement_direction() != 0.0: @@ -45,3 +48,11 @@ func process_physics(delta: float) -> State: return null +func exit() -> void: + for mods in modifier_stack_ref: + print(mods.name) + +# var testmod :StateModifier = StateModifier.new() +# testmod.name + + pass diff --git a/src/playerC/states/none.gd b/src/playerC/states/none.gd new file mode 100644 index 0000000..42b9832 --- /dev/null +++ b/src/playerC/states/none.gd @@ -0,0 +1 @@ +extends State diff --git a/src/pushdown_state_machine.gd b/src/pushdown_state_machine.gd new file mode 100644 index 0000000..1011f39 --- /dev/null +++ b/src/pushdown_state_machine.gd @@ -0,0 +1,46 @@ +class_name PushdownStateMachine extends Node + +export (NodePath) var starting_state + +var current_state: State +var state_stack = [] + +# Initialize the state machine by giving each child state a reference to the +# parent object it belongs to and enter the default starting_state. +func init(parent: KinematicBody2D, animations: AnimatedSprite, move_component) -> void: + for child in get_children(): + child.parent = parent + child.animations = animations + child.move_component = move_component + + # Initialize to the default state + change_state(get_node(starting_state)) + +# Change to the new state by first calling any exit logic on the current state. +func change_state(new_state: State) -> void: + if current_state: + current_state.exit() + + current_state = new_state + current_state.enter() + +func push_state(new_state: State) -> void: + state_stack.push_front(new_state) + + +# Pass through functions for the Player to call, +# handling state changes as needed. +func process_physics(delta: float) -> void: + var new_state = current_state.process_physics(delta) + if new_state: + change_state(new_state) + +func process_input(event: InputEvent) -> void: + var new_state = current_state.process_input(event) + if new_state: + change_state(new_state) + +func process_frame(delta: float) -> void: + var new_state = current_state.process_frame(delta) + if new_state: + change_state(new_state) diff --git a/src/state.gd b/src/state.gd index 3fd1ce5..028a7b5 100644 --- a/src/state.gd +++ b/src/state.gd @@ -1,13 +1,25 @@ class_name State extends Node +## Animation and movement state class +## +## A state intended to control the movement and animation +## functions of a KinimaticBody2D node. Initialized with a +## player, movement node, and kinematicbody2d +## I think if we also had modifier states that transfered along with the enter and entrance of nodes, that would be helpful. +## +## @WIP + export var animation_name: String export var move_speed: float = 60 export var timeout_seconds: float = 0.0 export(Array, String) var animation_sequence +export(Array, NodePath) var transition_state_nodes + var animation_index: int = 0 var current_animation_sequence: int = 0 +var modifier_stack_ref: Array # Well this didn't work var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") @@ -20,6 +32,7 @@ var parent: KinematicBody2D var state_timeout: Timer func _ready(): + # Only add timout node if timer value was specified. if(timeout_seconds > 0.0): state_timeout = Timer.new() state_timeout.wait_time = timeout_seconds @@ -30,6 +43,7 @@ func _ready(): func enter() -> void: print(parent.name, " entering State: ", self.name) + #modifier_stack_ref = state_modifiers if animation_name != '': animations.play(animation_name) elif animation_sequence.size() > 0: @@ -54,9 +68,3 @@ func process_frame(_delta: float) -> State: func process_physics(_delta: float) -> State: return null - -#func get_movement_input() -> float: -# return move_component.get_movement_direction() -# -#func get_jump() -> bool: -# return move_component.wants_jump() diff --git a/src/state_machine.gd b/src/state_machine.gd index c2c64b2..2628123 100644 --- a/src/state_machine.gd +++ b/src/state_machine.gd @@ -1,16 +1,20 @@ -extends Node +class_name StateMachine extends Node export (NodePath) var starting_state var current_state: State +var state_modifiers: Array # Initialize the state machine by giving each child state a reference to the # parent object it belongs to and enter the default starting_state. func init(parent: KinematicBody2D, animations: AnimatedSprite, move_component) -> void: for child in get_children(): - child.parent = parent - child.animations = animations - child.move_component = move_component + if child is State: + print("Initializing State Node for ", parent.name, ": ", child.name) + child.parent = parent + child.animations = animations + child.move_component = move_component + child.modifier_stack_ref = state_modifiers # Initialize to the default state change_state(get_node(starting_state)) @@ -22,6 +26,10 @@ func change_state(new_state: State) -> void: current_state = new_state current_state.enter() + if(state_modifiers.size() > 0): + print("Active Modifiers:") + for mods in state_modifiers: + print(mods.name) # Pass through functions for the Player to call, # handling state changes as needed. @@ -36,6 +44,14 @@ func process_input(event: InputEvent) -> void: change_state(new_state) func process_frame(delta: float) -> void: + # Check the state modifiers for timeouts, and maybe more conditions later + # If the modifier is no longer valid, pop it from the stack. + # We could iterate through a whole list of states but I'm not sure i want + # states to be that complex. + if state_modifiers.empty() == false: + if state_modifiers[-1].state_timeout.time_left == 0: + print("Pop State Modifier: ", state_modifiers[-1].name) + state_modifiers.pop_back() var new_state = current_state.process_frame(delta) if new_state: change_state(new_state) diff --git a/src/state_modifier.gd b/src/state_modifier.gd new file mode 100644 index 0000000..3b2ea51 --- /dev/null +++ b/src/state_modifier.gd @@ -0,0 +1,37 @@ +class_name StateModifier +extends Node +## State modification +## +## A state modifier doesn't have any direct control of a game object. +## They are transfered from the enter and exit of other states and +## influence the movement or animation of their parent states. +## Rather than going full into paralell state machines I hope that this +## modifier will be suitable for a basic state machine. +## +## @WIP + + +export var animation_name: String +export var move_speed: float = 60 +export var timeout_seconds: float = 0.0 + +export(Array, String) var animation_sequence +export(Array, NodePath) var transition_state_nodes +var animation_index: int = 0 +var current_animation_sequence: int = 0 + +var gravity: int = ProjectSettings.get_setting("physics/2d/default_gravity") + +#var animation_sequence_timer: Timer + +var state_timeout: Timer + +func _ready(): + state_timeout = Timer.new() + state_timeout.wait_time = timeout_seconds + state_timeout.one_shot = true + state_timeout.autostart = false + add_child(state_timeout) + +func _update(): + pass