学完提问几个问题吧:

position的锚点位置数值原点在哪里?

因为position是相对坐标,所以原点是父节点的锚点 。所以Canvas下面的直属节点原点就是世界坐标系的原点Canvas的锚点。

如何设置默认浏览器?设置--预览运行--预览使用浏览器--选择浏览.exe--确认按钮。

1.创建渲染节点:

Sprite(精灵):创建节点-》创建渲染节点-》Sprite(精灵)。是新建图片节点,在这个节点引入图片,要设置SpriteFrame(图片帧)。

Label(文字):创建节点-》创建渲染节点-》Label(文字)。是新建文本节点,可以设置属性:String,Font,Font Size。(还可以在某个节点下方,选择添加组件-》渲染组件-》Label。)

2.创建UI节点:

Button(按钮):创建节点-》创建UI节点-》Button(按钮)。

EditBox(输入框):创建节点-》创建UI节点-》EditBox(输入框)。

节点可以单独存在,也可以在节点下方挂上多个组件,实现功能。但是一个节点只能选择一个渲染类的组件。

给节点添加音频资源:

API获取节点:

获取当前脚本绑定的节点: let node : cc.Node = this.node;

父节点:this.node.parent;

子节点: this.node.children : cc.Node[ ];

全局查找:target = cc.find("Canvas/佩奇/名字");

{

let node : cc.Node = cc.find("Canvas/佩奇/名字");//找到路径

node.setPosition(0,-200);//使这个节点向下移动200px。

}

查找子节点:target = cc.find("Canvas/佩奇/名字",someNode);

对于已经定义的节点node,可以获取他挂载的组件对象。

获取组件:let label : cc.Label = node.getComponet(cc.Label);

获取自定义类型的组件(脚本组件):let script = node.getComponent("YourScript");

键盘事件(全局事件):cc.systemEvent.on();

cc.systemEvent.on('keydown',this.onKeyDown,this);

onKeyDown( evt : cc.Event.EventKeyboard ){

键盘事件的按键值使用:假如使用的是右键

if(evt.keyCode == cc.macro.KEY.right){...}

}

使用x,y轴修改节点:this.node.x += 10;this.node.y += 10;

动态显示图片:

翻转:this.node.scaleX = 0 - this.node.scaleX;

changeFace(){cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));
//大前提:  初始图片脸朝右 且 this.node.scaleX >0if(this.faceLeft && (this.node.scaleX >0)||((!this.faceLeft) && (this.node.scaleX <0))){//向左走 现在图片是脸朝右的,需要图片脸朝左     //向右走  现在图片是脸朝左的,需要图片脸朝右this.node.scaleX = 0 - this.node.scaleX;}else{
//((this.faceLeft && (this.node.scaleX <0))||((!this.faceLeft) && (this.node.scaleX >0)))
//向左走 现在图片是脸朝左的,不需要改变图片脸朝向 //向右走  现在图片是脸朝右的,不需要改变图片脸朝向  this.node.scaleX = this.node.scaleX;}
}   

直接换图片:
                图片放在资源管理其中;

添加属性加载图片帧:

// 两种状态的图片帧
@property(cc.SpriteFrame)
face1: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
 face2: cc.SpriteFrame = null;

根据条件切换图片:

changeFace(){
  cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));
  // 获取 Sprite 组件
  let sprite : cc.Sprite = this.node.getComponent(cc.Sprite);
  // 修改 Sprite 组件的 Sprite Frame 属性
  //无论初始图片是脸朝左还是脸朝右,都没关系。
  if(this.faceLeft){
     sprite.spriteFrame = this.face1; //需要图片脸朝左         
  }else{
     sprite.spriteFrame = this.face2; //需要图片脸朝右
  }
}

脚本组件的调用:

找路径的时候:鼠标右键“显示节点UUID和路径。”

let node : cc.Node = cc.find('Canvas/佩奇_右');//找到脚本的节点路径

let script = node.getComponent('PigScript');//根据脚本的节点找到这个脚本。

坐标系:

二维和三维:

let pos = new cc.Vec2(100,100);   简写,省略new:let pos =  cc.Vec2(100,100);

let pos = new cc.Vec3(100,100,100);  简写,省略new:let pos =  cc.Vec3(100,100,100);

设置一个节点的坐标: node.setPosition( cc.v2(250,250) );//相对坐标,相对父节点

设置一个节点的缩放:node.setScale( cc.v3(1,1,0) );//2D游戏,所以z轴设置为0.

缓动系统:

      cc.tween(node).to(1,{position:cc.v3(250,120,0)}).start();

动画:

update(dt){  }帧动画的绘制:默认: 这个方法每秒钟会被调用60次。

// onLoad () {}  //初始化加载
    start () { }    //第一次启动的时候
            //update() 方法 就是 帧动画的绘制。
    update (dt) {//cc.log("update() is called , time = " + new Date().getTime());

//打印设置每一帧的刷新时间
        if(this.node.x>= 200){
            return;  //总距离移动200像素。 然后停止运动。
        }else{
            this.node.x += 5; //将节点移动5像素。
        }
    }

要调节这个次数,需要:把GameInitScript挂在Canvas中。因为游戏是从根节点Canvas开始运行的,所以先加载Canvas下面的组件,故我们可以把所有的全局设置放在GameInitScript中,从根节点加载就开始运行组件GameInitScript。

onLoad () {
        cc.log('Pig Script : onload()');
        cc.game.setFrameRate(30);  //设置帧率为30帧/秒
    }
    start () {}
   // update (dt) {}

11.3根据键盘控制上下左右运动

11.4 计时器:间隔多少时间,回调什么函数方法,(可省略:重复几次,延迟几秒;省略时,默认一直重复,不延迟。)。

手柄拖动,

世界坐标坐标转换为父节点的本地坐标:

//需要把世界坐标转换为本地坐标
        let parent : cc.Node = this.node.parent;//父节点(圆形底盘)
            //e.getLocation() 获得触摸点的世界坐标(触点位置)
            //把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标
        let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());
            //根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标
        this.node.setPosition(pos);

const {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {// onLoad () {}start () {this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchCancel,this);this.node.on('touchcancel',this.onTouchCancel,this);}onTouchStart( e : cc.Event.EventTouch ){}//暂时没有用到,置空//计算触摸点的当前位置,把手柄的位置移动到触摸点所在的位置onTouchMove( e : cc.Event.EventTouch ){//e.getLocation()为触摸点的位置,是世界坐标//需要把世界坐标转换为本地坐标cc.log("世界坐标touch move : x=" + e.getLocationX() + ",y=" +e.getLocationY());let parent : cc.Node = this.node.parent;//父节点(圆形底盘)//e.getLocation() 获得触摸点的世界坐标(触点位置)//把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());//根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标this.node.setPosition(pos);cc.log("转换成本地坐标convert to local: x=" +  pos.x + ",y=" + pos.y); }onTouchCancel( e : cc.Event.EventTouch ){//触摸松开的时候,把手柄的位置移动到中央this.node.setPosition( cc.v3(0,0,0));}// update (dt) {}
}

方位角:let direction : cc.Vec2 = pos.normalize(); 最后得到direction =(cos值,sin值)。

角度表示: let angle:number =45;

计算两点之间的实际距离:此时两点都在同一个坐标系中。

let d: number = cc.Vec2.distance( pos,cc.v2(0,0) );
求a,b两个向量的夹角:radian = a.angle(b) ;

a位于b的顺时针方向:角度为正; a位于b的逆时针方向:角度为负。
弧度值:let radian = pos.signAngle(cc.v2(1,0)); 其中 cc.v2(1,0) 表示x轴方向的单位向量
把弧度制转换成角度值:let angle = radian / Math.PI * 180; 
        this.car.angle = -angle; // 按API要求,angle是按逆时针为正的
       // this.car.rotation = angle; //这样写也行,但是不如 this.car.angle = -angle;

显示Gif图参见13章。

一次加载多个资源参见14章。

触摸事件的事件冒泡机制:

加上 e.stopPropagation();(停止传递当前事件。)就不会上浮到父节点“道路”。而是只打印子节点汽车。

遮罩效果:

加上一个单色节点,然后调整节点的active激活或者非激活状态。

onTouch( e : cc.Event.EventTouch){

//设置节点状态处于非激活的状态。

this.node.active = false;

// e.stopPropagation();

}

使用Widget组件进行优化:

使得遮罩自适应屏幕,且和父节点(Canvas)同样大小遮满屏幕。把上下左右边距全部调成0px。

  1. 声明:全文 根据 B站博主阿发你好 的视频教程学习并记录。笔记持续学习更新中......

  2. B站博主阿发你好  的网址:Cocos Creator游戏开发教程_60集教学视频_哔哩哔哩_bilibili  https://www.bilibili.com/video/BV1sA411Y7x4?p=1

  3. 课程内容:学完之后制作2D小游戏。制作不需要安装打开就可以玩的小游戏。

    1. 地道的中文,便于阅读、学习。

    2. 官网的开发文档:Introduction · Cocos Creator  https://docs.cocos.com/creator/manual/zh/

    打开之后就是这个:

    1. 要会程序设计:Swing入门和Swing高级。这个要自学。

    2. (发现下载需要付费10元。)

    3. 资料下载:

    4. Cocos Creator 开发环境安装_时时师师的博客-CSDN博客

    3.1编辑器界面

    场景Scene

    资源 Asset(Texture 图片素材;audioClip 音频素材;)

    4.1场景编辑器

    4.2添加节点 

    把图片拖拽进入Canvas下面。

    Main Scene场景文件中使用json代码保存了图片的路径和名称。

    4.3节点的操作

     疑惑:缩放和矩形变换有什么区别?

    在属性检查器中设置才是一个精确的数值。

    4.4位置与锚点

    如下图,图片的坐标轴原点就是锚点(Anchor)。

    相对坐标

    父节点(Canvas)、子节点(佩奇、Main Camera)。

    佩奇的坐标是相对于世界坐标系的原点Canvas的(480,320)这个点来定坐标的。佩奇的position和Canvas这个点重合时,position=(0,0)。

    4.5 游戏的运行

    两种方式任选其一。

    设置默认浏览器:

    浏览器运行之后这里也可以选择设置中已有的功能。

    5.1添加图片节点

    在右侧添加组件,这样就可以看到Sprite。

    操作步骤:

    拖拽:鼠标移动到节点上,然后拖动到Sprite Frame就可以了,不要单击。

    5.2 文本节点

    右侧也可以选择添加组件来调整文本节点的细节:字体、颜色、大小、等等。

    5.3添加UI节点

    鼠标移到文字上:能显示这个属性的具体描述

    创建一个输入框:

    一些输入框的名字。

    5.5节点与组件

    节点Node的功能是由它下面的XX组件(Sprite图片、Label文本等)来决定的。

    在右侧的“添加组件”中选择。  渲染类型组件只能选择一个。

    5.5父子节点

    6.1 VSCode环境

    6.2创建游戏脚本

    TypeScript其实就是JavaScript的加强版。

    解释: JavaScript是无类型的,所以在编写的时候没有工具提示,比较麻烦。所以就发明了强类型的TypeScript,具有工具提示。

    按照默认的VS Code 安装步骤,安装之后,在cocos中配置VS Code,浏览,然后搜索 VS Code,找到Code.exe。点击保存。

    新建游戏脚本,就会自动打开VS Code。

    假如输入状态紊乱,那么可以切换一下中文,或者切换到其他窗口,再切回来。

    步骤演示:

    创建文件夹,然后点击选中图片佩奇,在右下角添加组件——用户脚本组件——Pigscript。

    选中之后会出现蓝色的边框。

    在cocos中不能直接编辑脚本,需要双击这个脚本,就会自动打开VS Code脚本窗口编辑。

    在VS Code中编辑完成之后,Ctrl+S保存,cocos会自动刷新右侧的代码内容。

    6.3 脚本的运行

    双击打开脚本之后先关闭脚本窗口(因为这样打开的窗口,直接进行编辑有一个BUG,所以需要先把这个脚本关闭),然后在右侧重新找到代码打开,再进行编辑。

    选择模拟器,还有下方的Resume script execution按钮(继续脚本执行),才能出现打印的日志。

    伪代码理解:

    生命周期回调 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html

    6.4 事件响应处理

    方法名不加()。并不是要调用它。

    6.5脚本的调试

    7.1 TypeScript 具体用法

    官网地址: TypeScript: JavaScript With Syntax For Types.  https://www.typescriptlang.org/zh/

    这样如下图,就有提示了:

    7.2 属性的定义

    加了@property之后,用户脚本组件的PigeScript中就会自动加上这个time属性。开发者就可以从这个属性面板中进行初始化设置。但是这里的修改不会影响代码,只会影响编译结果。

7.3基本类型的属性


const {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property(cc.Label)label: cc.Label = null;// 每一步的大小,单位为像素。@propertystep : number = 20 ;//行走方向@propertytowardsLeft : boolean = true;// LIFE-CYCLE CALLBACKS://调用move方法onLoad () {this.node.on("mousedown",this.move,this);}start () {cc.log("组件PigeScript开始运行!"+1123);}// 在TypeScript里,方法的末尾不需要加分号或者逗号move(evt : cc.Event.EventMouse){if(this.towardsLeft){this.node.x -= this.step;//向左走}else{this.node.x += this.step;//向右走}}// update (dt) {}
}

选择勾选(向左)或者不勾选(向右)属性“Towards Left”来控制方向。

新建的节点也可以通过添加组件使用这个PigeScript脚本的代码。

7.4引用类型的使用

全部代码:


const {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property(cc.Label)label: cc.Label = null;// 每一步的大小,单位为像素。@propertystep : number = 20 ;//行走方向@propertytowardsLeft : boolean = true;//脚步声(cc.AudioClip 表示一个音频资源,例如一个mp3)@property(cc.AudioClip)audio : cc.AudioClip = null;// LIFE-CYCLE CALLBACKS:onLoad () {this.node.on("mousedown",this.move,this);}start () {cc.log("组件PigeScript开始运行!"+1123);}// 在TypeScript里,方法的末尾不需要加分号或者逗号move(evt : cc.Event.EventMouse){if(this.towardsLeft){this.node.x -= this.step;//向左走}else{this.node.x += this.step;//向右走}//播放脚步声音频if(this.audio != null){cc.audioEngine.play(this.audio,false,1);}}// update (dt) {}
}

添加脚步声的关键代码:

  新建文件夹audio,里面加入音频,然后把这个音频拖拽到 Audio 这个属性框中进行添加。

8.1API获取节点

操作结果:点一下测试按钮,就能把名字放到佩奇的脚下。(点击测试按钮之前,名字在佩奇的头上。)

创建新脚本ButtonScript,添加给测试按钮。

代码:注意路径名称必须要区分大小写。

const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {onLoad () {//鼠标点击事件 onClicked()this.node.on("mousedown",this.onClicked,this);}start () {}onClicked(){let node : cc.Node = cc.find("Canvas/佩奇/名字");//找到路径node.setPosition(0,-200);//使这个节点向下移动200px。}// update (dt) {}
}

查看文档,寻找API。

8.2API获取组件

const {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {// LIFE-CYCLE CALLBACKS:onLoad () {this.node.on("mousedown",this.onClicked,this);}start () {}onClicked(){let targetNode : cc.Node = cc.find("Canvas/佩奇/名字");//根据路径找到节点 名字let label : cc.Label = targetNode.getComponent(cc.Label);//获取节点的组件Lablelabel.string = "小猪佩奇";//给属性string赋值}// update (dt) {}
}

8.3脚本组件的访问

为节点“名字”添加组件SimpleScript。

在按钮的组件ButtoScript中找到节点“名字”并调用它的组件SimpleScript中的方法doChange()。

方法doChange():每次,点击测试按钮,都让节点“名字”的y位置的值为相反值。

P31 9.0小游戏:行走的佩奇

B站的视频里没有详细介绍怎么制作,发现需要购买B站上阿发博主的学习资料才能看到详细教程。下面是详细制作教程的学习笔记。

9.1 实例:行走的佩奇

9.2场景布局

步骤如下:

效果如图:

制作图片按钮:

Label代表的就是按钮的文字button,可以删掉button换成图片。

直接把图片拖动到Background下面就好了,右边就会直接显示这个图片。还可以修改按钮在不同鼠标状态下的的背景颜色。

右按钮可以直接点击复制左按钮,然后修改名称并替换图片获得。然后移动位置即可。

(如果想要调节两个按钮之间的间距,需要先调节间距,再调节水平对齐的状态。)

  1. 按下Ctrl键然后选中两个按钮,
  2. 使用场景编辑器中的工具“垂直居中对齐”来使两个按钮位于同一条水平线上。
  3. 然后点击移动点拖动按钮整体移动位置。

或者把背景图片勾掉禁用,暂时不显示,然后利用网格来实现对齐。

9.3键盘事件

点击佩奇这个组件,然后把这个脚本直接拖动到添加组件按钮里面,就可以直接添加脚本。

Ctri+S保存脚本,然后看看这个脚本。

Left和Right就是左右箭头。

运行浏览器,看看这个脚本:需要先点击一下我们的游戏页面,让游戏捕捉到鼠标,然后点击键盘上的左右键才会出现打印日志。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property(cc.Label)label: cc.Label = null;@propertytext: string = 'hello';// LIFE-CYCLE CALLBACKS:onLoad () {cc.systemEvent.on('keydown',this.onKeyDown,this);//在onload中添加键盘监听事件onKeyDown}start () {}onKeyDown(evt : cc.Event.EventKeyboard){//参数evt类型是cc.事件和键盘事件。if(evt.keyCode == cc.macro.KEY.left){cc.log("Pig:向左一步");}else if(evt.keyCode == cc.macro.KEY.right){cc.log("Pig:向右一步");}}// update (dt) {}
}

9.4角色的移动

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {// @property(cc.Label)// label: cc.Label = null;// @property// text: string = 'hello';//当前脸的朝向 : true,脸朝左;false,脸朝右。faceLeft : boolean = true;// LIFE-CYCLE CALLBACKS:onLoad () {cc.systemEvent.on('keydown',this.onKeyDown,this);//在onload中添加键盘监听事件onKeyDown}start () {}onKeyDown(evt : cc.Event.EventKeyboard){if(evt.keyCode == cc.macro.KEY.left){cc.log("Pig:向左一步");this.moveLeft();}else if(evt.keyCode == cc.macro.KEY.right){cc.log("Pig:向右一步");this.moveRight();}}// update (dt) {}moveLeft(){if( ! this.faceLeft){this.faceLeft = true;this.changeFace();//改变脸的朝向}this.move();//移动一步}moveRight(){if( this.faceLeft){this.faceLeft = false;this.changeFace();//改变脸的朝向}this.move();//移动一步}move(){if(this.faceLeft){this.node.x -= 10;//向左移动}else{this.node.x += 10;//向右移动}}changeFace(){ //改变脸的朝向cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));}
}

 

9.5动态显示图片 

图文教程如下:

第一种方式:直接翻转(需要根据初始图片脸的朝向  分析代码)

this.node.scaleX = 0 - this.node.scaleX;

比如,原来节点的 scale 为 (0.7 , 0.7 ) ,翻转后为 (-0.7,  0.7)。

但这并不是一个好办法,因为直正的人可能比较复杂,翻转之后可能穿帮。

changeFace(){cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));
//大前提:  初始图片脸朝右 且 this.node.scaleX >0if(this.faceLeft && (this.node.scaleX >0)||((!this.faceLeft) && (this.node.scaleX <0))){//向左走 现在图片是脸朝右的,需要图片脸朝左     //向右走  现在图片是脸朝左的,需要图片脸朝右this.node.scaleX = 0 - this.node.scaleX;}else{
//((this.faceLeft && (this.node.scaleX <0))||((!this.faceLeft) && (this.node.scaleX >0)))
//向左走 现在图片是脸朝左的,不需要改变图片脸朝向 //向右走  现在图片是脸朝右的,不需要改变图片脸朝向  this.node.scaleX = this.node.scaleX;}
}   

