脚本与组件
创建一个Typescript脚本,默认的模板如下:
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@property
private isDebug: boolean = false;
@ccclass('GameInit')
export class GameInit extends Component {
start() {
}
update(deltaTime: number) {
}
}
- Cocos是基于组件化开发,游戏的运行是基于在节点上挂载的组件(脚本)来运行的。
- 其中import导入了Cocos的模块,_decorator引入了装饰器,修饰类型或数据成员。Component是组件类的基类。Node是节点,是组件的载体。
- ccclass是类装饰器,装饰之后编辑器的界面才能读取和显示该组件。property装饰类的数据成员,让编辑器可见。
- onLoad是组件第一次加载到场景中调用,start是第一次画面刷新的时候调用的,update每次画面刷新调用一次。
- 每个组件都有一个数据成员node,指向该节点(this.node)。常用 用法:this.node.parents,this.node.children[0],this.node.getChildByName(name),this.node.getChildByPath,this.node.name,this.node.getComponent,this.node.getComponents,this.node.getComponentsInChildren,this.node.addComponent。使用的时候可以省略中间的.node。
向量
import { _decorator, Component, Node, Vec3, v3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameMgr')
export class GameMgr extends Component {
start () {
// new + 构造函数;
var v = new Vec3(); // (0, 0, 0)
v = new Vec3(3, 4); // (3, 4, 0)
v = new Vec3(3, 4, 5); // (3, 4, 5)
console.log(v);
var vv = new Vec3(v); // v2(3, 4, 5)
console.log(vv);
// end
vv = v3(7, 8 ,9);
console.log(vv);
console.log(vv.length());
console.log(Vec3.len(vv));
console.log(Vec3.UNIT_X, Vec3.FORWARD, Vec3.ONE, Vec3.NEG_ONE);
}
}
平移、缩放、旋转
export class GameMgr extends Component {
start () {
// var ball = this.node.getChildByName("Sphere");
// console.log(ball?.worldPosition, ball?.position);
// get/set接口
// console.log(ball?.worldScale, ball?.scale); // x缩放多少,y缩放多少, z缩放多少
// var pos: Vec3 = (ball?.getPosition() as Vec3); // 获取一个坐标
// pos.x += 1.0;
// ball?.setPosition(pos);
// this.node.setWorldRotationFromEuler(0, 60, 0);
}
update(dt: number) {
var pos = this.node.getPosition();
pos.x += (2 * dt);
this.node.setPosition(pos);
var rot: Vec3 = this.node.eulerAngles;
rot.y += (30 * dt);
this.node.setRotationFromEuler(rot);
}
}
系统事件
import { _decorator, Component, Node, SystemEvent, systemEvent, System, EventAcceleration, EventKeyboard, Touch, EventTouch, SystemEventType, macro } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameMgr')
export class GameMgr extends Component {
public start(): void { // 在初始化这里,我们就可以监听我们的事件;
// 监听触摸事件
systemEvent.on(SystemEventType.TOUCH_START, this.onTouchStart, this);
systemEvent.on(SystemEventType.TOUCH_MOVE, this.onTouchMove, this);
systemEvent.on(SystemEventType.TOUCH_END, this.onTouchEnd, this);
systemEvent.on(SystemEventType.TOUCH_CANCEL, this.onTouchCancel, this);
// end
// 监听我们的键盘事件
systemEvent.on(SystemEventType.KEY_DOWN, this.onKeyDown, this);
systemEvent.on(SystemEventType.KEY_UP, this.onKeyUp, this);
// end
// 重力感应
systemEvent.on(SystemEventType.DEVICEMOTION, this.onDeviceMotion, this);
// end
}
onDestroy(): void {
// 当我们开始监听的时候,一定要思考什么时候释放;
systemEvent.off(SystemEventType.TOUCH_START);
// systemEvent.on(SystemEventType.TOUCH_START, this.onTouchStart, this);
systemEvent.off(SystemEventType.TOUCH_MOVE, this.onTouchMove, this);
systemEvent.off(SystemEventType.TOUCH_END, this.onTouchEnd, this);
systemEvent.off(SystemEventType.TOUCH_CANCEL, this.onTouchCancel, this);
// end
// 监听我们的键盘事件
systemEvent.off(SystemEventType.KEY_DOWN, this.onKeyDown, this);
systemEvent.off(SystemEventType.KEY_UP, this.onKeyUp, this);
// end
// 重力感应
systemEvent.off(SystemEventType.DEVICEMOTION, this.onDeviceMotion, this);
// end
}
private onDeviceMotion(event: EventAcceleration): void {
}
private onKeyDown(event: EventKeyboard): void {
switch(event.keyCode) {
case macro.KEY.s: // S
console.log("S 建被按下了");
break;
case 87: // w
break;
case 65: // A
break;
case 68: // D
break;
case macro.KEY.space:
console.log("空格按键按下了");
break;
}
}
private onKeyUp(event: EventKeyboard): void {
switch(event.keyCode) {
case 83: // S
break;
case 87: // w
break;
case 65: // A
break;
case 68: // D
break;
}
}
private onTouchStart(touch: Touch, event: EventTouch): void {
// this --->target;
console.log("onTouchStart", touch, touch.getLocation());
}
private onTouchEnd(touch: Touch, event: EventTouch): void {
// this --->target;
console.log("onTouchEnd");
}
private onTouchMove(touch: Touch, event: EventTouch): void {
console.log("onTouchMove");
console.log("getDelta", touch, touch.getDelta());
}
private onTouchCancel(touch: Touch, event: EventTouch): void {
console.log("onTouchCancel");
}
}
预制体生成
import { _decorator, Component, Node, Prefab, instantiate } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GamePrefabGen')
export class GamePrefabGen extends Component {
@property(Prefab)
// private cubePrefab: Prefab | null = null;
private cubePrefab: Prefab = null as unknown as Prefab;
start () {
// 实例化, 一定要加到场景树才可以了;
var cube: Node = instantiate(this.cubePrefab);
// 节点.addChild: 往这个节点,加入一个孩子节点
// this.node.addChild(cube); // API 就是将我们的节点加入到我们的场景树;
this.node.parent?.addChild(cube);
// end
}
}
3D知识
骨骼动画
天空盒、雾
摄像机
透视投影:3D使用
正交投影:2D UI使用
内置shader
物理组件
import { _decorator, Component, Node, RigidBody, v3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('CubeCtrl')
export class CubeCtrl extends Component {
private body: RigidBody = null as unknown as RigidBody;
// private body: RigidBody | null = null;
onLoad(): void {
// step1: 获取刚体组件实例
this.body = this.node.getComponent(RigidBody) as RigidBody;
this.body.useGravity = true;
}
start(): void {
/*
// step2: 我们来给一个线性速度
this.body.setLinearVelocity(v3(-1, 0, 0));
// 单位应该是弧度
// 绕哪个轴,每秒旋转多少度;
this.body.setAngularVelocity(v3(0, Math.PI * 2, 0));
*/
this.body.applyForce(v3(-1000, 0, 0));
}
}
监听碰撞事件
import { _decorator, Component, Node, Collider, ICollisionEvent } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('EventCollision')
export class EventCollision extends Component {
start(): void {
// 获取任意类型的碰撞器实例,你就可以使用基类
var collider = this.node.getComponent(Collider);
collider?.on("onCollisionEnter", this.onCollisionEnter, this);
collider?.on("onCollisionStay", this.onCollisionEnter, this);
collider?.on("onCollisionExit", this.onCollisionExit, this);
collider?.on("onTriggerEnter", this.onTriggerEnter, this);
collider?.on("onTriggerStay", this.onTriggerStay, this);
collider?.on("onTriggerExit", this.onTriggerExit, this);
}
private onTriggerEnter(event?: ICollisionEvent): void {
console.log("onTriggerEnter", event);
}
private onTriggerStay(event?: ICollisionEvent): void {
console.log("onTriggerStay");
}
private onTriggerExit(event?: ICollisionEvent): void {
console.log("onTriggerExit");
}
private onCollisionEnter(event?: ICollisionEvent): void {
console.log("onCollisionEnter");
}
private onCollisionStay(event?: ICollisionEvent): void {
console.log("onCollisionStay");
}
private onCollisionExit(event?: ICollisionEvent): void {
console.log("onCollisionExit");
}
}
射线检测:
import { _decorator, Component, Node, PhysicsSystem, geometry, systemEvent, SystemEvent, Camera } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameMgr')
export class GameMgr extends Component {
@property(Camera)
private mainCamera: Camera | null = null;
start() {
/*
// 物理系统 PhysicsSystem类, 大写字幕
// PhysicsSystem是一个全局单例 PhysicsSystem.instance
// var r: geometry.Ray | null = null;
// step1: 创建一条射线
var r: geometry.Ray = null as unknown as geometry.Ray;
r = geometry.Ray.create(0, 0, 0, 0, 0, -1);
// step2: 往3D世界里面放物体+物理碰撞器(编辑器,代码new 预制体)
// step3: 调用接口来计算,到底我们的设想穿越了哪些物体
// 两种模式(碰到一个物体以后,就停止检测(一个),继续检测,直到所有都做完)
// if(PhysicsSystem.instance.raycastClosest(r, (1 << 0) | (1 << 1))) { // 碰到了物体
if(PhysicsSystem.instance.raycast(r, (1 << 0) | (1 << 1))) { // 碰到了物体
// step4: 拿结果;
// console.log(PhysicsSystem.instance.raycastClosestResult);
// console.log(PhysicsSystem.instance.raycastClosestResult.collider.node.name);
console.log(PhysicsSystem.instance.raycastResults);
}
else { // 没有碰到物体
console.log("没有撞到任何物体");
}
*/
// 监听触摸事件;
systemEvent.on(SystemEvent.EventType.TOUCH_START, this.onTouchStart, this);
}
private onTouchStart(e: any): void {
// step1: 获取屏幕位置
var screenPos = e.getLocation();
// step2: 获取摄像机组件实例
this.mainCamera = this.node.getComponent(Camera);
// step3: 利用Camera,帮我们生成一个Ray对象
// var ray: geometry.Ray = geometry.Ray.create();
var ray: geometry.Ray = this.mainCamera?.screenPointToRay(screenPos.x, screenPos.y) as geometry.Ray;
// step4: 射线检测,拿结果
if(PhysicsSystem.instance.raycastClosest(ray)) {
console.log(PhysicsSystem.instance.raycastClosestResult.collider.node.name);
}
}
}
UI组件
图片Sprite
2D图片资源,修改图片通过UI SpriteFrame。注意图片的类型要改为2D UI。
type:使用类型
Alpha混合:可以设置图片的和背景混合,半透明效果
Color:设置图片的颜色叠加
SpriteAtlas:图集
spriteFrame:图片
Trim:去除图片周围的透明区域
GrayScale:是否置灰
SizeMode:是否由用户指定大小
模式:简单模式,切片模式,平铺模式,填充模式
图集的使用:要包含图集本身和说明位置的.plist文件,这样引擎就能自动分割图集
字体Label
string:文本的内容
对齐方式:左对齐,中间对齐,右对齐
字体大小,系统字库FontFamily,font(自带字库,位图字体),是否使用系统字库
Clamp模式:以节点范围为大小
Shark:根据节点的大小,自动调整文字
Resize_height:根据宽度换行
None:以文字大小为节点大小
CacheMode:缓存模式,用于节约DrawCall
按钮Button
Transition:设置按下大小变动
设定对应的响应事件为对应的函数。
public onButtonClick(): void {
console.log("onButtonClick");
}
UI的响应事件
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('EventListener')
export class EventListener extends Component {
start () {
this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
}
private onTouchStart(): void {
console.log("onTouchStart");
}
}
Mask组件
裁剪不需要显示的区域,例如小地图的应用,只能裁剪子节点
形状:矩形,扇形,图片(小于某个透明度被裁掉),多边形
反转:内部不显示还是外部不显示
ScollView
滚动列表
内容排版:加layout组件
滚动事件:滚动到最上方,最下方,最左方,最右方
屏幕适配
背景适配:背景的长度和宽度能够覆盖所有的屏幕 。
UI适配:加上widget组件,把UI分为上下左右中五个部分,设置左边部分的UI锚点为父物体左边,父物体固定为左边并且上下拉伸,其他同理
需要适配水滴屏
本地存储
原理:因为只能存储字符串,因此表序列化成Json对象来进行存储,使用时反序列化出来即可
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameApp')
export class GameApp extends Component {
start () {
var loginData = {uname: "blake", upwd: "123"};
var loginStr = JSON.stringify(loginData);
// 添加一个存储, key, value
localStorage.setItem("loginData", loginStr);
// 获取一个key
var strValue = localStorage.getItem("loginData");
var data = JSON.parse(strValue);
console.log(data);
// 删除一个key
localStorage.removeItem("loginData");
}
}
定时器
import { _decorator, Component, Node, macro, find, Sprite } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameApp')
export class GameApp extends Component {
start () {
// 触发一次,3秒以后的定时器;
this.scheduleOnce(()=>{
// this --->外面当前的this;
console.log("5秒时间到了!");
var item = find("Canvas/Item");
// 移除场景中的一个节点
// item?.removeFromParent();
// end
// 移除掉场景中的一个节点,并且销毁节点;
// item?.destroy();
// 删除掉一个组件实例类型就是Sprite组件实例
item?.getComponent(Sprite)?.destroy();
}, 5);
// 5秒以后启动定时器,每隔2秒,调用一次回调函数,一共调用6次;
var func = ()=>{
// this --->外面的this;
console.log("定时器触发了,2秒一次");
};
this.schedule(func, 2, macro.REPEAT_FOREVER, 5);
// 取消定时器:
this.scheduleOnce(()=>{
// 取消掉所有的组件实例的定时器
// this.unscheduleAllCallbacks();
// 取消特定的定时器? schedule是哪个函数,你现在就要传哪个函数进去;
this.unschedule(func);
}, 20)
console.log(this.node.name, this.node.layer); // (1 << 0) (1<<1) (1 << 2)
}
}
基于AB包加载资源
每个文件夹都有的功能,可以配置为AssetBundle,名字为文件夹本身的名字
默认有两个AB包:
- 场景中存在的资源会被打入main ab包
- resources文件夹中的文件会被打入另一个ab包
- 现在的项目尽量不使用resource文件夹,而是新建一个AssetPackage文件夹,存放ab资源包
import { _decorator, Component, Node, macro, find, Sprite, assetManager, AssetManager, SpriteAtlas, Texture2D, AudioClip, AudioSource, SpriteFrame } from 'cc'; const { ccclass, property } = _decorator; @ccclass('GameApp') export class GameApp extends Component { start () { var itemSprite: Sprite = find("Canvas/Item")?.getComponent(Sprite) as Sprite; // 异步加载,不是马上就加载好,加载好会掉你的函数; assetManager.loadBundle("GUI", (err, ab: AssetManager.Bundle)=> { // 在这里才正式加载好; // 加载图集资源 // 路径:是从我们的资源包的路径下开始 ab.load("test/airplane", SpriteAtlas, (err, atlas: SpriteAtlas)=>{ if(err) { console.log(err); return; } // console.log(atlas); var sp: SpriteFrame = atlas.getSpriteFrame("Jishen_W0") as SpriteFrame; itemSprite.spriteFrame = sp; sp.addRef(); }); // end // 加载我们的声音文件 ab.load("CK_attack1", AudioClip, (err, clip:AudioClip)=>{ if(err) { console.log(err); return; } var as: AudioSource = this.getComponent(AudioSource) as AudioSource; as.clip = clip; clip.addRef(); // 引用计数来判断,这个资源是否在使用; as.loop = true; as.play(); }); // end // 释放ab包, 不会释放从ab包里面加载的资源; // assetManager.removeBundle(ab); // end }); this.scheduleOnce(()=>{ // var ab = assetManager.getBundle("GUI"); // ab?.releaseAll(); // 释放所有资源 // ab?.releaseUnusedAssets(); // 释放没有用的资源, 引用计数 /* var ab = assetManager.getBundle("GUI"); ab?.release("test/airplane"); // 基于ab包释放单个资源 ab?.release("CK_attack1"); assetManager.removeBundle(ab as AssetManager.Bundle); */ var ab: AssetManager.Bundle | null = assetManager.getBundle("GUI"); assetManager.removeBundle(ab as AssetManager.Bundle); // assetManager.releaseAsset(); // assetManager.releaseUnusedAssets(); // assetManager.releaseAll(); // assetManager.loadRemote(); }, 10); } }
Tween缓动对象
基于node上的脚本进行控制
import { _decorator, Component, Node, tween, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('TweenCtrl')
export class TweenCtrl extends Component {
onLoad(): void {
// step1: 确定你要欢动的target--->当前节点; this.node;
// step2: 创建一个我们的tween对象;
var t = tween(this.node);
// step3: 给欢动对象来下控制的命令;
t.by(1, {position: new Vec3(200, 0, 0), scale: new Vec3(-1, -1, -1)});
t.call(()=>{
console.log("action end!!!");
});
// step4: 启动一个tween对象,让他控制我们的target来做上面的动作
t.start();
}
}
Json的序列化和反序列化
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameApp')
export class GameApp extends Component {
onLoad(): void {
var loginData = {
uname: "blake",
upwd: "123456",
};
var loginDataStr = JSON.stringify(loginData);
console.log(loginData);
console.log(loginDataStr);
var data = JSON.parse(loginDataStr);
console.log(data);
console.log(data.uname, data.upwd);
}
}
2D骨骼动画
- 首先需要有一张图集
- atlas文件表述了骨骼在图集中的位置
- json文件描述了动画控制
- 这三种文件要同名,存放一起
import { _decorator, Component, Node, sp } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('GameSpine')
export class GameSpine extends Component {
start () {
// step1: 获取组件实例, 哪个节点上的哪个组件实例; this.node/find;
var animCom: sp.Skeleton = this.node.getComponent(sp.Skeleton);
// step2: 调用接口来播放某个动画;
animCom.animation = "animation_3_stop";
}
}
如何制作摇杆
- 需要两张圆形图片
- 编写控制内部圆形的代码
import { _decorator, Component, Node, Vec2, v2, v3, UITransform, Camera } from 'cc'; const { ccclass, property } = _decorator; @ccclass('Joystick') export class Joystick extends Component { @property(Node) private stick: Node = null; public dir: Vec2 = v2(0, 0); @property private maxR: number = 100; @property(Camera) private uiCamera: Camera = null; private transfrom: UITransform = null; public onLoad(): void { // this.stick = this.node.getChildByName("stick"); this.transfrom = this.node.getComponent(UITransform); this.stick.on(Node.EventType.TOUCH_START, this.OnTouchStart, this); this.stick.on(Node.EventType.TOUCH_CANCEL, this.OnTouchEnd, this); this.stick.on(Node.EventType.TOUCH_MOVE, this.OnTouchMove, this); this.stick.on(Node.EventType.TOUCH_END, this.OnTouchEnd, this); } private OnTouchStart(e): void { this.dir.x = this.dir.y = 0; } private OnTouchEnd(e): void { this.dir.x = this.dir.y = 0; this.stick.setPosition(0, 0); } private OnTouchMove(e): void { var screenPos: Vec2 = e.getLocation(); // 摄像机,把我们的世界坐标,转到我们的屏幕坐标; var wPos = this.uiCamera.screenToWorld(v3(screenPos.x, screenPos.y, 0)); var pos = this.transfrom.convertToNodeSpaceAR(wPos); pos.z = 0; var len = pos.length(); if(len > this.maxR) { pos.x = pos.x * this.maxR / len; pos.y = pos.y * this.maxR / len; len = this.maxR; } this.dir.x = pos.x / len; // cos(角度) this.dir.y = pos.y / len; // sin(角度) this.stick.setPosition(pos); } }
Box2D物理引擎
- Box2D必须要有2D刚体
- 在onLoad里面开启物理引擎
- 地面设置静态刚体,人物等设置动态
- 开启碰撞检测与碰撞分组
管理类:
import { _decorator, Component, Node, PhysicsSystem2D, EPhysics2DDrawFlags } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('PhysicMgr')
export class PhysicMgr extends Component {
@property
private isDebug: boolean = false;
onLoad(): void {
PhysicsSystem2D.instance.enable = true; // 开启物理引擎;
if(this.isDebug) { // debugDraw;
PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.All;
}
else {
PhysicsSystem2D.instance.debugDrawFlags = EPhysics2DDrawFlags.None;
}
}
}
import { _decorator, Component, Node, BoxCollider2D, Contact2DType } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('ItemContact')
export class ItemContact extends Component {
private colliderBox: BoxCollider2D = null;
onLoad(): void {
// 获得你要监听碰撞事件得物理形状
this.colliderBox = this.node.getComponent(BoxCollider2D);
// 监听碰撞事件
this.colliderBox.on(Contact2DType.BEGIN_CONTACT, this.OnBeginContant, this);
}
private OnBeginContant(self, other): void {
console.log("OnBeginContant", other);
}
}
微信小游戏分包加载
internal:引擎内部的资源
main:场景中的资源和依赖,没有指定ab包的
普通ab包:游戏引擎代码库cocos-js,引擎核心代码cc.js
自己写的代码在main ab包的index.js
注意:需要在cocos打包发布的界面中选择小游戏分包,引擎会自动把AssetBundle打成分包。引擎代码库会打成不超过4M的主包。
当总体积超过20M就需要做第三方资源部署。