dota2rpg panorama ui的可拖动控件的实现

本文通过解析 dota_addons/ui_example里面的inventory_item控件来说明如何在dota2rpg的UI中制作一个可拖动的控件。

一、可拖动控件的注册和回调的声明

在dota2中,任何的panel都可以变为可拖动的元素,需要在定义这个panel的时候,将这个panel的draggabletag设置为true。即:


这个panel就可以变为可拖动的panel。

他们就可以通过$.RegisterEventHandler( 'DragEnter', panel, OnDragEnter );这类语句来绑定他们的拖动回调函数。
这些的事件包括:

$.RegisterEventHandler( 'DragEnter', panel, OnDragEnter );
$.RegisterEventHandler( 'DragDrop', panel, OnDragDrop );
$.RegisterEventHandler( 'DragLeave', panel, OnDragLeave );
$.RegisterEventHandler( 'DragStart', panel, OnDragStart );
$.RegisterEventHandler( 'DragEnd', panel, OnDragEnd);

当然,这也不代表你一定需要定义这么多东西,如果你只需要响应DragStart和DragEnd,那么根据你的实际需要去写即可。

二、拖动回调函数

拖动具体需要哪些函数需要根据制作的实际需要去写。在dota2的可拖动的物品中,需要的函数包括:

OnDragStart:当拖动开始的时候->需要隐藏物品的提示,创建拖动的图像,然后给拖动开始的格子添加一个是从这里开始拖动的显示效果。
OnDragLeave:当拖动着panel的鼠标移动离开panel的时候(这个离开可能是初始格子也可以是中途某个经过的格子)->取消这个格子的高亮状态
DragEnter: 当拖动着的panel鼠标移动到panel上方的时候->高亮这个格子来表示你想要拖动东西到这个格子里;
OnDragDrop:当拖动着panel的鼠标移动到panel上方并松开的时候->你想要交换拖动的原始格子和目标格子中的物品(目标格子可能为空);
OnDragEnd:当拖动着panel的鼠标松开的时候-> 无论是拖动到另一个格子上还是拖动到地上了,都会调用这个回调,因此需要在OnDragDrop里面对拖动到另一个格子里的事件做出响应。

具体的回调函数如下:

function OnDragStart( panelId, dragCallbacks )
{
    // m_Item是在全局里保存的当前物品的id,如果不存在则不开始拖动
    if ( m_Item == -1 )
    {
        return true;
    }

    // 获取物品名称
    var itemName = Abilities.GetAbilityName( m_Item );

    // 隐藏正在显示的物品提示
    ItemHideTooltip();

    // 创建用来拖动的那个panel,也就是被拖动的图像,他并不是原来显示在那里的那个物品图标,而是新建的
    var displayPanel = $.CreatePanel( "DOTAItemImage", $.GetContextPanel(), "dragImage" );
    // 在创建的那个panel里面保存一些数据,在其他的回调中可以用
    displayPanel.itemname = itemName; // 物品名称
    displayPanel.contextEntityIndex = m_Item; // 物品序列号ID
    displayPanel.data().m_DragItem = m_Item; // 拖动的物品ID,其实这个data()并不是必要的,甚至这一步指令也不是必要的
    displayPanel.data().m_DragCompleted = false; // 拖动到底是拖动到另一个格子还是拖动到地上的标志位

    // 将用来显示的panel赋值给dragCallbacks.displayPanel就可以开始跟随鼠标移动了
    // 这两个offset定义了这个panel到底要在粘上去的位置产生多少偏移
    // 因为displayPanel的创建时在$.GetContextPanel,也就是inventory_item的xml里面创建的,因此也就不需要偏移了
    dragCallbacks.displayPanel = displayPanel;
    dragCallbacks.offsetX = 0; 
    dragCallbacks.offsetY = 0; 
    
    // 这个class会隐藏当前格子的物品图标,就给玩家感觉像是当前的格子里的物品被拖走了
    // 其实他还在原来的位置,只不过被隐藏了
    // 被拖动的是上面创建的那个displayPanel
    $.GetContextPanel().AddClass( "dragging_from" );
    return true;
}