第二种方式:(不需要判断初始图片脸的朝向。直接换图片,然后移动位置就可以了。)

1 准备素材

朝左、朝右两种状态的图片,放在资源管理器里。

2 添加属性

// 两种状态的图片帧
@property(cc.SpriteFrame)
face1: cc.SpriteFrame = null;
@property(cc.SpriteFrame)
face2: cc.SpriteFrame = null;

3 在Cocos Creator里,给face1 face2指定资源图片

4 动态切换图片

 changeFace(){cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));// 获取 Sprite 组件let sprite : cc.Sprite = this.node.getComponent(cc.Sprite);// 修改 Sprite 组件的 Sprite Frame 属性//无论初始图片是脸朝左还是脸朝右,都没关系。if(this.faceLeft){sprite.spriteFrame = this.face1; //需要图片脸朝左         }else{sprite.spriteFrame = this.face2; //需要图片脸朝右}
}   

9.6脚本的调用

实现左右移动的按钮:

想办法在按钮ButtonScript.ts中调用PigScript.ts中的moveLeft()和moveRight()方法就可以了。找路径的时候可以使用显示节点的UUID和路径。

然后添加进按钮组件中。

注意右按钮的toLeft属性要去掉。

@property
toLeft : boolean = true;
// LIFE-CYCLE CALLBACKS:
onLoad () {this.node.on('mousedown',this.onClicked,this);
}
start () {}
onClicked(){let node : cc.Node = cc.find('Canvas/佩奇_右');let script = node.getComponent('PigScript');if(this.toLeft){script.moveLeft();}else{script.moveRight();}
}

至此按钮功能实现了。

接下来需要为行走添加音效:

参照:

先在PigScript.ts中添加

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;
//PigScript.ts
@ccclass
export default class NewClass extends cc.Component {// @property(cc.Label)// label: cc.Label = null;// @property// text: string = 'hello';//cc.AudioClip 表示一个音频资源,例如一个MP3@property(cc.AudioClip)audio : cc.AudioClip = null;//当前脸的朝向 : true,脸朝左;false,脸朝右。faceLeft : boolean = true;//两种状态的图片帧@property(cc.SpriteFrame)face1 : cc.SpriteFrame = null;@property(cc.SpriteFrame)face2 : cc.SpriteFrame = null;// LIFE-CYCLE CALLBACKS:onLoad () {cc.systemEvent.on('keydown',this.onKeyDown,this);//在onload中添加键盘监听事件onKeyDown}start () {}onKeyDown(evt : cc.Event.EventKeyboard){if(evt.keyCode == cc.macro.KEY.left){cc.log("Pig:向左一步");this.moveLeft();}else if(evt.keyCode == cc.macro.KEY.right){cc.log("Pig:向右一步");this.moveRight();}//播放脚步声音频if(this.audio != null){cc.audioEngine.play(this.audio,false,1);}}// update (dt) {}moveLeft(){if( ! this.faceLeft){this.faceLeft = true;//向左走this.changeFace();//改变脸的朝向}this.move();//移动一步}moveRight(){if(this.faceLeft){this.faceLeft = false;//向右走this.changeFace();//改变脸的朝向}this.move();//移动一步}//改变脸的朝向move(){if(this.faceLeft){this.node.x -= 10;//向左移动}else{this.node.x += 10;//向右移动}}changeFace(){cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));// 获取 Sprite 组件
// TODO: 为了优化效率,可以在onLoad()里就把这个 cc.Sprite引用给准备好let sprite : cc.Sprite = this.node.getComponent(cc.Sprite);// 修改 Sprite 组件的 Sprite Frame 属性//无论初始图片是脸朝左还是脸朝右,都没关系。if(this.faceLeft){//向左走sprite.spriteFrame = this.face1; //需要图片脸朝左        }else{//向右走sprite.spriteFrame = this.face2; //需要图片脸朝右}}   // changeFace(){//     cc.log("改变行走方向,脸朝" + (this.faceLeft ? '左' : '右'));//大前提:   初始图片脸朝右 且 this.node.scaleX >0//     if(this.faceLeft && (this.node.scaleX >0) ||( (!this.faceLeft) && (this.node.scaleX <0) ))//     { //向左走 现在图片是脸朝右的,需要图片脸朝左     //向右走  现在图片是脸朝左的,需要图片脸朝右//         this.node.scaleX = 0 - this.node.scaleX;//     }else{  ((this.faceLeft && (this.node.scaleX <0) ) || ((!this.faceLeft) && (this.node.scaleX >0)) )//             //向左走 现在图片是脸朝左的,不需要改变图片脸朝向     //向右走  现在图片是脸朝右的,不需要改变图片脸朝向  //         this.node.scaleX = this.node.scaleX;//     }// }
}

然后在ButtonScript.ts中添加音频,两个按钮都要添加:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;
//ButtonScript.ts
@ccclass
export default class NewClass extends cc.Component {// @property(cc.Label)// label: cc.Label = null;// @property// text: string = 'hello';//cc.AudioClip 表示一个音频资源,例如一个MP3@property(cc.AudioClip)audio : cc.AudioClip = null;@propertytoLeft : boolean = true;// LIFE-CYCLE CALLBACKS:onLoad () {this.node.on('mousedown',this.onClicked,this);}start () {}onClicked(){let node : cc.Node = cc.find('Canvas/佩奇_右');let script = node.getComponent('PigScript');if(this.toLeft){script.moveLeft();}else{script.moveRight();}//播放脚步声音频if(this.audio != null){//audio engine音频引擎cc.audioEngine.play(this.audio,false,1);}}// update (dt) {}
}

