Three.js游戏开发入门

JavaScript021

Three.js游戏开发入门,第1张

就在不久前,创建和部署 游戏 的唯一方法是选择像 Unity 或 Unreal 这样的 游戏 引擎,学习语言,然后打包 游戏 并将其部署到你选择的平台上。

试图通过浏览器向用户提供 游戏 的想法似乎是一项不可能完成的任务。

幸运的是,由于浏览器技术的进步和硬件加速在所有流行的浏览器中都可用,JavaScript 性能的改进以及可用处理能力的稳步提高,为浏览器创建交互式 游戏 体验变得越来越普遍。

在本文中,我们将了解如何使用 Three.js 创建 游戏 。但首先,让我们回顾一下 Three.js 是什么以及为什么它是 游戏 开发的好选择。

Three.js 在 GitHub 上的项目描述恰当地将 Three.js 描述为“......一个易于使用、轻量级、跨浏览器的通用 3D 库”。

Three.js 让我们作为开发人员可以相对简单地在屏幕上绘制 3D 对象和模型。如果没有它,我们将需要直接与 WebGL 交互,虽然这并非不可能,但即使是最小的 游戏 开发项目也会花费大量时间。

传统上,“ 游戏 引擎”由多个部分组成。例如,Unity 和 Unreal 提供了一种将对象渲染到屏幕上的方法,但也提供了大量其他功能,如网络、物理等等。

然而,Three.js 的方法更受限制,不包括物理或网络之类的东西。但是,这种更简单的方法意味着它更容易学习和更优化以做它最擅长的事情:将对象绘制到屏幕上。

它还有一组很棒的示例,我们可以使用它们来了解如何在屏幕上绘制各种对象。最后,它提供了一种简单且原生的方式将我们的模型加载到我们的场景中。

如果不希望用户需要通过应用商店下载应用或进行任何设置来玩你的 游戏 ,那么Three.js 作为 游戏 开发引擎可能是一个有吸引力的选择。如果你的 游戏 在浏览器中运行,那么进入门槛最低,这只能是一件好事。

今天,我们将通过制作一个使用着色器、模型、动画和 游戏 逻辑的 游戏 来浏览 Three.js。我们将创建的内容如下所示:

这个概念很简单。我们控制着一艘火箭飞船,穿越一个星球,我们的目标是拾取能量晶体。我们还需要通过增加护盾来管理飞船的 健康 状况,并尽量不要因为撞击场景中的岩石而严重损坏我们的船。

在我们的运行结束时,火箭飞船返回天空中的母舰,如果用户点击 NEXT LEVEL ,他们会再次尝试,这一次火箭要经过更长的路径。

随着用户玩 游戏 ,火箭飞船的速度会增加,因此他们必须更快地躲避岩石并收集能量晶体。

要创建这样的 游戏 ,我们必须回答以下问题:

到我们制作这款 游戏 时,我们将克服这些挑战。

不过,在我们开始编码之前,我们必须回顾一些简短的理论,特别是与我们将如何在 游戏 中创造运动感有关。

想象一下,你在现实生活中控制着一架直升机,并且正在跟踪地面上的一个物体。物体以逐渐增加的速度继续前进。为了跟上,你必须逐渐提高你所在直升机的速度。

如果对直升机或地面上的物体的速度没有限制,只要你想跟上地面上的物体,这种情况就会持续下去。

当创建一个跟随对象的 游戏 时,正如我们在本例中所做的那样,应用相同的逻辑可能很诱人。也就是说,在世界空间中随着物体的加速移动物体,并更新后面跟随的相机的速度。然而,这提出了一个直接的问题。

基本上,每个玩这个 游戏 的人都会在他们的手机或台式电脑上玩它。这些设备资源有限。如果我们尝试在相机移动时生成可能无限数量的对象,然后移动该相机,最终我们将耗尽所有可用资源,并且浏览器选项卡将变得无响应或崩溃。

我们还需要创建一个代表海洋的平面(一个平面 2D 对象)。当我们这样做时,我们必须给出海洋的尺寸。

