279 lines
6.7 KiB
GDScript3
279 lines
6.7 KiB
GDScript3
|
extends Spatial
|
||
|
class_name NPC
|
||
|
|
||
|
export(Array, NodePath) var tasks : Array
|
||
|
|
||
|
export var modelSize = 1.0
|
||
|
export var spriteSize = 1.0
|
||
|
export var spriteOffset = 0.0
|
||
|
export var spriteFPS = 15
|
||
|
|
||
|
export var timeline = "Test"
|
||
|
|
||
|
var speed = 1.0
|
||
|
|
||
|
var taskNodes = []
|
||
|
var navAgent : NavigationAgent
|
||
|
|
||
|
var npcModel = null # animated npc model
|
||
|
var camera = null # player camera for sprite calculation
|
||
|
var action = "Idle" # current animation
|
||
|
var skipSprites = false # skips sprite calculation
|
||
|
var spriteFrame = 0
|
||
|
|
||
|
var shouldNavigate = false # Does the current task require navigation?
|
||
|
var isAggressive = false # should hunt player if he is spotted
|
||
|
var investigateSounds = false # should investigate sounds
|
||
|
|
||
|
# senses
|
||
|
var sightNear : bool = false
|
||
|
var sightFar : bool = false
|
||
|
var hearNear : bool = false
|
||
|
var hearFar : bool = false
|
||
|
|
||
|
var has_heard = null
|
||
|
var velocity = Vector3.ZERO
|
||
|
|
||
|
var targetPos
|
||
|
var direction
|
||
|
var navigate = false
|
||
|
|
||
|
var currentTask = null # The current task the NPC wants to do ( usually timed tasks )
|
||
|
var playerTask = null # when the. spotted player
|
||
|
var investigate : Spatial # A point of interest to investigate ( usually when the player made a sound )
|
||
|
var task = null # the actual task
|
||
|
|
||
|
func _ready():
|
||
|
$NPCSprite.texture = $NPCRenderer.get_texture()
|
||
|
$NPCRenderer.set_update_mode(Viewport.UPDATE_ONCE)
|
||
|
|
||
|
investigate = Spatial.new()
|
||
|
get_tree().current_scene.call_deferred("add_child", investigate)
|
||
|
|
||
|
navAgent = $NavigationAgent
|
||
|
navAgent.connect("velocity_computed", self, "_on_velocity_computed")
|
||
|
for i in tasks:
|
||
|
taskNodes.push_back(get_node(i))
|
||
|
|
||
|
func SetModel(model):
|
||
|
npcModel = model
|
||
|
camera = get_viewport().get_camera()
|
||
|
model.get_parent().remove_child(model)
|
||
|
$NPCRenderer.add_child(model)
|
||
|
model.scale_object_local(Vector3(modelSize, modelSize, modelSize))
|
||
|
$NPCSprite.scale_object_local(Vector3(spriteSize, spriteSize, spriteSize))
|
||
|
$NPCSprite.translate_object_local(Vector3(0, spriteOffset, 0))
|
||
|
|
||
|
$NPC_Name.name = name
|
||
|
|
||
|
|
||
|
func changeState(state):
|
||
|
action = state
|
||
|
if npcModel != null:
|
||
|
npcModel.setAnimation(state)
|
||
|
|
||
|
func _process(_delta):
|
||
|
if camera == null:
|
||
|
return
|
||
|
calcSprites()
|
||
|
|
||
|
moveNPC()
|
||
|
|
||
|
func _on_VisibilityEnabler_screen_entered():
|
||
|
skipSprites = false
|
||
|
|
||
|
func _on_VisibilityEnabler_screen_exited():
|
||
|
skipSprites = true
|
||
|
|
||
|
func calcSprites():
|
||
|
if skipSprites:
|
||
|
pass
|
||
|
|
||
|
var camPos = camera.global_transform.origin
|
||
|
camPos.y = 0
|
||
|
|
||
|
$Look.global_transform = global_transform
|
||
|
$Look.global_transform.origin.y = 0
|
||
|
|
||
|
$Look.look_at(camPos, Vector3.UP)
|
||
|
$Look.rotation_degrees.y = stepify($Look.rotation_degrees.y, 45.0)
|
||
|
|
||
|
$NPCRenderer/CameraRig.global_rotation = $Look.global_rotation
|
||
|
|
||
|
if npcModel != null:
|
||
|
npcModel.rotation = rotation
|
||
|
|
||
|
|
||
|
spriteFrame += 1
|
||
|
if spriteFrame % spriteFPS == 0:
|
||
|
spriteFrame = 0
|
||
|
$NPCRenderer.set_update_mode(Viewport.UPDATE_ONCE)
|
||
|
|
||
|
func Tick(hour, minute, _day):
|
||
|
for t in taskNodes:
|
||
|
if t.hour == hour and t.minute == minute:
|
||
|
currentTask = t
|
||
|
shouldNavigate = t.shouldNavigate
|
||
|
if t.changeAggression:
|
||
|
isAggressive = t.aggression
|
||
|
if t.changeInvestigation:
|
||
|
investigateSounds = t.shouldInvestigate
|
||
|
|
||
|
func _on_velocity_computed(new_velocity):
|
||
|
if task == null or targetPos == null:
|
||
|
return
|
||
|
|
||
|
velocity = new_velocity
|
||
|
global_translate(new_velocity)
|
||
|
|
||
|
global_transform.origin.y = targetPos.y #$RayCast.get_collision_point().y
|
||
|
|
||
|
look_at(targetPos, Vector3.UP)
|
||
|
|
||
|
|
||
|
##########
|
||
|
## SENSES
|
||
|
##########
|
||
|
|
||
|
func _on_HearingNear_body_entered(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
hearNear = true
|
||
|
func _on_HearingNear_body_exited(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
hearNear = false
|
||
|
|
||
|
func _on_HearingFar_body_entered(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
hearFar = true
|
||
|
func _on_HearingFar_body_exited(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
hearFar = false
|
||
|
|
||
|
func _on_SightNear_body_entered(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
sightNear = true
|
||
|
func _on_SightNear_body_exited(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
sightNear = false
|
||
|
|
||
|
func _on_SightFar_body_entered(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
sightFar = true
|
||
|
func _on_SightFar_body_exited(body):
|
||
|
if body.is_in_group("Player"):
|
||
|
sightFar = false
|
||
|
|
||
|
func canSeePlayer(lightLevel):
|
||
|
if not sightFar and not sightNear:
|
||
|
return false
|
||
|
|
||
|
var space_state = get_world().direct_space_state
|
||
|
var result = space_state.intersect_ray($Head.global_transform.origin, camera.global_transform.origin)
|
||
|
if result.size() > 0:
|
||
|
var col = result["collider"]
|
||
|
if col.is_in_group("Player"):
|
||
|
if col.lightLevel > lightLevel:
|
||
|
## Detected player
|
||
|
playerTask = col
|
||
|
return true
|
||
|
if playerTask != null:
|
||
|
investigate.global_transform = playerTask.global_transform
|
||
|
playerTask = null
|
||
|
return false
|
||
|
|
||
|
###########
|
||
|
## REACTION
|
||
|
###########
|
||
|
|
||
|
func interact(_relate):
|
||
|
### TALKING TO NPC
|
||
|
|
||
|
#if behavior == null:
|
||
|
# return
|
||
|
#behavior.interact(relate)
|
||
|
pass
|
||
|
|
||
|
func heard(something):
|
||
|
if investigateSounds:
|
||
|
has_heard = something
|
||
|
|
||
|
func _on_InteractionDetection_body_entered(body):
|
||
|
if body.name == "Door" and velocity.length() > 0 and body.state == 0:
|
||
|
body.interact(self)
|
||
|
|
||
|
func _on_Tick_timeout():
|
||
|
# Update AI
|
||
|
|
||
|
investigate.global_translation != Vector3.ZERO
|
||
|
|
||
|
if isAggressive:
|
||
|
canSeePlayer(0.5)
|
||
|
if has_heard != null:
|
||
|
investigate.global_transform = has_heard.global_transform
|
||
|
has_heard = null
|
||
|
|
||
|
if playerTask != null:
|
||
|
task = playerTask
|
||
|
elif investigate.global_translation != Vector3.ZERO:
|
||
|
task = investigate
|
||
|
elif currentTask != null:
|
||
|
task = currentTask
|
||
|
else:
|
||
|
task = null
|
||
|
|
||
|
navigate = false
|
||
|
|
||
|
if task == null:
|
||
|
return
|
||
|
|
||
|
if task.has_method("get_transform"):
|
||
|
navAgent.set_target_location(task.get_transform().origin)
|
||
|
else:
|
||
|
navAgent.set_target_location(task.global_transform.origin)
|
||
|
|
||
|
navigate = not reachedTask()
|
||
|
|
||
|
func reachedTask():
|
||
|
if not task.has_method("waiting"):
|
||
|
return _reachedTarget()
|
||
|
#if task.should_align():
|
||
|
# global_transform = task.get_transform()
|
||
|
|
||
|
if task.waiting():
|
||
|
return false
|
||
|
if task.get_interaction() == null:
|
||
|
return true
|
||
|
if not task.get_interaction().has_method("interact"):
|
||
|
return true
|
||
|
|
||
|
task.get_interaction().interact(self)
|
||
|
|
||
|
if task.done():
|
||
|
task = null
|
||
|
currentTask = null
|
||
|
return true
|
||
|
else:
|
||
|
navAgent.set_target_location(task.get_transform().origin)
|
||
|
return false
|
||
|
|
||
|
func _reachedTarget():
|
||
|
if navAgent.is_navigation_finished():
|
||
|
velocity = Vector3.ZERO
|
||
|
navAgent.set_velocity(velocity)
|
||
|
return true
|
||
|
return false
|
||
|
|
||
|
func moveNPC():
|
||
|
if not navigate:
|
||
|
return
|
||
|
|
||
|
if task == currentTask and not shouldNavigate:
|
||
|
return
|
||
|
|
||
|
targetPos = navAgent.get_next_location()
|
||
|
targetPos.y = 0
|
||
|
direction = global_transform.origin.direction_to(targetPos)
|
||
|
var v = direction * speed * get_process_delta_time()
|
||
|
|
||
|
navAgent.set_velocity(v)
|