这个月要完成毕业设计,时间非常的紧迫,但是我打算写一篇博客记录下开发过程。总结会令写一篇博客。
毕设同时也是我的学习科技项目,所以难度还是有的,并且里面还有一些非技术类的问题,比如如何实现该游戏完成教育意义?
题目的意义简明的说,就是作为一款游戏,它具有寓教于乐的功能。可以帮助大学生以及初高中生更深入的了解编程。
大概就是这样的一款游戏:用户通过一系列指令,让一个物体移动起来,完成制定任务,获得成功。
由于我使用的是Mac,所以下面的部分内容可能不适用于Windows环境,如有错误请及时指正。
时间紧迫,所以文章中的知识点以及问题,大多数都是标题,没有补充进来。等时间轻松下来,毕设基本完成时我会做补充。
目录
2017.10.xx 耗时:4h
开发环境选择的是Cocos Creator,查了一下资料,在Unity和Cocos Creator之间选择了后者,Cocos Creator的可兼容性很强,适用于iOS、Android、Web,开发的难度也不是很高。至于两者之间具体的比较,请查看文章。
Cocos Creator下载:官网
Cocos Creator配置:安装及配置教程
同时我使用了VS作为开发工具,在安装配置教程里有相关说明。
2017.12.21 耗时:4h
熟悉界面,编写demo。
demo是官网教材中的。
注:demo的最后部分官网给出的代码有误,下载官网做好的demo替换错误代码即可。
2018.1.29 耗时:3h
看了一下用户手册,基本了解了一下JavaScript及CC.class:脚本开发指南
接下来的几天主要完成,越快越好:
PS:我觉得这个毕设,以我的能力非常难做完。
2018.1.30 耗时:4h
这就是今天的结果(我也不想做的这么丑)。
新建两个节点,用户控件(鸟头),以及按钮(left…right…)
//leftbutton.js
cc.Class({
extends: cc.Component,
properties: {
//用户节点,点击按钮时移动用户节点。
user:{
default: null,
type: cc.Node
}
},
onLoad: function () {
var usernoed = this.node;//获取按钮节点
var userb = this.user;//获取用户节点
//注册事件:点击按钮后移动用户节点
usernoed.on('touchstart',function(){
userb.runAction(cc.moveBy(0.1,-10,0));
});
},
});
将leftbutton.js拖拽至scene中的button 中。
将用户节点拖拽至user处。运行程序即可。
今天的代码和day05是一样的(昨天没想起来发),今天只需要看leftbutton.js,并保持scene中有一个节点和button即可。
具体代码:代码
注册了事件后,发生了一个错误,代码如下:
cc.Class({
extends: cc.Component,
properties: {
user:{
default: null,
type: cc.Node
}
},
// use this for initialization
onLoad: function () {
//有错误
this.node.on('mouseup',function(){
this.node.runAction(cc.p(50,50));
console.log("move!");
});
},
});
错误懒得贴了,大概意思是:找不到node节点。
作为一个没学过Js的人,我询问了一下大神,他给出的结论是:
this指的是当前的脚本,脚本继承的是组件(extends:cc.Component),所以this指的是当前组件。this.node则是这个脚本组件的节点。当在不同的环境下调用的时候(this.node)他的返回值是不一样的。
其实我没看懂。
关于this很基础,印象中老师在课上讲过this的用法,在不同的类中当然是不同的结果。但是我并不知道不同函数中,也会呈现不同的结果(function和onload中)。其实我还是不太明白两个不同的函数使用this为什么也会出现这种差异。
后来又出现了一个问题。
onLoad: function () {
this.node.on('touchstart',function(){
this.user.runAction(cc.moveBy(0.1,10,0));
});
改为下面的代码则正常运行:
onLoad: function () {
var usernoed = this.node;
var userb = this.user;
usernoed.on('touchstart',function(){
userb.runAction(cc.moveBy(0.1,10,0));
});
查看了一下开发手册,找到了一篇参考文章this 的值到底是什么?一次说清楚:
在Js中,函数调用只有一种形式:
func.call(context, p1, p2)
其他两种都是语法糖,可以等价地变为 call 形式:
func(p1, p2) 等价于func.call(undefined, p1, p2) obj.child.method(p1, p2)
等价于obj.child.method.call(obj.child, p1, p2)
所以,this其实就是指context。call 一个函数时,传入的 context。
另外:
如果你传的 context 就 null 或者 undefined,那么 window 对象就是默认的 context(严格模式下默认 context 是 undefined)
在网页中是window对象,在我出现的问题里,他是严格的 undefined。
而unload和function的context是不同的,所以this当然会不用,并且,在function中context为默认,所以就出现了undefined的错误。
这里还有一个问题,就是我并不知道context是什么,虽然有印象但是都忘光了。(基础有多差可见一斑),看了这篇文章,大体了解了什么是context,编程中什么是「Context(上下文)」?对于我刚刚出现的错误,在onload里就是该对象,而在监听事件里,context就是undefined(因为没有定义)。
关于this,以后会单独整理出来:待续
Day2中的三个目标目前完成:
还需要多学习,今天询问this的时候被无情的嘲讽了一番。
关于this,还有一篇进阶的文章:你怎么还没搞懂this
2018.1.31 耗时:4h
点击了down一会,将down提交至右边的richtext中。
今天主要是学习知识,require和数组之类的基础知识。实现了如图所示的功能,将操作保存至数组,点击commit就执行。
需要三个节点,用户节点,两个button,分别用来选择操作和提交用户选取的操作。
//op.js 存储用户选择的操作。
var cfg = new Array(3);
cfg[0] = 0;
cfg[1] = 0;
cfg[2] = 0;
module.exports = cfg;
//commit.js 提交按钮,读取op中的操作。
cc.Class({
extends: cc.Component,
properties: {
user:{
default: null,
type: cc.Node
},
},
onLoad () {
var commit = this.node;
var oprate = require("op");
var userb = this.user;
commit.on('touchstart',function(){
if(oprate[0]=="down")
{
userb.runAction(cc.moveBy(0.1,0,-10));
}
});
},
});
//downbutton.js
cc.Class({
extends: cc.Component,
properties: {
//用户节点
user:{
default: null,
type: cc.Node
},
//richtext 用来显示用户选取的操作
textboard:{
default: null,
type: cc.RichText
}
},
// use this for initialization
onLoad: function () {
var usernoed = this.node;
var userb = this.user;
var textboard = this.textboard;
var oprate = require("op");
usernoed.on('touchstart',function(){
userb.runAction(cc.moveBy(0.1,0,-10));
oprate[0] = "down";
textboard.string += "" + oprate[0]+"\n";
});
},
});
将commit.js downbutton.js分别添加至各节点的组件中。op用来保持数组,不用添加。
具体代码:这里写链接内容
为什么打不开?
Day2中的三个目标目前完成:
2018.2.1 耗时:3h
今天真是起起伏伏,在询问大神,发帖子等一系列救助措施无果后,绝望的我硬着头皮排查到了错误。真的是差点哭出来。本来已经困的已经睁不开眼,现在彻底精神了。
还是有些困,睡醒回来写
网友的弹窗插件,
2018.2.6 耗时:10h
失败1
这些天我的心路历程应该可以拍部电影了。把官方例子套进去一切正常,改为通过按键运动以后就无法实现障碍物的设计。折腾了三四天才弄好。其实是一件很简单的事。
关于碰撞体,我下载了官网的demo。测试全部正常,包含各种情况,有需求的同学可以去 GitHub官方Demo 下载。
碰撞体的设置与实现并不难,只需实现几个函数,困难的是如果你想实现触碰以后的操作,如障碍物、反弹等等效果需要自己写出来,并不能通过物理引擎来直接使用。
Day2中的三个目标已完成,接下来是:
这两天沉迷拍立得不能自拔,耽搁了两天,今天的日志更新其实是12号写的,也就是说我整整一个礼拜没有写了。
其实写到今天,并没有受到太多帮助。写整个程序,更多的是磨练了我的耐心。
重要的是学习能力以及耐心和虚心,编程的能力排在后面。
更重要的:有轮子就用,别自己写。
代码:点这里,以后只发下demo吧,只发代码好像没什么意义。
2018.2.14 耗时:6h
为什么还在写碰撞体,因为昨晚我发现了一个问题,这个问题是由于我并不熟悉物理机制已经js代码的编写造成的。其实是一个很简单的bug,最后再讲。先上图:
这个很重要,直接解决了图中的bug。
如图中所示,当蓝色的滑块位于红色障碍之间时,竟然穿过了障碍。原因其实很简单,先说一下原理。
当蓝块碰撞红块时,碰撞的方向的Button会对蓝块的speed赋值为0。这样就实现了碰撞的效果。但是当蓝块离开碰撞位置时,应该将speed赋值为正常的值。所以在离开碰撞区域时执行的函数 onCollisionExit(other)中添加恢复speed的函数。
问题就在这里,bug代码由于我的失误没有记录,大概就是:onCollisionExit(other)函数执行的时,我将四个方向(上下左右)的speed全部恢复了,但其实我只离开了一个方向的触碰区域(如失败图1)。
所以就是:蓝块同时碰撞了下方和右方的红块,当我离开下方触碰区域的时候,onCollisionExit(other)将右方的speed也恢复了,导致了“穿墙”。
解决方法也很简单,我忽略了传入的参数other,以及用other.name的方式在other对象中添加一个属性的方式。只要在发生触碰时的障碍中标记触碰的方向,根据标记的方向来对相应的方向恢复speed即可。
Day11的目标:
代码:这里写链接内容
2018.2.18 耗时:5h
每一天,每一天都是煎熬。
被各种琐事烦扰,奇怪的梦,莫名的白眼以及来自各处的嘲讽。
说正事,今天完成的是通过路径点的弹窗,问题是第二次加载界面后,弹窗无法初始化。以及一个简单的错误。
出现了以下的提示:
Can’t init canvas ‘Canvas’ because it conflicts with the existing
‘UI’,the scene should only have one active canvas at the same time
大概意思是Canvas重复了,只能有一个。Canvas的API
摘自官方API文档:
instance Canvas 当前激活的画布组件,场景同一时间只能有一个激活的画布。
这个问题算是结束了,以后可以随时补充。
继续上次论坛上写好的弹窗,第二次加载场景后无法在弹出窗口(失败1)。
var Alert = {
_alert: null, // prefab
_detailLabel: null, // 内容
_cancelButton: null, // 确定按钮
_enterButton: null, // 取消按钮
_enterCallBack: null, // 回调事件
_animSpeed: 0.3, // 动画速度
};
/**
* detailString : 内容 string 类型.
* enterCallBack: 确定点击事件回调 function 类型.
* neeCancel: 是否展示取消按钮 bool 类型 default YES.
* duration: 动画速度 default = 0.3.
*/
Alert.show = function (detailString, enterCallBack, needCancel, animSpeed) {
console.log("1.alert初始化");
// 引用
var self = this;
// // 判断
if (Alert._alert != undefined)
{
console.log("1.2alert无定义。");
return;
}
// console.log("2.判断");
//
Alert._animSpeed = animSpeed ? animSpeed : Alert._animSpeed;
// 加载 prefab 创建
cc.loader.loadRes("Alert", cc.Prefab, function (error, prefab) {
if (error) {
cc.error(error);
return;
}
// 实例
var alert = cc.instantiate(prefab);
// Alert 持有
Alert._alert = alert;
// 动画
var cbFadeOut = cc.callFunc(self.onFadeOutFinish, self);
var cbFadeIn = cc.callFunc(self.onFadeInFinish, self);
self.actionFadeIn = cc.sequence(cc.spawn(cc.fadeTo(Alert._animSpeed, 255), cc.scaleTo(Alert._animSpeed, 1.0)), cbFadeIn);
self.actionFadeOut = cc.sequence(cc.spawn(cc.fadeTo(Alert._animSpeed, 0), cc.scaleTo(Alert._animSpeed, 2.0)), cbFadeOut);
// 获取子节点
Alert._detailLabel = cc.find("alertBackground/detailLabel", alert).getComponent(cc.Label);
Alert._cancelButton = cc.find("alertBackground/cancelButton", alert);
Alert._enterButton = cc.find("alertBackground/enterButton", alert);
// 添加点击事件
Alert._enterButton.on('click', self.onButtonClicked, self);
Alert._cancelButton.on('click', self.onButtonClicked, self);
// 父视图
Alert._alert.parent = cc.find("Canvas");
// 展现 alert
self.startFadeIn();
// 参数
self.configAlert(detailString, enterCallBack, needCancel, animSpeed);
console.log("3.实例化");
});
// 参数
self.configAlert = function (detailString, enterCallBack, needCancel, animSpeed) {
// 回调
Alert._enterCallBack = enterCallBack;
// 内容
Alert._detailLabel.string = detailString;
// 是否需要取消按钮
if (needCancel || needCancel == undefined) { // 显示
Alert._cancelButton.active = true;
} else { // 隐藏
Alert._cancelButton.active = false;
Alert._enterButton.x = 0;
}
};
// 执行弹进动画
self.startFadeIn = function () {
console.log("4.弹进动画");
cc.eventManager.pauseTarget(Alert._alert, true);
Alert._alert.position = cc.p(0, 0);
Alert._alert.setScale(2);
Alert._alert.opacity = 0;
Alert._alert.runAction(self.actionFadeIn);
};
// 执行弹出动画
self.startFadeOut = function () {
console.log("5.˙执行弹出动画");
cc.eventManager.pauseTarget(Alert._alert, true);
Alert._alert.runAction(self.actionFadeOut);
};
// 弹进动画完成回调
self.onFadeInFinish = function () {
console.log("6.弹进动画完成回调");
cc.eventManager.resumeTarget(Alert._alert, true);
};
// 弹出动画完成回调
self.onFadeOutFinish = function () {
console.log("7.弹出动画完成回调");
self.onDestory();
};
// 按钮点击事件
self.onButtonClicked = function(event){
console.log("8.按钮点击事件");
if(event.target.name == "enterButton"){
if(self._enterCallBack){
self._enterCallBack();
}
}
self.startFadeOut();
};
// 销毁 alert (内存管理还没搞懂,暂且这样写吧~v~)
self.onDestory = function () {
console.log("9.销毁 alert");
Alert._alert.destroy();
Alert._enterCallBack = null;
Alert._alert = null;
Alert._detailLabel = null;
Alert._cancelButton = null;
Alert._enterButton = null;
Alert._animSpeed = 0.3;
};
};
咨询了一下群里的大神(我又加了新群),他告诉我将:
if (Alert._alert != undefined)
{
console.log("1.2alert无定义。");
return;
}
这段注释掉即可。
但是只知道怎么做不知道为什么肯定是不够的,这个问题的原因在于 self.onDestory 将 Alert._alert 赋值为了null。而这个判断发现prefab不等于undefined的时候就会return,导致弹窗无法弹出。
关于这个问题,我还是有些疑惑。以后补充。
问问
Day11的目标:
很绝望,看不到希望。
以后的资源全部发网盘,不需要积分下载。
2018.2.19 耗时:6h
今天相对前几天还是很顺利的,马上就要回北京,很舍不得离开家,哪怕我是蹲在沙发上写代码。
今天实现的是拖拽控件,扔到richbox中变成字符串。实现原理是点击按钮时触发touchmove事件,使节点跟随手指(鼠标)移动,如果移动至左边的区域就变成字符串或在其他区域松手,则调出destroy()删除节点。拖动的过程中,一旦离开原节点位置,便生出一个prefab在原位。这样就实现了入图成功中描述的样子。(图片快进了,有点看不出来,仔细看一下确实是这样的)。
失败1
成功
其实很简单,将锚点更改为1,1即可。
if (user.node.x != x && user.node.y != y)
{
var scene = cc.director.getScene();
console.log(scene);
var node = cc.instantiate(user.target);
node.parent = scene;
node.setPosition(x, y);
console.log("成功执行if,node:"+node.x);
}
如果这样加载,会显示prefab为空。
改为:
var scene = cc.find("Canvas/Commit");
就可以
不知道为什么,留个坑日后研究。
当前场景内的Prefab修改了js后依然执行的是未修改的,是因为没有同步的原因,手动或自动同步一下即可。
由于碰撞的模拟是按相对坐标算的,所以有不同尺寸会有问题。
但是将这两个勾选就可以。但是奇怪的是,第一次勾选这两个的时候,重新加载场景,会发生布局错位的问题。
onLoad() {
var user = this;
var x = user.node.x;
var y = user.node.y;
console.log(this.target);
//拖拽
user.node.on("touchmove", function (event) {
console.log("进入拖拽事件");
user.opacity = 255;
var delta = event.touch.getDelta();
user.node.x += delta.x;
user.node.y += delta.y;
if (user.node.x != x && user.node.y != y)
{
// var scene = cc.director.getScene();
var scene = cc.find("Canvas/Commit");
console.log(scene);
console.log("测试:"+cc.director.getScene());
var node = cc.instantiate(user.target);
node.parent = scene;
node.setPosition(x, y);
console.log("成功执行if,node:"+node.x);
}
});
//结束拖拽
user.node.on("touchend", function (event) {
if (user.node.x != x && user.node.y != y) {
user.node.destroy();
console.log("离开拖拽区域");
}
});
},
Day11的目标:
已经基本完成,下面几天完成将左侧的文本框中的数据转换为可执行的语句,类似于编译器。
马上要回北京愈发的烦躁,总有心事。但愿不打扰我完成这个程序。
2018.2.24 耗时:5h
现在最大的麻烦就是这个编译器。怎么做是个问题。
node.parent = scene;
node.setPosition(x, y);
将这两句调换位置即可,先设置节点位置,然后在寻找scene。因为prefab也有一个默认位置,是根据父坐标来定位的。生成时他会位于0,0点。所以先调节好他的位置,在显示出来。
过几天就要答辩,所以目标是:
因为要中期答辩,所以先只做一个关卡。
2018.2.25 耗时:6h
成功1
今天任务较少,补充动画。没有编写逻辑。并且把Day15的读取用户输入命令区域重写。
当生成一个Prefab的时候,无法动态修改string,控制台显示其实已经是修改完毕的。但是界面不显示。
prefab中的labe内容设置无效的BUG,16年有人提出过这个问题,但是我并不知道有没有修改。
错误的写法我没有记录,已改为:
var cannons = cc.Op.TouchNode.getChildByName("Text").getComponent(cc.Label);
这样可以获得Prefab的值。
cannons.string = self.node.string
此句无效,无法获得Label的值。(Self为label控件的this)
改为:
cannons.string = self.node.getComponent(cc.Label).string;
暂时不知道为什么
//待补充
下面几天完成主要的逻辑编写。
cc.OpPerNode = cc.Class({
properties: {
commad: "null",
untilltag: "null",
},
__ctor__: function (untilltag,commad) {
this.untilltag=untilltag;
this.commad = commad;
}
});
不能通过ctor传参数来实现构造函数,需使用_ ctor _。
//待补充
抽时间我会回来补充游戏的逻辑,我现在才明白,写代码之前先构思在画er图以及等等的计划有多重要,由于我基本处于看一步走一步的状态,重写了很多代码。目前的代码以及和前几天的完全不一样了,大部分函数基本重写。估计未来几天还会这样,所以我以后的任务将是注重思考如何将程序的耦合度降到最低以及如何贯彻面向对象的思想。
Day15的目标已完成一个,当前目标是:
起初想用递归来实现For的模拟,后来发现无法正确退出当前的函数切递归的空间复杂度较高,程序的负担较大,所以采用了更为简单的办法:
var timeCallback = function (dt) {
if (StartCount > EndCount) {
console.log("进入退出判断");
console.log("StartCount:" + StartCount + " end:" + EndCount);
this.unscheduleAllCallbacks();
return;
}
var key = cc.Op.OpObeject[StartCount].commad;
var UntillTag = cc.Op.OpObeject[StartCount].untilltag;
console.log("当前执行的命令:" + StartCount + cc.Op.OpObeject[StartCount].commad);
switch (key) {
case "MoveUp();": self.ActionMoveY("Up", UntillTag); break;
case "MoveDown();": self.ActionMoveY("Down", UntillTag); break;
case "MoveLeft();": self.ActionMoveX("Left", UntillTag); break;
case "MoveRight();": self.ActionMoveX("Right", UntillTag); break;
case "#Cycle":
if (cc.Op.OpObeject[StartCount].untilltag == cc.user.color[0]) {
StartCount = cc.Op.OpObeject[StartCount].end+1;
console.log("循环结束");
break;
}
else
{
StartCount = cc.Op.OpObeject[StartCount].start + 1;
break;
}
case "#End": StartCount = cc.Op.OpObeject[StartCount].end; break;
default: console.log("Do Noting!");
this.unscheduleAllCallbacks();
}
if (cc.Op.Opnext && key != "#End") {
StartCount++;
console.log("由谁请求:" + key);
console.log("进入下一语句判断StartCount的值:" + StartCount);
cc.Op.Opnext = false;
// OperateString[count] = null;
}
}
this.schedule(timeCallback, 1);
手动设置一个指针,如果不加限定条件,该回调函数一直在for中的语句无限次执行。
2018.3.7 耗时:11h
成功
今天都是一些修修补补,以及ui的问题。
非常重要,加载界面时出现了我已删除的场景。也就是该场景已无法找到,确又重新生成。清空缓存后该问题解决。
//待续
今天的任务不多,但是很繁琐也很麻烦。大多数都是自己编写的bug。找了很久。
Day15的目标已完成一个,接下来的目标是: