iT邦幫忙

2022 iThome 鐵人賽

DAY 21
1
Modern Web

30個遊戲程設的錦囊妙計系列 第 21

Trick 20: 把鎧甲拉到身上裝備的拖曳控制器

  • 分享至 

  • xImage
  •  

用滑鼠拖曳圖案的功能,在許多遊戲裏都能看得到,比如說拼圖遊戲、解謎遊戲、方塊遊戲,或是像暗黑破壞神(Diablo)裏的角色裝備介面,也需要用滑鼠拖曳鎧甲來進行裝備或鑲嵌寶石在盔甲上。
Diablo Equipment

我們今天就是要來寫一個功能完整的物件拖曳系統。

今天的專案要做什麼

我們今天要寫一個類似Diablo裏,把圖案在格子之間拖來拉去的介面。

專案會畫出物件要對齊的格子。接著會創造一些物件,並讓這些物件去訂閱滑鼠的事件,然後以滑鼠拖曳的設計邏輯去控制物件的移動。最後在放開滑鼠左鍵後,把物件放在離它最近的格子位置上。

雖然專案裏會用到少許CG繪圖的功能,不過我會儘量在每一行都加上說明,希望大家在看程式時不會有閱讀障礙。

滑鼠拖曳的邏輯

在滑鼠拖曳的邏輯中,最重要的就是要讓可拖曳的物件去監聽滑鼠左鍵按下去的事件。

監聽滑鼠,這是承續昨天事件驅動程式的概念。如果不太清楚這個概念的同學,可以先補個課再繼續。
Trick 19: 事件驅動的程式設計

當在物件收到滑鼠左鍵按下去的事件時,要先儲存這個時候物件和滑鼠的相對位置,接著要監聽滑鼠移動的事件,以及滑鼠左鍵放開的事件,然後進入拖曳狀態。

在收到滑鼠移動的事件時,利用目前滑鼠的位置以及先前儲存的物件相對位置進行計算,讓物件跟著滑鼠的位置移動。

在收到滑鼠左鍵放開的事件後結束拖曳狀態,將物件對齊格線放好,並取消訂閱滑鼠移動及放開左鍵的事件監聽。

// 定義格子大小
let gridSize = new Size(80, 80);

// 可拖曳物件的類別
class DraggableObj {
    // 用CG的功能畫出物件的圖案,詳情請看示範專案
    sprite: PIXI.Sprite = ...;
    // 用來存物件相對於滑鼠的位置
    relPos = new Point();
    
    // 建構子,要給初始位置
    constructor(position: Point) {
        // 將位置放到初始位置
        this.sprite.x = position.x;
        this.sprite.y = position.y;
        // 訂閱滑鼠左鍵點在this.sprite上的事件(支援觸控螢幕)
        this.sprite.on('pointerdown', this.onPointerDown, this);
    }
    // 收到滑鼠左鍵點在sprite的事件的接收器
    private onPointerDown(event: PIXI.InteractionEvent) {
        // 如果正在拖曳狀態就不用處理,直接離開
        if (this.dragging) {
            return;
        }
        // 進入拖曳狀態
        this.dragging = true;
        // 從事件中取得滑鼠的位置
        let mousePos = event.data.getLocalPosition(this.sprite.parent);
        // 稍稍平移物件的位置,才比較容易發現物件被滑鼠抓住了
        this.sprite.x += 5;
        this.sprite.y += 5;
        // 計算目前物件和滑鼠的相對位置
        this.relPos.x = this.sprite.x - mousePos.x;
        this.relPos.y = this.sprite.y - mousePos.y;

        /** 訂閱更多滑鼠事件,因為這些事件不管發生在哪都要收到
         * 所以我們要把事件的主人訂在繪圖引擎的根節點
         */
        // 訂閱滑鼠移動的事件(支援觸控螢幕)
        cg.pixi.root.on('pointermove', this.onPointerMove, this);
        // 訂閱滑鼠左鍵放開的事件(支援觸控螢幕)
        cg.pixi.root.on('pointerup', this.onPointerUp, this);
    }
    // 收到滑鼠移動事件的接收器
    private onPointerMove(event: PIXI.InteractionEvent) {
        // 從事件中取得滑鼠的位置
        let mousePos = event.data.getLocalPosition(this.sprite.parent);
        // 把sprite的位置移動到與滑鼠相對的正確位置
        this.sprite.x = mousePos.x + this.relPos.x;
        this.sprite.y = mousePos.y + this.relPos.y;
    }
    // 收到滑鼠左鍵放開事件的接收器
    private onPointerUp(event: PIXI.InteractionEvent) {
        const sprite = this.sprite;
        // 離開拖曳狀態
        this.dragging = false;
        // 取消訂閱
        cg.pixi.root.off('pointermove', this.onPointerMove, this);
        cg.pixi.root.off('pointerup', this.onPointerUp, this);
        // 將sprite的位置對齊格線
        sprite.position.set(
            Math.round(sprite.x / gridSize.width) * gridSize.width,
            Math.round(sprite.y / gridSize.height) * gridSize.height
        );
    }
}

// 建立兩個可拖曳的物件
new DraggableObj(new Point(100, 100));
new DraggableObj(new Point(300, 300));

在示範程式中,會有兩個代表武器的圖案可以用滑鼠拖來拉去,並且在放開滑鼠時自動對齊格線放好。同學們可以以這個範例為基礎,加上更多更完整的功能。這裏給同學們一些Idea:

  • 在程式一開始的時候就自動對齊所有武器
  • 拖曳的過程中可以讓對應的格子發亮
  • 依拖曳前、拖曳中等不同情況改變滑鼠游標的圖示

CG示範專案


以這個架構為藍本,還可以製作捲動頁面的捲動棒等更進階的介面拖曳功能。不過再繼續講下去,可能就會偏向個人寫作風格的討論了,所以我們在這兒打住,接下去就看各位同學的發揮了。

有興趣的同學可以看看小哈寫的相關的函式庫:
物件拖曳控制器 DragControl.ts
容器捲動控制器 ScrollControl.ts


上一篇
Trick 19: 事件驅動的程式設計
下一篇
Trick 21: 如何畫出貝茲那曼妙的曲線
系列文
30個遊戲程設的錦囊妙計32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言