然而,我们不能创建一个无限大的平面,我们也不能创建一个巨大的平面,只是希望用户永远不会在我们的关卡中前进到足以让他们离开平面的程度。

那是糟糕的设计,并且希望人们玩我们的 游戏 不足以体验错误似乎违反直觉。

我们不是在一个方向上无限期地移动我们的相机,而是让相机保持静止并移动它周围的环境。这有几个好处。

一是我们总是知道火箭飞船在哪里,因为火箭的位置不会移到远处;它只会左右移动。这让我们很容易判断物体是否在相机后面,并且可以从场景中移除以释放资源。

另一个好处是我们可以选择远处的一个点来创建对象。这意味着当物体接近玩家时,新的物品或物体将不断地在玩家视野之外的距离创建。

当它们从视野中消失时,无论是玩家与它们发生碰撞还是从玩家身后消失,这些物品都会从场景中移除,以降低内存使用量。

要创建这种效果,我们需要做两件事:首先,我们需要在程序上沿深度轴移动每个项目,以将对象移向相机。其次,我们必须为我们的水面提供一个可以抵消的值,并随着时间的推移增加这个偏移量。

这将产生水面移动越来越快的效果。

现在我们已经解决了如何在场景中向前移动火箭,让我们继续设置我们的项目。

让我们开始制作 游戏 吧!我们需要做的第一件事是设置构建环境。对于这个例子,我选择使用 Typescript 和 Webpack。这篇文章不是要讨论这些技术的有点,所以除了快速总结之外,我不会在这里详细介绍它们。

使用 Webpack 意味着当我们开发项目并保存文件时,Webpack 将看到我们的文件已更改,并使用保存的更改自动重新加载浏览器。

这意味着我们无需在每次进行更改时手动刷新浏览器,从而节省大量时间。这也意味着我们可以使用像three-minifier这样的插件,它可以在我们部署它时减小我们的包的大小。

在我们的示例中使用 TypeScript 意味着我们的项目将具有类型安全性。我发现这在使用 Three.js 的一些内部类型时特别有用,比如Vector3s 和Quaternions. 知道我将正确类型的值分配给变量是非常有价值的。

我们还将在 UI 中使用Materialize CSS。对于我们将用作 UI 的几个按钮和卡片,这个 CSS 框架将有很大帮助。

要开始我们的项目,请创建一个新文件夹。在文件夹中,创建一个package.json并粘贴以下内容:

然后,在命令窗口中,键入npm i以将包安装到新项目中。

我们现在需要创建三个文件,一个基本的 Webpack 配置文件,然后是我们项目的开发和生产配置文件。

在项目文件夹中创建一个webpack.common.js文件并粘贴以下配置:

然后,创建一个webpack.dev.js文件并粘贴这些详细信息。这配置了 Webpack 开发服务器的热重载功能:

最后,创建一个webpack.production.js文件并粘贴这些详细信息:

我们需要做的下一件事是配置 TypeScript 环境以允许我们使用来自 JavaScript 文件的导入。为此,请创建一个tsconfig.json文件并粘贴以下详细信息:

我们的构建环境现在已经配置好了。现在是时候开始为我们的玩家创造一个美丽而可信的场景了。

我们的场景包含以下元素:

我们将在一个名为 game.ts的文件中完成大部分工作,但我们也会将部分 游戏 拆分为单独的文件,这样我们就不会得到一个非常长的文件。我们现在可以继续创建文件game.ts。

因为我们正在处理一个非常复杂的主题,所以我还将包含指向此代码在 GitHub 上的项目中的位置的链接。这应该有望帮助你保持自己的方向,而不是在更大的项目中迷失方向。

我们需要做的第一件事是创建一个Scene,以便 Three.js 有一些东西可以渲染。在我们的game.ts中,我们将添加以下行来构建我们的并将 一个ScenePerspectiveCamera放置在场景中,这样我们就可以看到发生了什么。

最后,我们将为稍后分配的渲染器创建一个引用:

为了设置我们的场景,我们需要执行一些任务,比如创建一个新的WebGLRenderer和设置我们想要绘制的画布的大小。

