2D游戏中四元数导致的欧拉角问题

问题表现

问题表现

当旋转角度超过±90度时,欧拉角angle会发生突变(镜像)。

关键日志

我对2D俯视角游戏做了一段测试,一直向右旋转,以下是日志。检查存在的问题,以及可能的原因
[890] [log] [TankController] onKeyDown: 启动向右旋转

[890]  角度: 0,角度: (0, 0, 0),四元数: (0, 0, 0, 1)

[891]  角度: -1.852567240440002,角度: (0, 0, -1.852567240440002),四元数: (0, 0, -0.016165994762118024, 0.9998693217682755)

[892]  角度: -3.151274175755274,角度: (0, 0, -3.151274175755274),四元数: (0, 0, -0.027496588964432335, 0.999621897316841)

[893]  角度: -4.82240442047398,角度: (0, 0, -4.82240442047398),四元数: (0, 0, -0.04207099687944587, 0.9991146236651577)

[894]  角度: -6.369393554469093,角度: (0, 0, -6.369393554469093),四元数: (0, 0, -0.055554827840641655, 0.9984556380248432)

[895]  角度: -7.973678596229254,角度: (0, 0, -7.973678596229254),四元数: (0, 0, -0.06952733385791691, 0.9975800468366485)

[896]  角度: -9.520667730224368,角度: (0, 0, -9.520667730224368),四元数: (0, 0, -0.08298794668916183, 0.9965505510029669)

[897]  角度: -11.182248668365904,角度: (0, 0, -11.182248668365904),四元数: (0, 0, -0.09742872831862506, 0.9952425045677137)

[898]  角度: -12.710139177823004,角度: (0, 0, -12.710139177823004),四元数: (0, 0, -0.11068960150049388, 0.9938550257052896)

[899]  角度: -14.314424208199496,角度: (0, 0, -14.314424208199496),四元数: (0, 0, -0.12459229768041723, 0.9922080222204991)

[900]  角度: -15.947357192458508,角度: (0, 0, -15.947357192458508),四元数: (0, 0, -0.1387181619328161, 0.9903318996932196)

[901]  角度: -17.532543598296986,角度: (0, 0, -17.532543598296986),四元数: (0, 0, -0.15240407123188104, 0.9883182681059517)

[902]  角度: -19.13682862867347,角度: (0, 0, -19.13682862867347),四元数: (0, 0, -0.1662251672151471, 0.9860878225514684)

[903]  角度: -20.884353394311567,角度: (0, 0, -20.884353394311567),四元数: (0, 0, -0.18124312510307772, 0.9834383201822421)

[904]  角度: -22.383595961269823,角度: (0, 0, -22.383595961269823),四元数: (0, 0, -0.19409392329927222, 0.9809829503810432)

[905]  角度: -23.921035788687764,角度: (0, 0, -23.921035788687764),四元数: (0, 0, -0.20723760709814806, 0.9782906389229294)

[906]  角度: -25.47757424064372,角度: (0, 0, -25.47757424064372),四元数: (0, 0, -0.22050655467647512, 0.9753854926872302)

[907]  角度: -27.09140858898105,角度: (0, 0, -27.09140858898105),四元数: (0, 0, -0.23422093967316576, 0.9721833939224734)

[908]  角度: -28.896229262384185,角度: (0, 0, -28.896229262384185),四元数: (0, 0, -0.24950317554975224, 0.9683739801288496)

[909]  角度: -30.280880036579692,角度: (0, 0, -30.280880036579692),四元数: (0, 0, -0.26118588535394294, 0.9652885233399789)

[910]  角度: -31.875615748995326,角度: (0, 0, -31.875615748995326),四元数: (0, 0, -0.27459378505988924, 0.9615603221881004)

[911]  角度: -33.46080215483382,角度: (0, 0, -33.46080215483382),四元数: (0, 0, -0.2878686989341789, 0.9576698868472074)

[912]  角度: -35.05553787863313,角度: (0, 0, -35.05553787863313),四元数: (0, 0, -0.3011679920740851, 0.9535710988437431)

[913]  角度: -36.65027359104877,角度: (0, 0, -36.65027359104877),四元数: (0, 0, -0.3144089575348543, 0.9492876315542335)

