初学者使用cocos creator制作第一个小游戏

最近微信小游戏开始萌芽,根据老师的要求开始学习cocos creator紧跟互联网的潮流,先是花了几天大概学了学HTML/CSS/Javascript,之后就直接开始看cocos creator的官方文档,看了两三天总算是根据教程一步一步的做了官方提供的第一个小游戏,官方文档上给的第一个小游戏制作过程确实是比较详细的,但是它没有对用到的每个api做详细的解释,估计目的是让初学者大概了解一下一个小游戏的制作过程和cocos creator的大概使用方法。作为一名喜欢钻牛角尖的程序员,我看见不懂的api和用法就特别喜欢深究,所以就花了两三天研究每一个函数,每一个属性,学完之后感觉收获良多,因此就打算写一个官方文档的补充版,以官方文档的快速上手:制作第一个游戏为基础,整合了其他文档内容,同时添加一下自己学习文档的理解,不仅能复习一下自己学到的知识,也帮助新人学习开发更加简单,好了,废话不多说,进入正题。
ps:
1.观看这篇的博客的读者都默认掌握了Javascript基础知识
2.直到编写主角脚本部分之前都基本是官方文档的内容,从编写主角脚本部分开始会在官方文档的基础上加入自己的理解以及整合了官方没有整合到快速上手:制作第一个游戏 的文档内容,灰底部分为重点
3.有能力的读者建议最好还是自己查阅官方API和说明来理解文档内容,这样理解会更加深刻。
4.如有理解错误,欢迎指正

打开初始项目

  1. 我们首先启动 Cocos Creator,然后选择打开其他项目
  2. 在弹出的文件夹选择对话框中,选中我们刚下载并解压完成的 start_project,点击打开按钮
  3. Cocos Creator 编辑器主窗口会打开,我们将看到如下的项目状态
    这里写图片描述

检查游戏资源

下面我们先来了解一下项目中都有哪些资源,请关注名为资源管理器的面板,这里显示的是项目中的所有资源树状结构。(项目资源指的就是游戏的字体、场景图片、主角模型、音乐音效等)
可以看到,项目资源的根目录名叫assets,对应我们解压之后初始项目中的 assets 目录,只有这个目录下的资源才会被 Cocos Creator 导入项目并进行管理。
资源管理器可以显示任意层次的目录结构,我们可以看到这里写图片描述这样的图标就代表一个文件夹,点击文件夹左边的三角图标可以展开文件夹的内容。将文件夹全部展开后, 资源管理器 中就会呈现如下图的状态。
这里写图片描述
每个资源都是一个文件,导入项目后根据扩展名的不同而被识别为不同的资源类型,其图标也会有所区别,下面我们来看看项目中的资源各自的类型和作用:

  • 这里写图片描述声音文件,一般为 mp3 文件,我们将在主角跳跃和得分时播放名为jump和score的声音文件。
  • 这里写图片描述位图字体,由 fnt 文件和同名的 png 图片文件共同组成。位图字体(Bitmap Font)是一种游戏开发中常用的字体资源
  • 各式各样的缩略图标,这些都是图像资源,一般是 png 或 jpg 文件。图片文件导入项目后会经过简单的处理成为texture类型的资源。之后就可以将这些资源拖拽到场景或组件属性中去使用了。

创建游戏场景

场景是承载了所有游戏内容的载体,当玩家运行游戏时,就会载入游戏场景,游戏场景加载后就会自动运行所包含组件的游戏脚本,实现各种各样开发者设置的逻辑功能。所以除了资源以外,游戏场景是一切内容创作的基础,让我们现在就新建一个场景。

  1. 在资源管理器中点击选中assets目录,确保我们的场景会被创建在这个目录下
  2. 点击资源管理器左上角的加号按钮,在弹出的菜单中选择 Scene
    这里写图片描述
  3. 我们创建了一个名叫New Scene的场景文件,右键点击它并选择重命名,将它改名为game
  4. 双击game,就会在 场景编辑器层级编辑器中打开这个场景

了解Canvas

打开场景后, 层级管理器 中会显示当前场景中的所有节点和他们的层级关系。我们刚刚新建的场景中只有一个名叫Canvas的节点,Canvas可以被称为画布节点或渲染根节点,点击选中Canvas,可以在 属性检查器 中看到他的属性。
这里写图片描述
这里的Design Resolution属性规定了游戏的设计分辨率,Fit Height和Fit Width规定了在不同尺寸的屏幕上运行时,我们将如何缩放Canvas以适配不同的分辨率。
由于提供了多分辨率适配的功能,我们一般会将场景中的所有负责图像显示的节点都放在Canvas下面。这样当Canvas的scale(缩放)属性改变时,所有作为其子节点的图像也会跟着一起缩放以适应不同屏幕的大小。

设置场景图像

添加背景

首先在 资源管理器 里按照assets/textures/background的路径找到我们的背景图像资源,点击并拖拽这个资源到 层级编辑器 中的Canvas节点上,直到Canvas节点显示橙色高亮,表示将会添加一个以background为贴图资源子节点
这里写图片描述
这时就可以松开鼠标按键,可以看到Canvas下面添加了一个名叫background的节点当我们使用拖拽资源的方式添加节点时,节点会自动以贴图资源的文件名来命名
我们在对场景进行编辑修改时,可以通过主菜单文件->保存场景来及时保存我们的修改。也可以使用快捷键Ctrl+S(Windows)或Cmd + S(Mac)来保存。

