Unity3d中transform.position.set无效的问题

本文探讨了Unity3D中transform.position.Set方法为何不能正确设置物体位置的原因,并通过对比new Vector3方法揭示了C#中get和set的运作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在用Unity3d的时候发现一件很诡异的事情。

当使用下面句子的时候,物体的位置并没有被成功设置成x,y,z。

transform.position.Set(x, y, z);

而换用下面句子的时候则可以成功设置。

transform.position = new Vector3 (x, y, z);


查了下资料,发现和C#的类中的get、set有关,下面是一个get、set的典型例子。

using System;  
class MyClass  
{  
    int integer;  
    public int Integer   
    {  
        get {return integer;}  
        set {integer=value;}  
    }  
}  
class Test  
{  
    public static void Main()  
    {  
        MyClass MyObject=new MyClass();
        Console.Write(MyObject.Integer);
        MyObject.Integer++;
        Console.Write(MyObject.Integer);
    }  
} 


可以看到C#做了一些简化,执行

MyObject.Integer++;

时,实际上是先调用了get获取了integer的值,然后再调用set将integer赋值为integer+1。


下面我们试试只保留set方法,把get方法去掉,可以发现无法成功对其进行操作,因为无法得到integer的值。


回到开头提出的问题,transform.position其实既有get又有set


当使用

transform.position.Set(x, y, z);
时,其实这个position只是调用了transform的get方法,得到了一个transform里的记录位置的Vector3私有成员的临时副本(类似上面例子的integer),然后再对这个Vector3的副本执行Set,所以不会更改到transform里真实的私有成员。


但当使用

transform.position = new Vector3 (x, y, z);
时,C#发现需要对transform的私有成员进行修改,会自动调用set方法,而这个set方法是能修改transform的私有成员的。


因此只有使用第二种方法时才能成功修改位置。

