持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
一、前言
经过两天的努力,我们对pixijs有了更深刻的认知,先说一下今天的实现思路,如下:
a、开始界面,点击之后才加载场景
b、奔跑时背景滚动,造成视觉滚动
c、碰撞触发函数,提示游戏结束
改动有些大,为了避免大家伙看的云里雾里的,我尽量从头开始讲,也会在文章末尾贴一下源码。
注意: 1、本文使用ts,部分代码存在不兼容情况; 2、本文所使用的pixi版本为6.5.5。
下面就是成品啦,看看效果吧。
二、过程
1、基本步骤
正常的创建好我们的容器app,同时挂载到dom元素上,并加载要使用的图片,图片加载完调用我们的setup函数
// 创建我们的pixi容器
let app = new PIXI.Application({
width: 400, // default: 800 宽度
height: 400, // default: 600 高度
antialias: true, // default: false 反锯齿
transparent: false, // default: false 透明度
resolution: 1 // default: 1 分辨率
})
// 使用定时器变成异步等待标签加载完毕
setTimeout(() => {
console.log(app, PIXI)
document.getElementById("pixijsjs")?.appendChild(app.view)
app.loader
.add([
"tom.png",
"jerry.png",
"bg.png",
"gameover.jpeg"
])
.load(setup) // 调用自定义函数setup
}, 400)
2、创建三个场景和声明一些会用到的变量
用Container创建开始场景,游戏场景,结束场景
// 创建三个场景,开始游戏界面,游戏主页面,和游戏结束界面
let startGame = new PIXI.Container()
let Game = new PIXI.Container()
let overGame = new PIXI.Container()
// 游戏场景的变量
let cat: PIXI.Sprite
let mouse: PIXI.Sprite
let bg: PIXI.Sprite
let state: Function
// 键盘监测
let left = keyboard("ArrowLeft")
let right = keyboard("ArrowRight")
let start = keyboard("Enter")
let catVX = 0// 设置tom的速度属性
let bgVX = 0// 设置背景的速度属性
3、在setup函数里,往3个场景里添加各自的精灵
开场,只显示开始界面,其余界面通通隐藏。
// 将三个场景加入到容器里
app.stage.addChild(startGame)
app.stage.addChild(Game)
Game.visible = false
app.stage.addChild(overGame)
overGame.visible = false
// 开始场景的精灵
startScene()
// 游戏主场景的精灵
gameScene()
// 结束场景的精灵
overScene()
开始场景我们需要一点简单的文字,调用TextStyle生成下我们的开始界面文字!
let style = new PIXI.TextStyle({
fontFamily: "Futura",
fontSize: 30,
fill: "white"
});
let message = new PIXI.Text("按下回车键开始!", style)
message.x = 100
message.y = 180
startGame.addChild(message)
结束场景也比较简单,一张图和一段文字,记得顺序是先图后文字,先加入的在下层,所以必须先图后文,不然文字会被盖住!
let overbg = new PIXI.Sprite(app.loader.resources["gameover.jpeg"].texture)
overbg.width = 400
overbg.height = 400
overGame.addChild(overbg)
let style = new PIXI.TextStyle({
fontFamily: "Futura",
fontSize: 20,
fill: "#646cffaa"
});
let message = new PIXI.Text("游戏结束了,Tom 气炸了!", style)
message.x = 100
message.y = 10
overGame.addChild(message)
游戏场景需要加入我们的背景图,由于需要实现滚动效果,所以我们可以使用TilingSprite制作一个平铺图
PIXI.TilingSprite(loader加载好的图, 平铺宽度, 平铺高度)
这里有个容易忽略的点,就是bg的变量也需要同步设置为1800x400,不然会出现类似overflow:hidden的情况,就完全看不到bg宽高以外的背景图。
(因为用的pixi是6.5.5,写法上和5.x版本的PIXI.extras.TilingSprite存在差别,我一度以为是不起效了,结果是我bg宽度没同步设成1800)
// 设置背景
// bg = new PIXI.Sprite(app.loader.resources["bg.jpeg"].texture)
bg = new PIXI.TilingSprite(app.loader.resources["bg.png"].texture, 1800, 400) // 替换为这一行代码
bg.x = 0
bg.y = 0
bg.width = 1800
bg.height = 400
Game.addChild(bg)
然后是我们的主角tom和jerry,这里有个小点就是,我的素材里,tom的方向刚好是反了,所以我就稍微查了一下在pixi里如何实现sprite镜像水平翻转,就是anchor设置好scale缩放的中心点,然后设置scale乘以-1。
cat = new PIXI.Sprite(app.loader.resources["tom.png"].texture)
// 调整汤姆的大小和位置
//Change the sprite's position
cat.x = 40
cat.y = 190
// 用cat.scale.set(0.5, 0.5)也可以,缩放大小为原图的一半
//Change the sprite's size
cat.width = 80
cat.height = 240
// 实现sprite水平镜像反转
// 设置anchor 0left 0.5center 1right
cat.anchor.x = 0.5
// 水平缩放比例为-1
cat.scale.x *= -1
Game.addChild(cat)
mouse = new PIXI.Sprite(app.loader.resources["jerry.png"].texture)
// 调整杰瑞的大小和位置
//Change the sprite's position
mouse.x = 300
mouse.y = 280
//Change the sprite's size
mouse.width = 40
mouse.height = 60
// 杰瑞应该比汤姆小只
Game.addChild(mouse)
4、先监听键盘事件,实现对应的函数
引入完官网给的自定义键盘识别函数keyboard ,我就直接放最后了。
目前是需要监听← →和enter键
// 完善一下left和right的press和release函数
left.press = () => {
// console.log(cat)
catVX = -3
bgVX = 1
}
left.release = () => {
if (!right.isDown) {
catVX = 0
bgVX = 0
}
}
right.press = () => {
// console.log(cat)
catVX = 3
bgVX = -1
}
right.release = () => {
if (!left.isDown) {
catVX = 0
bgVX = 0
}
}
// 按回车键开始
start.press = () => {
console.log(cat)
startGame.visible = false
Game.visible = true
overGame.visible = false
}
5、碰撞检测逻辑
同样引入完hitTestRectangle函数后,在 游戏循环函数--具体处理逻辑 【play】函数里,添加好逻辑。
// 游戏循环函数--具体处理逻辑
function play(delta: number) {
cat.x += catVX
bg.x += bgVX
// 如果相撞了
if (hitTestRectangle(cat, mouse)) {
//There's a collision
// 隐藏开始场景和游戏场景,开启游戏结束场景
startGame.visible = false
Game.visible = false
overGame.visible = true
} else if (!hitTestRectangle(cat, mouse)) {
}
}
我们的项目就完成啦!
6、完整源码
<script setup lang="ts">
import * as PIXI from 'pixi.js'
defineProps<{ msg: string }>()
// 创建我们的pixi容器
let app = new PIXI.Application({
width: 400, // default: 800 宽度
height: 400, // default: 600 高度
antialias: true, // default: false 反锯齿
transparent: false, // default: false 透明度
resolution: 1 // default: 1 分辨率
})
// 创建三个场景,开始游戏界面,游戏主页面,和游戏结束界面
let startGame = new PIXI.Container()
let Game = new PIXI.Container()
let overGame = new PIXI.Container()
// 使用定时器变成异步等待标签加载完毕
setTimeout(() => {
console.log(app, PIXI)
document.getElementById("pixijsjs")?.appendChild(app.view)
app.loader
.add([
"tom.png",
"jerry.png",
"bg.png",
"gameover.jpeg"
])
.load(setup)
}, 400)
let cat: PIXI.Sprite
let mouse: PIXI.Sprite
let bg: PIXI.Sprite
let state: Function
function setup() {
// 加载完图片后
// 将三个场景加入到容器里
app.stage.addChild(startGame)
app.stage.addChild(Game)
Game.visible = false
app.stage.addChild(overGame)
overGame.visible = false
// 开始场景的精灵
startScene()
// 游戏主场景的精灵
gameScene()
// 结束场景的精灵
overScene()
state = play
app.ticker.add(delta => gameLoop(delta))
}
// 游戏循环函数
function gameLoop(delta: number){
// console.log(delta, cat)
// //Move the cat 1 pixel
// cat.x -= 1
state(delta)
}
// 游戏循环函数--具体处理逻辑
function play(delta: number) {
cat.x += catVX
bg.x += bgVX
// 如果相撞了
if (hitTestRectangle(cat, mouse)) {
// 隐藏开始场景和游戏场景,开启游戏结束场景
startGame.visible = false
Game.visible = false
overGame.visible = true
} else if (!hitTestRectangle(cat, mouse)) {
}
}
// 碰撞函数
function hitTestRectangle(r1: PIXI.Sprite, r2: PIXI.Sprite) {
//Define the variables we'll need to calculate
let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
//hit will determine whether there's a collision
hit = false;
//Find the center points of each sprite
r1.centerX = r1.x + r1.width / 2;
r1.centerY = r1.y + r1.height / 2;
r2.centerX = r2.x + r2.width / 2;
r2.centerY = r2.y + r2.height / 2;
//Find the half-widths and half-heights of each sprite
r1.halfWidth = r1.width / 2;
r1.halfHeight = r1.height / 2;
r2.halfWidth = r2.width / 2;
r2.halfHeight = r2.height / 2
//Calculate the distance vector between the sprites
vx = r1.centerX - r2.centerX
vy = r1.centerY - r2.centerY
//Figure out the combined half-widths and half-heights
combinedHalfWidths = r1.halfWidth + r2.halfWidth
combinedHalfHeights = r1.halfHeight + r2.halfHeight
//Check for a collision on the x axis
if (Math.abs(vx) < combinedHalfWidths) {
//A collision might be occurring. Check for a collision on the y axis
if (Math.abs(vy) < combinedHalfHeights) {
//There's definitely a collision happening
hit = true
} else {
//There's no collision on the y axis
hit = false
}
} else {
//There's no collision on the x axis
hit = false
}
//`hit` will be either `true` or `false`
return hit
}
let left = keyboard("ArrowLeft")
let right = keyboard("ArrowRight")
let start = keyboard("Enter")
let catVX = 0// 设置tom的速度属性
let bgVX = 0// 设置背景的速度属性
// 完善一下left和right的press和release函数
left.press = () => {
// console.log(cat)
catVX = -3
bgVX = 1
}
left.release = () => {
if (!right.isDown) {
catVX = 0
bgVX = 0
}
}
right.press = () => {
// console.log(cat)
catVX = 3
bgVX = -1
}
right.release = () => {
if (!left.isDown) {
catVX = 0
bgVX = 0
}
}
// 按回车键开始
start.press = () => {
console.log(cat)
startGame.visible = false
Game.visible = true
overGame.visible = false
}
// 自定义键盘识别函数
function keyboard(value: string) {
let key: { value: string; isDown: boolean; isUp: boolean; press: (() => void) | undefined; release: (() => void) | undefined; downHandler: { (event: any): void; bind?: any; }; upHandler: { (event: any): void; bind?: any; }; unsubscribe: () => void; };
key = {}
key.value = value;
key.isDown = false;
key.isUp = true;
key.press = undefined;
key.release = undefined;
//The `downHandler`
key.downHandler = event => {
if (event.key === key.value) {
if (key.isUp && key.press) key.press();
key.isDown = true;
key.isUp = false;
event.preventDefault();
}
};
//The `upHandler`
key.upHandler = event => {
if (event.key === key.value) {
if (key.isDown && key.release) key.release();
key.isDown = false;
key.isUp = true;
event.preventDefault();
}
};
//Attach event listeners
const downListener = key.downHandler.bind(key);
const upListener = key.upHandler.bind(key);
window.addEventListener(
"keydown", downListener, false
);
window.addEventListener(
"keyup", upListener, false
);
// Detach event listeners
key.unsubscribe = () => {
window.removeEventListener("keydown", downListener);
window.removeEventListener("keyup", upListener);
};
return key;
}
// 开始场景的精灵
function startScene(): void {
let style = new PIXI.TextStyle({
fontFamily: "Futura",
fontSize: 30,
fill: "white"
});
let message = new PIXI.Text("按下回车键开始!", style)
message.x = 100
message.y = 180
startGame.addChild(message)
}
// 游戏场景的精灵
function gameScene(): void {
// 设置背景
// bg = new PIXI.Sprite(app.loader.resources["bg.jpeg"].texture)
bg = new PIXI.TilingSprite(app.loader.resources["bg.png"].texture, 1800, 400) // 替换为这一行代码
bg.x = 0
bg.y = 0
bg.width = 1800
bg.height = 400
Game.addChild(bg)
cat = new PIXI.Sprite(app.loader.resources["tom.png"].texture)
// 调整汤姆的大小和位置
//Change the sprite's position
cat.x = 40
cat.y = 190
// 用cat.scale.set(0.5, 0.5)也可以,缩放大小为原图的一半
//Change the sprite's size
cat.width = 80
cat.height = 240
// 实现sprite水平镜像反转
// 设置anchor 0left 0.5center 1right
cat.anchor.x = 0.5
// 水平缩放比例为-1
cat.scale.x *= -1
Game.addChild(cat)
mouse = new PIXI.Sprite(app.loader.resources["jerry.png"].texture)
// 调整杰瑞的大小和位置
//Change the sprite's position
mouse.x = 300
mouse.y = 280
//Change the sprite's size
mouse.width = 40
mouse.height = 60
// 杰瑞应该比汤姆小只
Game.addChild(mouse)
}
// 结束场景的精灵
function overScene(): void {
let overbg = new PIXI.Sprite(app.loader.resources["gameover.jpeg"].texture)
overbg.width = 400
overbg.height = 400
overGame.addChild(overbg)
let style = new PIXI.TextStyle({
fontFamily: "Futura",
fontSize: 20,
fill: "#646cffaa"
});
let message = new PIXI.Text("游戏结束了,Tom 气炸了!", style)
message.x = 100
message.y = 10
overGame.addChild(message)
}
</script>
<template>
<p class="pixilabel"> pixi盒子</p>
<div id="pixijsjs" class="pixibox">
</div>
</template>
<style scoped>
.pixilabel {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
}
.pixibox {
width: 400px;
height: 400px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
三、 总结
经过这三天对pixijs的尝试,从零到现在完成一个小demo,也算是有点熟悉了,其实今天是打算实现精灵动画的,不过加班导致我没啥时间去钻研,而且感觉有些许复杂,加上手头没比较好的素材,就没加上tom的奔跑效果,等我琢磨一下再出一期讲精灵动画如何实现的。
ps: 我是地霊殿__三無,希望能和你一起进步!