-- Lua Third person camera and character controller script -- This script will load a simple scene with a character that can be controlled -- -- CONTROLS: -- WASD: walk -- SHIFT: speed -- SPACE: Jump -- Right Mouse Button: rotate camera local scene = GetScene() Character = { model = INVALID_ENTITY, target = INVALID_ENTITY, -- Camera will look at this location, rays will be started from this location, etc. idle_anim = INVALID_ENTITY, walk_anim = INVALID_ENTITY, head = INVALID_ENTITY, left_hand = INVALID_ENTITY, right_hand = INVALID_ENTITY, left_foot = INVALID_ENTITY, right_foot = INVALID_ENTITY, face = Vector(0,0,1), -- forward direction velocity = Vector(), velocityPrev = Vector(), ray = Ray(Vector(),Vector()), o = INVALID_ENTITY, -- collision prop with scene (entity) p,n = Vector(), -- collision props with scene (position,normal) savedPointerPos = Vector(), moveSpeed = 0.2, layerMask = 0x2, -- The character will be tagged to use this layer, so scene Picking can filter out the character scale = Vector(0.8,0.8,0.8), rotation = Vector(0,3.1415,0), states = { STAND = 0, WALK = 1, JUMP = 2, }, state = STAND, Create = function(self,entity) self.model = entity local layer = scene.Component_GetLayer(self.model) layer.SetLayerMask(self.layerMask) self.idle_anim = scene.Entity_FindByName("idle") self.walk_anim = scene.Entity_FindByName("walk") self.head = scene.Entity_FindByName("testa") self.left_hand = scene.Entity_FindByName("mano_L") self.right_hand = scene.Entity_FindByName("mano_R") self.left_foot = scene.Entity_FindByName("avampiede_L") self.right_foot = scene.Entity_FindByName("avampiede_R") local model_transform = scene.Component_GetTransform(self.model) model_transform.ClearTransform() model_transform.Scale(self.scale) model_transform.Rotate(self.rotation) model_transform.UpdateTransform() self.target = CreateEntity() local target_transform = scene.Component_CreateTransform(self.target) target_transform.ClearTransform() target_transform.Translate(Vector(0,3)) scene.Component_Attach(self.target, self.model) end, Jump = function(self,f) self.velocity = self.velocity:Add(Vector(0,f,0)) self.state = self.states.JUMP end, MoveDirection = function(self,dir,f) local model_transform = scene.Component_GetTransform(self.model) local target_transform = scene.Component_GetTransform(self.target) local savedPos = model_transform.GetPosition() model_transform.ClearTransform() self.face = vector.Transform(dir:Normalize(), target_transform.GetMatrix()) self.face.SetY(0) self.face = self.face.Normalize() model_transform.MatrixTransform(matrix.LookTo(Vector(),self.face):Inverse()) model_transform.Scale(self.scale) model_transform.Rotate(self.rotation) model_transform.Translate(savedPos) model_transform.UpdateTransform() scene.Component_Detach(self.target) scene.Component_Attach(self.target, self.model) self.velocityPrev = self.velocity; self.velocity = self.face:Multiply(Vector(f,f,f)) self.velocity.SetY(self.velocityPrev.GetY()) self.state = self.states.WALK end, Input = function(self) if(self.state==self.states.STAND) then local lookDir = Vector() if(input.Down(VK_LEFT) or input.Down(string.byte('A'))) then lookDir = lookDir:Add( Vector(-1) ) end if(input.Down(VK_RIGHT) or input.Down(string.byte('D'))) then lookDir = lookDir:Add( Vector(1) ) end if(input.Down(VK_UP) or input.Down(string.byte('W'))) then lookDir = lookDir:Add( Vector(0,0,1) ) end if(input.Down(VK_DOWN) or input.Down(string.byte('S'))) then lookDir = lookDir:Add( Vector(0,0,-1) ) end if(lookDir:Length()>0) then if(input.Down(VK_LSHIFT)) then self:MoveDirection(lookDir,self.moveSpeed*2) else self:MoveDirection(lookDir,self.moveSpeed) end end end if( input.Press(string.byte('J')) or input.Press(VK_SPACE) ) then self:Jump(0.6) end -- mouse control if(input.Down(VK_RBUTTON)) then local mousePosNew = input.GetPointer() local mouseDif = vector.Subtract(mousePosNew,self.savedPointerPos) mouseDif = mouseDif:Multiply(getDeltaTime() * 0.3) local target_transform = scene.Component_GetTransform(self.target) target_transform.Rotate(Vector(mouseDif.GetY(),mouseDif.GetX())) self.face.SetY(0) self.face=self.face:Normalize() input.SetPointer(self.savedPointerPos) input.HidePointer(true) else self.savedPointerPos = input.GetPointer() input.HidePointer(false) end end, Update = function(self) local model_transform = scene.Component_GetTransform(self.model) local target_transform = scene.Component_GetTransform(self.target) -- state and animation update if(self.state == self.states.STAND) then scene.Component_GetAnimation(self.idle_anim).Play() scene.Component_GetAnimation(self.walk_anim).Stop() self.state = self.states.STAND elseif(self.state == self.states.WALK) then scene.Component_GetAnimation(self.idle_anim).Stop() scene.Component_GetAnimation(self.walk_anim).Play() self.state = self.states.STAND elseif(self.state == self.states.JUMP) then scene.Component_GetAnimation(self.idle_anim).Play() scene.Component_GetAnimation(self.walk_anim).Stop() self.state = self.states.STAND end -- front block shoots multiple rays in front to try to find obstruction local rotations = {0, 3.1415*0.3, -3.1415*0.3} for i,rot in ipairs(rotations) do local origin = vector.Add(model_transform.GetPosition(), Vector(0,1,0)) -- this ray starts a little above character ground position local dir = vector.Transform(self.face, matrix.RotationY(rot)) local ray2 = Ray(origin,dir) local o2,p2,n2 = Pick(ray2, PICK_OPAQUE, ~self.layerMask) local dist = vector.Subtract(origin,p2):Length() if(o2 ~= INVALID_ENTITY and dist < 1) then -- run along wall instead of going through it local velocityLen = self.velocity.Length() local velocityNormalized = self.velocity.Normalize() local undesiredMotion = n2:Multiply(vector.Dot(velocityNormalized, n2)) local desiredMotion = vector.Subtract(velocityNormalized, undesiredMotion) self.velocity = vector.Multiply(desiredMotion, velocityLen) end end -- check what is below the character self.ray = Ray(target_transform.GetPosition(),Vector(0,-1,0)) local pPrev = self.p self.o,self.p,self.n = Pick(self.ray, PICK_OPAQUE, ~self.layerMask) if(self.o == INVALID_ENTITY) then self.p=pPrev -- if nothing, go back to previous position end -- try to put water ripple if character head is directly above water local head_transform = scene.Component_GetTransform(self.head) local waterRay = Ray(head_transform.GetPosition(),Vector(0,-1,0)) local w,wp,wn = Pick(waterRay,PICK_WATER) if(w ~= INVALID_ENTITY and self.velocity.Length()>0.1) then PutWaterRipple("../Editor/images/ripple.png",wp) end -- add gravity: self.velocity = vector.Add(self.velocity, Vector(0,-0.04,0)) -- check if we are below or on the ground: if(model_transform.GetPosition().GetY() <= self.p.GetY() and self.velocity.GetY()<=0) then self.state = self.states.STAND model_transform.Translate(vector.Subtract(self.p,model_transform.GetPosition())) -- snap back to last succesfully traced position self.velocity.SetY(0) -- don't fall below ground self.velocity = vector.Multiply(self.velocity, 0.8) -- slow down gradually on ground end -- apply velocity: model_transform.Translate(vector.Multiply(getDeltaTime() * 60, self.velocity)) end } -- Third person camera controller class: ThirdPersonCamera = { camera = INVALID_ENTITY, character = nil, side_offset = 1.4, height = 3, rest_distance = 6, correction_speed = 1.4, Create = function(self, character) self.character = character self.camera = CreateEntity() local camera_transform = scene.Component_CreateTransform(self.camera) scene.Component_Attach(self.camera, self.character.target) end, Update = function(self) if(self.character ~= nil) then local camera_transform = scene.Component_GetTransform(self.camera) local target_transform = scene.Component_GetTransform(self.character.target) local camPos = camera_transform.GetPosition() local targetPos = target_transform.GetPosition() local bestDistance = self.rest_distance -- Camera collision: local camDiff = vector.Subtract(targetPos, camPos) local camDist = camDiff.Length() local rayDir = camDiff.Normalize() local camRay = Ray(camPos, rayDir) local collObj,collPos,collNor = Pick(camRay, PICK_OPAQUE, ~self.character.layerMask) if(collObj ~= INVALID_ENTITY) then -- It hit something, see if it is between the player and camera: if(vector.Subtract(collPos, camPos).Length() < camDist) then local collDiff = vector.Subtract(targetPos, collPos) local collDist = collDiff.Length() bestDistance = math.min(bestDistance, collDist) bestDistance = math.max(0, bestDistance) end end camera_transform.ClearTransform() camera_transform.Translate(Vector(self.side_offset, self.height, -bestDistance)) -- because the main camera is not part of the scene, we ensure that it is following our script camera entity AttachCamera(self.camera) end end, } -- Player Controller local player = Character -- Third Person camera local camera = ThirdPersonCamera -- Main loop: 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 = main.GetActivePath() local path = RenderPath3D_TiledForward() path.SetLightShaftsEnabled(true) main.SetActivePath(path) LoadModel("../models/playground.wiscene") player:Create(LoadModel("../models/girl.wiscene")) camera:Create(player) while true do if(input.Press(VK_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") killProcesses() main.SetActivePath(prevPath) return end player:Input() player:Update() camera:Update() -- Wait for Engine update tick update() end end) -- Debug draw: -- Draw Helpers local DrawAxis = function(point,f) DrawLine(point,point:Add(Vector(f,0,0)),Vector(1,0,0,1)) DrawLine(point,point:Add(Vector(0,f,0)),Vector(0,1,0,1)) DrawLine(point,point:Add(Vector(0,0,f)),Vector(0,0,1,1)) end -- Draw runProcess(function() while true do while( backlog_isactive() ) do waitSeconds(1) end local model_transform = scene.Component_GetTransform(player.model) local target_transform = scene.Component_GetTransform(player.target) -- Drawing additional render data (slow, only for debug purposes) --velocity DrawLine(target_transform.GetPosition(),target_transform.GetPosition():Add(player.velocity)) --face DrawLine(target_transform.GetPosition(),target_transform.GetPosition():Add(player.face:Normalize()),Vector(0,1,0,1)) --intersection DrawAxis(player.p,0.5) -- camera target box and axis DrawBox(target_transform.GetMatrix()) -- Head bone DrawPoint(scene.Component_GetTransform(player.head).GetPosition(),0.2, Vector(0,1,1,1)) -- Left hand bone DrawPoint(scene.Component_GetTransform(player.left_hand).GetPosition(),0.2, Vector(0,1,1,1)) -- Right hand bone DrawPoint(scene.Component_GetTransform(player.right_hand).GetPosition(),0.2, Vector(0,1,1,1)) -- Left foot bone DrawPoint(scene.Component_GetTransform(player.left_foot).GetPosition(),0.2, Vector(0,1,1,1)) -- Right foot bone DrawPoint(scene.Component_GetTransform(player.right_foot).GetPosition(),0.2, Vector(0,1,1,1)) -- Wait for the engine to render the scene render() end end)