本文的目的是在DOTA2自定義遊戲中實作一個WASD控制的8方向移動,如果你想實作的是4方向的,稍微修改一點點代碼也可以做到。
通過本文,你也可以了解到DOTA2自定義按鍵的流程。
本文的所有代碼你都可以在我開源的Endless Dungeon項目中找到
一、聲明按鍵綁定
按鍵綁定需要寫在 game/dota_addons/你的項目檔案夾/addon_info.txt中(完整代碼)
"Default_Keys"
{
"01"
{
"Key" "D"
"Command" "+PlayerMoveRight"
"Name" "PlayerMoveRight"
}
"02"
{
"Key" "W"
"Command" "+PlayerMoveUp"
"Name" "PlayerMoveUp"
}
"03"
{
"Key" "S"
"Command" "+PlayerMoveDown"
"Name" "PlayerMoveDown"
}
"04"
{
"Key" "A"
"Command" "+PlayerMoveLeft"
"Name" "PlayerMoveLeft"
}
}
就完成了按鍵的聲明。
指令的回調聲明則是在content/dota_addons/你的項目名稱/panorama/scripts/custom_game/key_binding.js檔案中(完整代碼)
其中,要注意的是+代表按鍵按下,-代表按鍵擡起。
function WrapFunction(name) {
return function() {
Game[name]();
};
}
(function() {
Game.AddCommand( "+PlayerMoveUp", WrapFunction("PlayerMoveUp"), "", 0 );
Game.AddCommand( "+PlayerMoveDown", WrapFunction("PlayerMoveDown"), "", 0 );
Game.AddCommand( "+PlayerMoveLeft", WrapFunction("PlayerMoveLeft"), "", 0 );
Game.AddCommand( "+PlayerMoveRight", WrapFunction("PlayerMoveRight"), "", 0 );
Game.AddCommand( "-PlayerMoveUp", WrapFunction("PlayerMoveUp_End"), "", 0 );
Game.AddCommand( "-PlayerMoveDown", WrapFunction("PlayerMoveDown_End"), "", 0 );
Game.AddCommand( "-PlayerMoveLeft", WrapFunction("PlayerMoveLeft_End"), "", 0 );
Game.AddCommand( "-PlayerMoveRight", WrapFunction("PlayerMoveRight_End"), "", 0 );
)();
這裡需要注意的問題是,這個js檔案,你最好一次性寫完之後,隻在custom_ui_menifest.xml裡面引用他,這也是為什麼需要用WrapFunction方法來把回調放在其他的檔案中,這樣如果你如果修改回調方法并儲存js檔案後,引擎重新運作js檔案就不會運作到Game.AddCommand,否則,指令綁定就會失效導緻按鍵就會無響應(這是一個已知的BUG,很影響debug的效率)。
二、js的指令回調
在任意被引用的js檔案中寫明指令的回調如下:
Game.PlayerMoveUp = function() {
GameEvents.SendCustomGameEventToServer("ed_player_start_move_up", {})
};
Game.PlayerMoveDown = function() {
GameEvents.SendCustomGameEventToServer("ed_player_start_move_down", {})
};
Game.PlayerMoveLeft = function() {
GameEvents.SendCustomGameEventToServer("ed_player_start_move_left", {})
};
Game.PlayerMoveRight = function() {
GameEvents.SendCustomGameEventToServer("ed_player_start_move_right", {})
};
Game.PlayerMoveUp_End = function() {
GameEvents.SendCustomGameEventToServer("ed_player_end_move_up", {})
};
Game.PlayerMoveDown_End = function() {
GameEvents.SendCustomGameEventToServer("ed_player_end_move_down", {})
};
Game.PlayerMoveLeft_End = function() {
GameEvents.SendCustomGameEventToServer("ed_player_end_move_left", {})
};
Game.PlayerMoveRight_End = function() {
GameEvents.SendCustomGameEventToServer("ed_player_end_move_right", {})
};
三、伺服器端的事件響應
伺服器在收到ed_player_start_move_up之類的事件之後,做出具體的響應,代碼如下,具體說明見注釋
function CEDGameMode:_RegisterCustomGameEventListeners()
-- 注冊移動的事件響應,上下左右的開始和結束(按下即開始,松開即結束)
CustomGameEventManager:RegisterListener("ed_player_start_move_up", function(_, keys)
self:On_ed_player_start_move_up(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_start_move_down", function(_, keys)
self:On_ed_player_start_move_down(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_start_move_left", function(_, keys)
self:On_ed_player_start_move_left(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_start_move_right", function(_, keys)
self:On_ed_player_start_move_right(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_end_move_up", function(_, keys)
self:On_ed_player_end_move_up(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_end_move_down", function(_, keys)
self:On_ed_player_end_move_down(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_end_move_left", function(_, keys)
self:On_ed_player_end_move_left(keys)
end)
CustomGameEventManager:RegisterListener("ed_player_end_move_right", function(_, keys)
self:On_ed_player_end_move_right(keys)
end)
end
-- 上下左右四個方向
local upVector = Vector(0, 1, 0)
local downVector = Vector(0, - 1, 0)
local leftVector = Vector(- 1, 0, 0)
local rightVector = Vector(1, 0, 0)
-- 核心的控制上下左右移動的循環
local function createMovingTimer(hero)
if hero.m_MovingTimer then
return
end
-- 建立按鍵計時器,Timer函數需要https://github.com/XavierCHN/EndlessDungeon/blob/master/game/ed/scripts/vscripts/utils/funcs.lua
-- 中的Timer函數
hero.m_MovingTimer = Timer(function()
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingUp = false
hero.bMovingDown = false
hero.bMovingLeft = false
hero.bMovingRight = false
return 0.03
end
local movingVector = Vector(0, 0, 0)
-- 根據玩家英雄目前上下左右的狀态,計算玩家目前應該移動的方向
-- 如按下A則應當向左移動
-- 同時按AD應當不移動
-- 同時按WD應當向右上方移動
if hero.bMovingUp then
movingVector = movingVector + upVector
end
if hero.bMovingDown then
movingVector = movingVector + downVector
end
if hero.bMovingLeft then
movingVector = movingVector + leftVector
end
if hero.bMovingRight then
movingVector = movingVector + rightVector
end
movingVector = movingVector:Normalized()
-- 具體的移動
if movingVector.x == 0 and movingVector.y == 0 then
-- 如果x, y均等于0,則不移動
else
-- 開始移動
if not hero:IsStunned() then
-- 直接設定面向,這樣快速交替按AD才會有響應
hero:SetForwardVector(movingVector)
-- 往移動的方向移動一小步
ExecuteOrderFromTable({
UnitIndex = hero:entindex(),
OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
Position = hero:GetOrigin() + movingVector * 32
})
else
hero:Stop()
end
end
return 0.03
end)
end
-- 開始向上移動的按鍵響應,其他方向的與此類似
function CEDGameMode:On_ed_player_start_move_up(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingUp = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
-- 置向上移動為true
hero.bMovingUp = true
end
function CEDGameMode:On_ed_player_start_move_down(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingDown = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingDown = true
end
function CEDGameMode:On_ed_player_start_move_left(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingLeft = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingLeft = true
end
function CEDGameMode:On_ed_player_start_move_right(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingRight = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingRight = true
end
function CEDGameMode:On_ed_player_end_move_up(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingUp = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingUp = false
end
function CEDGameMode:On_ed_player_end_move_down(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingDown = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingDown = false
end
function CEDGameMode:On_ed_player_end_move_left(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingLeft = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingLeft = false
end
function CEDGameMode:On_ed_player_end_move_right(keys)
local player = PlayerResource:GetPlayer(keys.PlayerID)
if not player then
return
end
local hero = player:GetAssignedHero()
if not hero then
return
end
if not (IsValidEntity(hero) and hero:IsAlive()) then
hero.bMovingRight = false
ShowError("ed_hud_error_cannot_move", keys.PlayerID)
return
end
if not hero.m_MovingTimer then
createMovingTimer(hero)
end
hero.bMovingRight = false
end