51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

跨平台游戏引擎Cocos creator基础教程

# 跨平台游戏引擎Cocos creator基础教程 {#跨平台游戏引擎cocos-creator基础教程}

本文讲述跨平台游戏引擎Cocos的基础教程。Cocos Creator 深度支持各大主流平台,游戏可以快速发布到 Web、iOS、Android、Windows、Mac,以及各个小游戏平台。在 Web 和小游戏平台上提供了纯 JavaScript 开发的引擎运行时,以获得更好的性能和更小的包体。

# 1. 安装开发工具Cocos Creator 3 {#_1-安装开发工具cocos-creator-3}

Cocos Creator是cocos项目的开发工具,当前最新版本V3.0.0-Preview.1。
推荐使用最新版本,最新版本同时支持2D和3D游戏游戏开发,已经将Cocos Creator 2D和Cocos Creator 3D整合到一起,所以Creator 3D 后续不会再发布独立版本。

Cocos Creator 3.0手册 (opens new window)

# 1.1 注册账号 {#_1-1-注册账号}

访问官网 (opens new window)注册账号

# 1.2 下载并安装Creator Dashboard {#_1-2-下载并安装creator-dashboard}

下载Creator Dashboard,下载地址详见https://www.cocos.com/creator
安装Creator Dashboard并启动,会提示登录,使用注册好的账号登录后进入如下界面:
启动Creator Dashboard

# 1.3 下载并安装Creator Editor {#_1-3-下载并安装creator-editor}

运行Creator Dashboard后, 点击界面左侧的Editor菜单,再点击右下角的Download按钮
下载并安装Creator Editor

选择指定版本的Editor, 点击右侧的下载按钮即可。
下载并安装指定版本的Creator Editor

还需要安装的其它环境:

  • nodejs
  • git

目前不支持linux开发环境

# 2. 核心概念 {#_2-核心概念}

游戏场景包含以下内容:

  • 物体
  • 角色
  • UI
  • 组件
    可以是一个Typescript脚本
    当玩家运行游戏时,就会载入游戏场景,游戏场景加载后就会自动运行所包含组件的游戏脚本

# 3. 教程 {#_3-教程}

Creator和Editor都设置为中文   js脚本关联外部编辑器,如vscode   https://www.bilibili.com/video/BV1sA411Y7x4?p=3

# 4. 动态加载精灵节点的图片 {#_4-动态加载精灵节点的图片}

# 4.1 加载网络图片 {#_4-1-加载网络图片}

        var sprite = this.node.getComponent(cc.Sprite)
        cc.loader.load("http://localhost/666.png", function (err, texture) {
            cc.log(texture);
        //可以自定义宽和高, 如texture.height = 200;
        sprite.spriteFrame = new cc.SpriteFrame(texture);
    })

# 4.2 加载本地resources目录下的图片 {#_4-2-加载本地resources目录下的图片}

let that = this
let sprite = that.node.getChildByName('avator').getComponent(cc.Sprite)
sprite.spriteFrame = new cc.SpriteFrame(cc.url.raw('resources/prop_666.png'));

# 5. 碰撞 {#_5-碰撞}

# 5.1 碰撞的种类 {#_5-1-碰撞的种类}

碰撞分为2种(对应2种碰撞组件):

  • 非物理碰撞
    即没有物理效果的碰撞,只能监听到碰撞的事件。
    该方案仅适合简单的场景。
  • 物理碰撞 有物理效果的碰撞,即碰撞过程中会显示出物理性质,如重力、碰撞后的反弹。

# 5.2 物理碰撞 {#_5-2-物理碰撞}

在使用cocos creator为节点添加物理碰撞组件时,会自动添加一个刚体。

  • 碰撞组件
  • 刚体
    刚体用来反应物理特性。刚体是绑到碰撞组件上的。
    刚体分为4种类型。

# 5.2.1 刚体穿透 {#_5-2-1-刚体穿透}

注意不能使用setPosition类似方式移动物体,否则会出现刚体穿透的现象。应该以物理的方式移动物体,详见力与冲量 (opens new window)

# 5.2.2 移动物体 {#_5-2-2-移动物体}

物理的方式移动物理碰撞系统中的物体, 有3种方法:ApplyForce、ApplyLinearImpulse、SetLinearVelocity

  • ApplyForce - 力,循序渐进
    F=ma, 即力=质量*加速度

该方法的使用方法详见如下案例:
已知一个物体的初速度vel,和物体质量body->GetMass(),你要设定他t秒后的速度要变为desiredVel的话,可以计算出需要的力 f=(v2-v1)*m/t。 完整代码如下(为c++代码):

b2Vec2 vel = body->GetLinearVelocity();
float m = body->GetMass();// the mass of the body
float t = 1.0f/60.0f; // the time you set
b2Vec2 desiredVel = b2Vec2(10,10);// the vector speed you set
b2Vec2 velChange = desiredVel - vel;
b2Vec2 force = m * velChange / t; //f = mv/t
body->ApplyForce(force, body->GetWorldCenter() );
  • ApplyLinearImpulse(冲量) - 速度,叠加
    冲量的计算公式I=FΔt=mΔv,I代表冲量。
    由如上公式可以得出: m一定,那么施加冲量,则速度一定会改变,这里Δv是在原速度的基础上进行改变的。
    与ApplyForce不同,ApplyLinearImpulse不会产生力,而是直接影响刚体的速度。通过ApplyLinearImpulse方法添加的速度会与刚体原有的速度叠加,产生新的速度。

补课: 几个重要公式:

  • I=FΔt
  • F=ma
  • a=Δv/Δt
  • I=mΔv(由如上几个公式可以得出)

冲量是描述力对物体作用的时间累积效应的物理量。力的冲量是一个过程量。在谈及冲量时,必须明确是哪个力在哪段时间上的冲量。 由F=ma,a=Δv/Δt,设Δv=v2-v1,Δt=t2-t1可得 Δp=mv2-mv1=FΔt 即可说:物体所受合外力的冲量就是该物体的动量(p=mv)变化量。 动量和冲量都是矢量,一定要注意方向。

  • SetLinearVelocity - 一触即发
    setLinearVelocity与ApplyImpulse一样,直接影响刚体的速度。
    和ApplyImpulse的区别: setLinearVelocity添加的速度会覆盖刚体原有的速度。中间没有加速度的概念,新速度和原速度没有关系,直接定义新速度。
    不过,在SetLinearVelocity方法不会自动唤醒sleeping的刚体,所以在调用该方法之前,记得将刚体 body.wakeUp()一下。

# 5.2.3 碰撞监听 {#_5-2-3-碰撞监听}

可以通过cocos creator提供的碰撞回调函数来监听碰撞事件,如onBeginContact等。
需要注意回调函数的输入参数contact引用类型的对象,每次发生碰撞则使用该对象存储最新的碰撞信息(而不是每碰撞一次, new出一个contact对象)。所以当您要存储历史的碰撞点信息时,需要注意该点, 否则您可能发现存储的所有历史碰撞点的坐标都相同。

验证方法: 存储历史碰撞点(获取当前碰撞点的方法:contact.getWorldManifold().points[0])到全局数组中,最终发现该数组的所有元素的坐标信息都相同。解决方法: new一个cc.v2对象push到数组中, 而不是将contact.getWorldManifold().points[0]对象直接push到数组。

# 5.2.4 碰撞分组 {#_5-2-4-碰撞分组}

默认情况下, 所有节点的分组名称都是default
所属不同分组的节点之间发生碰撞才会进行碰撞检测,所以需要按实际情况配置碰撞分组。

# 5.2.5 碰撞组件的参考坐标系 {#_5-2-5-碰撞组件的参考坐标系}

和组件cc.Graphics类似,指定碰撞组件的坐标点时, 参考坐标系并不是当前节点的父节点坐标系(按本地坐标系的定义,您可能会这么认为, 但实际上非也),而是当前节点坐标系
所以,您在可移动的节点上创建的碰撞组件会随着当前节点的移动而移动。

# 5.2.6 动态创建碰撞体 {#_5-2-6-动态创建碰撞体}

可以通过程序动态创建碰撞体,但是建议指定碰撞体的坐标点时使用整数,而不使用小数,免得发生不可预料的异常。
如下展示一个真实的异常案例
如下代码做了2件事:

  • 根据顶点动态创建碰撞体

  • 根据顶点连线,从而分析出碰撞体的边界线

      // 如下顶点的定义创建碰撞体失败,
     let objArr =[{"x":-292,"y":1,"z":0},{"x":-173,"y":180,"z":0},{"x":-172,"y":182,"z":0},{"x":-171.8490380034824,"y":183.61515573074465,"z":0},{"x":-171.25946101609992,"y":185.17405815031,"z":0},{"x":-170.66988402871743,"y":186.73296056987536,"z":0},{"x":-162,"y":208,"z":0},{"x":-141,"y":461,"z":0},{"x":-320,"y":462,"z":0}];
     // 如下顶点的定义创建碰撞体成功,与前者定义的区别是将第3个顶点的x坐标由-171.8490380034824改成了-171.8490380034823
     // let objArr =[{"x":-292,"y":1,"z":0},{"x":-173,"y":180,"z":0},{"x":-172,"y":182,"z":0},{"x":-171,"y":183.61515573074465,"z":0},{"x":-171.25946101609992,"y":185.17405815031,"z":0},{"x":-170.66988402871743,"y":186.73296056987536,"z":0},{"x":-162,"y":208,"z":0},{"x":-141,"y":461,"z":0},{"x":-320,"y":462,"z":0}];
    

    for (let index = 0; index < objArr.length; index++) { const element = objArr[index]; let tmp = this.convertSpace(this.node.parent, this.pathDrawNode, cc.v2(element.x, element.y)); if(index == 0){ this.graphic.moveTo(tmp.x, tmp.y); }else{ this.graphic.lineTo(tmp.x, tmp.y); this.graphic.strokeColor = cc.Color.RED; this.graphic.lineWidth = 10; this.graphic.stroke(); } }

    let pointArr :cc.Vec2[] = []; for (let index = 0; index < objArr.length; index++) { const element = objArr[index]; pointArr.push(cc.v2(element["x"], element["y"]));
    } let collider = this.node.parent.addComponent(cc.PhysicsPolygonCollider); collider.friction = 0; collider.restitution = 0; collider.points = pointArr; collider.apply();

如上代码绘制出了碰撞体的边界路线,但是碰撞体却创建失败, 浏览器控制台报错如下:

CCPolygonSeparator.js:337 Uncaught TypeError: Cannot read property 'x' of undefined
   at Area (CCPolygonSeparator.js:337)
   at Right (CCPolygonSeparator.js:219)
   at Reflex (CCPolygonSeparator.js:207)
   at ConvexPartition (CCPolygonSeparator.js:77)
   at ConvexPartition (CCPolygonSeparator.js:154)
   at ConvexPartition (CCPolygonSeparator.js:154)
   at Object.ConvexPartition (CCPolygonSeparator.js:154)
   at cc_PhysicsPolygonCollider._createShape (CCPhysicsPolygonCollider.js:56)
   at cc_PhysicsPolygonCollider.__init (CCPhysicsCollider.js:172)
   at CCClass.pushDelayEvent (CCPhysicsManager.js:165)

错误截图如下:
cocos creator创建刚体失败

按如上代码注释的提示,启用成功时的顶点变量的定义,则创建碰撞体成功,运行成功的效果如下:
cocos creator创建刚体

通过不断验证发现,如下任意测试用例都可以创建成功:

  • 修改第3个顶点的x坐标
    将-171.8490380034824改为-171.8490380034823或-171.8490380034825或-171都可以成功
  • 修改第3个顶点的y坐标
    将183.61515573074465改为183可以成功
  • 修改第4个顶点
    修改方法同第3个顶点
  • 所有顶点坐标改为整数

总结:

  1. 指定碰撞体的坐标点时采用整数表示,别使用小数。
    通过如上几个测试用例,无法确定创建碰撞体失败的真正原因,但是推测可能是因为顶点坐标表示为小数,因为精度问题(别再追问为什么,我也不知道,只是感觉)导致引擎内部的一些bug。
  2. 不支持创建凹多边形碰撞体
    虽然官网 (opens new window)有提到支持创建凹多边形的碰撞体。但实际上支持是有问题的,详见冰冰的解释cocos creator不支持创建凹多边形碰撞体 (opens new window)
  3. 建议使用try catch
    听其它同学讲,在正常使用的情况下,还是偶尔会报如上异常,所以为了不让程序因异常停止运行,加入一个try catch语句做个保护。

# 6. 分辨率适配 {#_6-分辨率适配}

若不做分辨率适配,那么可能会造成背景图片的横向显示不全或竖向显示不全。为了能够完整显示背景图片,可以进行如下几步操作,设计分辨率和背景图片的分辨率保持一致,同时开启适配高度模式

// 设计分辨率要和背景图片的大小保持一致,否则会造成图片显示不全。将图片的宽和高设置为设计分辨率即可
cc.view.setDesignResolutionSize(texture.width, texture.height, cc.ResolutionPolicy.FIXED_HEIGHT);

# 7. 顶点坐标系 {#_7-顶点坐标系}

cocos creator的顶点坐标系有2种坐标系,分别为世界坐标系本地坐标系

cocos creator的坐标系是与OpenGL坐标系cocos2d-x坐标系的定义完全相同。与标准屏幕坐标系不同。下图对比了2者的区别:
cocos creator坐标系和标准屏幕坐标系的区别
此处先做个铺垫: 下一章节即将讲解的cocos creator 纹理坐标系标准屏幕坐标系是一致的,都是以左上角为原点。

若想进一步了解详细内容,请参考Cocos Creator 坐标系 (opens new window)坐标系和节点变换属性 (opens new window)

# 7.1 锚点Anchor {#_7-1-锚点anchor}

锚点是用来代表当前节点位置的一个参考点,那么以锚点的坐标来代表所属节点的坐标。

# 7.2 世界坐标系 {#_7-2-世界坐标系}

世界坐标系用来描述节点在场景中的绝对位置。 世界坐标系中的世界可以理解为场景, 那么世界坐标系可以理解为以场景的左下角为原点的坐标系。

取得节点的世界坐标(即当前节点的本地坐标系原点的世界坐标):
cocos creator 取得节点的世界坐标

# 7.3 本地坐标系 {#_7-3-本地坐标系}

本地坐标系用来描述节点相对于父节点的相对位置。 场景中的任何一个节点都有一个属于自己的独立的坐标系,该坐标系的原点为该节点的锚点。每个子节点的position属性的取值是在父节点的坐标系下的坐标值。

获取节点的本地坐标,对应方法: node.getPosition()。
注意,该方法是以父节点锚点为原点的。

注意,本地坐标系还有另一种叫法:节点坐标系
因为本地坐标系上的坐标点是相对于父节点的,所以坐标点在屏幕上的位置会随着父节点的移动而移动。

# 7.4 坐标系转换 {#_7-4-坐标系转换}

  • 本地坐标转换为世界坐标
    convertToWorldSpace(以节点左下角为原点)或convertToWorldSpaceAR(以锚点为原点)
    cocos creator 本地坐标转换为世界坐标

  • 世界坐标转换为本地坐标
    convertToNodeSpace(以节点左下角为原点)或convertToNodeSpaceAR(以锚点为原点)
    需要指定转换为哪个节点的本地坐标。
    cocos creator 世界坐标转换为本地坐标

  • 某节点的本地坐标转换为另一个节点的本地坐标
    使用世界坐标作为桥梁,实现不同节点的本地坐标的转换。
    cocos creator 某节点的本地坐标转换为另一个节点的本地坐标

# 7.5 获取刚体的坐标 {#_7-5-获取刚体的坐标}

使用刚体自带的方法比通过节点获取刚体坐标更快: 使用这些 API 来获取世界坐标系下的旋转位移会比通过节点来获取相关属性更快,因为节点中还需要通过矩阵运算来得到结果,而这些 api 是直接得到结果的。

# 7.5.1 获取刚体世界坐标值 {#_7-5-1-获取刚体世界坐标值}

// 直接获取返回值
var out = rigidbody.getWorldPosition();

// 或者通过参数来接收返回值 out = cc.v2(); rigidbody.getWorldPosition(out);

# 7.5.2 局部坐标与世界坐标转换 {#_7-5-2-局部坐标与世界坐标转换}

// 世界坐标转换到局部坐标
var localPoint = rigidbody.getLocalPoint(worldPoint);
// 或者
localPoint = cc.v2();
rigidbody.getLocalPoint(worldPoint, localPoint);
// 局部坐标转换到世界坐标
var worldPoint = rigidbody.getWorldPoint(localPoint);
// 或者
worldPoint = cc.v2();
rigidbody.getLocalPoint(localPoint, worldPoint);
// 局部向量转换为世界向量
var worldVector = rigidbody.getWorldVector(localVector);
// 或者
worldVector = cc.v2();
rigidbody.getWorldVector(localVector, worldVector);
var localVector = rigidbody.getLocalVector(worldVector);
// 或者
localVector = cc.v2();
rigidbody.getLocalVector(worldVector, localVector);

如上代码段中有提到向量,向量是有方向的线段,相关教程请前往

这里提供一个向量在游戏中的具体应用,请前往cocos creator教程之向量的妙用 (opens new window)

# 8. 纹理uv坐标系 {#_8-纹理uv坐标系}

纹理uv坐标系的原点在左上角,u轴是向右,v轴是向下,范围是0~1。

# 8.1 纹理坐标的含义 {#_8-1-纹理坐标的含义}

