使用 PlayCanvas 引擎构建基础演示
PlayCanvas 专门为现代浏览器打造,是一款功能齐全的 3D 游戏引擎,包含资源加载、实体和组件系统、高级图形处理、碰撞和物理引擎(使用 ammo.js 构建)、音频,以及处理来自各种设备(包括游戏手柄)的控制输入的工具。
这是一份令人印象深刻的功能列表 - 让我们看看一些实际效果。
我们首先尝试构建一个简单的演示 - 在屏幕上渲染一个立方体。如果你已经完成我们的 使用 Three.js 构建基础演示 文章(或者你熟悉其他 3D 库),你会注意到 PlayCanvas 基于类似的概念:摄像机、灯光和物体。
环境设置
要开始使用 PlayCanvas 开发,你不需要太多东西。你应该从以下步骤开始:
- 确保你使用的是支持 WebGL 的现代浏览器,例如最新的 Firefox 或 Chrome。
- 创建一个目录来存储你的实验。
- 将 最新 PlayCanvas 引擎 的副本保存到你的目录中。
- 在另一个选项卡中打开 PlayCanvas 文档 - 它很有用,可以作为参考。
HTML 结构
这是我们将使用的 HTML 结构。
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<title>MDN Games: PlayCanvas demo</title>
<style>
body {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<script src="playcanvas-latest.js"></script>
<canvas id="application-canvas"></canvas>
<script>
const canvas = document.getElementById("application-canvas");
/* all our JavaScript code goes here */
</script>
</body>
</html>
它包含一些基本信息,例如文档 <title>
,以及一些 CSS 用于将 PlayCanvas 将使用的 <canvas>
元素的宽度和高度设置为 100%,使其填充整个可用的视窗空间。第一个 <script>
元素将 PlayCanvas 库包含在页面中;我们将在第二个元素中编写示例代码。已经包含了一个辅助变量,它将存储对 <canvas>
元素的引用。
在继续阅读之前,将此代码复制到一个新的文本文件中,并将其保存到你的工作目录中,命名为 index.html
。
PlayCanvas 应用程序
要开始开发我们的游戏,我们首先必须创建 PlayCanvas 应用程序(使用给定的 <canvas>
元素),然后启动更新循环。将以下代码添加到你的第二个 <script>
元素的底部
const app = new pc.Application(canvas);
app.start();
pc
全局对象包含引擎中可用的所有 PlayCanvas 函数。
接下来,我们将设置画布以填充窗口,并自动更改其分辨率以与画布大小相同。同样,将以下几行添加到脚本的底部。
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
摄像机
现在,设置代码已到位,我们需要考虑实现标准场景组件:摄像机、灯光和物体。让我们从摄像机开始 - 将以下几行添加到你的代码中,位于之前的代码下方。
const camera = new pc.Entity();
camera.addComponent("camera", {
clearColor: new pc.Color(0.8, 0.8, 0.8),
});
app.root.addChild(camera);
camera.setPosition(0, 0, 7);
上面的代码将创建一个新的 Entity
。
注意: 实体是场景中使用的任何物体 - 它可以是像盒子、圆柱体或圆锥体这样的物体,也可以是摄像机、灯光或声音源。
然后它向其添加一个 camera
组件,该组件具有浅灰色 clearColor
- 该颜色将显示为背景。接下来,camera
对象将添加到应用程序的根目录,并定位在场景中心的 z
轴上 7 个单位。这使我们能够腾出一些空间来可视化我们稍后将创建的物体。
注意: 距离值(例如摄像机 z 位置)是无单位的,可以是任何你认为适合你的场景的值 - 毫米、米、英尺或英里 - 由你决定。
尝试保存文件并在你的浏览器中加载它。现在你应该看到一个灰色窗口。恭喜你!
几何体
现在场景已正确渲染,我们可以开始向其中添加 3D 形状了。为了加快开发速度,PlayCanvas 提供了一些预定义的基元,你可以使用它们在单行代码中立即创建形状。有立方体、球体、圆柱体以及更复杂的形状可用。引擎将负责绘制每个形状的所有内容,因此我们可以专注于高级编码。让我们从定义立方体形状的几何体开始 - 将以下新代码添加到你之前添加的内容下方
const box = new pc.Entity();
box.addComponent("model", { type: "box" });
app.root.addChild(box);
box.rotate(10, 15, 0);
它将创建一个具有 box
模型组件的 Entity
,并将其添加到应用程序的根目录,即我们的场景。我们还稍微旋转了一下盒子,以表明它实际上是一个 3D 立方体,而不是正方形。
立方体是可见的,但它是完全黑色的。为了使其看起来更好,我们需要在其上照射一些灯光。
灯光
PlayCanvas 中的基本灯光类型是方向光和环境光。第一种类型是放置在场景中的方向光,而第二种类型则反射第一种类型的光,因此看起来更自然;这可以在全局范围内设置。同样,将以下新代码添加到你之前添加的内容下方。
const light = new pc.Entity();
light.addComponent("light");
app.root.addChild(light);
light.rotate(45, 0, 0);
它将创建一个灯光 Entity
组件,并将其添加到场景中。我们可以沿着 x
轴旋转灯光,使其照射到立方体的多个侧面。现在是添加环境灯光的时候了
app.scene.ambientLight = new pc.Color(0.2, 0.2, 0.2);
上面的代码为整个场景分配了深灰色环境光。盒子现在看起来好多了,但它可以添加一些颜色以看起来更棒 - 为此,我们需要为它创建材质。
材质
此示例使用了较旧的 材质,称为“Phong 材质”(PlayCanvas 引擎 v1.65.0 及更高版本支持)。要使用 PhongMaterial
,请在之前的代码下方添加以下几行
const boxMaterial = new pc.PhongMaterial();
boxMaterial.diffuse.set(0, 0.58, 0.86);
boxMaterial.update();
box.model.model.meshInstances[0].material = boxMaterial;
通过在物体上漫射灯光,我们可以为其赋予自己的颜色 - 我们将选择一种熟悉的蓝色。
注意: 在 PlayCanvas 中,颜色通道值以 0-1
范围内的浮点数形式提供,而不是你可能习惯在 Web 上使用的 0-255
整数。
在创建材质并设置其颜色后,需要对其进行更新,以便应用我们的更改。然后,我们需要做的就是将 box
的材质设置为新创建的 boxMaterial
。
恭喜,你已使用 PlayCanvas 在 3D 环境中创建了你的第一个物体!比你想象的要容易,对吧?以下是它应该看起来的样子
以下是我们到目前为止创建的代码
你也可以 在 GitHub 上查看它。
更多形状
现在我们将向场景中添加更多形状。让我们将立方体向左移动 2 个单位,为一些朋友腾出空间 - 在之前的代码下方添加以下行
box.translate(-2, 0, 0);
现在让我们添加一个新的形状 - 如何添加一个圆柱体?
圆柱体
在你的 JavaScript 代码的底部添加以下几行
const cylinder = new pc.Entity();
cylinder.addComponent("model", { type: "cylinder" });
app.root.addChild(cylinder);
cylinder.rotate(15, 0, 0);
这看起来与用于创建立方体的代码非常相似,但我们添加的是 cylinder
组件,而不是 box
组件。它也围绕 x
轴旋转,以表明它实际上是一个 3D 形状。为了使圆柱体具有颜色,比如黄色,我们需要像之前一样为它创建材质。添加以下几行
const cylinderMaterial = new pc.PhongMaterial();
cylinderMaterial.diffuse.set(1, 0.58, 0);
cylinderMaterial.update();
cylinder.model.model.meshInstances[0].material = cylinderMaterial;
圆锥体
创建圆锥体及其材质的过程与创建圆柱体几乎完全相同。再次将以下代码添加到脚本的底部
const cone = new pc.Entity();
cone.addComponent("model", { type: "cone" });
app.root.addChild(cone);
cone.translate(2, 0, 0);
const coneMaterial = new pc.PhongMaterial();
coneMaterial.diffuse.set(0.9, 0.9, 0.9);
coneMaterial.update();
cone.model.model.meshInstances[0].material = coneMaterial;
上面的代码将创建一个新的 cone
,将其添加到 app
中,并将其向右移动 2 个单位,使其不与圆柱体重叠。然后创建材质,赋予其灰色颜色,并将其分配给圆锥体 Entity
。
以下是它现在应该看起来的样子
这可以工作,但它有点无聊。在游戏中,通常会发生一些事情 - 我们可以看到动画等等 - 因此,让我们尝试通过为这些形状添加动画来为它们注入一些活力。
动画
我们已经使用 translate
或 rotate
来调整形状的位置;我们也可以使用 setPosition
直接更改它们的位置,或者对它们进行缩放。要显示实际的动画,我们需要在渲染循环中更改这些值,以便它们在每一帧上更新。有一个特殊的 update
事件,我们可以使用它来实现 - 在之前的添加内容下方添加以下代码
let timer = 0;
app.on("update", (deltaTime) => {
timer += deltaTime;
// code executed on every frame
});
回调函数以deltaTime
作为参数,因此我们可以获得自上次调用此更新函数以来的相对时间。对于基于时间的动画,我们将使用一个timer
变量,它会在每次更新时将deltaTime
添加到其中,从而存储自应用程序启动以来的已过去时间。
旋转
旋转非常简单 - 你只需要在每一帧中向给定的旋转方向添加一个定义的值。将以下代码行添加到app.on("update")
回调函数中,紧接在timer
变量中添加deltaTime
之后
box.rotate(deltaTime * 10, deltaTime * 20, deltaTime * 30);
它将在每一帧中将box
在x
轴上旋转deltaTime*10
,在y
轴上旋转deltaTime*20
,在z
轴上旋转deltaTime*30
,从而产生平滑的动画。
缩放
我们也可以缩放给定对象 - 有一个名为setLocalScale
的函数可以实现此目的。将以下代码添加到回调函数中
cylinder.setLocalScale(1, Math.abs(Math.sin(timer)), 1);
在这里我们使用Math.sin
来循环缩放圆柱体,使其变大和变小。我们将y
轴的缩放值包装在Math.abs
中,以传递绝对值(大于或等于 0);sin
在 -1 和 0 之间变化,对于负值,圆柱体的缩放可能会意外呈现(在这种情况下,它看起来一半时间是黑色的。)
现在开始移动部分。
移动
除了旋转和缩放之外,我们还可以移动场景中的对象。添加以下代码来实现此目的。
cone.setPosition(2, Math.sin(timer * 2), 0);
这将通过在每一帧中将sin
值应用于y
轴来使cone
上下移动,并进行一些调整以使其看起来更酷。尝试更改值以查看它如何影响动画。
结论
以下是最终的代码清单,以及一个可视化的实时示例
你也可以在 GitHub 上查看,并分叉存储库,如果你想在本地玩它。现在你了解了 PlayCanvas 引擎的基本知识;祝你实验愉快!
总结
现在你可以继续阅读PlayCanvas 编辑器文章,返回使用 PlayCanvas 构建基本演示页面,或返回到主Web 上的 3D 游戏页面。