用 Oasis 開發一個跳一跳(一)—— 場景搭建

語言: CN / TW / HK

《跳一跳》是微信小遊戲裡第一款製作精良,也是非常受大家喜愛的遊戲。這裡以一個跳一跳 MVP(最簡可行版本) 版本為例,讓大家瞭解『如何用 Oasis 開發一款 Web 3D 遊戲』這一過程。最終的效果如下:

hello.gif
大家可以在: https://stackblitz.com/edit/oasis-jump-aeuowy?file=src/index.ts 體驗和嘗試修改程式碼。(BTW:如果訪問不了或太慢,可以自行到 https://github.com/gz65555/jump-game/tree/feat/game 克隆專案到本地啟動。)

我們把核心的部分分成三大部分,場景角色遊戲邏輯,後面的教程會以這三大部分為主,實現一個跳一跳的最小可用版本(並非完整的遊戲)。下圖是核心模組的分析:

第一期我們準備製作最基本的場景,完成燈光,相機,基本底座的擺放。

在具體進入開發之前,我們需要先把整個專案工程搭建好。

工程搭建

我們先使用 create-oasis-app 建立專案。

使用命令的第一步需要安裝 Node.js。如果已安裝 Node.js(version >= 12.0.0) 可以執行命令建立 oasis 模板: shell npm init @oasis-engine/oasis-app

因為我們無需額外開發前端部分,所以直接使用 Vanilla 模板即可。下面是呼叫命令的過程。

當執行完成後,我們進入到專案中的 terminal 裡,執行: npm install

在安裝完依賴後再使用: shell npm run dev

啟動 dev 伺服器,過程如下圖所示:

再開啟 http://localhost:3000 即可看到:

說明工程搭建已經完成。此時專案目錄如下: javascript |-jump-game |-index.html // HTML 檔案 |-main.js // 入口 js 檔案 |-package.json // 工程描述檔案 |-src | |-index.ts // oasis 部分程式碼

基本場景搭建

引擎和場景初始化

我們用 IDE 開啟專案,找到 /src/index.ts 如下面程式碼所示: ```typescript import { Camera, Vector3, WebGLEngine, DirectLight } from "oasis-engine";

// 初始化引擎 const engine = new WebGLEngine("canvas"); // 根據頁面設定 canvas 大小 engine.canvas.resizeByClientSize();

const zeroVector = new Vector3(0, 0, 0);

// 設定背景色 const scene = engine.sceneManager.activeScene; scene.background.solidColor.setValue(208 / 255, 210 / 255, 211 / 255, 1); scene.ambientLight.diffuseSolidColor.setValue(0.5, 0.5, 0.5, 1);

// 建立根節點 const rootEntity = scene.createRootEntity(); const cameraEntity = rootEntity.createChild("camera"); cameraEntity.transform.setPosition(-100, 100, 100); const camera = cameraEntity.addComponent(Camera);

// 初始化相機 camera.isOrthographic = true; cameraEntity.transform.lookAt(zeroVector);

// 初始化燈光 const directLightEntity = rootEntity.createChild("directLight"); const directLight = directLightEntity.addComponent(DirectLight); directLight.intensity = 0.5; directLightEntity.transform.setPosition(10, 30, 20); directLightEntity.transform.lookAt(zeroVector);

engine.run(); ```

此段程式碼建立了引擎,場景,並且初始化了相機,燈光。相機使用正交相機,朝向原點。直接光也設定為朝向原點。

完成以上步驟可以場景裡還是一片灰色,我們來給場景新增底座生成和相機移動的邏輯。

場景底座初始化

我們通過建立一個 SceneScript.ts 來做場景整體的管理,並且在 rootEntity上新增 SceneScripttypescript const sceneScript = rootEntity.addComponent(SceneScript); 指令碼是 Oasis Engine 非常核心的概念,是一種特殊的元件,元件又是掛載在實體(Entity)上的,來給引擎提供拓展的能力。更多關於實體和元件概念請檢視:《實體與元件》文件。

