【干货】Cocos Creator制作一个微信小游戏(下)

| 导语 微信小游戏都火成这样了,为什么不尝试一下? 我们的目标是使用Cocos Creator从零开始制作一个小游戏,并放到微信上玩。

上文链接:Cocos Creator制作一个微信小游戏(上)

四、游戏逻辑

开始游戏菜单逻辑

在Script文件夹上右键-新建-JavaScript,创建一个名字为Menu的代码文件。

双击一下资源管理器中的Menu场景,再在层级管理器中单击一下Canvas节点,显示节点的属性检查器,然后把Script目录中的Menu代码文件搬去到属性检查器中,以实现代码和场景的绑定。

下图是绑定成功后的样子。

然后用你喜欢的代码编辑器打开编辑Menu代码文件。

创建的代码,会帮你做好一些结构,有一些是注释掉的,自己清理一下,保留onLoad方法,并在properties结构中添加startBtn属性,如下:

cc.Class({
    extends: cc.Component,
    properties: {
        startBtn:{
            default: null,
            type: cc.Button
        }
    },

    onLoad () {
        //场景加载时执行
    }
});

保存代码后回到Cocos Creator,应该会自动刷新一下,可以看到属性检查器中多了StartBtn一项。

在层级管理器中,找到startBtn,并拖动到上面箭头所指的位置,就实现了startBtn组件和代码中的startBtn属性的绑定,接下来可以在代码中调用这个按钮了。

继续编写代码,添加对startBtn的监听,和相应的处理方法,如下:

cc.Class({
    extends: cc.Component,
    properties: {
        startBtn: {
            default: null,
            type: cc.Button
        }
    },

    onLoad () {
        //场景加载时执行
        //侦听touchend事件来触发开始游戏方法。不能用click,否则在微信中无效。
        this.startBtn.node.on('touchend', this.startGame.bind(this));
    },

    startGame: function () {
        //调用系统方法加载Game场景
        cc.director.loadScene('Game');
    }
});

预览一下,点击开始游戏按钮时,会进入游戏界面,说明一切顺利。

游戏逻辑

添加一个Game代码文件,绑定到Game场景。并编辑代码为正面的样子:

cc.Class({
    extends: cc.Component,

    properties: {
        sound1: {
            default: null,
            type: cc.Sprite
        },
        sound2: {
            default: null,
            type: cc.Sprite
        },
        help: {
            default: null,
            type: cc.Sprite
        },
        setting: {
            default: null,
                        type: cc.Sprite
        },
        scoreLabel: {            default: null,            type: cc.Label
        },
        tipText: {            default: null,            type: cc.RichText
        }
    },

    onLoad () {

    },

    update (dt) {
        //每帧会执行的方法
    },
});

 然后返回Game场景,把各个需要在代码中调用的元素都拖过来绑定一遍:

现在我们需要把几个小球做成可以通过new方法创建实例的预制资源。

从资源管理器中把1号小球拖到任意一个场景中,再在层级管理器中把小球命名为Ball1,然后把Ball1拖回资源管理器中,再从层级管理器中把Ball1删除,即可实现把小球做成预制资源。

重复操作把所有小球都做成预制资源。

在Game.js代码的properties结构中,添加Ball1-Ball6 六个属性,然后返回Cocos Creator绑定。

接下来添加实现寻路的代码文件。直接把本项目中的AStar.js代码复制到你的代码目录中即可。这里不打算介绍AStar实现寻路的原理,网上有很多相关资料,可以自行搜索。现在只需要当成第三方库直接用即可。

Cocos Creator支持require方法,可以很容易实现模块化开发。

在Game.js顶部引入AStar,如图:

可以开始写主逻辑了,在Game.js的onLoad函数中写入游戏初始化代码,并创建几个待调用的方法,此步骤完整代码如下:

var {AStar, Grid, Node} = require("AStar");

