<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Cocos Creator 3.0 3D 之相機(jī)跟隨與旋轉(zhuǎn)

          共 9735字,需瀏覽 20分鐘

           ·

          2021-03-16 20:50

          Cocos Creator 3.0 正式版更新后,許多同學(xué)開始嘗試著在相機(jī)上做些簡單的事情。現(xiàn)在吃雞或者 FPS 類的游戲挺火,相機(jī)跟隨方面的技術(shù)必不可少,但目前 Cocos Creator 3D 這方面的代碼文章尚不多

          所以今天想跟大家分享下此篇文章,通過學(xué)習(xí)四元數(shù),理解相機(jī)的旋轉(zhuǎn),實(shí)現(xiàn)游戲中常見的第一人稱視角及第三人稱視角

          本文轉(zhuǎn)載自 Cocos 中文社區(qū),作者:JoeyHuang312,歡迎閱讀。



          01

          初探


          基礎(chǔ)我就不說了,下面我就直接來點(diǎn)代碼。

          Cocos 3D 的四元數(shù)類 Qaut.ts 幫我們封裝了基本上常用的四元數(shù)計算、算法和相關(guān)公式。

          雖然我們這篇文章主題是跟隨與旋轉(zhuǎn),但其實(shí)絕大部分是針對四元數(shù)旋轉(zhuǎn)來說的。對于初學(xué)四元數(shù)的人來說可能會有些生疏,要運(yùn)用起來沒那么容易,下面我們針對幾個方法來講下 Quat 類如何使用:

          //static rotateY<Out extends IQuatLike>(out: Out, a: Out, rad: number): Out; //Quat里面很多方法第一個參數(shù)都帶一個輸出的參數(shù),表示的是當(dāng)前經(jīng)過計算要輸出的四元數(shù),當(dāng)然,他的返回值也是計算后的四元數(shù)。//下面這句話的意思是繞Y軸旋轉(zhuǎn)指定角度,方法里的單位是以弧度來算的,所以需要轉(zhuǎn)化this.node.rotation=Quat.rotateY(new Quat(),this.node.rotation,this.currAngle*Math.PI/180);//該方法是根據(jù)歐拉角獲取四元數(shù),歐拉角單位是角度this.node.rotation=Quat.fromEuler(new Quat(),this.angleY,this.angleX,0);//繞世界空間下指定軸旋轉(zhuǎn)四元數(shù),弧度為單位,繞UP軸Quat.rotateAround(_quat,this.node.rotation,Vec3.UP,rad);

          說下 rotateAround 這個方法,通過配圖看我們可以很清楚的看到,給定原四元數(shù),還有旋轉(zhuǎn)軸和角度(該方法以弧度為單位),就能確定繞著這個軸旋轉(zhuǎn)后的四元數(shù),也就是旋轉(zhuǎn)后的位置。

          至于具體的計算原理,這些計算四元數(shù)的公式其實(shí)百度就有一大把,想要更加深入的了解可以看下我上面發(fā)的四元數(shù)的知識鏈接。本文主要還是針對相機(jī)跟隨旋轉(zhuǎn)視角來講,就不深入來講四元數(shù)了。

          我們還可以看下這三個方法的源碼:

          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-axis Quat.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;    }

          其實(shí)這第一個參數(shù)我是覺得是不是有點(diǎn)多余,因為每次都要把多一個參數(shù)進(jìn)去。為了更方便使用,我自己又寫了個四元數(shù)類,去掉了第一個參數(shù)。

          PS:文章有個 Quaternion 類都是我自己封裝的,和 Quat 是差不多的,在原文的源碼有。


          02

          環(huán)繞物體


          在 Cocos 3D 環(huán)繞物體旋轉(zhuǎn),沒什么好說的,就幾句代碼,相關(guān)說明也有注釋了。

          update(dt:number)    {        //圍繞旋轉(zhuǎn)        Quaternion.RotationAroundNode(this.node,this.target.position,Vec3.UP,0.5);       this.node.lookAt(this.target.position);    }

          這個方法和上面的 Quat.rotateAround 方法是差不多的,只是這里多封裝了一個修改變換的位置,使得 Node 按照這個旋轉(zhuǎn)弧度去獲取位置更改位置。(PS:后面我會持續(xù)改進(jìn),還請繼續(xù)關(guān)注哈)

          這里說下 RotationAroundNode 這個方法,因為后面還會弄得到,這里 RotationAroundNode 我直接封裝在一個類里面了(源碼在原文后面),里面的具體實(shí)現(xiàn)方法是:

          /**     * 將變換圍繞穿過世界坐標(biāo)中的 point 的 axis 旋轉(zhuǎn) angle 度。     * 這會修改變換的位置和旋轉(zhuǎn)。     * @param self 要變換旋轉(zhuǎn)的目標(biāo)     * @param pos 指定圍繞的point     * @param axis 旋轉(zhuǎn)軸     * @param angle 旋轉(zhuǎn)角度     */    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;        //根據(jù)旋轉(zhuǎn)軸和旋轉(zhuǎn)弧度計算四元數(shù)        Quat.fromAxisAngle(_quat,axis,rad);        //相減,目標(biāo)點(diǎn)與相機(jī)點(diǎn)之間的向量        Vec3.subtract(v1,pos2,pos);        //把向量dir根據(jù)計算到的四元數(shù)旋轉(zhuǎn),然后計算出旋轉(zhuǎn)后的距離        Vec3.transformQuat(v2,v1,_quat);        self.position=Vec3.add(v2,pos,v2);        //根據(jù)軸和弧度繞世界空間下指定軸旋轉(zhuǎn)四元數(shù)        Quat.rotateAround(_quat,self.rotation,axis,rad);        return _quat;    }

          這段代碼如果不是很理解四元數(shù)的原理的話較難消化,當(dāng)然你也可以直接用,并不會有什么問題。不過這里還是建議大家理解四元數(shù)這個概念,會對 3D 旋轉(zhuǎn)有個很深的理解。


          03

          FPS必備-第一人稱跟隨


          第一人稱視角,例如我們在玩賽車的時候,都會有兩個視角,一個是從賽車外面看向前方,還有個就是以自己為中心從車?yán)锩嫱蜍囃饬恕?/p>

          1. 很簡單的兩句代碼,先根據(jù)鼠標(biāo)的偏移量來設(shè)置歐拉角,再把歐拉角轉(zhuǎn)換為四元數(shù)賦給相機(jī) 

          2. 注意這里鼠標(biāo)的X方向表示的是繞Y軸旋轉(zhuǎn),鼠標(biāo)Y方向表示的是繞X軸旋轉(zhuǎn)。所有在賦值轉(zhuǎn)換的時候是倒過來的。

          3. 相機(jī)抬頭低頭我還做了個限制角度

          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);        //歐拉角轉(zhuǎn)換為四元數(shù)        this.node.rotation=Quaternion.GetQuatFromAngle(new Vec3(this.angleY,this.angleX,0));
          return ;     }


          04

          上帝視角-第三人稱跟隨 


          上帝視角的第三人稱跟隨,好像不同游戲還有不同的跟隨方法,這里我先列舉出三個,以后如果還遇到更多的,我再補(bǔ)充。


          05

          簡單的


          這就是一個很簡單的跟隨,設(shè)置好距離目標(biāo)的高度和距離,獲取到相機(jī)要走到的目標(biāo)位置,再對相機(jī)進(jìn)行插值運(yùn)算。

          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);


          06

          尾隨屁股后面


          哈哈,尾隨這個詞,是不是有點(diǎn)不好聽。

          好像很多 RPG 游戲都是這樣的視角,一直跟在后方,相機(jī)是不能隨意旋轉(zhuǎn)的,他會自動根據(jù)人物的正前方向去旋轉(zhuǎn)。

          具體說明下面的注釋里也有,好像也沒什么好說的。還有注釋的兩句代碼是 Cocos 3D 原方法的,我用的是我自己略微修改了的封裝的方法。

          //這里計算出相機(jī)距離目標(biāo)的位置的所在坐標(biāo)先,距離多高Y,距離多遠(yuǎn)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);//本來這里應(yīng)該是減的,可是下面的lookat默認(rèn)前方是-z,所有這里倒轉(zhuǎn)過來變?yōu)榧?/span>Vec3.add(pos, pos, f);//球形差值移動,我發(fā)現(xiàn)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. 我們?yōu)榱嗽谟螒蛑懈玫膶?shí)現(xiàn)某一緩動效果,都要利用到插值。如果只是單純的綁定相互關(guān)系,實(shí)現(xiàn)出來的效果肯定很生硬,但我們加入插值計算之后,就能很好地實(shí)現(xiàn)鏡頭的緩沖效果。

          2. Lerp 是線性插值,在兩點(diǎn)之間進(jìn)行插值計算,進(jìn)行移動。

          3. SmoothDampV3 平滑緩沖,東西不是僵硬的移動而是做減速緩沖運(yùn)動到指定位置,Lerp 更像是線性衰減,而 SmoothDamp 像是弧形衰減,兩者都是由快而慢。

          最后兩句代碼是相機(jī)方向?qū)?zhǔn)目標(biāo),兩句代碼原理是一樣的都是這樣的:

          let _quat = new Quat();Vec3.normalize(_forward,_forward);//根據(jù)視口的前方向和上方向計算四元數(shù)Quat.fromViewUp(_quat,_forward,_upwards);

          如果大家還要看更加深入點(diǎn)的代碼原理,可以查看 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 位置:


          07

          我要自由旋轉(zhuǎn)的


          這個可以說是上面那個的升級版,因為這個跟隨著的時候相機(jī)是可以根據(jù)鼠標(biāo)來上下左右旋轉(zhuǎn)的,而且目標(biāo)會根據(jù)相機(jī)的正方向去行走。

          PS:需要配套目標(biāo)人物相關(guān)的旋轉(zhuǎn)代碼去使用,源碼戳文末原文

          /**     * 實(shí)時設(shè)置相機(jī)距離目標(biāo)的位置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); }
          /** * 計算根據(jù)鼠標(biāo)X,Y偏移量來圍繞X軸和Y軸的旋轉(zhuǎn)四元數(shù) * @param e */ private SetIndependentRotation(e: EventMouse) {
          let radX: number = -e.movementX; let radY: number = -e.movementY; let _quat: Quat = new Quat();
          //計算繞X軸旋轉(zhuǎn)的四元數(shù)并應(yīng)用到node,這里用的是鼠標(biāo)上下Y偏移量 let _right = Vec3.transformQuat(this._right, Vec3.RIGHT, this.node.rotation); _quat = Quaternion.RotationAroundNode(this.node, this.target.position, _right, radY); //獲取歐拉角,限制相機(jī)抬頭低頭的范圍 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軸旋轉(zhuǎn)的四元數(shù)并應(yīng)用到node,這里用的是鼠標(biāo)上下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));    }

          人物目標(biāo)跟隨旋轉(zhuǎn):

          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 軸旋轉(zhuǎn)的代碼,我限制了相機(jī)的抬頭和低頭的范圍是,角度在120-180 和 -180->-170。

          之所以我會設(shè)很奇葩的這兩個范圍是因為,如果相機(jī)正對著物體的時候是 0 度,從正對到抬頭是 0-180,從正對到低頭是 -180-0,所有才會分開判斷。

          這里要分開設(shè)置 X 軸和 Y 軸旋轉(zhuǎn)的代碼,可以看到我分開了好多,可能是我方法不對,我目前還沒找到簡化代碼的辦法,如果有大神知道懇請指點(diǎn)。

          其實(shí)通讀全文,我們可以看到,用得最多的就是 rotationAround、lookAt、transformQuat、transformQuat 這幾個,只要熟悉了對于旋轉(zhuǎn)就沒什么好怕的了。



          以上就是今天的全部分享,歡迎廣大開發(fā)者繼續(xù)挖掘 Cocos Creator 3.0 更多的可能性,點(diǎn)擊【閱讀原文】前往社區(qū)獲取完整源碼,跟原作者快樂交流

          瀏覽 99
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成年人免费看视频 | 欧美超逼视频 | A片大香蕉 | 中国美女正在操b | 成人精品视频网站 |