为此,让我们创建一个init函数并将其也放入我们的game.ts中。此init函数将为我们的场景执行初始设置,并且只运行一次(当 游戏 首次加载时):

我们还需要为场景利用渲染和动画循环。我们需要动画循环来根据需要在屏幕上移动对象,并且我们需要渲染循环来将新帧绘制到屏幕上。

让我们继续在game.ts中创建render函数。 一开始,这个函数看起来很简单,因为它只是请求一个动画帧然后渲染场景。

我们请求动画帧的原因有很多,但其中一个主要原因是如果用户更改选项卡,我们的 游戏 将暂停,这将提高性能并减少设备上可能浪费的资源:

好了,现在我们有了一个空的场景,里面有一个相机,但没有别的了。让我们在场景中添加一些水。

幸运的是,Three.js 包含一个我们可以在场景中使用的水对象示例。它包括实时反射,看起来相当不错;你可以在这里查看。

对我们来说幸运的是,这种水将完成我们在场景中想要做的大部分事情。我们唯一需要做的就是稍微改变水的着色器,这样我们就可以在渲染循环中更新它。

我们这样做是因为如果随着时间的推移,我们越来越多地抵消我们的水纹理,那么它会给我们带来速度的感觉。

作为演示,下面就是我们 游戏 的开场场景,但我每帧都增加了偏移量。随着偏移量的增加,感觉就像我们下方海洋的速度正在增加(即使火箭实际上是静止的)。

水对象可以在 Three.js GitHub 上找到。我们唯一需要做的就是做一个小的改变,使这个偏移量可以从我们的渲染循环中控制(所以我们可以随着时间的推移更新它)。

我们要做的第一件事是在 Three.js 存储库中获取 Water.js 示例的副本。我们将把这个文件objects/water.js放在我们的项目中。如果我们打开water.js文件,大约一半,我们将开始看到如下所示的内容:

这是海洋材质的着色器。着色器本身的介绍超出了本文的范围,但基本上,它们是我们的 游戏 将向用户的计算机提供的关于如何绘制此特定对象的说明。

这里还有我们的着色器代码,它是用 OpenGraph 着色器语言 (GLSL) 编写的,并合并到一个原本是 JavaScript 的文件中。

这没有什么问题,但是如果我们将这个着色器代码单独移动到一个文件中,那么我们可以将 GLSL 支持安装到我们选择的 IDE 中,我们将获得语法着色和验证之类的东西,这有助于我们自定义 GLSL .

要将 GLSL 分解为单独的文件,让我们在当前objects目录中创建一个shader目录,选择 我们的vertexShader和 fragmentShader 的内容, 并将它们分别移动到waterFragmentShader.glsl和waterVertexShader.glsl文件中。

在我们waterFragmentShader.glsl文件的顶部,我们有一个getNoise函数。默认情况下,它看起来像这样:

为了使这个偏移量可以从我们的 游戏 代码中调整,我们想在我们的 GLSL 文件中添加一个参数,允许我们在执行期间对其进行修改。为此,我们必须将此函数替换为以下函数:

你会注意到我们在这个 GLSL 文件中包含了一个新变量:speed变量。这是我们将更新以提供速度感的变量。

在我们的game.ts中,现在需要配置水的设置。在我们文件的顶部,添加以下变量:

然后,在我们的init函数中,必须配置水平面的旋转和位置,如下所示:

这将为海洋提供正确的旋转。

Three.js 带有一个相当令人信服的天空,我们可以在项目中免费使用它。你可以在此处的 Three.js 示例页面中查看此示例。

在我们的项目中添加天空非常容易;只需要将天空添加到场景中,设置天空盒的大小,然后设置一些参数来控制天空的外观。

我们需要对初始场景初始化做的最后一件事是添加一些光照并添加我们的火箭模型和母舰模型:

现在我们有了一些漂亮的水和火箭的场景。但是,我们缺乏任何可以真正使它成为 游戏 的东西。为了解决这个问题,我们需要构建一些基本参数来控制 游戏 并允许玩家朝着某些目标前进。

在我们game.ts文件的顶部,我们将添加以下sceneConfiguration变量,这有助于我们跟踪场景中的对象:

现在,我们必须为玩家所在的当前关卡执行初始化。这个场景设置函数很重要,因为每次用户开始一个新的关卡时都会调用它。

因此,我们需要将火箭的位置设置回起点并清理所有正在使用的旧资产。我在代码行内添加了一些注释,以便你可以看到每一行在做什么:

我们预计有两种类型的设备可以玩我们的 游戏 :台式电脑和手机。为此,我们需要适应两种类型的输入选项:

现在让我们配置这些。

在我们game.ts的开始,我们将添加以下变量来跟踪键盘上是否按下了左键或右键:

然后,在我们的init函数中,我们将注册keydownandkeyup事件来分别调用onKeyDownandonKeyUp函数:

最后,对于键盘输入,我们将记录按下这些键时要执行的操作:

我们的移动用户没有键盘可以输入,因此,我们将使用nippleJS在屏幕上创建一个操纵杆,并使用操纵杆的输出来影响火箭在屏幕上的位置。

在我们的init函数中,我们将通过检查它在屏幕上是否有非零数量的触摸点来检查设备是否是触摸设备。如果是,我们将创建操纵杆,但一旦玩家释放操纵杆的控制,我们还将将火箭的运动设置回零:

在我们的animate函数中,我们会跟踪此时按下左键或右键或操纵杆是否正在使用中的操作。我们还将火箭的位置夹在可接受的左右位置,这样火箭就不能完全移出屏幕:

正如我们已经讨论过的,火箭飞船在我们的场景中保持静止,并且物体朝它移动。这些物体移动的速度随着用户继续玩而逐渐增加,随着时间的推移增加了关卡的难度。

仍然在我们的动画循环中,我们希望逐步将这些对象移向玩家。当对象离开玩家的视野时,我们希望将它们从场景中移除,这样我们就不会占用玩家计算机上不必要的资源。

在我们的渲染循环中,我们可以像这样设置这个功能:

我们可以看到有几个函数是这个调用的一部分:

让我们 探索 一下这些函数在我们的 游戏 中完成了什么。

碰撞检测是我们 游戏 的重要途径。没有它,我们将不知道我们的火箭飞船是否达到了任何目标,或者它是否撞到了岩石并应该减速。这就是我们想要在 游戏 中使用碰撞检测的原因。

通常,我们可以使用物理引擎来检测场景中对象之间的碰撞,但是 Three.js 没有包含物理引擎。

不过,这并不是说 Three.js 不存在物理引擎。他们当然可以,但是为了我们的需要,我们不需要添加物理引擎来检查我们的火箭是否击中了另一个物体。

本质上,我们想回答这个问题,“我的火箭模型目前是否与屏幕上的任何其他模型相交?” 我们还需要根据受到的打击以某些方式做出反应。

例如,如果我们的玩家不断将火箭撞到岩石上,我们需要在受到一定程度的伤害后结束关卡。

为了实现这一点,让我们创建一个函数来检查我们的火箭和场景中的对象的交集。根据玩家击中的内容,我们会做出相应的反应。

我们将把这段代码放在我们的game目录中的一个collisionDetection.ts文件中:

对于碰撞检测,我们唯一需要做的另一件事是添加一个短动画,当用户与对象碰撞时播放该动画。此函数将获取发生碰撞的位置并从该原点生成一些框。

完成的结果将如下所示。

为了实现这一点,我们必须在碰撞发生的地方创建一个圆圈中的盒子,并将它们向外设置动画,这样看起来它们就像从碰撞中爆炸一样。为此,让我们在collisionDetection.ts文件中添加此功能:

这就是我们整理出来的碰撞检测,当物体被破坏时会有一个漂亮的动画。

随着场景的进行,我们希望在玩家的两侧添加一些悬崖,这样感觉就像他们的运动在某个空间内得到了适当的限制。我们使用模运算符在程序上将岩石添加到用户的右侧或左侧:

随着场景的进行,我们还希望将“挑战行”添加到场景中。这些是包含岩石、水晶或盾牌物品的物体。每次创建这些新行中的一个时,我们都会为每一行随机分配岩石、水晶和盾牌。

