A bit more on performance issues; in this video we have 10 mooses and you can see the FPS start to drop but only when they are on display, if I look off to one side the FPS goes back up to 60. Also the AI slider (which is really a 'script' slider) is only around 1/3 of the way across.
I do wonder what impact the animated gun has compared to a simpler static model.
Just for anyone interested this is what the main script looks like at the moment, I'm in the middle of tidying it up and generalising the leg movement part so it's a bit messy:
-- LUA Script - precede every function and global member with lowercase name of script + '_main'
local mooseVars = {}
local Q = require "scriptbank\\quatlib"
local sin = math.sin
local cos = math.cos
local rad = math.rad
local deg = math.deg
local abs = math.abs
local asin = math.asin
local atan = math.atan2
local modf = math.modf
local sqrt = math.sqrt
local random = math.random
-- make sure random number generator is seeded
random(); random(); random()
function moose_init(e)
Include("quatlib.lua")
end
function MooseAddRocket(e)
for k,v in pairs(mooseVars) do
for i = 1,8 do
if v.rockets[i] == nil then
mooseVars[k].rockets[i] = e
return true
end
end
end
return false
end
local turretMaxAng = 37
local turretIncMin = 0.1
local turretIncMax = 0.3
function MooseAddTurret(e)
for k, v in pairs(mooseVars) do
if v.turret == nil then
v.turret = {Ent = e, yAng = 0,
yTgtAng = random (-1, 1) * turretMaxAng,
yAngInc = turretIncMin + random() * (turretIncMax - turretIncMin),
pAng = -10, pTgtAng = 0, pAngInc = 0}
return k
end
end
return nil
end
function MooseAddGun(e, side)
for k, v in pairs(mooseVars) do
if v.guns[side] == nil then
v.guns[side] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
end
end
return nil
end
function MooseAddThigh(e, side)
for k, v in pairs(mooseVars) do
if v.thighs[side] == nil then
v.thighs[side] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
end
end
return nil
end
function MooseAddShin(e)
for k, v in pairs(mooseVars) do
if v.shins['L'] == nil then
v.shins['L'] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
elseif v.shins['R'] == nil then
v.shins['R'] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
end
end
return nil
end
function MooseAddFoot(e)
for k, v in pairs(mooseVars) do
if v.feet['L'] == nil then
v.feet['L'] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
elseif v.feet['R'] == nil then
v.feet['R'] = {Ent = e, pAng = 0, pTgtAng = 0}
return k
end
end
return nil
end
local function Rotate3D (x, y, z, xrot, yrot, zrot)
function RotatePoint2D (x, y, Ang) -- Ang in radians
local Sa, Ca = sin(Ang), cos(Ang)
return x*Ca - y*Sa, x*Sa + y*Ca
end
local NX, NY, NZ = x, y, z
-- X
NZ, NY = RotatePoint2D (NZ, NY, -xrot)
-- Y
NX, NZ = RotatePoint2D (NX, NZ, -yrot)
-- Z
NY, NX = RotatePoint2D (NY, NX, -zrot)
return NX, NY, NZ
end
local fo = {x = 0, y = -67.63, z = 4.52}
local function PositionFoot(side, v, x, y, z, xA, yA, zA)
local xO, yO, zO = Rotate3D (fo.x, fo.y, fo.z, xA, yA, zA)
local foot = v.feet[side]
foot.pos = {x = x + xO, y = y + yO, z = z + zO}
ResetPosition(foot.Ent, foot.pos.x, foot.pos.y, foot.pos.z)
local quat = Q.Mul(v.quat, Q.FromEuler(rad(foot.pAng),0,0))
local fxA, fyA, fzA = Q.ToEuler(quat)
ResetRotation(foot.Ent, deg(fxA), deg(fyA), deg(fzA))
end
local so = {x = -0.55, y = -91.34, z = 5.78}
local function PositionShin(side, v, x, y, z, xA, yA, zA)
local xO, yO, zO = Rotate3D (so.x, so.y, so.z, xA, yA, zA)
local shin = v.shins[side]
local sx, sy, sz = x + xO, y + yO, z + zO
ResetPosition(shin.Ent, sx, sy, sz)
local quat = Q.Mul(v.quat, Q.FromEuler(rad(shin.pAng),0,0))
local sxA, syA, szA = Q.ToEuler(quat)
ResetRotation(shin.Ent, deg(sxA), deg(syA), deg(szA))
PositionFoot(side, v, sx, sy, sz, sxA, syA, szA)
end
local legOffsets =
{['L'] = {x = 37.54, y = -0.3, z = 0.1},
['R'] = {x = -37.54, y = -0.3, z = 0.1}
}
local function PositionLeg(side, v, xA, yA, zA)
local lo = legOffsets[side]
local xO, yO, zO = Rotate3D (lo.x, lo.y, lo.z, xA, yA, zA)
local thigh = v.thighs[side]
local tx, ty, tz = v.x + xO, v.y + yO, v.z + zO
ResetPosition(thigh.Ent, tx, ty, tz)
local quat = Q.Mul(v.quat, Q.FromEuler(rad(thigh.pAng),0,0))
local txA, tyA, tzA = Q.ToEuler(quat)
ResetRotation(thigh.Ent, deg(txA), deg(tyA), deg(tzA))
PositionShin(side, v, tx, ty, tz, txA, tyA, tzA)
end
local hipLean = 0
local hipMax = 6
local hipInc = 0.05
local ftDown = 'N'
local function PositionMoose(e, v, xA, yA, zA)
ResetPosition(e, v.x, v.y, v.z)
local hxA, hyA, hzA = xA, yA, zA
if ftDown == 'N' then
hipLean = 0
else
if ftDown == 'L' then
if hipLean < hipMax then
hipLean = hipLean + hipInc
end
else
if hipLean > -hipMax then
hipLean = hipLean - hipInc
end
end
local quat = Q.FromEuler(rad(v.turret.pAng / 2),0,rad(hipLean))
hxA, hyA, hzA = Q.ToEuler(Q.Mul(v.quat, quat))
end
ResetRotation(e, deg(hxA), deg(hyA), deg(hzA))
PositionLeg('L', v, hxA, hyA, hzA)
PositionLeg('R', v, hxA, hyA, hzA)
local leftFootPos = v.feet['L'].pos
local rightFootPos = v.feet['R'].pos
if leftFootPos.y < rightFootPos.y then
v.y = v.y - (leftFootPos.y - 20 -
GetTerrainHeight(leftFootPos.x, leftFootPos.z))
else
v.y = v.y - (rightFootPos.y - 20 -
GetTerrainHeight(rightFootPos.x, rightFootPos.z))
end
end
local function DoRocket(e, x, y, z, xo, yo, zo, xA, yA, zA)
local XO, YO, ZO = Rotate3D (xo, yo, zo, xA, yA, zA)
ResetPosition(e, x + XO, y + YO, z + ZO)
ResetRotation(e, deg(xA), deg(yA), deg(zA))
end
local gunBarrelPosBot = {}
local gunBarrelPosTop = {}
local gbo = {['T'] = {x = 0, y = 10.3, z = 98},
['B'] = {x = 0, y = 5.8, z = 70}
}
local function SaveBarrelPos (e, bar, gx, gy, gz, xA, yA, zA)
local b = gbo[bar]
local XO, YO, ZO = Rotate3D (b.x, b.y, b.z, xA, yA, zA)
if bar == 'B' then
gunBarrelPosBot[e] = {x = gx + XO, y = gy + YO, z = gz + ZO}
else
gunBarrelPosTop[e] = {x = gx + XO, y = gy + YO, z = gz + ZO}
end
end
function MoosePositionMF(e, gun, barrel)
local pos = nil
if barrel == 'B' then
pos = gunBarrelPosBot[gun]
else
pos = gunBarrelPosTop[gun]
end
if pos ~= nil then
ResetPosition(e, pos.x, pos.y, pos.z)
end
end
local go = {['L'] = {x = 61, y = -16, z = 42},
['R'] = {x = -61, y = -16, z = 41}
}
local function PositionGun(side, v, x, y, z, xA, yA, zA)
local GO = go[side]
local gun = v.guns[side]
local XO, YO, ZO = Rotate3D (GO.x, GO.y, GO.z, xA, yA, zA)
local gx, gy, gz = x + XO, y + YO, z + ZO
ResetPosition(gun.Ent, gx, gy, gz)
ResetRotation(gun.Ent, deg(xA), deg(yA), deg(zA))
SaveBarrelPos(gun.Ent, 'T', gx, gy, gz, xA, yA, zA)
SaveBarrelPos(gun.Ent, 'B', gx, gy, gz, xA, yA, zA)
end
local rp = {{x = -67.89, y = 40.07, z = -21.46},
{x = -56.57, y = 40.07, z = -21.46},
{x = -67.89, y = 30.88, z = -21.46},
{x = -56.45, y = 30.88, z = -21.46},
{x = 67.43, y = 40.07, z = -21.46},
{x = 55.40, y = 40.07, z = -21.46},
{x = 67.43, y = 30.43, z = -21.46},
{x = 55.40, y = 30.43, z = -21.46}
}
local function DisplayRockets(v, tx, ty, tz, txA, tyA, tzA)
for i = 1,8 do
if v.rockets[i] ~= nil then
local r = rp[i]
DoRocket(v.rockets[i], tx, ty, tz, r.x, r.y, r.z, txA, tyA, tzA)
end
end
end
local function PositionTurret(v, xA, yA, zA)
local xO, yO, zO = Rotate3D (0, -0.5, 22.24, xA, yA, zA)
local turret = v.turret
local tx, ty, tz = v.x + xO, v.y + yO, v.z + zO
ResetPosition(turret.Ent, tx, ty, tz)
local quat = Q.FromEuler(rad(turret.pAng),rad(turret.yAng),0)
turret.quat = Q.Mul(v.quat, quat)
local txA, tyA, tzA = Q.ToEuler(turret.quat)
ResetRotation(turret.Ent, deg(txA), deg(tyA), deg(tzA))
PositionGun('L', v, tx, ty, tz, txA, tyA, tzA)
PositionGun('R', v, tx, ty, tz, txA, tyA, tzA)
DisplayRockets(v, tx, ty, tz, txA, tyA, tzA)
end
local function GotEnts(e, v)
if v.turret == nil then
PromptLocal(e, "No turret!")
return false
end
if v.thighs['L'] == nil or v.thighs['R'] == nil then
PromptLocal(e, "Not enough thigh entities!")
return false
end
if v.shins['L'] == nil or v.shins['R'] == nil then
PromptLocal(e, "Not enough shin entities!")
return false
end
if v.feet['L'] == nil or v.feet['R'] == nil then
PromptLocal(e, "Not enough feet entities!")
return false
end
if v.guns['L'] == nil or v.guns['R'] == nil then
PromptLocal(e, "Not enough guns entities!")
return false
end
return true
end
local function WrapAngle(ang)
if ang > 180 then
return ang - 360
elseif ang < -180 then
return ang + 360
end
return ang
end
local function fireRocket(v)
local t = v.turret
local i = random(1,8)
if v.rockets[i] ~= nil then
if mrFire ~= nil then
mrFire(v.rockets[i], t.quat)
v.rockets[i] = nil
end
end
end
local function rocketsLeft(v)
for i = 1,8 do
if v.rockets[i] ~= nil then return true end
end
return false
end
local function MoveTurret(v)
local turret = v.turret
turret.yAng = turret.yAng + turret.yAngInc
if turret.yAng < -turretMaxAng or turret.yAng > turretMaxAng then
turret.yAngInc = -turret.yAngInc
end
end
local maxSpeed = 20
local acceleration = 0.03
local function MoveMoose(v)
v.z = v.z + 0.45
end
local function MoveLegs(v)
local leftThigh = v.thighs['L']
local rightThigh = v.thighs['R']
leftThigh.pAng = leftThigh.pAng + v.angleInc
rightThigh.pAng = rightThigh.pAng - v.angleInc
if leftThigh.pAng < -25 or leftThigh.pAng > 25 then
v.angleInc = -v.angleInc
end
local leftShin = v.shins['L']
local rightShin = v.shins['R']
if leftThigh.pAng > -25 and v.angleInc > 0 then
if leftThigh.pAng < -10 then
leftShin.pAng = leftShin.pAng - 0.6
elseif leftThigh.pAng > 10 then
if leftThigh.pAng < 20 then
leftShin.pAng = leftShin.pAng + 1.2
else
ftDown = 'L'
end
end
elseif leftThigh.pAng > 10 then
if leftThigh.pAng < 20 and v.angleInc < 0 then
leftShin.pAng = leftShin.pAng - 0.3
end
else
leftShin.pAng = 0
end
if rightThigh.pAng > -25 and v.angleInc < 0 then
if rightThigh.pAng < -10 then
rightShin.pAng = rightShin.pAng - 0.6
elseif rightThigh.pAng > 10 then
if rightThigh.pAng < 20 then
rightShin.pAng = rightShin.pAng + 1.2
else
ftDown = 'R'
end
end
elseif rightThigh.pAng > 10 then
if rightThigh.pAng < 20 and v.angleInc > 0 then
rightShin.pAng = rightShin.pAng - 0.3
end
else
rightShin.pAng = 0
end
end
local function HandleWeapons(v)
if rocketsLeft(v) and random (1,200) == 50 then
fireRocket(v)
end
if random(1,10) == 1 and MfShoot ~= nil then
local gun = random(1,2)
local bar = random(1,2)
if gun == 1 then gun = 'L' else gun = 'R' end
if bar == 1 then bar = 'T' else bar = 'B' end
MfShoot(v.guns[gun].Ent, bar)
end
end
function moose_main(e)
local v = mooseVars[e]
if v == nil then
local Ent = g_Entity[e]
mooseVars[e] =
{state = 'init',
thighs = {}, shins = {}, feet = {},
turret = nil, rockets = {}, guns = {},
x = Ent.x, y = Ent.y, z = Ent.z,
quat = Q.FromEuler(0, 0, 0),
vmAng = 0, rotAng = 0, turnAng = 0,
speed = 0, timer = math.huge,
angleInc = 0.27, yAngInc = 0.17
}
return
end
if v.state == 'init' and GotEnts(e, v) then
v.state = 'idle'
v.thighs['L'].pAng = 25
v.thighs['R'].pAng = -25
v.timer = g_Time + random(3000,5000)
end
if v.state == 'idle' and g_Time > v.timer then
v.state = 'move'
end
if v.state == 'idle' or v.state == 'move' then
local xA, yA, zA = Q.ToEuler(v.quat)
PositionMoose(e, v, xA, yA, zA)
if v.turret ~= nil then
PositionTurret(v, xA, yA, zA)
end
if v.state == 'idle' then return end
MoveTurret(v)
MoveMoose(v)
MoveLegs(v)
HandleWeapons(v)
end
end
Been there, done that, got all the T-Shirts!