[914]  角度: -38.25455863280894,角度: (0, 0, -38.25455863280894),四元数: (0, 0, -0.3276677651759322, 0.9447930120743961)

[915]  角度: -39.839745027263746,角度: (0, 0, -39.839745027263746),四元数: (0, 0, -0.34070566001304387, 0.9401700129418488)

[916]  角度: -41.58726980428551,角度: (0, 0, -41.58726980428551),四元数: (0, 0, -0.3550031086426755, 0.9348651201398183)

[917]  角度: -43.03876578143956,角度: (0, 0, -43.03876578143956),四元数: (0, 0, -0.3668159616009896, 0.9302935291158062)

[918]  角度: -44.63350150523887,角度: (0, 0, -44.63350150523887),四元数: (0, 0, -0.3797266337226064, 0.9250987426441015)

[919]  角度: -46.209138593116506,角度: (0, 0, -46.209138593116506),四元数: (0, 0, -0.3924104704482903, 0.9197902057983394)

[920]  角度: -47.822972941453834,角度: (0, 0, -47.822972941453834),四元数: (0, 0, -0.40532486528377143, 0.9141727154004831)

[921]  角度: -49.40815934729231,角度: (0, 0, -49.40815934729231),四元数: (0, 0, -0.4179317619195534, 0.908478421526245)

[922]  角度: -50.9933457531308,角度: (0, 0, -50.9933457531308),四元数: (0, 0, -0.4304586836242305, 0.9026102822882612)

[923]  角度: -52.79816641515027,角度: (0, 0, -52.79816641515027),四元数: (0, 0, -0.44462084680259645, 0.8957188747528669)

[924]  角度: -54.182817189345755,角度: (0, 0, -54.182817189345755),四元数: (0, 0, -0.45541141620678, 0.8902811027919973)

[925]  角度: -55.78710221972225,角度: (0, 0, -55.78710221972225),四元数: (0, 0, -0.4678303396165951, 0.8838182920341835)

[926]  角度: -57.3627393075999,角度: (0, 0, -57.3627393075999),四元数: (0, 0, -0.4799382584410825, 0.8773022672286563)

[927]  角度: -59.12936270915966,角度: (0, 0, -59.12936270915966),四元数: (0, 0, -0.4934057929271914, 0.8697992432198878)

[928]  角度: -60.54266143723767,角度: (0, 0, -60.54266143723767),四元数: (0, 0, -0.5040955405451472, 0.8636478946900153)

[929]  角度: -62.31883415675831,角度: (0, 0, -62.31883415675831),四元数: (0, 0, -0.5174210201800096, 0.8557309669959818)

[930]  角度: -63.85627397279259,角度: (0, 0, -63.85627397279259),四元数: (0, 0, -0.5288551865951123, 0.8487120781581048)

[931]  角度: -65.31731927929114,角度: (0, 0, -65.31731927929114),四元数: (0, 0, -0.5396330085007303, 0.8419003599811861)

[932]  角度: -67.02664678446955,角度: (0, 0, -67.02664678446955),四元数: (0, 0, -0.5521308797468656, 0.8337574537177778)

[933]  角度: -68.57363592984832,角度: (0, 0, -68.57363592984832),四元数: (0, 0, -0.5633359736611967, 0.8262279230207555)

[934]  角度: -70.09197712134457,角度: (0, 0, -70.09197712134457),四元数: (0, 0, -0.5742337453816203, 0.8186913983089088)

[935]  角度: -71.69626215172106,角度: (0, 0, -71.69626215172106),四元数: (0, 0, -0.5856387992226624, 0.8105721416660198)

[936]  角度: -73.24325129709986,角度: (0, 0, -73.24325129709986),四元数: (0, 0, -0.5965278469297476, 0.8025923796282641)

[937]  角度: -74.82843770293834,角度: (0, 0, -74.82843770293834),四元数: (0, 0, -0.607572967537436, 0.7942638661790891)

[938]  角度: -76.45182136923647,角度: (0, 0, -76.45182136923647),四元数: (0, 0, -0.6187637176254677, 0.785577152003742)

[939]  角度: -78.02745845711414,角度: (0, 0, -78.02745845711414),四元数: (0, 0, -0.6295065928843558, 0.7769951412429359)