因此,在上面的示例中,单元格 1、2 和 4 没有添加任何内容,而单元格 3 和 5 分别添加了水晶和盾牌项目。

为了实现这一点,我们将这些挑战行分为五个不同的单元格。我们根据随机函数的输出在每个单元格中生成某个项目,如下所示:

可以在这些链接中的任何一个查看岩石、水晶和盾牌创建功能。

我们需要在渲染循环中完成的最后一件事是:

在我们的渲染函数结束时,我们可以添加以下代码来适应这个功能:

这就是我们的渲染循环完成了。

当人们加载我们的 游戏 时,他们会看到一些让他们能够开始玩的按钮。

这些只是简单的 HTML 元素,我们根据 游戏 中发生的情况以编程方式显示或隐藏它们。问题图标让玩家对 游戏 的内容有所了解,并包含有关如何玩 游戏 的说明。它还包括我们模型的(非常重要的!)许可证。

并且,按下红色按钮开始 游戏 。请注意,当我们点击红色的“播放”按钮时,摄像机会移动并旋转到火箭后面,让玩家准备好开始场景。

在我们的场景init函数中,我们将要执行此操作的事件注册到此按钮的onClick处理程序。要创建旋转和移动功能,我们需要执行以下操作:

为此,我们将在init函数中添加以下代码,如下所示:

当我们的关卡结束时,我们还必须连接我们的逻辑,并且可以在此处查看执行此操作的代码。

在 Three.js 中创建 游戏 可以让你接触到数量惊人的潜在客户。由于人们可以在浏览器中玩 游戏 而无需下载或安装到他们的设备上,因此它成为开发和分发 游戏 的一种非常有吸引力的方式。

正如我们所见,为广泛的用户创造一种引人入胜且有趣的体验是非常有可能的。所以,唯一需要解决的是,你将在 Three.js 中创建什么?

原文链接:http://www.bimant.com/blog/threejs-game-dev-tutorial/

跟你简要说一下我的自学路,以免你走弯路

背景:

有工作,而且很忙;在不影响工作基础上用业余时间鼓捣(经常会因为工作耽搁学习进度);基本没技术背景(初中qbasic、高中pascal半吊子),大学文科,工作也不是搞代码。因此我感觉我的自学路跟你还是挺契合的。

1

两年前的一天,决定要做独立游戏制作人。选定引擎cocos2d,开始学习objective-c语言;(如果你打算只在iOS平台开发可以学obj-c配合cocos2d或者sprite kit;如果打算跨平台就要用cocos2d-x和c++了;当然unity什么的也不错但是我还没尝试过,一直很向往)

2

我跳过了iOS程序开发(似懂非懂看完了斯坦福那一系列的教学视频),结合当时情况直接选择了cocos2d引擎,这时开始接触 @吴一刀 推荐的博客;我主要看的是子龙山人<子龙山人 - 博客园>、Ray Wenderlich<Ray Wenderlich>、Himi<黑米GameDev街区>这些博客里最基本的例子,知易那个以我当时的水平还看不懂。

3

尝试开始设计自己的简单游戏,我做打地鼠!当然比教学例子里的打地鼠复杂多了

4

这个过程中发现自己懂的实在是不够,所以又开啃这本书:Learn cocos2d 2 by Steffen Itterheim &Andreas Low

5

慢慢的发现需要一些趁手的工具和编辑器,并发展出一套自己构建于cocos2d之上的游戏设计架构。我目前的情况是这样:

关卡编辑我使用plist文件配合自己写的类;

sprite sheet使用TexturePacker<TexturePacker>;

动画/UI编辑最初自己手写太累,后来选择了cocosbuilder,请注意现在这个软件已经停止维护,转而引导用户使用SpriteBuilder<SpriteBuilder>(域名都做了自动跳转;SpriteBuilder我木有研究过,建议你研究一下)。

粒子编辑我自己还没实际用到,如果真正需要我感觉cocosbuilder的够我用的。//更新:后来用到了ParticleDesigner和cocosbuilder两个软件的粒子编辑混搭

