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