DirectX12开发:角色转向异常(绕圆心旋转)的修正方案咨询
问题描述
需要让角色模拟人类正常的转向效果,但当前角色会绕着圆心做圆周运动。
请问如何实现让角色仅根据角度进行小幅转向?
当前使用的代码片段:
double molecule = x * mLastMousePos.x + y * mLastMousePos.y; double denominator = sqrt(x * x + y * y) * sqrt(mLastMousePos.x * mLastMousePos.x + mLastMousePos.y * mLastMousePos.y); double cos_angle = molecule / denominator; double angle = acos((float)cos_angle) * 180 / 3.1415926; int i = 0; for (i = 0; i < mSkinned.size(); i++) { XMMATRIX rotation = XMMatrixRotationY(-angle); XMStoreFloat4x4(&mSkinned[i]->World, XMMatrixMultiply(XMLoadFloat4x4(&mSkinned[i]->World), rotation)); mSkinned[i]->NumFramesDirty = gNumFrameResources; }
解决方案
你的问题根源有两个:
- 直接在世界空间对角色的完整世界矩阵应用旋转,导致角色位置跟着旋转轴(世界原点)做圆周运动,而非自身转向。
- 没有限制单次转向的角度幅度,直接应用计算出的完整角度,转向过于生硬且不符合人类小幅转向的逻辑。
修正步骤:
1. 限制单次转向的最大角度并判断转向方向
人类转向是渐进式的,你需要设置一个最大转向速度(比如每秒最多转30度,需结合帧率调整),将计算出的角度限制在这个范围内,避免一次性转过大角度。另外,当前用acos计算的角度只有0~180度,无法区分左右转向,需要通过2D叉乘判断方向:
// 假设deltaTime是帧间隔时间,单位秒 const float maxTurnSpeed = 30.0f; // 每秒最大转向30度 float rawAngle = (float)(acos(cos_angle) * 180.0 / 3.1415926); // 用叉乘判断转向方向(z分量符号对应左右) float cross = x * mLastMousePos.y - y * mLastMousePos.x; float direction = cross > 0 ? 1.0f : -1.0f; // 限制单次转向角度 float clampedAngle = std::clamp(rawAngle * direction, -maxTurnSpeed * deltaTime, maxTurnSpeed * deltaTime);
2. 基于角色自身局部空间旋转,保持位置不变
不要直接修改完整的世界矩阵,而是提取角色当前的世界位置,仅更新旋转部分,确保转向时角色位置不动:
for (int i = 0; i < mSkinned.size(); i++) { XMMATRIX currentWorld = XMLoadFloat4x4(&mSkinned[i]->World); // 提取角色当前的世界位置(剥离旋转缩放信息) XMVECTOR position = XMVectorSet(currentWorld.r[3].m128_f32[0], currentWorld.r[3].m128_f32[1], currentWorld.r[3].m128_f32[2], 1.0f); // 提取角色当前的旋转状态 XMMATRIX currentRotation = XMMatrixRotationQuaternion(XMQuaternionRotationMatrix(currentWorld)); // 创建绕自身Y轴的局部转向旋转矩阵(转弧度) XMMATRIX turnRotation = XMMatrixRotationY(XMConvertToRadians(clampedAngle)); // 组合新的世界矩阵:先叠加局部转向,再保留原位置 XMMATRIX newRotation = XMMatrixMultiply(currentRotation, turnRotation); XMMATRIX newWorld = XMMatrixMultiply(newRotation, XMMatrixTranslationFromVector(position)); // 保存更新后的世界矩阵 XMStoreFloat4x4(&mSkinned[i]->World, newWorld); mSkinned[i]->NumFramesDirty = gNumFrameResources; }
关键说明:
- 提取位置时要确保只保留世界坐标,避免旋转缩放影响位置计算。
- 使用四元数提取当前旋转可以避免多次矩阵乘法带来的数值漂移问题。
- 转向速度绑定
deltaTime是为了保证不同帧率下,角色的转向速度一致,不会出现帧率高转向快的问题。
内容的提问来源于stack exchange,提问作者user23770211
相关产品推荐
相关产品推荐