纹理位图是用来表示物体图案的二维数组,数组的每一个元素都存储了一个颜色值,称为纹理元素。 纹理元素在表示纹理的数组中的二维下标(即它在位图中的二维坐标)称为纹理坐标,一般以字母表示为(u,v),也称为实际纹理坐标。假设位图的宽、高分别为w、h,显然, 0 ≤ u ≤ w,0 ≤ v ≤ h 因为在一个图形显示系统中往往存在多幅不同的纹理,它们的宽、高也不尽相同,用实际纹 理坐标表示纹理元素的位置在计算上很难统一,所以经常使用相对纹理坐标[设为(u′,v′)]代替实际纹理坐标,u′、v′分别是u、v所占宽、高的百分比: u′ = u / w,v′ = v / h 因此,在D3D中用两个0~1的浮点值(U,V)来设置一个点的纹理坐标,U是横轴、V是纵轴。纹理的左上角为(0,0),右下角为(1,1)。
纹理坐标定义说明图

# 8.2 纹理的示例教程 {#_8-2-纹理的示例教程}

# 9. 切图 {#_9-切图}

切图有多种方法:

# 9.1 mesh结合切耳法 {#_9-1-mesh结合切耳法}

实现过程详见文档使用mesh和切耳法实现多边形切图或计算多边形面积 (opens new window)
该文档有讲解纹理的应用。
该文档的对应工程源码地址为meshTexture (opens new window)
该工程的creator版本为2.2.2
切耳法的核心代码如下:

if (this.vertexes.length >= 3) {
    // 计算顶点索引 
    let ids = [];
    const vertexes = [].concat(this.vertexes);
// 多边形切割,未实现相交的复杂多边形,确保顶点按顺序且围成的线不相交
let index = 0, rootIndex = -1;
while (vertexes.length &gt; 3) {
    const p1 = vertexes[index];
    const p2 = vertexes[(index + 1) % vertexes.length];
    const p3 = vertexes[(index + 2) % vertexes.length];
const v1 = p2.sub(p1);
const v2 = p3.sub(p2);
if (v1.cross(v2) &amp;gt;= 0) {
    // 是凸点
    let isIn = false;
    for (const p_t of vertexes) {
        if (p_t !== p1 &amp;amp;&amp;amp; p_t !== p2 &amp;amp;&amp;amp; p_t !== p3 &amp;amp;&amp;amp; this._testInTriangle(p_t, p1, p2, p3)) {
            // 其他点在三角形内
            isIn = true;
            break;
        }
    }
    if (!isIn) {
        // 切耳朵,是凸点,且没有其他点在三角形内
        ids = ids.concat([this.vertexes.indexOf(p1), this.vertexes.indexOf(p2), this.vertexes.indexOf(p3)]);
        vertexes.splice(vertexes.indexOf(p2), 1);
        rootIndex = index;
    } else {
        index = (index + 1) % vertexes.length;
        if (index === rootIndex) {
            cc.log('循环一圈未发现');
            break;
        }
    }
} else {
    index = (index + 1) % vertexes.length;
    if (index === rootIndex) {
        cc.log('循环一圈未发现');
        break;
    }
}
// 感谢 @可有 修复
if (index &amp;gt; (vertexes.length - 1)) index = (vertexes.length - 1);

} ids = ids.concat(vertexes.map(v =&gt; { return this.vertexes.indexOf(v) })); mesh.setIndices(ids);

if (this.renderer.mesh != mesh) { // mesh 完成后再赋值给 MeshRenderer , 否则模拟器(mac)会跳出 this.renderer.mesh = mesh; }

} else {

}

# 9.2 mesh结合poly2tri库 {#_9-2-mesh结合poly2tri库}

核心代码如下:

// 计算顶点索引 
const ids = [];
// 多边形切割 poly2tri,支持简单的多边形,确保顶点按顺序且不自交
const countor = this.vertexes.map((p) => { return { x: p.x, y: p.y } }); 
const swctx = new poly2tri.SweepContext(countor, { cloneArrays: true });

swctx.triangulate(); const triangles = swctx.getTriangles(); triangles.forEach((tri) => { tri.getPoints().forEach(p => { const i = countor.indexOf(p as any); ids.push(i); }); }) mesh.setIndices(ids);

详细教程请前往使用mesh和poly2tri实现多边形切图或计算多边形面积 (opens new window)
该教程的源码地址为使用mesh和poly2tri实现多边形切图或计算多边形面积 (opens new window)

赞(2)
未经允许不得转载:工具盒子 » 跨平台游戏引擎Cocos creator基础教程