//播放音频
var id = cc.audioEngine.play(path, loop, volume );
//参数path代表音频路径,loop代表是否循环, volume代表音量范围0~1.0//设置音频是否循环
cc.audioEngine.setLoop(id, loop);   //id代表由play获得的id,loop代表是否循环//获取音频的循环状态
cc.audioEngine.isLoop(id);   //id代表由play获得的id//设置音量(0.0 ~ 1.0)
cc.audioEngine.setVolume(id, volume);   //id代表由play获得的id, volume代表音量范围0~1.0//获取音量(0.0 ~ 1.0)
var volume = cc.audioEngine.getVolume(id);    //id代表由play获得的id//设置当前的音频时间
cc.audioEngine.setCurrentTime(id, time);    //id代表由play获得的id,time代表播放的当前位置
(单位为秒)//获取当前的音频播放时间
var time = cc.audioEngine.getCurrentTime(id);   //id代表由play获得的id//获取音频总时长
var time = cc.audioEngine.getDuration(id);    //id代表由play获得的id//获取音频状态
var state = cc.audioEngine.getState(id);      //id代表由play获得的id//设置一个音频结束后的回调
cc.audioEngine.setFinishCallback(id, function () {});//id代表由play获得的id,第二个参数是自己的回调哦//暂停正在播放音频
cc.audioEngine.pause(id);      //id代表由play获得的id//暂停现在正在播放的所有音频
cc.audioEngine.pauseAll();//恢复播放指定的音频
cc.audioEngine.resume(id);     //id代表由play获得的id

10.1坐标系

二维坐标:

三维坐标:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property(cc.Label)label: cc.Label = null;@propertytext: string = 'hello';// LIFE-CYCLE CALLBACKS:onLoad () {this.node.on('mousedown',this.OnClicked,this);}start () {}OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//二维坐标两种写法://比较长:let pos =  new  cc.Vec2(100,100);//cocos还提供了一个方法,更简略的写法://更常用let pos2 =  cc.v2(100,100);//三维坐标let pos3 = new cc.Vec3(1000,100,100);let pos4 = cc.v3(1000,100,100);}// update (dt) {}
}

  OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');let pos : cc.Vec2 = node.getPosition();cc.log(pos);}

发现返回的是一个三维向量,所以,以后写的时候可以直接把z写为0。

let pos1 = cc.v3(100,100,0);
node.setPosition(cc.v3(250,-120,0);//设置一个节点的坐标

例如设置佩奇的节点坐标:使它发生平移。

OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');let pos : cc.Vec2 = node.getPosition();//平移佩奇node.setPosition(cc.v3(155.907,-29.374,0));
}

设置一个节点的缩放

node.setScale(cc.v3(1,1,0));

例如:

OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');let pos : cc.Vec2 = node.getPosition();//平移佩奇node.setPosition(cc.v3(155.907,-29.374,0));//设置大小缩放node.setScale(cc.v3(2,2,0));
}

10.2缓动系统

 OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//平移佩奇node.setPosition(cc.v3(250,-120,0));
}

缓动:

position:

 OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//平移佩奇//node.setPosition(cc.v3(250,-120,0));//缓动 用1s的时间实现从(-250,-120)移动到(250,-120)cc.tween(node).to(1,{position:cc.v3(250,-120,0)}).start();
}

图文教程:

链式调用,是 Java / C# 中的常用形式

cc.tween(node)

.to(1, { position : cc.v3(250, -120, 0) } )

.start();

相当于:

// 创建一个 cc.Tween 类型的对象

let tween = cc.tween ( node );

// tween.to()的返回值就是tween对象自身

tween = tween. to(1, { position : cc.v3(250, -120, 0) } ) ;

// 开始动作

tween.start();

OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//平移佩奇//node.setPosition(cc.v3(250,-120,0));//缓动 用5s的时间实现从(-250,-120)移动到(250,-120)// cc.tween(node).to(5,{position:cc.v3(250,-120,0)}).start();  //先用3s缓慢移动,然后再 用2s旋转360度。cc.tween(node).to(3,{position:cc.v3(250,-120,0)}).to(2,{rotation:360}).start();//另一种写法:一边旋转一边移动// cc.tween(node).to(1,{position:cc.v3(250,-120,0),rotation:360}).start();}

10.3 cc.tween的用法

文档--》 功能模块--》缓动系统。 缓动系统 · Cocos Creator

佩奇的运动轨迹是画了个正方形。

 OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//实现从(-250,-120,0)cc.tween(node).by(1,{position:cc.v3(400,0,0)})//到(150,-120,0).by(1,{position:cc.v3(0,400,0)})//到(150,280,0).by(1,{position:cc.v3(-400,0,0)})//到(-250,280,0).by(1,{position:cc.v3(0,-400,0)})//到(-250,-120,0).start();cc.tween(node).to(1,{position:cc.v3(150,-120,0)})//到(150,-120,0).to(1,{position:cc.v3(150,280,0)})//到(150,280,0).to(1,{position:cc.v3(-250,280,0)})//到(-250,280,0).to(1,{position:cc.v3(-250,-120,0)})//到(-250,-120,0).start();}

时间duration,目标参数props,速度easing。

