iT邦幫忙

2025 iThome 鐵人賽

DAY 17
1

原本我們只打算讓小女巫上下移動,但身為一位優秀的巫師,偶爾後退拉開距離,或是加速向前衝刺都是戰鬥必備的技巧!

因此,今天我們將為小女巫加入更完整的操控:上下移動,以及能夠微幅前後調整位置的功能。為了增加遊戲的真實感,我打算設定設定:

  • 上下移動:速度一致。
  • 向前微移:速度較慢,維持小女巫向前飛行的基本慣性。
  • 向後微退:速度最慢,作為緊急拉開距離或精準定位的手段。

今天會用到 Day 09 介紹的 Keyboard 功能,忘記的趕快去惡補一下!

▸ 程式碼實作:Witch 類別

為了管理小女巫的所有邏輯(移動、攻擊、血量等),我們將其封裝在一個獨立的 Witch 類別中,並繼承自 PIXI.Container

一、Witch 類別架構與速度設定

class Witch extends PIXI.Container {

    // 儲存女巫的 Sprite 物件
	private _sprite: PIXI.Sprite;
    // 用於計算每幀的移動方向
	private _moveDirection: PIXI.Point = new PIXI.Point(0, 0);
    // 移動速度
	private _speed = {
		up: 0.2,    // 垂直移動速度
		down: 0.2,
		back: 0.1,  // 向後微退速度(最慢)
		front: 0.15 // 向前微移速度
	}

	constructor() {
		super();

		// 取得小女巫的紋理,並設定錨點置中
		const texture = pixi.assets.getSpritesheet("ironman2025_cook.圖集動畫.角色").textures["witch"];
		const spirte = this._sprite = new PIXI.Sprite({
			texture: texture,
			anchor: 0.5
		});
		this.addChild(spirte);
	}

	// ... (update 函數在下一段)
}
  • PIXI.PointPIXI.Point 是 PixiJS 中用來表示 二維座標向量(Vector) 的實用類別。在這裡,我們使用它來儲存和計算小女巫在 X 軸(水平)Y 軸(垂直) 上的移動方向和距離。使用 dir.set(0, 0) 可以快速將向量歸零,方便每一幀重新計算移動量。
  • _speed:我們在這裡定義了四個方向的速度。透過設定不同的數值,實現了「垂直移動速度一致」、「向前微移」和「向後微退最慢」的效果,增強了遊戲的操作手感。
  • Spritesheet:為了方便管理之後的所有角色資源,包含敵人,我打算將所有的角色圖放在一個圖集動畫裡面,之後也可能會有其他像是 UI、特效之類的。

二、update() 函數:移動與邊界限制

所有遊戲物件的邏輯都在 update(dt: number) 函數中執行,確保移動與幀率無關。

// 承接上段的 Witch 類別

	update(dt: number): void {
		// 1. 偵測鍵盤輸入
		const isUp = keyboard.isDown(Key.W);
		const isDown = keyboard.isDown(Key.S);
		const isBack = keyboard.isDown(Key.A);
		const isFront = keyboard.isDown(Key.D);
        
        // 將屬性暫存成區域常數方便使用
		const speed = this._speed;
        // 重設移動速度,每一幀都會根據按鍵狀態重新計算
		const dir = this._moveDirection.set(0, 0);

		// 2. 依據按鍵計算移動向量(使用 dt 確保不同裝置之間的畫面同步)
		if (isUp) dir.y -= speed.up * dt;
		if (isDown) dir.y += speed.down * dt;
		if (isBack) dir.x -= speed.back * dt;
		if (isFront) dir.x += speed.front * dt;

		// 3. 更新位置
		this.x += dir.x;
		this.y += dir.y;

		// 4. 邊界限制(Stage Boundaries)
		const width = pixi.stageWidth;
		const height = pixi.stageHeight;
		// 計算物件的「半寬/半高」,因為錨點設為 0.5,所以取 Sprite 的寬高即可
		const halfWidth = this._sprite.width * this.scale.x * 0.5;
		const halfHeight = this._sprite.height * this.scale.y * 0.5;

		// 限制水平移動範圍(避免超出左右邊界)
		this.x = Math.max(halfWidth, Math.min(this.x, width - halfWidth));
		// 限制垂直移動範圍(避免超出上下邊界)
		this.y = Math.max(halfHeight, Math.min(this.y, height - halfHeight));

	}

// ...
  • 移動邏輯:我們利用 dir.xdir.y 來累加不同按鍵的移動量,然後一次性將結果加到 this.xthis.y 上。
  • 邊界限制:這是避免主角飛出螢幕的關鍵。我們使用 Math.max()Math.min() 兩個函數來夾住主角的位置:
    • Math.max(最小值, ...):確保主角位置不會小於最小值(例如:不會從左邊界外飛入)。
    • Math.min(..., 最大值):確保主角位置不會大於最大值(例如:不會從右邊界飛出)。
      由於小女巫的錨點在圖片正中間,因此圖片寬高的一半,才會是小女巫的四邊界。

▸ 專案初始化與主循環整合

最後,我們調整 start() 函數,除了背景之外,也要記得呼叫小女巫的 update()

import pixi = CG.Pixi.pixi;
import keyboard = CG.Base2.keyboard; // 記得檢查是否正確引用鍵盤相關功能
import Key = CG.Base2.keyboards.Key;

async function start() {

	// 載入所有資源(包含小女巫的圖集)
	await pixi.assets
		.add("ironman2025_cook.圖片.背景_天空")
		.add("ironman2025_cook.圖片.遠景")
		.add("ironman2025_cook.圖片.前景")
		.add("ironman2025_cook.圖集動畫.角色") // 新增女巫圖集資源
		.load();

	// 初始化 Pixi。
	await pixi.initialize({ stageWidth: 960, stageHeight: 540 });

	// 創建視差背景實例 (ParallaxBackground class 沿用 Day 16 的程式碼)
	const parallaxBackground = new ParallaxBackground();
	pixi.root.addChild(parallaxBackground);

	// 創建小女巫實例
	const witch = new Witch();
	// 將小女巫放置在舞台左側
	witch.position.set(pixi.stageWidth * 0.25, pixi.stageHeight * 0.5);
	// 將小女巫加入到舞台中。
	pixi.root.addChild(witch);

	// 設置更新循環函數
	CG.Base2.addUpdateFunction((dt: number) => {
		parallaxBackground.update(dt);
		witch.update(dt); // 這裡要記得呼叫喔!不然小女巫就動不了了
	});
}

start();

// ... (ParallaxBackground class 程式碼)
// ... (Witch class 程式碼)

小女巫移動 預覽

點我查看範例程式碼

▸ 總結

我們今天成功為小女巫加入了完整的二維移動操控,並透過 Math.min/max 實現了邊界限制。現在小女巫已經準備好在廣大的夜空中自由飛行了!

不過,一位巫師在戰鬥中只會閃躲可不行。所以明天,我們要為小女巫裝上火力,實作她的核心戰鬥機制——自動攻擊,發射她的第一顆魔法彈!


上一篇
Day 16:主角登場!小女巫與背景的初步建置
下一篇
Day 18:自動攻擊 - 發射第一顆魔法彈
系列文
用 PixiJS 寫遊戲!告別繁瑣設定,在 Code.Gamelet 打造你的第一個遊戲23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言