需要 |
---|
1. 按住屏幕,棍子伸长,放开手指,棍子放下。 |
2. 具备计时性能。 |
3. 计时完结或者棍子没有放到平安区域,则游戏完结。 |
独自一个 Scene
作为背景,避免显示 Scene
重叠时有遮挡
// background.ts
export default class extends Phaser.Scene {constructor() {
super({
key: 'BackgroundScene',
active: true
});
}
create(): void {const graphics = this.add.graphics();
graphics.fillGradientStyle(0x241a47, 0x241a47, 0x274aa0, 0x274aa0);
graphics.fillRect(0, 0, window.game.width, window.game.height);
this.scene.launch('FootScene');
this.scene.launch('StartScene');
}
}
底部组件
底部的云层成果作为公共组件应用
// foot.ts
import pngCloud from '@images/cloud.png';
const config: FootConfig = {
circleNum: 7,
blueCircleFrame: 0,
whileCircleFrame: 1,
blueCloudY: 160,
whileCloudY: 190,
height: 90
};
export default class extends Phaser.Scene {constructor() {
super({key: 'FootScene'});
}
preload(): void {this.load.spritesheet('ssCloud', pngCloud, { frameWidth: 256, frameHeight: 256});
}
create(): void {const blueGroup = this.add.group([], {
key: 'ssCloud',
frame: [config.blueCircleFrame],
frameQuantity: config.circleNum,
setXY: {
y: config.blueCloudY,
stepX: 120,
stepY: 0
}
});
const whileGroup = this.add.group([], {
key: 'ssCloud',
frame: [config.whileCircleFrame],
frameQuantity: config.circleNum,
setXY: {
y: config.whileCloudY,
stepX: 120,
stepY: 0
}
});
this.resize();
window.addEventListener('resize', () => {this.resize();
});
}
resize(): void {
const viewHeight = document.documentElement.clientHeight / window.rem;
const camerasY = (window.game.height - viewHeight) / 2 + viewHeight - config.height;
this.cameras.main.setPosition(0, camerasY);
}
}
增加动画
// foot.ts
export default class extends Phaser.Scene {create(): void {
this.add.tween({targets: blueGroup.getChildren(),
props: {y: (target) => {return target.y + Phaser.Math.Between(-5, 0);
},
scale: (target) => {return target.scale + Phaser.Math.FloatBetween(-0.05, 0.05);
}
},
duration: 3000,
yoyo: true,
repeat: -1,
ease: Phaser.Math.Easing.Sine.InOut
});
this.add.tween({targets: whileGroup.getChildren(),
props: {y: (target) => {return target.y + Phaser.Math.Between(-5, 0);
},
scale: (target) => {return target.scale + Phaser.Math.FloatBetween(0, 0.1);
}
},
duration: 3000,
yoyo: true,
repeat: -1,
ease: Phaser.Math.Easing.Sine.InOut
});
}
}
适配,始终显示在窗口底部,css 的 fixed 成果
// foot.ts
export default class extends Phaser.Scene {create(): void {this.resize();
window.addEventListener('resize', () => {this.resize();
});
}
resize(): void {
const viewHeight = document.documentElement.clientHeight / window.rem;
const camerasY = (window.game.height - viewHeight) / 2 + viewHeight - config.height;
this.cameras.main.setPosition(0, camerasY);
}
}
开始页面
// start.ts
import pngBtnStart from '@images/btn_start.png';
import pngTitle from '@images/title.png';
export default class extends Phaser.Scene {constructor() {
super({key: 'StartScene'});
}
preload(): void {this.load.image('imgBtnStart', pngBtnStart);
this.load.image('imgTitle', pngTitle);
}
create(): void {this.add.rectangle(window.game.width / 2, window.game.height / 2, window.game.width, window.game.height, 0x000000, 0.5);
this.add.image(window.game.width / 2, 240, 'imgTitle').setOrigin(0.5, 0);
const btnStart = this.add.sprite(window.game.width / 2, window.game.height / 2, 'imgBtnStart').setInteractive();
btnStart.on('pointerdown', () => {this.scene.start('MainScene');
});
this.add.tween({
targets: btnStart,
props: {y: (target) => {return target.y + 20;}
},
yoyo: true,
loop: -1,
duration: 2000,
ease: Phaser.Math.Easing.Sine.InOut
});
}
}
主场景
显示动态信息
// main.ts
import pngTips from '@images/tips.png';
import pngProcess from '@images/process_border.png';
import pngSprites from '@images/sprites.png';
import pngNinja from '@images/ninja.png';
let txtDistance: Phaser.GameObjects.Text; // 文本,显示间隔
let rect: Phaser.GameObjects.Rectangle; // 进度条
let stick: Phaser.GameObjects.Rectangle; // 棍子
let processTimerEvent: Phaser.Time.TimerEvent; // 计时事件
let overContainer: Phaser.GameObjects.Container; // 游戏完结内容容器
let gameContainer: Phaser.GameObjects.Container; // 游戏内容容器
let prePlatformDistance: number; // 到上一个站台的间隔
let nextPlatformDistance: number; // 到下一个站台的间隔
let curPlatformWidth: number; // 以后站台宽度
let prePlatformWidth: number; // 上一个站台宽度
let nextPlatformWidth: number; // 下一个站台宽度
let curPlatformX: number; // 以后站台地位
let nextPlatformX: number; // 下一个站台地位
let ninja: Phaser.GameObjects.Sprite; // 不靠谱的忍者本者
let distance = 0; // 间隔,站台数
let isPlaying = false; // 是否处于解决流程中,区间在从按下到开释后的动画播放完结
let isStart = false; // 是否开始游戏
const config: GameConfig = {
processLen: 500, // 进度条长度
processHeight: 29, // 进度条高度
platformHeight: 600, // 站台高度
stickWidth: 10, // 棍子宽度
stickHeight: -10 // 棍子长度,坐标系起因,取负值
};
// 变动的值独自拧进去
let stickHeight = config.stickHeight;
let processLen = config.processLen;
export default class extends Phaser.Scene {constructor() {
super({key: 'MainScene'});
}
preload(): void {this.load.image('imgTips', pngTips);
this.load.image('imgProcess', pngProcess);
this.load.spritesheet('ssSprites', pngSprites, { frameWidth: 150, frameHeight: 150});
this.load.spritesheet('ssNinja', pngNinja, { frameWidth: 462 / 6, frameHeight: 388 / 4});
}
create(): void {
// 提示信息
const tips = this.add.image(window.game.width / 2, 400, 'imgTips');
// 进度条
const process = this.add.image(0, 0, 'imgProcess');
txtDistance = this.add.text(260, -24, 'DISTANCE: 0', {
fontFamily: 'Arial',
fontSize: 40,
color: '#ffffff'
}).setOrigin(1);
rect = this.add.rectangle(-250, 0, config.processLen, config.processHeight, 0xffffff).setOrigin(0, 0.5);
// 进度条区域内容容器
const timerContainer = this.add.container(window.game.width / 2, 400, [process, txtDistance, rect]);
timerContainer.setAlpha(0);
// 游戏完结内容
const btnPlay = this.add.sprite(-110, 0, 'ssSprites', 0).setInteractive();
const btnHome = this.add.sprite(110, 0, 'ssSprites', 1).setInteractive();
overContainer = this.add.container(window.game.width / 2, 1800, [btnPlay, btnHome]);
overContainer.setAlpha(0.5);
// 初始化忍者
ninja = this.add.sprite(90, -600, 'ssNinja', 0).setOrigin(1);
this.anims.create({
key: 'stand',
frames: this.anims.generateFrameNumbers('ssNinja', { start: 0, end: 11}),
frameRate: 12,
repeat: -1,
yoyo: true
});
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('ssNinja', { start: 12, end: 19}),
frameRate: 12,
repeat: -1
});
ninja.play('stand');
// 初始化棍子
stick = this.add.rectangle(90, -config.platformHeight, config.stickWidth, config.stickHeight, 0x000000).setOrigin(1, 0);
gameContainer = this.add.container(window.game.width / 2, window.game.height, [ninja, stick]);
gameContainer.setSize(window.game.width, window.game.height);
gameContainer.setPosition(0, window.game.height);
// 初始化站台
const firstPlatform = this.add.rectangle(0, 0, 100, config.platformHeight, 0x000000).setOrigin(0, 1);
gameContainer.add(firstPlatform);
gameContainer.bringToTop(ninja);
curPlatformX = 0;
curPlatformWidth = 100;
prePlatformDistance = 0;
}
}
生成站台
// main.ts
export default class extends Phaser.Scene {create(): void {this.createPlatform(false);
}
createPlatform(playAnim: boolean): void {nextPlatformDistance = Phaser.Math.Between(150, 300); // 随机下个站台的间隔
nextPlatformWidth = Phaser.Math.Between(80, 150); // 随机下个站台的宽度
nextPlatformX = curPlatformX + nextPlatformDistance + curPlatformWidth; // 计算下个站台的地位
const rect = this.add.rectangle(nextPlatformX + 750, 0, nextPlatformWidth, config.platformHeight, 0x000000).setOrigin(0, 1); // 增加站台
gameContainer.add(rect);
gameContainer.bringToTop(ninja); // 忍者提到最上层,掉下去的时候不会被站台遮挡
if (playAnim) { // 是否播放站台呈现时的动画
this.add.tween({ // 游戏容器挪动
targets: gameContainer,
props: {x: (target) => {return target.x - (prePlatformDistance + prePlatformWidth);
}
},
duration: 300,
ease: Phaser.Math.Easing.Sine.In
});
this.add.tween({ // 新增站台挪动
targets: rect,
props: {x: nextPlatformX},
duration: 500,
ease: Phaser.Math.Easing.Sine.In
});
this.add.tween({ // 忍者回到指定地位
targets: ninja,
props: {y: (target) => {return target.y + 10;},
x: curPlatformX + (curPlatformWidth - 20)
},
duration: 300
});
this.add.tween({ // 棍子回到指定地位
targets: stick,
props: {alpha: 0},
duration: 300,
onComplete: () => {stick.setSize(config.stickWidth, config.stickHeight);
stick.setAngle(0);
stick.setX(curPlatformX + (curPlatformWidth - 10));
stick.setAlpha(1);
isPlaying = false;
}
});
} else {rect.setPosition(nextPlatformX, 0);
}
}
}
生成动画
// main.ts
createWalkTimeline(): void {const walkTimeline = this.tweens.createTimeline();
// 棍子动画
walkTimeline.add({
targets: stick,
props: {angle: 90},
duration: 500,
ease: Phaser.Math.Easing.Bounce.Out,
onComplete() {ninja.play('walk');
}
});
// 走路动画
walkTimeline.add({
targets: ninja,
props: {x: (target) => {return target.x + Math.abs(stickHeight) + ninja.getBounds().width / 2;},
y: (target) => {return target.y - 10;}
},
duration: 500,
onComplete: () => {ninja.play('stand');
this.calcDistance();
walkTimeline.destroy();}
});
walkTimeline.play();}
计算一次游戏后果
// main.ts
calcDistance(): void {const near = Phaser.Math.Distance.Between(stick.x, 0, nextPlatformX, 0); // 下个站台的近端
const far = Phaser.Math.Distance.Between(stick.x, 0, nextPlatformX + nextPlatformWidth, 0);// 下个站台的远端
// 以后值变动
curPlatformX = nextPlatformX;
prePlatformWidth = curPlatformWidth;
curPlatformWidth = nextPlatformWidth;
prePlatformDistance = nextPlatformDistance;
if (-stickHeight > near && -stickHeight < far) { // 平安区域
this.createPlatform(true);
txtDistance.setText(`DISTANCE: ${++distance}`); // 间隔 +1
} else {this.gameover(); // 游戏完结
if (-stickHeight < near) {
this.add.tween({
targets: stick,
props: {angle: 180},
duration: 800,
ease: Phaser.Math.Easing.Bounce.Out
});
}
}
// 重置棍子长度
stickHeight = config.stickHeight;
}
监听事件
// main.ts
export default class extends Phaser.Scene {create(): void {
let stickTimerEvent: Phaser.Time.TimerEvent;
this.input.on('pointerdown', () => { // 点击屏幕
if (!isPlaying) {tips.setAlpha(0);
timerContainer.setAlpha(1);
// 开始计时
if (!isStart) {
isStart = true;
processTimerEvent = this.time.addEvent({callback: this.processTimer.bind(this),
loop: true,
delay: 1000
});
}
// 棍子伸长事件
stickTimerEvent = this.time.addEvent({
callback: this.stickTimer,
loop: true,
delay: 10
});
}
});
this.input.on('pointerup', () => { // 开释
if (stickTimerEvent && !isPlaying) {
isPlaying = true;
stickTimerEvent.destroy();
this.createWalkTimeline();}
});
// 从新开始
btnPlay.on('pointerdown', (pointer, localX, localY, event) => {event.stopPropagation(); // 阻止冒泡
this.reset();
this.scene.restart();});
// 回到首页
btnHome.on('pointerdown', (pointer, localX, localY, event) => {event.stopPropagation();
this.reset();
this.scene.start('StartScene');
});
}
stickTimer(): void {
stickHeight -= 25;
stick.setSize(config.stickWidth, stickHeight);
}
processTimer(): void {
processLen -= 5;
rect.setSize(processLen, config.processHeight);
if (processLen === 0) {processTimerEvent.destroy();
this.gameover();}
}
// 数据复位
reset(): void {
processLen = config.processLen;
isPlaying = false;
isStart = false;
distance = 0;
}
}
游戏完结
// main.ts
gameover(): void {processTimerEvent.destroy();
// shake
const vec2 = new Phaser.Math.Vector2(0.005, 0.01);
this.add.tween({
targets: ninja,
ease: Phaser.Math.Easing.Linear,
duration: 600,
props: {
angle: 45,
x: (target) => {return target.x + 50;},
y: 50
},
onComplete: () => {
// 晃动成果
this.cameras.main.shake(200, vec2);
this.scene.get('FootScene').cameras.main.shake(200, vec2);
this.scene.get('BackgroundScene').cameras.main.shake(200, vec2);
}
});
this.add.tween({
targets: overContainer,
props: {
y: window.game.height / 2,
alpha: 1
},
delay: 1200,
duration: 800,
ease: Phaser.Math.Easing.Back.InOut
});
this.add.tween({
targets: gameContainer,
props: {alpha: 0},
delay: 1000,
duration: 800,
ease: Phaser.Math.Easing.Back.InOut
});
}
写在前面
- phaser 自身的适配仿佛没有绝对窗口定位的性能(相似 css 的 fixed),如果游戏中有这样的需要的话,就得本人手动再做多一步适配。
- phaser 和 webpack 联合导入资源时,感觉有些麻烦,须要先
import
之后再应用 phaser 的loader
,不能一步到位。
以上若有好的解决办法,还请各位不吝赐教。
预览:https://hewq.github.io/apps/a…
代码:https://github.com/hewq/Phase…
参考:https://triqui.itch.io/irresp…
作者:https://hewq.github.io/apps/r…