在这一教程中,我们将在上一教程的基础上,加入交互模块。
我们选择Gamepad Configs,选择Add Gamepad Config,在接入游戏手柄的时候可以使用编辑。
我们关注的是移动时的变量,我们为这四个按键创建变量Left Stick Y , Left Stick X,Right Stick X, Right Stick Y , 并把变量范围修改为-1~1.
对y我们勾选反向,来调整到正确的方向。
在我们控制手柄的时候,可以实时看到数值的变化。
在character菜单script选项卡下可以直接查看当前的脚本,勾选代表会在这些脚本中进行函数查找。
我们可以在script窗口中看到一列脚本名称,双击可打开任一脚本,如点击tutorial_locomotion.lua,可以看到事先已有的教程代码内容。
现在我们要做的是使用脚本来让I / O设备控制人物的行走状态。
首先,我们需要对Idle和Run状态添加一个脚本生成器,如下图所示。
这时候可以看到在文件夹对应位置出现了刚刚创建的脚本生成器:
双击可以进入脚本的节点编辑器。
在这里,我们可以看到一系列的回调函数,它们会在特定的条件下发起。
由于我们需要实现的是实时地控制人物的移动,所以我们可以创建一个On Pre Update Script的函数,我们在Value处填入我们的函数名onUpdateIdle。
在tutorial_locamotion.lua中,我们先把原来的代码删除,然后开始写onUpdateIdle的函数:
function onUpdateIdle()
local leftStickX = hkbGetVariable("LeftStickX")
local leftStickY = hkbGetVariable("LeftStickY")
local magnitude = math.sqrt(leftStickX * leftStickX + leftStickY * leftStickY)
if(magnitude > 0.5) then
hkbFireEvent("Go")
end
end
函数非常的简单,函数中以hkb开头的意味着它调用的是havok的SDK函数,这是一个C/C++函数。
首先,我们获取得到LeftStickX和LeftStickY的值,接下来我们计算这个2D向量的长度,如果它大于0.5,那么我们执行“Go”事件。
类似的,在相反情况下(小于0.5),我们使其停止。
function onUpdateRun()
local leftStickX = hkbGetVariable("LeftStickX")
local leftStickY = hkbGetVariable("LeftStickY")
local magnitude = math.sqrt(leftStickX * leftStickX + leftStickY * leftStickY)
if(magnitude < 0.5) then
hkbFireEvent("Stop")
end
end
我们再次打开脚本的编辑器,选中On Pre Update Script右边的三个点,将会显示我们要执行的函数。如果我们在这里写的函数不存在的话会显示错误。所以我们可以用这个键来检查绑定是否成功。
我们把原始的样例代码贴入。
-- globals
PI = 3.14159
TWO_PI = PI * 2.0
DEG_TO_RAD = PI / 180.0
RAD_TO_DEG = 180.0 / PI
UP_AXIS = hkVector4.new(0.0, 0.0, 1.0)
FORWARD_AXIS = hkVector4.new(0.0, -1.0, 0.0)
-- helper table to store the state of the game pad.
g_gamepadState =
{
m_leftStickX = 0,
m_leftStickY = 0,
m_leftStickMagnitude = 0,
m_leftStickAngle = 0,
m_rightStickX = 0,
m_rightStickY = 0,
m_rightStickMagnitude = 0,
m_lastLeftStickX = 0,
m_lastLeftStickY = 0,
m_leftStickHoldTime = 0
}
-- updates the state of the gamepad
function g_gamepadState:update()
self.m_rightStickX = hkbGetVariable("RightStickX")
self.m_rightStickY = hkbGetVariable("RightStickY")
self.m_rightStickMagnitude = math.sqrt( self.m_rightStickX * self.m_rightStickX +
self.m_rightStickY * self.m_rightStickY )
self.m_lastLeftStickX = self.m_leftStickX
self.m_lastLeftStickY = self.m_leftStickY
self.m_leftStickX = hkbGetVariable("LeftStickX")
self.m_leftStickY = hkbGetVariable("LeftStickY")
self.m_leftStickMagnitude = math.sqrt( self.m_leftStickX * self.m_leftStickX +
self.m_leftStickY * self.m_leftStickY )
self.m_leftStickAngle = math.atan2( self.m_leftStickX, self.m_leftStickY )
local stickDifference = (self.m_lastLeftStickX - self.m_leftStickX) *
(self.m_lastLeftStickX - self.m_leftStickX) +
(self.m_lastLeftStickY - self.m_leftStickY) *
(self.m_lastLeftStickY - self.m_leftStickY)
if( stickDifference < 0.1 ) then
self.m_leftStickHoldTime = self.m_leftStickHoldTime + hkbGetTimestep()
else
self.m_leftStickHoldTime = 0
end
end
-- called every time the idle state is updated
function onIdleUpdate()
local numDirections = 8
-- if the magnatiude is high enough start running
if( g_gamepadState.m_leftStickMagnitude > 0.5 ) then
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = computeDifference()
-- select the animation
local directionVariable = math.floor(directionDifference / (PI / (numDirections / 2)) + 0.5)
-- since the range of the direction variable is [-3, 3] we need to map negative
-- values to the animation index range in our selector which is [0,7]
if( directionVariable < 0 ) then
directionVariable = directionVariable + numDirections
end
-- select the animation in the manual selector generator
hkbSetVariable("DirectionAnimation", directionVariable)
-- raise the event to go (but don't spam the event queue)
if( hkbIsNodeActive("Idle to Run Selector") == false ) then
hkbFireEvent("Go")
end
end
end
-- called every time the run state is updated
function onRunUpdate()
-- if the magnatiude is low enough stop running, otherwise procedurally rotate the character
if( g_gamepadState.m_leftStickMagnitude < 0.5 and g_gamepadState.m_leftStickHoldTime > 0.1 ) then
hkbFireEvent("Stop")
elseif( g_gamepadState.m_leftStickMagnitude > 0.5 ) then
-- otherwise, check if the difference between the gamepad's angle and the character's angle
-- is large enough for a 180 turn
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = computeDifference()
-- if the difference is greater than this about, turn the character
local turn180Threashold = 115 * DEG_TO_RAD
-- if the difference is large, then turn 180 degrees
if ( (math.abs(directionDifference) > turn180Threashold) ) then
hkbFireEvent("Turn180")
end
end
end
-- called every time the run state is generated
function onRunGenerate()
-- don't try to turn if the character is already turning
if( hkbIsNodeActive("Run Turn 180") ) then
return
end
-- only rotate the character if the user is pushing up on the stick
if( g_gamepadState.m_leftStickMagnitude > 0.5 ) then
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = computeDifference()
-- compute the amount to turn the character
local turnSpeed = 4.0
local turnAmount = directionDifference * turnSpeed * hkbGetTimestep()
-- rotate the character to match the target angle
hkbSetWorldFromModel(hkbGetWorldFromModel() * hkQsTransform.new( UP_AXIS, -turnAmount))
end
end
-- called every time the idle to run state is updated
function onIdleToRunUpdate()
if( g_gamepadState.m_leftStickMagnitude < 0.5 ) then
hkbFireEvent("Stop")
end
end
-- computes the difference between the gamepad's angle and the character's angle
function computeDifference()
-- compute the angle of the character
local forward = hkVector4.new(0, 1, 0)
forward:setRotatedDir(hkbGetOldWorldFromModel():getRotation(), forward)
local characterAngle = math.atan2( forward[0], forward[1] )
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = g_gamepadState.m_leftStickAngle - g_cameraState.m_angle - characterAngle
-- keep the difference in the range of [0, pi]
if( directionDifference < -PI ) then
directionDifference = directionDifference + TWO_PI
end
if( directionDifference > PI) then
directionDifference = directionDifference - TWO_PI
end
return directionDifference
end
-- called every time the locomotion state is updated
function onLocomotionUpdate()
-- update the gamepad state
g_gamepadState:update()
-- update the camera state
g_cameraState:update3rdPerson()
-- Compute which foot is forward and store it in a behavior variable
local leftLegIndex = hkbGetBoneIndex("LeftLegCalf")
local rightLegIndex = hkbGetBoneIndex("RightLegCalf")
local leftLegModelSpace = hkbGetOldBoneModelSpace(leftLegIndex)
local rightLegModelSpace = hkbGetOldBoneModelSpace(rightLegIndex)
local leftForward = leftLegModelSpace:getTranslation():dot3(FORWARD_AXIS)
local rightForward = rightLegModelSpace:getTranslation():dot3(FORWARD_AXIS)
if rightForward > leftForward then
hkbSetVariable("IsRightFootForward", 1)
else
hkbSetVariable("IsRightFootForward", 0)
end
end
这里的代码显示了我们可以如何为lua表中的角色存储一些状态。
g_gamepadState =
{
m_leftStickX = 0,
m_leftStickY = 0,
m_leftStickMagnitude = 0,
m_leftStickAngle = 0,
m_rightStickX = 0,
m_rightStickY = 0,
m_rightStickMagnitude = 0,
m_lastLeftStickX = 0,
m_lastLeftStickY = 0,
m_leftStickHoldTime = 0
}
在函数function g_gamepadState:update()中,我们获取了gamepad的许多状态,通过调用这个函数,我们可以把我们之前的代码修改如下:
function onUpdateIdle()
g_gamepadState:update()
if(g_gamepadState.m_leftStickMagnitude > 0.5) then
hkbFireEvent("Go")
end
end
function onUpdateRun()
g_gamepadState:update()
if(g_gamepadState.m_leftStickMagnitude < 0.5) then
hkbFireEvent("Stop")
end
end
这是共享和复用代码的一个好办法。
接下来我们考虑加入转向的操作,我们首先加入一个计算手柄角度和角色角度之差的函数。
首先,通过模型矩阵来获取当前角色的旋转方位,存储在局部变量characterAngle中。接下来,直接计算手柄角度和角色角度的差。
-- computes the difference between the gamepad's angle and the character's angle
function computeDifference()
-- compute the angle of the character
local forward = hkVector4.new(0, 1, 0)
forward:setRotatedDir(hkbGetOldWorldFromModel():getRotation(), forward)
local characterAngle = math.atan2( forward[0], forward[1] )
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = g_gamepadState.m_leftStickAngle - g_cameraState.m_angle - characterAngle
-- keep the difference in the range of [0, pi]
if( directionDifference < -PI ) then
directionDifference = directionDifference + TWO_PI
end
if( directionDifference > PI) then
directionDifference = directionDifference - TWO_PI
end
return directionDifference
end
这时候,我们再次更新之前引入的代码:
function onUpdateIdle()
g_gamepadState:update()
local numDirection = 8
if(g_gamepadState.m_leftStickMagnitude > 0.5) then
local directionDifference = computeDifference()
local directionVariable = math.floor(directionDifference/(PI/numDirection))
if(directionVariable < 0) then
directionVariable = directionVariable + numDirections
end
hkbSetVariable("DirectionAnimation",directionVariable)
hkbFireEvent("Go")
end
end
function onUpdateRun()
g_gamepadState:update()
if(g_gamepadState.m_leftStickMagnitude < 0.5 and g_gamepadState.m_leftStickHoldTime > 0.1) then
hkbFireEvent("Stop")
elseif(g_gamepadState.m_leftStickMagnitude > 0.5) then
local directionDifference = computeDifference()
local turn180Threadhold = 115 * DEG_TO_RAD
if(math.abs(directionDifference) > turn180Threadhold) then
hkbFireEvent("Turn180")
end
end
end
为了实现运动转向,我们可以加入如下代码,和之前不一样,我们不在update中写这个代码,而是写在generate中。
function onRunGenerate()
-- don't try to turn if the character is already turning
if( hkbIsNodeActive("Run Turn 180") ) then
return
end
-- only rotate the character if the user is pushing up on the stick
if( g_gamepadState.m_leftStickMagnitude > 0.5 ) then
-- compute the difference between the gamepad's angle and the character's angle
local directionDifference = computeDifference()
-- compute the amount to turn the character
local turnSpeed = 4.0
local turnAmount = directionDifference * turnSpeed * hkbGetTimestep()
-- rotate the character to match the target angle
hkbSetWorldFromModel(hkbGetWorldFromModel() * hkQsTransform.new( UP_AXIS, -turnAmount))
end
end
这一代码的逻辑是:如果我们依旧在往一个方向行走,并且角色的朝向和我们希望的朝向有一定差别的话,我们会基于旋转速度来计算出我们希望角色旋转多少。接下来我们在模型视图执行变换。
我们再次进入script编辑器,在Generate回调中注册以上代码。