123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- extends CharacterBody3D
- class_name GridBasedMovable
- #signals
- signal target_changed(target_pos)
- signal pushed(direction)
- signal tween_started()
- #enums
- #fixme: make DIRECTIONS unique
- enum DIRECTIONS {UP, DOWN, LEFT, RIGHT, NONE}
- #constants
- const GRAVITY = -20
- #exports
- @export var step_size = 2
- @export var step_time = 0.15
- @export var stop_time = 0.0
- @export var can_slide = false
- @export var enable_gravity = true
- @export var invincible = false
- @export var vulnerable_to_spikes = true
- @export var enable_rotation = false
- #vars
- var requested_direction = DIRECTIONS.NONE
- var local_tween_position = Vector3()
- var target_position = Vector3()
- var distant_target_position = Vector3()
- var gravity_applied = false
- var enable_movement = true
- var level
- var speed_modifier = 1.0
- var already_hit_by_spikes = false
- var animated_mesh = null
- var has_animated_mesh = false
- var mesh_direction
- var animation_player = null
- #onready
- @onready var rc_holder = $RC_Holder
- @onready var ray_front = $RC_Holder/RayCastFront
- @onready var ray_downramp = $RC_Holder/RayCastDownRamp
- @onready var ray_front_pull = $RC_Holder/RayCastFrontPull
- @onready var ray_back = $RC_Holder/RayCastBack
- @onready var ray_ground = $RC_Holder/RayCastGround
- @onready var ray_top = $RC_Holder/RayCastTop
- @onready var rotation_point = $RotationPoint
- @onready var mesh_instance = $RotationPoint/StaticMesh
- @onready var detection_area = $DetectionArea
- @onready var protection_area = $RotationPoint/PlacementProtectionArea
- @onready var timer = $Timer
- @onready var SMR_timer = $SpeedModResetTimer
- @onready var health_node = $Health
- #----------- built-in functions -----------
- func _ready() -> void:
- animated_mesh = find_child("AnimatedMesh")
- if animated_mesh != null:
- has_animated_mesh = true
- animated_mesh.visible = true
- mesh_instance.visible = false
- animation_player = animated_mesh.find_child("AnimationPlayer")
-
- position.x = position.snapped(Vector3(step_size, step_size, step_size) - Vector3(1,0,1)).x
- position.z = position.snapped(Vector3(step_size, step_size, step_size) - Vector3(1,0,1)).z
-
- requested_direction = DIRECTIONS.NONE
- local_tween_position = position
- timer.wait_time = stop_time + (step_time * speed_modifier)
- target_position = position
- distant_target_position = position
- update_detection_area()
-
- level = get_tree().get_nodes_in_group("level")[0]
- already_hit_by_spikes = false
- func _physics_process(delta):
- _handle_movement(delta)
- #----------- movement related -----------
- func _handle_movement(delta):
-
- update_detection_area()
-
- #reset the raycast to be disabled when not moving
- #_update_raycast_direction(DIRECTIONS.NONE)
- rc_holder.update_raycast_direction(DIRECTIONS.NONE)
- gravity_applied = false
-
- if is_grid_aligned():
- if !is_on_floor():
- if enable_gravity:
- velocity.y += GRAVITY * delta
- gravity_applied = true
- target_position.y = position.y
- distant_target_position.y = position.y
- #else:
- if can_slide:
- _handle_slope_sliding()
-
- move_to(requested_direction)
-
- else:
- requested_direction = DIRECTIONS.NONE
-
- if !ray_ground.is_colliding() and on_floor_or_flying() and !gravity_applied and enable_gravity:
- velocity.y += GRAVITY * delta * 6
- position.x = local_tween_position.x
- position.z = local_tween_position.z
- if ray_ground.is_colliding():
- var groundObject = ray_ground.get_collider()
- if groundObject.is_in_group("spiky") and vulnerable_to_spikes == true and invincible == false:
-
- if already_hit_by_spikes == false:
- hit()
- already_hit_by_spikes = true
-
-
- move_and_slide()
- func can_move_to(direction: DIRECTIONS) -> bool:
- if direction != DIRECTIONS.NONE and is_grid_aligned() and on_floor_or_flying() and is_target_free(direction):
- rc_holder.update_raycast_direction(direction)
- return !ray_front.is_colliding()
-
- return false
-
- func move_to(direction, force_move: bool = false, instant: bool = false):
- #print ("move_to called: " + str(instant))
- if direction != DIRECTIONS.NONE and is_grid_aligned():
- rc_holder.update_raycast_direction(direction)
- update_rotation_point(direction)
-
-
- if (!ray_front.is_colliding() and is_target_free(direction) and on_floor_or_flying()) or force_move == true:
- if !instant:
- if timer.get_time_left() == 0:
- timer.start()
- _movement_tween(direction)
- else:
- print ("................")
- _instant_move(direction)
- func movement_started():
- pass
- func _movement_tween(direction: DIRECTIONS):
- #called in move_to, part of the actual movement, the vector "local_tween_position" is animated here
- #later, in _handle_movement, the actual position is set (only x and z coords)
-
- if not enable_movement:
- return
- var movedir = direction_to_vector(direction)
-
- var tween := create_tween()
- if tween == null:
- #fixes ugly crash with tween = null ;-)
- return
- tween.stop()
- tween.set_trans(Tween.TRANS_LINEAR)
- tween.set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
- target_position = position + movedir * step_size
- distant_target_position = target_position + movedir * step_size
- emit_signal("target_changed", target_position)
-
- tween.tween_property(self, "local_tween_position", target_position, step_time * speed_modifier)
- #print (str(name) + " starts movement, target pos: " + str(target_position))
- tween.play()
- emit_signal("tween_started")
- movement_started()
- already_hit_by_spikes = false
- func _instant_move(direction: DIRECTIONS):
- if not enable_movement:
- return
- var movedir = direction_to_vector(direction)
-
- target_position = position + movedir * step_size
- distant_target_position = target_position + movedir * step_size
- emit_signal("target_changed", target_position)
- local_tween_position = target_position
- position.x = target_position.x
- position.z = target_position.z
- func modify_speed_timed(amount, seconds = 0.01):
- #print ("modify speed")
- speed_modifier = amount
- SMR_timer.wait_time = seconds
- SMR_timer.start()
- func _on_speed_mod_reset_timer_timeout() -> void:
- speed_modifier = 1.0
- #print ("reset speed")
- func _handle_slope_sliding():
- if is_on_slope() and requested_direction == DIRECTIONS.NONE:
- move_and_slide()
- if get_floor_normal().x > 0.2:
- requested_direction = DIRECTIONS.RIGHT
- elif get_floor_normal().x < -0.2:
- requested_direction = DIRECTIONS.LEFT
- elif get_floor_normal().z > 0.2:
- requested_direction = DIRECTIONS.DOWN
- elif get_floor_normal().z < -0.2:
- requested_direction = DIRECTIONS.UP
- #----------- push and pull -----------
- func push(object_to_push: GridBasedMovable, direction: DIRECTIONS, force: bool = false, instant: bool = false):
- #print ("push......!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
- #prevents side pushing
- if object_to_push.enable_movement == false:
- return
- if direction == DIRECTIONS.UP or direction == DIRECTIONS.DOWN:
- if position.x != object_to_push.position.x:
- return
-
- elif direction == DIRECTIONS.LEFT or direction == DIRECTIONS.RIGHT:
- if position.z != object_to_push.position.z:
- return
- if object_to_push.can_move_to(direction) and object_to_push.timer.get_time_left() == 0:
- object_to_push.move_to(direction, force, instant)
- object_to_push.emit_signal("pushed", direction)
-
- move_to(direction, true, instant)
- return true
- return false
- func can_be_pulled_to(direction: DIRECTIONS) -> bool:
- rc_holder.update_raycast_direction(direction)
-
- if ray_front_pull.is_colliding():
- var _frontObject = ray_front_pull.get_collider()
- if _frontObject != null:
- print ("front collision: " + str(_frontObject.name))
- return false
-
- return true
- func get_push_down_ramp_object():
- if requested_direction != DIRECTIONS.NONE and is_on_slope() and ray_downramp.is_colliding() and is_grid_aligned():
- var downramp = ray_downramp.get_collider()
- if downramp != null:
- if downramp.is_in_group("pushable"):
- print ("downramp object detected")
- return downramp
- elif downramp.get_parent().is_in_group("pushable"):
- return downramp.get_parent()
- return null
- #----------- physics related -----------
- func on_floor_or_flying() -> bool:
- return is_on_floor() or enable_gravity == false
- func is_on_slope() -> bool:
-
- return get_floor_normal().y <= 0.96 and on_floor_or_flying()
- func hit():
- pass
- func update_detection_area():
- detection_area.position.x = target_position.x - position.x
- rc_holder.position.x = target_position.x - position.x
- detection_area.position.z = target_position.z - position.z
- rc_holder.position.z = target_position.z - position.z
-
- ray_front.force_raycast_update()
- ray_back.force_raycast_update()
- ray_ground.force_raycast_update()
- ray_downramp.force_raycast_update()
- ray_front_pull.force_raycast_update()
- func print_front_collision():
- if ray_front.is_colliding():
- var frontObject = ray_front.get_collider()
- if frontObject != null:
- print ("front collision: " + str(frontObject.name))
- else:
- print ("front object is null")
- else:
- print ("no front collision")
- func print_pull_collision():
- if ray_front_pull.is_colliding():
- var frontObject = ray_front_pull.get_collider()
- if frontObject != null:
- print ("pull collision: " + str(frontObject.name))
- else:
- print ("pull object is null")
- else:
- print ("no pull collision")
- func print_back_collision():
- if ray_back.is_colliding():
- var backObject = ray_back.get_collider()
- if backObject != null:
- print ("back collision: " + str(backObject.name))
- else:
- print ("back object is null")
- else:
- print ("no back collision")
- #----------- helper -----------
- func is_target_free(direction: DIRECTIONS):
- var target2 = position + direction_to_vector(direction) * step_size
- return level.is_target_free(self, target2)
- func is_grid_aligned() -> bool:
- return abs(fmod(position.x - 1, step_size)) == 0 and abs(fmod(position.z - 1, step_size)) == 0
- func get_opposite_direction(direction):
- if direction == DIRECTIONS.LEFT:
- return DIRECTIONS.RIGHT
- elif direction == DIRECTIONS.RIGHT:
- return DIRECTIONS.LEFT
- elif direction == DIRECTIONS.UP:
- return DIRECTIONS.DOWN
- elif direction == DIRECTIONS.DOWN:
- return DIRECTIONS.UP
- else:
- return DIRECTIONS.NONE
-
- func direction_to_vector(direction) -> Vector3:
- var dir = Vector3(0, 0, 0)
-
- if direction == DIRECTIONS.UP:
- dir = Vector3(0, 0, -1)
- elif direction == DIRECTIONS.DOWN:
- dir = Vector3(0, 0, 1)
- elif direction == DIRECTIONS.LEFT:
- dir = Vector3(-1, 0, 0)
- elif direction == DIRECTIONS.RIGHT:
- dir = Vector3(1, 0, 0)
- return dir
- func update_rotation_point(direction):
- if not enable_rotation:
- mesh_direction = DIRECTIONS.NONE
- return
- if direction != DIRECTIONS.NONE:
- mesh_direction = direction
- match direction:
- DIRECTIONS.UP:
- rotation_point.rotation = Vector3(0,0,0)
- DIRECTIONS.DOWN:
- rotation_point.rotation = Vector3(0,deg_to_rad(180),0)
- DIRECTIONS.LEFT:
- rotation_point.rotation = Vector3(0,deg_to_rad(90),0)
- DIRECTIONS.RIGHT:
- rotation_point.rotation = Vector3(0,deg_to_rad(-90),0)
- update_detection_area()
- rc_holder.update_raycast_direction(direction)
- func check_distant_object():
- if not is_grid_aligned():
- return level.find_object_at_position(self, distant_target_position)
-
- return null
-
- func check_current_target_object():
- if is_grid_aligned():
- var target_pos = position + direction_to_vector(mesh_direction) * step_size
- return level.find_object_at_position(self, target_pos)
- return null
-
|