CocosCreator3D相机跟随与旋转

前言

CocosCreator引擎版本:CocosCreator3D 3.0

CocosCreator3D正式版,千呼万唤的终于在年前几天更新,我尝试了下,额,虽然还有很大bug,但也理解。

然后我尝试着在相机上做些简单的事情,都知道现在吃鸡或者FPS类的游戏挺火,那相机跟随方面的技术是必不可少的,然后我就搜了下发现这方面的代码在cocoscreator3D上还是挺少的文章的,所有就想跟大家分享下此篇文章

大家互相学习互相进步,文章里如有错误的或者可以改进的地方还请大家指出。

目标

1.学习四元数,理解相机的旋转
2.实现游戏中常见的第一人称视角,第三人称视角

阅读所需知识和引用参考

  1. 向量、矩阵
  2. 3D基础
  3. 四元数与3D旋转实例
  4. 四元数可视化
  5. 三维旋转:欧拉角、四元数、旋转矩阵、轴角之间的转换

初探

基础我就不说了,上面那个大神的博客(四元数与3D旋转实例)讲的很清楚。下面我就直接来点代码

cocos3D的四元数类Qaut.ts帮我们封装了基本上常用的四元数计算、算法和相关公式。虽然我们这篇文章主题是跟随与旋转,但其实绝大部分是针对四元数旋转来说的。
但对于初学四元数的人来说可能会有些生疏,要运用起来没那么容易,这个其实Unity3D里面就做得很好,自带的API里面有很多可以直接调用的方法直接用就可以,而且有很多教程,下面的很多代码我也是大部分有参考unity的四元数方法来做的。

下面我们针对几个方法来讲下Quat类如何使用:

//static rotateY<Out extends IQuatLike>(out: Out, a: Out, rad: number): Out;
//Quat里面很多方法第一个参数都带一个输出的参数,表示的是当前经过计算要输出的四元数,当然,他的返回值也是计算后的四元数。
//下面这句话的意思是绕Y轴旋转指定角度,方法里的单位是以弧度来算的,所以需要转化
this.node.rotation=Quat.rotateY(new Quat(),this.node.rotation,this.currAngle*Math.PI/180);
//该方法是根据欧拉角获取四元数,欧拉角单位是角度
this.node.rotation=Quat.fromEuler(new Quat(),this.angleY,this.angleX,0);
//绕世界空间下指定轴旋转四元数,弧度为单位,绕UP轴
Quat.rotateAround(_quat,this.node.rotation,Vec3.UP,rad);

说下rotateAround这个方法,通过配图看我们可以很清楚的看到,给定原四元数,还有旋转轴和角度(该方法以弧度为单位),就能确定绕着这个轴旋转后的四元数,也就是旋转后的位置。至于具体的计算原理,这些计算四元数的公式其实百度就有一大把,想要更加深入的了解可以看下我上面发的四元数的知识链接。因为本文主要还是针对相机跟随旋转视角来讲,所以就不深入来讲四元数了。

我们还可以看下这三个方法的源码:

public static rotateY<Out extends IQuatLike> (out: Out, a: Out, rad: number) {rad *= 0.5;const by = Math.sin(rad);const bw = Math.cos(rad);const { x, y, z, w } = a;out.x = x * bw - z * by;out.y = y * bw + w * by;out.z = z * bw + x * by;out.w = w * bw - y * by;return out;}public static rotateAround<Out extends IQuatLike, VecLike extends IVec3Like> (out: Out, rot: Out, axis: VecLike, rad: number) {// get inv-axis (local to rot)Quat.invert(qt_1, rot);Vec3.transformQuat(v3_1, axis, qt_1);// rotate by inv-axisQuat.fromAxisAngle(qt_1, v3_1, rad);Quat.multiply(out, rot, qt_1);return out;}public static fromEuler<Out extends IQuatLike> (out: Out, x: number, y: number, z: number) {x *= halfToRad;y *= halfToRad;z *= halfToRad;const sx = Math.sin(x);const cx = Math.cos(x);const sy = Math.sin(y);const cy = Math.cos(y);const sz = Math.sin(z);const cz = Math.cos(z);out.x = sx * cy * cz + cx * sy * sz;out.y = cx * sy * cz + sx * cy * sz;out.z = cx * cy * sz - sx * sy * cz;out.w = cx * cy * cz - sx * sy * sz;return out;}

其实这第一个参数我是觉得是不是有点多余,因为每次都要把多一个参数进去。所以为了更方便使用,我自己又写了个四元数类,去掉了第一个参数。

PS:文章有个Quaternion类都是我自己封装的,和Quat是差不多的,在文章后面的源码有

环绕物体

在cocos3D

环绕物体旋转,没什么好说的,就几句代码,相关说明也有注释了。

本文源码都会在文章末尾放出来。

update(dt:number){//围绕旋转Quaternion.RotationAroundNode(this.node,this.target.position,Vec3.UP,0.5);this.node.lookAt(this.target.position);}

封装这个方法的想法来源于Unity的Transform是有这样一个方法的,这个方法和上面的Quat.rotateAround方法是差不多的,只是这里多封装了一个修改变换的位置,使得Node按照这个旋转弧度去获取位置更改位置。(PS:熟悉unity的应该知道这个方法对比unity的那个还是有点差别,后面我会持续改进,还请继续关注哈)

这里说下RotationAroundNode这个方法,因为后面还会弄得到,这里RotationAroundNode我是直接封装在一个类里面了(源码在后面),里面的具体实现方法是:

/*** 将变换围绕穿过世界坐标中的 point 的 axis 旋转 angle 度。* 这会修改变换的位置和旋转。* @param self 要变换旋转的目标* @param pos 指定围绕的point* @param axis 旋转轴* @param angle 旋转角度*/public static RotationAroundNode(self:Node,pos:Vec3,axis:Vec3,angle:number):Quat{let _quat=new Quat();let v1=new Vec3();let v2=new Vec3();let pos2:Vec3=self.position;let rad=angle* this.Deg2Rad;//根据旋转轴和旋转弧度计算四元数Quat.fromAxisAngle(_quat,axis,rad);//相减,目标点与相机点之间的向量Vec3.subtract(v1,pos2,pos);//把向量dir根据计算到的四元数旋转,然后计算出旋转后的距离Vec3.transformQuat(v2,v1,_quat);self.position=Vec3.add(v2,pos,v2);//根据轴和弧度绕世界空间下指定轴旋转四元数Quat.rotateAround(_quat,self.rotation,axis,rad);return _quat;}

这段代码如果很理解四元数的原理的话是很难理解的,当然你也可以直接用,并不会有什么问题。不过这里还是建议大家理解四元数这个概念,会对3D旋转有个很深的理解。

FPS必备-第一人称跟随

第一人称视角,例如我们在玩赛车的时候,都会有两个视角,一个是从赛车外面看向前方,还有个就是以自己为中心从车里面望向车外了。

  1. 很简单的两句代码,先根据鼠标的偏移量来设置欧拉角,再把欧拉角转换为四元数赋给相机
  2. 注意这里鼠标的X方向表示的是绕Y轴旋转,鼠标Y方向表示的是绕X轴旋转。所有在赋值转换的时候是倒过来的。
  3. 相机抬头低头我还做了个限制角度
private MouseMove(e: EventMouse) {this.angleX+=-e.movementX;this.angleY+=-e.movementY;console.log(this.angleY);this.angleY=this.Clamp(this.angleY,this.xAxisMin,this.xAxisMax);//this.node.rotation=Quat.fromEuler(new Quat(),this.angleY,this.angleX,0);//欧拉角转换为四元数this.node.rotation=Quaternion.GetQuatFromAngle(new Vec3(this.angleY,this.angleX,0));return ; }

上帝视角-第三人称跟随

上帝视角的第三人称跟随,好像不同游戏还有不同的跟随方法,这里我先列举出三个,以后如果还遇到更多的,我再补充。

简单的

这就是一个很简单的跟随,设置好距离目标的高度和距离,获取到相机要走到的目标位置,再对相机进行插值运算。

let temp: Vec3 = new Vec3();
Vec3.add(temp, this.lookAt.worldPosition, new Vec3(0, this.positionOffset.y, this.positionOffset.z));
this.node.position = this.node.position.lerp(temp, this.moveSmooth);

尾随屁股后面的

哈哈,尾随这个词,是不是有点不好听。

好像很多RPG游戏都是这样的视角一直跟在后方,相机是不能随意旋转的,他会自动根据人物的正前方向去旋转

具体说明下面的注释里也有,好像也没什么好说的。还有注释的两句代码是cocos3D原方法的,我用的是我自己略微修改了的封装的方法

 //这里计算出相机距离目标的位置的所在坐标先,距离多高Y,距离多远Z
//下面四句代码等同于:targetPosition+Up*updistance-forwardView*backDistance
let u = Vec3.multiplyScalar(new Vec3(), Vec3.UP, this.positionOffset.y);
let f = Vec3.multiplyScalar(new Vec3(), this.target.forward, this.positionOffset.z);
let pos = Vec3.add(new Vec3(), this.target.position, u);
//本来这里应该是减的,可是下面的lookat默认前方是-z,所有这里倒转过来变为加
Vec3.add(pos, pos, f);
//球形差值移动,我发现cocos只有Lerp差值移动,而我看unity是有SmoothDampV3平滑缓冲移动的,所有我这里照搬过来了一个球形差值
this.node.position = VectorTool.SmoothDampV3(this.node.position, pos, this.velocity, this.moveSmooth, 100000, 0.02);
//cocos的差值移动
//this.node.position=this.node.position.lerp(pos,this.moveSmooth);
//计算前方向
this.forwardView = Vec3.subtract(this.forwardView, this.node.position, this.target.getWorldPosition());
//this.node.lookAt(this.target.worldPosition);
this.node.rotation=Quaternion.LookRotation(this.forwardView);
  1. 我们为了在游戏中更好的实现某一缓动效果,都要利用到插值。如果只是单纯的绑定相互关系,那么实现出来的效果肯定很生硬,但我们加入插值计算之后,就能很好地实现镜头的缓冲效果。
  2. Lerp是线性插值,在两点之间进行插值计算,进行移动
  3. SmoothDampV3平滑缓冲,东西不是僵硬的移动而是做减速缓冲运动到指定位置,Lerp更像是线性衰减,而SmoothDamp像是弧形衰减,两者都是由快而慢

具体关于SmoothDampV3的解释大家可以看这里:unity中lerp与smoothdamp函数使用误区浅析

最后两句代码是相机方向对准目标,两句代码原理是一样的都是这样的:

let _quat = new Quat();
Vec3.normalize(_forward,_forward);
//根据视口的前方向和上方向计算四元数
Quat.fromViewUp(_quat,_forward,_upwards);

如果大家还要看更加深入点的代码原理,可以查看cocos源码,具体源码位置在:

Window位置:F:\CocosDashboard\resources.editors\Creator\3.0.0\resources\resources\3d\engine\cocos\core\math\quat.ts

Mac位置:/Applications/CocosCreator/Creator/3.0.0/CocosCreator.app/Contents/Resources/resources/3d/engine/bin/.cache/dev/editor/transform-cache/fs/cocos/core/math/quat.js
Window位置:

我要自由旋转的

想到这个其实我是在Unity官方的封装好的一个Camera插件Cinemacine里的相机功能看到的。然后我就试着用cocos3D来写一个。遗憾的是还有些瑕疵,后面我会持续改进。

这个可以说是上面那个的升级版,因为这个跟随着的时候相机是可以根据鼠标来上下左右旋转的,而且目标会根据相机的正方向去行走。

PS:需要配套目标人物相关的旋转代码去使用,源码我在文章后方也贴上去了

/*** 实时设置相机距离目标的位置position*/public SetMove() {this._forward = new Vec3();this._right = new Vec3();this._up = new Vec3();Vec3.transformQuat(this._forward, Vec3.FORWARD, this.node.rotation);//Vec3.transformQuat(this._right, Vec3.RIGHT, this.node.rotation);//Vec3.transformQuat(this._up, Vec3.UP, this.node.rotation);this._forward.multiplyScalar(this.positionOffset.z);//this._right.multiplyScalar(this.positionOffset.x);//this._up.multiplyScalar(this.positionOffset.y);let desiredPos = new Vec3();desiredPos = desiredPos.add(this.lookAt.worldPosition).subtract(this._forward).add(this._right).add(this._up);this.node.position = this.node.position.lerp(desiredPos, this.moveSmooth);}/*** 计算根据鼠标X,Y偏移量来围绕X轴和Y轴的旋转四元数* @param e */private SetIndependentRotation(e: EventMouse) {let radX: number = -e.movementX;let radY: number = -e.movementY;let _quat: Quat = new Quat();//计算绕X轴旋转的四元数并应用到node,这里用的是鼠标上下Y偏移量let _right = Vec3.transformQuat(this._right, Vec3.RIGHT, this.node.rotation);_quat = Quaternion.RotationAroundNode(this.node, this.target.position, _right, radY);//获取欧拉角,限制相机抬头低头的范围this.angle = Quaternion.GetEulerFromQuat(_quat);this.angle.x = this.angle.x > 0 ? this.Clamp(this.angle.x, 120, 180) : this.Clamp(this.angle.x, -180, -170);Quat.fromEuler(_quat, this.angle.x, this.angle.y, this.angle.z);this.node.setWorldRotation(_quat);//计算绕Y轴旋转的四元数并应用到node,这里用的是鼠标上下X偏移量_quat = Quaternion.RotationAroundNode(this.node, this.target.position, Vec3.UP, radX);this.node.setWorldRotation(_quat);this.angle = Quaternion.GetEulerFromQuat(_quat);this.MouseX = this.angle.y;this.MouseY = this.angle.x;//console.log(this.MouseX.toFixed(2),this.MouseY.toFixed(2));}

人物目标跟随旋转:

 if (data.keyCode == macro.KEY.a || data.keyCode == macro.KEY.d|| data.keyCode == macro.KEY.w || data.keyCode == macro.KEY.s) {if (this.camera.GetType()== ThirdPersonCameraType.FollowIndependentRotation) {let fq = Quat.fromEuler(new Quat(), 0, this.camera.MouseX, 0);this.node.rotation = Quat.slerp(new Quat(), this.node.rotation, fq, 0.1);}else {//this.node.rotation=Quat.rotateY(new Quat(),this.node.rotation,this.currAngle*Math.PI/180);Quaternion.RotateY(this.node, this.currAngle);}this.node.translate(this.movemenet, Node.NodeSpace.LOCAL);}

上面有句限制绕X轴旋转的代码,我限制了相机的抬头和低头的范围是,角度在120-180和-180->-170,之所以我会很奇葩的两个范围是因为,如果相机正对着物体的时候是0度,从正对到抬头是0-180,从正对到低头是-180-0。所有才会分开判断。这个在unity上好像是没这个问题的,应该是unity自动帮我们处理了

这里要分开设置X轴和Y轴旋转的代码,Unity上只要两句代码就可以,而这了可以看到我分开了好多,可能是我方法不对,我目前还没找到简化代码的办法,如果有大神知道恳请指点

其实通读全文,我们可以看到,用得最多的就是rotationAround,lookAt,transformQuat,transformQuat这几个,只要熟悉了对于旋转就没什么好怕的了。

全文源码(很长):

围绕物体:


import { _decorator, Component, Node, Quat, Vec3 } from 'cc';
import { Quaternion } from './Quaternion';
const { ccclass, property } = _decorator;/*** 围绕着指定物体旋转相机*/
@ccclass('CameraRotationAround')
export class CameraRotationAround extends Component {@property(Node)target:Node=null;private _angle:number=0;update(dt:number){//超级简化版Quaternion.RotationAroundNode(this.node,this.target.position,Vec3.UP,0.5);this.node.lookAt(this.target.position);}
}

第一人称视角:


import { _decorator, Component, Node, EventMouse, systemEvent, Quat, Vec2, Vec3 } from 'cc';
import { Quaternion } from './Quaternion';
const { ccclass, property } = _decorator;/*** 第一人称视角相机*/
@ccclass('FirstPersonCamera')
export class FirstPersonCamera extends Component
{@propertyxAxisMin:number=140;@propertyxAxisMax:number=210;private angleX:number=0;private angleY:number=0;start () {systemEvent.on(Node.EventType.MOUSE_MOVE, this.MouseMove, this);}/*** 根据鼠标偏移量,结合欧拉角进行旋转* @param e */private MouseMove(e: EventMouse) {this.angleX+=-e.movementX;this.angleY+=-e.movementY;console.log(this.angleY);this.angleY=this.Clamp(this.angleY,this.xAxisMin,this.xAxisMax);//this.node.rotation=Quat.fromEuler(new Quat(),this.angleY,this.angleX,0);//欧拉角转换为四元数this.node.rotation=Quaternion.GetQuatFromAngle(new Vec3(this.angleY,this.angleX,0));return ; }private Clamp(val:number,min:number,max:number):number{if(val<=min) val=min;if(val>=max) val=max;return val;}}

第三人称相机跟随:


import { _decorator, Component, Node, EventMouse, systemEvent, Quat, Vec3, Vec2, Enum, ValueType, IVec3Like } from 'cc';
import { Quaternion } from './Quaternion';
import { VectorTool } from './VectorTool';
const { ccclass, property } = _decorator;export enum ThirdPersonCameraType {/** 相机紧跟随着目标,相机不会旋转 */Follow = 0,/** 相机会旋转紧跟着目标正后方,旋转不可控制 */FollowTrackRotation = 1,/** 相机紧跟随着目标,相机可以自由旋转 */FollowIndependentRotation = 2,
}/*** 第三人称相机跟随* 这里总结了三个相机跟随* 1. 相机紧跟随着目标,相机不会旋转* 2. 相机紧跟随着目标,相机会旋转紧跟着目标正后方,旋转不可控制* 3. 相机紧跟随着目标,相机可以自由旋转,角色向前移动的时候,前方向永远是相机的正方向*/
@ccclass('ThirdFreeLookCamera')
export class ThirdFreeLookCamera extends Component {/** 目标 */@property(Node)target: Node = null;/** 注视的目标,这里我想让相机对准目标的上方一点,所有多加了注视(相机正对着)的目标 */@property(Node)lookAt: Node = null;@property({ type: Enum(ThirdPersonCameraType) })cameraType: ThirdPersonCameraType = ThirdPersonCameraType.Follow;/** 距离目标距离 */@propertypositionOffset: Vec3 = new Vec3(0, 120, 200);/** 移动差值移动系数 */@propertymoveSmooth: number = 0.02;/** 差值旋转系数 */@propertyrotateSmooth: number = 0.03;public MouseX: number = 0;public MouseY: number = 0;private _forward: Vec3 = new Vec3();private _right: Vec3 = new Vec3();private _up: Vec3 = new Vec3();private angle: IVec3Like = null;start() {systemEvent.on(Node.EventType.MOUSE_DOWN, this.MouseDown, this);systemEvent.on(Node.EventType.MOUSE_MOVE, this.MouseMove, this);systemEvent.on(Node.EventType.MOUSE_UP, this.MouseUp, this);this.cameraType == ThirdPersonCameraType.Follow && this.node.lookAt(this.target.worldPosition);}isDown: boolean = false;private MouseDown(e: EventMouse) {this.isDown = true;}private MouseMove(e: EventMouse) {if (this.cameraType == ThirdPersonCameraType.FollowIndependentRotation) {this.SetIndependentRotation(e);}}private MouseUp(e: EventMouse) {this.isDown = false;}update(dt: number) {if (this.target) {switch (this.cameraType) {case ThirdPersonCameraType.Follow:this.SetFollow();break;case ThirdPersonCameraType.FollowTrackRotation:this.SetFollowTrackRotation();break;case ThirdPersonCameraType.FollowIndependentRotation:this.SetMove();break;}}}private SetFollow() {let temp: Vec3 = new Vec3();Vec3.add(temp, this.lookAt.worldPosition, new Vec3(0, this.positionOffset.y, this.positionOffset.z));this.node.position = this.node.position.lerp(temp, this.moveSmooth);}private velocity = new Vec3();private forwardView: Vec3 = new Vec3();/*** */private SetFollowTrackRotation() {//这里计算出相机距离目标的位置的所在坐标先,距离多高Y,距离多远Z//下面四句代码等同于:targetPosition+Up*updistance-forwardView*backDistancelet u = Vec3.multiplyScalar(new Vec3(), Vec3.UP, this.positionOffset.y);let f = Vec3.multiplyScalar(new Vec3(), this.target.forward, this.positionOffset.z);let pos = Vec3.add(new Vec3(), this.target.position, u);//本来这里应该是减的,可是下面的lookat默认前方是-z,所有这里倒转过来变为加Vec3.add(pos, pos, f);//球形差值移动,我发现cocos只有Lerp差值移动,而我看unity是有球形差值移动的,所有我这里照搬过来了一个球形差值this.node.position = VectorTool.SmoothDampV3(this.node.position, pos, this.velocity, this.moveSmooth, 100000, 0.02);//cocos的差值移动//this.node.position=this.node.position.lerp(pos,this.moveSmooth);//计算前方向this.forwardView = Vec3.subtract(this.forwardView, this.node.position, this.target.getWorldPosition());this.node.lookAt(this.target.worldPosition);//this.node.rotation=Quaternion.LookRotation(this.forwardView);}/*************************FollowIndependentRotation***************** *//*** 实时设置相机距离目标的位置position*/public SetMove() {this._forward = new Vec3();this._right = new Vec3();this._up = new Vec3();Vec3.transformQuat(this._forward, Vec3.FORWARD, this.node.rotation);//Vec3.transformQuat(this._right, Vec3.RIGHT, this.node.rotation);//Vec3.transformQuat(this._up, Vec3.UP, this.node.rotation);this._forward.multiplyScalar(this.positionOffset.z);//this._right.multiplyScalar(this.positionOffset.x);//this._up.multiplyScalar(this.positionOffset.y);let desiredPos = new Vec3();desiredPos = desiredPos.add(this.lookAt.worldPosition).subtract(this._forward).add(this._right).add(this._up);this.node.position = this.node.position.lerp(desiredPos, this.moveSmooth);}/*** 计算根据鼠标X,Y偏移量来围绕X轴和Y轴的旋转四元数* @param e */private SetIndependentRotation(e: EventMouse) {let radX: number = -e.movementX;let radY: number = -e.movementY;let _quat: Quat = new Quat();//计算绕X轴旋转的四元数并应用到node,这里用的是鼠标上下Y偏移量let _right = Vec3.transformQuat(this._right, Vec3.RIGHT, this.node.rotation);_quat = Quaternion.RotationAroundNode(this.node, this.target.position, _right, radY);this.angle = Quaternion.GetEulerFromQuat(_quat);//限制相机抬头低头的范围this.angle.x = this.angle.x > 0 ? this.Clamp(this.angle.x, 120, 180) : this.Clamp(this.angle.x, -180, -170);Quat.fromEuler(_quat, this.angle.x, this.angle.y, this.angle.z);this.node.setWorldRotation(_quat);//计算绕Y轴旋转的四元数并应用到node,这里用的是鼠标上下X偏移量_quat = Quaternion.RotationAroundNode(this.node, this.target.position, Vec3.UP, radX);this.node.setWorldRotation(_quat);this.angle = Quaternion.GetEulerFromQuat(_quat);this.MouseX = this.angle.y;this.MouseY = this.angle.x;//console.log(this.MouseX.toFixed(2),this.MouseY.toFixed(2));}/*************************FollowIndependentRotation end***************** */private Clamp(val: number, min: number, max: number) {if (val <= min) val = min;else if (val >= max) val = max;return val;}public GetType(): ThirdPersonCameraType {return this.cameraType;}
}

扩展类:

import {  Component, EPSILON, IVec3Like, Mat3, math, Node, quat, Quat, Vec3 } from "cc";/*** 该类属于一个引擎内部的Quat四元数类的一个扩展简化版* 因为本人觉得有些out参数过于多余,在这里就隐藏掉了* 同时也加了些其他的四元数旋转的功能*/
export class Quaternion {private static Deg2Rad:number=(Math.PI) / 180;/*** 绕Y轴旋转置顶节点* @param _node 需要旋转的节点* @param _angle 旋转的角度(是角度不是弧度)*/public static RotateY(_node: Node, _angle: number): Quat {let _quat = new Quat();_node.rotation = Quat.rotateY(_quat, _node.rotation, _angle * this.Deg2Rad);return _quat;}/*** 绕X轴旋转置顶节点* @param _node 需要旋转的节点* @param _angle 旋转的角度(是角度不是弧度)*/public static RotateX(_node: Node, _angle: number): Quat {let _quat = new Quat();_node.rotation = Quat.rotateX(_quat, _node.rotation, _angle * this.Deg2Rad);return _quat;}/*** 绕Z轴旋转置顶节点* @param _node 需要旋转的节点* @param _angle 旋转的角度(是角度不是弧度)*/public static RotateZ(_node: Node, _angle: number): Quat {let _quat = new Quat();_node.rotation = Quat.rotateZ(_quat, _node.rotation, _angle * this.Deg2Rad);return _quat;}/*** 绕世界空间下指定轴旋转四元数* @param _targetQuat 指定要旋转四元数* @param axis 旋转轴* @param _angle 旋转角度*/public static RotateAround(_targetQuat: Quat, axis: Vec3, _angle: number): Quat {let _quat = new Quat();Quat.rotateAround(_quat, _targetQuat, axis, _angle * this.Deg2Rad);return _quat;}/***  绕本地空间下指定轴旋转四元数* @param _targetQuat 指定要旋转四元数* @param axis 旋转轴* @param _angle 旋转角度*/public static RotateAroundLocal(_targetQuat: Quat, axis: Vec3, _angle: number): Quat {let _quat = new Quat();Quat.rotateAroundLocal(_quat, _targetQuat, axis, _angle * this.Deg2Rad);return _quat;}/*** 将变换围绕穿过世界坐标中的 point 的 axis 旋转 angle 度。* 这会修改变换的位置和旋转。* @param self 要变换旋转的目标* @param pos 指定围绕的point* @param axis 旋转轴* @param angle 旋转角度*/public static RotationAroundNode(self:Node,pos:Vec3,axis:Vec3,angle:number):Quat{let _quat=new Quat();let v1=new Vec3();let v2=new Vec3();let pos2:Vec3=self.position;let rad=angle* this.Deg2Rad;Quat.fromAxisAngle(_quat,axis,rad);Vec3.subtract(v1,pos2,pos);Vec3.transformQuat(v2,v1,_quat);self.position=Vec3.add(v2,pos,v2);Quat.rotateAround(_quat,self.rotation,axis,rad);return _quat;}/*** 从四元数得到欧拉角* @param _quat 四元数*/public static GetEulerFromQuat(_quat: Quat): IVec3Like {let angle: IVec3Like = Quat.toEuler(new Vec3(), _quat, true);return angle;}/*** 从欧拉角得到四元数* @param _angle 欧拉角*/public static GetQuatFromAngle(_angle: IVec3Like): Quat {let _quat: Quat = Quat.fromEuler(new Quat(), _angle.x, _angle.y, _angle.z);return _quat;}/*** 四元数差值,在 a 和 b 之间插入 t,然后对结果进行标准化处理。参数 t 被限制在 [0, 1] 范围内。* 该方法比 Slerp 快,但如果旋转相距很远,其视觉效果也更糟糕。* @param _a * @param _b * @param _t */public static Lerp(_a: Quat, _b: Quat, _t: number): Quat {let _quat = new Quat();Quat.lerp(_quat, _a, _b, _t);return _quat;}/*** 四元数球形差值* 在 a 和 b 之间以球形方式插入 t。参数 t 被限制在 [0, 1] 范围内。* @param _a * @param _b * @param _t */public static Slerp(_a: Quat, _b: Quat, _t: number): Quat {let _quat = new Quat();Quat.slerp(_quat, _a, _b, _t);return _quat;}public static LookRotation(_forward: Vec3, _upwards: Vec3 = Vec3.UP): Quat {let _quat = new Quat();Vec3.normalize(_forward,_forward);Quat.fromViewUp(_quat,_forward,_upwards);return _quat;}}import { _decorator, Component, Node, Vec3, IVec3Like } from 'cc';export class VectorTool {public static SmoothDampV3(current:IVec3Like,target:Vec3,currentVelocity:IVec3Like,smoothTime:number,maxSpeed:number,deltaTime:number){let outputX:number=0;let outputY:number=0;let outputZ:number=0;smoothTime=Math.max(0.0001,smoothTime);let omega:number=2/smoothTime;let x:number=omega*deltaTime;let exp:number=1/(1+x+0.48*x*x+0.235*x*x*x);let changX:number=current.x-target.x;let changY:number=current.y-target.y;let changZ:number=current.z-target.z;let originalTo:Vec3=target;let maxChange:number=maxSpeed*smoothTime;let maxChangeSq:number=maxChange*maxChange;let sqrmag:number=changX*changX+changY*changY+changZ*changZ;if(sqrmag>maxChangeSq){let mag:number=Math.sqrt(sqrmag);changX=changX/mag*maxChangeSq;changY=changY/mag*maxChangeSq;changZ=changZ/mag*maxChangeSq;}target.x=current.x-changX;target.y=current.y-changY;target.z=current.z-changZ;let tempX:number=(currentVelocity.x+omega*changX)*deltaTime;let tempY:number=(currentVelocity.y+omega*changY)*deltaTime;let tempZ:number=(currentVelocity.z+omega*changZ)*deltaTime;currentVelocity.x=(currentVelocity.x-omega*tempX)*exp;currentVelocity.y=(currentVelocity.y-omega*tempY)*exp;currentVelocity.z=(currentVelocity.z-omega*tempZ)*exp;outputX=target.x+(changX+tempX)*exp;outputY=target.y+(changY+tempY)*exp;outputZ=target.z+(changZ+tempZ)*exp;let origMinusCurrentX:number=originalTo.x-current.x;let origMinusCurrentY:number=originalTo.y-current.y;let origMinusCurrentZ:number=originalTo.z-current.z;let outMinusOrigX:number=outputX-originalTo.x;let outMinusOrigY:number=outputY-originalTo.y;let outMinusOrigZ:number=outputZ-originalTo.z;if(origMinusCurrentX*outMinusOrigX+origMinusCurrentY*outMinusOrigY+origMinusCurrentZ*outMinusOrigZ>0){outputX=originalTo.x;outputY=originalTo.y;outputZ=originalTo.z;currentVelocity.x=(outputX-originalTo.x)/deltaTime;currentVelocity.y=(outputY-originalTo.y)/deltaTime;currentVelocity.z=(outputZ-originalTo.z)/deltaTime;}return new Vec3(outputX,outputY,outputZ);}public static SmoothDamp(current:number,target:number,currentVelocity:number,smoothTime:number,maxSpeed:number,deltaTime:number){smoothTime=Math.max(0.0001,smoothTime);let num:number=2/smoothTime;let num2:number=num*deltaTime;let num3:number=1/(1+num2+0.48*num2*num2+0.235*num2*num2*num2);let num4:number=current-target;let num5:number=target;let num6:number=maxSpeed*smoothTime;num4=VectorTool.Clamp(num4,-num6,num6);target=current-num4;let num7:number=(currentVelocity+num*num4)*deltaTime;currentVelocity=(currentVelocity-num*num7)*num3;let num8:number=target+(num4+num7)*num3;if(num5-current>0==num8>num5){num8=num5;currentVelocity=(num8-num5)/deltaTime;}return num8;}public static Clamp(val:number,min:number,max:number){if(val<=min) val=min;if(val>=max) val=max;return val;}
}

目标类:

import { _decorator, Component, Node, systemEvent, SystemEventType, macro, EventKeyboard, Vec2, Vec3, Quat, quat } from 'cc';
import { EventManager } from './EventManager';
import { KeyboardExtends } from './KeyboardExtends';
import { Quaternion } from './Quaternion';
import { ThirdFreeLookCamera, ThirdPersonCameraType } from './ThirdFreeLookCamera';
const { ccclass, property } = _decorator;@ccclass('TCar')
export class TCar extends Component {@property(ThirdFreeLookCamera)camera: ThirdFreeLookCamera = null;@propertyspeedVec: Vec2 = new Vec2(5, 5);@propertyangleSpeed: number = 5;private movemenet: Vec3 = Vec3.ZERO;private deltaTime: number = 0;onLoad() {this.movemenet = new Vec3(0, 0, 0);}start() {//systemEvent.on(SystemEventType.KEY_DOWN, this.onKeyDown, this);//systemEvent.on(SystemEventType.KEY_UP, this.onKeyUp, this);EventManager.addEventListener(SystemEventType.KEY_DOWN, this.onKeyDown, this);EventManager.addEventListener(SystemEventType.KEY_UP, this.onKeyUp, this);}private onKeyDown(event, data) {this.movemenet = new Vec3(0, 0, 0);this.currAngle = 0;switch (data.keyCode) {case macro.KEY.w:this.movemenet.z = this.speedVec.x * this.deltaTime * 10;break;case macro.KEY.s:this.movemenet.z = -this.speedVec.x * this.deltaTime * 10;break;case macro.KEY.a:if (this.camera.GetType() == ThirdPersonCameraType.FollowIndependentRotation)this.movemenet.x = this.speedVec.x * this.deltaTime * 10;elsethis.currAngle = this.angleSpeed * this.deltaTime * 10;break;case macro.KEY.d:if (this.camera.GetType() == ThirdPersonCameraType.FollowIndependentRotation)this.movemenet.x = -this.speedVec.x * this.deltaTime * 10;elsethis.currAngle -= this.angleSpeed * this.deltaTime * 10;break;case macro.KEY.c:break;}if (data.keyCode == macro.KEY.a || data.keyCode == macro.KEY.d|| data.keyCode == macro.KEY.w || data.keyCode == macro.KEY.s) {if (this.camera.GetType()== ThirdPersonCameraType.FollowIndependentRotation) {let fq = Quat.fromEuler(new Quat(), 0, this.camera.MouseX, 0);this.node.rotation = Quat.slerp(new Quat(), this.node.rotation, fq, 0.1);}else {//this.node.rotation=Quat.rotateY(new Quat(),this.node.rotation,this.currAngle*Math.PI/180);Quaternion.RotateY(this.node, this.currAngle);}this.node.translate(this.movemenet, Node.NodeSpace.LOCAL);}}private onKeyUp(event) {console.log('key up...');}private currQuat: Quat = new Quat();private currAngle: number = 0;update(dt: number) {this.deltaTime = dt;}}

CocosCreator3D之相机跟随与旋转相关推荐

  1. Unity3dRPG 相机跟随player旋转_轻松记录美好生活——橙影智能摄影机M1众测报告_相机配件...

    2020-11-10 10:08:270点赞0收藏0评论 9月28日 - 11月12日,参与#双11购物攻略#征稿活动,赢取苹果全家桶+8888元超级锦鲤大奖!瓜分十万金币,值得买周边一次全攒齐!品类 ...

  2. Unity3dRPG 相机跟随player旋转_人物头部和眼睛实现跟随目标转动的轻量级IK实践...

    前提 首先需求是在已有模型的基础上,尽量少做模型修改,少修改动画,实现一个眼睛+头部跟随相机的转动 因为美术要求动画使用Generic,所以不能使用Unity自带的Humanoid的IK系统 尝试过市 ...

  3. Unity3dRPG 相机跟随player旋转_跟随式灌装机

    跟随式灌装机,广州市胜川包装设备有限公司是酱料定量灌装机.膏体定量灌装机.液体定量灌装机.三(四)旋盖玻璃瓶旋盖机.螺纹盖自动旋盖机.广口瓶封口机.电磁感应铝箔封口机.真空封罐机.聚脂瓶封口机.真空包 ...

  4. Unity3dRPG 相机跟随player旋转_在胶卷中留下时代的记忆!胶卷相机推荐

    胶片相机以传统的胶卷作为显影介质,需要冲洗后才能得到自己拍摄的作品,而这种略显麻烦的方式,反而被现代的年轻人喜欢. 胶片相机虽然很好玩,但要入门也是不简单的一件事,而种类繁多的选择也让新手玩家无从下手 ...

  5. Unity3dRPG 相机跟随player旋转_记录生活 or 网红自媒体,2019年最新实用Vlog相机推荐...

    社交网络的兴起让人们更愿意去记录世界和生活,人们的目光逐渐从文字转向图片,再到近几年火热的短视频.而Vlog这种形式在当下也是异军突起.相比用手机就可以完成拍摄.制作和发布的短视频,Vlog需要相对专 ...

  6. Unity3dRPG 相机跟随player旋转_【玩码】升降式旋转镜头来袭:三星A80将于7月12日开始预约...

    早在今年 4 月,三星就在泰国发布了 Galaxy A80 手机,现在这款手机的国行版本终于也传来了预约的消息.根据三星 Galaxy 官方微博,Galaxy A80 将会在 7 月 12 日至 7 ...

  7. Unity3dRPG 相机跟随player旋转_运动相机支架设计的16种姿势,必能拍好小片!

    短视频火热的当下, 身边的朋友都开始用视频app, 来记录生活中的点点滴滴, 甚至开始营销自己. 短视频除了软件操作并不繁琐, 而且还有很多简单易用的设备及配件. 今天苏苏要介绍的, 就是一款便携小巧 ...

  8. Unity3dRPG 相机跟随player旋转_「Facebook」应用程序Bug会悄悄访问相机,发紧急声明会修复且更新...

    根据Twitter上的多个报道,当iOS应用程序使用Facebook时,该应用程序似乎在后台访问iPhone或iPad的相机. 当用户刷新Facebook信息流时,一些用户看到相机在后台被激活,如以下 ...

  9. Unity3d--实现第三人称视角(相机跟随)

    实现第三人称视角有三种: 方案1: 最简单的就是 直接 把主相机作为Player角色的子物体,并自行固定好相机的位置 方案2: 设置一个空的GameObject,并且与Player的旋转和位置保持一致 ...

  10. cesium 相机跟随

    一场大的台风,路径通常很长,可能从靠近赤道的太平洋一直往北吹到东三省.跨度这么大,在三维GIS中,往往不容易看全.如果能够实现相机随动效果,即相机跟随台风步进.移动,就生动许多了. 感觉在cesium ...

最新文章

  1. MySQL 学习笔记(12)— 数据类型(定长字符、变长字符、字符串大对象、数字类型、日期时间类型、二进制类型)
  2. c语言logout_C++ 格式化日志输出实现代码
  3. 栈与队列2——两个栈组成队列
  4. ok6410 3.0.1内核调用V4L接口出错解决方法(转)
  5. Matlab:基于Matlab通过GUI实现自动驾驶的车牌智能识别
  6. ant design表格添加loading效果
  7. .NET IdentityServer4实战-开篇介绍与规划
  8. react只停留在表层?五大知识点带你梳理进阶知识
  9. GITHUB来获得UE4源代码
  10. 爱奇艺副总裁离职?本人回应了 曾一手打造现象级节目《中国有嘻哈》
  11. Prometheus Alertmanager 报警模块
  12. !!!css如何让img图片居中?css的display属性实现图片居中(代码实例)
  13. hive udf 分组取top1_Hive中UDF练习
  14. (机器学习)痛苦的Caffe配置之路(win10 教育版+vs2015+cmaker+cpu_only+python接口)
  15. 《Oracle从入门到精通》
  16. POJ3348 Cows
  17. 【赶紧收藏】平面设计必备字体,广告设计常用字体
  18. Web初学-2022.10.22-27
  19. Django项目使用QQ实现第三方登录
  20. 移动通信中的信道编码基础

热门文章

  1. latex中页眉怎么去掉_LaTeX页面布局专题——页眉和页脚
  2. 【搜索力】提高你搜索能力的必备技巧
  3. 禁止搜索引擎收录网站内容,百度,谷歌,所有等...
  4. 中继器,集线器,网桥的区别
  5. 魔百盒B863AV3.2-M,B863AV3.1-M2线刷+卡刷精简固件(S905L3A-B)
  6. 怎么制作U盘启动盘来安装系统
  7. PHP实现输入地址,获取当前位置的经纬度,$lng和$lat即为经纬度的返回值
  8. 2020年你还不会做绿幕特效?这4步基础技巧要点了解一下!
  9. 生成二维码如何制作二维码
  10. Keil 保护视力背景颜色设置