前言
此我的项目应用 pixi.js 和 vue 实现,局部素材来自爱给网,本我的项目仅作用于 pixi.js 学习用处,侵权立删。
我的项目地址
shellingfordly/pixi-games
demo 地址
pixi-games
初始化我的项目
应用 vite 初始化我的项目
pnpm create vite my-vue-app
装置 pixi.js 和 pixi-tweener
pixi-tweener 一个做适度动画的开源库
pnpm i pixi.js pixi-tweener
次要逻辑
useParkour
此函数用于创立 pixi app,将场景,障碍物,人物增加到 app 中
- containerRef canvas 的容器
- gameStart 开始游戏
- start 开始状态
- score 分数
- hp 血量
export function useParkour() {const containerRef = ref();
const app = new Application({
width: BODY_HEIGHT,
height: BODY_WIDTH,
backgroundColor: 0xffffff,
});
const start = ref(false);
Tweener.init(app.ticker);
const container = new Container();
app.stage.addChild(container);
const player = new Player();
const {scene, runScene, stopScene} = useScene();
const {trap, runHurdle, score, hp} = useHurdle();
container.addChild(player);
container.addChild(scene);
container.addChild(trap);
container.sortChildren();
runScene();
function gameStart() {
start.value = true;
player.play();
const timer = setTimeout(() => {runHurdle(player);
clearTimeout(timer);
}, 1000);
}
watch(hp, (value) => {if (value === 0) {player.stop();
stopScene();
start.value = false;
}
});
onMounted(() => {if (containerRef.value) containerRef.value.appendChild(app.view);
});
return {containerRef, app, score, hp, gameStart, start};
}
useScene
此函数用于创立天空、高空场景
加载天空、高空纹理,创立平铺精灵 (TilingSprite),这个 TilingSprite 类比拟不便做场景的挪动
创立 ticker 递加精灵的 x 坐标实现场景挪动
export function useScene() {const loader = new Loader();
const scene = new Container();
scene.height = 130;
scene.zIndex = 1;
const ticker = new Ticker();
loader
.add("footer", FooterImg)
.add("sky", SkyImg)
.load(() => {
const footer = new TilingSprite(
loader.resources.footer.texture as Texture,
BODY_HEIGHT,
130
);
footer.y = BODY_WIDTH - 130;
footer.zIndex = 2;
const sky = new TilingSprite(
loader.resources.sky.texture as Texture,
BODY_HEIGHT,
BODY_WIDTH - 80
);
sky.tileScale.y = 0.6;
sky.zIndex = 1;
sky.y = -30;
scene.addChild(footer);
scene.addChild(sky);
scene.sortChildren();
const sceneTicker = () => {
footer.tilePosition.x -= 3;
sky.tilePosition.x -= 3;
};
ticker.add(sceneTicker);
});
function runScene() {ticker.start();
}
function stopScene() {ticker.stop();
}
return {scene, runScene, stopScene};
}
useHurdle
此函数用于创立障碍物
和创立场景其实差不多,只是障碍物是一般的精灵类 (Sprite),创立 ticker 挪动其 x
在挪动时做和人物的碰撞检测,如果碰撞则缩小生命值,如果没有则减少分数
export function useHurdle() {const loader = new Loader();
const trap = new Container();
const ticker = new Ticker();
const textures: Texture[] = [];
let player: Sprite | null = null;
const hp = ref(100);
const score = ref(0);
let timer: NodeJS.Timer;
trap.zIndex = 3;
loader.add("trap", TrapImg).load((_, resources) => {TrapTexturePosition.forEach((position) => {
const t = new Texture(
resources.trap.texture as any,
new Rectangle(...position)
);
textures.push(t);
});
});
loader.load(() => {timer = setInterval(() => {const index = Math.floor(Math.random() * 2) + 1;
const item = new Sprite(textures[index]);
item.width = 80;
item.height = 40;
item.x = BODY_HEIGHT;
item.y = BODY_WIDTH - 120;
trap.addChild(item);
let scoreFlag = true;
let hpFlag = true;
let isHit = false;
function itemTicker() {
item.x -= 8;
if (player && !isHit) {isHit = hitTestRectangle(player, item);
if (hpFlag && isHit) {
hp.value -= 10;
hpFlag = false;
if (hp.value === 0) stopGame();} else if (scoreFlag && item.x < player.x) {
score.value++;
scoreFlag = false;
}
}
if (item.x < -item.width) {ticker.remove(itemTicker);
trap.removeChild(item);
}
}
ticker.add(itemTicker);
}, 2000);
});
function runHurdle(target: Sprite) {
player = target;
ticker.start();}
function stopGame() {timer && clearInterval(timer);
ticker.stop();}
return {trap, runHurdle, score, hp};
}
Player
玩家类:实现跑动、上跳、滑铲、入场的成果
通过变换 Sprite 的 texture 实现跑动成果,监听键盘事件,上键时减 y,下键时更换滑铲纹理
export class Player extends Sprite {
defaultY = BODY_WIDTH - 160;
textures: Texture[] = [];
status: "run" | "jump" | "slide" = "run";
ticker = new Ticker();
constructor() {super();
this.width = 80;
this.height = 80;
this.x = -120;
this.y = this.defaultY;
this.zIndex = 10;
this.loader();
this.watchEvent();}
private loader() {const loader = new Loader();
loader.add("player", PlayerImg).load((_, resources) => {PlayerTexturePosition.forEach((position, i) => {
const texture = new Texture(
resources.player.texture as any,
new Rectangle(...position)
);
this.textures.push(texture);
});
});
}
private watchEvent() {document.addEventListener("keydown", this.keydown);
document.addEventListener("keyup", this.keyup);
}
private clearEvent() {document.removeEventListener("keyup", this.keydown);
document.removeEventListener("keydown", this.keyup);
}
private keydown = (e: any) => {if (e.code === "ArrowUp") {
this.status = "jump";
if (this.y === this.defaultY) {
Tweener.add({ target: this, duration: 0.3, ease: Easing.easeInOutQuint},
{y: this.y - 120}
);
}
} else if (e.code === "ArrowDown") {
this.status = "slide";
this.texture = this.textures[10];
}
};
private keyup = () => {this.status = "run";};
stop() {this.ticker.stop();
this.clearEvent();}
play() {
this.ticker.autoStart = true;
const runTicker = () => {this.down();
this.entrance();
if (this.status === "run") this.run();
else if (this.status === "jump") this.jump();};
this.ticker.add(runTicker);
}
// 跑
run() {this.texture = this.textures[Math.floor(Date.now() / 100) % 8];
}
jump() {this.texture = this.textures[(Math.floor(Date.now() / 100) % 5) + 11];
}
// 着落
down() {if (this.y < this.defaultY) {
this.status = "jump";
this.y += 5;
} else {if (this.status === "jump") {this.status = "run";}
}
}
// 入场
entrance() {if (this.x < 120) this.x += 5;
}
}