用 Oasis 開發一個跳一跳(一)—— 場景搭建
《跳一跳》是微信小遊戲裡第一款製作精良,也是非常受大家喜愛的遊戲。這裡以一個跳一跳 MVP(最簡可行版本) 版本為例,讓大家瞭解『如何用 Oasis 開發一款 Web 3D 遊戲』這一過程。最終的效果如下:
大家可以在: 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
上新增 SceneScript
:
typescript
const sceneScript = rootEntity.addComponent(SceneScript);
指令碼是 Oasis Engine 非常核心的概念,是一種特殊的元件,元件又是掛載在實體(Entity)上的,來給引擎提供拓展的能力。更多關於實體和元件概念請檢視:《實體與元件》文件。
接下來,在 SceneScript
的 onAwake 生命週期中建立一個 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(); } } ```
我們可以看到上面的的 tableSize
和 tableHeight
都是在 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 開源社群群 (釘釘):
Oasis 開源社群群管理員 (微信):
網站
官網地址
https://oasisengine.cn
Git 原始碼地址
https://github.com/oasis-engine/engine