288 lines
7.0 KiB
GDScript
288 lines
7.0 KiB
GDScript
extends KinematicBody
|
|
class_name Character
|
|
|
|
enum States {
|
|
Alive,
|
|
Dead
|
|
}
|
|
|
|
enum GroundType {
|
|
DIRT,
|
|
WATER,
|
|
WOOD,
|
|
METAL,
|
|
SAND,
|
|
GRASS
|
|
}
|
|
|
|
signal health_changed
|
|
signal died
|
|
signal changed_weapon
|
|
signal fired(projectile)
|
|
signal pickup_weapon
|
|
signal jumped
|
|
|
|
export var max_weapons: int = 5
|
|
var weapons: Array = []
|
|
var current_weapon: Weapon
|
|
var is_in_water: bool = false
|
|
export var weapon_refpt_right: NodePath
|
|
export var weapon_refpt_left: NodePath
|
|
export var water_splash_sound: AudioStreamSample
|
|
var animation_blend_vector = Vector2.ZERO
|
|
export var animation_tree_path: NodePath
|
|
var animation_tree: AnimationTree
|
|
export var back_camera_path: NodePath
|
|
onready var back_camera = get_node(back_camera_path)
|
|
export var head_path: NodePath
|
|
onready var head: Spatial = get_node(head_path)
|
|
var marker: MeshInstance
|
|
|
|
|
|
# Move
|
|
var velocity := Vector3()
|
|
var move_axis: Vector3 = Vector3.ZERO
|
|
var can_sprint := true
|
|
var sprinting := false
|
|
const FLOOR_NORMAL := Vector3.UP
|
|
export var sprint_speed := 16.0
|
|
export var acceleration := 8
|
|
export var deacceleration := 6
|
|
export(float, 0.0, 1.0, 0.00001) var air_control := 0.001
|
|
export var can_jump = true
|
|
export var jump_height := 5
|
|
export var floor_max_angle := 90.0
|
|
export var gravity: float = 40.0
|
|
export var walk_speed: float = 2.0
|
|
export var ground_friction: float = 3.0
|
|
export var animation_blend_speed: float = 10
|
|
var ground_snap = Vector3.DOWN
|
|
export var has_marker: bool = false
|
|
|
|
var ground_type = GroundType.DIRT
|
|
|
|
var footstep_sounds = {}
|
|
|
|
export var max_health = 100
|
|
export var health = 100
|
|
var state = States.Alive
|
|
var shoot_delay: float = 0
|
|
|
|
func has_weapon(weapon: Weapon) -> bool:
|
|
if get_similar_weapon(weapon):
|
|
return true
|
|
else:
|
|
return false
|
|
|
|
func get_similar_weapon(weapon: Weapon) -> Weapon:
|
|
for weap in weapons:
|
|
if weap.name == weapon.name:
|
|
return weap
|
|
return null
|
|
|
|
func can_pickup_weapon(weapon: Weapon) -> bool:
|
|
return has_weapon(weapon) or len(weapons) < max_weapons
|
|
|
|
func can_see(other_character: Character) -> bool:
|
|
var ray = RayCast.new()
|
|
ray.translation = head.translation
|
|
ray.cast_to(other_character.head)
|
|
var collider = ray.get_collider()
|
|
return collider == other_character.head
|
|
|
|
func can_shoot_delay() -> bool:
|
|
# returns true if the character has not shot a bullet recently
|
|
return shoot_delay <= 0
|
|
|
|
func pickup_weapon(weapon: Weapon) -> Weapon:
|
|
var similar = get_similar_weapon(weapon)
|
|
if similar:
|
|
similar.current_ammo = clamp(similar.current_ammo + weapon.current_ammo, 0, weapon.max_ammo)
|
|
return similar
|
|
else:
|
|
# add new weapon
|
|
weapons.append(weapon)
|
|
weapon.holder = self
|
|
weapon.visible = false
|
|
if weapon.held_hand == Weapon.WeaponHeld.Left:
|
|
get_node(weapon_refpt_left).add_child(weapon)
|
|
else:
|
|
get_node(weapon_refpt_right).add_child(weapon)
|
|
return weapon
|
|
|
|
func take_damage(hitpts: float):
|
|
if state == States.Alive:
|
|
health -= hitpts
|
|
emit_signal("health_changed")
|
|
if health <= 0:
|
|
die()
|
|
|
|
func get_current_weapon():
|
|
return current_weapon
|
|
|
|
func die():
|
|
state = States.Dead
|
|
emit_signal("died")
|
|
print("Me ded")
|
|
|
|
func set_weapon_slot(slot: int):
|
|
if current_weapon:
|
|
current_weapon.visible = false
|
|
current_weapon.disconnect("fired", self, "weapon_fired")
|
|
var newweapon = weapons[slot]
|
|
current_weapon = newweapon
|
|
current_weapon.visible = true
|
|
current_weapon.play_switch_sound()
|
|
current_weapon.connect("fired", self, "weapon_fired")
|
|
|
|
func play_footstep():
|
|
if ground_type in footstep_sounds:
|
|
var sound = footstep_sounds[ground_type][randi() % len(footstep_sounds[ground_type])]
|
|
Globals.play_sound(sound, Globals.Bus.Sounds, self)
|
|
|
|
func get_current_weapon_slot():
|
|
return get_weapon_slot(current_weapon)
|
|
|
|
func get_weapon_slot(weapon: Weapon):
|
|
var i = 0
|
|
for weap in weapons:
|
|
if weap == weapon:
|
|
return i
|
|
i+=1
|
|
return i
|
|
|
|
func create_weapon(weapon_scene: PackedScene):
|
|
var weapon = weapon_scene.instance()
|
|
if weapon is Weapon:
|
|
return weapon
|
|
else:
|
|
weapon.queue_free()
|
|
return null
|
|
|
|
func get_next_weapon_slot():
|
|
var current = get_current_weapon_slot()
|
|
if current + 1 < len(weapons):
|
|
return current + 1
|
|
else:
|
|
return 0
|
|
|
|
func switch_next_weapon_slot():
|
|
var next_slot = get_next_weapon_slot()
|
|
if next_slot == get_current_weapon_slot():
|
|
return
|
|
set_weapon_slot(next_slot)
|
|
|
|
func enter_water(force: float):
|
|
is_in_water = true
|
|
ground_type = GroundType.WATER
|
|
if abs(force) >= 50:
|
|
Globals.play_sound(water_splash_sound, Globals.Bus.Sounds, self)
|
|
|
|
func exit_water(force: float):
|
|
is_in_water = false
|
|
ground_type = GroundType.GRASS
|
|
|
|
func _ready():
|
|
if animation_tree_path:
|
|
animation_tree = get_node(animation_tree_path)
|
|
if has_marker:
|
|
marker = preload("res://ui/player_marker.tscn").instance()
|
|
head.add_child(marker)
|
|
marker.transform.origin = Vector3(0, 0.337, 0)
|
|
marker.mesh.size = Vector2.ONE * 5
|
|
|
|
func _process(delta):
|
|
if shoot_delay > 0:
|
|
shoot_delay -= delta
|
|
|
|
func _physics_process(delta):
|
|
if Engine.editor_hint:
|
|
return
|
|
|
|
walk(delta)
|
|
|
|
if not animation_tree:
|
|
return
|
|
|
|
# Blend nicely between animations
|
|
var anim_vector = Vector2(move_axis.y, move_axis.x)
|
|
var res = animation_blend_vector.linear_interpolate(anim_vector, delta*animation_blend_speed)
|
|
animation_tree.set("parameters/movement/blend_position", res)
|
|
animation_blend_vector = res
|
|
|
|
func fire_pressed():
|
|
if current_weapon and can_shoot_delay():
|
|
current_weapon.activate(Globals.target_position["position"])
|
|
|
|
func fire_released():
|
|
if current_weapon:
|
|
current_weapon.deactivate(Globals.target_position["position"])
|
|
|
|
func jump():
|
|
if can_jump and is_on_floor():
|
|
velocity.y = jump_height
|
|
emit_signal("jumped")
|
|
|
|
func set_sprint(sprint_state: bool):
|
|
if can_sprint:
|
|
sprinting = sprint_state
|
|
|
|
func weapon_fired():
|
|
shoot_delay = current_weapon.repeat_time
|
|
|
|
func walk(delta: float) -> void:
|
|
# Input
|
|
var direction = Vector3()
|
|
var aim: Basis = get_global_transform().basis
|
|
if move_axis.x >= 0.5:
|
|
direction -= aim.z
|
|
if move_axis.x <= -0.5:
|
|
direction += aim.z
|
|
if move_axis.y <= -0.5:
|
|
direction -= aim.x
|
|
if move_axis.y >= 0.5:
|
|
direction += aim.x
|
|
direction.y = 0
|
|
direction = direction.normalized()
|
|
|
|
# Apply Gravity
|
|
velocity.y -= gravity * delta
|
|
# if is_in_water:
|
|
# velocity.y = 0
|
|
|
|
# Sprint
|
|
var _speed: float
|
|
if sprinting:
|
|
# if can_sprint and move_axis.x == 1:
|
|
_speed = sprint_speed
|
|
else:
|
|
_speed = walk_speed
|
|
|
|
# Acceleration and Deacceleration
|
|
# where would the player go
|
|
var _temp_vel: Vector3 = velocity
|
|
_temp_vel.y = 0
|
|
var _target: Vector3 = direction * _speed
|
|
var _temp_accel: float
|
|
if direction.dot(_temp_vel) > 0:
|
|
_temp_accel = acceleration
|
|
else:
|
|
_temp_accel = deacceleration
|
|
if not is_on_floor():
|
|
_temp_accel *= air_control
|
|
# interpolation
|
|
_temp_vel = _temp_vel.linear_interpolate(_target, _temp_accel * delta)
|
|
velocity.x = _temp_vel.x
|
|
velocity.z = _temp_vel.z
|
|
# clamping (to stop on slopes)
|
|
if direction.dot(velocity) == 0:
|
|
var _vel_clamp := 1
|
|
if velocity.x < _vel_clamp and velocity.x > -_vel_clamp:
|
|
velocity.x = 0
|
|
if velocity.z < _vel_clamp and velocity.z > -_vel_clamp:
|
|
velocity.z = 0
|
|
|
|
# Move
|
|
velocity = move_and_slide(velocity, FLOOR_NORMAL, true, 4, deg2rad(floor_max_angle))
|
|
# velocity.y = move_and_slide_with_snap(velocity, _snap, FLOOR_NORMAL, true, 4, deg2rad(floor_max_angle)).y
|