022 - cocos creator 3D

#cocos creator 3D

001 初识creator3D

  • 光、摄像机
  • 坐标系:
    • X轴: 红色
    • Y轴: 绿色
    • Z轴: 蓝色
  • 3D组件 预览
  • 游戏发布简介
  • 3D坐标系
    • 右手坐标系、左手坐标系
    • 物体:
      • 前方: -z
      • 右边: +x
      • 上面: +y
  • 创建3D物体
    • plane: 平面:10*10
基础
export class GameMgr extends Component {
    @property({ type: NodePos, tooltip: '箭' })
    NodePos_arrow: NodePos = null;
}
快捷键
  • 双击目录–定位到物体
  • 右键旋转
  • ALT+鼠标左键 旋转物体
  • atrl+shift+f–》点击摄像机节点,调成编辑器摄像机视角
  • Ctrl+D 复制

002 creator3D如何显示3D物体_Mesh_shader_材质

  • 准备:
    • (1)做3D建模(3DMax,Zbrush,Maya),模型原点,模型坐标系。
    • (2)放入3D世界中(位置、缩放、旋转)
    • (3)摄像机、正方向
  • 形状改变、坐标变幻
GPU绘制物体标准流程:–> 渲染管道流水线 --> pass
  • 绘制流程:
    • 顶点初始化–顶点shader–Tellellation曲面化–集合shader–裁剪,投影–三角形遍历–片元着色shader–输出2D图像
  • 【1】顶点初始化:CPU读取3D模型得顶点数据传到显存,给GPU使用
    • 点—>线—>面(三角形)—>体.顶点(位置(模型的坐标系),纹理贴图(uv坐标), 法线的向量,…)
  • 【2】顶点shader:shader我们给GPU写好的一段程序,将你写好的程序装载到GPU里面;
    • stepl: 根据模型坐标—>世界变换矩阵(平移矩阵,旋转矩阵,缩放矩阵)–>CPU(游戏引擎根据节点)
      • 每个顶点模型× 世界变换矩阵—>世界坐标; 北京+ 三里屯+1.5(模型坐标)
    • step2: 我们的游戏引擎,根据摄像机的位置,旋转—>世界坐标系,变换到以摄像机为原点坐标系;
      • 游戏引擎根据摄像机的节点位置,旋转—>世界坐标系,摄像机坐标系—>变换矩阵;
      • 世界坐标变幻矩阵* 摄像机矩阵*模型坐标—>模型坐标—>观蔡者为中心坐标系下;
    • step3: 你可以们顶点shader代码里面自己修改模型坐标, 改变模型形状(可选)
    • cocos creator,unity,都会自己写好一些标准的常用的3D shader,我们就用可以了;除非你自己要定制一一些持殊的效果;
  • 【3】曲面化,【4】集合shader:显卡的标准流程—>3D物体更平滑;
  • 【5】裁剪, 投影
    • 背对摄像机的三角形我们都会被裁掉(cull正面,背面, 不裁剪,默认摄像机背对的面裁减掉)
    • 投影: 关键的一个动作: 3D–>2D
    • 如何计算投影
      • (1)椎体投影(透视投影)【3D用的比较多】
        • x墙/x = y墙/y = z墙/z ===> x投影=xz墙/z,y投影 = yz墙/z
      • (2)正交投影【2D用的比较多】
  • 【6】三角形遍历:3D网格–》2D三角形
  • 【7】片元着色shader:每个三角形进行着色:着色Shader–>代码自己写,引擎已经写好了
2.creator3D网络、材质,Shader;
  • (1)Creator3D组件:例如cc.ModelConponent–>复制显示3D物体
    • Mesh:初始化好我们要绘制的模型的网络–》ModelComponent相关–》mesh网络数据–》GPU顶点初始化
  • (2)装载shader,3D绘制算法不一样,绘制效果不一样–》装载算法—》shader代码在哪里
    • ModelCoponent–>Matrials–>材质(配置文件,effect(指定的shader),渲染管道的参数)builitin-standard
    • 渲染管道就配置好了,我们的这个shader–>set pass call(性能优化很重要的一个点)
  • (3) ModelCoponent相关–》世界变换矩阵,设想变幻矩阵—》传递给顶点shader
  • (4)着色:ModelCoponent相关 读取材质相关的参数,将这些着色相关的纹理,颜色传递给着色shader进行着色
3.导入一个3D 模型, 并显示出来;
  • ModelCoponent功能类是的,另外一个绘制3D物体的模型组件(蒙皮网格)
  • SkinnedModelComponent
    1. creator 3D 内置常用的shader:unlit

003 creator3.D.组件化开发

  • 1.新建一个脚本类,被编辑器识别—。自动创建完成
    1. new 组件类的实例到节点上
    • 所有的组件类–》new一个组件类组件类实例–》到节点上, 组件类的实例–》节点上
    • 【1】编辑器添加组件实例到节点
      • new 组件类的实例—》 点击“添加组件”
    • 【2】代码添加组件实例到节点
  • 3.游戏引擎如何调用代码
    • 规定:游戏引擎运行–》游戏场景–》每个场景–》每个节点–》节点上每个组件实例,拿到后可以调用组件实例成员方法
      • 第一次运行,遍历每个组件实例,调用组建历史的onLoad方法–》都调用一遍
      • 下一帧时:调用.onStart只调用一次
      • onLoad–》.onStart两个初始化入口
    • update的时间差是不固定的,根据运行时间来判断
  • 4.this,this.node,数据成员如何绑定到编辑器的
    • 普通数据类型: 定义成员: // 权限 名称:类型 = 默认值
    • cocos类型: @property(类型)
    • 数据组: @property(类型)
// cc.Component --> 组件类的基类
// cc.Node ---> 节点类
// cc.Vec3 ---> 三维向量类
import { _decorator, Component, Node, Vec3 } from "cc";
// 装饰器:ccclass,property
// 装饰器是给编辑装载脚本代码的时候来读取的
// 装饰器:ccclass 是指定一个类是一个组件类
// 装饰器:property指定一个成员变量为属性成员,编辑器读到这个装饰器,会把这个数据成员,作为属性绑定到编辑器
const { ccclass, property } = _decorator;