[940]  角度: -79.65084212341233,角度: (0, 0, -79.65084212341233),四元数: (0, 0, -0.6404505094457654, 0.7679994433270506)

[941]  角度: -81.25512715378875,角度: (0, 0, -81.25512715378875),四元数: (0, 0, -0.6511394085199261, 0.7589581481691337)

[942]  角度: -83.18408892653184,角度: (0, 0, -83.18408892653184),四元数: (0, 0, -0.6638223742560095, 0.7478902696499764)

[943]  角度: -84.41595064750491,角度: (0, 0, -84.41595064750491),四元数: (0, 0, -0.6718236995987397, 0.7407110885206609)

[944]  角度: -86.04888363176384,角度: (0, 0, -86.04888363176384),四元数: (0, 0, -0.6823102862925674, 0.7310627012913151)

[945]  角度: -87.65316866214063,角度: (0, 0, -87.65316866214063),四元数: (0, 0, -0.6924779846302928, 0.7214390069869858)

[946]  角度: -89.24790438593982,角度: (0, 0, -89.24790438593982),四元数: (0, 0, -0.702450650297128, 0.7117324524687222)

[947]  角度: -89.10961332322378,角度: (180, 180, -89.10961332322378),四元数: (0, 0, -0.712579663493251, 0.7015912080234794)

[948]  角度: -87.54352555330738,角度: (180, 180, -87.54352555330738),四元数: (0, 0, -0.7221012510964865, 0.6917873829182555)

[949]  角度: -86.03473366838816,角度: (180, 180, -86.03473366838816),四元数: (0, 0, -0.7311469485686924, 0.6822200082075355)

[950]  角度: -84.44954726254979,角度: (180, 180, -84.44954726254979),四元数: (0, 0, -0.7405140875364491, 0.6720408366759123)

[951]  角度: -82.86436086809492,角度: (180, 180, -82.86436086809492),四元数: (0, 0, -0.7497395225324027, 0.661733064273567)

[952]  角度: -81.13593472699482,角度: (180, 180, -81.13593472699482),四元数: (0, 0, -0.7596350202344682, 0.6503496259961861)

[953]  角度: -79.55074832115638,角度: (180, 180, -79.55074832115638),四元数: (0, 0, -0.7685585730382474, 0.6397794305926168)

[954]  角度: -78.03240712966011,角度: (180, 180, -78.03240712966011),四元数: (0, 0, -0.776967955078047, 0.6295401470770852)

[955]  角度: -76.44722072382166,角度: (180, 180, -76.44722072382166),四元数: (0, 0, -0.7856019936232534, 0.6187321776141674)

[956]  角度: -74.8620343179832,角度: (180, 180, -74.8620343179832),四元数: (0, 0, -0.7940857002936408, 0.6078058082884353)

大致就是四元数正常,但欧拉角不正常。

直接怀疑引擎为了解决万向锁,内部使用了四元数,在欧拉角超过±90度时,因为四元数转换算法不同。

将(0,0,z)转换为等价的(180,180,f(z))(没算,反正就是和z有关的一个函数,单纯意思意思)这样的形式。

但对于2D游戏而言这就是极大的缺陷,明明只旋转的z轴的angle却导致了xy轴角度的修改(虽然总体等价),但这直接导致了BUG。

问题代码

代码仓库:https://cnb.cool/158com_code/tank_war_cocos

提交分支:feature-cocos

提交哈希:5896dcb249485f193c8e574ad82aecf39979679d

提交信息:chore(TankController): 添加坦克控制器旋转调试日志

提交时间:25.08.18.17:21:16 GMT+0800

问题原因

引擎使用了四元数作为游戏的底层角度。在第三四象限时,四元数解算的结果未保持xy轴旋转角度,从而导致z轴旋转角异常。

问题建议

使用四元数解决万向节锁是不错,但请不要忘了2D游戏中只有z轴,请保持z轴的旋转角度不会发生突变。

即使这可能是四元数本身的特性,但我相信改变他并不难,为了2D游戏着想,给2D游戏额外增加一个转换函数。

最起码也要给开发者提个醒,让我们知道有这么回事。