本文通过示例探讨了当物体的某个轴设置方向之后,其他轴可能会发生偏转的现象及其解决方法。
void Update()
{
Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray,out hit,500f,LayerMask.GetMask("Module")))
{
Debug.Log("鼠标指到物体:"+hit.collider.gameObject.name);
//Debug.DrawLine(hit.point, hit.point + hit.normal, Color.red, 1.0f);
if (preViewModuleObj == null)
{
//生成预览模块物体
preViewModuleObj = Instantiate(ModulePrefabs[1]);
//将生成的预览模块物体上挂载的所有collider组件设置为无效。
Collider[] cols=preViewModuleObj.GetComponents<Collider>();
foreach (Collider col in cols) {
col.enabled = false;
}
}
//对齐预览模块物体
alignPreViewModule(hit);
}
else
{
if(preViewModuleObj!=null) Destroy(preViewModuleObj);
}
}
此例子想要达到的效果是,当鼠标悬停在原始方块的某个表面时,会在鼠标处生成一个预览方块,这个预览方块以鼠标悬停处的原始方块的表面的法线方向为up方向,紧贴原始方块的表面生成,并且其他几个轴也能对齐。所以效果上是在预览方块一侧并排并紧贴生成了一个方块,这个方块的旋转和原始方块是对齐的,但是up方向是鼠标悬停处原始方块的面的法线方向。
代码原理是使用射线检测被鼠标悬停到的原始方块,检测到原始方块后生成一个预览方块,然后使用自定义alignPreViewModule()方法设置预览方块的位置和旋转,这个自定义方法也是本文着重研究的部分。
首先我们试图通过先对齐预览方块的up方向与鼠标悬停处原始方块面的法线方向,之后再对齐预览方块的forward方向与任意一个夹角比较小的原始方块面的法线方向来达到这个效果,代码如下:
///<summary>
/// 对齐预览模块
///</summary>>
private void alignPreViewModule(RaycastHit hit)
{
//设置预览模块的位置和旋转:预览模块的位置中心在鼠标悬停的单位box collider的中心沿鼠标悬停的面的法线方向1.0f(鼠标悬停的单位box collider中心到预览模块单位box collider的中心的距离)距离处,并旋转对齐。
preViewModuleObj.transform.SetPositionAndRotation(hit.transform.position + hit.normal * 1.0f,hit.transform.rotation);
Debug.Log("源方块旋转:"+hit.transform.rotation.eulerAngles+",up方向:"+hit.transform.TransformDirection(Vector3.up)+",right方向:"+ hit.transform.TransformDirection(Vector3.right) + ",预览方块旋转:"+preViewModuleObj.transform.rotation.eulerAngles);
Debug.DrawLine(hit.transform.position,hit.transform.position+hit.transform.up*1.0f,Color.green,1.0f);
Debug.DrawLine(hit.transform.position, hit.transform.position + hit.transform.right * 1.0f, Color.red, 1.0f);
//对齐预览模块的up和hit面的法线方向
preViewModuleObj.transform.up = hit.normal;
Debug.Log("up朝向hit.normal后,预览方块旋转:"+preViewModuleObj.transform.rotation.eulerAngles+",预览方块up:"+preViewModuleObj.transform.TransformDirection(Vector3.up));
//找出预览模块的forward方向与hit.collider的6个面法线方向夹角最小的法线方向,并让预览模块的forward方向与此方向对齐。
float minAngle = 90f; int minAngleIndex = -1; float currentAngle;
for (int i = 0; i < dir6.Length; i++)
{
currentAngle = Vector3.Angle(preViewModuleObj.transform.forward, hit.transform.TransformDirection(dir6[i]));
if (currentAngle < minAngle)
{
minAngle = currentAngle;
minAngleIndex = i;
}
}
Debug.Log("预览方块forward与6法线最小夹角为:" + minAngle + "°" + ",此法线为:" + dir6[minAngleIndex]+",方向为:"+ hit.transform.TransformDirection(dir6[minAngleIndex]));
//对齐预览方块的forward方向
preViewModuleObj.transform.forward = hit.transform.TransformDirection(dir6[minAngleIndex]);
Debug.Log("对齐forward后预览方块旋转为:"+preViewModuleObj.transform.rotation.eulerAngles+",预览方块up:"+preViewModuleObj.transform.TransformDirection(Vector3.up)+",预览方块forward:"+preViewModuleObj.transform.TransformDirection(Vector3.forward));
}
程序运行后发现并不能达到预期的对齐效果:



发现预览效果与原始方块不能正确对齐:
检查Debug输出:

发现是在第二次设置方向的时候,也就是设置forward方向的时候,up方向发生了一些偏转,导致最终预览方块不能对齐。
经过研究,发现可以使用Quaternion.LookRotation()方法来解决这个问题。Quaternion.LookRotation()方法有两个版本,一个版本是单一参数,还有一个版本具有两个参数。
单一参数的Quaternion.LookRotation(Vector3 forward)方法可以将物体的forward朝向目标方向,但是同样不能保证其他轴的朝向不发生偏转。而两个参数的Quaternion.LookRotation(Vector3 forward,Vector3 upward)方法,可以同时对准forward和upward两个方向,这样就可以精确的确定物体的旋转。下面我们用这个方法来试试看。
///<summary>
/// 对齐预览模块
///</summary>>
private void alignPreViewModule(RaycastHit hit)
{
//设置预览模块的位置和旋转:预览模块的位置中心在鼠标悬停的单位box collider的中心沿鼠标悬停的面的法线方向1.0f(鼠标悬停的单位box collider中心到预览模块单位box collider的中心的距离)距离处,并旋转对齐。
preViewModuleObj.transform.SetPositionAndRotation(hit.transform.position + hit.normal * 1.0f,hit.transform.rotation);
Debug.Log("源方块旋转:"+hit.transform.rotation.eulerAngles+",up方向:"+hit.transform.TransformDirection(Vector3.up)+",right方向:"+ hit.transform.TransformDirection(Vector3.right) + ",预览方块旋转:"+preViewModuleObj.transform.rotation.eulerAngles);
Debug.DrawLine(hit.transform.position,hit.transform.position+hit.transform.up*1.0f,Color.green,1.0f);
Debug.DrawLine(hit.transform.position, hit.transform.position + hit.transform.right * 1.0f, Color.red, 1.0f);
//对齐预览模块的up和hit面的法线方向
preViewModuleObj.transform.up = hit.normal;
Debug.Log("up朝向hit.normal后,预览方块旋转:"+preViewModuleObj.transform.rotation.eulerAngles+",预览方块up:"+preViewModuleObj.transform.TransformDirection(Vector3.up));
//找出预览模块的forward方向与hit.collider的6个面法线方向夹角最小的法线方向,并让预览模块的forward方向与此方向对齐。
float minAngle = 90f; int minAngleIndex = -1; float currentAngle;
for (int i = 0; i < dir6.Length; i++)
{
currentAngle = Vector3.Angle(preViewModuleObj.transform.forward, hit.transform.TransformDirection(dir6[i]));
if (currentAngle < minAngle)
{
minAngle = currentAngle;
minAngleIndex = i;
}
}
Debug.Log("预览方块forward与6法线最小夹角为:" + minAngle + "°" + ",此法线为:" + dir6[minAngleIndex]+",方向为:"+ hit.transform.TransformDirection(dir6[minAngleIndex]));
//preViewModuleObj.transform.forward = hit.transform.TransformDirection(dir6[minAngleIndex]);
//同时对齐预览模块的forward方向和upward方向,因为如果只对齐forward方向的话,upward方向可能会偏转。
preViewModuleObj.transform.rotation = Quaternion.LookRotation(hit.transform.TransformDirection(dir6[minAngleIndex]),hit.normal);
Debug.Log("对齐forward后预览方块旋转为:"+preViewModuleObj.transform.rotation.eulerAngles+",预览方块up:"+preViewModuleObj.transform.TransformDirection(Vector3.up)+",预览方块forward:"+preViewModuleObj.transform.TransformDirection(Vector3.forward));
}
运行之后结果如下:



我们看到,这是预览方块就能正确对齐了。

可以看到同时设置两个轴向的朝向以后就圆满的解决了这个问题。
以上就是Unity中当物体一个轴设置朝向的时候其他轴可能发生偏转的问题的解决方法了。如果对大家有所帮助或启发,别忘了三连加关注哦!
¥169.00
steam 女神异闻录5皇家版 国区激活码CDKey P5R 女神异闻录5S乱战 合集Persona 5 Strikers Royal PC游戏正版
¥90.00
逃离塔科夫Escape From Tarkov 逃离塔克夫 黑边版 全球版 黑边升级包 塔可夫 激活码 中文正版游戏PC
¥19.80
XGPU2个月充值卡Xbox Game Pass Ultimate一年123年终极会员xgp14天pc EA Play金会员pgp兑换码激活码礼品卡
¥49.00
PC中文steam 鬼泣5 国区激活码 cdkey 鬼泣五 Devil May Cry 5 DMC5 正版 Vergil 维吉尔DLC游戏
¥143.00
xgpu兑换码 三年 3年 xbox 微软会员 一年 1年 老用户36个月 代充 xgp 金会员 1 2个月 13个月激活码充值秒发
¥45.00
Uplay 彩虹六号围攻 CDK激活码 彩虹6号 彩虹6号特勤干员 Y8豪华版终极 Y7终极版 PC游戏育碧正版中文