Last active
January 26, 2022 15:16
-
-
Save N-Carter/fcabdaa51c5b62ece8658e369ccbb2e8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends KinematicBody2D | |
export(float) var speed := 8.0 | |
export(float) var jump_impulse := 200.0 | |
export(float) var max_speed := 200.0 | |
export(float) var gravity_multiplier := 1.0 | |
export(float) var snap_length := 5.0 | |
export(float) var floor_damping := 200.0 | |
export var _max_stamina := 100.0 | |
onready var _stamina := _max_stamina | |
export var _stamina_regeneration_rate := 60.0 | |
export var _stamina_flap_consumption := 15.0 | |
onready var _gravity : float = ProjectSettings.get_setting("physics/2d/default_gravity") | |
onready var _gravity_vector : Vector2 = ProjectSettings.get_setting("physics/2d/default_gravity_vector") | |
var _velocity : Vector2 | |
var _is_jumping : bool | |
var _time_in_air : float | |
var _is_powered_flight : bool | |
var _is_busy : bool | |
export var sprite : NodePath | |
onready var _sprite_node := get_node(sprite) as AnimatedSprite | |
export var sensor : NodePath | |
onready var _sensor_node := get_node(sensor) as CollisionShape2D | |
export var audio : NodePath | |
onready var _audio_node := get_node(audio) as AudioStreamPlayer2D | |
export(Array, AudioStream) var flap_sounds | |
export var pickup_sound : AudioStream | |
export var drop_sound : AudioStream | |
onready var _physics_query := Physics2DShapeQueryParameters.new() | |
var _escape_route_waypoints : Array | |
var _current_waypoint := 0 | |
var _is_fleeing := false | |
# The contents of these arrays must match the Globals.EffectType enum: | |
export(Array, NodePath) var effect_statuses : Array | |
var _effect_status_nodes := [] | |
var _use_state_machine := true | |
func _ready(): | |
assert(not Game.player) | |
Game.player = self | |
_sprite_node.play() | |
if _use_state_machine: | |
_sprite_node.connect("frame_changed", self, "_frame_changed_state_machine") | |
_sprite_node.connect("animation_finished", self, "_animation_finished_state_machine") | |
_set_state("_idle_state") | |
Game.dialogue_box.connect("completed", self, "_dialogue_completed") | |
else: | |
_sprite_node.connect("frame_changed", self, "_frame_changed") | |
_sprite_node.connect("animation_finished", self, "_animation_finished") | |
_physics_query.collide_with_areas = true | |
_physics_query.collide_with_bodies = true | |
_physics_query.collision_layer = 0b1010 | |
_physics_query.set_shape(_sensor_node.shape) | |
Game.overlay.set_max_stamina(_max_stamina) | |
Game.overlay.set_stamina(_stamina) | |
for effect_status in effect_statuses: | |
_effect_status_nodes.append(get_node(effect_status)) | |
_effect_status_nodes[Globals.EffectType.THREAT].connect("on_threshold_reached", self, "_on_threat_triggered") | |
_effect_status_nodes[Globals.EffectType.THREAT].connect("on_effect_ended", self, "_on_threat_ended") | |
var frames = _sprite_node.frames | |
frames.set_animation_loop("land", false) | |
frames.set_animation_loop("pickup", false) | |
frames.set_animation_loop("jump", false) | |
frames.set_animation_loop("operate", false) | |
frames.set_animation_speed("listen", 4.0) | |
func _process(_delta): | |
Game.overlay.set_stamina(_stamina) | |
var effect_status := _effect_status_nodes[Globals.EffectType.THREAT] as EffectStatus | |
Game.overlay.set_max_threat(effect_status.get_capacity()) | |
Game.overlay.set_threat(effect_status.get_current()) | |
if Game.overlay.is_debug_visible: | |
Game.overlay.set_stats_text("V: %+6.2f, %+6.2f" % [_velocity.x, _velocity.y]) | |
func _physics_process(delta): | |
if _use_state_machine: | |
_physics_process_state_machine(delta) | |
else: | |
if _is_fleeing: | |
_flee() | |
elif not _is_busy: | |
_player_control(delta) | |
# Original non-state machine behaviour: | |
func _player_control(delta : float) -> void: | |
# var joystick := Vector2(Input.get_joy_axis(0, JOY_AXIS_0), Input.get_joy_axis(0, JOY_AXIS_1)) | |
var joystick := Vector2() # Now uses the input settings instead, for better or worse. | |
var force := Vector2.ZERO | |
var floored := is_on_floor() | |
if floored and _time_in_air > 0.2: | |
print("Landing") | |
_sprite_node.animation = "land" | |
_is_busy = true | |
_time_in_air = 0.0 | |
return | |
if _velocity.y > 0.0: | |
_is_jumping = false | |
if Input.is_action_pressed("move_left") or joystick.x < -0.5: | |
force.x -= 1.0 | |
_sprite_node.flip_h = true | |
if Input.is_action_pressed("move_right") or joystick.x > 0.5: | |
force.x += 1.0 | |
_sprite_node.flip_h = false | |
if Input.is_action_pressed("move_up") or joystick.y < -0.5: | |
_is_powered_flight = true | |
if floored: | |
_jump() | |
else: | |
if _stamina > 0.0: | |
force.y -= 2.0 | |
else: | |
_is_powered_flight = false | |
_velocity += force * speed | |
if floored: | |
_sprite_node.animation = "walk" if abs(_velocity.x) > 0.1 else "idle" | |
_stamina += 30.0 * delta | |
_time_in_air = 0.0 | |
else: | |
_sprite_node.animation = "fly" if _velocity.y < 0.0 else "dive" | |
_stamina += 10.0 * delta | |
_time_in_air += delta | |
_stamina = min(_stamina, _max_stamina) | |
_velocity += _gravity_vector * (_gravity * gravity_multiplier) | |
var speed_limit = max_speed * (1.0 if floored else 2.0) | |
_velocity.x = clamp(_velocity.x, -speed_limit, speed_limit) | |
_velocity.y = clamp(_velocity.y, -speed_limit, speed_limit) | |
# move_and_slide multiplies by delta internally, so don't do it here! | |
var snap = 0.0 if _is_jumping else snap_length | |
var old_x = _velocity.x | |
# _velocity = move_and_slide(_velocity, Vector2.UP, true, 4, 1.0) | |
_velocity = move_and_slide_with_snap(_velocity, Vector2.DOWN * snap, Vector2.UP, true, 4, 1.0) | |
_velocity.x = lerp(old_x, _velocity.x, 0.5) | |
# var num_slides := get_slide_count() | |
# if(num_slides > 0): | |
# for i in num_slides: | |
# var slide := get_slide_collision(i) | |
## _velocity.x *= slide.normal.x # This is not correct, but something in here might help! | |
# _velocity.x -= slide.travel.x * 20.0 | |
# if floored: | |
_velocity.x = move_toward(_velocity.x, 0.0, floor_damping * delta) | |
# print("%6.2f, %6.2f" % [_velocity.x, _velocity.y]) | |
if Input.is_action_just_pressed("interact"): | |
_action() | |
if Input.is_action_just_pressed("held_item_use"): | |
_throw(1.0) | |
if Input.is_action_just_pressed("held_item_drop"): | |
_drop() | |
# FIXME: Unfinished! | |
# if Input.is_key_pressed(KEY_F): | |
# _escape_route_waypoints = $EscapeRoute.get_escape_route() | |
func _flee() -> void: | |
if _current_waypoint >= _escape_route_waypoints.size(): | |
print("Fled to end of path") | |
return | |
var direction := _escape_route_waypoints[_current_waypoint] as Vector2 - global_position | |
var distance := direction.length() | |
if distance > 16.0: | |
_velocity = direction.normalized() * max_speed * 4.0 # Hardcoded multiplier! | |
move_and_slide(_velocity, Vector2.UP, true, 4, 1.0) | |
# print("Fleeing direction %s, to point %s, distance %f" % [direction, _escape_route_waypoints[_current_waypoint], distance]) | |
else: | |
_current_waypoint += 1 | |
print("Next waypoint ", _current_waypoint) | |
func _jump() -> void: | |
# print("Jump frame: %d" % get_tree().get_frame()) | |
_velocity.y -= jump_impulse | |
_is_jumping = true | |
# sprite_node.animation = "fly" | |
# The action function is still used by the state machine: | |
func _action() -> void: | |
var state := get_world_2d().direct_space_state | |
_physics_query.transform = transform * _sensor_node.transform | |
print("Detecting:") | |
var results := state.intersect_shape(_physics_query) | |
for result in results: | |
var item = result["collider"] | |
print("Found %s" % item.name) | |
if item is Pickup: | |
if _use_state_machine: | |
_set_state("_pickup_state") | |
else: | |
_sprite_node.animation = "pickup" | |
_is_busy = true | |
_audio_node.stream = pickup_sound | |
_audio_node.play() | |
_face_point(item.global_position) | |
item.pick_up() | |
Game.overlay.add_to_inventory(item) | |
return | |
if item.has_method("action"): | |
print("Calling action() on " + item.name + " (owner: " + item.owner.name + ")") | |
_face_point(item.global_position) | |
if _use_state_machine: | |
_set_state("_operate_state") | |
item.action() | |
return | |
elif item.has_method("talk"): | |
print("Calling talk() on " + item.name + " (owner: " + item.owner.name + ")") | |
_face_point(item.global_position) | |
if _use_state_machine: | |
_set_state("_listen_state") | |
item.talk() | |
return | |
break | |
func _throw(force : float) -> void: | |
var pickup := Game.overlay.get_held_items().release_selected() as Pickup | |
if not pickup: | |
return | |
print("Throwing " + pickup.name) | |
pickup.drop(self) | |
pickup.apply_central_impulse(Vector2(-force if _sprite_node.flip_h else force, -0.2) * 200.0) | |
_audio_node.stream = drop_sound | |
_audio_node.play() | |
func _drop() -> void: | |
var pickup := Game.overlay.get_held_items().release_selected() as Pickup | |
if not pickup: | |
return | |
print("Dropping " + pickup.name) | |
pickup.drop(self) | |
_audio_node.stream = drop_sound | |
_audio_node.play() | |
func _frame_changed(): | |
if(_sprite_node.animation == "fly" and _sprite_node.frame == 1): | |
_audio_node.stream = flap_sounds[randi() % flap_sounds.size()] | |
_audio_node.play() | |
if _is_powered_flight and _stamina > -15.0: | |
_stamina -= 15.0 | |
func _animation_finished(): | |
match _sprite_node.animation: | |
"pickup": | |
_is_busy = false | |
_velocity.x = 0.0 | |
"land": | |
_is_busy = false | |
_sprite_node.animation = "default" | |
_velocity.x = 0.0 | |
_: | |
return | |
print("Animation finished: ", _sprite_node.animation) | |
func add_effect(type : int, amount : float) -> void: | |
var effect_status := _effect_status_nodes[type] as EffectStatus | |
effect_status.add(amount) | |
func _on_threat_triggered() -> void: | |
Game.overlay.set_threat_draining(true) | |
_escape_route_waypoints = $EscapeRoute.get_escape_route() | |
_current_waypoint = 0 | |
_is_fleeing = true | |
func _on_threat_ended() -> void: | |
Game.overlay.set_threat_draining(false) | |
_is_fleeing = false | |
# State machine stuff in progress: | |
var _current_state := "" | |
var _is_new_state := 0 # If greater than 0, this is still a new state | |
var _next_state_on_animation_end := "" | |
var _has_animation_frame_changed : bool | |
var _has_animation_ended : bool | |
var _delta : float | |
var _state_time : float | |
var _joystick : Vector2 | |
var _is_floored : bool | |
var _last_state_velocity_y : float | |
func _physics_process_state_machine(delta): | |
_delta = delta | |
_joystick = Vector2.ZERO | |
_is_floored = is_on_floor() | |
if Input.is_action_pressed("move_left"): | |
_joystick.x -= 1.0 | |
if Input.is_action_pressed("move_right"): | |
_joystick.x += 1.0 | |
if Input.is_action_pressed("move_up"): | |
_joystick.y -= 1.0 | |
if Input.is_action_pressed("move_down"): | |
_joystick.y += 1.0 | |
_call_state() | |
_state_time += _delta | |
_has_animation_ended = false | |
_has_animation_frame_changed = false | |
func _set_state(state_name : String) -> void: | |
if _current_state != state_name: | |
_current_state = state_name | |
_is_new_state = 2 | |
_state_time = 0.0 | |
print("New state: ", state_name) | |
else: | |
print("Tried to switch to this state while already in it: ", state_name) | |
func _call_state() -> void: | |
call(_current_state) | |
if _is_new_state > 0: | |
_is_new_state -= 1 | |
# State machine states: | |
func _idle_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "idle" | |
_regenerate_stamina(1.0) | |
_stand_motion() | |
_ground_interactions() | |
if not _is_floored: | |
_set_state("_dive_state") | |
return | |
if abs(_joystick.x) > 0.01: | |
_set_state("_walk_state") | |
return | |
if _joystick.y < -0.01: | |
_set_state("_jump_state") | |
return | |
func _walk_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "walk" | |
_regenerate_stamina(1.0) | |
_correct_flip() | |
_walk_motion() | |
_ground_interactions() | |
if not _is_floored: | |
_set_state("_dive_state") | |
return | |
if abs(_joystick.x) <= 0.01: | |
_set_state("_idle_state") | |
return | |
if _joystick.y < -0.01: | |
_set_state("_jump_state") | |
return | |
func _jump_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "jump" | |
_next_state_on_animation_end = "_fly_state" | |
_velocity.y -= jump_impulse | |
_consume_stamina(_stamina_flap_consumption) | |
print("Jump impulse applied") | |
# _set_state("_fly_state") | |
func _fly_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "fly" | |
# Scale joystick for up direction only: | |
if _joystick.y < 0.0: | |
_sprite_node.speed_scale = (1.0 - _joystick.y * 0.5) | |
else: | |
_sprite_node.speed_scale = 1.0 | |
# Downwards acceleration when out of stamina, crude and doesn't feel right: | |
if _stamina <= 0.0: | |
_velocity.y += 400.0 * _delta | |
_regenerate_stamina(0.33) | |
_correct_flip() | |
_fly_motion() | |
if(_has_animation_frame_changed and _sprite_node.frame == 1): | |
_audio_node.stream = flap_sounds[randi() % flap_sounds.size()] | |
_audio_node.play() | |
# _velocity.y -= jump_impulse * 0.5 # Add some vertical speed on each flap | |
if _joystick.y < 0.0 and _stamina > -_stamina_flap_consumption: | |
_consume_stamina(_stamina_flap_consumption) | |
# if _stamina <= 0.0: | |
# _set_state("_dive_state") | |
# return | |
if _joystick.y > 0.0 and _velocity.y > 0.0: | |
_set_state("_dive_state") | |
return | |
if _is_floored: | |
# _set_state("_touchdown_state") | |
_set_state("_walk_state") | |
return | |
func _dive_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "dive" | |
_regenerate_stamina(0.5) | |
_correct_flip() | |
_fly_motion() | |
if _joystick.y <= 0.0: | |
_set_state("_fly_state") | |
return | |
if _is_floored: | |
_set_state("_land_state") | |
return | |
func _land_state() -> void: | |
# Note that this is for heavy landings, and shouldn't really be used normally as it makes the character less nimble. | |
if _is_new_state: | |
_sprite_node.animation = "land" | |
_next_state_on_animation_end = "_idle_state" | |
_regenerate_stamina(1.0) | |
_stand_motion() | |
#func _touchdown_state() -> void: | |
# if _is_new_state: | |
# _sprite_node.animation = "jump" | |
# _next_state_on_animation_end = "_idle_state" | |
# | |
# _stand_motion() | |
func _pickup_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "pickup" | |
_next_state_on_animation_end = "_idle_state" | |
_regenerate_stamina(1.0) | |
_stand_motion() | |
func _operate_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "operate" | |
_next_state_on_animation_end = "_idle_state" | |
_regenerate_stamina(1.0) | |
_stand_motion() | |
func _listen_state() -> void: | |
if _is_new_state: | |
_sprite_node.animation = "listen" | |
_regenerate_stamina(1.0) | |
_stand_motion() | |
func _panic_state() -> void: | |
pass | |
# State machine utility functions: | |
func _correct_flip() -> void: | |
if _joystick.x < -0.01: | |
_sprite_node.flip_h = true | |
elif _joystick.x > 0.01: | |
_sprite_node.flip_h = false | |
func _face_point(global_point : Vector2) -> void: | |
_sprite_node.flip_h = global_position.x > global_point.x | |
func _stand_motion() -> void: | |
_velocity += _gravity_vector * (_gravity * gravity_multiplier) | |
_velocity.x = 0.0 | |
# Alternative with heavy damping. If you make it too light, it slides constantly. | |
# _velocity.x = move_toward(_velocity.x, 0.0, max_speed * 20.0 * _delta) | |
_velocity = move_and_slide_with_snap(_velocity, Vector2.DOWN * snap_length, Vector2.UP, true, 4, 1.0) | |
func _walk_motion() -> void: | |
_velocity += _joystick * speed | |
_velocity += _gravity_vector * (_gravity * gravity_multiplier) | |
_velocity.x = clamp(_velocity.x, -max_speed, max_speed) | |
# _velocity.y = clamp(_velocity.y, -max_speed, max_speed) | |
# move_and_slide multiplies by delta internally, so don't do it here! | |
_velocity = move_and_slide_with_snap(_velocity, Vector2.DOWN * snap_length, Vector2.UP, true, 4, 1.0) | |
func _fly_motion() -> void: | |
var speed_scaled := speed * 0.5 | |
_velocity.x += _joystick.x * speed_scaled | |
_velocity.y += _joystick.y * (1.0 if _joystick.y > 0.0 else 2.0) * speed_scaled | |
_velocity += _gravity_vector * (_gravity * gravity_multiplier) # Gravity | |
_velocity.x += (-1.0 if _sprite_node.flip_h else 1.0) * speed_scaled * 0.2 # Drift in direction of travel | |
_velocity = move_and_slide_with_snap(_velocity, Vector2.ZERO, Vector2.UP, true, 4, 1.0) | |
_velocity *= pow(0.9, _delta * Engine.iterations_per_second) | |
func _ground_interactions() -> void: | |
if Input.is_action_just_pressed("interact"): | |
_action() | |
if Input.is_action_just_pressed("held_item_use"): | |
_throw(1.0) | |
if Input.is_action_just_pressed("held_item_drop"): | |
_drop() | |
func _consume_stamina(amount : float) -> void: | |
_stamina -= amount | |
func _regenerate_stamina(factor : float) -> void: | |
_stamina += _stamina_regeneration_rate * factor * _delta | |
_stamina = min(_stamina, _max_stamina) | |
func _frame_changed_state_machine(): | |
_has_animation_frame_changed = true | |
func _animation_finished_state_machine(): | |
# print("Animation finished (state machine): ", _current_state) | |
_has_animation_ended = true | |
if _next_state_on_animation_end != "": | |
_set_state(_next_state_on_animation_end) | |
_next_state_on_animation_end = "" | |
func _dialogue_completed(): | |
_set_state("_idle_state") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment