1034 lines
40 KiB
Lua
1034 lines
40 KiB
Lua
-- Lua Third person camera and character controller script
|
|
-- This script will load a simple scene with a character that can be controlled
|
|
--
|
|
-- Tips:
|
|
-- - Character models that you use should have a humanoid rig (this is created automatically from VRM, Makehuman and Mixamo model imports)
|
|
-- - The level objects should be tagged as "Navmesh" because the characters will collide with only those, and these are optimized for intersections
|
|
-- - To set the player start position, you can put a metadata component to the level scene and set it to "Player" preset
|
|
-- - To add NPCs you can put a metadata component to the level scene and set it to "NPC" preset
|
|
-- - To specify which model a character in the level (Player or NPC) uses, add a string property to its metadata named "name" and its value is the name of the character, for example name = johnny will use assets/johnny.wiscene
|
|
-- - To specify which animation set a character should use, add "animset" string property to metadata, the value should be the name of the animset which will be concatenated to anim names with "_". For example: animset = male will load animations like "walk_male", etc. if available, else fall back to "walk"
|
|
-- - To set patrol route for an NPC character, add a "waypoint" named string property to its metadata and the value is a name of the target waypoint entity. Waypoints can be chained by having a metadata for them and having "waypoint" string properties on each.
|
|
-- - To set conversation dialog for an NPC character, add a "dialog" named string property to its metadata and the value is a name of the lua file in assets/dialogs/. For example dialog = hello will make the character use the assets/dialogs/hello.lua dialog script
|
|
--
|
|
-- CONTROLS:
|
|
-- WASD/left thumbstick: walk/jog
|
|
-- Left Control: slow walk
|
|
-- Left Shift/left shoulder button: jog -> run
|
|
-- SPACE/gamepad X/gamepad button 3: jump
|
|
-- Right Mouse Button/Right thumbstick: rotate camera
|
|
-- Scoll middle mouse/Left-Right triggers: adjust camera distance
|
|
-- H: toggle debug draw
|
|
-- L: toggle framerate lock
|
|
-- ESCAPE key: quit
|
|
-- R: reload script
|
|
-- ENTER: interaction
|
|
|
|
local debug = false -- press H to toggle
|
|
local framerate_lock = false
|
|
local framerate_lock_target = 20
|
|
local slope_threshold = 0.2 -- How much slopeiness will cause character to slide down instead of standing on it
|
|
local gravity = -30
|
|
local dynamic_voxelization = false -- Set to true to revoxelize navigation every frame
|
|
|
|
dofile(script_dir() .. "/assets/conversation.lua") -- load conversation system logic from separate file
|
|
conversation = Conversation() -- conversation will be accessible from dialog scripts
|
|
|
|
local scene = GetScene()
|
|
|
|
local Layers = {
|
|
Player = 1 << 0,
|
|
NPC = 1 << 1,
|
|
}
|
|
States = {
|
|
IDLE = "idle",
|
|
WALK = "walk",
|
|
JOG = "jog",
|
|
RUN = "run",
|
|
JUMP = "jump",
|
|
SWIM_IDLE = "swim_idle",
|
|
SWIM = "swim",
|
|
DANCE = "dance",
|
|
WAVE = "wave"
|
|
}
|
|
|
|
Mood = {
|
|
Neutral = ExpressionPreset.Neutral,
|
|
Happy = ExpressionPreset.Happy,
|
|
Angry = ExpressionPreset.Angry,
|
|
Sad = ExpressionPreset.Sad,
|
|
Relaxed = ExpressionPreset.Relaxed,
|
|
Surprised = ExpressionPreset.Surprised
|
|
}
|
|
|
|
player = nil
|
|
npcs = {}
|
|
|
|
local enable_footprints = true
|
|
local footprint_texture = Texture(script_dir() .. "assets/footprint.dds")
|
|
local footprints = {}
|
|
|
|
local character_capsules = {}
|
|
local voxelgrid = VoxelGrid(128,32,128)
|
|
voxelgrid.SetVoxelSize(0.25)
|
|
voxelgrid.SetCenter(Vector(0,0.1,0))
|
|
|
|
local function Character(model_scene, start_transform, controllable, anim_scene, animset)
|
|
local self = {
|
|
model = INVALID_ENTITY,
|
|
target_rot_horizontal = 0,
|
|
target_rot_vertical = 0,
|
|
target_height = 0,
|
|
anims = {},
|
|
neck = INVALID_ENTITY,
|
|
head = INVALID_ENTITY,
|
|
left_hand = INVALID_ENTITY,
|
|
right_hand = INVALID_ENTITY,
|
|
left_foot = INVALID_ENTITY,
|
|
right_foot = INVALID_ENTITY,
|
|
left_toes = INVALID_ENTITY,
|
|
right_toes = INVALID_ENTITY,
|
|
savedPointerPos = Vector(),
|
|
walk_speed = 0.1,
|
|
jog_speed = 0.2,
|
|
run_speed = 0.4,
|
|
jump_speed = 8,
|
|
swim_speed = 0.2,
|
|
layerMask = ~0, -- layerMask will be used to filter collisions
|
|
scale = Vector(1, 1, 1),
|
|
rotation = Vector(0,math.pi,0),
|
|
position = Vector(),
|
|
ground_intersect = false,
|
|
controllable = true,
|
|
foot_placed_left = false,
|
|
foot_placed_right = false,
|
|
mood = Mood.Neutral,
|
|
mood_amount = 1,
|
|
expression = INVALID_ENTITY,
|
|
object_entities = {},
|
|
|
|
patrol_waypoints = {},
|
|
patrol_waypoints_original = {},
|
|
patrol_next = 0,
|
|
patrol_wait = 0,
|
|
patrol_reached = false,
|
|
hired = nil,
|
|
dialogs = {},
|
|
next_dialog = 1,
|
|
|
|
GetFacing = function(self)
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
return charactercomponent.GetFacingSmoothed()
|
|
end,
|
|
Jump = function(self,f)
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
charactercomponent.Jump(f)
|
|
self.state = States.JUMP
|
|
charactercomponent.StopAnimation() -- fix for jumping when jump anim didn't finish yet, instead I force prev anim to finish
|
|
end,
|
|
Turn = function(self,dir)
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
charactercomponent.Turn(dir)
|
|
end,
|
|
MoveDirection = function(self,dir)
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
local rotation_matrix = matrix.Multiply(matrix.RotationY(self.target_rot_horizontal), matrix.RotationX(self.target_rot_vertical))
|
|
dir = vector.TransformNormal(dir.Normalize(), rotation_matrix)
|
|
dir.SetY(0)
|
|
charactercomponent.Turn(dir.Normalize())
|
|
local speed = self.walk_speed
|
|
if self.state == States.WALK then
|
|
speed = self.walk_speed
|
|
elseif self.state == States.JOG then
|
|
speed = self.jog_speed
|
|
elseif self.state == States.RUN then
|
|
speed = self.run_speed
|
|
elseif self.state == States.SWIM then
|
|
speed = self.swim_speed
|
|
end
|
|
charactercomponent.Move(self:GetFacing():Multiply(Vector(speed,speed,speed)))
|
|
end,
|
|
SetAnimationAmount = function(self,amount)
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
charactercomponent.SetAnimationAmount(amount)
|
|
end,
|
|
|
|
Update = function(self)
|
|
|
|
local dt = getDeltaTime()
|
|
|
|
local charactercomponent = scene.Component_GetCharacter(self.model)
|
|
self.ground_intersect = charactercomponent.IsGrounded()
|
|
self.position = charactercomponent.GetPositionInterpolated()
|
|
local capsule = charactercomponent.GetCapsule()
|
|
character_capsules[self.model] = capsule
|
|
--DrawCapsule(capsule)
|
|
|
|
local humanoid = scene.Component_GetHumanoid(self.humanoid)
|
|
humanoid.SetLookAtEnabled(false)
|
|
|
|
if self.controllable then
|
|
-- Camera target control:
|
|
|
|
-- read from gamepad analog stick:
|
|
local diff = input.GetAnalog(GAMEPAD_ANALOG_THUMBSTICK_R)
|
|
diff = vector.Multiply(diff, dt * 4)
|
|
|
|
-- read from mouse:
|
|
if(input.Down(MOUSE_BUTTON_RIGHT)) then
|
|
local mouseDif = input.GetPointerDelta()
|
|
mouseDif = mouseDif:Multiply(0.016 * 0.1) -- not using delta time for delta mouse because it's not a diff from prev frame but hw delta movement reading!
|
|
diff = vector.Add(diff, mouseDif)
|
|
input.SetPointer(self.savedPointerPos)
|
|
input.HidePointer(true)
|
|
else
|
|
self.savedPointerPos = input.GetPointer()
|
|
input.HidePointer(false)
|
|
end
|
|
|
|
self.target_rot_horizontal = self.target_rot_horizontal + diff.GetX()
|
|
self.target_rot_vertical = math.clamp(self.target_rot_vertical + diff.GetY(), -math.pi * 0.3, math.pi * 0.4) -- vertical camers limits
|
|
end
|
|
|
|
-- Blend to current mood, blend out other moods:
|
|
local expression = scene.Component_GetExpression(self.expression)
|
|
if expression ~= nil then
|
|
for i,preset in pairs(Mood) do
|
|
local weight = expression.GetPresetWeight(preset)
|
|
if preset == self.mood then
|
|
weight = math.lerp(weight, self.mood_amount, 0.1)
|
|
else
|
|
weight = math.lerp(weight, 0, 0.1)
|
|
end
|
|
expression.SetPresetWeight(preset, weight)
|
|
end
|
|
end
|
|
|
|
-- state and animation update
|
|
charactercomponent.PlayAnimation(self.anims[self.state])
|
|
if self.state == States.JUMP then
|
|
if charactercomponent.IsAnimationEnded() then
|
|
self.state = States.IDLE
|
|
end
|
|
else
|
|
if self.ground_intersect and self.state ~= States.DANCE and self.state ~= States.WAVE then
|
|
self.state = States.IDLE
|
|
end
|
|
end
|
|
|
|
if charactercomponent.IsSwimming() then
|
|
self.state = States.SWIM_IDLE
|
|
end
|
|
|
|
if self.controllable then
|
|
if not backlog_isactive() then
|
|
-- Movement input:
|
|
local lookDir = Vector()
|
|
if(input.Down(KEYBOARD_BUTTON_LEFT) or input.Down(string.byte('A'))) then
|
|
lookDir = lookDir:Add( Vector(-1) )
|
|
end
|
|
if(input.Down(KEYBOARD_BUTTON_RIGHT) or input.Down(string.byte('D'))) then
|
|
lookDir = lookDir:Add( Vector(1) )
|
|
end
|
|
|
|
if(input.Down(KEYBOARD_BUTTON_UP) or input.Down(string.byte('W'))) then
|
|
lookDir = lookDir:Add( Vector(0,0,1) )
|
|
end
|
|
if(input.Down(KEYBOARD_BUTTON_DOWN) or input.Down(string.byte('S'))) then
|
|
lookDir = lookDir:Add( Vector(0,0,-1) )
|
|
end
|
|
|
|
local analog = input.GetAnalog(GAMEPAD_ANALOG_THUMBSTICK_L)
|
|
lookDir = vector.Add(lookDir, Vector(analog.GetX(), 0, analog.GetY()))
|
|
|
|
-- Apply movement:
|
|
if(lookDir.Length() > 0) then
|
|
if self.state == States.SWIM_IDLE then
|
|
self.state = States.SWIM
|
|
self:MoveDirection(lookDir)
|
|
elseif self.ground_intersect or charactercomponent.IsWallIntersect() then
|
|
if self.ground_intersect then
|
|
local analog_len = vector.Length(analog)
|
|
if input.Down(KEYBOARD_BUTTON_LCONTROL) or (analog_len > 0 and analog_len < 0.75) then
|
|
self.state = States.WALK
|
|
else
|
|
if input.Down(KEYBOARD_BUTTON_LSHIFT) or input.Down(GAMEPAD_BUTTON_5) then
|
|
self.state = States.RUN
|
|
else
|
|
self.state = States.JOG
|
|
end
|
|
end
|
|
end
|
|
self:MoveDirection(lookDir)
|
|
end
|
|
end
|
|
|
|
if (input.Press(string.byte('J')) or input.Press(KEYBOARD_BUTTON_SPACE) or input.Press(GAMEPAD_BUTTON_3)) and self.ground_intersect then
|
|
self:Jump(self.jump_speed)
|
|
end
|
|
end
|
|
|
|
elseif not(conversation.override_input and conversation.character == self) then
|
|
|
|
-- NPC patrol behavior:
|
|
local patrol_count = len(self.patrol_waypoints)
|
|
if patrol_count > 0 then
|
|
local pos = self.position
|
|
local patrol = self.patrol_waypoints[self.patrol_next % patrol_count + 1]
|
|
local patrol_transform = scene.Component_GetTransform(patrol.entity)
|
|
if patrol_transform ~= nil then
|
|
local patrol_pos = patrol_transform.GetPosition()
|
|
local patrol_wait = patrol.wait or 0 -- default: 0
|
|
local patrol_vec = vector.Subtract(patrol_pos, pos)
|
|
patrol_vec.SetY(0)
|
|
local distance = patrol_vec.Length()
|
|
local patrol_dist = patrol.distance or 0.5 -- default : 0.5
|
|
local patrol_dist_threshold = patrol.distance_threshold or 0 -- default : 0
|
|
if distance < patrol_dist then
|
|
-- reached patrol waypoint:
|
|
self.patrol_reached = true
|
|
if self.state ~= States.SWIM_IDLE then
|
|
self.state = States.IDLE
|
|
end
|
|
if self.patrol_wait >= patrol_wait then
|
|
self.patrol_next = self.patrol_next + 1
|
|
else
|
|
self.patrol_wait = self.patrol_wait + dt
|
|
end
|
|
elseif self.patrol_reached then
|
|
-- threshold to avoid jerking between moving and reached destination:
|
|
-- Between distance and distance + threshold, the character will not advance towards waypoint
|
|
-- Useful for dynamic waypoint, such as following behaviour
|
|
if distance > patrol_dist + patrol_dist_threshold then
|
|
self.patrol_reached = false
|
|
end
|
|
else
|
|
-- move towards patrol waypoint:
|
|
charactercomponent.SetPathGoal(patrol_pos, voxelgrid) -- this is deferred into scene update
|
|
local pathquery = charactercomponent.GetPathQuery()
|
|
pathquery.SetAgentHeight(3)
|
|
if pathquery.IsSuccessful() then
|
|
-- If there is a valid pathfinding result for waypoint, replace heading direction by that:
|
|
patrol_vec = vector.Subtract(pathquery.GetNextWaypoint(), pos)
|
|
patrol_vec.SetY(0)
|
|
end
|
|
if debug then
|
|
DrawPathQuery(pathquery)
|
|
end
|
|
self.patrol_wait = 0
|
|
-- check if it's blocked by player collision:
|
|
local capsule = charactercomponent.GetCapsule()
|
|
local forward_offset = vector.Multiply(patrol_vec.Normalize(), 0.5)
|
|
capsule.SetBase(vector.Add(capsule.GetBase(), forward_offset))
|
|
capsule.SetTip(vector.Add(capsule.GetTip(), forward_offset))
|
|
capsule.SetRadius(capsule.GetRadius() * 2)
|
|
|
|
-- Manual check for blocked patrol:
|
|
-- This is manually done because it is difficult to filter out current NPC among other NPCs
|
|
-- Because NPCs can also be blocked by other NPCs but not themselves
|
|
-- And introducing custom layer for every NPC is not very good
|
|
local blocked = false
|
|
for other_entity,other_capsule in pairs(character_capsules) do
|
|
if other_entity ~= self.model then
|
|
if capsule.Intersects(other_capsule) then
|
|
blocked = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if blocked then
|
|
if debug then
|
|
DrawDebugText("patrol blocked", vector.Add(capsule.GetTip(), Vector(0,0.1)), Vector(1,0,0,1), 0.08, DEBUG_TEXT_DEPTH_TEST | DEBUG_TEXT_CAMERA_FACING)
|
|
DrawCapsule(capsule, Vector(1,0,0,1))
|
|
end
|
|
else
|
|
if self.state == States.SWIM_IDLE then
|
|
self.state = States.SWIM
|
|
else
|
|
self.state = patrol.state or States.WALK -- default : WALK
|
|
end
|
|
self:MoveDirection(patrol_vec)
|
|
if debug then
|
|
DrawDebugText("patrol blocking check", vector.Add(capsule.GetTip(), Vector(0,0.1)), Vector(0,1,0,1), 0.08, DEBUG_TEXT_DEPTH_TEST | DEBUG_TEXT_CAMERA_FACING)
|
|
DrawCapsule(capsule, Vector(0,1,0,1))
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- If camera is inside character capsule, fade out the character, otherwise fade in:
|
|
if capsule.Intersects(GetCamera().GetPosition()) then
|
|
for i,entity in ipairs(self.object_entities) do
|
|
local object = scene.Component_GetObject(entity)
|
|
local color = object.GetColor()
|
|
local opacity = color.GetW()
|
|
opacity = math.lerp(opacity, 0, 0.1)
|
|
color.SetW(opacity)
|
|
object.SetColor(color)
|
|
end
|
|
else
|
|
for i,entity in ipairs(self.object_entities) do
|
|
local object = scene.Component_GetObject(entity)
|
|
local color = object.GetColor()
|
|
local opacity = color.GetW()
|
|
opacity = math.lerp(opacity, 1, 0.1)
|
|
color.SetW(opacity)
|
|
object.SetColor(color)
|
|
end
|
|
end
|
|
|
|
end,
|
|
|
|
Update_Footprints = function(self)
|
|
local radius = 0.1
|
|
|
|
-- Left footprint:
|
|
local capsule = Capsule(scene.Component_GetTransform(self.left_foot).GetPosition(), scene.Component_GetTransform(self.left_toes).GetPosition(), 0.1)
|
|
--DrawCapsule(capsule, Vector(1,0,0,1))
|
|
local collEntity,collPos = scene.Intersects(capsule, FILTER_NAVIGATION_MESH)
|
|
if collEntity ~= INVALID_ENTITY then
|
|
if not self.foot_placed_left then
|
|
self.foot_placed_left = true
|
|
local entity = CreateEntity()
|
|
local transform = scene.Component_CreateTransform(entity)
|
|
local material = scene.Component_CreateMaterial(entity)
|
|
local decal = scene.Component_CreateDecal(entity)
|
|
local layer = scene.Component_CreateLayer(entity)
|
|
transform.MatrixTransform(matrix.LookTo(Vector(),self:GetFacing()):Inverse())
|
|
transform.Rotate(Vector(math.pi * 0.5))
|
|
transform.Scale(0.1)
|
|
transform.Translate(collPos)
|
|
material.SetTexture(TextureSlot.BASECOLORMAP, footprint_texture)
|
|
material.SetBaseColor(Vector(0.1,0.1,0.1,1))
|
|
decal.SetSlopeBlendPower(2)
|
|
layer.SetLayerMask(~(Layers.Player | Layers.NPC))
|
|
scene.Component_Attach(entity, collEntity)
|
|
table.insert(footprints, entity)
|
|
end
|
|
else
|
|
self.foot_placed_left = false
|
|
end
|
|
|
|
-- Right footprint:
|
|
local capsule = Capsule(scene.Component_GetTransform(self.right_foot).GetPosition(), scene.Component_GetTransform(self.right_toes).GetPosition(), 0.1)
|
|
--DrawCapsule(capsule, Vector(1,0,0,1))
|
|
local collEntity,collPos = scene.Intersects(capsule, FILTER_NAVIGATION_MESH)
|
|
if collEntity ~= INVALID_ENTITY then
|
|
if not self.foot_placed_right then
|
|
self.foot_placed_right = true
|
|
local entity = CreateEntity()
|
|
local transform = scene.Component_CreateTransform(entity)
|
|
local material = scene.Component_CreateMaterial(entity)
|
|
local decal = scene.Component_CreateDecal(entity)
|
|
local layer = scene.Component_CreateLayer(entity)
|
|
transform.MatrixTransform(matrix.LookTo(Vector(),self:GetFacing()):Inverse())
|
|
transform.Rotate(Vector(math.pi * 0.5))
|
|
transform.Scale(0.1)
|
|
transform.Translate(collPos)
|
|
material.SetTexture(TextureSlot.BASECOLORMAP, footprint_texture)
|
|
material.SetTexMulAdd(Vector(-1, 1, 0, 0)) -- flip left footprint texture to be right
|
|
material.SetBaseColor(Vector(0.1,0.1,0.1,1))
|
|
decal.SetSlopeBlendPower(2)
|
|
layer.SetLayerMask(~(Layers.Player | Layers.NPC))
|
|
scene.Component_Attach(entity, collEntity)
|
|
table.insert(footprints, entity)
|
|
end
|
|
else
|
|
self.foot_placed_right = false
|
|
end
|
|
end,
|
|
|
|
SetPatrol = function(self, patrol)
|
|
self.patrol_waypoints_original = patrol
|
|
self.patrol_waypoints = patrol
|
|
end,
|
|
ReturnToPatrol = function(self) -- return to original patrol route
|
|
self.patrol_waypoints = self.patrol_waypoints_original
|
|
end,
|
|
|
|
Follow = function(self, leader)
|
|
self.hired = leader
|
|
end,
|
|
Unfollow = function(self)
|
|
self.hired = nil
|
|
self.patrol_waypoints = {}
|
|
end,
|
|
|
|
}
|
|
|
|
-- Character initialization:
|
|
self.position = start_transform.GetPosition()
|
|
local facing = vector.Rotate(start_transform.GetForward(), vector.QuaternionFromRollPitchYaw(self.rotation))
|
|
self.controllable = controllable
|
|
if controllable then
|
|
self.layerMask = Layers.Player
|
|
self.target_rot_horizontal = vector.GetAngle(Vector(0,0,1), facing, Vector(0,1,0)) -- only modify camera rot for player
|
|
else
|
|
self.layerMask = Layers.NPC
|
|
end
|
|
|
|
-- Note: we instantiate the model_scene into the main scene, start_transform stores a component pointer which gets invalidated after this!!
|
|
self.model = scene.Instantiate(model_scene, true)
|
|
|
|
local charactercomponent = scene.Component_CreateCharacter(self.model) -- some character features will be handled by the built-in CharacterComponent in Wicked Engine
|
|
charactercomponent.SetPosition(self.position)
|
|
charactercomponent.SetFacing(facing)
|
|
charactercomponent.SetWidth(0.3)
|
|
charactercomponent.SetHeight(1.8)
|
|
if controllable then
|
|
charactercomponent.SetTurningSpeed(9)
|
|
else
|
|
charactercomponent.SetTurningSpeed(7) -- Set NPC to turn slower because when we approach it for conversation, fast turning looks bad
|
|
end
|
|
--charactercomponent.SetFootPlacementEnabled(false)
|
|
|
|
local layer = scene.Component_GetLayer(self.model)
|
|
layer.SetLayerMask(self.layerMask)
|
|
|
|
self.state = States.IDLE
|
|
|
|
for i,entity in ipairs(scene.Entity_GetHumanoidArray()) do
|
|
if scene.Entity_IsDescendant(entity, self.model) then
|
|
self.humanoid = entity
|
|
local humanoid = scene.Component_GetHumanoid(self.humanoid)
|
|
humanoid.SetLookAtEnabled(false)
|
|
self.neck = humanoid.GetBoneEntity(HumanoidBone.Neck)
|
|
self.head = humanoid.GetBoneEntity(HumanoidBone.Head)
|
|
self.left_hand = humanoid.GetBoneEntity(HumanoidBone.LeftHand)
|
|
self.right_hand = humanoid.GetBoneEntity(HumanoidBone.RightHand)
|
|
self.left_foot = humanoid.GetBoneEntity(HumanoidBone.LeftFoot)
|
|
self.right_foot = humanoid.GetBoneEntity(HumanoidBone.RightFoot)
|
|
self.left_toes = humanoid.GetBoneEntity(HumanoidBone.LeftToes)
|
|
self.right_toes = humanoid.GetBoneEntity(HumanoidBone.RightToes)
|
|
break
|
|
end
|
|
end
|
|
for i,entity in ipairs(scene.Entity_GetExpressionArray()) do
|
|
if scene.Entity_IsDescendant(entity, self.model) then
|
|
self.expression = entity
|
|
|
|
-- Set up some overrides to avoid bad looking expression combinations:
|
|
local expression = scene.Component_GetExpression(self.expression)
|
|
expression.SetPresetOverrideBlink(ExpressionPreset.Happy, ExpressionOverride.Block)
|
|
expression.SetPresetOverrideMouth(ExpressionPreset.Happy, ExpressionOverride.Blend)
|
|
expression.SetPresetOverrideBlink(ExpressionPreset.Surprised, ExpressionOverride.Block)
|
|
end
|
|
end
|
|
|
|
-- Enable wetmap for all objects of character, so it can get wet in the ocean (if the weather has it):
|
|
for i,entity in ipairs(scene.Entity_GetObjectArray()) do
|
|
if scene.Entity_IsDescendant(entity, self.model) then
|
|
local object = scene.Component_GetObject(entity)
|
|
object.SetWetmapEnabled(true)
|
|
table.insert(self.object_entities, entity) -- save object array for later use
|
|
end
|
|
end
|
|
|
|
self.root = self.humanoid
|
|
|
|
scene.ResetPose(self.model)
|
|
|
|
-- The base animation entities are queried from the anim_scene, they are not yet tergeting our character:
|
|
self.anims[States.IDLE] = anim_scene.Entity_FindByName("idle")
|
|
self.anims[States.WALK] = anim_scene.Entity_FindByName("walk")
|
|
self.anims[States.JOG] = anim_scene.Entity_FindByName("jog")
|
|
self.anims[States.RUN] = anim_scene.Entity_FindByName("run")
|
|
self.anims[States.JUMP] = anim_scene.Entity_FindByName("jump")
|
|
self.anims[States.SWIM_IDLE] = anim_scene.Entity_FindByName("swim_idle")
|
|
self.anims[States.SWIM] = anim_scene.Entity_FindByName("swim")
|
|
self.anims[States.DANCE] = anim_scene.Entity_FindByName("dance")
|
|
self.anims[States.WAVE] = anim_scene.Entity_FindByName("wave")
|
|
|
|
-- Retarget animation entities onto the character, and add them to the CharacterComponent:
|
|
-- Also see if a requested animation set (animset) is available for each animation, and use that instead if yes
|
|
animset = "_" .. animset
|
|
for state,anim in pairs(self.anims) do
|
|
local anim_name_component = anim_scene.Component_GetName(anim)
|
|
if anim_name_component ~= nil then
|
|
local anim_set_name = anim_name_component.GetName() .. animset
|
|
local anim_set_entity = anim_scene.Entity_FindByName(anim_set_name)
|
|
if anim_set_entity ~= INVALID_ENTITY then
|
|
anim = anim_set_entity -- if there is animation with requested animset, replace default anim to that
|
|
end
|
|
self.anims[state] = scene.RetargetAnimation(self.humanoid, anim, false, anim_scene)
|
|
charactercomponent.AddAnimation(self.anims[state]) --
|
|
end
|
|
end
|
|
|
|
local model_transform = scene.Component_GetTransform(self.model)
|
|
model_transform.ClearTransform()
|
|
model_transform.Scale(self.scale)
|
|
model_transform.Rotate(self.rotation)
|
|
model_transform.Translate(self.position)
|
|
model_transform.UpdateTransform()
|
|
|
|
self.target_height = scene.Component_GetTransform(self.neck).GetPosition().GetY()
|
|
|
|
return self
|
|
end
|
|
|
|
-- Interaction between two characters:
|
|
local ResolveCharacters = function(characterA, characterB)
|
|
local humanoidA = scene.Component_GetHumanoid(characterA.humanoid)
|
|
local humanoidB = scene.Component_GetHumanoid(characterB.humanoid)
|
|
if humanoidA ~= INVALID_ENTITY and humanoidB ~= INVALID_ENTITY then
|
|
local headA = scene.Component_GetTransform(characterA.head).GetPosition()
|
|
local headB = scene.Component_GetTransform(characterB.head).GetPosition()
|
|
local distance = vector.Subtract(headA, headB).Length()
|
|
if distance < 1.5 then
|
|
humanoidA.SetLookAtEnabled(true)
|
|
humanoidB.SetLookAtEnabled(true)
|
|
humanoidA.SetLookAt(headB)
|
|
humanoidB.SetLookAt(headA)
|
|
|
|
local facing_amount = vector.Dot(characterB:GetFacing(), vector.Subtract(characterA.position, characterB.position).Normalize())
|
|
if #characterA.dialogs > 0 and conversation.state == ConversationState.Disabled and facing_amount > 0.8 then
|
|
if input.Press(KEYBOARD_BUTTON_ENTER) or input.Press(GAMEPAD_BUTTON_2) then
|
|
characterA:Turn(vector.Subtract(headB, headA):Normalize())
|
|
conversation:Enter(characterA)
|
|
application.CrossFade(0.5)
|
|
end
|
|
DrawDebugText("", vector.Add(headA, Vector(0,0.4)), Vector(1,1,1,1), 0.1, DEBUG_TEXT_DEPTH_TEST | DEBUG_TEXT_CAMERA_FACING)
|
|
end
|
|
end
|
|
|
|
if characterA.hired ~= nil then
|
|
characterA.patrol_waypoints = {
|
|
{
|
|
entity = characterA.hired.model,
|
|
distance = 2,
|
|
distance_threshold = 1
|
|
}
|
|
}
|
|
if distance < 5 then
|
|
if characterA.hired.state == States.JOG or characterA.hired.state == States.RUN then
|
|
characterA.patrol_waypoints[1].state = States.JOG
|
|
else
|
|
characterA.patrol_waypoints[1].state = States.WALK
|
|
end
|
|
elseif distance < 10 then
|
|
characterA.patrol_waypoints[1].state = States.JOG
|
|
else
|
|
characterA.patrol_waypoints[1].state = States.RUN
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Third person camera controller class:
|
|
local function ThirdPersonCamera(character)
|
|
local self = {
|
|
camera = INVALID_ENTITY,
|
|
character = nil,
|
|
side_offset = 0.2,
|
|
rest_distance = 1,
|
|
rest_distance_new = 1,
|
|
min_distance = 0.5,
|
|
zoom_speed = 0.3,
|
|
target_rot_horizontal = 0,
|
|
target_rot_vertical = 0,
|
|
target_height = 1,
|
|
|
|
Create = function(self, character)
|
|
self.character = character
|
|
|
|
self.camera = CreateEntity()
|
|
local camera_transform = scene.Component_CreateTransform(self.camera)
|
|
end,
|
|
|
|
Update = function(self)
|
|
if self.character == nil then
|
|
return
|
|
end
|
|
|
|
-- Mouse scroll or gamepad triggers will move the camera distance:
|
|
local scroll = input.GetPointer().GetZ() -- pointer.z is the mouse wheel delta this frame
|
|
scroll = scroll + input.GetAnalog(GAMEPAD_ANALOG_TRIGGER_R).GetX()
|
|
scroll = scroll - input.GetAnalog(GAMEPAD_ANALOG_TRIGGER_L).GetX()
|
|
scroll = scroll * self.zoom_speed
|
|
self.rest_distance_new = math.max(self.rest_distance_new - scroll, self.min_distance) -- do not allow too close using max
|
|
self.rest_distance = math.lerp(self.rest_distance, self.rest_distance_new, 0.1) -- lerp will smooth out the zooming
|
|
|
|
-- This will allow some smoothing for certain movements of camera target:
|
|
local charactercomponent = scene.Component_GetCharacter(self.character.model)
|
|
local character_position = charactercomponent.GetPositionInterpolated()
|
|
self.target_rot_horizontal = math.lerp(self.target_rot_horizontal, self.character.target_rot_horizontal, 0.1)
|
|
self.target_rot_vertical = math.lerp(self.target_rot_vertical, self.character.target_rot_vertical, 0.1)
|
|
self.target_height = math.lerp(self.target_height, character_position.GetY() + self.character.target_height + charactercomponent.GetFootOffset(), 0.1)
|
|
|
|
local camera_transform = scene.Component_GetTransform(self.camera)
|
|
local target_transform = TransformComponent()
|
|
target_transform.Translate(Vector(character_position.GetX(), 0, character_position.GetZ()))
|
|
target_transform.Translate(Vector(0, self.target_height))
|
|
target_transform.Rotate(Vector(self.target_rot_vertical, self.target_rot_horizontal))
|
|
target_transform.UpdateTransform()
|
|
|
|
-- First calculate the rest orientation (transform) of the camera:
|
|
local mat = matrix.Translation(Vector(self.side_offset, 0, -self.rest_distance))
|
|
mat = matrix.Multiply(mat, target_transform.GetMatrix())
|
|
camera_transform.ClearTransform()
|
|
camera_transform.MatrixTransform(mat)
|
|
camera_transform.UpdateTransform()
|
|
|
|
|
|
-- Camera collision:
|
|
|
|
-- Compute the relation vectors between camera and target:
|
|
local camPos = camera_transform.GetPosition()
|
|
local targetPos = target_transform.GetPosition()
|
|
local camDistance = vector.Subtract(camPos, targetPos).Length()
|
|
|
|
-- These will store the closest collision distance and required camera position:
|
|
local bestDistance = camDistance
|
|
local bestPos = camPos
|
|
local camera = GetCamera()
|
|
|
|
-- Update global camera matrices for rest position:
|
|
camera.TransformCamera(camera_transform)
|
|
camera.UpdateCamera()
|
|
|
|
-- Cast rays from target to clip space points on the camera near plane to avoid clipping through objects:
|
|
local unproj = camera.GetInvViewProjection() -- camera matrix used to unproject from clip space to world space
|
|
local clip_coords = {
|
|
Vector(0,0,1,1), -- center
|
|
Vector(-1,-1,1,1), -- bottom left
|
|
Vector(-1,1,1,1), -- top left
|
|
Vector(1,-1,1,1), -- bottom right
|
|
Vector(1,1,1,1), -- top right
|
|
}
|
|
for i,coord in ipairs(clip_coords) do
|
|
local corner = vector.TransformCoord(coord, unproj)
|
|
local target_to_corner = vector.Subtract(corner, targetPos)
|
|
local corner_to_campos = vector.Subtract(camPos, corner)
|
|
local TMin = 0
|
|
local TMax = target_to_corner.Length() -- optimization: limit the ray tracing distance
|
|
|
|
local ray = Ray(targetPos, target_to_corner.Normalize(), TMin, TMax)
|
|
|
|
local collision_layer = ~(Layers.Player | Layers.NPC) -- specifies that neither NPC, nor Player can collide with camera
|
|
local collObj,collPos,collNor = scene.Intersects(ray, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, collision_layer)
|
|
if(collObj ~= INVALID_ENTITY) then
|
|
-- It hit something, see if it is between the player and camera:
|
|
local collDiff = vector.Subtract(collPos, targetPos)
|
|
local collDist = collDiff.Length()
|
|
if(collDist > 0 and collDist < bestDistance) then
|
|
bestDistance = collDist
|
|
bestPos = vector.Add(collPos, corner_to_campos)
|
|
--DrawPoint(collPos, 0.1, Vector(1,0,0,1))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- We have the best candidate for new camera position now, so offset the camera with the delta between the old and new camera position:
|
|
local collision_offset = vector.Subtract(bestPos, camPos)
|
|
mat = matrix.Multiply(mat, matrix.Translation(collision_offset))
|
|
camera_transform.ClearTransform()
|
|
camera_transform.MatrixTransform(mat)
|
|
camera_transform.UpdateTransform()
|
|
--DrawPoint(bestPos, 0.1, Vector(1,1,0,1))
|
|
|
|
-- Feed back camera after collision:
|
|
camera.TransformCamera(camera_transform)
|
|
camera.UpdateCamera()
|
|
|
|
end,
|
|
}
|
|
|
|
self:Create(character)
|
|
return self
|
|
end
|
|
|
|
runProcess(function()
|
|
|
|
-- We will override the render path so we can invoke the script from Editor and controls won't collide with editor scripts
|
|
-- Also save the active component that we can restore when ESCAPE is pressed
|
|
local prevPath = application.GetActivePath()
|
|
local path = RenderPath3D()
|
|
local loadingscreen = LoadingScreen()
|
|
|
|
--path.SetLightShaftsEnabled(true)
|
|
path.SetLightShaftsStrength(0.01)
|
|
path.SetAO(AO_MSAO)
|
|
path.SetAOPower(0.25)
|
|
--path.SetOutlineEnabled(true)
|
|
path.SetOutlineThreshold(0.11)
|
|
path.SetOutlineThickness(1.7)
|
|
path.SetOutlineColor(0,0,0,0.6)
|
|
path.SetBloomThreshold(5)
|
|
SetCapsuleShadowEnabled(true)
|
|
SetCapsuleShadowFade(0.4)
|
|
SetCapsuleShadowAngle(math.pi * 0.15)
|
|
|
|
--application.SetInfoDisplay(false)
|
|
--application.SetFPSDisplay(true)
|
|
--path.SetResolutionScale(0.75)
|
|
--path.SetFSR2Enabled(true)
|
|
--path.SetFSR2Preset(FSR2_Preset.Performance)
|
|
--SetProfilerEnabled(true)
|
|
SetDebugEnvProbesEnabled(false)
|
|
SetGridHelperEnabled(false)
|
|
SetDebugCamerasEnabled(false)
|
|
|
|
-- Configure a simple loading progress bar:
|
|
local loadingbar = Sprite()
|
|
loadingbar.SetMaskTexture(texturehelper.CreateGradientTexture(
|
|
GradientType.Linear,
|
|
2048, 1,
|
|
Vector(0, 0), Vector(1, 0),
|
|
GradientFlags.Inverse,
|
|
"111r"
|
|
))
|
|
local loadingbarparams = loadingbar.GetParams()
|
|
loadingbarparams.SetColor(Vector(1,0.2,0.2,1))
|
|
loadingbarparams.SetBlendMode(BLENDMODE_ALPHA)
|
|
loadingscreen.AddSprite(loadingbar)
|
|
loadingscreen.SetBackgroundTexture(Texture(script_dir() .. "assets/loadingscreen.png"))
|
|
|
|
-- All LoadModel tasks are started by the LoadingScreen asynchronously, but we get back root Entity handles immediately:
|
|
local anim_scene = Scene()
|
|
loadingscreen.AddLoadModelTask(anim_scene, script_dir() .. "assets/animations.wiscene")
|
|
local loading_scene = Scene()
|
|
loadingscreen.AddLoadModelTask(loading_scene, script_dir() .. "assets/level.wiscene")
|
|
loadingscreen.AddRenderPathActivationTask(path, application, 0.5, 0, 0, 0, FadeType.FadeToColor)
|
|
application.SetActivePath(loadingscreen, 0.5, 0, 0, 0, FadeType.CrossFade) -- activate and switch to loading screen
|
|
|
|
-- Because we are in a runProcess, we can block loading screen like this while application is still running normally:
|
|
-- Meanwhile, we can update the progress bar sprite
|
|
while not loadingscreen.IsFinished() do
|
|
update() -- gives back control for application for one frame, script waits until next update() phase
|
|
local canvas = application.GetCanvas()
|
|
loadingbarparams.SetPos(Vector(50, canvas.GetLogicalHeight() * 0.8))
|
|
loadingbarparams.SetSize(Vector(canvas.GetLogicalWidth() - 100, 20))
|
|
local progress = 1 - loadingscreen.GetProgress() / 100.0
|
|
loadingbarparams.SetMaskAlphaRange(math.saturate(progress - 0.05), math.saturate(progress))
|
|
loadingbar.SetParams(loadingbarparams)
|
|
end
|
|
|
|
-- After loading finished, we clear the main scene, and merge loaded scene into it:
|
|
scene.Clear()
|
|
scene.Merge(loading_scene)
|
|
scene.Update(0)
|
|
|
|
if len(scene.Component_GetVoxelGridArray()) > 0 then
|
|
voxelgrid = scene.Component_GetVoxelGridArray()[1] -- take existing voxel grid from scene if available
|
|
else
|
|
scene.VoxelizeScene(voxelgrid, false, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~(Layers.Player | Layers.NPC)) -- generate a voxel grid in code, player and NPCs not included
|
|
end
|
|
|
|
-- Create characters from scene metadata components:
|
|
local character_scenes = {}
|
|
for i,entity in ipairs(scene.Entity_GetMetadataArray()) do
|
|
local metadata = scene.Component_GetMetadata(entity)
|
|
local transform = scene.Component_GetTransform(entity)
|
|
if metadata ~= nil and transform ~= nil then
|
|
|
|
-- Determine name of the placed character:
|
|
local name = "character" -- default name
|
|
if metadata.HasString("name") then
|
|
name = metadata.GetString("name")
|
|
end
|
|
|
|
-- Load character model if doesn't exist yet:
|
|
if character_scenes[name] == nil then
|
|
character_scenes[name] = Scene()
|
|
LoadModel(character_scenes[name], script_dir() .. "assets/" .. name .. ".wiscene")
|
|
end
|
|
|
|
if player == nil and metadata.GetPreset() == MetadataPreset.Player then
|
|
player = Character(character_scenes[name], transform, true, anim_scene, metadata.GetString("animset"))
|
|
end
|
|
if metadata.GetPreset() == MetadataPreset.NPC then
|
|
local npc = Character(character_scenes[name], transform, false, anim_scene, metadata.GetString("animset"))
|
|
|
|
-- add dialog tree if found:
|
|
if metadata.HasString("dialog") then
|
|
npc.dialogs = dofile(script_dir() .. "assets/dialogs/" .. metadata.GetString("dialog") .. ".lua")
|
|
end
|
|
|
|
-- Add patrol waypoints if found:
|
|
-- It will be looking for "waypoint" named string values in metadata components, and they can be chained by their value
|
|
local patrol_waypoints = {}
|
|
local visited = {} -- avoid infinite loop
|
|
while metadata ~= nil and metadata.HasString("waypoint") do
|
|
local waypoint_name = metadata.GetString("waypoint")
|
|
local waypoint_entity = scene.Entity_FindByName(waypoint_name)
|
|
if waypoint_entity ~= INVALID_ENTITY then
|
|
if visited[waypoint_entity] then
|
|
break
|
|
else
|
|
metadata = scene.Component_GetMetadata(waypoint_entity) -- chain waypoints
|
|
visited[waypoint_entity] = true
|
|
end
|
|
if metadata == nil then
|
|
break
|
|
end
|
|
local waypoint = {
|
|
entity = waypoint_entity,
|
|
wait = metadata.GetFloat("wait")
|
|
}
|
|
if metadata.HasString("state") then
|
|
waypoint.state = metadata.GetString("state")
|
|
end
|
|
table.insert(patrol_waypoints, waypoint) -- add waypoint to NPC
|
|
else
|
|
break
|
|
end
|
|
end
|
|
npc:SetPatrol(patrol_waypoints)
|
|
table.insert(npcs, npc) -- add NPC
|
|
end
|
|
end
|
|
end
|
|
|
|
-- if player was not created from a metadata component, create a default player:
|
|
if player == nil then
|
|
if character_scenes["character"] == nil then
|
|
character_scenes["character"] = Scene()
|
|
LoadModel(character_scenes["character"], script_dir() .. "assets/character.wiscene")
|
|
end
|
|
player = Character(character_scenes["character"], TransformComponent(), true, anim_scene, "")
|
|
end
|
|
|
|
local camera = ThirdPersonCamera(player)
|
|
|
|
path.AddFont(conversation.font)
|
|
path.AddFont(conversation.advance_font)
|
|
|
|
local help_text = "Wicked Engine Character demo (LUA)\n\n"
|
|
help_text = help_text .. "Controls:\n"
|
|
help_text = help_text .. "#############\n"
|
|
help_text = help_text .. "WASD/arrows/left analog stick: jog\n"
|
|
help_text = help_text .. "SHIFT/right shoulder button: jog -> run\n"
|
|
help_text = help_text .. "LEFT CONTROL: walk\n"
|
|
help_text = help_text .. "SPACE/gamepad X/gamepad button 3: Jump\n"
|
|
help_text = help_text .. "Right Mouse Button/Right thumbstick: rotate camera\n"
|
|
help_text = help_text .. "Scoll middle mouse/Left-Right triggers: adjust camera distance\n"
|
|
help_text = help_text .. "ESCAPE key: quit\n"
|
|
help_text = help_text .. "ENTER key: interact\n"
|
|
help_text = help_text .. "R: reload script\n"
|
|
help_text = help_text .. "H: toggle debug draw\n"
|
|
help_text = help_text .. "L: toggle framerate lock\n"
|
|
|
|
-- Main loop:
|
|
while true do
|
|
|
|
update()
|
|
|
|
player:Update()
|
|
for k,npc in pairs(npcs) do
|
|
npc:Update()
|
|
ResolveCharacters(npc, player)
|
|
end
|
|
|
|
if enable_footprints then
|
|
player:Update_Footprints()
|
|
for k,npc in pairs(npcs) do
|
|
npc:Update_Footprints()
|
|
end
|
|
-- Cap the max number of decals:
|
|
while #footprints > 100 do
|
|
local entity = footprints[1]
|
|
scene.Entity_Remove(entity)
|
|
table.remove(footprints, 1)
|
|
end
|
|
end
|
|
|
|
conversation:Update(path, scene, player)
|
|
player.controllable = not conversation.override_input
|
|
|
|
if dynamic_voxelization then
|
|
voxelgrid.ClearData()
|
|
scene.VoxelizeScene(voxelgrid, false, FILTER_NAVIGATION_MESH | FILTER_COLLIDER, ~(Layers.Player | Layers.NPC)) -- player and npc layers not included in voxelization
|
|
end
|
|
|
|
render() -- Camera update below will be happening after render phase is signaled, after update phase ended (this helps camera jitter as it will use the latest scene.Update() transforms doen by engine system)
|
|
|
|
if not conversation.override_input then
|
|
camera:Update()
|
|
end
|
|
|
|
-- Do some debug draw geometry:
|
|
|
|
DrawDebugText(help_text, Vector(0,2,2), Vector(1,1,1,1), 0.1, DEBUG_TEXT_DEPTH_TEST)
|
|
|
|
if input.Press(string.byte('H')) then
|
|
debug = not debug
|
|
end
|
|
|
|
if debug and backlog_isactive() == false then
|
|
|
|
local model_transform = scene.Component_GetTransform(player.model)
|
|
local target_transform = scene.Component_GetTransform(player.neck)
|
|
|
|
-- camera target box and axis
|
|
--DrawBox(target_transform.GetMatrix())
|
|
|
|
-- Neck bone
|
|
DrawPoint(scene.Component_GetTransform(player.neck).GetPosition(),0.05, Vector(0,1,1,1))
|
|
-- Head bone
|
|
DrawPoint(scene.Component_GetTransform(player.head).GetPosition(),0.05, Vector(0,1,1,1))
|
|
-- Left hand bone
|
|
DrawPoint(scene.Component_GetTransform(player.left_hand).GetPosition(),0.05, Vector(0,1,1,1))
|
|
-- Right hand bone
|
|
DrawPoint(scene.Component_GetTransform(player.right_hand).GetPosition(),0.05, Vector(0,1,1,1))
|
|
-- Left foot bone
|
|
DrawPoint(scene.Component_GetTransform(player.left_foot).GetPosition(),0.05, Vector(0,1,1,1))
|
|
-- Right foot bone
|
|
DrawPoint(scene.Component_GetTransform(player.right_foot).GetPosition(),0.05, Vector(0,1,1,1))
|
|
|
|
|
|
local capsule = scene.Component_GetCharacter(player.model).GetCapsule()
|
|
DrawCapsule(capsule)
|
|
|
|
local str = "State: " .. player.state .. "\n"
|
|
DrawDebugText(str, vector.Add(capsule.GetBase(), Vector(0,0.4)), Vector(1,1,1,1), 1, DEBUG_TEXT_CAMERA_FACING | DEBUG_TEXT_CAMERA_SCALING)
|
|
|
|
DrawVoxelGrid(voxelgrid)
|
|
|
|
end
|
|
|
|
if not backlog_isactive() then
|
|
if(input.Press(KEYBOARD_BUTTON_ESCAPE)) then
|
|
-- restore previous component
|
|
-- so if you loaded this script from the editor, you can go back to the editor with ESC
|
|
backlog_post("EXIT")
|
|
for i,anim in ipairs(scene.Component_GetAnimationArray()) do
|
|
anim.Stop() -- stop animations because some of them are retargeted and animation source scene will be lost after we exit this script!
|
|
end
|
|
for i,character in ipairs(scene.Component_GetCharacterArray()) do
|
|
character.StopAnimation()
|
|
end
|
|
application.SetActivePath(prevPath, 0.5, 0, 0, 0, FadeType.CrossFade)
|
|
killProcesses()
|
|
return
|
|
end
|
|
if(input.Press(string.byte('R'))) then
|
|
-- reload script
|
|
backlog_post("RELOAD")
|
|
application.SetActivePath(prevPath, 0.5, 0, 0, 0, FadeType.CrossFade)
|
|
while not application.IsFaded() do
|
|
update()
|
|
end
|
|
killProcesses()
|
|
dofile(script_file())
|
|
return
|
|
end
|
|
if input.Press(string.byte('L')) then
|
|
framerate_lock = not framerate_lock
|
|
application.SetFrameRateLock(framerate_lock)
|
|
if framerate_lock then
|
|
application.SetTargetFrameRate(framerate_lock_target)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end)
|