首先页面上并非所有内容都需要用eva.js去实现,只是有复杂动效的例如骨骼动画的我们需要用到eva.js。一般一个应用会有很多个页面,弹框之类,常常会碰到不同的页面上会有不同的游戏场景,或者页面和弹框内容里同时存在游戏场景。一般下意识的做法可能是不同页面创建不同的游戏,弹框也这样,关闭的时候销毁游戏,但这样影响性能而且当两个游戏对象同时存在的时候会存在bug,绘制游戏会出现混乱的情况。于是可以借助loadScene来绘制到多个场景,页面退出时销毁scene。但是eva.js存在bug,scene无法销毁,反复的退出进入会导致scene过多而崩溃。所以想到了动态创建scene配合组件封装开发小游戏的方案。
首先创建vue前先创建一个game,一个项目只需要一个game作为游戏引擎。
const gameContainer = await createGame();//创建游戏
const app = createApp(App);
app.provide('gameContainer', gameContainer);
app.use(router).mount('#app');
createGame方法里面是创建游戏,添加所需要的系统依赖
import { Game } from '@eva/eva.js';
import { RendererSystem } from '@eva/plugin-renderer';
import { ImgSystem } from '@eva/plugin-renderer-img';
import { SpineSystem } from '@eva/plugin-renderer-spine38';
import { RenderSystem } from '@eva/plugin-renderer-render';
import { EventSystem } from '@eva/plugin-renderer-event';
import { TransitionSystem } from '@eva/plugin-transition';
import { TextSystem } from '@eva/plugin-renderer-text';
import { MaskSystem } from '@eva/plugin-renderer-mask';
import { SoundSystem } from '@eva/plugin-sound';
import { GraphicsSystem } from '@eva/plugin-renderer-graphics';
import { getFrameRateByOSVersion } from '@/utils';
export default async () => {
const frameRate = await getFrameRateByOSVersion();
// 创建 game,添加渲染器
game = new Game({
frameRate,
systems: [
new RendererSystem({}),
new ImgSystem(),
new SpineSystem(),
new RenderSystem(),
new EventSystem(),
new TransitionSystem(),
new TextSystem(),
new MaskSystem(),
new SoundSystem(),
new GraphicsSystem(),
],
autoStart: true,
frameRate: 60,
});
return game;
}
接下来封装游戏场景组件
<template>
<!-- 游戏场景 -->
<div class="game-container" ref="gamebody">
<div ref="seat"></div>
<slot></slot>
</div>
</template>
<script setup>
import { GameObject } from '@eva/eva.js';
import { Render } from '@eva/plugin-renderer-render';
import getScnce from '@/mixins/getScnce';
import { onBeforeUnmount } from 'vue';
const gamebody = ref(null);
const seat = ref(null);
const game = inject('gameContainer');
const sceneContainer = getScnce(game);
const { scene, canvas } = sceneContainer;
const container = new GameObject('container');
container.addComponent(
new Render({
sortableChildren: true,
})
);
scene.addChild(container);
provide('game', container);
//挂载完成事件
const emit = defineEmits(['loaded']);
onMounted(() => {
// 挂载canvas
gamebody.value.insertBefore(canvas, seat.value);
gamebody.value.removeChild(seat.value);
emit('loaded', container);
});
onBeforeUnmount(() => {
scene.removeChild(container);
container.destroy();
sceneContainer.free = true;
});
</script>
<style scoped lang="scss">
.game-container {
position: relative;
width: 100%;
}
</style>
通过getScene获取scene和canves dom,scene中拆入一个带有渲染能力的gameObject,利用这个gameObject绘制游戏场景,将canves拆入页面目标位置,并且在组件销毁时,将该scene中的内容移除,并设置为空闲scene。getScnce是一个动态获取scnce的方法,如果存在空闲scene则使用空闲scene,否则创建新的scene并缓存起来。该方法实现如下:
import { Scene } from '@eva/eva.js';
import { LOAD_SCENE_MODE } from '@eva/eva.js';
const scenes = [];
export default (game) => {
const sc = scenes.find(v => v.free);
if (sc) {
sc.free = false;
return sc;
}
const nsc = new Scene(`scene-${scenes.length + 1}`);
const canvas = document.createElement('canvas');
canvas.style.width = '100%';
game.loadScene({
scene: nsc,
mode: LOAD_SCENE_MODE.MULTI_CANVAS,
params: {
canvas,
resolution: 3,
transparent: true,
width: 360,
height: 1000,
},
});
const nscObj = { free: false, scene: nsc, canvas };
scenes.push(nscObj);
return nscObj;
}
这样游戏场景组件就封装好了,使用的时候配合组件的生命周期可以让游戏实现更加简单。比如游戏场景里有很多士兵,士兵战斗会死去消失。那么引入这个组件在这个组件插槽里面添加多个士兵组件,士兵的死亡就是士兵组件的销毁,配合生命周期移除该士兵的绘制,此外该士兵的例如血条完全可以用html绘制减轻工作量,数据层面也可以很好的和vue的响应式数据绑定。