《跳一跳》是微信小游戏里第一款制作精良,也是非常受大家喜爱的游戏。这里以一个跳一跳 MVP(最简可行版本) 版本为例,让大家了解『如何用 Oasis 开发一款 Web 3D 游戏』这一过程。最终的效果如下:
大家可以在: https://stackblitz.com/edit/o... 克隆项目到本地启动。)
我们把核心的部分分成三大部分,场景、角色和游戏逻辑,后面的教程会以这三大部分为主,实现一个跳一跳的最小可用版本(并非完整的游戏)。下图是核心模块的分析:
第一期我们准备制作最基本的场景,完成灯光,相机,基本底座的摆放。
在具体进入开发之前,我们需要先把整个项目工程搭建好。
工程搭建
我们先使用 create-oasis-app 创建项目。
使用命令的第一步需要安装 Node.js。如果已安装 Node.js(version >= 12.0.0) 可以执行命令创建 oasis 模板:
npm init @oasis-engine/oasis-app
因为我们无需额外开发前端部分,所以直接使用 Vanilla 模板即可。下面是调用命令的过程。
当执行完成后,我们进入到项目中的 terminal 里,执行:
npm install
在安装完依赖后再使用:
npm run dev
启动 dev 服务器,过程如下图所示:
再打开 http://localhost:3000 即可看到:
说明工程搭建已经完成。此时项目目录如下:
|-jump-game
|-index.html // HTML 文件
|-main.js // 入口 js 文件
|-package.json // 工程描述文件
|-src
| |-index.ts // oasis 部分代码
基本场景搭建
引擎和场景初始化
我们用 IDE 打开项目,找到 /src/index.ts
如下面代码所示:
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
:
const sceneScript = rootEntity.addComponent(SceneScript);
脚本是 Oasis Engine 非常核心的概念,是一种特殊的组件,组件又是挂载在实体(Entity)上的,来给引擎提供拓展的能力。更多关于实体和组件概念请查看:《实体与组件》文档。
接下来,在 SceneScript
的 onAwake
生命周期中创建一个 ground 实体,用来摆放跳一跳的底座。onAwake
生命周期函数是在挂载的实体被激活时调用,一般用来放置一下初始化的代码。引擎中还有许多生命周期的钩子函数用来帮助开发者编写业务逻辑,更多脚本生命周期可以查看:《脚本》。
同时创建 TableManager
对象来控制底座的生成。
onAwake() {
this.ground = this.entity.createChild("ground");
this.tableManager = new TableManager(this._engine, this.ground);
}
我们在 TableManager
里创建可以复用的材质(Material)和网格(Mesh)。创建一个 3D 物体的渲染,需要用到 MeshRenderer
网格渲染器组件,设置好材质和网格即可。
因为是 MVP 版本,我这里只用一个红色的立方体 Table 作为示意:
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
来设置游戏配置:
export module Config {
export const tableHeight = 5 / 3;
export const tableSize = 8 / 3;
}
我们通过把配置收敛到统一文件里,方便配置的修改。
我们再到 SceneScript
中添加 reset
方法:
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 分支。
本文由mdnice多平台发布