Unity如何让物体朝向移动方向 仅XZ轴旋转避免角色穿地
Unity角色转向穿地问题修复
问题现象
- 实现角色朝向移动方向逻辑时,挂载/移除Rigidbody测试均出现异常:角色初始转向正常,到达目标塔举盾时预制体异常旋转、下陷穿地
- 需求:角色仅在X、Z轴平面完成转向,全程锁定Y轴旋转角度,不产生倾斜
- 附加线索:预制体实例化时子物体位置数值异常,移除Rigidbody后问题未消失
问题根因
问题和Rigidbody无直接关联,核心是代码逻辑存在4个错误:
- 位置锁定逻辑写错:Update开头的位置修正代码同时强制设置
pos.x=0、pos.y=0,会直接把角色拉到世界坐标X=0、Y=0的位置,完全不是锁定Y轴高度的逻辑 - 朝向计算未做水平约束:直接将带Y轴分量的方向向量赋值给
transform.forward,只要目标和角色存在高度差、动画带Y轴偏移、碰撞产生微小Y位移,就会让角色朝向带上俯仰/倾斜角度,触发X、Z轴旋转,最终歪倒穿地 - 攻击状态未做朝向锁定:角色停步攻击时没有持续约束旋转,动画根运动、碰撞抖动都可能带偏角色角度
- 缺少空值防护:场景内无
Block标签物体时,getClosestBlock()返回null,后续直接访问该对象属性会触发报错、中断逻辑
修复方案
修正后完整代码
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BlockRadar : MonoBehaviour { public Transform Tower; private GameObject[] multipleBlocks; public Transform closestBlock; public bool blockContact; public float currentDistance; public float stopDistance; public Animator animator; public int damage; public float attackspeed; private float canAttack; public float moveSpeed = 5f; private Vector3 movement; // 缓存角色初始地面高度,用于锁定Y轴位置 private float _initGroundY; void Start() { closestBlock = null; blockContact = false; // 开局记录角色所在地面的Y轴坐标 _initGroundY = transform.position.y; } void Update() { // 仅锁定Y轴高度,不修改X、Z轴的移动坐标 Vector3 pos = transform.position; pos.y = _initGroundY; transform.position = pos; // 每帧强制清空X、Z轴旋转,从根源避免倾斜 Vector3 rot = transform.eulerAngles; rot.x = 0; rot.z = 0; transform.eulerAngles = rot; closestBlock = getClosestBlock(); // 无目标时直接返回,避免空引用报错 if (closestBlock == null) { animator.SetBool("Moving", false); return; } closestBlock.gameObject.GetComponent<Renderer>().material.color = new Color(1, 0.7f, 0, 1); // 计算方向时强制清空Y轴分量,保证方向完全处于XZ水平面 Vector3 direction = closestBlock.position - transform.position; direction.y = 0; direction.Normalize(); movement = direction; float dist = Vector3.Distance(closestBlock.position, transform.position); if (dist <= 1.5f) { blockContact = true; animator.SetBool("Moving", false); // 攻击状态下也保持朝向目标,避免角度偏移 transform.forward = movement; if (attackspeed <= canAttack) { Attack(); canAttack = 0f; } else { canAttack += Time.deltaTime; } } else { transform.forward = movement; blockContact = false; animator.SetBool("Moving", true); moveCharacter(movement); } } public void Attack() { Debug.Log("ATTACKING!"); Damage(closestBlock.transform); animator.SetTrigger("Attacking"); } void moveCharacter(Vector3 direction) { transform.Translate(direction * moveSpeed * Time.deltaTime, Space.World); } private void OnDestroy() { if (closestBlock !=null) { closestBlock.gameObject.GetComponent<Renderer>().material.color = new Color(0, 0, 0, 0); } } public Transform getClosestBlock() { multipleBlocks = GameObject.FindGameObjectsWithTag("Block"); float closestDistance = Mathf.Infinity; Transform trans = null; foreach (GameObject go in multipleBlocks) { currentDistance = Vector3.Distance(transform.position, go.transform.position); if (currentDistance < closestDistance) { closestDistance = currentDistance; trans = go.transform; } } return trans; } void Damage(Transform block) { Tower_Stats e = block.GetComponent<Tower_Stats>(); if (e != null) { e.TakeDamage(damage); } } }
配套配置调整
- 如果保留Rigidbody组件,在Rigidbody的Constraints栏勾选Freeze Position Y、Freeze Rotation X、Freeze Rotation Z,从物理层面双重锁定倾斜和下陷
- 如果开启了Animator的Apply Root Motion,要么关闭该选项用代码完全控制位移旋转,要么进入举盾动画的剪辑面板,删除根运动轨道上的X/Z旋转、Y轴位移关键帧,避免动画带动角色偏移
- 预制体子物体位置异常排查:修复根节点约束后如果问题仍存在,检查子物体碰撞体是否和角色自身碰撞体产生自碰撞,将角色所有部件放到同一层,在Physics设置里开启该层的层内碰撞忽略即可;如果是骨骼节点坐标异常,重新配置Avatar的骨骼映射,修正绑定错误
内容的提问来源于stack exchange,提问作者E C
相关产品推荐
相关产品推荐

