vue3+ts+phaser3+vite 类似大富翁。实现内容:路径排列+玩家跳跃 效果如下
平台图片
具体实现
// phaserScene.ts
import Phaser from "phaser";
interface customParamType {
zoomRatio: number;
gameWidth: number;
gameHeight: number;
}
export class GameScene extends Phaser.Scene {
private tileWidth = 66; // 方块宽
private tileHeight = 50; // 方块高
private prizeH = 24; // 礼物宽
private prizeW = 24; // 礼物高
public zoomRatio: number
public tiles: Phaser.GameObjects.Image[]
public rewards: { index: number, object: Phaser.GameObjects.Image}[]
public player!: Phaser.GameObjects.Image
public currentTileIndex: number // 当前玩家所在位置
constructor(config: Phaser.Types.Scenes.SettingsConfig, customParam: customParamType) {
super(config); // 调用父类构造函数
this.zoomRatio = customParam.zoomRatio
this.tileWidth = this.tileWidth * this.zoomRatio
this.tileHeight = this.tileHeight * this.zoomRatio
this.prizeH = this.prizeH * this.zoomRatio
this.prizeW = this.prizeW * this.zoomRatio
this.tiles = []
this.rewards = []
this.currentTileIndex = 0
}
preload(): void {
this.load.image("tile", "/image/1732537181376995917.png");
this.load.image("player", "/image/1732537181376317481.png");
this.load.image("reward", "/image/1732537181376495211.png");
}
create(): void {
// 路径
const pathData = [
{ sX: 242, sY: 464, angle: 210, reduce: -1, itemList: [
{ x: 0, y: 0 , depth: -1, reward: false },
{ x: 0, y: 0 , depth: -2, reward: true },
{ x: 0, y: 0 , depth: -3, reward: false },
{ x: 0, y: 0 , depth: -4, reward: true },
{ x: 0, y: 0 , depth: -5, reward: false },
]
},
{ sX: 80, sY: 340, angle: -30, reduce: 1, itemList: [
{ x: 0, y: 0 , depth: -6, reward: true },
{ x: 0, y: 0 , depth: -7, reward: true },
{ x: 0, y: 0 , depth: -8, reward: false },
{ x: 0, y: 0 , depth: -9, reward: true },
{ x: 0, y: 0 , depth: -10, reward: false },
{ x: 0, y: 0 , depth: -11, reward: true },
]
},
{ sX: 247, sY: 217, angle: 0, reduce: -1, itemList: [
{ x: 0, y: 0 , depth: -13, reward: false }
]
},
{ sX: 200, sY: 233, angle: 210, reduce: -1, itemList: [
{ x: 0, y: 0 , depth: -12, reward: false },
{ x: 0, y: 0 , depth: -14, reward: true },
{ x: 0, y: 0 , depth: -15, reward: false },
{ x: 0, y: 0 , depth: -16, reward: true },
{ x: 0, y: 0 , depth: -17, reward: false },
]
},
{ sX: 102, sY: 131, angle: -30, reduce: 1, itemList: [
{ x: 0, y: 0 , depth: -18, reward: true },
{ x: 0, y: 0 , depth: -19, reward: false },
{ x: 0, y: 0 , depth: -20, reward: true },
{ x: 0, y: 0 , depth: -21, reward: true },
]
},
{ sX: 222, sY: 40, angle: 210, reduce: -1, itemList: [
{ x: 0, y: 0 , depth: -22, reward: false },
{ x: 0, y: 0 , depth: -23, reward: true }
]
}
]
pathData.forEach((pItem, i) => {
const angle = Phaser.Math.DegToRad(pItem.angle); // 倾斜角度(30 度)
const stepX = 120; // 每个 Tile 的水平间距
const stepY = Math.tan(angle) * stepX; // 根据角度计算垂直偏移
pItem.itemList.forEach((tItem, j) => {
const x = pItem.sX * this.zoomRatio + j * stepX * pItem.reduce
const y = pItem.sY * this.zoomRatio + j * stepY * pItem.reduce
const tile = this.add.image(x, y, 'tile')
tile.setDisplaySize(this.tileWidth, this.tileHeight)
tile.setDepth(tItem.depth)
// tiles.push({x, y, obj: tile})
this.tiles.push(tile)
if (tItem.reward) {
const rx = pItem.sX * this.zoomRatio + j * stepX * pItem.reduce
const ry = (pItem.sY - 14) * this.zoomRatio + j * stepY * pItem.reduce
const reward = this.add.image(rx, ry, "reward");
reward.setDisplaySize(this.prizeW, this.prizeH)
this.rewards.push({ index: i, object: reward });
}
})
})
// 创建玩家
this.player = this.add.image(242 * this.zoomRatio, 464 * this.zoomRatio, "player");
this.player.setDisplaySize(80, 80)
this.player.setOrigin(0.5, 1)
}
update(): void {
// TODO
}
// 玩家移动逻辑
public movePlayer = (steps: number) => {
const nextTileIndex = Math.min(this.currentTileIndex + steps, this.tiles.length - 1);
this.jumpStepByStep(this.player, this.tiles, this.currentTileIndex, nextTileIndex, () => {
this.currentTileIndex = nextTileIndex; // 更新玩家位置
console.log(`Player reached platform ${this.currentTileIndex}`);
});
};
public jumpStepByStep = (player: Phaser.GameObjects.Image, platforms: Phaser.GameObjects.Image[], startIndex: number, targetIndex: number, onComplete: Function) => {
if (startIndex >= targetIndex) {
onComplete && onComplete();
return;
}
const nextIndex = startIndex + 1;
const nextPlatform = platforms[nextIndex];
this.jumpToPlatform(player, nextPlatform, () => {
// 方块缩放效果
this.applyPlatformEffect(nextPlatform);
// 递归跳到下一个平台
this.jumpStepByStep(player, platforms, nextIndex, targetIndex, onComplete);
});
}
// 跳跃动画函数
public jumpToPlatform = (player: Phaser.GameObjects.Image, targetPlatform: Phaser.GameObjects.Image, onComplete: Function) => {
const startX = player.x;
const startY = player.y;
const targetX = targetPlatform.x;
const targetY = targetPlatform.y;
// console.log(startX, startY, targetX, targetY)
// 计算跳跃曲线高度(与距离成比例)
const distance = Phaser.Math.Distance.Between(startX, startY, targetX, targetY);
const jumpHeight = distance / 2; // 跳跃高度可以根据需求调整
// 自定义路径模拟抛物线
const topP = new Phaser.Math.Vector2(targetX, targetY - jumpHeight)
const topP2 = new Phaser.Math.Vector2(targetX, targetY)
const curve = new Phaser.Curves.Path(startX, startY);
curve.splineTo([topP, topP2]);
// 用路径控制跳跃动画
const duration = 500; // 动画时长
let progress = 0;
const curveGraphics = player.scene.add.graphics();
const tween = player.scene.tweens.add({
targets: { t: 0 },
t: 1,
duration,
ease: 'Linear',
onUpdate: (tween, target) => {
progress = target.t;
const point = curve.getPoint(progress);
player.setPosition(point.x, point.y);
// 可选:绘制路径调试
curveGraphics.clear();
curveGraphics.lineStyle(2, 0xff0000, 1);
curve.draw(curveGraphics, 64);
},
onComplete: () => {
curveGraphics.clear(); // 清除调试路径
player.setY(targetY - 10); // 确保玩家落在方块上
onComplete && onComplete();
},
});
}
// 平台效果(缩放动画)
public applyPlatformEffect = (platform: Phaser.GameObjects.Image) => {
platform.scene.tweens.add({
targets: platform,
scale: 0.06,
duration: 100,
yoyo: true,
ease: 'Quad.easeInOut',
});
}
}
使用
<template>
<div ref="gameContainer" class="game-container"></div>
<button @click="rollDice">掷骰子</button>
</template>
<script setup lang="ts">
// https://juejin.cn/post/7385098943941673014
// https://github.com/phaserjs/custom-build
// https://juejin.cn/post/7140833400835244045
import Phaser from 'phaser'
import { onMounted, onBeforeUnmount, ref } from "vue";
import { GameScene } from './phaserScene'
const gameContainer = ref<HTMLElement>(); // 绑定 Phaser 容器
let game: Phaser.Game; // Phaser 游戏实例
let gameWidth = 0;
let gameHeight = 0;
let zoomRatio = 1; // 缩放比
// 初始化 Phaser 游戏
const initGame = () => {
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.CANVAS,
parent: gameContainer.value,
width: gameWidth,
height: gameHeight,
canvasStyle: 'width:100%;height:100%',
backgroundColor: 0x291F61,
physics: {
default: "arcade",
},
scene: [
new GameScene({key: "GameScene"}, { zoomRatio, gameWidth, gameHeight })
],
loader: {
baseURL: 'https://xxx', // 设置自定义域名路径
}
};
game = new Phaser.Game(config);
game.input.enabled = false
};
// 掷骰子
const rollDice = () => {
const steps = Math.floor(Math.random() * 6) + 1; // 随机生成 1-6 的步数
const gameScene = game.scene.keys["GameScene"] as GameScene; // 获取场景实例
gameScene.movePlayer(steps)
};
// 初始化游戏
onMounted(() => {
if (gameContainer.value) {
// 获取游戏屏幕高度
gameHeight = gameContainer.value.clientHeight || 542
gameWidth = gameContainer.value.clientWidth || 351
// https://segmentfault.com/q/1010000021068970
gameHeight = gameHeight * window.devicePixelRatio
gameWidth = gameWidth * window.devicePixelRatio
zoomRatio = Number((gameHeight/542).toFixed(4))
console.log(gameHeight, gameWidth, zoomRatio)
}
initGame()
});
// 销毁游戏实例
onBeforeUnmount(() => {
if (game) {
game.destroy(true);
}
});
</script>
<style>
.game-container {
width: 351px;
height: 542px;
margin: 0 auto;
border: 1px solid #ccc;
position: relative;
}
button {
margin-top: 10px;
}
</style>
欢迎批评指正
参考内容: 都在代码里