本文通過(guò)解析 dota_addons/ui_example里面的inventory_item控件來(lái)說(shuō)明如何在dota2rpg的UI中制作一個(gè)可拖動(dòng)的控件。
一众弓、可拖動(dòng)控件的注冊(cè)和回調(diào)的聲明
在dota2中蒲跨,任何的panel都可以變?yōu)榭赏蟿?dòng)的元素剩彬,需要在定義這個(gè)panel的時(shí)候,將這個(gè)panel的draggable
tag設(shè)置為true句灌。即:
<Panel draggable="true" />
這個(gè)panel就可以變?yōu)榭赏蟿?dòng)的panel揍堕。
他們就可以通過(guò)$.RegisterEventHandler( 'DragEnter', panel, OnDragEnter );
這類語(yǔ)句來(lái)綁定他們的拖動(dòng)回調(diào)函數(shù)料身。
這些的事件包括:
$.RegisterEventHandler( 'DragEnter', panel, OnDragEnter );
$.RegisterEventHandler( 'DragDrop', panel, OnDragDrop );
$.RegisterEventHandler( 'DragLeave', panel, OnDragLeave );
$.RegisterEventHandler( 'DragStart', panel, OnDragStart );
$.RegisterEventHandler( 'DragEnd', panel, OnDragEnd);
當(dāng)然,這也不代表你一定需要定義這么多東西衩茸,如果你只需要響應(yīng)DragStart和DragEnd芹血,那么根據(jù)你的實(shí)際需要去寫(xiě)即可。
二、拖動(dòng)回調(diào)函數(shù)
拖動(dòng)具體需要哪些函數(shù)需要根據(jù)制作的實(shí)際需要去寫(xiě)祟牲。在dota2的可拖動(dòng)的物品中隙畜,需要的函數(shù)包括:
OnDragStart:當(dāng)拖動(dòng)開(kāi)始的時(shí)候->需要隱藏物品的提示,創(chuàng)建拖動(dòng)的圖像说贝,然后給拖動(dòng)開(kāi)始的格子添加一個(gè)是從這里開(kāi)始拖動(dòng)的顯示效果议惰。
OnDragLeave:當(dāng)拖動(dòng)著panel的鼠標(biāo)移動(dòng)離開(kāi)panel的時(shí)候(這個(gè)離開(kāi)可能是初始格子也可以是中途某個(gè)經(jīng)過(guò)的格子)->取消這個(gè)格子的高亮狀態(tài)
DragEnter: 當(dāng)拖動(dòng)著的panel鼠標(biāo)移動(dòng)到panel上方的時(shí)候->高亮這個(gè)格子來(lái)表示你想要拖動(dòng)?xùn)|西到這個(gè)格子里;
OnDragDrop:當(dāng)拖動(dòng)著panel的鼠標(biāo)移動(dòng)到panel上方并松開(kāi)的時(shí)候->你想要交換拖動(dòng)的原始格子和目標(biāo)格子中的物品(目標(biāo)格子可能為空)乡恕;
OnDragEnd:當(dāng)拖動(dòng)著panel的鼠標(biāo)松開(kāi)的時(shí)候-> 無(wú)論是拖動(dòng)到另一個(gè)格子上還是拖動(dòng)到地上了言询,都會(huì)調(diào)用這個(gè)回調(diào),因此需要在OnDragDrop
里面對(duì)拖動(dòng)到另一個(gè)格子里的事件做出響應(yīng)傲宜。
具體的回調(diào)函數(shù)如下:
function OnDragStart( panelId, dragCallbacks )
{
// m_Item是在全局里保存的當(dāng)前物品的id运杭,如果不存在則不開(kāi)始拖動(dòng)
if ( m_Item == -1 )
{
return true;
}
// 獲取物品名稱
var itemName = Abilities.GetAbilityName( m_Item );
// 隱藏正在顯示的物品提示
ItemHideTooltip();
// 創(chuàng)建用來(lái)拖動(dòng)的那個(gè)panel,也就是被拖動(dòng)的圖像函卒,他并不是原來(lái)顯示在那里的那個(gè)物品圖標(biāo)辆憔,而是新建的
var displayPanel = $.CreatePanel( "DOTAItemImage", $.GetContextPanel(), "dragImage" );
// 在創(chuàng)建的那個(gè)panel里面保存一些數(shù)據(jù),在其他的回調(diào)中可以用
displayPanel.itemname = itemName; // 物品名稱
displayPanel.contextEntityIndex = m_Item; // 物品序列號(hào)ID
displayPanel.data().m_DragItem = m_Item; // 拖動(dòng)的物品ID报嵌,其實(shí)這個(gè)data()并不是必要的虱咧,甚至這一步指令也不是必要的
displayPanel.data().m_DragCompleted = false; // 拖動(dòng)到底是拖動(dòng)到另一個(gè)格子還是拖動(dòng)到地上的標(biāo)志位
// 將用來(lái)顯示的panel賦值給dragCallbacks.displayPanel就可以開(kāi)始跟隨鼠標(biāo)移動(dòng)了
// 這兩個(gè)offset定義了這個(gè)panel到底要在粘上去的位置產(chǎn)生多少偏移
// 因?yàn)閐isplayPanel的創(chuàng)建時(shí)在$.GetContextPanel,也就是inventory_item的xml里面創(chuàng)建的锚国,因此也就不需要偏移了
dragCallbacks.displayPanel = displayPanel;
dragCallbacks.offsetX = 0;
dragCallbacks.offsetY = 0;
// 這個(gè)class會(huì)隱藏當(dāng)前格子的物品圖標(biāo)腕巡,就給玩家感覺(jué)像是當(dāng)前的格子里的物品被拖走了
// 其實(shí)他還在原來(lái)的位置,只不過(guò)被隱藏了
// 被拖動(dòng)的是上面創(chuàng)建的那個(gè)displayPanel
$.GetContextPanel().AddClass( "dragging_from" );
return true;
}
function OnDragLeave( panelId, draggedPanel )
{
// 獲取正在拖動(dòng)的物品的ID
var draggedItem = draggedPanel.data().m_DragItem;
// 如果正在拖動(dòng)的就是當(dāng)前的物品(也就是第一次離開(kāi)原始的格子血筑,那么不做響應(yīng))
if ( draggedItem === null || draggedItem == m_Item )
return false;
// 如果是拖動(dòng)經(jīng)過(guò)了這個(gè)格子绘沉,因?yàn)镺nDragEnter添加了這個(gè)class
// 用來(lái)指示這個(gè)是當(dāng)前的目標(biāo),離開(kāi)的時(shí)候需要移除這個(gè)class
$.GetContextPanel().RemoveClass( "potential_drop_target" );
return true;
}
function OnDragEnter( a, draggedPanel )
{
// 獲取正在拖動(dòng)的物品ID
var draggedItem = draggedPanel.data().m_DragItem;
// 如果正在拖動(dòng)的就是當(dāng)前的物品(也就是第一次離開(kāi)原始的格子豺总,那么不做響應(yīng))
if ( draggedItem === null || draggedItem == m_Item )
return true;
// 拖動(dòng)經(jīng)過(guò)這個(gè)格子了车伞,高亮他,用來(lái)指示玩家想要拖動(dòng)物品到這個(gè)格子
$.GetContextPanel().AddClass( "potential_drop_target" );
return true;
}
// 這個(gè)是關(guān)鍵的回調(diào)喻喳,因?yàn)檫@個(gè)回調(diào)里有發(fā)送給服務(wù)器的交換物品的API
function OnDragDrop( panelId, draggedPanel )
{
// 獲取拖動(dòng)的物品ID
var draggedItem = draggedPanel.data().m_DragItem;
// 如果正在拖動(dòng)的就是當(dāng)前的物品(也就是第一次離開(kāi)原始的格子另玖,那么不做響應(yīng))
// 只需要標(biāo)志一下m_DragCompleted給OnDragEnd使用一下,讓他不要
// 丟物品到地上
if ( draggedItem === null )
return true;
draggedPanel.data().m_DragCompleted = true;
if ( draggedItem == m_Item ) // 拿起來(lái)又放到同一個(gè)格子里面
return true; // 返回沸枯,不做交換物品的操作
// 告知服務(wù)器玩家想要交換物品
var moveItemOrder =
{
OrderType: dotaunitorder_t.DOTA_UNIT_ORDER_MOVE_ITEM,
TargetIndex: m_ItemSlot,
AbilityIndex: draggedItem
};
Game.PrepareUnitOrders( moveItemOrder );
return true;
}
// 拖動(dòng)結(jié)束,這個(gè)里面有丟物品到地上的API
function OnDragEnd( panelId, draggedPanel )
{
// 這個(gè)方法無(wú)論是拖動(dòng)到另一個(gè)格子里赂弓,還是拖動(dòng)到地上绑榴,都會(huì)響應(yīng)
// 如果調(diào)用這個(gè)之前沒(méi)有先調(diào)用OnDragDrop的話,那么就告知
// 服務(wù)器玩家想要丟東西到地上
if ( !draggedPanel.data().m_DragCompleted )
{
Game.DropItemAtCursor( m_QueryUnit, m_Item );
}
// 移除被拖動(dòng)的面板
draggedPanel.DeleteAsync( 0 );
// 清除拖動(dòng)來(lái)源的格子盈魁,也就是調(diào)用這個(gè)的格子的dragging_from效果
// UI里的交換物品圖標(biāo)翔怎,則會(huì)在服務(wù)器完成調(diào)換格子的操作之后
// 通知客戶端更新
$.GetContextPanel().RemoveClass( "dragging_from" );
return true;
}
三、其他的拖動(dòng)需求分析
- 可拖動(dòng)的窗口,例如
背包窗口
等赤套,那么OnDragEnter飘痛、OnDragDrop和OnDragLeave是不需要的,應(yīng)該只需要OnDragStart和OnDragEnd,此外容握,由于沒(méi)有兄弟panel的問(wèn)題宣脉,因此displayPanel
可以使用窗口本體,這樣你甚至連OnDragEnd都不需要寫(xiě)具體的內(nèi)容剔氏。 - 卡牌游戲的卡牌塑猖,卡牌游戲的卡牌,也只需要OnDragStart和OnDragEnd谈跛,此外羊苟,卡牌在拖動(dòng)過(guò)程中的響應(yīng),需要自己使用Schedule不斷更新感憾,比如更新卡牌的當(dāng)前狀態(tài)蜡励,指示當(dāng)前卡牌目標(biāo)位置等。