修改背景尺寸

这里写图片描述
在 场景编辑器 中,可以看到我们刚刚添加的背景图像,下面我们将修改背景图像的尺寸,来让他覆盖整个屏幕。
首先选中background节点,然后点击主窗口左上角工具栏第四个 矩形变换工具:
这里写图片描述
使用这个工具我们可以方便的修改图像节点的尺寸,将鼠标移动到 场景编辑器 中 background 的左边,按住并向左拖拽直到 background 的左边超出表示设计分辨率的蓝色线框。然后再用同样的方法将 background 的右边向右拖拽。
这里写图片描述
之后需要拖拽上下两边,使背景图的大小能够填满设计分辨率的线框。
这里写图片描述
在使用 矩形变换工具 修改背景图尺寸时,可以在 属性检查器 中看到 Node (节点)中的 Size 属性也在随之变化,完成后我们的背景图尺寸大概是 (1360, 760)。您也可以直接在 Size 属性的输入框中输入数值,和使用 矩形变换工具 可以达到同样效果。这样大小的背景图在市面流行的手机上都可以覆盖整个屏幕,不会出现穿帮情况。

添加地面

我们的主角需要一个可以在上面跳跃的地面,我们马上来添加一个。用和添加背景图相同的方式,拖拽 资源管理器 中 assets/textures/ground 资源到 层级管理器 的 Canvas 上。这次在拖拽时我们还可以选择新添加的节点和 background 节点的顺序关系。拖拽资源的状态下移动鼠标指针到 background 节点的下方,直到在 Canvas 上显示橙色高亮框,并同时在 background 下方显示表示插入位置的绿色线条,然后松开鼠标,这样 ground 在场景层级中就被放在了 background 下方,同时也是 Canvas 的一个子节点。

在 层级管理器 中,显示在下方的节点的渲染顺序会在上方节点的后面,我们可以看到位于最下的ground物体在 场景编辑器 中显示在了最前。另外子节点也会永远显示在父节点之前,我们可以随时调整节点的层级顺序和关系来控制他们的显示顺序。

按照修改背景的方法,我们也可以使用矩形变换工具来为地面节点设置一个合适的大小。在激活矩形变换工具的时候,如果拖拽节点顶点和四边之外的部分,就可以更改节点的位置。下图是我们设置好的地面节点状态:
这里写图片描述
除了矩形变换工具之外,我们还可以使用移动工具这里写图片描述来改变节点的位置。尝试按住移动工具显示在节点上的箭头并拖拽,就可以一次改变节点在单个座标轴上的位置。

我们在设置背景和地面的位置和尺寸时不需要很精确的数值,可以凭感觉拖拽。如果您偏好比较完整的数字,也可以按照截图直接输入position和size的数值。

添加主角

接下来我们的主角小怪兽要登场了,从 资源管理器 拖拽assets/texture/PurpleMonster到 层级管理器 中 Canvas 的下面,并确保他的排序在 ground 之下,这样我们的主角会显示在最前面。 注意小怪兽节点应该是 Canvas 的子节点,和 ground 节点平行。

为了让主角的光环在场景节点中非常醒目,我们右键点击刚刚添加的PurpleMonster节点,选择重命名之后将其改名为Player。

接下来我们要对主角的属性进行一些设置,首先是改变锚点(Anchor)的位置。默认状态下,任何节点的锚点都会在节点的中心,也就是说该节点中心点所在的位置就是该节点的位置。我们希望控制主角的底部的位置来模拟在地面上跳跃的效果,所以现在我们需要把主角的锚点设置在脚下。在 属性检查器 里找到Anchor属性,把其中的y值设为0,可以看到 场景编辑器 中,表示主角位置的移动工具的箭头出现在了主角脚下。
注意锚点的取值,锚点的取值为(0,0)时表示锚点在节点的左下角,取值为(1,1)时表示锚点在节点的右上角,(0.5,0.5)表示在节点的中心,其他的可以类推。
接下来 场景编辑器 中拖拽Player,把他放在地面上,效果如下图:
这里写图片描述
这样我们基本的场景美术内容就配置好了。下面一节我们要编写代码让游戏里的内容生动起来。

编写主角脚本

Cocos Creator 开发游戏的一个核心理念就是让内容生产和功能开发可以流畅的并行协作,我们在上个部分着重于处理美术内容,而接下来就是通过编写脚本来开发功能的流程,之后我们还会看到写好的程序脚本可以很容易的被内容生产者使用。

如果您从没写过程序也不用担心,我们会在教程中提供所有需要的代码,只要复制粘贴到正确的位置就可以了,之后这部分工作可以找您的程序员小伙伴来解决。下面让我们开始创建驱动主角行动的脚本吧。

  1. 首先在 资源管理器 中右键点击assets文件夹,选择新建->文件夹
    这里写图片描述
  2. 右键点击New Folder,选择重命名,将其改名为scripts,之后我们所有的脚本都会存放在这里。
  3. 右键点击scripts文件夹,选择新建->JavaScript,创建一个JavaScript脚本
  4. 将新建脚本的名字改为Player。双击这个脚本,打开代码编辑器。

