You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
6.1 KiB
169 lines
6.1 KiB
extends KinematicBody
|
|
|
|
enum State {
|
|
APPROACH = 0,
|
|
AIM = 1,
|
|
SHOOTING = 2,
|
|
}
|
|
|
|
const PLAYER_AIM_TOLERANCE_DEGREES = 15
|
|
|
|
const SHOOT_WAIT = 0.5
|
|
const AIM_TIME = 1
|
|
|
|
const AIM_PREPARE_TIME = 0.5
|
|
const BLEND_AIM_SPEED = 0.05
|
|
|
|
export(int) var health = 5
|
|
export(bool) var test_shoot = false
|
|
|
|
var state = State.APPROACH
|
|
|
|
var shoot_countdown = SHOOT_WAIT
|
|
var aim_countdown = AIM_TIME
|
|
var aim_preparing = AIM_PREPARE_TIME
|
|
var dead = false
|
|
|
|
var player = null
|
|
var velocity = Vector3()
|
|
var orientation = Transform()
|
|
|
|
|
|
const ROTATION_INTERPOLATE_SPEED = 0.025
|
|
|
|
var root_motion = Transform()
|
|
var motion = Vector2()
|
|
|
|
var last_player_sighting = Vector3()
|
|
|
|
onready var initial_position = transform.origin
|
|
onready var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") * ProjectSettings.get_setting("physics/3d/default_gravity_vector")
|
|
|
|
onready var animation_tree = $AnimationTree
|
|
onready var player_model = $PlayerModel
|
|
onready var shoot_from = player_model.get_node(@"Colette_Armature/Skeleton/GunBone/ShootFrom")
|
|
onready var fire_cooldown = $FireCooldown
|
|
onready var collision_shape = $CapsuleShape
|
|
|
|
onready var sound_effects = $SoundEffects
|
|
onready var sound_effect_jump = sound_effects.get_node(@"Jump")
|
|
onready var sound_effect_land = sound_effects.get_node(@"Land")
|
|
onready var sound_effect_shoot = sound_effects.get_node(@"Shoot")
|
|
onready var explosion_sound = sound_effects.get_node(@"Explosion")
|
|
onready var hit_sound = sound_effects.get_node(@"Hit")
|
|
|
|
onready var debug_marker = $debug_marker
|
|
|
|
func _ready():
|
|
# Pre-initialize orientation transform.
|
|
orientation = player_model.global_transform
|
|
orientation.origin = Vector3()
|
|
|
|
func _physics_process(delta):
|
|
if dead:
|
|
return
|
|
|
|
if player != null and fire_cooldown.time_left == 0:
|
|
# See if player can be killed because in they're sight.
|
|
var ray_origin = shoot_from.global_transform.origin
|
|
var ray_to = player.global_transform.origin + Vector3.UP # Above middle of player.
|
|
var col = get_world().direct_space_state.intersect_ray(ray_origin, ray_to, [self], 0b111)
|
|
# If our raycast hit a player
|
|
if not col.empty() and col.collider == player:
|
|
# Record last sighting (in case they hide later)
|
|
last_player_sighting = ray_to
|
|
|
|
state = State.AIM
|
|
aim_countdown = AIM_TIME
|
|
aim_preparing = 0
|
|
animation_tree["parameters/state/current"] = 1
|
|
|
|
# If the player is detected by at too large an angle, slerp rotate towards them
|
|
var to_player_local = global_transform.xform_inv(player.global_transform.origin)
|
|
# The front of this is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
|
|
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
|
|
var tolerance = deg2rad(PLAYER_AIM_TOLERANCE_DEGREES)
|
|
if angle_to_player > tolerance:
|
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
|
debug_marker.global_transform.origin = self.global_transform.origin + (self.transform.basis.z * 3)
|
|
elif angle_to_player < -tolerance:
|
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
|
debug_marker.global_transform.origin = self.global_transform.origin + (self.transform.basis.z * 3)
|
|
else:
|
|
# Facing player, try to shoot.
|
|
self.look_at(ray_to, Vector3.UP)
|
|
self.rotate_object_local(Vector3.UP, PI)
|
|
|
|
var bullet = preload("res://player/bullet/bullet.tscn").instance()
|
|
get_parent().add_child(bullet)
|
|
bullet.global_transform.origin = ray_origin
|
|
# If we don't rotate the bullets there is no useful way to control the particles ..
|
|
bullet.look_at(ray_to, Vector3.UP)
|
|
bullet.add_collision_exception_with(self)
|
|
var shoot_particle = $PlayerModel/Colette_Armature/Skeleton/GunBone/ShootFrom/ShootParticle
|
|
shoot_particle.restart()
|
|
shoot_particle.emitting = true
|
|
var muzzle_particle = $PlayerModel/Colette_Armature/Skeleton/GunBone/ShootFrom/MuzzleFlash
|
|
muzzle_particle.restart()
|
|
muzzle_particle.emitting = true
|
|
fire_cooldown.start()
|
|
sound_effect_shoot.play()
|
|
|
|
# Player not in sight.
|
|
shoot_countdown = SHOOT_WAIT
|
|
|
|
# Run at their last known position
|
|
if last_player_sighting != Vector3():
|
|
|
|
# If the player is detected by at too large an angle, slerp rotate towards them
|
|
var to_player_local = global_transform.xform_inv(last_player_sighting)
|
|
# The front of this is +Z, and atan2 is zero at +X, so we need to use the Z for the X parameter (second one).
|
|
var angle_to_player = atan2(to_player_local.x, to_player_local.z)
|
|
var tolerance = deg2rad(PLAYER_AIM_TOLERANCE_DEGREES)
|
|
if angle_to_player > tolerance:
|
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
|
debug_marker.global_transform.origin = self.global_transform.origin + (self.transform.basis.z * 3)
|
|
elif angle_to_player < -tolerance:
|
|
lerp_to_face_player(angle_to_player, ROTATION_INTERPOLATE_SPEED)
|
|
debug_marker.global_transform.origin = self.global_transform.origin + (self.transform.basis.z * 3)
|
|
|
|
animation_tree["parameters/state/current"] = 2
|
|
# Blend position for walk speed based on motion.
|
|
|
|
# Run animation straight forward, high speed
|
|
animation_tree["parameters/walk/blend_position"] = Vector2(0.5, 0)
|
|
|
|
# move in XY direction facing
|
|
root_motion = global_transform.basis.z * 3# * delta
|
|
move_and_slide(root_motion.length() * self.transform.basis.z * Vector3(1,0,1), Vector3.UP)
|
|
if get_slide_count() == 0:
|
|
move_and_slide(Vector3.DOWN * 9.8, Vector3.UP)
|
|
|
|
# else: # Not in air or aiming, idle.
|
|
|
|
func lerp_to_face_player(angle_to_player, rot_speed):
|
|
var langle = lerp_angle(0, angle_to_player, rot_speed)
|
|
# restrict to Y axis (no vertical rotation)
|
|
self.global_transform.basis = Basis(Vector3(0, 1, 0), langle) * self.global_transform.basis
|
|
|
|
func hit(col):
|
|
health -= 1
|
|
if health <= 0:
|
|
dead = true
|
|
animation_tree.active = false
|
|
player_model.visible = false
|
|
collision_shape.disabled = true
|
|
explosion_sound.play()
|
|
self.queue_free() # delete self
|
|
hit_sound.play()
|
|
|
|
func _on_Area_body_entered(body):
|
|
if body.name == "Player" or body.name == "Target":
|
|
player = body
|
|
animation_tree["parameters/idle_aim/current"] = 1
|
|
|
|
|
|
func _on_Area_body_exited(body):
|
|
if body.name == "Player":
|
|
player = null
|
|
animation_tree["parameters/idle_aim/current"] = 0
|
|
|