function OnDragLeave( panelId, draggedPanel )
{
    // 获取正在拖动的物品的ID
    var draggedItem = draggedPanel.data().m_DragItem;
    // 如果正在拖动的就是当前的物品(也就是第一次离开原始的格子,那么不做响应)
    if ( draggedItem === null || draggedItem == m_Item )
        return false;

    // 如果是拖动经过了这个格子,因为OnDragEnter添加了这个class
    // 用来指示这个是当前的目标,离开的时候需要移除这个class
    $.GetContextPanel().RemoveClass( "potential_drop_target" );
    return true;
}


function OnDragEnter( a, draggedPanel )
{
    // 获取正在拖动的物品ID
    var draggedItem = draggedPanel.data().m_DragItem;

    // 如果正在拖动的就是当前的物品(也就是第一次离开原始的格子,那么不做响应)
    if ( draggedItem === null || draggedItem == m_Item )
        return true;

    // 拖动经过这个格子了,高亮他,用来指示玩家想要拖动物品到这个格子
    $.GetContextPanel().AddClass( "potential_drop_target" );
    return true;
}

// 这个是关键的回调,因为这个回调里有发送给服务器的交换物品的API
function OnDragDrop( panelId, draggedPanel )
{
    // 获取拖动的物品ID
    var draggedItem = draggedPanel.data().m_DragItem;
    
    // 如果正在拖动的就是当前的物品(也就是第一次离开原始的格子,那么不做响应)
    // 只需要标志一下m_DragCompleted给OnDragEnd使用一下,让他不要
    // 丢物品到地上
    if ( draggedItem === null )
        return true;
    draggedPanel.data().m_DragCompleted = true;
    if ( draggedItem == m_Item ) // 拿起来又放到同一个格子里面
        return true; // 返回,不做交换物品的操作

    // 告知服务器玩家想要交换物品
    var moveItemOrder =
    {
        OrderType: dotaunitorder_t.DOTA_UNIT_ORDER_MOVE_ITEM,
        TargetIndex: m_ItemSlot,
        AbilityIndex: draggedItem
    };
    Game.PrepareUnitOrders( moveItemOrder );

    return true;
}

// 拖动结束,这个里面有丢物品到地上的API
function OnDragEnd( panelId, draggedPanel )
{
    // 这个方法无论是拖动到另一个格子里,还是拖动到地上,都会响应
    // 如果调用这个之前没有先调用OnDragDrop的话,那么就告知
    // 服务器玩家想要丢东西到地上
    if ( !draggedPanel.data().m_DragCompleted )
    {
        Game.DropItemAtCursor( m_QueryUnit, m_Item );
    }

    // 移除被拖动的面板
    draggedPanel.DeleteAsync( 0 );

    // 清除拖动来源的格子,也就是调用这个的格子的dragging_from效果
    // UI里的交换物品图标,则会在服务器完成调换格子的操作之后
    // 通知客户端更新
    $.GetContextPanel().RemoveClass( "dragging_from" );
    
    return true;
}

三、其他的拖动需求分析

  1. 可拖动的窗口,例如背包窗口等,那么OnDragEnter、OnDragDrop和OnDragLeave是不需要的,应该只需要OnDragStart和OnDragEnd,此外,由于没有兄弟panel的问题,因此displayPanel可以使用窗口本体,这样你甚至连OnDragEnd都不需要写具体的内容。
  2. 卡牌游戏的卡牌,卡牌游戏的卡牌,也只需要OnDragStart和OnDragEnd,此外,卡牌在拖动过程中的响应,需要自己使用Schedule不断更新,比如更新卡牌的当前状态,指示当前卡牌目标位置等。

你可能感兴趣的:(dota2rpg panorama ui的可拖动控件的实现)