注意: Cocos Creator 中脚本名称就是组件的名称,这个命名是大小写敏感的!如果组件名称的大小写不正确,将无法正确通过名称使用组件!

编写组件属性

打开脚本后我们会发现里面已经有一些编辑好的代码块:

cc.Class({
    extends: cc.Component,

    properties: {
        // foo: {
        //     // ATTRIBUTES:
        //     default: null,        // The default value will be used only when the component attaching
        //                           // to a node for the first time
        //     type: cc.SpriteFrame, // optional, default is typeof default
        //     serializable: true,   // optional, default is true
        // },
        // bar: {
        //     get () {
        //         return this._bar;
        //     },
        //     set (value) {
        //         this._bar = value;
        //     }
        // },
    },

    // LIFE-CYCLE CALLBACKS:

    // onLoad () {},

    start () {

    },

    // update (dt) {},
});

让我们来大概了解一下这些代码的作用。首先我们可以看到一个包裹了全局的cc.Class()方法,什么是cc呢?cc是cocos的简称,cocos 引擎的主要命名空间,引擎代码中所有的类,函数,属性和常量都在这个命名空间中定义。而Class()就是cc模块下的一个方法,这个方法用于声明 Cocos Creator 中的类,为了方便区分,我们把使用 cc.Class 声明的类叫做 CCClass。Class()方法的参数是一个原型对象,在原型对象中以键值对的形式设定所需的类型参数,就能创建出所需要的类。
例如:

var Sprite = cc.Class({
    name: "sprite"
});

以上代码用 cc.Class()方法创建了一个类型,并且赋给了 Sprite 变量。同时还将类名设为 “sprite”,类名用于序列化,一般可以省略。
对于cc.Class的详细学习可以参考:http://docs.cocos.com/creator/manual/zh/scripting/class.html

现在我们回到脚本编辑器看回之前的代码,这些代码就是编写一个组件(脚本)所需的结构。具有这样结构的脚本就是 Cocos Creator 中的组件(Component)他们能够挂载到场景中的节点上提供控制节点的各种功能。我们先来设置一些属性,然后看看怎样在场景中调整他们。

// Player.js
    //...
    properties: {
        // 主角跳跃高度
        jumpHeight: 0,
        // 主角跳跃持续时间
        jumpDuration: 0,
        // 最大移动速度
        maxMoveSpeed: 0,
        // 加速度
        accel: 0,
    },
    //...

Cocos Creator规定一个节点具有的属性都需要写在properties代码块中,这些属性将规定主角的移动方式,在代码中我们不需要关心这些数值是多少,因为我们之后会直接在属性检查器中设置这些数值。
以后在游戏制作过程中,我们将需要能够随时调整的属性都可以放在properties中

现在我们可以把 Player 组件添加到主角节点上。在 层级编辑器 中选中 Player 节点,然后在 属性检查器 中点击 添加组件 按钮,选择 添加用户脚本组件->Player,为主角节点添加 Player 组件。
这里写图片描述
现在我们可以在 属性检查器 中(需要选中 Player 节点)看到刚添加的 Player 组件了,按照下图将主角跳跃和移动的相关属性设置好:
这里写图片描述
这些数值除了 jumpDuration 的单位是秒之外,其他的数值都是以像素为单位的,根据我们现在对 Player 组件的设置:我们的主角将能够跳跃 200 像素的高度,起跳到最高点所需的时间是 0.3 秒,最大水平方向移动速度是 400 像素每秒,水平加速度是 350 像素每秒。

这些数值都是建议,一会等游戏运行起来,您完全可以按照自己的喜好随时在 属性检查器 中修改这些数值,不需要改动任何代码,很方便吧!

编写跳跃和移动代码

下面我们添加一个方法,来让主角跳跃起来,在 properties: {…},代码块的下面,添加叫做setJumpAction的方法:

// Player.js
    properties: {
        //...
    },

    setJumpAction: function () {
        // 跳跃上升
        var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
        // 下落
        var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
        // 不断重复
        return cc.repeatForever(cc.sequence(jumpUp, jumpDown));
    },

这里就需要了解一下Cocos Creator的动作(Action)系统了,由于动作系统比较复杂,这里就简单的和初学者形容一下,若形容不当欢迎指正。

在Cocos Creator中,动作简单来说就是节点的位移,缩放和旋转。

例如在上面的代码中,moveBy()方法的作用是在规定的时间内移动指定的一段距离,第一个参数就是我们之前定义主角属性中的跳跃时间,第二个参数是一个Vec2(表示 2D 向量和座标)类型的对象,为了更好的理解,我们可以看看官方给的函数官方说明:

/**
 * !#en
 * Moves a Node object x,y pixels by modifying its position property.                                  <br/>
 * x and y are relative to the position of the object.                                                     <br/>
 * Several MoveBy actions can be concurrently called, and the resulting                                  <br/>
 * movement will be the sum of individual movements.
 * !#zh 移动指定的距离。
 * @method moveBy
 * @param {Number} duration duration in seconds
 * @param {Vec2|Number} deltaPos
 * @param {Number} [deltaY]
 * @return {ActionInterval}
 * @example
 * // example
 * var actionTo = cc.moveBy(2, cc.p(windowSize.width - 40, windowSize.height - 40));
 */
cc.moveBy = function (duration, deltaPos, deltaY) {
    return new cc.MoveBy(duration, deltaPos, deltaY);
};

