因为position是相对坐标,所以原点是父节点的锚点 。所以Canvas下面的直属节点原点就是世界坐标系的原点Canvas的锚点。
给节点添加音频资源:
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 >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;
}
}
直接换图片:
图片放在资源管理其中;
添加属性加载图片帧:
// 两种状态的图片帧
@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。
打开之后就是这个:
场景Scene
资源 Asset(Texture 图片素材;audioClip 音频素材;)
把图片拖拽进入Canvas下面。
Main Scene场景文件中使用json代码保存了图片的路径和名称。
疑惑:缩放和矩形变换有什么区别?
在属性检查器中设置才是一个精确的数值。
如下图,图片的坐标轴原点就是锚点(Anchor)。
父节点(Canvas)、子节点(佩奇、Main Camera)。
佩奇的坐标是相对于世界坐标系的原点Canvas的(480,320)这个点来定坐标的。佩奇的position和Canvas这个点重合时,position=(0,0)。
两种方式任选其一。
设置默认浏览器:
浏览器运行之后这里也可以选择设置中已有的功能。
在右侧添加组件,这样就可以看到Sprite。
操作步骤:
拖拽:鼠标移动到节点上,然后拖动到Sprite Frame就可以了,不要单击。
右侧也可以选择添加组件来调整文本节点的细节:字体、颜色、大小、等等。
鼠标移到文字上:能显示这个属性的具体描述
创建一个输入框:
一些输入框的名字。
在右侧的“添加组件”中选择。 渲染类型组件只能选择一个。
TypeScript其实就是JavaScript的加强版。
解释: JavaScript是无类型的,所以在编写的时候没有工具提示,比较麻烦。所以就发明了强类型的TypeScript,具有工具提示。
按照默认的VS Code 安装步骤,安装之后,在cocos中配置VS Code,浏览,然后搜索 VS Code,找到Code.exe。点击保存。
新建游戏脚本,就会自动打开VS Code。
假如输入状态紊乱,那么可以切换一下中文,或者切换到其他窗口,再切回来。
步骤演示:
创建文件夹,然后点击选中图片佩奇,在右下角添加组件——用户脚本组件——Pigscript。
选中之后会出现蓝色的边框。
在cocos中不能直接编辑脚本,需要双击这个脚本,就会自动打开VS Code脚本窗口编辑。
在VS Code中编辑完成之后,Ctrl+S保存,cocos会自动刷新右侧的代码内容。
双击打开脚本之后先关闭脚本窗口(因为这样打开的窗口,直接进行编辑有一个BUG,所以需要先把这个脚本关闭),然后在右侧重新找到代码打开,再进行编辑。
选择模拟器,还有下方的Resume script execution按钮(继续脚本执行),才能出现打印的日志。
伪代码理解:
生命周期回调 · Cocos Creator https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
方法名不加()。并不是要调用它。
官网地址: TypeScript: JavaScript With Syntax For Types. https://www.typescriptlang.org/zh/
加了@property之后,用户脚本组件的PigeScript中就会自动加上这个time属性。开发者就可以从这个属性面板中进行初始化设置。但是这里的修改不会影响代码,只会影响编译结果。
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
@property(cc.Label)
label: cc.Label = null;
// 每一步的大小,单位为像素。
@property
step : number = 20 ;
//行走方向
@property
towardsLeft : 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脚本的代码。
全部代码:
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
@property(cc.Label)
label: cc.Label = null;
// 每一步的大小,单位为像素。
@property
step : number = 20 ;
//行走方向
@property
towardsLeft : 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 这个属性框中进行添加。
操作结果:点一下测试按钮,就能把名字放到佩奇的脚下。(点击测试按钮之前,名字在佩奇的头上。)
创建新脚本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。
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);//获取节点的组件Lable
label.string = "小猪佩奇";//给属性string赋值
}
// update (dt) {}
}
为节点“名字”添加组件SimpleScript。
在按钮的组件ButtoScript中找到节点“名字”并调用它的组件SimpleScript中的方法doChange()。
方法doChange():每次,点击测试按钮,都让节点“名字”的y位置的值为相反值。
步骤如下:
效果如图:
制作图片按钮:
Label代表的就是按钮的文字button,可以删掉button换成图片。
直接把图片拖动到Background下面就好了,右边就会直接显示这个图片。还可以修改按钮在不同鼠标状态下的的背景颜色。
右按钮可以直接点击复制左按钮,然后修改名称并替换图片获得。然后移动位置即可。
(如果想要调节两个按钮之间的间距,需要先调节间距,再调节水平对齐的状态。)
或者把背景图片勾掉禁用,暂时不显示,然后利用网格来实现对齐。
点击佩奇这个组件,然后把这个脚本直接拖动到添加组件按钮里面,就可以直接添加脚本。
Ctri+S保存脚本,然后看看这个脚本。
运行浏览器,看看这个脚本:需要先点击一下我们的游戏页面,让游戏捕捉到鼠标,然后点击键盘上的左右键才会出现打印日志。
// 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 {
@property(cc.Label)
label: cc.Label = null;
@property
text: 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) {}
}
// 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 {
// @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 ? '左' : '右'));
}
}
图文教程如下:
第一种方式:直接翻转(需要根据初始图片脸的朝向 分析代码)
this.node.scaleX = 0 - this.node.scaleX;
比如,原来节点的 scale 为 (0.7 , 0.7 ) ,翻转后为 (-0.7, 0.7)。
但这并不是一个好办法,因为直正的人可能比较复杂,翻转之后可能穿帮。
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;
}
}
第二种方式:(不需要判断初始图片脸的朝向。直接换图片,然后移动位置就可以了。)
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; //需要图片脸朝右
}
}
实现左右移动的按钮:
想办法在按钮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.html
const {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.html
const {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;
@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();
}
//播放脚步声音频
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
三维坐标:
// 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 {
@property(cc.Label)
label: cc.Label = null;
@property
text: 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));
}
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();
}
文档--》 功能模块--》缓动系统。 缓动系统 · 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/佩奇_右');
//easing
cc.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();
}
导入示例:
import { TweenEasing } from "cc";
内置缓动函数的字符串值定义。
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
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.
运行:篮球掉落到地平线上。
创建这个文件夹列表,然后把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像素。
}
}
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) {}
// 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;
//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);
}
}
使用计时器 · Cocos Creator https://docs.cocos.com/creator/manual/zh/scripting/scheduler.html
下面是 Component 中所有关于计时器的函数:
计时器的应用:
// 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 ;
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.html
const {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;//每次更新组件label
text : string = null; //按照text的值显示字段的长度并赋值给组件label中的string
index : number = 0;//指定字段的长度最大值
onLoad () {
/*获得这个代码绑定的文字节点中的其他组件Label,
并赋值给全局变量cc.Label类型的label。 */
this.label = this.getComponent(cc.Label);
//获取全局变量cc.Label组件的label的string值,赋值给text
this.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) {}
}
然后再创建一个纯色的背景,并修改相关的参数:
调整缩放Scale让小车也变小一点。
因为我们的游戏最终都是要放到手机上进行的(所以不采用原来单一的mousedown等(鼠标类型))。换成touch~等事件,可以使用鼠标或者触摸屏操作。
把脚本GamePad.ts添加到手柄下面,作为组件:
(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:
_getCapturingTargets
), e.g. parents in node tree, from root to the real target_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 方法触发,触发的过程包含三个阶段:_getCapturingTargets
获取),比如,节点树中注册了捕获阶段的父节点,从根节点开始派发直到目标节点。_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()方法:
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 设置节点在父节点坐标系中的位置。 可以通过下面的方式设置坐标点:
@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.html
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) {}
}
世界坐标是Canvas的原点,本地坐标是游戏摇杆的底盘原点。
单位圆的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.html
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()为触摸点的位置,是世界坐标
//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 * cos
pos.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可以看出,以上图是正确的:(因为两张图不是一次截的,有一点是后期修改了,请不要介意。因为数据是对的。)
关于角度的解释,以下为正确解释:
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.html
const {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 * cos
pos.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) {}
}
新建并添加脚本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.html
const {ccclass, property} = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {
//每次刷新小车移动的距离
@property
speed :number = 3;
//小车移动的方向
@property
direction : 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.html
const {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 * cos
pos.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) {}
}
在PS中打开就能看到这些图片。
2.把这0~11的12张图片加入到项目中。
3.选中图片,然后把属性检查器中的Sprite中的Trim Type(裁剪类型)属性设置为None,然后点击应用。每一张图片都要设置。(因为每一张图片自动裁剪的大小不一样,使用不是方便。)
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.html
const {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点击回车,就会出现下列数组,就可以逐一添加图片啦。
就可以了:
图集资源 · Cocos Creator https://docs.cocos.com/creator/manual/zh/asset/atlas.html
Atlas的制作教程:
文档中写的这两个都是外国的收费软件。百度搜索,然后点击进入官网。
下载这个试用版:
把所有图片一张张拖到中间这个小人的区域:
一般情况下 希望不要修剪,修剪模式改成“没有”(修剪的话合成的图片更小但是可能裁剪掉一些东西):
设置好之后,点击“发布精灵表”就可以得到.plist文件和.png文件
在plist中代码描述,大图中的内容和每一张小图的位置和相关信息。
可以在飞羽图集中看到那时12张图片。
现在改成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.html
const {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) {}
}
把脚本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.html
const {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 = 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.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 =
});
(2) 类型转换
assets是cc.Asset类型,使用类型转换
self.node.getComponent(cc.Sprite).spriteFrame =
获取和加载资源 · 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
cc.resources.load(paths,callback(err,assets){ ... })
其中,paths类型[cc.String](路径),assets类型[cc.Assets](数组),
我们可以使用paths指定一个路径或者一个路径的数组。如果我们指定一个路径的数组,那么在回调方法callback(err,assets){ ... }中就会传回一个assets数组。
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.html
const {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);
}
}
节点事件系统 · 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
只点击“汽车”或者“停车标志”会上浮到父节点“道路”:日志打印了被点击的子节点和上浮到的父节点。
加上 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 停止传递当前事件。
}
把脚本附加给遮罩这个节点。
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。
可以发现,调整之后:场景编辑器中的遮罩范围自动适应了整个相机大小。且运行后效果也是满屏遮罩。
在提示框下面添加单色节点“遮罩层”,设置不透明度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;
}
}
用于实现遮罩层出现之后,使得遮罩层后面的内容不能再被点击。这个阻止输入事件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; //推断一次运行游戏的移动过程中,调用计算了多少次TouchMove
onLoad () {
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坐标 = 两点之间的间距distance
let 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();
}
}
添加单色背景:画布Canvas》创建节点》创建渲染节点》Sprite单色,设置:颜色color淡蓝色,背景大小960X640(和我们的画布Canvas分辨率相同。),还要添加UI组件Widget,使得背景自适应屏幕画布。
添加炮塔节点:直接拖动图片拖上来。然后调整一下大小就可以了。
子弹需要动态创建,所以不需要创建。
创建脚本 CannonSprite.ts,加进节点“炮塔”中,然后把子弹图片添加进“Bullet Icon”子弹图标属性中。点击炮塔之后,子弹出现的效果如下图:
@ccclass
export default class NewClass extends cc.Component {
//添加新属性,把子弹图片添加去。
@property(cc.SpriteFrame)
bulletIcon : cc.SpriteFrame = null; //子弹Icon
onLoad () {//点击炮塔
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) {}
}
先创建一个脚本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; //子弹Icon
onLoad () {//点击炮塔
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) {}
}
打开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; //子弹Icon
onLoad () {//点击炮塔
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);
//把爆炸效果图片交给“子弹脚本”script
script.explodeEffect = this.explodeEffect;
}
// update (dt) {}
}
效果:
会移动的靶标,加农炮的炮口会随着鼠标的方向转动,点击出现射击的子弹。射中的时候出现爆炸的效果,没射中就只是出现子弹射出。一共有10发子弹。
先把需要的材料加入资源管理器。然后把“雪地背景”、“靶子”拖动上去到Canvas下面作为节点。适当调整这两个图片的大小(“雪地背景”:960X240px,加:UI组件->widget。或者只是调整大小到覆盖整个Main Camera。)。新建一个空节点,命名为射击系统。然后把半圆和炮塔都拖动到这个节点下方。
大致位置在中间(x=500),然后把加农炮的基准点调整到中心(使用矩形变换工具或者调整Anchor数值)。
效果如下图所示:
新建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值,例如π/4
let 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) {}
}
在Cannon.ts中添加代码:
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值,例如π/4
let 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;//给精灵的精灵帧 赋值 子弹Icon
bullet.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,简直正中间,完美。
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() {
//靶标与射击基准之间的距离400
if(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值,例如π/4
let 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;//给精灵的精灵帧 赋值 子弹Icon
bulletNode.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; //子弹飞行的方向
}
}
效果如图所示:
射击基准和靶标之间的距离要等于代码中写的是400。如图所示,还不是靶心。子弹还有长度,再调多一点到100,正好到靶心。
在Bullet.ts中进行检测:
在Cannon.ts中加上:
但是这样也一点不准确。看来和半径没有关系。还是需要计算子弹的延长线和最终的终点才好。(试图计算,思路:计算靶标圆心和子弹延长线的终点(不会算),这两个点之间的距离小于靶标半径100,判定命中靶标。)调节射击系统和靶子的位置,然后让子弹弹头基本上可以垂直射中靶心。
如下代码,表示射在这条红线上是命中。(这就从视觉上比较精确了。)
在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.html
const {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() {
//靶标与射击基准之间的距离400
if(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;//初始透明度50
cc.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.html
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;
//爆炸效果图片
@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值,例如π/4
let 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;//给精灵的精灵帧 赋值 子弹Icon
bulletNode.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; //爆炸效果的图片需要传递
}
}
添加新脚本靶子Target.ts;挂在靶子这个节点下面。
@ccclass
export default class Target extends cc.Component {
//运动方向,规定左为正方向
isLeaf : boolean = true;
onLoad () {}
start () {}
update (dt) {
let dx : number = 3;//每一帧移动3px
if(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,则向左移动
}
}
}
创建一个渲染节点->单色节点,自己选一个颜色,作为弹夹的背景颜色区域。 宽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.html
const {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;
else
nodes[i].active = false;
}
}
}
方法一:
//射出一颗子弹爆炸之后,减少一颗子弹的图片显示
//简单方法:找到弹夹下面的组件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中调用)
在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) {}
}
每一发子弹有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 + '分';
}