Jam10/Scripts/Unit.gd

283 lines
9.5 KiB
GDScript

extends Area2D
# base class for units
# we assume every unit can move and jump so we see their handlers here
# sprite management is handled here (and subclasses) as well
class_name Unit
const Constants = preload("res://Scripts/Constants.gd")
const GameUtils = preload("res://Scripts/GameUtils.gd")
var scene
# position
export var unit_type : int
var actions = {}
var unit_conditions = {}
var facing : int = Constants.Direction.RIGHT
var current_action_time_elapsed : float = 0
var unit_condition_timers = {}
var pos : Vector2
var h_speed : float = 0
var v_speed : float = 0
var target_move_speed : float
var last_contacted_ground_collider : Array
var current_sprite : Node2D
var sprite_class_nodes = {} # sprite class to node list dictionary
var hit_queued : bool = false
var hit_dir : int
var time_elapsed : float
var is_flash : bool = false
var flash_start_timestamp : float
# Called when the node enters the scene tree for the first time
func _ready():
for action_num in Constants.UNIT_TYPE_ACTIONS[unit_type]:
actions[action_num] = false
for condition_num in Constants.UNIT_TYPE_CONDITIONS[unit_type].keys():
set_unit_condition(condition_num, Constants.UNIT_TYPE_CONDITIONS[unit_type][condition_num])
for condition_num in Constants.UNIT_CONDITION_TIMERS[unit_type].keys():
unit_condition_timers[condition_num] = 0
target_move_speed = Constants.UNIT_TYPE_MOVE_SPEEDS[unit_type]
# populate sprite_class_nodes
for sprite_class in Constants.UNIT_SPRITES[unit_type]:
sprite_class_nodes[sprite_class] = []
for node_name in Constants.UNIT_SPRITES[unit_type][sprite_class][1]:
sprite_class_nodes[sprite_class].append(get_node(node_name))
pos = Vector2(position.x / Constants.GRID_SIZE, -1 * position.y / Constants.GRID_SIZE)
position.x = position.x * Constants.SCALE_FACTOR
position.y = position.y * Constants.SCALE_FACTOR
scale.x = Constants.SCALE_FACTOR
scale.y = Constants.SCALE_FACTOR
func init_unit_w_scene(scene):
self.scene = scene
func set_action(action : int):
assert(action in Constants.UNIT_TYPE_ACTIONS[unit_type])
actions[action] = true
func set_unit_condition(condition_type : int, condition):
assert(condition_type in Constants.UNIT_TYPE_CONDITIONS[unit_type].keys())
unit_conditions[condition_type] = condition
func set_unit_condition_with_timer(condition_type : int):
assert(condition_type in Constants.UNIT_CONDITION_TIMERS[unit_type].keys())
set_unit_condition(condition_type, Constants.UNIT_CONDITION_TIMERS[unit_type][condition_type][1])
unit_condition_timers[condition_type] = Constants.UNIT_CONDITION_TIMERS[unit_type][condition_type][0]
func get_condition(condition_num : int, default):
if condition_num in Constants.UNIT_TYPE_CONDITIONS[unit_type].keys():
return unit_conditions[condition_num]
else:
return default
func is_current_action_timer_done(current_action : int):
assert(current_action in Constants.CURRENT_ACTION_TIMERS[unit_type].keys())
return current_action_time_elapsed >= Constants.CURRENT_ACTION_TIMERS[unit_type][current_action]
func reset_actions():
for action_num in Constants.UNIT_TYPE_ACTIONS[unit_type]:
actions[action_num] = false
func process_unit(delta, time_elapsed : float):
current_action_time_elapsed += delta
execute_actions(delta)
handle_idle()
advance_timers(delta)
handle_moving_status(delta)
handle_recoil() # must be after handle_moving_status
reset_current_action()
self.time_elapsed = time_elapsed
func advance_timers(delta):
for condition_num in Constants.UNIT_CONDITION_TIMERS[unit_type].keys():
unit_condition_timers[condition_num] = move_toward(unit_condition_timers[condition_num], 0, delta)
if unit_condition_timers[condition_num] == 0:
set_unit_condition(condition_num, Constants.UNIT_CONDITION_TIMERS[unit_type][condition_num][2])
if condition_num == Constants.UnitCondition.IS_INVINCIBLE:
invincibility_ended()
func reset_current_action():
# process CURRENT_ACTION
if get_current_action() == Constants.UnitCurrentAction.JUMPING:
if not actions[Constants.ActionType.JUMP]:
set_current_action(Constants.UnitCurrentAction.IDLE)
# process MOVING_STATUS
if not actions[Constants.ActionType.MOVE]:
set_unit_condition(Constants.UnitCondition.MOVING_STATUS, Constants.UnitMovingStatus.IDLE)
func handle_input(delta):
# implemented in subclass
pass
func get_current_action():
return unit_conditions[Constants.UnitCondition.CURRENT_ACTION]
func set_current_action(current_action : int):
assert(current_action in Constants.UNIT_TYPE_CURRENT_ACTIONS[unit_type])
if get_current_action() != current_action:
current_action_time_elapsed = 0
set_unit_condition(Constants.UnitCondition.CURRENT_ACTION, current_action)
func execute_actions(delta):
for action_num in Constants.UNIT_TYPE_ACTIONS[unit_type]:
if !actions[action_num]:
continue
match action_num:
Constants.ActionType.JUMP:
jump()
Constants.ActionType.MOVE:
move()
func jump():
set_current_action(Constants.UnitCurrentAction.JUMPING)
if (unit_conditions[Constants.UnitCondition.IS_ON_GROUND]):
# hit ground
v_speed = max(Constants.UNIT_TYPE_JUMP_SPEEDS[unit_type], v_speed)
else:
# airborne
v_speed = max(Constants.UNIT_TYPE_JUMP_SPEEDS[unit_type], move_toward(v_speed, Constants.UNIT_TYPE_JUMP_SPEEDS[unit_type], get_process_delta_time() * Constants.GRAVITY))
set_unit_condition(Constants.UnitCondition.IS_ON_GROUND, false)
if get_current_action() == Constants.UnitCurrentAction.JUMPING and v_speed > 0:
set_sprite(Constants.SpriteClass.JUMP, 0)
if is_current_action_timer_done(Constants.UnitCurrentAction.JUMPING):
set_current_action(Constants.UnitCurrentAction.IDLE)
func move():
set_unit_condition(Constants.UnitCondition.MOVING_STATUS, Constants.UnitMovingStatus.MOVING)
if (get_current_action() == Constants.UnitCurrentAction.IDLE
and unit_conditions[Constants.UnitCondition.IS_ON_GROUND]):
set_sprite(Constants.SpriteClass.WALK)
func handle_recoil():
# implemented in subclass
pass
func handle_moving_status(delta):
# what we have: facing, current speed, move status, grounded
# we want: to set the new intended speed
var magnitude : float
if unit_conditions[Constants.UnitCondition.IS_ON_GROUND]:
magnitude = sqrt(pow(v_speed, 2) + pow(h_speed, 2))
else:
magnitude = abs(h_speed)
# if move status is idle
if unit_conditions[Constants.UnitCondition.MOVING_STATUS] == Constants.UnitMovingStatus.IDLE:
# slow down
magnitude = move_toward(magnitude, 0, Constants.ACCELERATION * delta)
# if move status is not idle
else:
# if is facing-aligned
if (h_speed <= 0 and facing == Constants.Direction.LEFT) or (h_speed >= 0 and facing == Constants.Direction.RIGHT):
# speed up
magnitude = move_toward(magnitude, target_move_speed, Constants.ACCELERATION * delta)
# if is not facing-aligned
else:
# slow down
magnitude = move_toward(magnitude, 0, Constants.ACCELERATION * delta)
# if is grounded
if unit_conditions[Constants.UnitCondition.IS_ON_GROUND]:
# make magnitude greater than quantum distance
if magnitude > 0 and magnitude < Constants.QUANTUM_DIST:
magnitude = Constants.QUANTUM_DIST * 2
# make move vector point down
if magnitude > 0:
if h_speed > 0:
h_speed = Constants.QUANTUM_DIST # preserve h direction
elif h_speed < 0:
h_speed = -1 * Constants.QUANTUM_DIST
else:
# from still to moving
if facing == Constants.Direction.RIGHT:
h_speed = Constants.QUANTUM_DIST
else:
h_speed = -1 * Constants.QUANTUM_DIST
else:
h_speed = 0
v_speed = -1 * magnitude
# if is not grounded
else:
# set h_speed
if magnitude > 0:
if h_speed > 0:
h_speed = magnitude
elif h_speed < 0:
h_speed = -1 * magnitude
else:
# from no lateral movement to having lateral movement
if facing == Constants.Direction.RIGHT:
h_speed = magnitude
else:
h_speed = -1 * magnitude
else:
h_speed = 0
func handle_idle():
if get_current_action() == Constants.UnitCurrentAction.IDLE:
if unit_conditions[Constants.UnitCondition.IS_ON_GROUND]:
if unit_conditions[Constants.UnitCondition.MOVING_STATUS] == Constants.UnitMovingStatus.IDLE:
set_sprite(Constants.SpriteClass.IDLE)
elif v_speed > 0:
set_sprite(Constants.SpriteClass.JUMP, 0)
else:
set_sprite(Constants.SpriteClass.JUMP, 1)
func set_sprite(sprite_class : int, index : int = 0):
assert(unit_type in Constants.UNIT_SPRITES)
assert(sprite_class in Constants.UNIT_SPRITES[unit_type])
var node_list = sprite_class_nodes[sprite_class]
var true_index : int = index
if true_index > len(node_list) - 1:
true_index = 0
var new_sprite : Node2D = node_list[true_index]
if (is_flash):
if int((time_elapsed - flash_start_timestamp) / Constants.FLASH_CYCLE) % 2 == 1:
new_sprite.set_modulate(Color(2, 1, 1))
else:
new_sprite.set_modulate(Color(1, .5, .5))
else:
new_sprite.set_modulate(Color(1, 1, 1))
if current_sprite == null or current_sprite != new_sprite:
if current_sprite != null:
current_sprite.visible = false
current_sprite = new_sprite
current_sprite.visible = true
if (Constants.UNIT_SPRITES[unit_type][sprite_class][0]):
current_sprite.set_frame(0)
current_sprite.play()
if facing == Constants.Direction.LEFT:
current_sprite.scale.x = -1
else:
current_sprite.scale.x = 1
func react(delta):
pos.x = pos.x + h_speed * delta
pos.y = pos.y + v_speed * delta
position.x = pos.x * Constants.GRID_SIZE * Constants.SCALE_FACTOR
position.y = -1 * pos.y * Constants.GRID_SIZE * Constants.SCALE_FACTOR
func hit(dir : int):
# implemented in subclass
hit_queued = true
hit_dir = dir
func start_flash():
is_flash = true
flash_start_timestamp = time_elapsed
func invincibility_ended():
# implemented in subclass
pass