Initial Commit
This commit is contained in:
parent
2dce133760
commit
dbb0f17743
63 changed files with 2881 additions and 1 deletions
197
Scripts/Constants.gd
Normal file
197
Scripts/Constants.gd
Normal file
|
@ -0,0 +1,197 @@
|
|||
enum UnitType {
|
||||
PLAYER,
|
||||
NPC,
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
JUMP,
|
||||
MOVE,
|
||||
RECOIL,
|
||||
}
|
||||
|
||||
enum UnitCondition {
|
||||
CURRENT_ACTION,
|
||||
IS_ON_GROUND,
|
||||
MOVING_STATUS,
|
||||
IS_INVINCIBLE,
|
||||
}
|
||||
|
||||
enum UnitCurrentAction {
|
||||
IDLE,
|
||||
JUMPING,
|
||||
RECOILING,
|
||||
}
|
||||
|
||||
enum UnitMovingStatus {
|
||||
IDLE,
|
||||
MOVING,
|
||||
}
|
||||
|
||||
enum PlayerInput {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
GBA_A,
|
||||
GBA_B,
|
||||
GBA_START,
|
||||
GBA_SELECT,
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
}
|
||||
|
||||
enum MapElemType {
|
||||
SQUARE,
|
||||
SLOPE_LEFT,
|
||||
SLOPE_RIGHT,
|
||||
SMALL_SLOPE_LEFT_1,
|
||||
SMALL_SLOPE_LEFT_2,
|
||||
SMALL_SLOPE_RIGHT_1,
|
||||
SMALL_SLOPE_RIGHT_2,
|
||||
LEDGE,
|
||||
}
|
||||
|
||||
enum SpriteClass {
|
||||
IDLE,
|
||||
WALK,
|
||||
JUMP,
|
||||
RECOIL,
|
||||
}
|
||||
|
||||
const UNIT_TYPE_ACTIONS = {
|
||||
UnitType.PLAYER: [
|
||||
ActionType.JUMP,
|
||||
ActionType.MOVE,
|
||||
ActionType.RECOIL,
|
||||
],
|
||||
UnitType.NPC: [
|
||||
ActionType.MOVE,
|
||||
],
|
||||
}
|
||||
|
||||
const UNIT_TYPE_CURRENT_ACTIONS = {
|
||||
UnitType.PLAYER: [
|
||||
UnitCurrentAction.IDLE,
|
||||
UnitCurrentAction.JUMPING,
|
||||
UnitCurrentAction.RECOILING,
|
||||
],
|
||||
UnitType.NPC: [
|
||||
UnitCurrentAction.IDLE,
|
||||
],
|
||||
}
|
||||
|
||||
# default conditions
|
||||
const UNIT_TYPE_CONDITIONS = {
|
||||
UnitType.PLAYER: {
|
||||
UnitCondition.CURRENT_ACTION: UnitCurrentAction.IDLE,
|
||||
UnitCondition.IS_ON_GROUND: false,
|
||||
UnitCondition.MOVING_STATUS: UnitMovingStatus.IDLE,
|
||||
UnitCondition.IS_INVINCIBLE: false,
|
||||
},
|
||||
UnitType.NPC: {
|
||||
UnitCondition.CURRENT_ACTION: UnitCurrentAction.IDLE,
|
||||
UnitCondition.IS_ON_GROUND: false,
|
||||
UnitCondition.MOVING_STATUS: UnitMovingStatus.IDLE,
|
||||
},
|
||||
}
|
||||
|
||||
# in seconds
|
||||
const CURRENT_ACTION_TIMERS = {
|
||||
UnitType.PLAYER: {
|
||||
UnitCurrentAction.JUMPING: 0.4,
|
||||
UnitCurrentAction.RECOILING: 0.67,
|
||||
},
|
||||
}
|
||||
|
||||
const UNIT_CONDITION_TIMERS = {
|
||||
# condition type: [duration, on value, off value]
|
||||
UnitType.PLAYER: {
|
||||
UnitCondition.IS_INVINCIBLE: [2.5, true, false],
|
||||
},
|
||||
UnitType.NPC: {},
|
||||
}
|
||||
|
||||
# Position relative to player's origin, list of directions to check for collision
|
||||
const ENV_COLLIDERS = {
|
||||
UnitType.PLAYER: [
|
||||
[Vector2(0, 1.5), [Direction.LEFT, Direction.UP, Direction.RIGHT]],
|
||||
[Vector2(-.25, .25), [Direction.LEFT]],
|
||||
[Vector2(.25, .25), [Direction.RIGHT]],
|
||||
[Vector2(-.25, 1.25), [Direction.LEFT]],
|
||||
[Vector2(.25, 1.25), [Direction.RIGHT]],
|
||||
# contact with ground is at (0, 0)
|
||||
[Vector2(0, 0), [Direction.LEFT, Direction.DOWN, Direction.RIGHT]],
|
||||
],
|
||||
UnitType.NPC: [
|
||||
[Vector2(0, 1.5), [Direction.LEFT, Direction.UP, Direction.RIGHT]],
|
||||
[Vector2(-.25, .25), [Direction.LEFT]],
|
||||
[Vector2(.25, .25), [Direction.RIGHT]],
|
||||
[Vector2(-.25, 1.25), [Direction.LEFT]],
|
||||
[Vector2(.25, 1.25), [Direction.RIGHT]],
|
||||
[Vector2(0, 0), [Direction.LEFT, Direction.DOWN, Direction.RIGHT]],
|
||||
],
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
PlayerInput.UP: "ui_up",
|
||||
PlayerInput.DOWN: "ui_down",
|
||||
PlayerInput.LEFT: "ui_left",
|
||||
PlayerInput.RIGHT: "ui_right",
|
||||
PlayerInput.GBA_A: "gba_a",
|
||||
PlayerInput.GBA_B: "gba_b",
|
||||
PlayerInput.GBA_START: "gba_start",
|
||||
PlayerInput.GBA_SELECT: "gba_select",
|
||||
}
|
||||
|
||||
const TILE_SET_MAP_ELEMS = {
|
||||
"TestTileSet": {
|
||||
MapElemType.SQUARE: [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
MapElemType.SLOPE_LEFT: [15, 16],
|
||||
MapElemType.SLOPE_RIGHT: [17, 18],
|
||||
MapElemType.SMALL_SLOPE_LEFT_1: [9],
|
||||
MapElemType.SMALL_SLOPE_LEFT_2: [10, 11],
|
||||
MapElemType.SMALL_SLOPE_RIGHT_1: [12],
|
||||
MapElemType.SMALL_SLOPE_RIGHT_2: [13, 14],
|
||||
MapElemType.LEDGE: [19, 20, 21, 22],
|
||||
},
|
||||
}
|
||||
|
||||
const UNIT_SPRITES = {
|
||||
# Sprite-class: [Is-animation?, Nodes]
|
||||
UnitType.PLAYER: {
|
||||
SpriteClass.IDLE: [false, ["Idle"]],
|
||||
SpriteClass.WALK: [true, ["Walk"]],
|
||||
SpriteClass.JUMP: [false, ["Jump1", "Jump2"]],
|
||||
SpriteClass.RECOIL: [false, ["Recoil"]],
|
||||
},
|
||||
UnitType.NPC: {
|
||||
SpriteClass.IDLE: [false, ["Idle"]],
|
||||
SpriteClass.WALK: [true, ["Walk"]],
|
||||
SpriteClass.JUMP: [false, ["Jump2"]],
|
||||
},
|
||||
}
|
||||
|
||||
const UNIT_TYPE_MOVE_SPEEDS = {
|
||||
UnitType.PLAYER: 6,
|
||||
UnitType.NPC: 3,
|
||||
}
|
||||
|
||||
const UNIT_TYPE_JUMP_SPEEDS = {
|
||||
UnitType.PLAYER: 5,
|
||||
}
|
||||
|
||||
const SCALE_FACTOR = 3.5
|
||||
const GRID_SIZE = 20 # pixels
|
||||
const GRAVITY = 30
|
||||
const MAX_FALL_SPEED = -12
|
||||
const ACCELERATION = 35
|
||||
const QUANTUM_DIST = 0.001
|
||||
const SPAWN_DISTANCE = 10
|
||||
|
||||
# specialized constants
|
||||
const FLASH_CYCLE = 0.15
|
153
Scripts/GameScene.gd
Normal file
153
Scripts/GameScene.gd
Normal file
|
@ -0,0 +1,153 @@
|
|||
extends Node
|
||||
|
||||
# _process(delta) is called by this class
|
||||
# player input is handled here
|
||||
# unit declares its intention in process_unit()
|
||||
# stage environment interacts with the unit in interact()
|
||||
# unit executes its resulting state in react()
|
||||
# stage environment interacts with the unit once more in interact_post()
|
||||
class_name GameScene
|
||||
|
||||
export var tile_set_name: String
|
||||
const Constants = preload("res://Scripts/Constants.gd")
|
||||
const Unit = preload("res://Scripts/Unit.gd")
|
||||
const UNIT_DIRECTORY = {
|
||||
Constants.UnitType.NPC: preload("res://Units/NPC.tscn"),
|
||||
}
|
||||
|
||||
# positions to unit string
|
||||
export var spawning : Dictionary
|
||||
var spawning_map = {} # keeps track of what's alive
|
||||
|
||||
var paused : bool = false
|
||||
|
||||
var units = []
|
||||
var player : Player
|
||||
var player_cam : Camera2D
|
||||
|
||||
# [pressed?, just pressed?, just released?]
|
||||
var input_table = {
|
||||
Constants.PlayerInput.UP: [false, false, false],
|
||||
Constants.PlayerInput.DOWN: [false, false, false],
|
||||
Constants.PlayerInput.LEFT: [false, false, false],
|
||||
Constants.PlayerInput.RIGHT: [false, false, false],
|
||||
Constants.PlayerInput.GBA_A: [false, false, false],
|
||||
Constants.PlayerInput.GBA_B: [false, false, false],
|
||||
Constants.PlayerInput.GBA_SELECT: [false, false, false],
|
||||
}
|
||||
const I_T_PRESSED : int = 0
|
||||
const I_T_JUST_PRESSED : int = 1
|
||||
const I_T_JUST_RELEASED : int = 2
|
||||
|
||||
var stage_env
|
||||
|
||||
var time_elapsed : float = 0
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
units.append(get_node("Player"))
|
||||
player = units[0]
|
||||
player.init_unit_w_scene(self)
|
||||
player_cam = player.get_node("Camera2D")
|
||||
player_cam.make_current()
|
||||
|
||||
stage_env = load("res://Scripts/StageEnvironment.gd").new(self)
|
||||
player.get_node("Camera2D").make_current()
|
||||
for spawning_key in spawning:
|
||||
spawning_map[spawning_key] = null
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
# visual effects
|
||||
if (player.facing == Constants.Direction.RIGHT):
|
||||
player_cam.offset_h = 1
|
||||
else:
|
||||
player_cam.offset_h = -1
|
||||
|
||||
read_paused()
|
||||
if not paused:
|
||||
# game logic
|
||||
process_spawning()
|
||||
for unit in units:
|
||||
unit.reset_actions()
|
||||
unit.handle_input(delta)
|
||||
unit.process_unit(delta, time_elapsed)
|
||||
stage_env.interact(unit, delta)
|
||||
unit.react(delta)
|
||||
time_elapsed += delta
|
||||
|
||||
func read_paused():
|
||||
if Input.is_action_just_pressed(Constants.INPUT_MAP[Constants.PlayerInput.GBA_START]):
|
||||
paused = !paused
|
||||
|
||||
func process_spawning():
|
||||
for one_spawn in spawning.keys():
|
||||
if spawning_map[one_spawn] != null:
|
||||
continue
|
||||
if abs(one_spawn[0] - player.pos.x) >= Constants.SPAWN_DISTANCE + 1 or abs(one_spawn[1] - player.pos.y) >= Constants.SPAWN_DISTANCE + 1:
|
||||
continue
|
||||
if abs(one_spawn[0] - player.pos.x) <= Constants.SPAWN_DISTANCE:
|
||||
continue
|
||||
# NPCUnit
|
||||
var npc_scene = UNIT_DIRECTORY[Constants.UnitType.get(spawning[one_spawn])]
|
||||
var npc_instance = npc_scene.instance()
|
||||
add_child(npc_instance)
|
||||
units.append(npc_instance)
|
||||
npc_instance.spawn_point = one_spawn
|
||||
spawning_map[one_spawn] = npc_instance
|
||||
npc_instance.pos.x = one_spawn[0]
|
||||
npc_instance.pos.y = one_spawn[1]
|
||||
npc_instance.position.x = npc_instance.pos.x * Constants.GRID_SIZE
|
||||
npc_instance.position.y = -1 * npc_instance.pos.y * Constants.GRID_SIZE
|
||||
npc_instance.init_unit_w_scene(self)
|
||||
|
||||
func handle_player_input():
|
||||
# early exit
|
||||
|
||||
if player.get_current_action() == Constants.UnitCurrentAction.RECOILING:
|
||||
player.set_action(Constants.ActionType.RECOIL)
|
||||
return
|
||||
|
||||
for input_num in input_table.keys():
|
||||
if Input.is_action_pressed(Constants.INPUT_MAP[input_num]):
|
||||
input_table[input_num][I_T_PRESSED] = true
|
||||
input_table[input_num][I_T_JUST_RELEASED] = false
|
||||
if Input.is_action_just_pressed(Constants.INPUT_MAP[input_num]):
|
||||
input_table[input_num][I_T_JUST_PRESSED] = true
|
||||
else:
|
||||
input_table[input_num][I_T_JUST_PRESSED] = false
|
||||
else:
|
||||
input_table[input_num][I_T_PRESSED] = false
|
||||
input_table[input_num][I_T_JUST_PRESSED] = false
|
||||
if Input.is_action_just_released(Constants.INPUT_MAP[input_num]):
|
||||
input_table[input_num][I_T_JUST_RELEASED] = true
|
||||
else:
|
||||
input_table[input_num][I_T_JUST_RELEASED] = false
|
||||
|
||||
# process input_table
|
||||
|
||||
if input_table[Constants.PlayerInput.LEFT][I_T_PRESSED] or input_table[Constants.PlayerInput.RIGHT][I_T_PRESSED]:
|
||||
if input_table[Constants.PlayerInput.LEFT][I_T_PRESSED] and input_table[Constants.PlayerInput.RIGHT][I_T_PRESSED]:
|
||||
input_table[Constants.PlayerInput.LEFT][I_T_PRESSED] = false
|
||||
input_table[Constants.PlayerInput.LEFT][I_T_JUST_PRESSED] = false
|
||||
var input_dir
|
||||
if input_table[Constants.PlayerInput.LEFT][I_T_PRESSED]:
|
||||
input_dir = Constants.Direction.LEFT
|
||||
else:
|
||||
input_dir = Constants.Direction.RIGHT
|
||||
# if action-idle or action-jumping
|
||||
if (player.get_current_action() == Constants.UnitCurrentAction.IDLE
|
||||
or player.get_current_action() == Constants.UnitCurrentAction.JUMPING):
|
||||
# set move
|
||||
player.set_action(Constants.ActionType.MOVE)
|
||||
# set facing
|
||||
player.facing = input_dir
|
||||
|
||||
if input_table[Constants.PlayerInput.GBA_A][I_T_PRESSED]:
|
||||
if (player.get_current_action() == Constants.UnitCurrentAction.JUMPING
|
||||
or (player.get_current_action() == Constants.UnitCurrentAction.IDLE
|
||||
and player.unit_conditions[Constants.UnitCondition.IS_ON_GROUND]
|
||||
and input_table[Constants.PlayerInput.GBA_A][I_T_JUST_PRESSED])):
|
||||
player.set_action(Constants.ActionType.JUMP)
|
64
Scripts/GameUtils.gd
Normal file
64
Scripts/GameUtils.gd
Normal file
|
@ -0,0 +1,64 @@
|
|||
extends Node
|
||||
|
||||
# also returns x, y of intersection
|
||||
static func path_intersects_border(path_from : Vector2, path_to : Vector2, border_pt_a : Vector2, border_pt_b : Vector2):
|
||||
# y = mx + b
|
||||
|
||||
# if path is vertical
|
||||
if path_from.x == path_to.x:
|
||||
# if border is vertical
|
||||
if border_pt_a.x == border_pt_b.x:
|
||||
return [false, Vector2()]
|
||||
# if border is not vertical
|
||||
else:
|
||||
# if path x is not within range
|
||||
if path_from.x < min(border_pt_a.x, border_pt_b.x) or path_from.x > max(border_pt_a.x, border_pt_b.x):
|
||||
return [false, Vector2()]
|
||||
# find m and b and solve for where border intersects with vertical line
|
||||
var m = get_m(border_pt_b, border_pt_a)
|
||||
var b = get_b(border_pt_a, m)
|
||||
var intersect_y = m * path_from.x + b
|
||||
var intersects : bool = intersect_y >= min(path_from.y, path_to.y) and intersect_y <= max(path_from.y, path_to.y)
|
||||
return [intersects, Vector2(path_from.x, intersect_y)]
|
||||
# else if border is vertical
|
||||
elif border_pt_a.x == border_pt_b.x:
|
||||
# if border x is not within range
|
||||
if border_pt_a.x < min(path_from.x, path_to.x) or border_pt_a.x > max(path_from.x, path_to.x):
|
||||
return [false, Vector2()]
|
||||
# find m and b and solve for where border intersects with vertical line
|
||||
var m = get_m(path_to, path_from)
|
||||
var b = get_b(path_from, m)
|
||||
var intersect_y = m * border_pt_a.x + b
|
||||
return [intersect_y >= min(border_pt_a.y, border_pt_b.y) and intersect_y <= max(border_pt_a.y, border_pt_b.y), Vector2(border_pt_a.x, intersect_y)]
|
||||
# else if path and border are parallel
|
||||
elif get_m(path_to, path_from) == get_m(border_pt_b, border_pt_a):
|
||||
return [false, Vector2()]
|
||||
else:
|
||||
var path_m = get_m(path_to, path_from)
|
||||
var path_b = get_b(path_from, path_m)
|
||||
var border_m = get_m(border_pt_b, border_pt_a)
|
||||
var border_b = get_b(border_pt_a, border_m)
|
||||
# m1x + b1 = m2x + b2
|
||||
# m1x - m2x = b2 - b1
|
||||
# x = (b2 - b1) / (m1 - m2)
|
||||
var intersect_x = (border_b - path_b) / (path_m - border_m)
|
||||
return [(intersect_x >= min(border_pt_a.x, border_pt_b.x) and intersect_x <= max(border_pt_a.x, border_pt_b.x)
|
||||
and intersect_x >= min(path_from.x, path_to.x) and intersect_x <= max(path_from.x, path_to.x)), Vector2(intersect_x, path_m * intersect_x + path_b)]
|
||||
|
||||
static func get_m(pt_a : Vector2, pt_b : Vector2):
|
||||
return (pt_b.y - pt_a.y) / (pt_b.x - pt_a.x)
|
||||
|
||||
static func get_b(pt : Vector2, m : float):
|
||||
return pt.y - (m * pt.x)
|
||||
|
||||
static func reangle_move(unit, angle_helper):
|
||||
# pythagoras
|
||||
var unit_magnitude = sqrt(pow(unit.h_speed, 2) + pow(unit.v_speed, 2))
|
||||
var helper_magnitude = sqrt(pow(angle_helper[1].x - angle_helper[0].x, 2) + pow(angle_helper[1].y - angle_helper[0].y, 2))
|
||||
if helper_magnitude == 0:
|
||||
unit.h_speed = 0
|
||||
unit.v_speed = 0
|
||||
return
|
||||
var factor = unit_magnitude / helper_magnitude
|
||||
unit.h_speed = (angle_helper[1].x - angle_helper[0].x) * factor
|
||||
unit.v_speed = (angle_helper[1].y - angle_helper[0].y) * factor
|
306
Scripts/StageEnvironment.gd
Normal file
306
Scripts/StageEnvironment.gd
Normal file
|
@ -0,0 +1,306 @@
|
|||
# Handles unit-environment iteraction
|
||||
extends Object
|
||||
|
||||
const GameUtils = preload("res://Scripts/GameUtils.gd")
|
||||
const Constants = preload("res://Scripts/Constants.gd")
|
||||
const GameScene = preload("res://Scripts/GameScene.gd")
|
||||
const Unit = preload("res://Scripts/Unit.gd")
|
||||
|
||||
var scene : GameScene
|
||||
|
||||
var colliders = []
|
||||
# if unit's move vector has at least one of these directional components,
|
||||
# do the collision check
|
||||
var collision_into_direction_arrays = [] # nested array
|
||||
|
||||
var unit_collision_bounds = {} # maps unit type to [upper, lower, left, right]
|
||||
|
||||
func _init(the_scene : GameScene):
|
||||
scene = the_scene
|
||||
var stage : TileMap = scene.get_node("Stage")
|
||||
init_stage_grid(stage)
|
||||
stage.scale.x = Constants.SCALE_FACTOR
|
||||
stage.scale.y = Constants.SCALE_FACTOR
|
||||
# populate unit_collision_bounds
|
||||
for unit_type in Constants.ENV_COLLIDERS.keys():
|
||||
var initial_detect_pt = Constants.ENV_COLLIDERS[unit_type][0]
|
||||
var upper : float = initial_detect_pt[0].y
|
||||
var lower : float = initial_detect_pt[0].y
|
||||
var left : float = initial_detect_pt[0].x
|
||||
var right: float = initial_detect_pt[0].x
|
||||
for detect_pt in Constants.ENV_COLLIDERS[unit_type]:
|
||||
if (detect_pt[1].find(Constants.Direction.UP) != -1
|
||||
and detect_pt[0].y > upper):
|
||||
upper = detect_pt[0].y
|
||||
if (detect_pt[1].find(Constants.Direction.DOWN) != -1
|
||||
and detect_pt[0].y < lower):
|
||||
lower = detect_pt[0].y
|
||||
if (detect_pt[1].find(Constants.Direction.LEFT) != -1
|
||||
and detect_pt[0].x < left):
|
||||
left = detect_pt[0].x
|
||||
if (detect_pt[1].find(Constants.Direction.RIGHT) != -1
|
||||
and detect_pt[0].x > right):
|
||||
right = detect_pt[0].x
|
||||
unit_collision_bounds[unit_type] = [upper, lower, left, right]
|
||||
|
||||
func init_stage_grid(tilemap : TileMap):
|
||||
for map_elem in tilemap.get_used_cells():
|
||||
var stage_x = floor(tilemap.map_to_world(map_elem).x / Constants.GRID_SIZE)
|
||||
var stage_y = floor(-1 * tilemap.map_to_world(map_elem).y / Constants.GRID_SIZE) - 1
|
||||
var map_elem_type : int
|
||||
var cellv = tilemap.get_cellv(map_elem)
|
||||
var found_map_elem_type : bool = false
|
||||
for test_map_elem_type in [
|
||||
Constants.MapElemType.SQUARE,
|
||||
Constants.MapElemType.SLOPE_LEFT,
|
||||
Constants.MapElemType.SLOPE_RIGHT,
|
||||
Constants.MapElemType.SMALL_SLOPE_LEFT_1,
|
||||
Constants.MapElemType.SMALL_SLOPE_LEFT_2,
|
||||
Constants.MapElemType.SMALL_SLOPE_RIGHT_1,
|
||||
Constants.MapElemType.SMALL_SLOPE_RIGHT_2,
|
||||
Constants.MapElemType.LEDGE]:
|
||||
for test_cell_v in Constants.TILE_SET_MAP_ELEMS[scene.tile_set_name][test_map_elem_type]:
|
||||
if test_cell_v == cellv:
|
||||
map_elem_type = test_map_elem_type
|
||||
found_map_elem_type = true
|
||||
break
|
||||
if found_map_elem_type:
|
||||
break
|
||||
match map_elem_type:
|
||||
Constants.MapElemType.SQUARE:
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.DOWN, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1)
|
||||
Constants.MapElemType.SLOPE_LEFT:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y),
|
||||
Vector2(stage_x + 1, stage_y + 1),
|
||||
[Constants.Direction.RIGHT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.SLOPE_RIGHT:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y + 1),
|
||||
Vector2(stage_x + 1, stage_y),
|
||||
[Constants.Direction.LEFT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.SMALL_SLOPE_LEFT_1:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y),
|
||||
Vector2(stage_x + 1, stage_y + .5),
|
||||
[Constants.Direction.RIGHT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, .5)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.SMALL_SLOPE_LEFT_2:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y + .5),
|
||||
Vector2(stage_x + 1, stage_y + 1),
|
||||
[Constants.Direction.RIGHT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, .5)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.SMALL_SLOPE_RIGHT_1:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y + .5),
|
||||
Vector2(stage_x + 1, stage_y),
|
||||
[Constants.Direction.LEFT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, .5)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.SMALL_SLOPE_RIGHT_2:
|
||||
try_insert_collider(
|
||||
Vector2(stage_x, stage_y + 1),
|
||||
Vector2(stage_x + 1, stage_y + .5),
|
||||
[Constants.Direction.LEFT, Constants.Direction.DOWN]
|
||||
)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, .5)
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1)
|
||||
Constants.MapElemType.LEDGE:
|
||||
insert_grid_collider(stage_x, stage_y, Constants.Direction.DOWN, 1)
|
||||
|
||||
func insert_grid_collider(stage_x, stage_y, direction : int, fractional_height : float):
|
||||
var check_colliders = []
|
||||
var insert_colliders = []
|
||||
var point_a : Vector2
|
||||
var point_b : Vector2
|
||||
match direction:
|
||||
Constants.Direction.UP:
|
||||
point_a = Vector2(stage_x, stage_y)
|
||||
point_b = Vector2(stage_x + 1, stage_y)
|
||||
Constants.Direction.DOWN:
|
||||
point_a = Vector2(stage_x, stage_y + 1)
|
||||
point_b = Vector2(stage_x + 1, stage_y + 1)
|
||||
Constants.Direction.LEFT:
|
||||
point_a = Vector2(stage_x + 1, stage_y + (1 * fractional_height))
|
||||
point_b = Vector2(stage_x + 1, stage_y)
|
||||
Constants.Direction.RIGHT:
|
||||
point_a = Vector2(stage_x, stage_y + (1 * fractional_height))
|
||||
point_b = Vector2(stage_x, stage_y)
|
||||
try_insert_collider(point_a, point_b, [direction])
|
||||
|
||||
func try_insert_collider(point_a : Vector2, point_b : Vector2, directions : Array):
|
||||
if directions.size() == 1:
|
||||
# aligned with grid
|
||||
for i in range(len(colliders)):
|
||||
if (colliders[i][0] == point_a
|
||||
and colliders[i][1] == point_b
|
||||
and are_inverse_directions(collision_into_direction_arrays[i][0], directions[0])):
|
||||
colliders.remove(i)
|
||||
collision_into_direction_arrays.remove(i)
|
||||
return
|
||||
colliders.append([point_a, point_b])
|
||||
collision_into_direction_arrays.append(directions)
|
||||
|
||||
func are_inverse_directions(d1, d2):
|
||||
return ((d1 == Constants.Direction.LEFT and d2 == Constants.Direction.RIGHT)
|
||||
or (d1 == Constants.Direction.RIGHT and d2 == Constants.Direction.LEFT)
|
||||
or (d1 == Constants.Direction.UP and d2 == Constants.Direction.DOWN)
|
||||
or (d1 == Constants.Direction.DOWN and d2 == Constants.Direction.UP))
|
||||
|
||||
func interact(unit : Unit, delta):
|
||||
if unit.unit_conditions[Constants.UnitCondition.IS_ON_GROUND]:
|
||||
if unit.v_speed < 0:
|
||||
# reassign the move speeds so that it reflects the true movement
|
||||
reangle_grounded_move(unit)
|
||||
else:
|
||||
# apply gravity
|
||||
unit.v_speed = max(unit.v_speed - (Constants.GRAVITY * delta), Constants.MAX_FALL_SPEED)
|
||||
if not unit.h_speed == 0 or not unit.v_speed == 0:
|
||||
# regular collision
|
||||
for i in range(colliders.size()):
|
||||
if check_collision(unit, colliders[i], collision_into_direction_arrays[i], delta):
|
||||
break
|
||||
# Do this a second time in case the unit's new move displacement needs
|
||||
# fixing
|
||||
for i in range(colliders.size()):
|
||||
if check_collision(unit, colliders[i], collision_into_direction_arrays[i], delta):
|
||||
break
|
||||
|
||||
func reangle_grounded_move(unit : Unit):
|
||||
var has_ground_collision : bool = false
|
||||
for i in range(colliders.size()):
|
||||
var collider = colliders[i]
|
||||
var collision_into_directions = collision_into_direction_arrays[i]
|
||||
if collider[0].x == collider[1].x:
|
||||
continue
|
||||
if collision_early_exit(unit, collider, collision_into_directions):
|
||||
continue
|
||||
if collision_into_directions.find(Constants.Direction.DOWN) == -1:
|
||||
continue
|
||||
# returns [collision?, x, y]
|
||||
var intersects_results = GameUtils.path_intersects_border(
|
||||
Vector2(unit.pos.x, unit.pos.y),
|
||||
Vector2(unit.pos.x, unit.pos.y - .5),
|
||||
collider[0],
|
||||
collider[1])
|
||||
if intersects_results[0]:
|
||||
has_ground_collision = true
|
||||
unit.pos.y = intersects_results[1].y + Constants.QUANTUM_DIST
|
||||
unit.last_contacted_ground_collider = collider
|
||||
reangle_move(unit, collider, true)
|
||||
if !has_ground_collision:
|
||||
reangle_move(unit, unit.last_contacted_ground_collider, true)
|
||||
unit.set_unit_condition(Constants.UnitCondition.IS_ON_GROUND, false)
|
||||
|
||||
# nullify_h_speed should be true if we are reangling a ground movement vector
|
||||
func reangle_move(unit : Unit, collider, nullify_h_speed : bool):
|
||||
var angle_helper
|
||||
if unit.h_speed > 0:
|
||||
angle_helper = collider
|
||||
else:
|
||||
angle_helper = [collider[1], collider[0]]
|
||||
if nullify_h_speed:
|
||||
unit.h_speed = 0
|
||||
GameUtils.reangle_move(unit, angle_helper)
|
||||
|
||||
func check_collision(unit : Unit, collider, collision_into_directions, delta):
|
||||
if collision_early_exit(unit, collider, collision_into_directions):
|
||||
return false
|
||||
var is_ground_collision : bool = collision_into_directions.find(Constants.Direction.DOWN) != -1
|
||||
for unit_env_collider in Constants.ENV_COLLIDERS[unit.unit_type]:
|
||||
if unit_env_collider_early_exit(unit_env_collider[1], collision_into_directions):
|
||||
continue
|
||||
var collision_check_location : Vector2 = unit.pos + unit_env_collider[0]
|
||||
var collision_check_try_location : Vector2 = collision_check_location + Vector2(unit.h_speed * delta, unit.v_speed * delta)
|
||||
# returns [collision?, (x, y)]
|
||||
var intersects_results = GameUtils.path_intersects_border(
|
||||
collision_check_location,
|
||||
collision_check_try_location,
|
||||
collider[0],
|
||||
collider[1])
|
||||
if intersects_results[0]:
|
||||
if is_ground_collision:
|
||||
if unit_env_collider[0] == Vector2(0, 0):
|
||||
unit.pos.y = intersects_results[1].y + Constants.QUANTUM_DIST
|
||||
unit.pos.x = intersects_results[1].x
|
||||
if unit.unit_conditions[Constants.UnitCondition.IS_ON_GROUND]:
|
||||
# preserve magnitude
|
||||
reangle_move(unit, collider, false)
|
||||
else:
|
||||
if unit.get_current_action() != Constants.UnitCurrentAction.JUMPING:
|
||||
unit.set_unit_condition(Constants.UnitCondition.IS_ON_GROUND, true)
|
||||
# landed on ground, horizontal component to become magnitude
|
||||
unit.v_speed = 0
|
||||
reangle_move(unit, collider, false)
|
||||
else:
|
||||
if collider[0].x == collider[1].x:
|
||||
# vertical wall collision
|
||||
var new_cc_x : float
|
||||
if unit.h_speed < 0:
|
||||
new_cc_x = intersects_results[1].x + Constants.QUANTUM_DIST
|
||||
else:
|
||||
new_cc_x = intersects_results[1].x - Constants.QUANTUM_DIST
|
||||
var target_h_speed : float = (new_cc_x - collision_check_location.x) / delta
|
||||
var factor : float = target_h_speed / unit.h_speed
|
||||
unit.h_speed *= factor
|
||||
if unit.get_condition(Constants.UnitCondition.IS_ON_GROUND, false):
|
||||
# also shorten vertical component to preserve move vector direction
|
||||
unit.v_speed *= factor
|
||||
else:
|
||||
# ceiling collision (horizontal only for now)
|
||||
var new_cc_y : float = intersects_results[1].y - Constants.QUANTUM_DIST
|
||||
unit.v_speed = (new_cc_y - collision_check_location.y) / delta
|
||||
if unit.get_current_action() == Constants.UnitCurrentAction.JUMPING:
|
||||
unit.set_current_action(Constants.UnitCurrentAction.IDLE)
|
||||
if !is_ground_collision or unit_env_collider[0] == Vector2(0, 0):
|
||||
# return true if there's a collision
|
||||
# don't return true if it's a ground collision but the unit environment collider is not the (0, 0) collider
|
||||
return true
|
||||
return false
|
||||
|
||||
func collision_early_exit(unit : Unit, collider, collision_into_directions):
|
||||
if (collider[0].y > unit.pos.y + unit_collision_bounds[unit.unit_type][0] + 1
|
||||
and collider[1].y > unit.pos.y + unit_collision_bounds[unit.unit_type][0] + 1):
|
||||
return true
|
||||
if (collider[0].y < unit.pos.y + unit_collision_bounds[unit.unit_type][1] - 1
|
||||
and collider[1].y < unit.pos.y + unit_collision_bounds[unit.unit_type][1] - 1):
|
||||
return true
|
||||
if collider[1].x < unit.pos.x + unit_collision_bounds[unit.unit_type][2] - 1:
|
||||
return true
|
||||
if collider[0].x > unit.pos.x + unit_collision_bounds[unit.unit_type][3] + 1:
|
||||
return true
|
||||
for collision_into_direction in collision_into_directions:
|
||||
if collision_into_direction == Constants.Direction.UP and unit.v_speed > 0:
|
||||
return false
|
||||
if collision_into_direction == Constants.Direction.DOWN and unit.v_speed < 0:
|
||||
return false
|
||||
if collision_into_direction == Constants.Direction.LEFT and unit.h_speed < 0:
|
||||
return false
|
||||
if collision_into_direction == Constants.Direction.RIGHT and unit.h_speed > 0:
|
||||
return false
|
||||
return true
|
||||
|
||||
func unit_env_collider_early_exit(env_collider_directions, collision_into_directions):
|
||||
var found_matching_direction : bool = false
|
||||
for env_collider_direction in env_collider_directions:
|
||||
for collision_into_direction in collision_into_directions:
|
||||
if env_collider_direction == collision_into_direction:
|
||||
return false
|
||||
return true
|
282
Scripts/Unit.gd
Normal file
282
Scripts/Unit.gd
Normal file
|
@ -0,0 +1,282 @@
|
|||
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
|
8
Scripts/Units/NPCExample.gd
Normal file
8
Scripts/Units/NPCExample.gd
Normal file
|
@ -0,0 +1,8 @@
|
|||
extends NPCUnit
|
||||
|
||||
|
||||
func before_tick():
|
||||
if scene.rng.randf() < 0.5:
|
||||
facing = Constants.Direction.RIGHT
|
||||
else:
|
||||
facing = Constants.Direction.LEFT
|
79
Scripts/Units/NPCUnit.gd
Normal file
79
Scripts/Units/NPCUnit.gd
Normal file
|
@ -0,0 +1,79 @@
|
|||
extends Unit
|
||||
|
||||
class_name NPCUnit
|
||||
|
||||
var spawn_point : Vector2
|
||||
|
||||
export var tick_duration : float
|
||||
var tick_timer : float = 0
|
||||
|
||||
export(Dictionary) var action_sequence_map # action sequence to weight, [] = do nothing
|
||||
# action sequence is an array of action type and an array of timestamps
|
||||
# action type is the string representation of Constants.ActionType
|
||||
# example action map: {[[action1], [0]]: 1, [[action2, action3], [0, 1]]: 2}
|
||||
var weight_sum : float
|
||||
|
||||
export var action_duration_map = {} # specific durations for given action
|
||||
var current_npc_action_times_elapsed = {}
|
||||
var current_npc_action_active = {}
|
||||
|
||||
var current_action_sequence = null
|
||||
var current_action_sequence_time_elapsed : float = 0
|
||||
var current_action_sequence_index : int = 0
|
||||
|
||||
func _ready():
|
||||
for action_sequence in action_sequence_map.keys():
|
||||
weight_sum += action_sequence_map[action_sequence]
|
||||
for action in action_duration_map:
|
||||
current_npc_action_times_elapsed[action] = 0
|
||||
current_npc_action_active[action] = false
|
||||
|
||||
func before_tick():
|
||||
pass
|
||||
|
||||
func handle_input(delta):
|
||||
if current_action_sequence != null:
|
||||
for action in current_npc_action_active:
|
||||
if current_npc_action_active[action]:
|
||||
if current_npc_action_times_elapsed[action] < action_duration_map[action]:
|
||||
set_action(Constants.ActionType.get(action))
|
||||
current_npc_action_times_elapsed[action] += delta
|
||||
else:
|
||||
current_npc_action_active[action] = false
|
||||
if (current_action_sequence_index < current_action_sequence[1].size()
|
||||
and current_action_sequence_time_elapsed >= current_action_sequence[1][current_action_sequence_index]):
|
||||
var action = current_action_sequence[0][current_action_sequence_index]
|
||||
set_action(Constants.ActionType.get(action))
|
||||
if action_duration_map.has(action):
|
||||
current_npc_action_active[action] = true
|
||||
current_npc_action_times_elapsed[action] = 0
|
||||
current_action_sequence_index += 1
|
||||
var current_action_sequence_duration : float = current_action_sequence[1][-1]
|
||||
if action_duration_map.has(current_action_sequence[0][-1]):
|
||||
current_action_sequence_duration += action_duration_map[current_action_sequence[0][-1]]
|
||||
if current_action_sequence_time_elapsed > current_action_sequence_duration:
|
||||
reset_npc_unit()
|
||||
current_action_sequence_time_elapsed += delta
|
||||
else:
|
||||
if tick_timer == 0:
|
||||
before_tick()
|
||||
var rand_num : float = scene.rng.randf() * weight_sum
|
||||
var temp_sum : float = 0
|
||||
for action_sequence in action_sequence_map.keys():
|
||||
temp_sum += action_sequence_map[action_sequence]
|
||||
if temp_sum >= rand_num:
|
||||
if action_sequence == []:
|
||||
tick_timer = tick_duration
|
||||
else:
|
||||
current_action_sequence = action_sequence
|
||||
current_action_sequence_time_elapsed = 0
|
||||
current_action_sequence_index = 0
|
||||
break
|
||||
else:
|
||||
tick_timer = max(0, tick_timer - delta)
|
||||
|
||||
func reset_npc_unit():
|
||||
current_action_sequence = null
|
||||
tick_timer = tick_duration
|
||||
for action in current_npc_action_active:
|
||||
current_npc_action_active[action] = false
|
92
Scripts/Units/Player.gd
Normal file
92
Scripts/Units/Player.gd
Normal file
|
@ -0,0 +1,92 @@
|
|||
extends Unit
|
||||
|
||||
# Player-specific code
|
||||
class_name Player
|
||||
|
||||
const RECOIL_PUSHBACK = 15
|
||||
|
||||
func _init():
|
||||
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
|
||||
|
||||
func execute_actions(delta):
|
||||
.execute_actions(delta)
|
||||
for action_num in Constants.UNIT_TYPE_ACTIONS[Constants.UnitType.PLAYER]:
|
||||
if !actions[action_num]:
|
||||
continue
|
||||
match action_num:
|
||||
# handle custom actions
|
||||
Constants.ActionType.RECOIL:
|
||||
recoil()
|
||||
_:
|
||||
pass
|
||||
|
||||
func recoil():
|
||||
if is_current_action_timer_done(Constants.UnitCurrentAction.RECOILING):
|
||||
set_current_action(Constants.UnitCurrentAction.IDLE)
|
||||
else:
|
||||
set_current_action(Constants.UnitCurrentAction.RECOILING)
|
||||
set_sprite(Constants.SpriteClass.RECOIL)
|
||||
|
||||
func handle_input(delta):
|
||||
scene.handle_player_input()
|
||||
|
||||
func _on_Player_area_entered(area: Area2D) -> void:
|
||||
if get_condition(Constants.UnitCondition.IS_INVINCIBLE, false):
|
||||
return
|
||||
if area is Unit:
|
||||
hit_from_area(area)
|
||||
|
||||
func hit_from_area(other_area : Area2D):
|
||||
var collision_dir : int
|
||||
if other_area.position > position:
|
||||
collision_dir = Constants.Direction.RIGHT
|
||||
else:
|
||||
collision_dir = Constants.Direction.LEFT
|
||||
hit(collision_dir)
|
||||
|
||||
func hit(dir : int):
|
||||
.hit(dir)
|
||||
set_unit_condition_with_timer(Constants.UnitCondition.IS_INVINCIBLE)
|
||||
start_flash()
|
||||
set_action(Constants.ActionType.RECOIL)
|
||||
set_current_action(Constants.UnitCurrentAction.RECOILING)
|
||||
set_unit_condition(Constants.UnitCondition.MOVING_STATUS, Constants.UnitMovingStatus.IDLE)
|
||||
|
||||
func invincibility_ended():
|
||||
is_flash = false
|
||||
if get_overlapping_areas().size() > 0:
|
||||
if get_overlapping_areas()[0] is Unit:
|
||||
hit_from_area(get_overlapping_areas()[0])
|
||||
|
||||
func handle_recoil():
|
||||
if not hit_queued:
|
||||
return
|
||||
hit_queued = false
|
||||
if get_condition(Constants.UnitCondition.IS_ON_GROUND, true):
|
||||
if h_speed > 0:
|
||||
if hit_dir == Constants.Direction.LEFT:
|
||||
v_speed -= RECOIL_PUSHBACK
|
||||
else:
|
||||
v_speed += RECOIL_PUSHBACK
|
||||
elif h_speed < 0:
|
||||
if hit_dir == Constants.Direction.LEFT:
|
||||
v_speed += RECOIL_PUSHBACK
|
||||
else:
|
||||
v_speed -= RECOIL_PUSHBACK
|
||||
else:
|
||||
v_speed = -RECOIL_PUSHBACK
|
||||
if hit_dir == Constants.Direction.LEFT:
|
||||
h_speed = Constants.QUANTUM_DIST
|
||||
else:
|
||||
h_speed = -Constants.QUANTUM_DIST
|
||||
if v_speed > 0:
|
||||
h_speed *= -1
|
||||
v_speed = -v_speed
|
||||
else:
|
||||
if hit_dir == Constants.Direction.LEFT:
|
||||
h_speed += RECOIL_PUSHBACK
|
||||
else:
|
||||
h_speed -= RECOIL_PUSHBACK
|
||||
facing = hit_dir
|
Loading…
Add table
Add a link
Reference in a new issue