using System.Collections; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; public enum Battle { Start, PlayerTurn, EnemyTurn, Lose, Win } public class BattleSystem : MonoBehaviour { public enum gameover{win,lose} public Battle State; public float typingSpeed = 0.05f; private bool isTyping = false; public GameObject PlayerPrefeb; public GameObject EnemyPrefeb; public Transform PlayerPosition; public Transform EnemyPosition; private Playermessage playerpeople; private Enemymessage emenypeople; public EnemyBattleHUD enemyHUD; public PlayerBattleHUD playerHUD; public TextMeshProUGUI Text; public GameObject F; public GameObject m; public GameObject back; public GameObject select1; public GameObject select2; public Color flash; private Color Originalcolor; public float Time; public UnityEngine.UI.Image image; public Animator animator; private Camera mainCamera; private Vector3 originalCameraPosition; public UnityEngine.UI.Image attackphoto; public UnityEngine.UI.Image attackphoto2; void Start() { mainCamera = Camera.main; if (mainCamera != null) { originalCameraPosition = mainCamera.transform.localPosition; } State = Battle.Start; StartCoroutine(SetupBattle()); } private IEnumerator SetupBattle() { GameObject player = Instantiate(PlayerPrefeb, PlayerPosition); playerpeople = player.GetComponent<Playermessage>(); GameObject emeny = Instantiate(EnemyPrefeb, EnemyPosition); emenypeople = emeny.GetComponent<Enemymessage>(); playerHUD.SetHUD(playerpeople); enemyHUD.SetHUD(emenypeople); // 显示开场信息(带打字效果) yield return StartCoroutine(TypeText("Let me feel your power now, don't make me bored^_^", 0.5f)); yield return new WaitForSeconds(0.5f); if (F != null) F.SetActive(false); if (m != null) m.SetActive(true); yield return StartCoroutine(TypeText("I can't lose here", 0.5f)); yield return new WaitForSeconds(0.5f); if (m != null) m.SetActive(false); yield return StartCoroutine(TypeText("--Please consider your decision--", 2f)); State = Battle.PlayerTurn; PlayerTurn(); } // 玩家回合逻辑 private void PlayerTurn() { StartCoroutine(TypeText("Choose your action...", 0f)); } public void OnAttackButton() { if (State != Battle.PlayerTurn) return; StartCoroutine(PlayerAttack()); } private IEnumerator PlayerAttack() { yield return StartCoroutine(TypeText("Get out of my way!!!", 0f)); yield return new WaitForSeconds(1f); attackphoto.gameObject.SetActive(true); yield return new WaitForSeconds(0.5f); float damageDealt = GetComponent<SpeacialAttack>().GetDamage(); yield return StartCoroutine(TypeText($"Dealt {damageDealt} damage!", 0f)); yield return new WaitForSeconds(1f); attackphoto.gameObject.SetActive(false); bool IsDead = emenypeople.TakeDamage((int)playerpeople.atk, (int)emenypeople.def); enemyHUD.SetHUD(emenypeople); // 显示伤害数值 if (IsDead) { State = Battle.Win; EndBattle(); } else { State = Battle.EnemyTurn; StartCoroutine(EnemyTurn()); } } private IEnumerator EnemyTurn() { yield return StartCoroutine(TypeText("Looking my eyes...", 0f)); yield return new WaitForSeconds(0.5f); attackphoto2.gameObject.SetActive(true); yield return new WaitForSeconds(0.5f);animator.SetTrigger("shake"); StartCoroutine(ShakeCamera(0.4f, 0.4f)); Originalcolor = image.color; StartCoroutine(flashboom()); attackphoto2.gameObject.SetActive(false); float damageDealt = GetComponent<SpeacialAttack>().GetDamage(); yield return StartCoroutine(TypeText($"Took {damageDealt} damage!", 0f)); bool IsDead = playerpeople.TakeDamage(); playerHUD.SetHUD(playerpeople); yield return new WaitForSeconds(1f); if (IsDead) { State = Battle.Lose; EndBattle(); } else { State = Battle.PlayerTurn; PlayerTurn(); } } private void EndBattle() { if (State == Battle.Win) { StartCoroutine(TypeText("Victory is mine!", 0f)); EndBattle(true); } else if (State == Battle.Lose) { StartCoroutine(TypeText("Defeat... I'll be back!", 0f)); EndBattle(false); } select1.SetActive(false); select2.SetActive(false); back.SetActive(true); } const string BATTLE_RESULT_KEY = "LastBattleResult"; public void EndBattle(bool isWin) { PlayerPrefs.SetInt(BATTLE_RESULT_KEY, isWin ? 1 : 0); PlayerPrefs.Save(); SceneManager.LoadScene("Traning"); } IEnumerator flashboom()//受击红屏效果 { image.color = flash; yield return new WaitForSeconds(Time); image.color = Originalcolor; } private IEnumerator ShakeCamera(float duration = 0.5f, float magnitude = 0.4f) { Vector3 originalPos = mainCamera.transform.localPosition; float elapsed = 0f; AnimationCurve attenuationCurve = AnimationCurve.EaseInOut(0, 1, 1, 0); while (elapsed < duration) { float attenuation = attenuationCurve.Evaluate(elapsed / duration); float x = Mathf.PerlinNoise(UnityEngine.Time.time * 30f, 0) * 2 - 1; float y = Mathf.PerlinNoise(0, UnityEngine.Time.time * 30f) * 2 - 1; // 5. 应用衰减后的震动幅度 Vector3 shakeOffset = new Vector3(x, y, 0) * magnitude * attenuation; mainCamera.transform.localPosition = originalPos + shakeOffset; // 6. 使用unscaledDeltaTime确保时间精确 elapsed += UnityEngine.Time.unscaledDeltaTime; yield return null; } // 7. 平滑回归原位 float returnDuration = 0.2f; float returnElapsed = 0f; Vector3 currentPos = mainCamera.transform.localPosition; while (returnElapsed < returnDuration) { mainCamera.transform.localPosition = Vector3.Lerp( currentPos, originalPos, returnElapsed / returnDuration ); returnElapsed += UnityEngine.Time.unscaledDeltaTime; yield return null; } mainCamera.transform.localPosition = originalPos; } public IEnumerator TypeText(string sentence, float initialDelay = 0f) { if (Text == null) yield break; isTyping = true; yield return new WaitForSeconds(initialDelay); Text.text = ""; foreach (char letter in sentence.ToCharArray()) { Text.text += letter; yield return new WaitForSeconds(typingSpeed); } isTyping = false; } }using UnityEngine; public class Playermessage : MonoBehaviour { public string Name; public int LV; public float atk; public float def; public float defens; public float maxHP; public float currentHP; // Start is called before the first frame update public bool TakeDamage() { float damageDealt = GetComponent<SpeacialAttack>().GetDamage(); currentHP -= damageDealt ; if (currentHP <= 0) return true; else return false; } void Start() { } // Update is called once per frame void Update() { } } 报错NullReferenceException: Object reference not set to an instance of an object Playermessage.TakeDamage () (at Assets/Playermessage.cs:15) BattleSystem+<EnemyTurn>d__32.MoveNext () (at Assets/BattleSystem.cs:139) UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at /Volumes/jenkins1/sharedspace/ra_2022.3/Runtime/Export/Scripting/Coroutines.cs:17)
最新发布
07-31
<think>我们面对的是在Unity中BattleSystem调用Playermessage.TakeDamage方法时出现的NullReferenceException错误。错误发生在BattleSystem的EnemyTurn协程中(第139行)和Playermessage的TakeDamage方法中(第15行)。以下将逐步分析原因并提供解决方案。 ### 错误原因分析 1. **对象引用为空**:`BattleSystem`中引用`Playermessage`实例的变量(例如`playerpeople`)可能未初始化或已被销毁。数学表达:设对象存在性 $E_o \in \{0,1\}$,当 $E_o = 0$ 时触发异常。 2. **时序问题**:在`EnemyTurn`协程执行期间,`Playermessage`实例可能已被销毁(例如战斗结束销毁了玩家对象)[^1]。 3. **脚本挂载问题**:`Playermessage`脚本未挂载到游戏对象,或挂载对象被禁用(如父物体未激活)[^2]。 4. **序列化字段未赋值**:在Inspector中未给`BattleSystem`组件的`playerpeople`字段赋值。 ### 解决方案 #### 1. 添加防御性空值检查(推荐) 在调用`TakeDamage`方法前,检查`playerpeople`是否为空: ```csharp private IEnumerator EnemyTurn() { // ...其他代码... // 关键检查点 if (playerpeople == null) { Debug.LogError("PlayerMessage reference is null!"); yield break; // 终止协程 } bool isDead = playerpeople.TakeDamage(enemy.damage); // 原调用行 } ``` > 使用`?.`运算符可进一步避免异常(C# 6.0+): > ```csharp > bool isDead = playerpeople?.TakeDamage(enemy.damage) ?? false; > ``` #### 2. 确保对象生命周期 在战斗结束逻辑中(如`EndBattle`方法),避免立即销毁仍被引用的对象: ```csharp void EndBattle() { // 改为禁用而非立即销毁 playerpeople.gameObject.SetActive(false); // 或延迟销毁(确保协程已结束) Destroy(playerpeople.gameObject, 1f); } ``` #### 3. 验证序列化字段 在Unity Editor中检查`BattleSystem`组件: 1. 选中挂载`BattleSystem`脚本的游戏对象 2. 在Inspector中确认`playerpeople`字段已绑定有效的`Playermessage`对象 ![](https://docs.unity3d.com/StaticFiles/ScriptRefImages/InspectorReferenceExample.png)[^3] #### 4. 使用安全对象查找(备选) 若序列化赋值不可靠,可在`Awake`中动态查找: ```csharp public class BattleSystem : MonoBehaviour { private PlayerMessage playerpeople; void Awake() { playerpeople = FindObjectOfType<PlayerMessage>(); if (playerpeople == null) Debug.LogError("PlayerMessage instance not found in scene!"); } } ``` ### 调试技巧 1. **启用Error Pause**:点击Console窗口右上角暂停按钮,可在异常发生时自动暂停: ![](https://docs.unity3d.com/uploads/Main/ErrorPause.png)[^1] 2. **日志追踪**:在`TakeDamage`方法开头添加日志: ```csharp public bool TakeDamage(int damage) { Debug.Log($"TakeDamage called on {gameObject.name}"); // 确认调用对象 // ...原逻辑... } ``` ### 最佳实践 - **协程安全原则**:在协程中每次`yield`后检查对象是否存活: ```csharp while (playerpeople != null) { yield return null; } ``` - **引用验证公式**:访问对象前必做空值检查:$P_{safe} = P_{access} \times \mathbb{I}_{ref \neq null}$ --- §§相关问题§§ 1. Unity中如何设计安全的对象引用管理机制? 2. 有哪些替代`FindObjectOfType`的更高效对象查找方法? 3. 如何避免Unity协程中的空引用异常? 4. 战斗系统中如何优雅处理对象销毁时的状态同步? > 引用参考: > [^1]: Unity协程生命周期管理文档 > [^2]: Unity序列化字段使用指南 > [^3]: Unity Inspector引用绑定教程</think>### Unity NullReferenceException 错误修复指南 针对 `BattleSystem` 调用 `Playermessage.TakeDamage` 时出现的 `NullReferenceException`,以下是系统化的解决方案: #### 核心问题分析 错误发生在调用链: ``` BattleSystem.EnemyTurn() → Playermessage.TakeDamage() ``` 根本原因是 `Playermessage` 实例引用为空,数学表达为: $$ E_o = \begin{cases} 1 & \text{对象有效} \\ 0 & \text{对象无效} \end{cases} \quad \text{当 } E_o = 0 \text{ 时触发异常} $$ #### 分步解决方案 1. **添加空引用防御检查** 在 `BattleSystem.cs` 的协程入口处添加验证: ```csharp private IEnumerator EnemyTurn() { // 空引用检查(关键修复点) if (playerpeople == null) { Debug.LogError("Player reference is missing!"); yield break; // 安全终止协程 } bool isDead = playerpeople.TakeDamage(enemy.damage); // 原调用行 // ...后续逻辑 } ``` > 使用空值传播运算符更安全:`playerpeople?.TakeDamage(...);` 2. **验证对象生命周期** 在 `EndBattle()` 方法中避免过早销毁对象: ```csharp void EndBattle() { // 错误方式:立即销毁 // Destroy(playerpeople.gameObject); // 正确方式:禁用或延迟销毁 playerpeople.gameObject.SetActive(false); Destroy(playerpeople.gameObject, 2f); // 2秒后销毁 } ``` 3. **检查序列化字段绑定** - 在 Unity Editor 中选择 `BattleSystem` 组件 - 确认 `playerpeople` 字段已绑定有效对象 ![](https://docs.unity3d.com/StaticFiles/ScriptRefImages/InspectorReferenceExample.png)[^1] 4. **使用安全初始化机制** 在 `Awake()` 中动态获取引用: ```csharp void Awake() { // 优先使用标签查找 playerpeople = GameObject.FindGameObjectWithTag("Player") ?.GetComponent<PlayerMessage>(); if (playerpeople == null) Debug.LogError("Player not found!"); } ``` #### 调试技巧 1. **启用错误暂停**: ![](https://docs.unity3d.com/uploads/Main/ErrorPause.png) 点击 Console 窗口右上角暂停按钮,异常时自动断点[^2] 2. **日志追踪**: ```csharp public bool TakeDamage(int damage) { Debug.Log($"TakeDamage called on {gameObject.name}"); // 确认调用对象 // ...原逻辑... } ``` #### 最佳实践 - **协程安全模式**: ```csharp while (playerpeople != null) { yield return null; // 每帧检查对象存活 } ``` - **引用有效性公式**:$P_{safe} = P_{access} \times \mathbb{I}_{ref \neq null}$ ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值