cc.Class({
    extends: cc.Component,

    properties: {
        Ball1: {
            default: null,
            type: cc.Prefab
        },
        Ball2: {
            default: null,
            type: cc.Prefab
        },
        Ball3: {            
            default: null,
            type: cc.Prefab
        },
        Ball4: {            
            default: null,
            type: cc.Prefab
        },
        Ball5: {            
            default: null,
            type: cc.Prefab
        },
        Ball6: {            
            default: null,
            type: cc.Prefab
        },
        gameContent: {            
            default: null,
            type: cc.Node
        },
        sound1: {            
            default: null,
            type: cc.Sprite
        },
        sound2: {            
            default: null,
            type: cc.Sprite
        },
        help: {            
            default: null,
            type: cc.Sprite
        },
        setting: {            
            default: null,
            type: cc.Sprite
        },
        scoreLabel: {            
            default: null,
            type: cc.Label
        },
        tipText: {            
            default: null,
            type: cc.RichText
        }
    },

    onLoad () {        
        //默认隐藏提示信息
        this.tipText.node.active = false; 
        
        //行列数
        this.row = 14;        
        this.col = 10;        
        this.maxNodes = this.row * this.col;        
        
        //格子尺寸
        this.gridSize = 50;        
        
        //每次增加小球数
        this.addBallsCount = 4;        
        
        //棋盘边距
        this.paddingLeft = 20;        
        this.paddingTop = 150;        
        
        //棋盘在坐标系的起始位置(根据cocos 坐标系统推算)
        this.posTop = 480 - this.paddingTop;        
        this.posLeft = -270 + this.paddingLeft;        
        
        //记录选中的格子
        this.node1 = undefined;        
        this.node2 = undefined;        
        
        //是否正在移动小球
        this.movingBall = false;        
        
        //游戏是否已经结束
        this.gameOver = false;        
        
        //用于存放将要被清掉的小球的数组
        this.toClearBallsArr = [];        
        
        //游戏得分
        this.score = 0;        
        this.displayScore = 0;        
        this.updateScore();        
        
        //开始创建棋盘并添加小球
        let maxCount = this.row * this.col;
        let startCount = Math.round(maxCount * 0.1);        
        //创建一个随机棋盘,得到的是一个二维数组,结构如[[0,1,1,0],[0,1,0,0]...]
        let startArr = this.getRandomArr(maxCount, startCount);        
        
        this.grid = new Grid(this.col, this.row);        
        this.aStar = new AStar();        
        
        this.mapArr = [];        
        this.nodesArr = [];        
        for (let i = 0; i < this.col; i++) {
            let arr = [];            
            this.mapArr.push(arr);            
            for (let j = 0; j < this.row; j++) {
                let value = startArr.pop();
                let node;                
                if (value) {
                    let randomId = parseInt(cc.random0To1() * 6 + 1);
                    let Prefab = this['Ball' + randomId];
                    let newBall = cc.instantiate(Prefab);
                    newBall.type = randomId;                    
                    this.gameContent.addChild(newBall);
                    newBall.setPosition(cc.p(this.posLeft + 25 + i * 50, this.posTop - 25 - j * 50));                    
                    //newBall.scale = 0.3;
                    node = {ball: newBall, val: value, x: i, y: j}
                    arr.push(node);                    
                    this.grid.setWalkable(i, j, false);
                } else {                    
                    this.grid.setWalkable(i, j, true);
                    node = {ball: undefined, val: value, x: i, y: j};
                    arr.push(node);
                }                
                this.nodesArr.push(node);
            }
        }
                        
        this.node.on('touchend', this.onTouchEnd.bind(this));        
        this.sound1.node.on('touchend', function () {            
            //静音
        }.bind(this));        
            
        this.sound2.node.on('touchend', function () {            
            //解除
        }.bind(this));

        let _this = this;        
        this.help.node.on('touchend', function () {            
            //显示帮助
        }.bind(this));        
            
        this.setting.node.on('touchend', function () {            
            //显示设置面板
        }.bind(this));
    },

    onTouchEnd: function (e) {

    },

    updateScore: function () {        
        this.scoreLabel.string = this.displayScore;
    },    
        
    //创建一个包含指定数量真值的二维数组
    getRandomArr: function (maxNum, trueNum) {
        let arr = [];        
        for (let i = 0; i < maxNum; i++) {
            arr.push(i > trueNum - 1 ? 0 : 1);
        }

        arr.sort(function () {            
            return cc.random0To1() > 0.5 ? 1 : -1;
        })        
            
        return arr;
    },

    update (dt) {        
        //每帧会执行的方法
    },
});

预览一下,应该能看到游戏界面初始化后的样子:

随机产生了14个不同状态的小球。

