Einlander Posted January 31, 2016 Posted January 31, 2016 I've been staring at this code for way too long and now I need different brains to look at it. This is the beginnings of a 3rd person character controller. I am now working at adding mouse look features but there seems to be a bug when the camera is at specific angles, notable y45 and y270. At those specific locations the camera glitches out. I will give it another try tomorrow, and no I don't want to create a pivot and anchor the camera to it and spin that --[[ Title: Third Person Controller Author: Einlander Start Date: 1-30-2016 Version: .01 Description: Script to control Player in 3rd person with keyboard and mouse Notes: This script is designed to mimic classical 3rd person game controls: *Look with mouse *Move with WASD *Follow Player as they move ]]-- Script.Cam =nil --entity "Camera" Script.moveSpeed = 2.5 --float "Move Speed" Script.speedMultiplier = 1.5 --float "Run Multiplier" Script.strafeSpeed = 4 --float "Strafe Speed" Script.jumpForce = 8 --float "Jump Force" function Script:Start() self.input={} self.unitangle = Vec3() self.anglemagnitude = nil end --[[ function Script:UpdateWorld() end --]] function Script:UpdatePhysics() local movex=0 local movez=0 self.input[0]=0 self.input[1]=0 local playerMovement = Vec3() -- I learned the boolean shortcuts from my years programming on a Casio Graphing Calculator self.input[1] = self.input[1] + ((window:KeyDown(Key.W) and 1 or 0) - (window:KeyDown(Key.S) and 1 or 0)) self.input[0] = self.input[0] - ((window:KeyDown(Key.A) and 1 or 0) - (window:KeyDown(Key.D) and 1 or 0)) playerMovement.z = self.input[1] * self.moveSpeed -- Strafing playerMovement.x = self.input[0] * self.moveSpeed if self.carryingEntity == nil and window:KeyDown(Key.Shift) then playerMovement.z = playerMovement.z * self.speedMultiplier -- Run while Strafed -- playerMovement.x = playerMovement.x * self.speedMultiplier end local jump = 0 if window:KeyHit(Key.Space) and self:IsAirborne() == 0 then jump = self.jumpForce playerMovement = playerMovement * 1.6 end self.entity:SetInput(0, playerMovement.z, playerMovement.x, jump , false, 1.0, 0.5, true) local startpos = Vec3( self.entity:GetPosition(true).x, self.entity:GetPosition(true).y + 5 , self.entity:GetPosition(true).z -5 ) -- y distance to get above players head, z - distance we want from player local endpos = Vec3() -- rotate the starting position (where the camera is at) around the player position if self.rot == nil then self.rot = 0 end self.rot = self.rot+.5 local finalpos = Vec3() finalpos = self:rotateX3D({startpos},self.entity:GetPosition(true) , 0)[1] finalpos = self:rotatePointY({finalpos},self.entity:GetPosition(true) , self.rot)[1] -- when self.rot ~ 45 or 270 it flips just for that 1 angle -- set camera at the final rotated positon self.Cam:SetPosition(finalpos) -- make camera look at player local lookAt = self:LookAt(self.entity:GetPosition(true)) lookAt.z = 0 -- THIS LINE IS IMPORTANT. DO NOT REMOVE IT, IF YOU DO IT WILL CAUSE A JUMP WHEN THE Z ANGLE CHANGES FROM NEGATIVE TO POSITIVE OR VICE VERSA self.Cam:SetRotation(lookAt,true) end function Script:IsAirborne() return self.entity:GetAirborne() and 1 or 0 end function Script:LookAt(lookAt) --http://www.leadwerks.com/werkspace/topic/10191-short-example-of-mathatan2/page__hl__lookat --http://stackoverflow.com/questions/1251828/calculate-rotations-to-look-at-a-3d-point --http://leadwerks.wikidot.com/wiki:face-entity --// Calculate angle from point A towards point B local tv = lookAt - self.Cam:GetPosition(true) local tRoty = Math:ATan2(tv.x, tv.z) local tRotx = 0 if lookAt.z >= self.Cam:GetPosition(true).z then tRotx = -Math:ATan2(tv.y* Math:Cos(tRoty), tv.z) else tRotx = Math:ATan2(tv.y* Math:Cos(tRoty), -tv.z) end tRotz = Math:ATan2( Math:Cos(tRotx), Math:Sin(tRotx) * Math:Sin(tRoty) ) return Vec3(tRotx,tRoty,tRotz-90) end function Script:Get3dDistance(pointa --[[as vec3--]], pointb--[[as vec3--]]) --[[as float--]] return math.sqrt((pointb.x - pointa.x)^2+ (pointb.y - pointa.y)^2+ (pointb.z - pointa.z)^2) end function Script:Get3dMagnitude(pointa --[[as vec3--]]) --[[as float--]] --http://www.fundza.com/vectors/normalize/ return math.abs(math.sqrt((pointa.x * pointa.x)+ (pointa.y * pointa.y)+ (pointa.z * pointa.z))) end function Script:NormalizeVector(pointa--[[as vec3--]], length--[[as float--]])--[[as vec3--]] --Yes I know leadwerks has these functions built in somewhere, but sometimes you just need to learn what it is you are exactly doing --http://www.fundza.com/vectors/normalize/ pointa.x = pointa.x / math.abs(length) pointa.y = pointa.y / math.abs(length) pointa.z = pointa.z / math.abs(length) return pointa end toRadians = function(degrees) return degrees / 180 * math.pi end function Script:rotatePointY(points, origin, degrees) local pointsout = {} for i = 1, #points do local point = Vec3(points.x,points.y,points.z) local x = origin.x + ( math.cos(toRadians(degrees)) * (point.x - origin.x) - math.sin(toRadians(degrees)) * (point.z - origin.z) ) local z = origin.z + ( math.sin(toRadians(degrees)) * (point.x - origin.x) + math.cos(toRadians(degrees)) * (point.z - origin.z) ) point.x = x point.z = z table.insert(pointsout, point) end return pointsout end function Script:rotatePointZ(points, origin, degrees) local pointsout = {} for i = 1, #points do local point = Vec3(points.x,points.y,points.z) local x = origin.x + ( math.cos(toRadians(degrees)) * (point.x - origin.x) - math.sin(toRadians(degrees)) * (point.y - origin.y) ) local y = origin.y + ( math.sin(toRadians(degrees)) * (point.x - origin.x) + math.cos(toRadians(degrees)) * (point.y - origin.y) ) point.x = x point.y = y table.insert(pointsout, point) end return pointsout end function Script:rotatePointX(points, origin, degrees) local pointsout = {} for i = 1, #points do local point = Vec3(points.x,points.y,points.z) local y = origin.y + ( math.cos(toRadians(degrees)) * (point.y - origin.y) - math.sin(toRadians(degrees)) * (point.z - origin.z) ) local z = origin.z + ( math.sin(toRadians(degrees)) * (point.y - origin.y) + math.cos(toRadians(degrees)) * (point.z - origin.z) ) point.y = y point.z = z table.insert(pointsout, point) end return pointsout end function Script:rotateX3D (points, origin, degrees) local pointsout = {} for i = 1, #points do local point = Vec3(points.x,points.y,points.z) local y = point.y; local z = point.z; point.y = origin.y + (y- origin.y) * math.cos(toRadians(degrees)) - (z - origin.z) * math.sin(toRadians(degrees)); point.z = origin.z + (z- origin.z) * math.cos(toRadians(degrees)) + (y - origin.y) * math.sin(toRadians(degrees)); table.insert(pointsout, point) end return pointsout end function Script:WrapAngle(angle) local currentrotation = angle if currentrotation.x < 0 then currentrotation.x = 359 - math.mod(currentrotation.x , 359) end if currentrotation.y < 0 then currentrotation.y = math.mod(currentrotation.y , 359) currentrotation.y = 359 - math.abs(currentrotation.y) end if currentrotation.z < 0 then currentrotation.z = 359 - math.mod(currentrotation.z , 359) end currentrotation.x = math.mod(currentrotation.x , 359) currentrotation.y = math.mod(currentrotation.y , 359) currentrotation.z = math.mod(currentrotation.z , 359) return currentrotation end --[[ function Script:Collision(entity, position, normal, speed) end --]] --[[ function Script:Draw() end --]] --[[ function Script:DrawEach(camera) end --]] --This function will be called after the world is rendered, before the screen is refreshed. --Use this to perform any 2D drawing you want the entity to display. function Script:PostRender(context) context:SetBlendMode(Blend.Alpha) local pos = self.Cam:GetRotation(true) local outText = "Actual angle" .. pos.x .. "|" ..pos.y .."|" ..pos.z --local outText = self:FaceEntity(self.Cam:GetPosition(true),self.entity:GetPosition(true)).x .."|" .. --self:FaceEntity(self.Cam:GetPosition(true),self.entity:GetPosition(true)).y .."|" .. --self:FaceEntity(self.Cam:GetPosition(true),self.entity:GetPosition(true)).z context:DrawText(outText,0,150) context:DrawText("Distance:" .. self:Get3dDistance(self.Cam:GetPosition(true) , self.entity:GetPosition(true)),0,165) pos = self.unitangle local outText = "unit angle" .. pos.x .. "|" ..pos.y .."|" ..pos.z context:DrawText(outText,0,180) pos = self.Cam:GetPosition(true) local outText = "Cam Position" .. pos.x .. "|" ..pos.y .."|" ..pos.z context:DrawText(outText,0,195) pos = self.entity:GetPosition(true) local outText = "Player Position" .. pos.x .. "|" ..pos.y .."|" ..pos.z context:DrawText(outText,0,210) end --[[ --This function will be called when the entity is deleted. function Script:Detach() end --]] --[[ --This function will be called when the last instance of this script is deleted. function Script:Cleanup() end --]] Quote
macklebee Posted January 31, 2016 Posted January 31, 2016 Since you dont want to use a pivot parent with a child camera (even though it makes it straightforward to do), you essentially just need to determine the points on a circle on the XZ plane using the basic circle equation: X = Cx + (r * cos(angle)) Z = Cz + (r * sin(angle)) where Cx/Cz are the center of the circle, r is the radius of the circle, angle is in radians. Cx/Cz, would be the global X&Z position of the player with r being the distance you want the camera from the player. This is an example of that: Script.Cam =nil --entity "Camera" Script.radius = 5 --float "Radius" Script.moveSpeed = 2.5 --float "Move Speed" Script.speedMultiplier = 1.5 --float "Run Multiplier" Script.strafeSpeed = 4 --float "Strafe Speed" Script.jumpForce = 8 --float "Jump Force" function Script:Start() self.input={} self.rot = 0 end function Script:UpdateWorld() self.rot = self.rot+.5 if self.rot>=360 then self.rot = 0 end entitypos = self.entity:GetPosition(true) CamX = entitypos.x + (self.radius * math.cos(math.rad(self.rot))) CamZ = entitypos.z + (self.radius * math.sin(math.rad(self.rot))) self.Cam:SetPosition(Vec3(CamX, entitypos.y+5, CamZ),true) self.Cam:Point(self.entity) end function Script:UpdatePhysics() self.input[0]=0 self.input[1]=0 local playerMovement = Vec3() self.input[1] = self.input[1] + ((window:KeyDown(Key.W) and 1 or 0) - (window:KeyDown(Key.S) and 1 or 0)) self.input[0] = self.input[0] - ((window:KeyDown(Key.A) and 1 or 0) - (window:KeyDown(Key.D) and 1 or 0)) playerMovement.z = self.input[1] * self.moveSpeed playerMovement.x = self.input[0] * self.moveSpeed local jump = 0 if window:KeyHit(Key.Space) and self.entity:GetAirborne() == false then jump = self.jumpForce playerMovement = playerMovement * 1.6 end self.entity:SetInput(0, playerMovement.z, playerMovement.x, jump , false, 1.0, 0.5, true) end function Script:PostRender(context) context:SetBlendMode(Blend.Alpha) context:DrawText("Actual Angle: "..self.Cam:GetRotation(true):ToString(),0,150) context:SetBlendMode(Blend.Solid) end If you have your heart on not using Entity:Point() for whatever reason, then using the Math:ATan2() function with the camera and player's position will give the angle you need. Quote Win7 64bit / Intel i7-2600 CPU @ 3.9 GHz / 16 GB DDR3 / NVIDIA GeForce GTX 590 LE / 3DWS / BMX / Hexagon macklebee's channel
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.