我当时遇到的一个大麻烦是如何让cocosbuilder、cocos2d版本互相兼容且cocosbuilder无问题。大概情况是这样:cocosbuilder2.1和cocos2d-iphone2.0及2.1版本都不协调怎么办?;What version of Cocosbuilder and Cocos2d-iphone should I choose?

目前我使用的cocosbuilder 2.1版本(渐变层有bug、并且很可惜动画播放完没有触发消息的机制,我只好把每个动画时长都手动记录一下自己处理)

======3月12日更新======

在 @GarfieldKwong 指点下发现这个版本动画播放完是可以触发消息机制的,更高级版本3.x支持的效果应该是在动画中就加入callback的关键帧

新技能get√

具体代码可见下面学习案例的第一个例子里Explosion部分

=====================

cocosbuider的学习可以看两个例子Creating a Game with CocosBuilder;Introduction to CocosBuilder;然后就是多用多尝试。

6

整个学习过程要学会查资料、学会寻求帮助,我主要用的stackoverflow(话说刚刚发现stackoverflow的reputation喜过15,终于可以投票了:)

在学习过程中得到了子龙山人(我认为本尊是这位 @屈光辉 )、LearnCocos2d作者Steffen、 @kubisoft 以及众多网上朋友的帮助,再次表示感谢。

7

硬件及开发者资格:看完c++开始看obj-c,这个过程一直是使用的vmware虚拟机安装的mac系统写程序;然后入手一个最便宜的mac pro、以及iPhone,尝试玩各种游戏;从看c++开始大约过了1年半(已经有非常简陋的游戏demo)才真正购入iOS开发者资格并开始真机测试。

8

尝试过的一些其他工具、各种弯路和坑、以及发散内容:

可以拖拽方式写代码的stencyl <Stencyl: Make iPhone, iPad, Android &Flash Games without code>;

最初学的不是ojc-c而是谭浩强老师的c++程序设计(虽然有人不喜谭老师,但那书是中文的让我对面对对象程序设计有一些初步的概念,再看英文的obj-c不至于太摸不着头脑,反正手头正好有这么一本买了6年没看的c++。。。);

尝试过<Tiled Map Editor>但自己目前的坑没有用到;

尝试过一点物理引擎和粒子,但自己目前的坑没有用到;现在粒子用到了,做了火焰等效果,也挺美的;但是一定要注意CPU占用优化,内存方面我最低支持4S没遇到大问题

学习音乐制作,这个我小时候学琴一直也喜欢音乐所以有一点点底子,如果没有基础可能上手有门槛;开始用的软件LMMS,但是音源插件在64位系统的问题我一直没解决掉时间精力也不多,所以是暂时停滞了;最近发现Garage Band可能也可以写歌;另外还试过一个很有历史的微软的音乐自动编辑器,很好玩,用来做背景音乐也不错。

入手手绘板学画画(这中间纠结了好久才决定用位图不用矢量图),如果你能找到热爱游戏的美术同学一起搞那最好不过。但是我一直对画画心痒难搔。。。所以入了这个深坑,晒一晒:

转载

z

CreateJS包含4个部分,EaselJS、TweenJS、PreloadJS、SoundJS,其中最主要的部分EaselJS包含了开发Html5游戏的所有功能,仅仅使用EaselJS几乎可以完成所有的开发工作,其余三项可以看作EaselJS的辅助工具。比如响应tick事件然后改变元素坐标就可以实现动画功能,而使用TweenJS来创建补间动画,则可以省去你很多代码,简化了操作。一个简单的tick动画看起来是这样的:<pre t="code" l="js">var stage, circle

function init(){

stage = new createjs.Stage(document.getElementById('game'))

createjs.Ticker.addEventListener("tick", handleTick)

createjs.Ticker.setFPS(60)

circle = new createjs.Shape()

circle.graphics.f("red").dc(0,0,50)

circle.x = 0

circle.y = 100

stage.addChild(circle)

circle.addEventListener("click", function(event){

createjs.Ticker.setPaused(!createjs.Ticker.getPaused())

})

}

function handleTick(event){

if(!event.paused){

circle.x +=5

if(circle.x >1000){

circle.x = 0

}

}

stage.update()

}