// component导入进来的组件类的基类
// export 导出这个类 import {game_mgr} from "./game_mgr"
// 装饰器@开头的;
@ccclass("game_mgr") // 编辑器识别我们这个脚本后,会把他当成组件类
export class game_mgr extends Component {
    // 权限 名称:类型 = 默认值
    //普通的基本数据类型:boolean,number
    @property
    private is_debug: boolean = false;
    
    @property
    private speed: number = 200;
    
    // 复杂数据类型
    @property(Node)
    private test_node: Node = null;
    
    @property([Node])
    private test_array: Array<Node> = [];

    start () {
        console.log("start")
    }
    update (deltaTime: number) {
        var s = deltaTime * 5;
        this.node.translate(cc.v3(0,0,-s))
    }
}

004 三维向量

  • 三维向量常用方法
var v:Vec3 = cc.v3(3,4,5);
var v1 = new Vec3(0,0,0);

var v3 = new Vec3(v);
  • 【2】常用单位向量
    UNIT_X: (1, 0, 0), UNIT_Y(0, 1, 0), UNIT_Y(0, 0, 1);ZERO: (0, 0, 0), ONE: (1, 1, 1), NEG_ONE (-1, -1, -1);
  • 【3】常用向量方法
    • 向量长度 Vec3.len
      • var v5 = Vec3.len(v4);==>v4.length()
    • 向量加减法: add,sub。
      • 静态方法: 放在新的向量里面v4.add(Vec3.UNIT_Y);结果是v4
      • 成员方法:将结构修改到实例对象Vec3.add(v3,Vec3.ONE,Vec3.NEG_ONE)结果在v3中
    • 两个向量的距离:Vec3.distance var v9 = Vec3.distance(v1,v4);
    • 两个向量线性插值:lerp范围是(0,1)
      • V = A + T * (B-A) T(0, 1);
// 线性插值  V = A + T * (B-A)  T(0, 1);
// v3 = v1 + 0.3 * (v4 - v1)
Vec3.lerp(v3, v1, v4, 0.3);
console.log(v3);

// v1 = v1 + 0.3 * (v4 - v1); 
v1.lerp(v4, 0.3);
console.log(v1);
+ 两个向量点乘dot,原理,
    - A(x1,y1,z1),B(x2,y2,z2)
    - A*B = (x1*x2+y1*y2+z1*z2)
    - A*B = |A|+|B|+cos(夹角)
+ 夹角:两个向量之间较小的角度【0,180】
+ 求夹角:|A|*|B|*cos(夹角) = (x1*x2+y1*y2+z1*z2)--->cos(夹角) = ((x1*x2+y1*y2+z1*z2)/(|A|*|B|))
// 点乘 (x1*x2 + y1 * y2 + z1 * z2)
console.log(Vec3.dot(v1, v4)); // 静态方法;
console.log(v1.dot(v4)); // 成员方法   
+ 叉积(cross):法相向量
    - 返回一个向量
// 叉积----》(法线向量)
console.log(Vec3.cross(v3, v1, v4));  // v3 = v1 x v4;
// v1.cross(v4); // v1 = v1 x v4;
// console.log(v1); 
v4.cross(v1); // v4 = v4 x v1;  // 相反的;
console.log(v4);
+ 两个向量单位化:normalize
// (x, y, z) ---> 单位向量, 方向相同, 向量长度的为一;
Vec3.normalize(v3, v4); // v3 = v4的单位向量;
v4.normalize(); // v4 = v4的单位向量;
console.log(v3, v4, v4.length());
  • 【4】标量分解到对应向量
    • dir:(x,y,z),len是dir长度,标量大小v,按照dir分解以后得到的向量:(v*dir.x/len,v*dir.y/len,v*dir.z/len)

005 Node的平移与缩放

  • 1: 本地坐标系:
    • position: 获取本地坐标位置 只读不能用于修改坐标位置(直接修改节点poistion, 不会创建内存);
    • setPosition: 修改物体本地坐标位置;
    • getPosition: 获取物体的本地坐标位置; (新的内存,不是node 的postion)
  • 2: 世界坐标系:
    • worldPosition: 获取世界坐标位置(readonly);
    • setWorldPosition: 设置世界坐标的位置;
    • getWorldPosition: 获取世界坐标位置; 创建一个新的内存对象;
  • 3: 平移:translate(位置增量,Local/World) 往前走100米
    • Node.NodeSpace.Local/ Node.NodeSpace.World
update(dt: number) {
    var speed: number = 5;
    this.node.translate(cc.v3(0, 0, -speed * dt), Node.NodeSpace.LOCAL);
    // this.node.translate(cc.v3(0, 0, -speed * dt), Node.NodeSpace.WORLD); 记录一下;有问题
  
var pos: Vec3 = this.node.worldPosition;
pos.z -= (speed *dt)
this.node.worldPosition = pos;
}
  • 3D物体的缩放控制:
    • 缩放是一个三维向量,你分别再x,y,z缩放多少
  • 1: 本地缩放:
    • scale: 获取本地坐标位置 只读不能用于修改坐标位置;
    • setScale: 修改物体本地缩放;
    • getScale: 获取物体的本地缩放; // 创建一个新的内存
  • 2: 世界缩放:
    • worldScale: 获取世界缩放;
    • setWorldScale: 设置世界坐标的缩放;
    • getWorldScale: 获取世界坐标缩放; // (新的内存)
// 缩放, 只读, get新new Vec3内存,set;
this.node.setScale(2, 2, 2);
var scale: Vec3 = this.node.scale; // 同一个内存;
scale = this.node.getScale(); // 新建一个内存;

scale = this.node.worldScale; 

006 Node的_欧拉角_四元数_旋转

