335 lines
14 KiB
GDScript
335 lines
14 KiB
GDScript
# 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 collider_to_map_elem_type = {}
|
|
|
|
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,
|
|
Constants.MapElemType.OOB_LOWER]:
|
|
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, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.DOWN, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, .5, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, .5, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, .5, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
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],
|
|
map_elem_type
|
|
)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.RIGHT, 1, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.LEFT, .5, map_elem_type)
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.UP, 1, map_elem_type)
|
|
Constants.MapElemType.LEDGE:
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.DOWN, 1, map_elem_type)
|
|
Constants.MapElemType.OOB_LOWER:
|
|
insert_grid_collider(stage_x, stage_y, Constants.Direction.DOWN, 1, map_elem_type)
|
|
|
|
func insert_grid_collider(stage_x, stage_y, direction : int, fractional_height : float, map_elem_type : int):
|
|
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], map_elem_type)
|
|
|
|
func try_insert_collider(point_a : Vector2, point_b : Vector2, directions : Array, map_elem_type : int):
|
|
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)
|
|
collider_to_map_elem_type[[point_a, point_b]] = map_elem_type
|
|
|
|
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)
|
|
# slope acceleration for DownhillAutoscroller:
|
|
# set the player's last_contacted_map_elem_type field
|
|
if unit is DownhillAutoscrollerPlayer:
|
|
unit.last_contacted_map_elem_type = collider_to_map_elem_type[collider]
|
|
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):
|
|
|
|
# slope acceleration for DownhillAutoscroller:
|
|
# set the player's last_contacted_map_elem_type field
|
|
if unit is DownhillAutoscrollerPlayer:
|
|
unit.last_contacted_map_elem_type = collider_to_map_elem_type[collider]
|
|
if collider_to_map_elem_type[collider] == Constants.MapElemType.OOB_LOWER:
|
|
# this collision should have no effect
|
|
return false
|
|
|
|
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.landed()
|
|
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
|
|
# slow down unit if DownhillAutoscroller:
|
|
if unit is DownhillAutoscrollerPlayer:
|
|
unit.target_move_speed = unit.min_speed
|
|
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
|