iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
SideProject30

毫無自信的開發Web遊戲接龍-成為創造龍的英雄之路!系列 第 11

Day 11 拖曳紙牌的效果(三)如何一次拖曳多張卡牌

  • 分享至 

  • xImage
  •  

今日預計只實作如何一次拖曳 多張卡牌

多張卡牌拖曳的考察研究

這部分可能會讓很多人(我)失望,因為vue.draggable.next最近一次的合併更新在2021年8月,
所以目前Vue3無法像原本Vue2能使用vue.draggableMulti-drag的擴充功能,所以我捨棄使用套件原生多筆拖曳的想法和 拖曳多張牌完美的畫面效果

如果願意改變資料結構為巢狀Vue3版本還是有辦法對巢狀物件進行一次性的拖曳,但對我來說在未來資料處理的靈活性降低又提高判斷卡牌順序的複雜度因此不考慮。

想法邏輯

  1. 來源牌堆先拖曳一張牌A移動到目標牌堆的指定位置
  2. 來源牌堆A牌後的剩餘卡片複製到目標牌堆的指定位置後方
  3. 刪除來源牌堆A位置後的剩餘元素

實作後發現其實可以先複製一份來源牌堆目標牌堆移動後的結果,後續處理會更為靈活。

實作邏輯

  1. :move對應的函數中判斷可拖曳時,產生『若拖曳成功後,來源/目標陣列的新狀態』並封裝成一個箭頭函數儲存至ref變數changeOption
function limitLocalMove(evt) {
    // 限制同個牌堆無法拖曳
    const result = evt.from !== evt.to;
    if (result) {
        // 取得牌堆的來源、目標名稱,對應reactive`cardStacks`內的名稱
        const from = getDomName(evt.from);
        const to = getDomName(evt.to);
        const draggedContext = evt.draggedContext
        const { index, futureIndex } = draggedContext;
        // 產生多筆拖曳後,來源牌堆、目的牌堆的陣列變動後的結果
        const newFromCards = cardStacks[from].slice(0, index);
        const newToCards = [
            ...cardStacks[to].slice(0, futureIndex),
            ...cardStacks[from].slice(index),
            ...cardStacks[to].slice(futureIndex)
        ];
        // 將變動牌堆的函數暫存,預計等到拖曳完成後執行
        changeOption.value = () => {
            cardStacks[from] = newFromCards;
            cardStacks[to] = newToCards;
            changeOption.value = null;
        };
    }
    // 仍使用原生的拖曳效果
    return result;
}
  1. @change事件發生時,若定義ref變數changeOption有值則執行第1點暫存的函數,將最終結果更新對應的牌堆陣列之中。

@change只有在卡牌拖曳完成對陣列產生異動時才會自動被執行,此為draggable原始機制

function cardChange(event) {
   if (changeOption.value) {
       changeOption.value();
       changeOption.value = null;
   } else {
       console.log(`no trigger changeOption`);
   };
}
  1. 樣板調整除了<draggable>的屬性:move@change之外,眼尖的朋友可以注意到兩個<draggable>分別添加了ref="first"ref="second"對應到第一/第二牌堆的元素,主要是為了第1點在:move函數執行中可以判斷來源和目標陣列對應到cardStacks內的哪一個陣列。
<GameBoard>
    <div>
        <h4 class="title">牌堆1</h4>
        <draggable :list="cardStacks.first" group="pokers" itemKey="value"
            style="display: grid; grid-template-columns: repeat(13, 3rem); background-color: yellow;"
            :move="limitLocalMove" @change="cardChange" ref="first">
            <template #item="{ element, index }">
                <Card :value="element.value" :isOpen="element.isOpen" />
            </template>
        </draggable>
    </div>
    <div>
        <h4 class="title">牌堆2</h4>
        <draggable :list="cardStacks.second" group="pokers" itemKey="value"
            style="display: grid; grid-template-columns: repeat(13, 3rem); background-color: yellow;padding: 1px;"
            :move="limitLocalMove" @change="cardChange" ref="second">
            <template #item="{ element, index }">
                <Card :value="element.value" :isOpen="element.isOpen" />
            </template>
        </draggable>
    </div>
</GameBoard>

因為:move本身攜帶的資訊無法得到拖曳來源、目標的:list,但卻有紀錄拖曳列表來源、目標的DOM(from,to),所以撰寫一個函數可取得DOM所對應的牌組名稱。

function getDomName(dom) {
    if (dom == first.value.targetDomElement) {
        return 'first';
    } else if (dom == second.value.targetDomElement) {
        return 'second';
    } else {
        return 'none';
    }
}

上方的first/second都有先定義成ref(null)否則執行可是會失敗的喔!
如下所示:

<script setup>
const first = ref(null); 
const second = ref(null);
// ...
</script>

小結

雖然原本除了多筆拖曳還想要額外判斷特定順序,但花了蠻多時間在vue.draggable.next要如何正常實現畫面,最終還是只撰寫/實作多筆拖曳的功能。

實作前思考最理想的狀態,實作中則逐步去做取捨,若一昧追求完美便無法完成作品/寫稿,但身為軟體工程師同時需要考慮到作品的未來運行和功能上的調整,實作後仍需思考是否有改善的空間。

程式碼: https://github.com/kabuto412rock/ithelp-pokergame/tree/day11
Yes

參考


上一篇
Day 10 拖曳紙牌的效果(二)限制內部拖曳
下一篇
Day 12 實作拖曳卡牌只能置放到目標牌堆的牌尾、蓋牌無法拖曳
系列文
毫無自信的開發Web遊戲接龍-成為創造龍的英雄之路!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言