欧拉旋转:
  • (1)直观的旋转,任何3D物体,可以把物体旋转某个角度,分别绕x,y,z分别旋转,然后得到一个朝向
  • (2)先后顺序很重要:
    • unity: z-x-y顺序旋转
    • 欧拉: y-z-x
  • (3)欧拉旋转–》3维向量(x,y,z)—》绕x旋转多少度、y、z
  • (4)优缺点:
    • 优点: 简单,直观,非常好控制,
    • 缺点:不方便参与矩阵计算(模型坐标,(平移旋转*缩放矩阵)), 万向节锁;
四元数 -Quat–引擎里面使用 Quat来表示四元数;
  • 缺点:不能直观的表示旋转, 程序一般不直接使用, 引擎内部用四元素计算旋转;

  • 优点:方便参与矩阵计算,没有万向节锁

  • 四元数和欧拉旋转互换:Quat(x,y,z).静态方法、成员方法

    • 四元数—》欧拉角; toEuler(Vec2,四元数,z的取值范围) 成员方法:getEulerAngles(out: Vec3): Vec3;
    • 欧拉角—》四元数:fromEluer(四元数对象,x,y,z);给定一个欧拉角,返回一个可以表示旋转的四元数对象
      • 成员方法/getEulerAngles
  • 【4】3D物体旋转控制:

    • 四元数控制旋转:
      • rotation: 获取本地旋转,返回对象是四元数;
      • worldRotation:世界旋转,返回对象是一个四元数
      • getRotation(是否new四元数对象):如果你是先new传入,name数据就会放到你new的这个对象里面没如果你不new,混熟回帮你new一个
      • getWorldRotation()获取世界旋转,返回一个四元数对象
      • setRotation();设置节点本地(相对父节点)旋转,有两个版本,四元数对象版本(quat),四元数分量(x,y,z,w)
      • setWorldRotation():设置节点世界旋转(叠加附近的旋转角度)、
    • 欧拉角控制旋转
      • eulerAngles: 欧拉角;表示本地旋转
      • setRotationFromEuler: 使用欧拉角来设置物体的本地旋转(x,y,z),绕x轴旋转多少度,绕y轴,z轴多少度。
      • setWorldRotationFromEuler使用世界旋转(x,y,z) Y-Z-X
    • 旋转方法:
      • rotate:
      • lookAt:方向调整:(x,y,z forward(-z方向))—》forward方向(-z)指向某个点;—》前方,对准某个点控制我们角色的朝向
      • lookAt();-z指向哪里(调整物体的旋转)lookAt(目标位置,头顶的方向(默认是0,1,0))设置当前节点旋转为面向目标位置,默认前方为 -z 方向
        • this.node.lookAt(cc.v3(0,10,0));设置node朝向y轴10方向
    • 获取世界坐标系前方: 对于本地坐标(-z是前方)世界坐标(0,0,-1)—>forward—>获取世界前方的方向
  • 四元数插值:

    • 球面插值。slerp。一般使用,平滑过渡我们的旋转[0,1]—》t—>中间的某个状态—》详细的讲解
    • 线性插值。lerp:A+t*(B-A);

007 摄像机的使用

[1].摄像机的模式与成像的原理
  • 1.在底层openGL的设置里没摄像机的概念–》根据摄像机节点—》视角—》‘世界坐标’–》摄像机坐标—》变幻矩阵
  • 2。成像原理:3D游戏—》2D画面—》屏幕上—》 将3D物体–2D影像
    • 透视成像:一般3D游戏成像使用方法。远小近大;
      • 已知一个3D坐标(x,y,z)—》z投影
      • y投影/y = z投影/z —>y投影 = yz投影/z; x投影 = x z投影/z
    • 正交成像
      • 2D中使用,特殊的3D可能需要
[2]摄像机常用组件
  • 摄像机节点+摄像枳的组件实例组成:—》位置,旋转,缩放—》世界坐标—》摄像机坐标系下的一个变换矩阵
  • 摄像机定义了成像模式和相关的参数. 一> 投影;
  • cc.CameraComponent:
  • ClearFlags: 清理屏幕
    • dont_clear不清空.原来有什么就是什么;
    • depth_only只清空深度
    • solid_color清空颜色、深度与模板缓冲;
    • skyBox启用天空盒,只清空深度
  • color: 清理屏幕的颜色;
  • priority: 相机的渲染优先级,值越小越优先渲染
  • depth:清空为指定的深度
  • FOV: 相机的视角大小,
  • near: 相机的近裁剪距离,应在可接受范围内尽量取最大
  • far: 相机的远裁剪距离,应在可接受范围内尽量取最小
  • OrthoHeight正交模式下的视角
  • Projection相机投影模式。分为 透视投影(PERSPECTIVE) 和 正交投影(ORTHO)
  • Rect: 此相机最终渲染到屏幕上的视口位置和大小
  • Stencil清空为指定的模板缓冲
  • TargetTexture: 指定此相机的渲染输出目标贴图,默认为空,直接渲染到屏幕
  • Visibility:可见性掩码,声明在当前相机中可见的节点层级集合。
[3]:第三人称摄像机与第一人称摄像机;
  • 第三人称摄像机:摄像机跟着角色走,但是拍摄的角度不变—》王者荣耀
    • 主角移动,摄像机移动,但不会因为主角的脸的朝向,改变摄像机的事业方向
  • 第一人称:摄像机随着主角的朝向变化而变化—》CS\守望先锋。。。

008 3D模型与动画播放

  • .fbx的流行格式
导入和显示3D模型
  • 导入后的有【fbx模型】+【贴图】
  • 贴图类型要做成texture;fbx[Body,只读材质,动画文件,骨骼文件]
  • 关联贴图:
    • 1.新建文件夹(材料)【material
    • 2.Effect->选择builtin-unlit(高效的3Dshader)
    • 3.下边选择框中勾选[USE texture]
    • 4.下边MainTexture中添加贴图
    • 5.选中模型-body-Materials添加刚刚做好的贴图material
  • 替换材质,渲染组件
模型动画的切割
  • 动画切割及添加
  • 选中资源当中的模型文件(.fbx)文件–>右边属性检查器中–》动画即可分割
显示3D物体组件
  • 绘制3D物体组件:【Meshrander】普通网格–》cc.ModelComponent
    • Mesh: 3D物体网格
    • Material:材质
  • 绘制"蒙皮网格":—》cc.SkinningModelComponent
    • Mesh:3D物体网格
    • Material:材质
