Cocos 2D

text::TypeScript

official::Cocos Creator手册

official::CocosAPI

源码路径:\Cocos\Creator\3.8.0\resources\resources\3d\engine


一、引擎基础

1 初始配置

初始配置:

  • 设置中文:File——Preferences——General——Language
  • 默认编译器:File——Preferences——Program Manager——Default Script Editor
  • 默认浏览器:File——Preferences——Program Manager——Default Browser

竖屏游戏:

  • Project——Project Setting——Design Width/Height分别为720/1280

  • 勾选Fit Width和Fit Height

Vscode:

  • 文件后缀:文件——首选项——设置——文本编辑器——文件——Exclude——新加**/*.meta

2 界面

类似Untiy。

3 机制

渲染机制:需要一个标识来证明节点状态发生改变了才会重新读取信息,否则不渲染。

函数重载:函数内部通过多个if else对入参进行判断来实现重载。

// 渲染机制
// 不触发渲染
update(deltaTime: number){
this.node.position.x += 1;
}
// 触发渲染
update(deltaTime: number){
const p = this.node.position;
p.x += 1;
this.node.position = p;
}

4 项目目录结构

文件夹:

  • 资源文件夹(assets):存放所有的本地资源、脚本和第三方库文件。assets 中的每个文件在导入项目后都会生成一个相同名字的 .meta 文件,用于存储对应的资源配置和索引信息。

  • 资源库(library):library 是将 assets 中的资源导入后生成的,在这里文件的结构和资源的格式将被处理成最终游戏发布时需要的形式。当 library 丢失或损坏的时候,只要删除整个 library 文件夹再打开项目,就会重新生成资源库。

  • 本地设置(local):local 文件夹中包含该项目的本机上的配置信息,包括编辑器面板布局,窗口大小,位置等信息。开发者不需要关心这里的内容。

  • 扩展插件文件夹(packages):packages 文件夹用于放置此项目的自定义扩展插件。如需手动安装扩展插件,可以手动创建此文件夹。如需卸载扩展插件,在 packages 中删除对应的文件夹即可。

  • 项目设置(settings):settings 里保存项目相关的设置,如构建发布菜单里的包名、场景和平台选择等。

  • 临时文件夹(temp):temp 是临时文件夹,用于缓存一些 Cocos Creator 在本地的临时文件。这个文件夹可以在关闭 Cocos Creator 后手动删除,开发者不需要关心这里面的内容。

  • project.json:project.json 文件和 assets 文件夹一起,作为验证 Cocos Creator 项目合法性的标志,只有包括了这两个内容的文件夹才能作为 Cocos Creator 项目打开。开发者不需要关心里面的内容。

  • 构建目标(build):在使用主菜单中的 项目 -> 构建发布… 使用默认发布路径发布项目后,编辑器会在项目路径下创建 build目录,并存放所有目标平台的构建工程。

5 内置属性

// SpriteFrame:精灵框架
@property([SpriteFrame]) cardsSF: SpriteFrame[] = [];

// Number是设置
@property({type: Number}) public hp: number;

二、常见控制组件

1 Canvas

默认的Canvas的左下角为世界原点。

对于在Canvas中的物体,当Position为(0,0)时是处在画布中心。

在Canvas中,越下面的组件图层越在前。

2 Node

位置,旋转,缩放。

Mobility:节点的可移动性。不同的可移动性会导致节点在光照上有不同的特性和表现。

Layer:层级。

official::节点和组件文档

3 Camera

摄像机。


三、常见UI组件

official::UI 组件

1 cc.Label

文本组件。

official::Label 组件参考

official::字体资源

// 文本赋值
const label = this.node.getComponent(Label);
label.string = "Hello World";

2 cc.Sprite

图片组件。

属性 功能说明
CustomMaterial 图片材质
Color 颜色
Sprite Atlas Sprite 显示图片资源所属的 Atlas 图集资源。
Sprite Frame 渲染 Sprite 使用的 SpriteFrame 图片资源。
Grayscale 灰度渲染模式
Size Mode 指定 Sprite 的尺寸,包括自定义尺寸(CUSTOM),裁剪透明尺寸(TRIMMED),不裁剪(RAW)
Type 渲染模式,包括普通(SIMPLE)、九宫格(SLICED)、平铺(TILED)、填充(FILLED)
Trim 勾选后将在渲染时去除原始图像周围的透明像素区域,该项仅在 Type 设置为SIMPLE 时生效。
Fill — 填充方式,该项仅在 Type 设置为 FILLED 时生效。

3 cc.Button

按钮组件。

4 cc.UIOpacity

透明度。0透明,255不透明。

5 cc.Layout

布局组件。

Type:横向,纵向,矩阵。

Affected By Scale:子节点缩放影响布局(当子节点物体缩放后,布局会动态调整间隔,否则默认是缩放为1时的间隔)

6 cc.BoxCollider2D

碰撞。

Sensor:触发器。

7 cc.Rigidbody2D

刚体。

Enabled Contact Listener:只有开启了刚体的碰撞监听,刚体发生碰撞时才会回调到对应的组件上

Type:静态,动态,动力学,动画(动力学的衍生)

8 关节组件

official::2D 物理关节

DistanceJoint2D:距离关节。将关节两端的刚体约束在一个最大范围内。超出该范围时,刚体的运动会互相影响。

FixedJoint2D:固定关节。根据两个点将两个物体固定在一起。

HingeJoint2D:铰链关节。刚体会围绕一个共同点来旋转(自身中心点要拉至对方中心点位置)。

MouseJoint2D:可以用鼠标拖拽物理

RelativeJoint2D:相对关节。控制两个刚体间的相对运动。(双偷盗宝)

SliderJoint2D:滑动关节。两个刚体位置间的角度是固定的,它们只能在一个指定的轴上滑动。

SpringJoint2D:弹簧关节。将关节两端物体像弹簧一样连接在一起。

WheelJoint2D:轮子关节。模拟车轮。


四、资源

1 图片

official::图集资源

Sprite-frame:

  • Trim Type:默认auto,自动裁剪透明像素。选择None不裁。

Texture:

  • Filter Mode:图片处理方式
    • Nearest(None):像素图片处理。
    • Bilinear:一般图片处理。

2 声音

概念

official::AudioSource 组件参考

offcial::Web Audio和DOM Audio的兼容性说明

属性

// 声音片段
@property(AudioClip) bgmClip: AudioClip = null;
// 声音资源
_audioSource: AudioSource = null;

方法

// 新建对象
this._audioSource = new AudioSource();
// 绑定片段
this._audioSource.clip = this.bgmClip;
// 声音大小
this._audioSource.volume = 0.5;
// 设置循环
this._audioSource.loop = true;
// 播放
this._audioSource.play();

案例

// 循环播放bgm
initBgm() {
if (this.bgmClip) {
this._audioSource = new AudioSource();
this._audioSource.clip = this.bgmClip;
this._audioSource.loop = true;
this._audioSource.play();
}
}

3 粒子

official::2D 粒子

Particle Designer:粒子效果制作器

在线粒子编辑器

const {ccclass, property} = cc._decorator;

@ccclass("ParticleController")
export default class ParticleController extends cc.Component {

@property(cc.ParticleSystem2D)
particleSystem2D: cc.ParticleSystem2D = null;

start() {
// 初始化粒子系统
this.particleSystem2D.resetSystem(); // 将粒子系统重置到初始状态
}

// 播放粒子效果
playParticle() {
this.particleSystem2D.play();
}

// 暂停粒子效果
pauseParticle() {
this.particleSystem2D.pause();
}

// 销毁粒子系统
destroyParticle() {
this.particleSystem2D.node.destroy(); // 销毁整个粒子系统节点
}
}

4 骨骼动画

Spine官网: 专注于游戏的2D动画软件 (esotericsoftware.com)

传统的动画:一般是对一个物体对象进行位移、旋转、缩放、变形,然后把关键帧的信息记录下来,在播放的时候按照关键帧时间对物体对象进行位移、旋转、缩放、变形,并在关键帧与关键帧之间做插值运算。

骨骼动画的特点:需要做动画的物体对象本身不记录位移、旋转、缩放、变形信息,而是通过了第三方的“骨骼”物体记录动画信息,然后物体对象本身只记录受到骨骼物体影响的权重。在播放的时候,通过骨骼物体的关键帧和物体对象记录的权重,让动画重现。

骨骼动画好处:

  1. 骨骼动画是影响到顶点级别的动画,而且可以多根骨骼根据权重影响同一个顶点,不论是2D或者3D使用,都可以让动画做得更丰富。
  2. 做到了对象和动画分离,我们只需要记录了物体对于骨骼的蒙皮权重,就可以单独的去制作骨骼的动画,在保证蒙皮信息和骨骼信息一致的情况下,还可以多个物体之间共享动画。
  3. 相对于2D的逐帧动画大大的节省资源容量。

骨骼动画所需资源有:

  • .json/.skel:骨骼数据,骨骼数据以及动画数据
  • .png:图集纹理,切割好的图片
  • .txt/.atlas:图集数据,管理皮肤贴图

使用时,拖动.json文件到层级管理器或场景中即可。

5 预制件

方法

// 加载预制件
const fab = instantiate(Prefab);

案例

const {ccclass, property} = cc._decorator;

@ccclass('PrefabExample')
export default class PrefabExample extends cc.Component {

@property(cc.Prefab)
prefab: cc.Prefab = null;

// 实例化预制体
createPrefab() {
let newNode = cc.instantiate(this.prefab);
this.node.addChild(newNode);
}

// 访问预制体中的组件
accessPrefabComponent() {
let prefabNode = cc.instantiate(this.prefab);
let customComponent = prefabNode.getComponent("CustomComponent");
if (customComponent) {
customComponent.customFunction();
}
}

// 克隆预制体
clonePrefab() {
let clonedPrefab = cc.instantiate(this.prefab);
this.node.parent.addChild(clonedPrefab);
}

// 销毁预制体
destroyPrefab(prefabNode: cc.Node) {
prefabNode.destroy();
}
}

6 Resource

official::资源加载

动态加载资源,资源需要放在resources文件夹下

方法

// 加载资源(路径,名称,处理函数(错误,资源名称))
resources.load("test_assets/prefab", Prefab, (err, prefab) => {
const newNode = instantiate(prefab);
this.node.addChild(newNode);
});

// 加载资源
resources.load("test_assets/anim", AnimationClip, (err, clip) => {s
this.node.getComponent(Animation).addClip(clip, "anim");
});

// 如果动态加载出来的资源需要长期引用、持有,或者复用时,建议使用 addRef 接口手动增加引用计数。
resources.load('images/background/spriteFrame', SpriteFrame, function (err, spriteFrame) {
self.getComponent(Sprite).spriteFrame = spriteFrame;
spriteFrame.addRef();
});

// 释放资源(两者一起)
this.spriteFrame.decRef();
this.spriteFrame = null;

五、脚本

1 新建脚本

// 从 'cc' 模块中导入 _decorator、Component 和 Node 等符号
import { _decorator, Component, Node } from 'cc';
// 解构_decorator对象中的值
const { ccclass, property } = _decorator;

// 组件类装饰器,用于将下面的类标记为一个 Cocos Creator 组件类,并且指定了组件的名称为 'Test'
@ccclass('Test')
// 继承Component类
export class Test extends Component {
// 组件实例被激活时调用
start() {

}
// 每一帧更新时调用,deltaTime为1 / 帧数
update(deltaTime: number) {

}
}
// 类的设计
@ccclass('Test')
export class Test extends Components {
// 值
_value: number;
label: Label;

// 属性
get value(){
return this._value;
}
set value(val){
this._value = val;
}

// 构造函数
constructor() {
super();
// 可以在构造函数中进行初始化操作
this._value = 10;
}

// 方法
start() {
this.label = this.node.getComponent(Label);
this.label.string = this.value.toString();

}

update(deltaTime: number) {
this.value += 1;
this.label.string = this.value.toString();
}
}

2 自定义属性

import {CCInteger, CCFloat} from 'cc';
@ccclass('GameRoot')
export class GameRoot extends Component {
// 加了@property的属性会在Inspector中显示,括号中表示类型。
@property(Node) button:Node;
// 精灵框架数组
@property([SpriteFrame]);

@property(CCInteger) HP:Number;
// 私有属性加了也不会显示
@property(CCFloat) _MP:Number;
// 不加不会显示
Exp:Number;


start() {}

3 生命周期

official::生命周期回调

// 组件脚本的初始化阶段,回调会在节点首次激活时触发
onLoad(){}

// enabled或active属性从false变成true
onEnable(){}

// 组件第一次激活前,也就是第一次执行update之前触发
start(){}

// 每一帧渲染前更新物体的行为,状态和方位
update(){}

// 用在动效完成后进行额外操作
lateUpdate(){}

// enabled或active属性从true变成false
onDisable(){}

// 当组件或者所在节点调用了destroy(),则会调用onDestroy回调
onDestroy(){}

4 组件节点操作

// 获取指定组件(name是组件名)
const comp = this.node.getComponent(name);
// 获得子物体
let children = this.node.children; // 所有
let comp = this.node.getChildByName("name"); // 单个
// 获得子物体数量
let childCount = this.node.childrenCount;
// 添加子物体
this.comp.addChild(egg);


// 全局名字查找(参数1:节点路径。参数2:查找起始位置,不写就是根)
let node = cc.find("Weapon"[,this.node]);
let node = cc.find("Player/Weapon"[,this.node]);
let node = cc.find("Canvas/Player/Weapon"[,this.node]);


// 更改父节点
this.node.parent = parentNode;
// 移除当前父节点
this.node.removeFromParent();


// 激活/关闭节点
this.node.active = true / false;

5 基础属性操作

属性

// 位置
this.node.position;
// 旋转(四元数)
this.node.rotation;
// 缩放
this.node.scale;

方法

// 使用三维向量要导入类
import {Vec3} from 'cc';

// 获得坐标
this.node.getPosition(Vec3);

// 设置坐标
this.node.setPosition(x,y[,z]);
this.node.setPosition(new Vec3(x,y,z));

// 设置角度(2D)
this.node.angle += 1;

案例

// 不能按如下方式直接赋值坐标
update(deltaTime: number){
this.node.position.x += 1;
}
// 可以按如下方式直接赋值坐标
update(deltaTime: number){
const p = this.node.position;
p.x += 1;
this.node.position = p;
}

// 性能优化设置位置(防止多次new对象)
curPos = new Vec3();
update(deltaTime: number){
this.node.getPosition(this.curPos);
this.curPos.x += 100 * deltaTime;
this.node.setPosition(this.curPos);
}

6 时空控制

方法

// 装载计时器
this.schedule(func, interval);
// 卸载计时器
this.unschedule(func);


// 加载场景
director.loadScene("scene");
// 预加载场景(空间换时间,防止卡顿)
director.preloadScene("table", function () {
cc.log("Next scene preloaded");
});
// 设置常驻节点
game.addPersistRootNode(myNode);
// 销毁常驻节点
game.removePersistRootNode(myNode);


// 暂停游戏循环,包括渲染和更新逻辑。
director.pause();
// 恢复游戏
director.resume();
// 结束游戏
director.end();

// 暂停游戏逻辑更新,不会影响渲染
game.pause();
// 恢复游戏
game.resume();
// 结束游戏
game.end();

案例

// 更新计时器间隔时间
startCreateEggs(gap: number){
this.unschedule(this.startOneEgg);
this.schedule(this.startOneEgg, gap);
}

// 按钮绑定重开游戏(游戏应该是用director.pause()暂停的)
bindRestartEvent(){
this.restartButton.node.on(Node.EventType.TOUCH_END, () => {
director.resume();
director.loadScene("scene");
});
}

// 按钮绑定结束游戏
bindExitEvent(){
this.exitButton.node.on(Node.EventType.TOUCH_END, () => {
director.end();
});
}

7 事件

official::事件系统

属性

// 鼠标按下时
Node.EventType.TOUCH_START
// 鼠标移动时
Node.EventType.TOUCH_MOVE
// 鼠标在控件内离开时
Node.EventType.TOUCH_END
// 鼠标在控件外离开时
Node.EventType.TOUCH_CANCEL

方法

// 绑定事件
this.node.on(event, func, this);
input.on(Input.EventType, func, this);

// 卸载事件
this.node.off(event, func, this);
input.off(Input.EventType, func, this);

// 项目元素实际位置
const uiPos = event.getLocation();
const delta = event.getDelta();
const dx = event.getDeltaX();
const dy = event.getDeltaY();

// 元素在UI上的位置
const uiPos = event.getUILocation();
const delta = event.getUIDelta();
const dx = delta.x;
const dy = delta.y;

案例

// 绑定事件(EventTouch)
import {EventTouch} from 'cc';
start(){
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
}
onTouchStart(event: EventTouch) {}
onTouchMove(event: EventTouch) {}
onTouchEnd(event: EventTouch) {}
onTouchCancel(event: EventTouch){}

// 绑定事件(Input)
import {Input} from 'cc';
start(){
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
}
onKeyDown(event: EventKeyboard){
console.log(event.keyCode === KeyCode.KEY_W)
}

// 点击按钮
onTouchStart(event: EventTouch) {
this.node.setScale(0.9,0.9);
}
onTouchEnd(event: EventTouch) {
this.node.setScale(1.0,1.0);
}

// 拖动物体
onTouchMove(event: EventTouch) {
const x = this.node.position.x;
const y = this.node.position.y;
const delta = event.getUIDelta();
const dx = delta.x;
const dy = delta.y;
this.node.setPosition(x + dx,y + dy);
}

// 点击物体左右移动
onTouchStart(event: EventTouch) {
const uiPos = event.getUILocation();

const transform = this.node.getComponent(UITransform);
const nodePos = transform.convertToNodeSpaceAR(new Vec3(uiPos.x,uiPos.y,0));

const dx = nodePos.x > 0 ? 50 : -50;
this.node.setPosition(this.node.position.x + dx, this.node.position.y);
}
// 自定义监听时间
const { ccclass, property } = cc._decorator;

@ccclass('CustomEventListener')
export default class CustomEventListener extends cc.Component {
// 定义自定义事件类型
static readonly CUSTOM_EVENT: string = "custom_event";

onLoad() {
// 监听自定义事件
this.node.on(CustomEventListener.CUSTOM_EVENT, this.onCustomEvent, this);
}

start() {
// 发送自定义事件
setTimeout(() => {
this.node.emit(CustomEventListener.CUSTOM_EVENT, "Custom event data");
}, 3000);
}

onCustomEvent(data: string) {
cc.log("Custom event received: " + data);
}

onDestroy() {
// 移除自定义事件监听
this.node.off(CustomEventListener.CUSTOM_EVENT, this.onCustomEvent, this);
}
}

8 物理碰撞

official::2D 物理系统

单体碰撞:单个碰撞体有自己的碰撞逻辑。

属性

方法

// 注册单个碰撞体的回调函数
collider.on(Contact2DType, func, this);

// 注册全局碰撞回调函数
PhysicsSystem2D.instance.on(Contact2DType, func, this);

案例

// 注册单个碰撞函数
let collider = this.getComponent(Collider2D);
if (collider) { collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this); }

onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) { console.log('onBeginContact'); }

// 碰撞销毁(不要直接销毁)
const comp = this.player.getComponent(Collider2D);
comp.on(Contact2DType.BEGIN_CONTACT, (selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) => {
director.once(Director.EVENT_AFTER_PHYSICS, () => {
otherCollider.node.destroy();
}, this);
}, this);

六、动画

1 tween动画

official::缓动系统

方法

// 设定要改变的对象
tween(obj)

// 在上述代码后添加代码进行设定
// 添加标签
.tag(number)
// 移动到
.to(time, vec3)
// 向该方向移动
.by(time, vec3)
// 将上述缓动动作打包成一个
.union()
// 永久重复
.repeatForever()
// 开始和结束
.start()
.stop()

// 停止所有缓动
Tween.stopAll()
// 停止指定标签的缓动
Tween.stopAllByTag(0);
// 停止所有指定对象的缓动
Tween.stopAllByTarget(this.node);

案例

// 自定义对象自定义属性自定义缓动曲线
const obj = {n: 0;}
const comp = this.labelNode.getComponent(Label);
tween(obj)
.to(3, {n: 1000}, {
onUpdate: (target,ratio) => {
// 转换成两位小数后转换成字符串输出
comp.string = `${obj.n.toFixed(2)}`;
},
// 自定义缓动曲线
easing: 'elasticOut'
})
.start();

// 持续跳跃
const obj = {y: 0}
tween(obj)
.to(0.5, {n: 200}, {
onUpdate: (target, ratio) => {
this.node.setPosition(0, obj.y)
},
easing: 'quadOut'
})
.to(0.5, {n: 0}, {
onUpdate: (target, ratio) => {
this.node.setPosition(0, obj.y)
},
easing: 'quadIn'
})
// 将前面的动作整合
.union()
// 传递动作,不传则重复上一个动作(此处重复union)
.repeatForever()
.start();

// 异步翻牌
makeCardTurn(node, isBack, id?){
return new Promise<void>(resolve => {
tween(node)
.to(0.3, {scale: new Vec3(0, 1, 1)})
.call(() => {
const sprite = node.getComponent(Sprite);
sprite.spriteFrame = isBack ? this.cardManager.getCardBeiSF() : this.cardManager.getCardSFById(id);
})
.to(0.3, { scale: new Vec3(0.2 ,1 ,1)} )
.call(() => resolve())
.start();
});
}

2 Animation

组件:Animation

动画片段:Clip(面板——动画编辑器进行制作)

3 Marionette


七、辅助工具

1 Tiled

official::Tiled

使用流程:

  1. 新建项目——新建地图集——新建图层。

Q1:[scene] cannot read property ‘vb’ of null

A1:项目——宏配置——BATCHER2D_MEM_INCREMENT改大点


八、常见问题


九、发行

1 微信环境

微信环境对ES6的安全考虑:

  • 不支持使用eval执行JS代码
  • 不支持使用new Function创建函数

2 打包

打包