接着我们处理选择小球和移动小球。

onTouchEnd: function (e) {        
        if (this.movingBall) {
            console.log('正在移动小球');            
            return;
        }        
        if (this.gameOver) {
            console.log('游戏已结束');            
            return;
        }        
        //获取点击的位置,并根据点击位置获取到对应的格子。
        let x = e.touch._point.x;
        let y = e.touch._point.y;

        x = x - this.paddingLeft;
        y = this.node.height - this.paddingTop - y;
        console.log(x, y);
        let colId = Math.floor(x / this.gridSize);
        let rowId = Math.floor(y / this.gridSize);        
        if (!(colId >= 0 && colId < this.col && rowId >= 0 && rowId < this.row)) {
            console.log('没有选中格子');            
            return;
        }
        let clickNode = this.mapArr[colId][rowId];        

        //
        if (!this.node1) {            
            //第一点击的是小球,只需要记录一下
            if (clickNode.ball) {                
                //console.log('选中了小球:', rowId, colId);
                this.node1 = this.mapArr[colId][rowId];
            } else {                //console.log('请选择一个小球');
                this.AudioPlayer.playSound('warm');
            }
        } else {            
            if (clickNode.ball) {                
                //第二次点击的还是小球,也是记录一下,替代第一次点击的小球
                //console.log('重新选中了小球:', rowId, colId);
                this.node1 = this.mapArr[colId][rowId];
            } else {                
                //第二次点击的如果是空格子,就开始处理移动小球逻辑了
                //设置网格,开始寻路
                this.node2 = this.mapArr[colId][rowId];                
                this.grid.setStartNode(this.node1.x, this.node1.y);                
                this.grid.setEndNode(this.node2.x, this.node2.y);                
                this.grid.setWalkable(this.node1.x, this.node1.y, true);                
                if (this.aStar.findPath(this.grid)) {                    
                    //找到了通路
                    let pathArr = this.aStar.get_path();
                    pathArr.shift();
                    let ball = this.mapArr[this.node1.x][this.node1.y].ball;                    
                    this.movingBall = true;                    
                    this.grid.setWalkable(this.node2.x, this.node2.y, false);                    
                    this.node2.ball = ball;                    
                    this.mapArr[this.node1.x][this.node1.y].ball = undefined;
                    let _this = this;                    
                    this.moveBall(ball, pathArr, function () {
                        _this.movingBall = false;
                    });
                }
            }
        }
    },

移动小球。

moveBall: function (ball, pathArr, onComplete) {        
        let node = pathArr.shift();        
        let newPosition = cc.p(this.posLeft + 25 + node.x * this.gridSize, this.posTop - 25 - node.y * this.gridSize);
        ball.setPosition(newPosition);        
        let _this = this;        
        if (pathArr.length) {
            setTimeout(() => {
                _this.moveBall(ball, pathArr, onComplete)
            }, 50);
        } else {
            onComplete();
        }
    },

至此,我们已经通过代码实现了开始菜单到游戏场景的跳转、游戏初始化、寻路和移动小球等功能,接下来的游戏逻辑,篇幅所限,接下来的游戏逻辑部分,不打算在这里详细讲了,有兴趣的可以直接下载源码自行研究,项目源码在文章后面。

五、游戏动画

增加一些动态效果,游戏应该会生动很多。

比如我们点击小球的时候,可以让小球处理一个循环放大缩小的动画效果。做法就是在update方法里检测当前选中小球,如果存在就循环改变它的scale。

update (dt) {
    //选中的小球的动画    
    if (this.node1 && this.node1.ball) {
        let ball = this.node1.ball;
        let scale = ball.scale;
        let scaleDirection = ball.scaleDirection || -1;        
        if (scaleDirection === -1) {            
            scale -= 0.02;            
            if (scale <= 0.7) {
                ball.scaleDirection = 1;
            }
        } else {            
            scale += 0.02;            
            if (scale >= 1) {
                ball.scaleDirection = -1;
            }
        }
        ball.scale = scale;
    }
}

其它动画,比如小球增加时、消除时、提示文本的向上飞行效果等,都可以这样做。 

六、游戏声音

好玩的游戏是少不了音效的。我们来看看怎样给游戏添加背景音乐和音效。

我们需要一个可以全局调用的播放器,然后把需要播放的音频资源都绑定到这个播放器内,代码如下:

cc.Class({    
    extends: cc.Component,

    properties: {
        bgMusic: {
            url: cc.AudioClip,            
            default: null
        },
        select: {
            url: cc.AudioClip,          
            default: null
        },
        lose: {
            url: cc.AudioClip,            
            default: null
        },
        clear: {
            url: cc.AudioClip,            
            default: null
        },
        warm: {
            url: cc.AudioClip,            
            default: null
        },
        move: {
            url: cc.AudioClip,            
            default: null
        },
        gameOver: {
            url: cc.AudioClip,            
            default: null
        },
        mute: false
    },

    onLoad () {
        console.log('AudioClip onload');
    },

    playBgMusic: function () {        
        if (!this.mute && this.bgMusicChannel === undefined) {            
            this.bgMusicChannel = cc.audioEngine.play(this.bgMusic, true, 0.5);
        }
    },

    switchMute: function () {        
        this.mute = !this.mute;        
        if (this.mute) {            
            this.stopBgMusic();
        } else {            
            this.playBgMusic();
        }
    },

    stopBgMusic: function () {        
        if (this.bgMusicChannel !== undefined) {
            cc.audioEngine.stop(this.bgMusicChannel);            
            this.bgMusicChannel = undefined;
        }
    },

    playSound: function (sound, vol) {        
        if (!this.mute) {
            cc.audioEngine.play(this[sound], false, vol || 1);
        }
    }
});

然后让这个播放器可以全局调用。我的办法是在开始菜单场场景,创建一个空节点,并绑定播放器代码,然后注册到全局。 

在Menu的onload方法里面,把播放器注册到全局:

cc.game.addPersistRootNode(this.AudioClip);

然后在需要调用播放器的场景,获取播放器并调用其中的方法:

//获取全局播放器
this.AudioPlayer = cc.find('AudioClip').getComponent("AudioClip");
//停止再开启背景音乐
this.AudioPlayer.stopBgMusic();
this.AudioPlayer.playBgMusic();
//根据静音状态设置声音按钮按钮
if (this.AudioPlayer.mute) {    
    this.sound1Sprite.node.active = false;    
    this.sound2Sprite.node.active = true;
} else {    
    this.sound1Sprite.node.active = true;    
    this.sound2Sprite.node.active = false;
}

七、进一步完善

加入帮助界面和游戏设置界面。

界面内逻辑就不说了,经过上面的介绍,已经变得很简单。

但需要注意的是,这类界面有可能用户永远不会点开,我们就没必要一开始就创建或绑定了。这时可以在项目下创建一个名称为resources(文件夹名称必须为resources)的目录,把需要动态加载的预制资源放到这里。需要使用的时候,可以这样加载并创建:

this.helpSprite.node.on('touchend', function () {
    cc.loader.loadRes('Help', function (err, prefab) {        
        var help = cc.instantiate(prefab);
        help.x = help.y = 0;
        _this.container.addChild(help);        

        var hideHelp = function (e) {
            help.off('touchend', hideHelp);
            help.destroy();
            hideHelp = undefined;
            e.stopPropagation();
        }
        help.on('touchend', hideHelp);
    })
}.bind(this));

cc.loader.loadRes会查找resources目录下的预制资源并加载。

八、iphonex适配

iphonex和其他手机屏幕分辨率不一致,可能会导致界面和逻辑混乱。适配就是根据屏幕分辨率做一些界面或而已调整。

先完成适配的方法util,然后使用第五节中提到的办法把代码注册到全局可以调用,在每个场景加载的时候,去调整下尺寸就可以了。

比如在开始菜单中把GlobalData节点注册到全局,并注入resize方法:

cc.game.addPersistRootNode(this.GlobalData);
this.GlobalData.resize = function (container) {
    let originHeight = 1170;
    container.y = -(originHeight - cc.view.getVisibleSize().height) * 0.5;
}
this.GlobalData.resize(this.container);

 在其他场景调用这个全局方法:

//根据屏幕高度调整全局显示内容的位置
this.GlobalData = cc.find('GlobalData');
this.GlobalData.resize(this.container);
//根据屏幕高度调整游戏内容的位置
this.contentOffsetHeight = 0;
if (cc.view.getVisibleSize().height > 1150) {
   this.contentOffsetHeight = 105;
}
this.gameContent.y = this.gameContent.y - this.contentOffsetHeight;