4.动画组件的基本原理与代码播放动画
  • 播放动画基本原理—> 动画组件,读取动画文件里面内容,每一帧来修改网格的Mesh顶点的位置—》shader
  • 动画组件是非常消耗性能.
  • 播放动画组件: cc.SkeIetaIAnimationComponent
    • clips: 是当前这个组件带了哪些动画剪辑(attack , clip , run, idle)—>AnimationClip
    • DefauItCIip默认的动画剪辑
    • Play on Load:是否在装载的时候就开始播放,勾选上一一》播放默认的anim clip;
  • 代码里面如何播放动画.
    • stepl: 获取动画播放组件实例–》cc.SkeletalAnimationComponent
    • step2: 动画组件的播放接口,播放你指定的动画,
    • step3: 播放动画的时候注意下, 动画切换—》idle–》atatck;—> 动画过渡;
      • play: 立即切换,播放我们的动画,如果没有给动画的名字—》默认的动画,
      • clips: 获取我们动画组件上面的,所有的动画剪辑—> cc.AnimationClip数组;
      • defaultClip: 默认的动画剪辑,
      • crossFade: 切换动画加上了平滑过渡一》自然;
      • stop:停止播放动画
      • pause:暂停播放;
import { _decorator, Component, Node, SkeletalAnimationComponent } from "cc";
const { ccclass, property } = _decorator;
@ccclass("aniPlay")
export class aniPlay extends Component {
    private anim: SkeletalAnimationComponent = null;
    onLoad(){
        this.anim = this.node.getComponent(SkeletalAnimationComponent);
    }
    start () {
        this.anim.play('Run');
        var that = this;
        this.scheduleOnce(function(){
            that.anim.crossFade('Die')
        },5)
    }
}
  • 可以和2D一样写
import {
  _decorator,
  Component,
  Node,
  AnimationComponent
} from "cc";
private anim: AnimationComponent = null;
  start() {
    this.anim = this.node.getComponent(AnimationComponent);
}
this.anim.play('001_flyjian');

009 常用的Shader与天空盒

  • flip UV翻转
  • builitin-standard 标准
    • USE_ALBEDO_MAP是否使用漫反射贴图?
      • 勾选上,再放入贴图
    • USE_NORMAL_MAP是否使用法线贴图?没增加三角形情况下增加更多细节
【1】Unlit Shader以及重要得参数;
  • (1)builitin-unlit最高效的3D shader, 效果最少把一个3D模型最基本的显示出来—>unlinght不受光照影响;
    • 只是把物体本来的样子显示出来; 贴图—>3D模型上,—>高效—》我们做微信小游戏Unlit;
  • (2) 3D物体渲染队列:
    • opaque(不透明物体渲染队列), transparent(透明物体渲染队列,透明的物体消耗会大一些)
  • (3)Pass只有一个pass; (完整渲染流程)—> pass: 是否使用顶点颜色,
    • a:是否使用顶点颜色,
    • b: 是否使用纹理贴图: MainTexture: 指定贴图的资源;tilling offset
    • c: 是否给她加环境颜色
  • (4)纹理坐标,贴图的基本原理
【2】Stand Shader以及重要参数
  • (1)是否使用顶点颜色:USE VERTEX COLOR
  • (2)是否使用法线贴图:3D的绘制技术,在不增加三角形面的情况下,能加入更多的细节;
  • (3)3D物体的本来的贴图:USE ALBEDO MAP
【3】天空盒
  • (1)在真实的环境中, 我们的四面八方都是被天空包住的,用一个"盒子"来做天空;
    • 摄像机绘制的时候, 纯色—>使用天空盒字
  • (2)天空盒子是一个立方体, 前后上下6个面
  • (3)使用天空盒
    • [1].要配置摄像机的清理模式是天空盒,先绘制天空skyBox
      • camera–>cc.CameraComponent组件–》ClearFlags–skyBox
    • [2].设置天空盒资源
      • 点击场景Scene—skyBox–勾选Enable
    • [3]创建cubemap
      • 在资源管理器中–右键创建cubemap—导入资源
    • [4]在scene中添加刚创建的cubemap

010 Unity场景转creator3D场景搭建示例

011 打造3D路径编辑系统

导入SWS 路径编辑插件

录点:
  • 【1】在unity 开发工具中—窗口(window)—simple Waypoint System—WayPointManager—会创建一个Waypoint Manager节点
  • 【2】选中Waypoint Manager节点—》右边创建名称Enter Path Name—》一般选标准—》Start Path
  • 【3】鼠标移动到目标位置,按p键(就会记录下一个路径点)—》接着点下一个路径点—》直到完成路径—》finishing Path
  • 选路径点时千万不要点鼠标左键
导出路径数据
  • 需要导入一个插件:bycw_roaddata_export.unitypackage
  • 使用
    • 选中刚刚的waypointmanager节点
    • bycw–> 打开一个窗口—》多语言版本的类型
    • 在生成的路径那儿填入路径:/../就会生成一个Waypoint Manager.ts文件
  • 注意导出的路径文件需用单个单词命名,否则会报错Waypoint Manager.ts–》WaypointManager.ts
cocos/Unity路径点数据的坐标差异
  • unity是左手坐标系
    • 赛车x,y,z前方+z
  • cocos 右手坐标系;x,y,z前方是-z.基于cocos开发游戏,前方主角朝向一定要是-z。
  • 总结:路径:cocos,使用unity的路径数据,-x.根据场景来定

012 打造路径导航组件

示例:F:\cocos3D\009_test\assets\scenes\012routePoint

【1】获取路径数据
  • 重点: cocos中主角朝向一定要在-z方向。可以外包父节点模型转到-z方向。
  • 导出的路径数据:
    • 类的静态成员:roads:[路径1,路径2,路径3,...路径数据[p1,p2,p3{x,y,z}...pend]]