可以看到,方法moveBy一共可以传入三个参数,前两个参数我们已经知道,第三个参数是Number类型的Y座标,具体作用我自己也还没有搞清楚,我大致的猜测是这样的,我们可以发现第二个参数是可以传入两种类型的,第一种是Number类型,第二种才是Vec2类型,如果我们在这里传入的是Number类型,那么默认这个参数就是X座标,此时就要填第三个参数,为Y座标。上面的例子中cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight))第二个参数传入的是使用cc.p方法构建的Vec2类型对象,这个类型表示的是一个座标,即有X座标也有Y座标,因为不需要再传入第三个参数,这是我个人的理解,如果有错的话希望大佬指出!同时注意官方的一段话x and y are relative to the position of the object. ,这句话的意思是传入的X、Y座标都是相对于节点当前的座标位置,而不是整个座标系的绝对座标。

了解了参数的含义之后,我们再来关注moveBy()方法的返回值,看官方说明可以知道,这个方法返回的是一个ActionInterval类型的对象,ActionInterval在cocos中是一个表示时间间隔动作的类, 这种动作在已定时间内完成。到这里我们就能理解代码
cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut())
前一部分的意思了,它的意思就是构造一个ActionInterval类型的对象,这个对象表示在jumpDuration的时间内,移动到相对于当前节点的(0,this.jumpHeight)的座标位置,简单来说,就是一个向上跳跃的动作。
那么后半部分easing(cc.easeCubicActionOut())的作用是什么呢?easing是ActionInterval类下的一个方法,这个方法可以让时间间隔动作呈现为一种缓动运动,传入的参数是一个缓动对象,返回一个ActionInterval类型对象,这里传入的是使用easeCubicActionInOut方法构建的缓动对象,EaseCubicInOut是按三次函数缓动进入并退出的动作,具体曲线可参考下图:
这里写图片描述
如果还有不懂的可以查看官方api及文档:http://docs.cocos.com/creator/api/zh/modules/cc.html?h=easecubicactionout()

接下来看代码的最后一句return cc.repeatForever(cc.sequence(jumpUp, jumpDown));,repeatForever非常好理解,就如其字面意思:永远地重复一个动作(返回ActionInterval),在这里就是一直重复的跳起来,落下去,这个方法需要传入一个FiniteTimeAction类型的参数,这里传入的参数是使用cc.sequence()方法返回一个ActionInterval类型的对象,sequence()方法的作用是根据传入的Action对象顺序执行动作,创建的动作将按顺序依次运行,所以cc.sequence(jumpUp, jumpDown)就是一个先起跳再下降的动作。

接下来回到游戏制作过程,在onLoad方法里调用刚添加的setJumpAction方法,然后执行runAction来开始动作:

// Player.js
    onLoad: function () {
        // 初始化跳跃动作
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);
    },

onLoad方法会在场景加载后立刻执行,所以我们会把初始化相关的操作和逻辑都放在这里面。我们首先将循环跳跃的动作传给了jumpAction变量,之后调用这个组件挂载的节点下的runAction方法,传入循环跳跃的Action从而让节点(主角)一直跳跃。

保存脚本,然后我们就可以开始第一次运行游戏了!
点击 Cocos Creator 编辑器上方正中的预览游戏按钮这里写图片描述,Cocos Creator 会自动打开您的默认浏览器并开始在里面运行游戏,现在应该可以看到我们的主角——紫色小怪兽在场景中间活泼的蹦个不停了。

移动控制

只能在原地傻蹦的主角可没前途,让我们为主角添加键盘输入,用A和D来控制他的跳跃方向。
setJumpAction方法的下面添加setInputControl方法:

// Player.js
    setJumpAction: function () {
        //...
    },

    setInputControl: function () {
        var self = this;
        // 添加键盘事件监听
        cc.eventManager.addListener({
            event: cc.EventListener.KEYBOARD,
            // 有按键按下时,判断是否是我们指定的方向控制键,并设置向对应方向加速
            onKeyPressed: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = true;
                        self.accRight = false;
                        break;
                    case cc.KEY.d:
                        self.accLeft = false;
                        self.accRight = true;
                        break;
                }
            },
            // 松开按键时,停止向该方向的加速
            onKeyReleased: function(keyCode, event) {
                switch(keyCode) {
                    case cc.KEY.a:
                        self.accLeft = false;
                        break;
                    case cc.KEY.d:
                        self.accRight = false;
                        break;
                }
            }
        }, self.node);
    },

有Android开发经验的同学比较好理解,这里的监听器实质上就和Android里的OnClickListener差不多,在cc中通过eventManager来管理事件监听器注册和派发系统事件。原始设计中,它支持鼠标,触摸,键盘,陀螺仪和自定义事件。 在 Creator 的设计中,鼠标,触摸和自定义事件的监听和派发请参考:http://cocos.com/docs/creator/scripting/events.html

总之这里通过向eventManager注册了一个监听器,这个监听器用来监听键盘输入,通过
switch判断键盘上的A和D是否被按下或松开,若按下就执行对应的操作,由于事件管理器和监听器比较复杂,这里不过多赘述,想了解的同学麻烦查看官方api及文档。

然后修改onLoad方法,在其中加入向左和向右加速的开关,以及主角当前在水平方向的速度,最后再调用我们刚添加的setInputControl方法,在场景加载后就开始监听键盘输入:

// Player.js
    onLoad: function () {
        // 初始化跳跃动作
        this.jumpAction = this.setJumpAction();
        this.node.runAction(this.jumpAction);

        // 加速度方向开关
        this.accLeft = false;
        this.accRight = false;
        // 主角当前水平方向速度
        this.xSpeed = 0;

        // 初始化键盘输入监听
        this.setInputControl();
    },

最后修改update方法的内容,添加加速度、速度和主角当前位置的设置:

// Player.js
    update: function (dt) {
        // 根据当前加速度方向每帧更新速度
        if (this.accLeft) {
            this.xSpeed -= this.accel * dt;
        } else if (this.accRight) {
            this.xSpeed += this.accel * dt;
        }
        // 限制主角的速度不能超过最大值
        if ( Math.abs(this.xSpeed) > this.maxMoveSpeed ) {
            // if speed reach limit, use max speed with current direction
            this.xSpeed = this.maxMoveSpeed * this.xSpeed / Math.abs(this.xSpeed);
        }

        // 根据当前速度更新主角的位置
        this.node.x += this.xSpeed * dt;
    },

update在场景加载后就会每帧调用一次,传回的参数dt是每帧调用的时间,我们一般把需要经常计算或及时更新的逻辑内容放在这里。在我们的游戏中,根据键盘输入获得加速度方向后,就需要每帧在update中计算主角的速度和位置,this.node.x就是当前组件挂载的节点的x座标

保存脚本后,下面就可以去泡杯茶,点击预览游戏来看看我们最新的成果。在浏览器打开预览后,用鼠标点击一下游戏画面(这是浏览器的限制,要点击游戏画面才能接受键盘输入),然后就可以按A和D键来控制主角左右移动了!

感觉移动起来有点迟缓?主角跳的不够高?希望跳跃时间长一些?没问题,这些都可以随时调整。只要为Player组件设置不同的属性值,就可以按照您的想法调整游戏。这里有一组设置可供参考:

Jump Height: 150
Jump Duration: 0.3
Max Move Speed: 400
Accel: 1000

这组属性设置会让主角变得灵活无比,至于如何选择,就看您想做一个什么风格的游戏了。

制作星星

主角现在可以跳来跳去了,我们要给玩家一个目标,也就是会不断出现在场景中的星星,玩家需要引导小怪兽碰触星星来收集分数。被主角碰到的星星会消失,然后马上在随机位置重新生成一个。

制作Prefab

对于需要重复生成的节点,我们可以将他保存成 Prefab(预制) 资源,作为我们动态生成节点时使用的模板。关于 Prefab 的更多信息,请阅读预制资源

首先从 资源管理器 中拖拽 assets/textures/star 图片到场景中,位置随意,我们只是需要借助场景作为我们制作 Prefab 的工作台,制作完成后会我们把这个节点从场景中删除。

我们不需要修改星星的位置或渲染属性,但要让星星能够被主角碰触后消失,我们需要为星星也添加一个专门的组件。按照和添加 Player 脚本相同的方法,添加名叫 Star 的 JavaScript 脚本到 assets/scripts/ 中。
这里写图片描述
接下来双击这个脚本开始编辑,星星组件只需要一个属性用来规定主角距离星星多近时就可以完成收集,修改 properties,加入以下内容:

// Star.js
    properties: {
        // 星星和主角之间的距离小于这个数值时,就会完成收集
        pickRadius: 0
    },

保存脚本后,将这个脚本添加到刚创建的 star 节点上。然后在 属性检查器 中把 Pick Radius 属性值设为 60:
这里写图片描述
Star Prefab 需要的设置就完成了,现在从 层级管理器 中将 star 节点拖拽到 资源管理器 中的 assets 文件夹下,就生成了名叫 star 的 Prefab 资源。
这里写图片描述
现在可以从场景中删除 star 节点了,后续可以直接双击这个 star Prefab 资源进行编辑。

接下去我们会在脚本中动态使用星星的 Prefab 资源生成星星。

添加游戏控制脚本

星星的生成是游戏主逻辑的一部分,所以我们要添加一个叫做Game的脚本作为游戏主逻辑脚本,这个脚本之后还会添加计分、游戏失败和重新开始的相关逻辑。

添加Game脚本到assets/scripts文件夹下,双击打开脚本。首先添加生成星星需要的属性:

// Game.js
    properties: {
        // 这个属性引用了星星预制资源
        starPrefab: {
            default: null,
            type: cc.Prefab
        },
        // 星星产生后消失时间的随机范围
        maxStarDuration: 0,
        minStarDuration: 0,
        // 地面节点,用于确定星星生成的高度
        ground: {
            default: null,
            type: cc.Node
        },
        // player 节点,用于获取主角弹跳的高度,和控制主角行动开关
        player: {
            default: null,
            type: cc.Node
        }
    },

这里初学者可能会疑惑,为什么像starPrefab这样的属性会用中括号括起来,括号里面还有新的“属性”呢?其实这是属性的一种完整声明,之前我们的属性声明都是不完整的,有些情况下,我们需要为属性声明添加参数,这些参数控制了属性在 属性检查器 中的显示方式,以及属性在场景序列化过程中的行为。例如:

properties: {
    score: {
        default: 0,
        displayName: "Score (player)",
        tooltip: "The score of player",
    }
}

以上代码为 score 属性设置了三个参数 default, displayName 和 tooltip。这几个参数分别指定了 score 的默认值(default)为 0,在 属性检查器 里,其属性名(displayName)将显示为:“Score (player)”,并且当鼠标移到参数上时,显示对应的 Tooltip。

下面是常用参数:

default: 设置属性的默认值,这个默认值仅在组件第一次添加到节点上时才会用到
type: 限定属性的数据类型,详见 CCClass 进阶参考:type 参数
visible: 设为 false 则不在 属性检查器 面板中显示该属性
serializable: 设为 false 则不序列化(保存)该属性
displayName: 在 属性检查器 面板中显示成指定名字
tooltip: 在 属性检查器 面板中添加属性的 Tooltip

所以上面的代码

        starPrefab: {
            default: null,
            type: cc.Prefab
        },

就可以理解了,首先在Game组件下声明了starPrefab属性,这个属性默认值为null,能传入的类型必须是Prefab预制资源类型。这样之后的ground、player属性也可以理解了。

保存脚本后将Game组件添加到 层级编辑器 中的Canvas节点上(选中Canvas节点后,拖拽脚本到 属性检查器 上,或点击 属性检查器 的 添加组件 按钮,并从 用户自定义脚本 中选择 Game,接下来从 资源管理器 中拖拽star Prefab 资源到Game组件的Star Prefab属性中。这是我们第一次为属性设置引用,只有在属性声明时规定type为引用类型时(比如我们这里写的cc.Prefab类型),才能够将资源或节点拖拽到该属性上。

接下来从 层级编辑器 中拖拽ground和Player 节点到组件中相同名字的属性上,完成节点引用。

然后设置Min Star Duration和Max Star Duration属性的值为3和5,之后我们生成星星时,会在这两个之间随机取值,就是星星消失前经过的时间。

在随机位置生成星星

接下来我们继续修改Game脚本,在onLoad方法后面添加生成星星的逻辑:

// Game.js
    onLoad: function () {
        // 获取地平面的 y 轴座标
        this.groundY = this.ground.y + this.ground.height/2;
        // 生成一个新的星星
        this.spawnNewStar();
    },

    spawnNewStar: function() {
        // 使用给定的模板在场景中生成一个新节点
        var newStar = cc.instantiate(this.starPrefab);
        // 将新增的节点添加到 Canvas 节点下面
        this.node.addChild(newStar);
        // 为星星设置一个随机位置
        newStar.setPosition(this.getNewStarPosition());
    },

    getNewStarPosition: function () {
        var randX = 0;
        // 根据地平面位置和主角跳跃高度,随机得到一个星星的 y 座标
        var randY = this.groundY + cc.random0To1() * this.player.getComponent('Player').jumpHeight + 50;
        // 根据屏幕宽度,随机得到一个星星 x 座标
        var maxX = this.node.width/2;
        randX = cc.randomMinus1To1() * maxX;
        // 返回星星座标
        return cc.p(randX, randY);
    }

这里需要注意几个问题:
一、节点下的y属性对应的是锚点所在的y座标,因为锚点默认在节点的中心,所以需要加上地面高度的一半才是地面的y座标。
二、instantiate方法的作用是:克隆指定的任意类型的对象,或者从 Prefab 实例化出新节点,返回值为Node或者Object
三、Node下的addChild方法结果是将新节点建立在该节点的下一级,所以新节点的显示效果在该节点之上。
四、Node下的setPosition方法作用是:设置节点在父节点座标系中的位置。可以通过两种方式设置座标点:1、传入 2 个数值 x 和 y。2、传入 cc.v2(x, y) 类型为 cc.Vec2 的对象。
五、通过Node下的getComponent方法可以得到该节点上挂载的组件引用。

保存脚本以后点击预览游戏按钮,在浏览器中可以看到,游戏开始后动态生成了一颗星星!用同样的方法,您可以在游戏中动态生成任何预先设置好的以 Prefab 为模板的节点。
这里写图片描述

添加主角碰触收集星星的行为

现在要添加主角收集星星的行为逻辑了,这里的重点在于,星星要随时可以获得主角节点的位置,才能判断他们之间的距离是否小于可收集距离,如何获得主角节点的引用呢?别忘了我们前面做过的两件事:

  1. Game组件中有个名叫player的属性,保存了主角节点的引用。
  2. 每个星星都是在Game脚本中动态生成的。

所以我们只要在Game脚本生成Star节点实例时,将Game组件的实例传入星星并保存起来就好了,之后我们可以在星星组件里随时通过game.player来访问到主角节点,从而获取到主角节点的位置。让我们打开Game脚本,在spawnNewStar方法最后面添加这样一句:

// Game.js
    spawnNewStar: function() {
        // ...
        // 将 Game 组件的实例传入星星组件
        newStar.getComponent('Star').game = this;
    },

保存后打开Star脚本,现在我们可以利用Game组件中引用的player节点来判断距离了,在onLoad方法后面添加名为getPlayerDistance和onPicked的方法:

// Star.js
    getPlayerDistance: function () {
        // 根据 player 节点位置判断距离
        var playerPos = this.game.player.getPosition();
        // 根据两点位置计算两点之间距离
        var dist = cc.pDistance(this.node.position, playerPos);
        return dist;
    },

    onPicked: function() {
        // 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星
        this.game.spawnNewStar();
        // 然后销毁当前星星节点
        this.node.destroy();
    },

Node下的getPosition()方法返回的是节点的在父节点座标系中的位置(x, y),即一个Vec2类型对象。cc下的pDistance方法很简单,这里不多述,同时注意调用Node下的destroy()方法就可以销毁节点。

然后在update方法中添加每帧判断距离,如果距离小于pickRadius属性规定的收集距离,就执行收集行为:

// Star.js
    update: function (dt) {
        // 每帧判断和主角之间的距离是否小于收集距离
        if (this.getPlayerDistance() < this.pickRadius) {
            // 调用收集行为
            this.onPicked();
            return;
        }
    },

保存脚本,然后再次预览测试,可以看到控制主角靠近星星时,星星就会消失掉,然后在随机位置生成了新的星星!

添加得分

小怪兽辛辛苦苦的收集星星,没有奖励怎么行,让我们现在就在收集星星时添加得分奖励的逻辑和显示。

添加分数文字(Label)

游戏开始时得分从0开始,每收集一个星星分数就会加1。要显示得分,首先要创建一个 Label 节点。在 层级管理器 中选中Canvas节点,右键点击并选择菜单中的创建新节点->创建渲染节点->Label(文字),一个新的 Label 节点会被创建在Canvas下面,而且顺序在最下面。接下来我们要用如下的步骤配置这个 Label 节点:

  1. 将该节点名字改为score
  2. 将score节点的位置(position属性)设为(0, 180)。
  3. 选中该节点,编辑Label组件的string属性,填入Score: 0的文字。
  4. 将Label组件的Font Size属性设为50。
  5. 从 资源管理器 中拖拽assets/mikado_outline_shadow位图字体资源(注意图标是bmfont)到Label组件的Font属性中,将文字的字体替换成我们项目资源中的位图字体。

这里注意Label组件的String属性加了字体后,无法识别中文的冒号,所以建议打英文的冒号。

完成后效果如下图所示:
这里写图片描述

在 Game 脚本中添加得分逻辑

我们将会把计分和更新分数显示的逻辑放在Game脚本里,打开Game脚本开始编辑,首先在properties区块的最后添加分数显示 Label 的引用属性:

// Game.js
    properties: {
        // ...
        // score label 的引用
        scoreDisplay: {
            default: null,
            type: cc.Label
        }
    },

接下来在onLoad方法里添加计分用的变量的初始化:

// Game.js
    onLoad: function () {
        // ...
        // 初始化计分
        this.score = 0;
    },

然后在update方法后面添加名叫gainScore的新方法:

// Game.js
    gainScore: function () {
        this.score += 1;
        // 更新 scoreDisplay Label 的文字
        this.scoreDisplay.string = 'Score: ' + this.score.toString();
    },

这里的toString()方法是将分数score转换为能String字符串类型便于输出到屏幕。

保存 Game 脚本后,回到 层级管理器,选中 Canvas 节点,然后把前面添加好的 score 节点拖拽到 属性检查器 里 Game 组件的 Score Display 属性中。

在 Star 脚本中调用 Game 中的得分逻辑

下面打开Star脚本,在onPicked方法中加入gainScore的调用:

// Star.js
    onPicked: function() {
        // 当星星被收集时,调用 Game 脚本中的接口,生成一个新的星星
        this.game.spawnNewStar();
        // 调用 Game 脚本的得分方法
        this.game.gainScore();
        // 然后销毁当前星星节点
        this.node.destroy();
    },

保存后预览,可以看到现在收集星星时屏幕正上方显示的分数会增加了!
这里写图片描述

失败判定和重新开始

现在我们的游戏已经初具规模,但得分再多,不可能失败的游戏也不会给人成就感。现在让我们加入星星定时消失的行为,而且让星星消失时就判定为游戏失败。也就是说,玩家需要在每颗星星消失之前完成收集,并不断重复这个过程完成玩法的循环。

为星星加入计时消失的逻辑

打开Game脚本,在onLoad方法的spawnNewStar调用之前加入计时需要的变量声明:

// Game.js
    onLoad: function () {
        // ...
        // 初始化计时器
        this.timer = 0;
        this.starDuration = 0;
        // 生成一个新的星星
        this.spawnNewStar();
        // 初始化计分
        this.score = 0;
    },

然后在spawnNewStar方法最后加入重置计时器的逻辑,其中this.minStarDuration和this.maxStarDuration是我们一开始声明的Game组件属性,用来规定星星消失时间的随机范围:

// Game.js
    spawnNewStar: function() {
        // ...
        // 重置计时器,根据消失时间范围随机取一个值
        this.starDuration = this.minStarDuration + cc.random0To1() * (this.maxStarDuration - this.minStarDuration);
        this.timer = 0;
    },

在update方法中加入计时器更新和判断超过时限的逻辑:

// Game.js
    update: function (dt) {
        // 每帧更新计时器,超过限度还没有生成新的星星
        // 就会调用游戏失败逻辑
        if (this.timer > this.starDuration) {
            this.gameOver();
            return;
        }
        this.timer += dt;
    },

最后加入gameOver方法,游戏失败时重新加载场景。

// Game.js
    gameOver: function () {
        this.player.stopAllActions(); //停止 player 节点的跳跃动作
        cc.director.loadScene('game');
    }

这里需要初学者了解的是,cc.director 是一个管理你的游戏的逻辑流程的单例对象。由于 cc.director 是一个单例,你不需要调用任何构造函数或创建函数,使用它的标准方法是通过调用cc.director.methodName() ,例如这里的cc.director.loadScene('game')就是重新加载游戏场景game,也就是游戏重新开始。

而节点下的stopAllActions方法就显而易见了,这个方法会让节点上的所有Action都失效。

对Game脚本的修改就完成了,保存脚本,然后打开Star脚本,我们需要为即将消失的星星加入简单的视觉提示效果,在update方法最后加入以下代码:

// Star.js
    update: function() {
        // ...
        // 根据 Game 脚本中的计时器更新星星的透明度
        var opacityRatio = 1 - this.game.timer/this.game.starDuration;
        var minOpacity = 50;
        this.node.opacity = minOpacity + Math.floor(opacityRatio * (255 - minOpacity));
    }

这串代码比较简单,就是为星星节点设置一个透明度,来提示玩家生成的星星何时会消失。

保存Star脚本,我们的游戏玩法逻辑就全部完成了!现在点击预览游戏按钮,我们在浏览器看到的就是一个有核心玩法、激励机制、失败机制的合格游戏了。

加入音效

尽管很多人玩手游的时候会无视声音,我们为了教程展示的工作流程尽量完整,还是要补全加入音效的任务。

跳跃音效

首先加入跳跃音效,打开Player脚本,添加引用声音文件资源的jumpAudio属性:

// Player.js
    properties: {
        // ...
        // 跳跃音效资源
        jumpAudio: {
            default: null,
            url: cc.AudioClip
        },
    },

然后改写setJumpAction方法,插入播放音效的回调,并通过添加playJumpSound方法来播放声音:

// Player.js
    setJumpAction: function () {
        // 跳跃上升
        var jumpUp = cc.moveBy(this.jumpDuration, cc.p(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
        // 下落
        var jumpDown = cc.moveBy(this.jumpDuration, cc.p(0, -this.jumpHeight)).easing(cc.easeCubicActionIn());
        // 添加一个回调函数,用于在动作结束时调用我们定义的其他方法
        var callback = cc.callFunc(this.playJumpSound, this);
        // 不断重复,而且每次完成落地动作后调用回调来播放声音
        return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
    },

    playJumpSound: function () {
        // 调用声音引擎播放声音
        cc.audioEngine.playEffect(this.jumpAudio, false);
    },

这里需要强调的是回调函数的作用,我们首先来看官方对callFunc()方法的定义:

/**
 * !#en Creates the action with the callback.
 * !#zh 执行回调函数。
 * @method callFunc
 * @param {function} selector
 * @param {object} [selectorTarget=null]
 * @param {*} [data=null] - data for function, it accepts all data types.
 * @return {ActionInstant}
 * @example
 * // example
 * // CallFunc without data
 * var finish = cc.callFunc(this.removeSprite, this);
 *
 * // CallFunc with data
 * var finish = cc.callFunc(this.removeFromParentAndCleanup, this._grossini,  true);
 */
cc.callFunc = function (selector, selectorTarget, data) {
    return new cc.CallFunc(selector, selectorTarget, data);
};

我们可以看到callFunc方法可以传入三个参数,第一个参数是方法的selector,我们可以理解为方法名,第二个参数是Object类型,一般填入this,第三个参数为带回的数据,可以是所有数据类型,可以不填。我们再注意到这个方法的返回值——ActionInstant,这是一个瞬间执行的动作类,到这里我们就可以理解了,使用callFunc调用回调函数可以让函数转变为cc中的Action(动作)!这一用法在cc的动作系统里非常好用!例如在上面我们将播放声音的函数传入callFunc赋值给callBack,让callBack成为了一个播放声音的动作Action,那么我们之后就能通过cc.sequence将跳跃和播放声音的动作组合起来,实现每跳一次就能播放音效的功能

得分音效

保存Player脚本以后打开Game脚本,来添加得分音效,首先仍然是在properties中添加一个属性来引用声音文件资源:

// Game.js
    properties: {
        // ...
        // 得分音效资源
        scoreAudio: {
            default: null,
            url: cc.AudioClip
        }
    },

然后在gainScore方法里插入播放声音的代码:

// Game.js
    gainScore: function () {
        this.score += 1;
        // 更新 scoreDisplay Label 的文字
        this.scoreDisplay.string = 'Score: ' + this.score.toString();
        // 播放得分音效
        cc.audioEngine.playEffect(this.scoreAudio, false);
    },

保存脚本,回到 层级编辑器 ,选中Player节点,然后从 资源管理器 里拖拽assets/audio/jump资源到Player组件的Jump Audio属性上。

然后选中Canvas节点,把assets/audio/score资源拖拽到Game组件的Score Audio属性上。

这样就大功告成了!完成形态的场景层级和各个关键组件的属性如下:
这里写图片描述
这里写图片描述
这里写图片描述
现在我们可以尽情享受刚制作完成的游戏了,您能打到多少分呢?别忘了您可以随时修改Player和Game组件里的移动控制和星星持续时间等游戏参数,来快速调节游戏的难度。修改组件属性之后需要保存场景,修改后的数值才会被记录下来。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章