The videos and code below cover the setup, motion, and actions for an RTS "like" camera in Leadwerks. We will focus on the basic elements of the Rise of Nations camera.
What we want:
- Camera that moves forwards, backwards, left, right.
- Zooms in and out.
- Can turn left and right.
- Has a speed multiplier.
- Can "pick" entities/actors/characters and can assign them to move to a specific location.
- [update] Group selection via "Selection Box" has been added.
We will place a pivot into our map, this pivot will have a script attached. In the script we will create a camera, which will have the pivot as its parent. Most of the movement manipulations will be done on the pivot, but there are some that will be specific to the camera (such as zoom in/out). Picking characters and assigning actions will be done through raycasting from the camera and mouse input.
MOTION
ACTIONS
The Actions portion has been broken down into three subportions: Actions Setup, Pick Actors, and Assign Actions.
Actions - Actions Setup
The Actions Setup video covers creating "global" scope arrays/tables. The values of these tables are then used in other scripts to identify floors, walls, actors, etc. Some of the tables are also used for animations, actions, subactions, and other things that are shared in multiple script files. Using LUA Table Maker does facilitate the creation, and updating, of LUA arrays.
Actions - Pick Actors
Actions - Assign Actions
RTSCam.lua
-- RTS Camera by Jesse B Andersen -- August 20, 2014 -- Full tutorial at: -- http://www.jessebandersen.com/2014/08/rts-camera-in-leadwerks-engine.html function Script:Start() -- Pivot Settings self.initPos = self.entity:GetPosition(true) self.initAngle = self.entity:GetRotation(true) self.moveSpeed = 0.2 self.moveMultiplier = 2 self.turnSpeed = 0.4 self.yOffset = 1.0 self.entity:SetPosition(self.initPos.x, self.initPos.y + self.yOffset, self.initPos.z) -- Camera Settings self.cam = Camera:Create(self.entity) self.camInitPos = Vec3(0, 7, -8) self.cam:SetPosition(self.camInitPos) self.zoomIn = 2 self.zoomOut = 25 self.cam:Point(self.entity) -- Pick Settings self.pickRadius = 0.01 self.pickedActor = nil end function Script:UpdateWorld() local Win = Window:GetCurrent() local Ts = Time:GetSpeed() local smp = 1 --System:Print("Animations run: " .. animations.run) -- Increase speed if Win:KeyDown(Key.Shift) then smp = self.moveMultiplier end -- Forwards if Win:KeyDown(Key.W) then self.entity:Move(0, 0, self.moveSpeed * smp * Ts) end -- Backwards if Win:KeyDown(Key.S) then self.entity:Move(0, 0, -self.moveSpeed * smp * Ts) end -- Left if Win:KeyDown(Key.A) then self.entity:Move(-self.moveSpeed * smp * Ts, 0, 0) end -- Right if Win:KeyDown(Key.D) then self.entity:Move(self.moveSpeed * smp * Ts, 0, 0) end -- Zoom in if Win:KeyDown(Key.R) then if self.cam:GetDistance(self.entity) > self.zoomIn then self.cam:Move(0, 0, self.moveSpeed * smp * Ts) end end -- Zoom Out if Win:KeyDown(Key.F) then if self.cam:GetDistance(self.entity) < self.zoomOut then self.cam:Move(0, 0, -self.moveSpeed * smp * Ts) end end -- Turn Left if Win:KeyDown(Key.Q) then self.entity:Turn(0, -self.turnSpeed * smp * Ts, 0) end -- Turn Right if Win:KeyDown(Key.E) then self.entity:Turn(0, self.turnSpeed * smp * Ts, 0) end -- Reset Camera if Win:KeyHit(Key.F12) then self.entity:SetPosition(self.initPos.x, self.initPos.y + self.yOffset, self.initPos.z) self.entity:SetRotation(self.initAngle) self.cam:SetPosition(self.camInitPos) self.cam:Point(self.entity) end -- Left Click if Win:MouseHit(1) then self:DoPick(Win) end -- Right Click if Win:MouseHit(2) then self:DoAssign(Win) end -- Walk Roam if Win:KeyHit(Key.F1) then if self.pickedActor ~= nil then self.pickedActor.script:Roam(subActions.walk) end end -- Run Roam if Win:KeyHit(Key.F2) then if self.pickedActor ~= nil then self.pickedActor.script:Roam(subActions.run) end end end function Script:DoPick(Win) local pickInfo = PickInfo() local mp = Win:GetMousePosition() -- Release existing actor if self.pickedActor ~= nil then self.pickedActor.script:GotReleased() self.pickedActor = nil end -- Raycast if self.cam:Pick(mp.x, mp.y, pickInfo, self.pickRadius, true) then -- Ensure we have script and entiType if pickInfo.entity.script ~= nil and pickInfo.entity.script.entiType ~= nil then -- Ensure we have an actor if pickInfo.entity.script.entiType == entiTypes.actor then -- Store new actor self.pickedActor = pickInfo.entity self.pickedActor.script:GotPicked() end end end end function Script:DoAssign(Win) if self.pickedActor == nil then return end local pickInfo = PickInfo() local mp = Win:GetMousePosition() -- Raycast if self.cam:Pick(mp.x, mp.y, pickInfo, self.pickRadius, true) then -- Ensure we have script and entiType if pickInfo.entity.script ~= nil and pickInfo.entity.script.entiType ~= nil then -- Ensure we have a floor if pickInfo.entity.script.entiType == entiTypes.floor then -- If shift then make actor run, else walk if Win:KeyDown(Key.Shift) then self.pickedActor.script:MoveTo(pickInfo.position, subActions.run) else self.pickedActor.script:MoveTo(pickInfo.position, subActions.walk) end end end end end
Selection Box - Setup
Selection Box - Drawing the Selection Box
Selection Box - Selecting Actors
Selection Box - Assigning Actions
RTSCam.lua -- with Group Selection via Selection Box
-- RTS Camera (with group selection) by Jesse B Andersen -- September 02, 2014 -- Full tutorial at: -- http://www.jessebandersen.com/2014/08/rts-camera-in-leadwerks-engine.html function Script:Start() -- Pivot Settings self.initPos = self.entity:GetPosition(true) self.initAngle = self.entity:GetRotation(true) self.moveSpeed = 0.2 self.moveMultiplier = 2 self.turnSpeed = 0.4 self.yOffset = 1.0 self.entity:SetPosition(self.initPos.x, self.initPos.y + self.yOffset, self.initPos.z) -- Camera Settings self.cam = Camera:Create(self.entity) self.camInitPos = Vec3(0, 7, -8) self.cam:SetPosition(self.camInitPos) self.zoomIn = 2 self.zoomOut = 25 self.cam:Point(self.entity) -- Pick Settings self.pickRadius = 0.01 self.pickedActor = nil self.groupPicked = false self.pickedActors = {} -- Pick Actors Rectangle self.context = nil self.pickInit = false self.mpInit = Vec2(0) self.mpEnd = Vec2(0) end function Script:UpdateWorld() local Win = Window:GetCurrent() local Ts = Time:GetSpeed() local smp = 1 --System:Print("Animations run: " .. animations.run) -- Increase speed if Win:KeyDown(Key.Shift) then smp = self.moveMultiplier end -- Forwards if Win:KeyDown(Key.W) then self.entity:Move(0, 0, self.moveSpeed * smp * Ts) end -- Backwards if Win:KeyDown(Key.S) then self.entity:Move(0, 0, -self.moveSpeed * smp * Ts) end -- Left if Win:KeyDown(Key.A) then self.entity:Move(-self.moveSpeed * smp * Ts, 0, 0) end -- Right if Win:KeyDown(Key.D) then self.entity:Move(self.moveSpeed * smp * Ts, 0, 0) end -- Zoom in if Win:KeyDown(Key.R) then if self.cam:GetDistance(self.entity) > self.zoomIn then self.cam:Move(0, 0, self.moveSpeed * smp * Ts) end end -- Zoom Out if Win:KeyDown(Key.F) then if self.cam:GetDistance(self.entity) < self.zoomOut then self.cam:Move(0, 0, -self.moveSpeed * smp * Ts) end end -- Turn Left if Win:KeyDown(Key.Q) then self.entity:Turn(0, -self.turnSpeed * smp * Ts, 0) end -- Turn Right if Win:KeyDown(Key.E) then self.entity:Turn(0, self.turnSpeed * smp * Ts, 0) end -- Reset Camera if Win:KeyHit(Key.F12) then self.entity:SetPosition(self.initPos.x, self.initPos.y + self.yOffset, self.initPos.z) self.entity:SetRotation(self.initAngle) self.cam:SetPosition(self.camInitPos) self.cam:Point(self.entity) end -- Left Click if Win:MouseHit(1) then self:DoPick(Win) end -- Right Click if Win:MouseHit(2) then self:DoAssign(Win) end -- Left mouse down if Win:MouseDown(1) then -- Begin tracking mouse position if self.pickInit == false then self.pickInit = true self.mpInit = Win:GetMousePosition() self.mpEnd = Win:GetMousePosition() self.context = Context:GetCurrent() else -- Tracking of mouse position is on going self.mpEnd = Win:GetMousePosition() end else -- Determine if a "release" has occurred and perform a group selection if self.pickInit == true then self.pickInit = false self:DoSelection(Win) self.context = nil end end -- Walk Roam if Win:KeyHit(Key.F1) then -- For single actor if self.pickedActor ~= nil then self.pickedActor.script:Roam(subActions.walk) end -- For group if self.groupPicked == true then for j, jActor in pairs(self.pickedActors) do jActor.entity.script:Roam(subActions.walk) end end end -- Run Roam if Win:KeyHit(Key.F2) then -- For single actor if self.pickedActor ~= nil then self.pickedActor.script:Roam(subActions.run) end -- For group if self.groupPicked == true then for j, jActor in pairs(self.pickedActors) do jActor.entity.script:Roam(subActions.run) end end end end function Script:DoSelection(Win) -- Release existing group if self.groupPicked == true then self.groupPicked = false -- Iterate through the pickedActors table and release for j, jActor in pairs(self.pickedActors) do jActor.entity.script:GotReleased() end self.pickedActors = {} end -- Get the new group -- Determine the smallest x and the smallest y local initPos = Vec2(self.mpInit.x, self.mpInit.y) local endPos = Vec2(self.mpEnd.x, self.mpEnd.y) if endPos.x < initPos.x then local temp = initPos.x initPos.x = endPos.x endPos.x = temp end if endPos.y < initPos.y then local temp = initPos.y initPos.y = endPos.y endPos.y = temp end -- Populate the pickedActors table for j, jActor in pairs(actors) do local p = self.cam:Project(jActor.entity:GetPosition(true)) if p.x > initPos.x and p.x < endPos.x then if p.y > initPos.y and p.y < endPos.y then jActor.entity.script:GotPicked() table.insert(self.pickedActors, jActor) self.groupPicked = true end end end end function Script:PostRender() if self.pickInit == true then -- Red self.context:SetColor(1, 0, 0) -- Horizontal lines self.context:DrawLine(self.mpInit.x, self.mpInit.y, self.mpEnd.x, self.mpInit.y) self.context:DrawLine(self.mpInit.x, self.mpEnd.y, self.mpEnd.x, self.mpEnd.y) -- Vertical lines self.context:DrawLine(self.mpInit.x, self.mpInit.y, self.mpInit.x, self.mpEnd.y) self.context:DrawLine(self.mpEnd.x, self.mpInit.y, self.mpEnd.x, self.mpEnd.y) end end function Script:DoPick(Win) local pickInfo = PickInfo() local mp = Win:GetMousePosition() -- Release existing actor if self.pickedActor ~= nil then self.pickedActor.script:GotReleased() self.pickedActor = nil end -- Raycast if self.cam:Pick(mp.x, mp.y, pickInfo, self.pickRadius, true) then -- Ensure we have script and entiType if pickInfo.entity.script ~= nil and pickInfo.entity.script.entiType ~= nil then -- Ensure we have an actor if pickInfo.entity.script.entiType == entiTypes.actor then -- Store new actor self.pickedActor = pickInfo.entity self.pickedActor.script:GotPicked() end end end end function Script:DoAssign(Win) local pickInfo = PickInfo() local mp = Win:GetMousePosition() -- Raycast if self.cam:Pick(mp.x, mp.y, pickInfo, self.pickRadius, true) then -- Ensure we have script and entiType if pickInfo.entity.script ~= nil and pickInfo.entity.script.entiType ~= nil then -- Ensure we have a floor if pickInfo.entity.script.entiType == entiTypes.floor then -- If shift then make actor run, else walk if Win:KeyDown(Key.Shift) then -- Assign a single actor to run if self.pickedActor ~= nil then self.pickedActor.script:MoveTo(pickInfo.position, subActions.run) end -- Assign a group of actors to run if self.groupPicked == true then for j, jActor in pairs(self.pickedActors) do jActor.entity.script:MoveTo(pickInfo.position, subActions.run) end end else -- Assign a single actor to walk if self.pickedActor ~= nil then self.pickedActor.script:MoveTo(pickInfo.position, subActions.walk) end -- Assign a group of actors to walk if self.groupPicked == true then for j, jActor in pairs(self.pickedActors) do jActor.entity.script:MoveTo(pickInfo.position, subActions.walk) end end end end end end end
I presume that not everything I said made "sense" or was technically correct. If there are issues that need to be clarified then please let me know via email or in the comments below. The tutorial should cover most of the principles requires to make RTS games. Please support the superb Leadwerks game engine by getting a copy or demo at http://www.leadwerks.com/ or Steam. Lastly, thank you for visiting this blog.
No comments:
Post a Comment