【2】设置初始值
  • speed:速度50
  • 让赛车在路径上行走—>walk_on_road()
  • 将赛车放在路径起点位置,录制路径点时下沉了-0.08,要在cocos中纠正。
【3】实现一个一个路径点行走
  • unity路径点坐标和cocos路径点坐标不一样。
    • unity.x == -cocos.x
  • this.vx = this.speed * dir.x / len;
  • this.vy = this.speed * dir.y / len;
  • this.vz = this.speed * dir.z / len;
  • 上一次update没有到,下一次update超过
  • 如何判断从src走到dst:
    • 距离: 是很难判断的,两个点是否重合很难判断,每次更新"一段"距离—》不采用距离;
    • 时间: 时司都没有了,那么这个时候说明我们走到了,
    • 每次update, 根据速度,我更新一点点距离,假设没有行走,那么这个时候,update不要更新.
    • walk_to_next–》lookAt—>朝向前方
【4】实现方向调整和摄像机的平滑过渡

路径点有噪点,路径点本身不是平滑,导致摄像机在一个很短是时间内来回晃动

  • 不是一次性lookAt过来的—>当前的旋转—》目标需安装—》update,不断的插值
【5】高速物体的控制与移动

013 3D物理引擎、刚体、碰撞器、的使用

F:\cocos3D\009_test_physics\assets\scenes
#####【1】编辑物理场景,刚体,碰撞器,物理材质

  • 物理:
    • 动态物体:刚体(运动)+ 形状(碰撞器)
    • 静态物体:形状。不能通过代码来修改物体位置
  • 静态碰撞器:
    • BoxCollider矩形:右键–》component–》BoxCollider
    • SphereColliderComponent球形:右键–》component–》SphereCollider
  • 动态物体:刚体-【计算物理运动,力,速度】
    • RigidBodyComponent刚体:右键–》component–》RigidBody
      • Mass刚体质量
      • LinearDamping线性运动的阻尼
      • AngularDamping角速度阻尼
      • IsKinematic不会受物体运动,但可以改变物体的位置
      • UseGravity是否使用重力
      • FixedRotation刚体是否固定旋转
      • LinearFactor线性速度因子
      • AngularFacto旋转速度因子
碰撞器BoxColliderComponent
  • Material物理材质—》弹力、摩擦系数
    • 右键新建physic-material
    • Restitution弹力
    • Friction摩擦系数
  • IsTrigger是否为触发器。只穿过物体,不改变物体运动。
  • Center中心
  • Size大小
【2】刚体组件实例的相关操作:刚体加力、速度
  • 给这个物理节点添加代码。获取刚体组件实例
  • applyForce加力
import { _decorator, Component, Node, RigidBodyComponent } from "cc";
const { ccclass, property } = _decorator;
@ccclass("physics")
export class physics extends Component {
    start () {
        this.body = this.node.getComponent(RigidBodyComponent);
        // this.body.applyForce(cc.v3(2000,3,500));//加力
        // this.body.setLinearVelocity(cc.v3(5,-3,4));//线性速度
        this.body.setAngularVelocity(cc.v3(0,45,0));// 旋转速度
    }
}
【3】二进制位运算
  • 按位与

  • 按位或

  • 左移:1<<1;0000 0001 <<1 = 0000 0010 左移了一位,右边0补齐。没左移一位*2

  • 右移:0000 0010>> 0000 0001 把右边的去掉,高位,用数字最高位补齐 除以2

  • 非常棒的技巧:可以用一个整数来表示集合(类型的集合)
    8bit的整数位: 0000 0000
    0000 0001 0000 0010 0000 0100 0000 1000
    1<<0 1<<1 1<<2 1<<3 1<<4 1<<5 1<<6 1<<7

  • 做成几个类的类型集合:[1,2,3] 0000 0001 | 0000 0010 | 0000 0100 = 0000 0111

判断,某种类型,是否在这个集合里面 value&(1<<类型)

【4】碰撞器与碰撞检测

BoxColliderComponent,ICollisionEvent,ITriggerEvent
当物理引擎检测到碰撞的时候,可以收到碰撞事件

  • 【1】形状的碰撞,碰撞检测,是基于碰撞器,不是基于刚体
  • 【2】碰撞发生后,会抛出事件,需要监听这些事情。
    • 碰撞开始onCollisionEnter
    • 碰撞持续onCollisionStay
    • 碰撞离开onCollisionExit
  • 【3】要监听碰撞器抛出的这些事件
  • 【4】获取碰撞信息ICollisionEvent
    • type:碰撞事件的类型
    • selfCollider:碰撞中的自己的碰撞器。
    • otherCollider:碰撞中的另一个碰撞器。
    • contacts:碰撞中的所有碰撞点的信息。
  • 特殊碰撞:只触发碰撞检测,不去改变物体的运动;—》(触发器,trigger,sensor)
    • (1)做好一个trigger
  • 【5】trigger或碰撞事件,三种状态,只使用其中一种就行
  • 【6】碰撞配置,我们经常做游戏会遇到:敌人的子弹不会打到敌人。自己的子弹不会打自己
    • 物体碰撞分group
      • 要分好敌人、自己、敌人子弹、自己的子弹,类型:默认default,类型: 1<<0,1<<1,1<<2…32种类型
    • 物体发生碰撞时,会有一个可碰撞的物理集合,32bit整数mask来表示,((1<<0)(1<<1)(1<<2)),0xffffffff—>-1
      • 默认物体碰撞集合-1. 111111111111111111111111111111–>1;
    • 发生碰撞: A(groupA,maskA),B(groupB,maskB)v:
      • 发生碰撞条件:A的碰撞集合里有B(maskA & groupB 为真),B的碰撞集合里有A(maskB & groupA 为真),
      • setGroup/getCroup``setMask/getMask
    • 碰撞分组监测:
      • Group, Mask:
      • A, B两个物体是否发生碰撞,(GroupA & MaskB) && (GroupB & MaskA)
      • setGroup/getCroup
      • setMask/getMask
PhysicsSystem:物理引擎全局设置

console.log(PhysicsSystem.instance)

  • 【1】物理引擎代码模块–》项目设置–》Physics,用到物理引擎,需要勾选上
    • cannon.js 标准物理引擎
    • buildin.js 没有物理计算,只有碰撞检测。代码体积会小
  • 【2】物理引擎全局对象,可以对物理引擎参数设置
    • 1:重力:物理世界的重力值,默认为 (0, -10, 0)
    • 2:enable:是否开启物理系统,默认为 true
    • 3:allowSleep:是否允许物理系统自动休眠,默认为 true
    • 4:maxSubStep:物理每帧模拟的最大子步数,默认为 2
    • 5:deltaTime:物理每步模拟消耗的时间,注意不是每帧,默认为 1 / 60
    • 6:gravity: 物理世界的重力值,默认为 (0, -10, 0)
import {
  _decorator,
  Component,
  Node,
  RigidBodyComponent,
  BoxColliderComponent,
  ICollisionEvent,
  ITriggerEvent,
  PhysicsSystem,
} from "cc";
const {ccclass, property} = _decorator;
@ccclass("physics")
export class physics extends Component {
  private body: RigidBodyComponent = null;
  // private body: BoxColliderComponent = null;
  onLoad() {
    // this.body = this.node.getComponent(RigidBodyComponent);
    // this.body.applyForce(cc.v3(2000,3,500));
  }
  start() {
    this.body = this.node.getComponent(RigidBodyComponent);
    // this.body.applyForce(cc.v3(2000,3,500));//加力
    // this.body.setLinearVelocity(cc.v3(5,-3,4));//线性速度
    // this.body.setAngularVelocity(cc.v3(0,45,0));// 旋转速度
    // 碰撞
    var collider = this.node.getComponent(BoxColliderComponent);
    collider.on("onCollisionEnter", this.onCollisionEnter, this);
    collider.on("onCollisionStay", this.onCollisionStay, this);
    collider.on("onCollisionExit", this.onCollisionExit, this);

    collider.on("onTriggerEnter", this.onTriggerEvent, this);
    collider.on("onTriggerStay", this.onTriggerEvent, this);
    collider.on("onTriggerExit", this.onTriggerEvent, this);

    console.log(collider.getGroup(), collider.getMask())// 1 -1
    collider.setMask((1 << 1) | (1 << 2));  // 设置碰撞关系
    console.log(collider.getGroup(), collider.getMask())// 1 -1
    //  0000 0010    0000 0100    --> 0000 0110
    // cube:[1,mask(0000 0110)],
    // group(0000 0001,mask -1)
    console.log(PhysicsSystem.instance)
  }
  private onCollisionEnter(e: ICollisionEvent): void {
    console.log('onCollisionEnter');
    console.log(e)
  }
  private onCollisionStay(e: ICollisionEvent): void {
    // console.log('onCollisionStay')
  }
  private onCollisionExit(e: ICollisionEvent): void {
    console.log('onCollisionExit')
  }
  private onTriggerEvent(e: ITriggerEvent): void {
    console.log('onTriggerEnter')
    console.log(e);
    console.log(e.type);
  }
  update(dt: number) {
  }
}

014 常用3D_UI开发

  • 可以2D/3D切换
  • canvas
  • [2]UI是独立于3D游戏场景的–》创建一个UI类型–》2D
  • [3]UI Canvas --> UI画布—》Canvas—>设计分辨率:
    • UITransformComponent:画布大小、锚点
    • CanvasComponent:
  • [4]widget
  • 导入资源要改格式。选中src中的图片资源—》属性检查器–》type:sprite-frame
  • 图片九宫格拉伸–》将图片拖入节点中–》选择sliced—》到资源管理器中选中图片—》编辑–九宫格设置

015 事件响应_射线检测_3D拾取

  • DEVICEMOTION 重力感应
【1】系统全局事件监听
  • 1.获取鼠标, 获取键盘,
    • stepl: 全局事件对象的单例;cc.systemEvent
    • step2: 监听我们的事件类型通过API接口查看;
    • step3: destory销毁事件
    • step4: 全局系统事件支持的类型
    • step5: 如何使事件对象
      • EventKeyboard: —>keyCode, 按键码
      • Touch, EventTouch: 支持多点
      • 位置是一个屏幕坐标getLocation;
import { _decorator, Component, Node, systemEvent, SystemEvent, Vec2, SystemEventType,EventTouch,Touch ,EventKeyboard} from "cc";
const { ccclass, property } = _decorator;
@ccclass("gameMgr")
export class gameMgr extends Component {
    start () {
        //鼠标事件
        systemEvent.on(SystemEventType.TOUCH_START,this.onTouchStart,this);
        //重力感应
        // systemEvent.on(SystemEventType.DEVICEMOTION,this.DEVICEMOTION,this);
        // 键盘事件
        systemEvent.on(SystemEventType.KEY_DOWN,this.keyDown,this)
    }
    onTouchStart(touch: Touch,event: EventTouch){
        console.log(touch);
        console.log(event); // 包含touch
        var pos:Vec2 = event.touch.getLocation();
        console.log(pos);
    }
    keyDown(e:EventKeyboard){
        switch (e.keyCode) {
            case 32: console.log(123);// 空格
            break;
        }
        console.log(e)
    }
    destory(){
        super.destroy();// 调用component destory
        console.log('注销事件');
        systemEvent.off(SystemEventType.TOUCH_START,this.onTouchStart,this);
        systemEvent.off(SystemEventType.KEY_DOWN,this.keyDown,this);
    }
}
【2】UI节点时间监听–>和2D一样
  • UI事件会挡住系统事件
【3】屏幕坐标与世界坐标的转换
  • 摄像机坐标转换
  • worldToScreen:世界坐标系—》屏幕坐标
    var w_pos: Vec3 = this.camera.screenToWorld(cc.v3(pos.x, pos.y, 0))
  • screenToWorld:屏幕坐标—》世界坐标系
【4】射线对象
  • 2D坐标–》判断是3D物体

  • 射线检测—》从某个点–》射线—》射线,这条射线碰撞到哪些物体

  • 3D拾取:屏幕点击—》世界坐标,摄像机的世界坐标原点,(点击点-摄像机点),发射一条射线

    • Camera组件:screenPointToRay—>
  • 自己创建射线对象:枪的坐标—》枪.forward—>构建一个自己的射线对象

  • 射线对象:原点(Vec3),方向(Vec3)

【5】射线检测与3D拾取

被点击的物体一定要加物理碰撞器(trigger)

  • step1: 获取射线对象

  • step2: 射线碰撞到了哪些物体(物理模块,射线检测基于物理碰撞器),有没点击到cube

  • step3:使用物理引擎的接口来做射线检测

    • raycast 检测所有的碰撞盒,并记录所有被检测到的结果,通过 PhysicsSystem.instance.raycastResults 访问结果
      • worldRay 世界空间下的一条射线
      • mask 掩码,默认为 0xffffffff
      • maxDistance 最大检测距离,默认为 10000000,目前请勿传入 Infinity 或 Number.MAX_VALUE
      • queryTrigger 是否检测触发器
      • boolean 表示是否有检测到碰撞盒
    • raycastClosest检测所有的碰撞盒,并记录与射线距离最短的检测结果,通过 PhysicsSystem.instance.raycastClosestResult 访问结果
  • geometry 几何体信息

1: systemEvent 全局事件单例;
2: 监听触摸: TOUCH_START/TOUCH_MOVE/TOUCH_END/TOUCH_CANCEL
鼠标事件: MOUSE_DOWN/MOUSE_MOVE/MOUSE_UP/
键盘: KEY_DOWN/KEY_UP
重力感应: DEVICEMOTION

3: UI 节点来监听事件。

1: 摄象机,触摸坐标转射线;
2: 射线检测与3D拾取;

016 Creator3D案例FPS控制《拇指射箭》

  • 1.环境搭建
  • 2.项目创建,导入素材,搭建第一人称场景
  • 3.代码开发基本流程
  • 4.拇指触摸控制
  • 5.第一人称射箭瞄准
  • 6.打包发布

粒子系统

ParticleSystemComponent

3D A* 寻路系统

  • 1.3D地图
  • 2.根据场景生成地图数据–哪些地方可以走,哪些不行。把地图分成对应区域块32*32,每一米为1块。[32X32] = [true,false]
    • 地图烘焙工具
  • 3.编写算法,起点–终点,寻出可达路径
  • 4.导航组件–>从当前点,以固定速度移动到目标点。
寻路算法–》A*/A星/Astar

022 3D游戏摇杆与角色控制

  • 公共组件已保存

Cocos Creator之打包设置横竖屏

构建发布--设备方向

  • Portrait —> 这个是竖屏展示;
  • Upside Down    --> 这个是手机竖屏,但是你的手机需要倒过来;
  • Landscape Left      --> 这个是横屏,屏幕在home键左边;(常用的)
  • Landscape Right    --> 这个也是横屏,屏幕在home右边。

技巧

  • 【1】导入的资源–》prefab—>导入场景中后—》属性检查器中–》右上角还原为普通节点
  • 【2】调整好的视角—》在编辑器中调好视角–》点击camera–》ctrl + shift + F
  • 【3】导入另一个js文件.在编辑器中拖入摇杆组件(携带js文件的组件)
import { UIJoyStick } from "./UIJoyStick";
@property(UIJoyStick)
private stick: UIJoyStick = null;  // 引入js文件
  • 【4】物体受重力落下,掉在平面上的组件配置:
    • 物体需添加2个组件:
      • 刚体:cc.RigidBodyComponent—》UseGravity重力
      • 碰撞器:cc.BoxColliderComponent—》Friction:0.1 Restitution 0弹力
    • 平面:cc.BoxColliderComponent—》不要添加材质
  • 【5】角色在平面上移动,组件配置:
    • 物体需添加2个组件:
      • 刚体:cc.RigidBodyComponent—》UseGravity重力不勾选,
      • 碰撞器:cc.BoxColliderComponent—》不要添加材质
    • 平面:cc.BoxColliderComponent—》不要添加材质
  • 【6】设置墙面,不要让物体穿过
    • 只设置:碰撞器:cc.BoxColliderComponent—》不要添加材质

组件

  • shader着色器
  • billboard 广告牌
  • blockInputEvents
  • boxCollier
  • CapsuleColliderComponent 胶囊碰撞组件
  • skinnedModelComponent 蒙皮网格
    • builitin-standard 标准
    • builitin-unlity 用不用光照都行
import {
    _decorator,
    Component,
    Node,
    RigidBodyComponent, // 刚体 物理组件
    BoxColliderComponent,// 碰撞组件
    ICollisionEvent,
    ITriggerEvent,
    PhysicsSystem, // 物理系统
    systemEvent, // 系统
    SystemEvent,
    Vec2, // 二维向量
    Vec3, // 三维向量
    CanvasComponent, // canvas组件用到
    renderer //
    } from "cc";

_decorator,
Component,
Node,

封装组件

  • 3D摇杆:F:\cocos3D\000_component\摇杆公共组件

Builtins 材质

无光照的 unlit、基于物理光照的 standard、skybox、粒子、sprite 等

  • USE_BATCHING 是否启用动态合批?
  • USE_SKINNING 是否启用顶点蒙皮? 对蒙皮模型必须启用。执行动画添加的材质要勾选上,否则不执行动画
  • USE_VERTEX_COLOR 如果启用,顶点色会与漫反射项相乘
  • USE_ALPHA_TEST 是否开启透明测试(镂空效果)?

方法命名

  • 程序外部使用:public walk_on_road():void {}
  • 程序内部使用:private walk_to_next():void {}
  • 记得消除事件
destory(){
    super.destroy();// 调用component destory
    console.log('注销事件');
    systemEvent.off(SystemEventType.TOUCH_START,this.onTouchStart,this);
}
  • UIJoyStick摇杆、操纵杆

  • 动画非常消耗性能
  • ShadowCastingMode需要显示阴影的模型组件设置为 ON

warning 报错

  • 如果有的看不见了,检查摄像机Visibility是否显示
  • 报错:cannot find module ‘cc’
    • 解决:将temp-declarations-cc.d.ts改为:///
  • 注意向量的异步执行: // var v6 = v4.add(Vec3.UNIT_Y);
    - var v8 = v4.subtract(Vec3.UNIT_Y); // 注意向量的异步执行
  • 只能有一个plane,否则角色找不到plane
  • 如果物体下坠,但没有在平面上停下来,可能是物体外包着空node,没有设置size。

重点 重中之重

  • 1.3D游戏导入的资源,要把图片格式改为sprite-frame格式。否则不显示

TexturePackerUi 使用

  • 1.将要打包的图集拖入工具内
  • 2.在Texture中选择路径并输入文件名,上面也会出现相同的路径和名称
  • 3.Publish即可

笔记

  • tweenUtil、tween
tweenUtil(this.NodePos_arrow)
.stop()
.to(5, { z: targetZ, x: targetX, y: targetY })
.to(1, {})
.call(() => {
    this.gameOver();
})
.start()
  • vmath 是已经废弃的 API,尽量使用 math:
import { Quat, math } from 'cc';
const q_tmp = new Quat();
const out_Q = math.Quat.rotateAround(q_tmp, this.node._quat, cc.v3(), Math.PI * 0.1);
this.node.setRotation(out_Q.x, out_Q.y, out_Q.z, out_Q.w);
  • cc.v3(-1, 1, 0)时旋转变形,加上 cc.v3(-1, 1, 0).normalizeSelf()就不变形了
var out_Q = cc.vmath.quat.rotateAround(q_tmp, this.node._quat, cc.v3(-1, 1, 0), Math.PI * 0.1);
this.node.setRotation(out_Q.x, out_Q.y, out_Q.z, out_Q.w);

let q_tmp = new Quat();
let v_tmp = new Vec3(0, 0, 1);
v_tmp.normalize();
let out_Q = Quat.rotateAround(q_tmp, this.zhizhen.rotation, cc.v3(0,0,1), Math.PI * 0.1);
this.zhizhen.setRotation(out_Q.x, out_Q.y, out_Q.z, out_Q.w);
  • 四元素旋转:
    var out_Q = math.Quat.rotateAround(q_tmp, this.xuanzhaunti.rotation, cc.v3(1, 0, 0), Math.PI * 0.1); //Math.PI * 0.1 * 180 / Math.PI = 18度
    cc.v3(1, 0, 0)是绕X轴旋转,cc.v3(0, 1, 0)是绕Y轴旋转,cc.v3(0, 0, 1)是绕Z轴旋转,

旋转

  onLoad(){
    this.lab.on(Node.EventType.TOUCH_MOVE, this.callback, this);
  }
  callback(EventTouch){
    let dif = EventTouch.getDelta();
    let q_tmp = new Quat();
    let v_tmp = new Vec3(-dif.y, dif.x, 0);
    v_tmp.normalize();
    let out_Q = Quat.rotateAround(q_tmp, this.testNode.rotation, v_tmp, Math.PI * 0.01);
    this.testNode.setRotation(out_Q.x, out_Q.y, out_Q.z, out_Q.w);
  }

四元数 欧拉角

/**
* @zh 根据欧拉角信息计算四元数,旋转顺序为 YZX
*/
static fromEuler<Out extends __internal.$cocos.$core.$math.$type_define.IQuatLike>(out: Out, x: number, y: number, z: number): Out;
/**
* @zh 根据四元数计算欧拉角,返回角度 x, y 在 [-180, 180] 区间内, z 默认在 [-90, 90] 区间内,旋转顺序为 YZX
* @param outerZ z 取值范围区间改为 [-180, -90] U [90, 180]
*/
static toEuler<Out extends __internal.$cocos.$core.$math.$type_define.IVec3Like>(out: Out, q: __internal.$cocos.$core.$math.$type_define.IQuatLike, outerZ?: boolean): Out;
/**
* @zh 将当前四元数转化为欧拉角(x-y-z)并赋值给出口向量。
* @param out 出口向量。
*/
getEulerAngles(out: Vec3): Vec3;
/**
 * @zh 根据欧拉角信息计算四元数,旋转顺序为 YZX
 */
static fromEuler<Out extends __internal.$cocos.$core.$math.$type_define.IQuatLike>(out: Out, x: number, y: number, z: number): Out;
/**
 * @zh 根据四元数计算欧拉角,返回角度 x, y 在 [-180, 180] 区间内, z 默认在 [-90, 90] 区间内,旋转顺序为 YZX
 * @param outerZ z 取值范围区间改为 [-180, -90] U [90, 180]
 */
static toEuler<Out extends __internal.$cocos.$core.$math.$type_define.IVec3Like>(out: Out, q: __internal.$cocos.$core.$math.$type_define.IQuatLike, outerZ?: boolean): Out;
/**
 * @zh
 * 通过欧拉角设置世界旋转
 * @param x - 目标欧拉角的 X 分量
 * @param y - 目标欧拉角的 Y 分量
 * @param z - 目标欧拉角的 Z 分量
 */
setWorldRotationFromEuler(x: number, y: number, z: number): void;
/**
 * @zh
 * 通过欧拉角设置本地旋转
 * @param x - 目标欧拉角的 X 分量
 * @param y - 目标欧拉角的 Y 分量
 * @param z - 目标欧拉角的 Z 分量
 */
setRotationFromEuler(x: number, y: number, z: number): void;
/**
 * @zh
 * 设置本地旋转
 * @param x 目标本地旋转的 X 分量
 * @param y 目标本地旋转的 Y 分量
 * @param z 目标本地旋转的 Z 分量
 * @param w 目标本地旋转的 W 分量
 */

setRotation(x: number, y: number, z: number, w: number): void;
/**
 * @zh
 * 设置本地旋转
 * @param rotation 目标本地旋转
 */
setRotation(rotation: Quat): void;
/**
 * @zh 将当前四元数转化为欧拉角(x-y-z)并赋值给出口向量。
 * @param out 出口向量。
 */
getEulerAngles(out: Vec3): Vec3;

你可能感兴趣的:(小游戏)