OnClicked(){let node : cc.Node= cc.find('Canvas/佩奇_右');//easingcc.tween(node).by(5,{position:cc.v3(0,400,0)},{easing:'quadOut'}).by(5,{position:cc.v3(800,0,0)},{easing:'quadOut'}).by(5,{position:cc.v3(0,-400,0)},{easing:'quadOut'}).by(5,{position:cc.v3(-800,0,0)},{easing:'quadOut'}).start();}

类型别名: TweenEasing

导入示例:

import { TweenEasing } from "cc";

TweenEasing

内置缓动函数的字符串值定义。

public TweenEasing : "linear" | "smooth" | "fade" | "constant" | "quadIn" | "quadOut" | "quadInOut" | "quadOutIn" | "cubicIn" | "cubicOut" | "cubicInOut" | "cubicOutIn" | "quartIn" | "quartOut" | "quartInOut" | "quartOutIn" | "quintIn" | "quintOut" | "quintInOut" | "quintOutIn" | "sineIn" | "sineOut" | "sineInOut" | "sineOutIn" | "expoIn" | "expoOut" | "expoInOut" | "expoOutIn" | "circIn" | "circOut" | "circInOut" | "circOutIn" | "elasticIn" | "elasticOut" | "elasticInOut" | "elasticOutIn" | "backIn" | "backOut" | "backInOut" | "backOutIn" | "bounceIn" | "bounceOut" | "bounceInOut" | "bounceOutIn"

内置缓动函数的字符串值定义。

Defined in cocos/tween/export-api.ts:33

找不到这个文档。 可以在csdn上直接搜索“tween 缓动 ”。

(14条消息) tween的缓动效果大全和使用方法_飞浪纪元[FWC–FE]的博客-CSDN博客_tween 缓动  https://blog.csdn.net/weixin_38531633/article/details/115480255

10.4拍球

onLoad () {this.node.on('touchstart',this.onClicked,this);}
start () {}
onClicked(){let h : number = 400;cc.tween(this.node).by(0.5,{position : cc.v3(0,-h,0)},{easing:"quardIn"})  //加速,下降.by(0.2,{position : cc.v3(0,h/4,0)},{easing:"quardOut"}) //反弹,减速,上升.by(0.2,{position : cc.v3(0,-h/4,0)},{easing:"quardIn"}) //加速,再下降.start();}

 

得到一个白色方块,修改名称为 地面。

修改锚点anchor的y值为0,让它的中心点在图像篮球的下边缘。

地面的锚点anchor的y值为1,让它的中心点在图像地面的上边缘。计算Positon的y=180-400=-220.

运行:篮球掉落到地平线上。

11.1动画

创建这个文件夹列表,然后把PigScript组件添加给佩奇。

update()方法 默认 每秒钟会被这个动画调用60次。可以使用日志看一眼:

update (dt) {cc.log("update() is called , time = " + new Date().getTime());
}

// onLoad () {}  //初始化加载start () { }    //第一次启动的时候//update() 方法 就是 帧动画的绘制。update (dt) {//cc.log("update() is called , time = " + new Date().getTime());//打印设置每一帧的刷新时间if(this.node.x>= 200){return;  //总距离移动200像素。 然后停止运动。}else{this.node.x += 5; //将节点移动5像素。}}

11.2帧率

 update (dt) {//cc.log("update() is called , time = " + new Date().getTime());//打印设置每一帧的刷新时间cc.log('delta time=' + dt);   //打印设置每一帧的时间间隔if(this.node.x>= 200){return;  //总距离移动200像素。 然后停止运动。}else{this.node.x += 5; //将节点移动5像素。}}

可以看到0.016s。

把GameInitScript挂在Canvas中。因为游戏是从根节点Canvas开始运行的,所以先加载Canvas下面的组件,故我们可以把所有的全局设置放在GameInitScript中,从根节点加载就开始运行组件GameInitScript。

GameInitScript.ts 中:

onLoad () {cc.log('Pig Script : onload()');cc.game.setFrameRate(30);  //设置帧率为30帧/秒}start () {}// update (dt) {}

FPS大概是30帧/秒。

11.3状态控制

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;//PigScript.ts的代码
@ccclass
export default class NewClass extends cc.Component {//速度(每次移动多少像素)speed : number = 3;//方向//例如,水平向右(1,0) 数直向下(0,-1)direction : cc.Vec2 = null;// LIFE-CYCLE CALLBACKS://初始化加载onLoad () {cc.log('Pig Script : onload()');cc.systemEvent.on('keydown',this.onKeyPress,this);} //第一次启动的时候start () {}  //onKeyPress()onKeyPress(e : cc.Event.EventKeyboard){if(e.keyCode == cc.macro.KEY.left){this.direction = cc.v2(-1,0);}else if(e.keyCode == cc.macro.KEY.right){this.direction = cc.v2(1,0);}else if(e.keyCode == cc.macro.KEY.up){this.direction = cc.v2(0,1);}else if(e.keyCode == cc.macro.KEY.down){this.direction = cc.v2(0,-1);}else if(e.keyCode == cc.macro.KEY.space){this.direction = null;}} //update() 方法 就是 帧动画的绘制。update (dt) {if(this.direction == null) return; //原地不动let pos:cc.Vec2 = this.node.getPosition();pos.x += this.speed * this.direction.x;pos.y += this.speed * this.direction.y;this.node.setPosition(pos);       }
}

11.4计时器

使用计时器 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/scripting/scheduler.html

下面是 Component 中所有关于计时器的函数:

  • schedule:开始一个计时器
  • scheduleOnce:开始一个只执行一次的计时器
  • unschedule:取消一个计时器
  • unscheduleAllCallbacks:取消这个组件的所有计时器

计时器的应用:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {label : cc.Label = null ;text : string = null;index : number = 0;// LIFE-CYCLE CALLBACKS:onLoad () {this.label = this.getComponent(cc.Label);this.text = this.label.string;//取得完整的文本this.label.string = '';//清空文本,从头显示 this.schedule(this.onTimer,0.3);//无限运行下去,每次间隔0.3秒//显示每个字“恭喜请领取奖励”}start () {}onTimer(){this.index ++;let str : string = this.text.substring(0,this.index);this.label.string = str;cc.log('显示label=' + str);if(this.index >= this.text.length){this.unschedule(this.onTimer);//取消这个onTimer()计时器}      }// update (dt) {}
}

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {label : cc.Label = null ;text : string = null;index : number = 0;// LIFE-CYCLE CALLBACKS:onLoad () {this.label = this.getComponent(cc.Label);this.text = this.label.string;//取得完整的文本cc.log('显示this.text=' + this.text);this.label.string = '';//清空文本,从头显示 cc.log('显示this.label.string=' + this.label.string);this.schedule(this.onTimer,0.3);//无限运行下去,每次间隔0.3秒//显示每个字“恭喜请领取奖励”}start () {}onTimer(){this.index ++;cc.log('显示this.index=' + this.index);let str : string = this.text.substring(0,this.index);this.label.string = str;cc.log('显示label=' + str);if(this.index >= this.text.length){this.unschedule(this.onTimer);//取消这个onTimer()计时器}      }// update (dt) {}
}

间隔0.3秒,文字“恭喜请领取奖励”一个个的出现。

再次优化,在文字上面添加一个灰字的效果:

复制,提示文字,命名为 灰字(不要ShowTips代码),修改原来的为 高亮(要ShowTips代码)。

显示效果如图:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {//作为全局变量,在方法中使用label : cc.Label = null;//每次更新组件labeltext : string = null; //按照text的值显示字段的长度并赋值给组件label中的stringindex : number = 0;//指定字段的长度最大值onLoad () {/*获得这个代码绑定的文字节点中的其他组件Label,并赋值给全局变量cc.Label类型的label。 */this.label = this.getComponent(cc.Label);//获取全局变量cc.Label组件的label的string值,赋值给textthis.text = this.label.string;//清空全局变量 labal 中的string值。this.label.string = '';//在脚本 计时器方法schedule中,回调onTimer计时器方法。/* comp.schedule(callback(回调 赋值this.onTimer) , interval(时间间隔 赋值0.3) ,repeat(重复次数 未赋值) , delay(延迟时间 未赋值));*/this.schedule(this.onTimer,0.3);      }start () {}//onTimer计时器方法onTimer(){this.index ++;//指定string需要显示的字段的长度最大值//使用substring()获取对象中指定位置的字符串let str : string = this.text.substring(0,this.index);//赋值给 被清空的全局变量 labal 中的string。this.label.string = str;   cc.log('显示label=' + str);//如果index大于字符串的总长度,那么结束回调方法if(this.index >= this.text.length){this.unschedule(this.onTimer);}    }// update (dt) {}
}

12.1小游戏实例:手柄控制器

12.2布置场景

然后再创建一个纯色的背景,并修改相关的参数:

调整缩放Scale让小车也变小一点。

12.3手柄的拖动

因为我们的游戏最终都是要放到手机上进行的(所以不采用原来单一的mousedown等(鼠标类型))。换成touch~等事件,可以使用鼠标或者触摸屏操作。

把脚本GamePad.ts添加到手柄下面,作为组件:

this.node.on()方法:

(method) cc.Node.on<(e: cc.Event.EventTouch) => void>(type: string, callback: (e: cc.Event.EventTouch) => void, target?: any, useCapture?: boolean): (e: cc.Event.EventTouch) => void

!#en Register a callback of a specific event type on Node. Use this method to register touch or mouse event permit propagation based on scene graph, These kinds of event are triggered with dispatchEvent, the dispatch process has three steps:

  1. Capturing phase: dispatch in capture targets (_getCapturingTargets), e.g. parents in node tree, from root to the real target
  2. At target phase: dispatch to the listeners of the real target
  3. Bubbling phase: dispatch in bubble targets (_getBubblingTargets), e.g. parents in node tree, from the real target to root In any moment of the dispatching process, it can be stopped via event.stopPropagation() or event.stopPropagationImmidiate(). It's the recommended way to register touch/mouse event for Node, please do not use cc.eventManager directly for Node. You can also register custom event and use emit to trigger custom event on Node. For such events, there won't be capturing and bubbling phase, your event will be dispatched directly to its listeners registered on the same node. You can also pass event callback parameters with emit by passing parameters after type. !#zh 在节点上注册指定类型的回调函数,也可以设置 target 用于绑定响应函数的 this 对象。 鼠标或触摸事件会被系统调用 dispatchEvent 方法触发,触发的过程包含三个阶段:
  4. 捕获阶段:派发事件给捕获目标(通过 _getCapturingTargets 获取),比如,节点树中注册了捕获阶段的父节点,从根节点开始派发直到目标节点。
  5. 目标阶段:派发给目标节点的监听器。
  6. 冒泡阶段:派发事件给冒泡目标(通过 _getBubblingTargets 获取),比如,节点树中注册了冒泡阶段的父节点,从目标节点开始派发直到根节点。 同时您可以将事件派发到父节点或者通过调用 stopPropagation 拦截它。 推荐使用这种方式来监听节点上的触摸或鼠标事件,请不要在节点上直接使用 cc.eventManager。 你也可以注册自定义事件到节点上,并通过 emit 方法触发此类事件,对于这类事件,不会发生捕获冒泡阶段,只会直接派发给注册在该节点上的监听器 你可以通过在 emit 方法调用时在 type 之后传递额外的参数作为事件回调的参数列表

@param type — A string representing the event type to listen for.See {{#crossLink "Node/EventTyupe/POSITION_CHANGED"}}Node Events{{/crossLink}} for all builtin events.

(表示要侦听的事件类型的字符串。 所有内置事件请参见{{#crossLink"Node/EventTyupe/POSITION_CHANGED"}}Node Events{{/crossLink}}。  )

@param callback — The callback that will be invoked when the event is dispatched. The callback is ignored if it is a duplicate (the callbacks are unique).(当事件被分派时将被调用的回调。 如果回调是重复的(回调是唯一的),则忽略回调。 )

@param target — The target (this object) to invoke the callback, can be null(调用回调的目标(此对象)可以为空  )

@param useCapture — When set to true, the listener will be triggered at capturing phase which is ahead of the final target emit, otherwise it will be triggered during bubbling phase.(当设置为true时,侦听器将在捕获阶段(在最终目标发出之前)触发,否则将在冒泡阶段触发。  )

@example

this.node.on(cc.Node.EventType.TOUCH_START, this.memberFunction, this); // if "this" is component and the "memberFunction" declared in CCClass.
node.on(cc.Node.EventType.TOUCH_START, callback, this);
node.on(cc.Node.EventType.TOUCH_MOVE, callback, this);
node.on(cc.Node.EventType.TOUCH_END, callback, this);
node.on(cc.Node.EventType.TOUCH_CANCEL, callback, this);
node.on(cc.Node.EventType.ANCHOR_CHANGED, callback);
node.on(cc.Node.EventType.COLOR_CHANGED, callback);

另外几个方法:

getLocation()方法:

convertToNodeSpaceAR()方法:

setPosition()方法:

(method) cc.Node.setPosition(newPosOrX: number | cc.Vec2 | cc.Vec3, y?: number, z?: number): void

!#en Sets the position (x, y, z) of the node in its parent's coordinates. Usually we use cc.v2(x, y) to compose cc.Vec2 object, and passing two numbers (x, y) is more efficient than passing cc.Vec2 object. For 3D node we can use cc.v3(x, y, z) to compose cc.Vec3 object, and passing three numbers (x, y, z) is more efficient than passing cc.Vec3 object. !#zh 设置节点在父节点坐标系中的位置。 可以通过下面的方式设置坐标点:

  1. 传入 2 个数值 x, y。
  2. 传入 cc.v2(x, y) 类型为 cc.Vec2 的对象。
  3. 对于 3D 节点可以传入 3 个数值 x, y, z。
  4. 对于 3D 节点可以传入 cc.v3(x, y, z) 类型为 cc.Vec3 的对象。

@param newPosOrX — X coordinate for position or the position (x, y, z) of the node in coordinates

@param y — Y coordinate for position

@param z — Z coordinate for position

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {// onLoad () {}start () {this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchCancel,this);this.node.on('touchcancel',this.onTouchCancel,this);}onTouchStart( e : cc.Event.EventTouch ){}//暂时没有用到,置空//计算触摸点的当前位置,把手柄的位置移动到触摸点所在的位置onTouchMove( e : cc.Event.EventTouch ){//e.getLocation()为触摸点的位置,是世界坐标//需要把世界坐标转换为本地坐标cc.log("世界坐标touch move : x=" + e.getLocationX() + ",y=" +e.getLocationY());let parent : cc.Node = this.node.parent;//父节点(圆形底盘)//e.getLocation() 获得触摸点的世界坐标(触点位置)//把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());//根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标this.node.setPosition(pos);cc.log("转换成本地坐标convert to local: x=" +  pos.x + ",y=" + pos.y); }onTouchCancel( e : cc.Event.EventTouch ){//触摸松开的时候,把手柄的位置移动到中央this.node.setPosition( cc.v3(0,0,0));}// update (dt) {}
}

 

世界坐标是Canvas的原点,本地坐标是游戏摇杆的底盘原点。

12.4方位角

这个点 连接 原点形成的线和x轴的夹角就是方位角。

单位圆的x值就是cos值,y值就是sin值。 使用normalize()就可以得到这个方位角的(x,y)值。

有了方位角之后,我们来给手柄一个拖动范围的限制:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {// onLoad () {}start () {this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchCancel,this);this.node.on('touchcancel',this.onTouchCancel,this);}onTouchStart( e : cc.Event.EventTouch ){}//暂时没有用到,置空//计算触摸点的当前位置,把手柄的位置移动到触摸点所在的位置onTouchMove( e : cc.Event.EventTouch ){//e.getLocation()为触摸点的位置,是世界坐标//1.需要把世界坐标转换为本地坐标cc.log("世界坐标touch move : x=" + e.getLocationX() + ",y=" +e.getLocationY());let parent : cc.Node = this.node.parent;//父节点(圆形底盘)//e.getLocation() 获得触摸点的世界坐标(触点位置)//把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());// 该点所在的方位(cos,sin)let direction : cc.Vec2 = pos.normalize();cc.log("方位 : cos=" + direction.x + ", sin=" + direction.y);//2.限制手柄在“圆形底盘”的边界之内//最大距离圆形底盘的半径R=100,手柄的半径r=40;maxD=R-r=60;let maxD = 100 * 0.6; //“触摸点”和“手柄圆心”两点之间的实际距离△d(在二维空间中的欧氏距离就是两点之间的直线段距离)let d: number = cc.Vec2.distance( pos , cc.v2(0,0) );cc.log("欧氏距离 :△d=" + d);if( d > maxD ){pos.x = maxD * direction.x;//d * cospos.y = maxD * direction.y;//d * sin}//根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标this.node.setPosition(pos);cc.log("转换成本地坐标convert to local: x=" +  pos.x + ",y=" + pos.y); }onTouchCancel( e : cc.Event.EventTouch ){//触摸松开的时候,把手柄的位置移动到父节点(圆形底盘)的中央this.node.setPosition( cc.v3(0,0,0));}// update (dt) {}
}

找到相似的触摸点,从△d≈59可以看出,以上图是正确的:(因为两张图不是一次截的,有一点是后期修改了,请不要介意。因为数据是对的。)

12.5小车的旋转

添加节点小车:

关于角度的解释,以下为正确解释:

1 cc.Node.angle

表示旋转的角度,逆时针为正

官方建议不要使用 cc.Node.rotation

2 a.signAngle( b)

a和b为两个向量,返回值是一a,b的夹角 (弧度值)

radian = a.signAngle(b)

分两种情况:

(1) a位于b的顺时针方向:角度为正

(2) a位于b的逆时针方向:角度为负

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {//目标小车  节点car : cc.Node = null;onLoad(){this.car = cc.find('Canvas/小车');}start () {this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchCancel,this);this.node.on('touchcancel',this.onTouchCancel,this);}onTouchStart( e : cc.Event.EventTouch ){}//暂时没有用到,置空//计算触摸点的当前位置,把手柄的位置移动到触摸点所在的位置onTouchMove( e : cc.Event.EventTouch ){//e.getLocation()为触摸点的位置,是世界坐标//1.需要把世界坐标转换为本地坐标cc.log("世界坐标touch move : x=" + e.getLocationX() + ",y=" +e.getLocationY());let parent : cc.Node = this.node.parent;//父节点(圆形底盘)//e.getLocation() 获得触摸点的世界坐标(触点位置)//把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());// 该点所在的方位(cos,sin)let direction : cc.Vec2 = pos.normalize();cc.log("方位 : cos=" + direction.x + ", sin=" + direction.y);//2.限制手柄在“圆形底盘”的边界之内//最大距离圆形底盘的半径R=100,手柄的半径r=40;maxD=R-r=60;let maxD = 100 * 0.6; //“触摸点”和“手柄圆心”两点之间的实际距离△d(在二维空间中的欧氏距离就是两点之间的直线段距离)let d: number = cc.Vec2.distance( pos , cc.v2(0,0) );cc.log("欧氏距离 :△d=" + d);if( d > maxD ){pos.x = maxD * direction.x;//d * cospos.y = maxD * direction.y;//d * sin}//根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标this.node.setPosition(pos);cc.log("转换成本地坐标convert to local: x=" +  pos.x + ",y=" + pos.y); //3.操纵目标小车//  radian = a.angle(b) 求a,b两个向量的夹角//  其中 cc.v2(1,0) 表示x轴方向的单位向量let radian = pos.signAngle(cc.v2(1,0)); //弧度值let angle = radian / Math.PI * 180; //把弧度制转换成角度值this.car.angle = -angle; // 按API要求,angle是按逆时针为正的// this.car.rotation = angle; //这样写也行,但是不如 this.car.angle = -angle;}onTouchCancel( e : cc.Event.EventTouch ){//触摸松开的时候,把手柄的位置移动到父节点(圆形底盘)的中央this.node.setPosition( cc.v3(0,0,0));}// update (dt) {}
}

12.6小车的运动

新建并添加脚本CarScript.ts到小车节点。

CarScript.ts 的代码:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {//每次刷新小车移动的距离@propertyspeed :number = 3;//小车移动的方向@propertydirection : cc.Vec2 = null;// LIFE-CYCLE CALLBACKS:onLoad () {// 小车的初始方向测试 this.direction = cc.v2(1,0);}start () {}update (dt) {//静止if(this.direction == null){return;}// speed 步长// direction 方向let dx = this.speed * this.direction.x;let dy = this.speed * this.direction.y;let pos = this.node.getPosition();pos.x += dx;pos.y += dy;this.node.setPosition(pos);}
}

GamePad.ts 的代码:

红色下划线没关系。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {//目标小车  节点car : cc.Node = null;//控制小车的脚本carScript : cc.Component = null; onLoad(){this.car = cc.find('Canvas/小车');//获取 控制小车的脚本this.carScript = this.car.getComponent('CarScript');}start () {this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchCancel,this);this.node.on('touchcancel',this.onTouchCancel,this);}onTouchStart( e : cc.Event.EventTouch ){}//暂时没有用到,置空//计算触摸点的当前位置,把手柄的位置移动到触摸点所在的位置onTouchMove( e : cc.Event.EventTouch ){//e.getLocation()为触摸点的位置,是世界坐标//1.需要把世界坐标转换为本地坐标cc.log("世界坐标touch move : x=" + e.getLocationX() + ",y=" +e.getLocationY());let parent : cc.Node = this.node.parent;//父节点(圆形底盘)//e.getLocation() 获得触摸点的世界坐标(触点位置)//把触摸点的世界坐标转换为父节点(圆形底盘)的本地坐标 ,pos代表触摸点的本地坐标let pos : cc.Vec2 = parent.convertToNodeSpaceAR(e.getLocation());// 触摸点所在的方位(cos,sin)let direction : cc.Vec2 = pos.normalize();cc.log("方位 : cos=" + direction.x + ", sin=" + direction.y);//2.限制手柄在“圆形底盘”的边界之内//最大距离圆形底盘的半径R=100,手柄的半径r=40;maxD=R-r=60;let maxD = 100 * 0.6; //“触摸点”和“手柄圆心”两点之间的实际距离△d(在二维空间中的欧氏距离就是两点之间的直线段距离)let d: number = cc.Vec2.distance( pos , cc.v2(0,0) );cc.log("欧氏距离 :△d=" + d);if( d > maxD ){pos.x = maxD * direction.x;//d * cospos.y = maxD * direction.y;//d * sin}//根据pos触摸点的本地坐标,使用setPosition()设置手柄在父节点的本地坐标this.node.setPosition(pos);cc.log("转换成本地坐标convert to local: x=" +  pos.x + ",y=" + pos.y); //3.操纵目标小车//  radian = a.angle(b) 求a,b两个向量的夹角//  其中 cc.v2(1,0) 表示x轴方向的单位向量let radian = pos.signAngle(cc.v2(1,0)); //弧度值let angle = radian / Math.PI * 180; //把弧度制转换成角度值this.car.angle = -angle; // 按API要求,angle是按逆时针为正的// this.car.rotation = angle; //这样写也行,但是不如 this.car.angle = -angle;//把触摸点所在的方位(cos,sin) 赋值给小车的方位this.carScript.direction = direction;}onTouchCancel( e : cc.Event.EventTouch ){//触摸松开的时候,把手柄的位置移动到父节点(圆形底盘)的中央this.node.setPosition( cc.v3(0,0,0));// 松开手柄 小车停止this.carScript.direction = null;}// update (dt) {}
}

13.1GIF图的显示

13.2准备GIF素材

在PS中打开就能看到这些图片。

GIF提取教程:1.每次只显示一张,然后另存为png图片。

2.把这0~11的12张图片加入到项目中。

自动剪裁红色框内容大小是不一样的。

3.选中图片,然后把属性检查器中的Sprite中的Trim Type(裁剪类型)属性设置为None,然后点击应用。每一张图片都要设置。(因为每一张图片自动裁剪的大小不一样,使用不是方便。)

13.3GIfPlayer

1.新建图片节点

给节点添加默认显示的图片帧,例如第一张图片0。

2.添加一个脚本GifPlayer.ts,挂在这个图片节点上。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property( [cc.SpriteFrame] )frames : cc.SpriteFrame[] = [];//数组sprite : cc.Sprite = null; //Sprite组件index : number = 0; //当前显示第n张图片interval : number = 0.1; //定时器的间隔// LIFE-CYCLE CALLBACKS:onLoad () {this.sprite = this.getComponent(cc.Sprite);}start () {this.schedule(this.onTimer,this.interval);}onTimer(){if(this.frames.length ==0){return;}this.sprite.spriteFrame = this.frames[this.index];//下一帧this.index ++;if(this.index >= this.frames.length){this.index = 0;}}onDestroy(){this.unschedule(this.onTimer);}// update (dt) {}
}

在Frames中输入12点击回车,就会出现下列数组,就可以逐一添加图片啦。

就可以了:

13.4图集Atlas

图集资源 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/asset/atlas.html

Atlas的制作教程:

文档中写的这两个都是外国的收费软件。百度搜索,然后点击进入官网。

下载这个试用版:

把所有图片一张张拖到中间这个小人的区域:

一般情况下 希望不要修剪,修剪模式改成“没有”(修剪的话合成的图片更小但是可能裁剪掉一些东西):

设置好之后,点击“发布精灵表”就可以得到.plist文件和.png文件

在plist中代码描述,大图中的内容和每一张小图的位置和相关信息。

可以在飞羽图集中看到那时12张图片。

选中这些图片,然后把Trim Type选为None。

现在改成AtlasPlayer.ts,把GifPlayer.ts勾选掉。

代码:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {@property( [cc.SpriteAtlas] )atlas : cc.SpriteAtlas = null;frames : cc.SpriteFrame[] = [];sprite : cc.Sprite = null; //Sprite组件index : number = 0; //当前显示第n张图片interval : number = 0.1; //定时器的间隔// LIFE-CYCLE CALLBACKS:onLoad () {this.sprite = this.getComponent(cc.Sprite);if(this.atlas != null){this.frames = this.atlas.getSpriteFrames();}}start () {this.schedule(this.onTimer,this.interval);}onTimer(){if(this.frames.length ==0){return;}this.sprite.spriteFrame = this.frames[this.index];//下一帧this.index ++;if(this.index >= this.frames.length){this.index = 0;}}onDestroy(){this.unschedule(this.onTimer);}// update (dt) {}}

14.1动态加载资源(一次加载一个资源)

1.静态加载资源:节点引用图片资源(在Sprite中的SpriteFrame引用飞机这个图片)。

2.动态加载:在脚本中使用cc.resources.load() 加载资源。

把脚本Demo1.ts挂到“图片”节点上。实现点击图片飞机后,出现图片汽车。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {onLoad () {this.node.on('touchstart', this.onClicked, this);}start () {}onClicked(){let self = this;cc.resources.load("icon/汽车", cc.SpriteFrame, function (err, assets ) {// if(err) { cc.log(err); return }self.node.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame> assets;});}// update (dt) {}
}

《 图文教程》

cc.resources.load() 加载资源

示例:

onClicked(){

let self = this;    // 这是JS中的闭包语法

cc.resources.load("icon/汽车", cc.SpriteFrame, function (err, assets) {

self.node.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame> assets;

});

}

其中,方法的原型:

cc.resources.load( path, type, onComplete)

第1个参数:  path 表示要加载的资源的路径。

例如,icon/汽车 指的是 assets/resources/icon/汽车。

(*) 待加载的资源必须放在resources目录下。

(*) 路径不能加后缀名。

第2个参数:type 指定资源对象类型,可以省略。例如:cc.SpriteFrame , cc.AudioClip

第3个参数:onComplete指定回调方法,当资源加载完毕时调用。

function( err, assets ) {

}

方法含两个参数(err错误原因,assets资源对象)

若err == null ,表示资源加载成功,assets即为加载得到的资源对象。

若err!=null ,表示资源加载出错,err即为出错的原因。

相关语法说明:

(1) 闭包

在回调中无法直接调用this,所以先定义一个 self = this ,以便在回调中调用

let self = this;

cc.resources.load("icon/汽车", cc.SpriteFrame, function (err, assets) {

self.node.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame> assets;

});

(2) 类型转换

assets是cc.Asset类型,使用类型转换<cc.SpriteFrame> (左边是<cc.spriteFrame>类型,右边是<cc.Asset>类型):

self.node.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame> assets;

在官方文档中,也可以找到资源加载介绍:

获取和加载资源 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/scripting/load-assets.html?h=%E8%8E%B7%E5%8F%96%E5%92%8C

动态加载 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/asset/dynamic-load-resources.html

14.2一次加载多个资源

1.指定多个资源路径

cc.resources.load(paths,callback(err,assets){ ... })

其中,paths类型[cc.String](路径),assets类型[cc.Assets](数组),

我们可以使用paths指定一个路径或者一个路径的数组。如果我们指定一个路径的数组,那么在回调方法callback(err,assets){ ... }中就会传回一个assets数组。

2.指定一个资源目录

cc.resources.loadDir(path,callback(err,assets){ ... })

其中,loadDir(文件管理器),path是一个文件夹路径,assets是[cc.Asset]。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class NewClass extends cc.Component {frames : cc.SpriteFrame[] = new Array(); //数组帧 sprite : cc.Sprite = null; //Script组件index : number = 0; //当前显示第N张图片interval : number = 0.1; //定时器的间隔秒数flag : number = 0; //判断是正数(1)还是倒数(-1)图片onLoad () {this.sprite = this.getComponent(cc.Sprite);// 动态加载一个目录下的所有资源let self = this;cc.resources.loadDir("飞羽gif/",cc.SpriteFrame, function(err,assets:[cc.SpriteFrame]){self.frames = assets;});}start () {//schedule(callback: Function, interval?: number, repeat?: number, delay?: number): void;      //调度一个只运行一次的回调函数,可以指定 0 让回调函数在下一帧立即执行或者在一定的延时之后执行。this.schedule(this.onTimer , this.interval);//时间表:onTimer,每隔0.1秒显示。}onTimer(){if(this.frames.length == 0){return;}else{//居然识别不了 else if !!!!!只能用if了。this.sprite.spriteFrame = this.frames[this.index];cc.log("this.index="+this.index +"; this.flag="+this.flag);if(this.flag == 1 || this.index == 0){//从第一张图片开始正数显示(因为在cocos中添加的默认是第一张this.frames[0])this.index ++;//下一帧this.flag = 1;}if(this.flag == -1){this.index --;//下一帧this.flag = -1;}if(this.index >= this.frames.length){//循环到最后一张this.index = this.frames.length-2; //从最后一张图片开始倒数显示this.frames[11]this.flag = -1;}}}// update (dt) {}onDestroy(){this.unschedule(this.onTimer);}
}

居然识别不了else if!!!!!只能用if!!!

看来需要学一学TypeScript的语法。

变量声明 - TypeScript 中文手册  https://typescript.bootcss.com/variable-declarations.html

15.1触摸事件

节点事件系统 · Cocos Creator  https://docs.cocos.com/creator/manual/zh/engine/event/event-node.html

事件 API · Cocos Creator  https://docs.cocos.com/creator/manual/zh/engine/event/event-api.html

15.2事件冒泡机制

只点击“汽车”或者“停车标志”会上浮到父节点“道路”:日志打印了被点击的子节点和上浮到的父节点。

加上 e.stopPropagation();(停止传递当前事件。)就不会上浮到父节点“道路”。而是只打印子节点汽车。

 onLoad () {this.node.on('touchstart',this.onTouch,this);}start () {}onTouch(e: cc.Event.EventTouch){cc.log('汽车 onTouch(): ');// e.stopPropagation();// (method) cc.Event.stopPropagation(): void// !#en Stops propagation for current event. !#zh 停止传递当前事件。}

15.3(练习)遮罩效果

把脚本附加给遮罩这个节点。

 onLoad () {this.node.on('touchstart', this.onTouch, this);}start () {}onTouch( e : cc.Event.EventTouch){//设置节点状态处于非激活的状态。this.node.active = false;// e.stopPropagation();}// update (dt) {}

可以发现:只有点击“灰色区域”才能使得遮罩处于非激活状态,即去掉遮罩。而点击“游戏主画面”背景图是没有用的。

使用Widget组件进行优化,使得遮罩自适应屏幕,且和父节点(Canvas)同样大小遮满屏幕。把上下左右边距全部调成0px。

可以发现,调整之后:场景编辑器中的遮罩范围自动适应了整个相机大小。且运行后效果也是满屏遮罩。

15.4(练习)模态提示框

设置提示框大小和Canvas同等大小960x640。

在提示框下面添加单色节点“遮罩层”,设置不透明度Opacity、大小Size和自适应边距Widget。

添加按钮节点,并让他们对齐:

然后添加脚本:

下一关按钮:

再玩一遍按钮:

代码是一样的:

@ccclass
export default class NewClass extends cc.Component {onLoad () {this.node.on('touchstart', this.onTouch, this);}start () {}onTouch( e ){// 隐藏对话框   let node : cc.Node = cc.find('Canvas/提示框');node.active = false;// 下面的代码省略,仅为模拟操作}// update (dt) {}
}

我们可以先把提示框隐藏掉,然后多加一个按钮“结束”。把脚本附加到结束按钮中。

@ccclass
export default class NewClass extends cc.Component {onLoad () {this.node.on('touchstart', this.onTouch, this);}start () {}onTouch( e ){// 显示对话框let node : cc.Node = cc.find('Canvas/提示框');node.active = true;}
}

效果:

15.5组件Block Input Events

用于实现遮罩层出现之后,使得遮罩层后面的内容不能再被点击。这个阻止输入事件BlockInputEvents的按钮加在遮罩层上:

(提示框中)点击遮罩层上的按钮“再玩一局”的代码(结束对话框EndDialogBox.ts添加在提示框这个父结点上,结束整个遮罩层生显示的内容),使得遮罩层的状态为未激活false:

@ccclass
export default class NewClass extends cc.Component {onLoad () {let replayButton : cc.Node = cc.find('再玩一局', this.node);replayButton.on('touchstart', this.onReplay, this);}start () {}onReplay(){//当前节点的自身激活状态。this.node.active = false;}// update (dt) {}
}

游戏主画面上兔子随鼠标可以被拖动到鼠标移动停止的位置(Rabbit.ts代码):


@ccclass
export default class Rabbit extends cc.Component {startPoint : cc.Vec2 = null; // 开始的触摸点startNodePos : cc.Vec2 = null; // 节点的初始位置num : number = 1; //推断一次运行游戏的移动过程中,调用计算了多少次TouchMoveonLoad () {this.num=0;//移动一次清零一次。this.node.on('touchstart', this.onTouchStart, this);this.node.on('touchmove', this.onTouchMove, this);}start () {}onTouchStart( e : cc.Event.EventTouch){
//Path: Canvas/游戏主画面/兔子, UUID: 2fGs7432NHZ5nIZXxcf1qr//1.把初始点(只需获取)和触点(世界坐标需要转换)都转换成父节点(游戏主画面)空间坐标系的位置this.startNodePos = this.node.getPosition();//获取初始点的节点在父节点坐标系中的位置(x, y, z)cc.log("startNodePos"+ this.startNodePos);// 转换到父节点的本地坐标{.convertToNodeSpaceAR(将(e.getLocation()触点位置)转换到节点 (局部父节点) 空间坐标系。)}this.startPoint = this.node.parent.convertToNodeSpaceAR(e.getLocation());cc.log("e.getLocation()"+ e.getLocation());}onTouchMove( e: cc.Event.EventTouch){//把移动的终点endPoint转换到父节点的本地坐标    let endPoint : cc.Vec2 = this.node.parent.convertToNodeSpaceAR(e.getLocation()); cc.log("endPoint"+ endPoint);cc.log("startPoint"+ this.startPoint);// 两个向量可以直接加减// 加法: result = a.add(b)// 减法:result = a.sub(b)        //移动的终点endPoint坐标减去初始点startPoint坐标 = 两点之间的间距distancelet distance : cc.Vec2 =  endPoint.sub( this.startPoint);cc.log("distance=endPoint-startPoint="+ distance);let pos = this.startNodePos.add( distance);//初始节点加上间距distance得到节点在父节点坐标系的具体位置数值this.node.setPosition( pos );cc.log("pos=startNodePos+distance="+ pos);  cc.log("num"+this.num);this.num ++;}}

点击“结束按钮”触发遮罩层的代码,使得对话框的状态为true激活状态:

@ccclass
export default class NewClass extends cc.Component {onLoad () {this.node.on('touchstart', this.onTouch, this);}start () {}onTouch(){//Tooltip 提示框; dialog 对话、会话、对话框let dialogNode : cc.Node = cc.find('Canvas/提示框');dialogNode.active = true;}// update (dt) {}
}

还可以自己写一个脚本,使得遮罩层出现后,遮罩层后面的内容不能被操作。其他内容不变,只修改这里就可以了。

// 提示:
// 可以自己写一个脚本组件,也可以直接使用系统自带的 BlockInputEvents 组件
@ccclass
export default class StopInputEvent extends cc.Component {onLoad(){this.node.on('touchstart', this.stopEvents, this);this.node.on('touchmove', this.stopEvents, this);this.node.on('touchend', this.stopEvents, this);this.node.on('touchcancel', this.stopEvents, this);}stopEvents( e : cc.Event){e.stopPropagation();}
}

16.1小游戏:子弹发射效果

16.2布置场景

添加单色背景:画布Canvas》创建节点》创建渲染节点》Sprite单色,设置:颜色color淡蓝色,背景大小960X640(和我们的画布Canvas分辨率相同。),还要添加UI组件Widget,使得背景自适应屏幕画布。

添加炮塔节点:直接拖动图片拖上来。然后调整一下大小就可以了。

子弹需要动态创建,所以不需要创建。

16.3动态创建节点

创建脚本 CannonSprite.ts,加进节点“炮塔”中,然后把子弹图片添加进“Bullet Icon”子弹图标属性中。点击炮塔之后,子弹出现的效果如下图:

@ccclass
export default class NewClass extends cc.Component {//添加新属性,把子弹图片添加去。@property(cc.SpriteFrame)bulletIcon : cc.SpriteFrame = null;  //子弹IcononLoad () {//点击炮塔this.node.on('touchstart',this.onTouch,this);}start () {}onTouch(){this.fire();}fire(){if(this.bulletIcon==null){cc.log('请设置bulletIcon图片');return;}//动态创建一个Node(节点作为子弹的节点),添加Sprite组件()let bullet : cc.Node = new cc.Node();let sprite : cc.Sprite = bullet.addComponent(cc.Sprite);//把图片添加到Sprite组件的spriteFrame图片帧中sprite.spriteFrame = this.bulletIcon;bullet.parent = this.node;//把这个动态创建的子弹节点作为子节点,挂到大炮下面bullet.setPosition(cc.v3(0,65,0));//设置初始位置}// update (dt) {}
}

16.4附加脚本组件

先创建一个脚本BulletScript.ts(只创建,不用挂到节点上。最后从CannonScript.ts引入即可。),并且规范化类名class写成BulletScript。

@ccclass
export default class BulletScript extends cc.Component {onLoad () {this.schedule(this.onTimer,0.016);}start () {}onTimer(){if(this.node.y>300){//子弹最多飞300px,(超过300px则)this.unschedule(this.onTimer);//停止定时器this.node.destroy();//销毁节点,使子弹消失。return;}this.node.y += 10;//每0.016s移动10px。}
}

然后在CannonScript.ts中也要修改类名:

然后加挂一个脚本组件,并点击“快速修复”,自动得到import地址。

效果:点击加农炮之后子弹会一直向上飞,然后到y=300停止并消失。


import BulletScript from "./BulletScript";const {ccclass, property} = cc._decorator;@ccclass
export default class CannonScript extends cc.Component {//添加新属性,把子弹图片添加去。@property(cc.SpriteFrame)bulletIcon : cc.SpriteFrame = null;  //子弹IcononLoad () {//点击炮塔this.node.on('touchstart',this.onTouch,this);}start () {}onTouch(){this.fire();}fire(){if(this.bulletIcon==null){cc.log('请设置bulletIcon图片');return;}//动态创建一个Node(节点作为子弹的节点),添加Sprite组件()let bullet : cc.Node = new cc.Node();let sprite : cc.Sprite = bullet.addComponent(cc.Sprite);//把图片添加到Sprite组件的spriteFrame图片帧中sprite.spriteFrame = this.bulletIcon;bullet.parent = this.node;//把这个动态创建的子弹节点作为子节点,挂到大炮下面bullet.setPosition(cc.v3(0,65,0));//设置初始位置//加挂一个脚本组件let script = bullet.addComponent(BulletScript);}// update (dt) {}
}

16.5爆炸效果

打开BulletScript.ts脚本,


const {ccclass, property} = cc._decorator;@ccclass
export default class BulletScript extends cc.Component {//添加属性爆炸效果 图片帧@property(cc.SpriteFrame)explodeEffect : cc.SpriteFrame = null;onLoad () {this.schedule(this.onTimer,0.016);}start () {}onTimer(){if(this.node.y>300){//子弹最多飞300px,(超过300px则)this.unschedule(this.onTimer);//停止定时器//this.node.destroy();//销毁节点,使子弹消失。this.beginExplode();//开始执行爆炸效果return;}this.node.y += 10;//每0.016s移动10px。}beginExplode(){//XX.getComponent获取节点上指定类型的组件(例如:cc.Sprite),如果节点有附加指定类型的组件,则返回,如果没有则为空。 传入参数也可以是脚本的名称。let sprite : cc.Sprite = this.node.getComponent(cc.Sprite);sprite.spriteFrame = this.explodeEffect;//显示爆炸图片this.node.scale =0.1;//初始的爆炸图片 缩放为0.1倍(默认是1倍。)//透明度的初始值。默认就是255。不用设置。let self = this;//使用闭包语法//使用缓动系统tween。在0.5s时间内把爆炸图片放大到0.5倍,然后透明度逐渐变淡到0。cc.tween(this.node).to(0.5,{scale:0.5,opacity:0}).call(function(){self.afterExplode();}).start();}afterExplode() {this.node.destroy();//销毁这个节点,使子弹消失。}
}

10.3中学习了缓动系统:(to(时间0.5s,最终值:缩放0.5,透明度为0):对属性进行绝对值计算,传入最终值;by是变化值);

14.1中用到了“闭包”:在回调中无法直接调用this,所以先定义一个 self = this ,以便在回调中调用。

打开CannonScript.ts脚本,添加新属性爆炸效果,把图片加进去。


import BulletScript from "./BulletScript";const {ccclass, property} = cc._decorator;@ccclass
export default class CannonScript extends cc.Component {//添加新属性,把子弹图片添加去。@property(cc.SpriteFrame)bulletIcon : cc.SpriteFrame = null;  //子弹Icon//添加新属性,把爆炸图片添加去。@property(cc.SpriteFrame)explodeEffect : cc.SpriteFrame = null;  //子弹IcononLoad () {//点击炮塔this.node.on('touchstart',this.onTouch,this);}start () {}onTouch(){this.fire();}fire(){if(this.bulletIcon==null){cc.log('请设置bulletIcon图片');return;}//动态创建一个Node(节点作为子弹的节点),添加Sprite组件()let bullet : cc.Node = new cc.Node();let sprite : cc.Sprite = bullet.addComponent(cc.Sprite);//把图片添加到Sprite组件的spriteFrame图片帧中sprite.spriteFrame = this.bulletIcon;bullet.parent = this.node;//把这个动态创建的子弹节点作为子节点,挂到大炮下面bullet.setPosition(cc.v3(0,65,0));//设置初始位置//加挂一个脚本组件“子弹脚本”script->(BulletScript)let script : BulletScript = bullet.addComponent(BulletScript);//把爆炸效果图片交给“子弹脚本”scriptscript.explodeEffect = this.explodeEffect;}// update (dt) {}
}

效果:

17.1(综合)雪地射击小游戏

会移动的靶标,加农炮的炮口会随着鼠标的方向转动,点击出现射击的子弹。射中的时候出现爆炸的效果,没射中就只是出现子弹射出。一共有10发子弹。

17.2添加场景

先把需要的材料加入资源管理器。然后把“雪地背景”、“靶子”拖动上去到Canvas下面作为节点。适当调整这两个图片的大小(“雪地背景”:960X240px,加:UI组件->widget。或者只是调整大小到覆盖整个Main Camera。)。新建一个空节点,命名为射击系统。然后把半圆和炮塔都拖动到这个节点下方。

大致位置在中间(x=500),然后把加农炮的基准点调整到中心(使用矩形变换工具或者调整Anchor数值)。

效果如下图所示:

17.3炮口的旋转

新建Cannon.ts,添加到炮塔的下面。弧度值:c在b的右侧,在顺时针方向,所以c.signAngle(b)=π/4,为正;a在b的左侧,在逆时针方向,所以a.signAngle(b)=-π/4,为负。

Cannon.ts的代码:

const {ccclass, property} = cc._decorator;@ccclass
export default class Cannon extends cc.Component {//内部属性startPos : cc.Vec2 = null;startAngle : number = null;//炮口的角度// LIFE-CYCLE CALLBACKS:onLoad () {this.node.angle = 90; // 初始值设置为90度this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchEnd,this);this.node.on('touchcancle',this.onTouchCancle,this);}start () {}// update (dt) {}onTouchStart(e:cc.Event.EventTouch) {// startPos :触点开始的位置 (将一个点转换到节点 (局部:父节点) 空间坐标系。)this.startPos = this.node.parent.convertToNodeSpaceAR(e.getLocation());// startAngle:炮口的初始角度(x轴方向为0度)this.startAngle = this.node.angle;}onTouchMove(e:cc.Event.EventTouch) {// 触点的当前位置let pos = this.node.parent.convertToNodeSpaceAR(e.getLocation());//摆动的角度,a.signAngle(b)即a向量与b向量之间的夹角。let sweep_radian = pos.signAngle(this.startPos);//弧度radian值,例如π/4let sweep_angle = 180 * (sweep_radian / Math.PI); //弧度radian值 转化成 角度angle值/*炮口的新指向:比如,原来炮口90度,向右摆动15度,则炮口应该指向90-15=75度;比如,原来炮口90度,向左摆动15度,则炮口应该指向90-(-15)=105度; */let angle = this.startAngle - sweep_angle;//炮口的新角度// 炮口角度限制在45~135度之间if(angle < 45){angle = 45;}if(angle > 135){angle = 135;}cc.log("炮口摆动:" + sweep_angle+ ",修正后的角度:" +angle);this.node.angle = angle;}onTouchEnd(e:cc.Event.EventTouch) {}onTouchCancle(e:cc.Event.EventTouch) {}
}

17.4发射子弹

在Cannon.ts中添加代码:

添加子弹Icon属性,并给他赋值为 子弹图片。

鼠标松开,开火出现子弹。

const {ccclass, property} = cc._decorator;@ccclass
export default class Cannon extends cc.Component {//内部属性startPos : cc.Vec2 = null;startAngle : number = null;//炮口的角度//添加子弹Icon@property(cc.SpriteFrame)bulletIcon: cc.SpriteFrame = null;// LIFE-CYCLE CALLBACKS:onLoad () {this.node.angle = 90; // 初始值设置为90度this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchEnd,this);this.node.on('touchcancle',this.onTouchCancle,this);}start () {}// update (dt) {}onTouchStart(e:cc.Event.EventTouch) {// startPos :触点开始的位置 (将一个点转换到节点 (局部:父节点) 空间坐标系。)this.startPos = this.node.parent.convertToNodeSpaceAR(e.getLocation());// startAngle:炮口的初始角度(x轴方向为0度)this.startAngle = this.node.angle;}onTouchMove(e:cc.Event.EventTouch) {// 触点的当前位置let pos = this.node.parent.convertToNodeSpaceAR(e.getLocation());//摆动的角度,a.signAngle(b)即a向量与b向量之间的夹角。let sweep_radian = pos.signAngle(this.startPos);//弧度radian值,例如π/4let sweep_angle = 180 * sweep_radian / Math.PI; //弧度radian值 转化成 角度angle值/*炮口的新指向:比如,原来炮口90度,向右摆动15度,则炮口应该指向90-15=75度;比如,原来炮口90度,向左摆动15度,则炮口应该指向90-(-15)=105度; */let angle = this.startAngle - sweep_angle;//炮口的新角度// 炮口角度限制在45~135度之间if(angle < 45){angle = 45;}if(angle > 135){angle = 135;}cc.log("炮口摆动:" + sweep_angle+ ",修正后的角度:" +angle);this.node.angle = angle;}onTouchEnd(e:cc.Event.EventTouch) {this.fire();//鼠标松开,开火}onTouchCancle(e:cc.Event.EventTouch) {}   //开火 发射子弹fire() {if(this.bulletIcon==null){cc.log("请设置bulletIcon图片");return;}//炮口的指向,应是子弹的运行方向let angle : number = this.node.angle;let radian = angle * Math.PI/180; //角度值 转换成 弧度值let direction = cc.v2(Math.cos(radian),Math.sin(radian));//标准化向量//动态创建一个Node,添加Sprite组件let bullet : cc.Node = new cc.Node(); //创建 子弹 节点let sprite : cc.Sprite = bullet.addComponent(cc.Sprite); // 创建 子弹节点的 精灵 组件sprite.spriteFrame = this.bulletIcon;//给精灵的精灵帧 赋值 子弹Iconbullet.parent = this.node.parent; //指定父节点是 炮塔的父节点->射击系统//子弹的角度及初始位置bullet.angle = this.node.angle;//子弹的角度 赋值成 炮口的角度let r = 110;//子弹与射击基准的距离let bullet_x = r * direction.x;let bullet_y= r * direction.y;bullet.setPosition(cc.v3(bullet_x,bullet_y));//子弹的初始位置}
}

出现这样偏移的问题,仔细想了想,估计是父节点的中心点和子节点的对不上。

我修改了他们的位置,让他们的x,y轴重叠,且原点和父节点 射击系统 重叠。效果如下:

还是不在中间。那么感觉需要让炮台的图像向左一些,让y轴在炮口中间,y=0.5。不断调整这两个值,然后保持Position(0,0)。终于,子弹在炮口中间了。

微调一下r=140,简直正中间,完美。

17.5子弹的飞行

Bullet.ts的代码:

const {ccclass, property} = cc._decorator;@ccclass
export default class Bullet extends cc.Component {//飞行的方向(标准化向量)direction : cc.Vec2 = null;// LIFE-CYCLE CALLBACKS:onLoad () {}start () {this.schedule(this.onTimer,0.016);}// update (dt) {}onTimer() {//靶标与射击基准之间的距离400if(this.node.y > 400){this.dismiss();return;}let speed : number = 15; //步长let dx = speed * this.direction.x;let dy = speed * this.direction.y;this.node.x += dx;this.node.y += dy;}   dismiss() {this.node.destroy();}}

修改Cannon.ts的代码:把bullet子弹节点改名为bulletNode。更精确,不影响下面的给子弹附加脚本组件的时候声明的let bullet。

给节点bulletNode添加组件Bullet.ts需要手动导入。(记得给Bullet.ts改名

声明一下bullet的类型为Bullet类型。

Cannon.ts的完整代码:

import Bullet from "./Bullet";const {ccclass, property} = cc._decorator;@ccclass
export default class Cannon extends cc.Component {//内部属性startPos : cc.Vec2 = null;startAngle : number = null;//炮口的角度//添加子弹Icon@property(cc.SpriteFrame)bulletIcon: cc.SpriteFrame = null;// LIFE-CYCLE CALLBACKS:onLoad () {this.node.angle = 90; // 初始值设置为90度this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchEnd,this);this.node.on('touchcancle',this.onTouchCancle,this);}start () {}// update (dt) {}onTouchStart(e:cc.Event.EventTouch) {// startPos :触点开始的位置 (将一个点转换到节点 (局部:父节点) 空间坐标系。)this.startPos = this.node.parent.convertToNodeSpaceAR(e.getLocation());// startAngle:炮口的初始角度(x轴方向为0度)this.startAngle = this.node.angle;}onTouchMove(e:cc.Event.EventTouch) {// 触点的当前位置let pos = this.node.parent.convertToNodeSpaceAR(e.getLocation());//摆动的角度,a.signAngle(b)即a向量与b向量之间的夹角。let sweep_radian = pos.signAngle(this.startPos);//弧度radian值,例如π/4let sweep_angle = 180 * sweep_radian / Math.PI; //弧度radian值 转化成 角度angle值/*炮口的新指向:比如,原来炮口90度,向右摆动15度,则炮口应该指向90-15=75度;比如,原来炮口90度,向左摆动15度,则炮口应该指向90-(-15)=105度; */let angle = this.startAngle - sweep_angle;//炮口的新角度// 炮口角度限制在45~135度之间if(angle < 45){angle = 45;}if(angle > 135){angle = 135;}cc.log("炮口摆动:" + sweep_angle+ ",修正后的角度:" +angle);this.node.angle = angle;}onTouchEnd(e:cc.Event.EventTouch) {this.fire();//鼠标松开,开火}onTouchCancle(e:cc.Event.EventTouch) {}   //开火 发射子弹fire() {if(this.bulletIcon==null){cc.log("请设置bulletIcon图片");return;}//炮口的指向,应是子弹的运行方向let angle : number = this.node.angle;let radian = angle * Math.PI/180; //角度值 转换成 弧度值let direction = cc.v2(Math.cos(radian),Math.sin(radian));//标准化向量//动态创建一个子弹 bulletNode,添加Sprite组件let bulletNode : cc.Node = new cc.Node(); //创建 子弹 节点let sprite : cc.Sprite = bulletNode.addComponent(cc.Sprite); // 创建 子弹节点的 精灵 组件sprite.spriteFrame = this.bulletIcon;//给精灵的精灵帧 赋值 子弹IconbulletNode.parent = this.node.parent; //指定父节点是 炮塔的父节点->射击系统//子弹的角度及初始位置bulletNode.angle = this.node.angle;//子弹的角度 赋值成 炮口的角度let r = 140;//子弹与射击基准的距离let bullet_x = r * direction.x;let bullet_y= r * direction.y;bulletNode.setPosition(cc.v3(bullet_x,bullet_y));//子弹的初始位置//给子弹附加一个脚本组件Bullet.tslet bullet : Bullet = bulletNode.addComponent(Bullet);bullet.direction = direction; //子弹飞行的方向}
}

效果如图所示:

 

射击基准和靶标之间的距离要等于代码中写的是400。如图所示,还不是靶心。子弹还有长度,再调多一点到100,正好到靶心。

17.6手工碰撞计算

在Bullet.ts中进行检测:

在Cannon.ts中加上:

但是这样也一点不准确。看来和半径没有关系。还是需要计算子弹的延长线和最终的终点才好。(试图计算,思路:计算靶标圆心和子弹延长线的终点(不会算),这两个点之间的距离小于靶标半径100,判定命中靶标。)调节射击系统和靶子的位置,然后让子弹弹头基本上可以垂直射中靶心。

如下代码,表示射在这条红线上是命中。(这就从视觉上比较精确了。)

17.7特效提示

在Cannon.ts中,添加:爆炸效果需要显示的图片,并把这个图片传递给Bullets.ts。

在Bullet.ts中需要添加: 爆炸图片帧接受Cannon.ts中传递的图片。然后再命中目标之后,显示爆炸效果(图片)和加分效果(文字)。

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;@ccclass
export default class Bullet extends cc.Component {//飞行的方向(标准化向量)direction : cc.Vec2 = null;//靶标target : cc.Node = null;//爆炸特效explodeEffect : cc.SpriteFrame = null;// LIFE-CYCLE CALLBACKS:onLoad () {}start () {if(this.target == null){cc.log('未设置靶标target属性!');return;}this.schedule(this.onTimer,0.016);}// update (dt) {}onTimer() {//靶标与射击基准之间的距离400if(this.node.y > 400){//this.dismiss();if(this.isHit()){this.success();}else{this.failed();}return;}let speed : number = 15; //步长let dx = speed * this.direction.x;let dy = speed * this.direction.y;this.node.x += dx;this.node.y += dy;}   isHit() : boolean {let targetPos : cc.Vec2 = this.getWorldLocation(this.target); //靶子的世界坐标let selfPos : cc.Vec2 = this.getWorldLocation(this.node);//子弹运行400px之后的世界坐标//let distance : number = Math.abs( targetPos.x - selfPos.x); //x方向距离cc.log('靶标x='+ targetPos.x + ',子弹x=' + selfPos.x);cc.log('靶标y='+ targetPos.y + ',子弹y=' + selfPos.y);let distanceD : number = cc.Vec2.distance( targetPos,selfPos);//两点的欧式距离,即两点直线距离。cc.log('间距distanceD='+ distanceD);// if(distance < 50){//粗略判断:一点也不准确!!!!if(distanceD > 607 && distanceD < 612.8){//根据射击数据和间距diatanceD看出来的607和612.8。return true;}else{return false;}}//获取一个节点的世界坐标getWorldLocation(node: cc.Node): cc.Vec2 {let pos = node.getPosition();return node.parent.convertToNodeSpaceAR(pos);}success() {//此处应该添加特效cc.log('命中目标!');//this.dismiss();this.explode();//爆炸this.cheer();//加分}//爆炸特效explode() {cc.log('爆炸效果...');let sp : cc.Sprite = this.node.getComponent(cc.Sprite);//创建新的渲染节点精灵,sp.spriteFrame = this.explodeEffect;//把爆炸效果图加入这个精灵的精灵帧,作为图片资源。this.node.scale = 0.8;//爆炸效果图初始显示的缩放大小,//“闭包”:在回调中无法直接调用this,所以先定义一个 self = this ,以便在回调中调用。let self = this;cc.tween(this.node)//缓动效果to设置最终值.to(0.3,{scale:1.5})//消失时的大小。.to(0.2,{opacity:0})//消失时的透明度0,变透明.call(function(){self.dismiss();})//爆炸显示完之后,销毁子弹.start();}//加分效果cheer(){let labelNode : cc.Node = new cc.Node();//声明新的节点let label : cc.Label = labelNode.addComponent(cc.Label);//声明文字标签组件label.string = "+10分";//给文字标签组件的string赋值labelNode.color = new cc.Color(255,0,0);//设置颜色labelNode.parent = this.node.parent;//设置该节点的父节点labelNode.setPosition(cc.v3(0,550,0));//设置该节点出现的位置labelNode.opacity =50;//初始透明度50cc.tween(labelNode) //缓动效果文字的缩放和透明度.to(0.3,{scale:1}).to(0.2,{opacity:0}).call(function(){labelNode.destroy();})//爆炸显示完之后,文本也销毁.start();}failed() {cc.log('脱靶!');this.dismiss();}dismiss() {this.node.destroy();}}
// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlimport Bullet from "./Bullet";const {ccclass, property} = cc._decorator;@ccclass
export default class Cannon extends cc.Component {//内部属性startPos : cc.Vec2 = null;startAngle : number = null;//炮口的角度//添加子弹Icon@property(cc.SpriteFrame)bulletIcon: cc.SpriteFrame = null;//爆炸效果图片@property(cc.SpriteFrame)explodeEffect: cc.SpriteFrame = null;// LIFE-CYCLE CALLBACKS:onLoad () {this.node.angle = 90; // 初始值设置为90度this.node.on('touchstart',this.onTouchStart,this);this.node.on('touchmove',this.onTouchMove,this);this.node.on('touchend',this.onTouchEnd,this);this.node.on('touchcancle',this.onTouchCancle,this);}start () {}// update (dt) {}onTouchStart(e:cc.Event.EventTouch) {// startPos :触点开始的位置 (将一个点转换到节点 (局部:父节点) 空间坐标系。)this.startPos = this.node.parent.convertToNodeSpaceAR(e.getLocation());// startAngle:炮口的初始角度(x轴方向为0度)this.startAngle = this.node.angle;}onTouchMove(e:cc.Event.EventTouch) {// 触点的当前位置let pos = this.node.parent.convertToNodeSpaceAR(e.getLocation());//摆动的角度,a.signAngle(b)即a向量与b向量之间的夹角。let sweep_radian = pos.signAngle(this.startPos);//弧度radian值,例如π/4let sweep_angle = 180 * sweep_radian / Math.PI; //弧度radian值 转化成 角度angle值/*炮口的新指向:比如,原来炮口90度,向右摆动15度,则炮口应该指向90-15=75度;比如,原来炮口90度,向左摆动15度,则炮口应该指向90-(-15)=105度; */let angle = this.startAngle - sweep_angle;//炮口的新角度// 炮口角度限制在45~135度之间if(angle < 45){angle = 45;}if(angle > 135){angle = 135;}cc.log("炮口摆动:" + sweep_angle+ ",修正后的角度:" +angle);this.node.angle = angle;}onTouchEnd(e:cc.Event.EventTouch) {this.fire();//鼠标松开,开火}onTouchCancle(e:cc.Event.EventTouch) {}   //开火 发射子弹fire() {if(this.bulletIcon==null){cc.log("请设置bulletIcon图片");return;}//炮口的指向,应是子弹的运行方向let angle : number = this.node.angle;let radian = angle * Math.PI/180; //角度值 转换成 弧度值let direction = cc.v2(Math.cos(radian),Math.sin(radian));//标准化向量//动态创建一个子弹 bulletNode,添加Sprite组件let bulletNode : cc.Node = new cc.Node(); //创建 子弹 节点let sprite : cc.Sprite = bulletNode.addComponent(cc.Sprite); // 创建 子弹节点的 精灵 组件sprite.spriteFrame = this.bulletIcon;//给精灵的精灵帧 赋值 子弹IconbulletNode.parent = this.node.parent; //指定父节点是 炮塔的父节点->射击系统//子弹的角度及初始位置bulletNode.angle = this.node.angle;//子弹的角度 赋值成 炮口的角度let r = 140;//子弹与射击基准的距离let bullet_x = r * direction.x;let bullet_y= r * direction.y;bulletNode.setPosition(cc.v3(bullet_x,bullet_y));//子弹的初始位置//给子弹(方向在炮塔)附加一个脚本组件Bullet.ts,控制子弹飞行的效果:距离、特效let bullet : Bullet = bulletNode.addComponent(Bullet);bullet.direction = direction; //子弹飞行的方向//添加子弹的子节点 target 靶子  和爆炸效果图bullet.target = cc.find('Canvas/靶子');bullet.explodeEffect = this.explodeEffect; //爆炸效果的图片需要传递}
}

17.8靶标的运动控制

添加新脚本靶子Target.ts;挂在靶子这个节点下面。

@ccclass
export default class Target  extends cc.Component {//运动方向,规定左为正方向isLeaf : boolean = true;onLoad () {}start () {}update (dt) {let dx : number = 3;//每一帧移动3pxif(this.isLeaf==true){//如果运动到最左端,就换方向dx = 0 - dx;   }this.node.x += dx;//判断移动的方向和最大距离if(this.isLeaf && this.node.x < -400){this.isLeaf = false;//如果向左移动超过200,则则向右移动}if( ! this.isLeaf && this.node.x > 400){this.isLeaf = true;//如果向右移动超过200,则向左移动}}
}

17.09弹药量显示

创建一个渲染节点->单色节点,自己选一个颜色,作为弹夹的背景颜色区域。 宽250,高50。

把弹夹的锚点设置在左方。

然后给弹仓下面附加一个脚本Magazine.ts。并添加图片。

为了使弹仓显示在左下角。可以给弹仓添加一个组件:

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.htmlconst {ccclass, property} = cc._decorator;// 弹匣@ccclass
export default class Magazine extends cc.Component {// 子弹图片@property(cc.SpriteFrame)bulletIcon : cc.SpriteFrame = null;capacity : number = 10; // 最大弹药capacity容量。count : number = 10; // 现有弹药数量onLoad () {// 子弹的水平间距let space: number = this.node.width / this.capacity;// 动态创建10个子弹。let i : number = 0;for(i =0; i< this.capacity ; i++){let bulletNode : cc.Node = new cc.Node();let bulletSprite  : cc.Sprite = bulletNode.addComponent(cc.Sprite);bulletSprite.spriteFrame = this.bulletIcon;this.node.addChild( bulletNode );//位置:bulletNode.x = space * i + 10; // 向右偏移一些bulletNode.y = 0;}}start () {}// update (dt) {}//下面的代码暂时还没有用到。 // 重置reset (){this.count = this.capacity;this.display();}// 消耗n个子弹consume ( n : number){this.count -= n;if(this.count < 0) this.count = 0;this.display();}// 显示剩余的子弹// active的表示剩下的子弹display (){let nodes : cc.Node[] = this.node.children;let i : number = 0;for(i=0; i< nodes.length ; i++){if(this.count > i)nodes[i].active = true;elsenodes[i].active = false;}}
}

17.10全局对象

方法一:

 //射出一颗子弹爆炸之后,减少一颗子弹的图片显示//简单方法:找到弹夹下面的组件Magazine,然后给方法comsume传递一个参数1。let magazine : Magazine = cc.find('Canvas/弹夹').getComponent('Magazine');magazine.consume(1);

这样写很直接,但是不优美。因为在其他地方也可能会用到Magazine这个对象。这样就会很多次的调用find和get方法。所以用下面这些方法:集中定义的方法,把需要使用的方法集中定义,然后初始化。

使用全局对象,把经常要访问的对象定义为全局对象,放在一个文件中。

方法二:

在文件夹script中 添加一个新的组件Common.ts设置全局对象。

新建一个一级节点:其他。(和Canvas平级。)然后在其他下面新建一个节点:游戏初始化,把Common.ts挂载这个节点下面。(当游戏加载的时候,会先加载Canvas,然后加载其他和下面的方法)

有三个步骤:

1 添加 Common.ts

import Magazine from "./Magazine";const {ccclass, property} = cc._decorator;@ccclass
export default class Common extends cc.Component {// 全局对象 ( 或者叫静态对象、静态引用)static magazine : Magazine = null;onLoad () {Common.magazine =cc.find('Canvas/弹夹').getComponent('Magazine');}}

2 初始化

(有两种方法。这里说的是其中一种。)

(1) 添加一个节点(其他->游戏初始化)

(2) 挂载Coomon.ts脚本

于是,当游戏加载该节点时,Common.onLoad()被调用

3 全局对象的调用(在Cannon.ts中调用)

17.11结果提示框

在Canvas下面创建一个节点“结果提示框”,设置大小,添加组件Widget。然后在节点下面创建单色节点作为子节点 “遮罩层”把游戏主画面遮住,并设置颜色、大小和透明度,添加组件Widget。

当提示框显示的时候,背后的游戏应该是不能操作的,需要添加拦截。

现在我们先做出效果,然后再加拦截。

把分数框图片添加到结果提示框下面,并调整大小和位置。

在结果提示框下面,创建一个文字节点,命名为 replay。文字内容是“再玩一局”。设置颜色和位置。

在结果提示框下面,单独加一个节点,作为分数。写入内容10分。调整颜色和位置。

在文件夹script中 新建ResultDialog.ts脚本,并添加屏蔽。


const {ccclass, property} = cc._decorator;@ccclass
export default class ResultDialog extends cc.Component {//找到replay节点,响应方法dimiss,关掉结果提示框onLoad () {let replayNode : cc.Node = cc.find('replay',this.node);replayNode.on('touchstart',this.dismiss,this);//添加屏蔽this.node.on('touchstart',this.onTouchDisable,this);this.node.on('touchmove',this.onTouchDisable,this);this.node.on('touchend',this.onTouchDisable,this);}start () {}//显示提示框show(){this.node.active = true;}//隐藏提示框dismiss() {this.node.active = false;}//停止传递当前事件onTouchDisable( e : cc.Event.EventTouch ) {e.stopPropagation();}// update (dt) {}
}

17.12得分统计

每一发子弹有10分,10发子弹满分100分,每次击中则记录加10分。这需要一个全局变量,记录一个玩家在一局中所得的分数。步骤如下:

1 添加一个全局变量score得分。

Common.ts


import Magazine from "./Magazine";
import ResultDialog from "./ResultDialog";const {ccclass, property} = cc._decorator;@ccclass
export default class Common extends cc.Component {// 全局对象 ( 或者叫静态对象、静态引用)static magazine : Magazine = null;//得分统计static score : number = 0;//结果提示框static resultDialog : ResultDialog = null;onLoad () {Common.magazine =cc.find('Canvas/弹夹').getComponent('Magazine');Common.resultDialog =cc.find('Canvas/结果提示框').getComponent('ResultDialog');}}

2 命中时加分

Bullet.ts

success(){

this.explode();

this.cheer();

// 得分

Common.score += 10;

}

3 结局判断  (结果提示框也要加入全局变量。)

Bullet.ts

dismiss(){

this.node.destroy();

//如果10发子弹用完,游戏结束,显示 结果提示框。

if(Common.magazine.count <= 0) {

Common.resultDialog.show();

}

}

4 得分显示

ResultDialog.ts

show(){

this.node.active = true;

// 显示最终得分

let scoreNode : cc.Node = cc.find('分数', this.node);

let scoreLabel : cc.Label = scoreNode.getComponent(cc.Label);

scoreLabel.string = Common.score + '分';

}

17.13重新开始游戏

17.14音效及其他

Cocos Creator游戏开发教程 学习笔记相关推荐

  1. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(一)前言,界面UI

    前言 这个是我去年3月份在简书上发布的,不玩简书了,就迁到CSDN吧-- 最近遇到一款游戏,感觉玩起来还行,于是顺带就用来熟悉一下Cocos Creator(太久没用). 项目地址已放到 github ...

  2. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(三)代码实现

    [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(一)前言,界面UI [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(二)物理刚体关节 项目地址已放到 github ...

  3. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(二)物理刚体关节

    [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(一)前言,界面UI 项目地址已放到 github 上,需要的小伙伴可自行下载. 这节我们讲一下车子的物理刚体关节. 我在项目中添加了一 ...

  4. Cocos Creator 游戏开发常见问题(第一期)

    Cocos Creator 游戏开发常见问题(001) 1.creator 电脑双屏问题能不能修一下 2.Label 下一帧才刷新大小 怎么办 3.CocosCreator调试预览的时候,如何设置不显 ...

  5. 微信小游戏 腾讯课堂《白鹭教育 - 成语大挑战小游戏开发》学习笔记

    腾讯课堂<白鹭教育 - 成语大挑战小游戏开发>:https://ke.qq.com/course/287266 学习笔记 首先这个视频课程简单介绍了一个小游戏的制作,包括新建项目.适配模式 ...

  6. 【Cocos Creator 游戏开发】开发日志-前言

    启 断断续续三四年,每年也只看一点点Cocos Creator的知识点.正好最近想用其他的东西填满自己的思绪,就开始深入研究基于Cocos Creator的游戏开发吧. 念 个人对3D游戏开发不太熟悉 ...

  7. Unity3D游戏开发入门学习笔记

    学习内容概要: 软件面板功能.材质球.预制体.摄像机.灯光.鼠标键盘输入.组件.刚体.碰撞体.PC端游戏打包发布.... 第1课:课程介绍与Unity3D环境搭建 1.Unity3D,一个游戏开发引擎 ...

  8. 2011斯坦福大学iOS应用开发教程学习笔记(第一课)MVC.and.Introduction.to.Objective-C

    2011年冬季斯坦福大学公开课 iOS应用开发教程是个很经典的教程,这个老头讲的很给力.做笔记总结. 第一课名称: MVC and Introduction to Objective-C 这课的主要内 ...

  9. 《3D数学基础:图形与游戏开发》 学习笔记(一)

    (以下学习笔记为本人最近在学习本书的时候所记载,之中还加入了一些做项目过程中遇到的问题,以及相关知识的补充.笔者水平有限,文中不足之处,还请给予指正,谢谢~) 1.将左手坐标系变换到右手坐标系,只需改 ...

最新文章

  1. 讽刺笑话_完全不讽刺的2019年网络设计指南
  2. 44种模型、1200种子网,RobustART评测CNN、Transformer、MLP-Mixer谁最鲁棒?
  3. CTFshow 反序列化 web257
  4. 【Scratch】青少年蓝桥杯_每日一题_2.01_画五角星
  5. python有哪些插件_Python和它高大上的插件们
  6. static和extern的用法总结
  7. c# 修改xslt并转为html,c#使用XSLT将xml文档转换为html文档
  8. 3K和3w的月薪的程序员,差别在哪里?
  9. ip地址互转十进制数字(函数)
  10. wordpress雪花下雪WP Snow Effect插件
  11. 小熊派IoT开发板系列教程正式发布——免费学习
  12. 【华为云技术分享】[HDC.Cloud]基于鲲鹏平台的Ceph深度性能调优
  13. 初学Golang:Go 的数据类型及常见特性
  14. python-绘制双轴柱状图
  15. 俄罗斯方块、纯前端实现俄罗斯方块、俄罗斯方块代码
  16. matlab mpopt,matpower安装到MATLAB下遇到的问题
  17. Backtrader量化回测8——手续费
  18. 女生适合学大数据开发吗,女生怎样学大数据开发
  19. WPF MVVM Page 页面导航实例
  20. linux和aix命令总结

热门文章

  1. 去水印的手机APP哪个好用,怎么一键去水印
  2. rails rjs select method help
  3. MySQL 数据库之实现热备份
  4. 成功解决ValueError: pos_label=1 is not a valid label: array([‘0‘, ‘1‘], dtype=‘<U1‘)
  5. cpu和gpu各自的作用
  6. 【《Unity 2018 Shaders and Effects Cookbook》翻译提炼】(九)Physically - Based Rendering
  7. Python快速计算函数耗时timeit
  8. nyoj 105 九的余数
  9. 秦观 满庭芳-山抹微云 改阳韵
  10. 用 Python 实现浪漫表白程序