接下來,在 SceneScriptonAwake 生命週期中建立一個 ground 實體,用來擺放跳一跳的底座。onAwake生命週期函式是在掛載的實體被啟用時呼叫,一般用來放置一下初始化的程式碼。引擎中還有許多生命週期的鉤子函式用來幫助開發者編寫業務邏輯,更多指令碼生命週期可以檢視:《指令碼》。

同時建立 TableManager 物件來控制底座的生成。 typescript onAwake() { this.ground = this.entity.createChild("ground"); this.tableManager = new TableManager(this._engine, this.ground); }

我們在 TableManager 裡建立可以複用的材質(Material)和網格(Mesh)。建立一個 3D 物體的渲染,需要用到 MeshRenderer 網格渲染器元件,設定好材質和網格即可。

因為是 MVP 版本,我這裡只用一個紅色的立方體 Table 作為示意: ```typescript import { BlinnPhongMaterial, Engine, Entity, MeshRenderer, ModelMesh, PrimitiveMesh, } from "oasis-engine"; import { Config } from "./Config";

export class TableManager { // 底座的 Mesh private cuboidMesh: ModelMesh; // 底座的材質 private cuboidMaterial: BlinnPhongMaterial;

constructor(engine: Engine, private sceneEntity: Entity) { // 建立基本網格 this.cuboidMesh = PrimitiveMesh.createCuboid( engine, Config.tableSize, Config.tableHeight, Config.tableSize ); // 建立基本材質 this.cuboidMaterial = new BlinnPhongMaterial(engine); // 設定材質顏色 this.cuboidMaterial.baseColor.setValue(1, 0, 0, 1); }

// 建立一個方塊的底座 createCuboid(x: number, y: number, z: number) { // 建立渲染實體 const cuboid = this.sceneEntity.createChild("cuboid"); const renderEntity = cuboid.createChild("render"); // 設定座標 renderEntity.transform.setPosition(0, Config.tableHeight / 2, 0); cuboid.transform.setPosition(x, y, z); // 建立 MeshRenderer 元件 const renderer = renderEntity.addComponent(MeshRenderer); // 設定網格 renderer.mesh = this.cuboidMesh; // 設定設定材質 renderer.setMaterial(this.cuboidMaterial); return { entity: cuboid, size: Config.tableSize }; }

// 清除所有底座 clearAll() { this.sceneEntity.clearChildren(); } } ```

我們可以看到上面的的 tableSizetableHeight 都是在 GameConfig 裡定義的,我們也需要建立一個 Config.ts 來設定遊戲配置: typescript export module Config { export const tableHeight = 5 / 3; export const tableSize = 8 / 3; } 我們通過把配置收斂到統一檔案裡,方便配置的修改。

我們再到 SceneScript 中新增 reset 方法: typescript reset() { // 清除所有的底座 this.ground.clearChildren(); // 初始化的第一個底座 this.tableManager.createCuboid(-2.5, 0, 0); // 初始化的第二個底座 this.tableManager.createCuboid(4.2, 0, 0); }

reset 方法是之後每次遊戲開始時和結束後都需要呼叫的方法。上面的幾個數值都是實際開發中調試出的結果,相對來說比較接近真實的遊戲。

我們在 index.ts 呼叫 sceneScript.reset() 即可看到效果:

總結

這篇文章講了一下跳一跳的簡單場景搭建,涉及到的知識點有:

下一期會帶來場景的邏輯部分:底座生成和相機移動的部分。
本次教程程式碼可參考 feat/init 分支。

如何進一步瞭解我們

Oasis 開源社群群 (釘釘):

JPG.png

Oasis 開源社群群管理員 (微信):

image.png

網站

官網地址
https://oasisengine.cn
Git 原始碼地址
https://github.com/oasis-engine/engine