基于Unity WalkerAgent训练ragdoll时碰撞触发Agent为空问题
我在基于Unity的WalkerAgent训练布偶行走时做了自定义修改,现在布偶任意身体部位碰撞地面时会触发NullReferenceException,报错指向ObjectContact.OnCollisionStay方法中Agent对象为空,无法应用奖励。已确认脚本执行顺序无误,Agent应该在调用前完成初始化。
报错信息
nullreferenceexception: Object reference not set to an instance of an objectUnity.MLAgentsExamples.ObjectContact.OnCollisionStay(UnityEngine.Collision col) (at Assets/Scripts/ObjectContact.cs:55)UnityEngine.Physics:OnSceneContact(PhysicsScene, IntPtr, Int32) (at /Users/bokken/build/output/unity/unity/Modules/Physics/ScriptBindings/PhysicsContact.bindings.cs:49)
代码问题分析
查看JointDriveController.SetupBodyPart方法的逻辑,存在先给未创建的ObjectContact赋值Agent,再判断是否需要创建ObjectContact的顺序错误:
- 先执行
bp.objectContact = t.GetComponent<ObjectContact>();,如果物体上没有该组件,bp.objectContact会是null - 紧接着执行
bp.objectContact.agent = agent;——此时若bp.objectContact为null,已经会触发隐藏的空引用,后续创建组件后的赋值逻辑无法覆盖这个错误状态 - 重复赋值Agent的冗余逻辑也增加了出错概率
修复方案
1. 修正JointDriveController.SetupBodyPart逻辑顺序
确保先获取/创建ObjectContact组件,再赋值Agent:
public void SetupBodyPart(Transform t) { var bp = new BodyPart { rb = t.GetComponent<Rigidbody>(), joint = t.GetComponent<CharacterJoint>(), startingPos = t.position, startingRot = t.rotation }; bp.rb.maxAngularVelocity = k_MaxAngularVelocity; // 先确保ObjectContact组件存在 bp.objectContact = t.GetComponent<ObjectContact>(); if (!bp.objectContact) { bp.objectContact = t.gameObject.AddComponent<ObjectContact>(); } // 获取或创建Agent(注意:gameObject为JointDriveController所在物体,需是WalkerAgent的载体) var agent = gameObject.GetComponent<Agent>(); if (agent == null) { agent = gameObject.AddComponent<Agent>(); Debug.LogWarning($"自动给{gameObject.name}添加了Agent组件"); } // 赋值Agent bp.objectContact.agent = agent; if (bp.objectContact.agent == null) Debug.LogError($"Agent not found for {t.name}"); bp.thisJdController = this; bodyPartsDict.Add(t, bp); bodyPartsList.Add(bp); }
2. 调整ObjectContact.OnCollisionStay的空判断顺序
先检查Agent是否为空,再执行奖励操作,避免空引用:
void OnCollisionStay(Collision col) { // 先检查Agent状态,提前退出 if (agent == null) { Debug.LogError($"Agent is null during OnCollisionStay on {gameObject.name}"); return; } Debug.Log($"OnCollisionStay called for {gameObject.name} with {col.gameObject.name}"); if (col.transform.CompareTag(k_Ground)) agent.AddReward(groundContactPenalty); if (col.transform.CompareTag(k_Wall)) agent.AddReward(wallContactPenalty); }
3. 增加兜底逻辑(可选)
在ObjectContact的Awake方法中添加兜底的Agent查找逻辑,防止Setup阶段赋值失败:
void Awake() { if (agent == null) { agent = GetComponentInParent<Agent>(); if (agent == null) { Debug.LogError($"ObjectContact on {gameObject.name} 无法找到Agent组件"); } } }
额外验证点
- 确认WalkerAgent与JointDriveController挂载在同一物体上,确保
gameObject.GetComponent<Agent>()能正确获取到WalkerAgent(WalkerAgent继承自Agent) - 检查布偶所有身体部位的Rigidbody碰撞检测是否启用,确保碰撞事件能正常触发
内容的提问来源于stack exchange,提问作者Matt Clarke