九、在微信上运行。

首先安装好微信开发者工具,打开并登录好。

然后在cocos creator中找到偏好设置,设置好微信开发者工具的安装路径。

然后选择项目-构建发布,设置参数如下:

发布平台选择Wechat Game,然后点构建,构建完成后点运行,就会自动在微信开发者工具中打开并运行了。

如需要在手机微信上真机运行,只需要点击微信开发者工具中的预览按钮,代码上传完毕后就可以用自己的微信扫码体验了。只是自己体验的话是不需要专门注册小游戏账号的。

小游戏在没有发布之前,也只能自娱自乐了。实在想让别人体验一下,可以让他用微信登录一下开发者工具,再上传次一次代码,就可以用他的微信扫码体验了。

十、微信小游戏API调用

//振动15毫秒
window.wx && window.wx.vibrateShort();

附项目git地址:http://git.code.oa.com/jamesgan/WisdomBalls.git

--------------------------------------------------------------------------

原文作者:腾讯高级工程师甘远腾。

来源:腾讯内部KM论坛。

你也想成为腾讯工程师?

也想年终奖人手一部 Iphone X?

那就快加入NEXT学院吧!

NEXT学院课程「小游戏开发与实战」火热招生中!

  感兴趣的同学赶紧点击原文了解详情吧~

原文发布于微信公众号 - 腾讯NEXT学院(Next_Academy)

原文发表时间:2018-11-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的知识天地

tag标签是什么?对seo有什么用?

作为SEOer的我们在优化网站的时候回遇到几种标签,而tag标签就是其中一种,tag标签和一般的HTML标签不太一样,tag标签是一种特殊的标签,那么tag标签...

10410
来自专栏郭霖

Android通知栏微技巧,那些你所没关注过的小细节

本篇文章首发于我的微信公众号,其实通常情况下我都不会将微信文章再在博客上发表的,因为我认为两者区别比较大。微信文章偏向于短小精炼,毕竟要在手机上阅读,博客文章则...

35880
来自专栏MixLab科技+设计实验室

App之应用图标标记

以下为正文: ? 1、概念 应用图标标记,称为Badge App Icon。 Badge,徽章,具象来说, 就是佩带在身上用来表示身份、职业的标志。 它有着悠久...

69870
来自专栏听雨堂

Morris图表使用小记

挺好用的,碰到几个问题,有的是瞎试解决了的: 1、我想折线图能够响应单击事件,即点击某个节点后,就能加载进一步的信息,帮助没找到,参照另外一个地方的写法,居然支...

30580
来自专栏葡萄城控件技术团队

ActiveReports 9实战教程(3): 图文并茂的报表形式

基于上面2节内容,我们搭建了AR9的开发环境,配置好了数据源。在本节,我们以官方提供的3个中文图文并茂的报表来展示AR9的功能,并通过实战的方式一一分享。 以往...

21360
来自专栏腾讯IVWEB团队的专栏

APP-hybrid页面性能测试的一些知识记录

做hybrid页面,需要测试其性能。我们不能认为用浏览器打开该网页得到的数据就算它线上的性能,因为 webview 的环境,其性能和浏览器还是有所差距的。最近一...

40400
来自专栏魏艾斯博客www.vpsss.net

Picdiet 在线图片压缩 极速压缩 80%图片质量不变

51610
来自专栏腾讯移动品质中心TMQ的专栏

漫步VR——Unity语音聊天室开发

一、背景介绍 VR是什么 虚拟现实VirtualReality的英语缩写。VR 主要有手机盒子、头盔和一体机三种。 虚拟现实技术是一种可以创建和体验虚拟世界的计...

52980
来自专栏二次元

给你的博客加上个Live2D看板娘吧

本文章中所用模型解包自药水制作师手机游戏,版权归该官方所有。(没错,我也是来安利这款游戏的)

37400
来自专栏王肖的UT

使用FFmpeg处理音视频

本文主要是介绍如何使用ffmpeg命令行工具进行各式各样的音视频处理操作——缩放、裁剪、剪辑、旋转、格式转换,etc。。。学了本文,基本可以把格式工厂之类的音视...

1.8K80

扫码关注云+社区

领取腾讯云代金券