eva.js结合vue3开发小游戏

416 阅读3分钟

首先页面上并非所有内容都需要用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的响应式数据绑定。