实例赋值为null但仍能引用属性?

huangapple go评论97阅读模式
英文:

Instance assignment is null but still able to reference properties?

问题

感谢阅读我的帖子。我正在为一个文本游戏编写一个战斗脚本。在编写时,它曾经能够正常工作,但现在出现了最奇怪的错误。

我创建了两个名为 Character 的对象,称为 charAcharB。我从已实例化的 Character 对象数组中随机选择了两个索引分配给 charAcharB。Debug.Log 告诉我 characters[indexA] 为 null,因此 charA 也为 null,所以我的 combatEncounter() 函数引发了异常,但 charA.Name 显示在 TMP 组件中的名称。所有 charA 的属性在 UI 中都显示正确,但类实例本身为 null。我不知道如何修复这个问题,感觉自己漏掉了重要的东西。

以下是您的代码的翻译部分:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class ButtonGenerateName : MonoBehaviour
{
    // 省略部分声明

    // 开始方法
    void Start()
    {
        // 初始化各种组件

        for (int i = 0; i < NumberOfCharacters; i++) 
        {
            // 生成角色名字和属性

            // 创建角色并添加到列表
        }
        displayName();
        displayStory();
    }

    public void displayName()
    {
        // 检查是否有足够的角色供选择

        // 随机选择两个不同的索引
        int indexA = UtilityFunctions.GetRandomInt(0, characters.Count - 1);
        int indexB;
        do
        {
            indexB = UtilityFunctions.GetRandomInt(0, characters.Count - 1);
        } while (indexA == indexB);

        charA = characters[indexA];
        charB = characters[indexB];
        
        // 设置名称到 TextMeshPro 组件
        combatantNameA.text = charA.Name;
        combatantNameB.text = charB.Name;

        // 设置生命值到 TextMeshPro 组件
        combatanthpA.text = $"生命值: {charA.CharacterStats.Health.ToString()}";
        combatanthpB.text = $"生命值: {charB.CharacterStats.Health.ToString()}";

        // 设置武器名字到 TextMeshPro 组件
        combatantWpnA.text = $"{charA.CharacterWeapon.Name}, 伤害: ({charA.CharacterWeapon.Damage})";
        combatantWpnB.text = $"{charB.CharacterWeapon.Name}, 伤害: ({charB.CharacterWeapon.Damage})";

        // 设置力量到 TextMeshPro 组件
        combatantstrA.text = $"力量: {charA.CharacterStats.Strength.ToString()}";
        combatantstrB.text = $"力量: {charB.CharacterStats.Strength.ToString()}";

        // 设置敏捷度到 TextMeshPro 组件
        combatantdexA.text = $"敏捷: {charA.CharacterStats.Agility.ToString()}";
        combatantdexB.text = $"敏捷: {charB.CharacterStats.Agility.ToString()}";
    }

    public void displayStory()
    {
        // 生成故事

        // 设置故事到 TextMeshPro 组件
        storyPanel.text = paragraph;
    }

    private string combatEncounter(Character A, Character B)
    {
        if(A == null || B == null) 
        {
            Debug.Log("A: " + A);
            Debug.Log("B: " + B);
            Debug.LogError("一个或两个角色为 null。");
            return "";
        }

        string whatHappened = "";
        string turn = "";
        int damageA;
        int damageB;
        int hpA;
        int hpB;

        damageA = A.CharacterWeapon.Damage;
        damageB = B.CharacterWeapon.Damage;
        hpA = A.CharacterStats.Health;
        hpB = B.CharacterStats.Health;
        
        while (hpA > 0 && hpB > 0)
        {
            // 处理战斗逻辑并记录下来

            whatHappened += $"{turn}\n\n";
        }
        return whatHappened;
    }
}

如果您需要进一步的帮助或有其他问题,请随时提出。

英文:

thank you for reading my post. I am working on a combat script for a text game. I got it to work once while writing it but now getting the most bizarre error.

I am making two Character objects called charA, charB. I am taking my array of instantiated Character objects, and assigning two random indices to charA, charB. The Debug.Log is telling me that characters[indexA] is null, and thus charA is null, so my combatEncounter() function is throwing an exception, and yet charA.Name displays the name in the TMP component. All of the charA stats are showing properly in the UI, but the class instance itself is null. I have no idea how to fix this and I feel like I'm missing something vital.

My code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class ButtonGenerateName : MonoBehaviour
{
private GenerateName generatorName;
private GenerateWord generatorWord;
private GameController gc;
private Character character;
private Character charA;
private Character charB;
private BuildingBlocks bb;
public TextMeshProUGUI combatantNameA;
public TextMeshProUGUI combatantNameB;
public TextMeshProUGUI storyPanel;
public TextMeshProUGUI combatanthpA;
public TextMeshProUGUI combatanthpB;
public TextMeshProUGUI combatantWpnA;
public TextMeshProUGUI combatantWpnB;
public TextMeshProUGUI combatantstrA;
public TextMeshProUGUI combatantstrB;
public TextMeshProUGUI combatantdexA;
public TextMeshProUGUI combatantdexB;
public int NumberOfCharacters = 20;
public List&lt;Character&gt; characters = new List&lt;Character&gt;();
// Start is called before the first frame update
void Start()
{
generatorName = GetComponent&lt;GenerateName&gt;();
generatorWord = GetComponent&lt;GenerateWord&gt;();
character = GetComponent&lt;Character&gt;();
bb = GetComponent&lt;BuildingBlocks&gt;();
// Initialize GameController
gc = GetComponent&lt;GameController&gt;();
for (int i = 0; i &lt; NumberOfCharacters; i++) 
{
if (generatorName != null)
{
Debug.Log($&quot;Loop `{i}` is entered!&quot;);
/**
* * NAME GENERATION:
* * PREFIX =&gt; FIRST NAME =&gt; LAST NAME (DYNASTY) =&gt; SUFFIX =&gt; TITLE
**/
name = $&quot;{bb.PREFIXES[UtilityFunctions.GetRandomInt(0, bb.PREFIXES.Count)]} {generatorName.nameGen(UtilityFunctions.GetRandomInt(1, 5))} {generatorName.nameGen(UtilityFunctions.GetRandomInt(1, 5))} {bb.SUFFIXES[UtilityFunctions.GetRandomInt(0, bb.SUFFIXES.Count)]} {bb.TITLES[UtilityFunctions.GetRandomInt(0, bb.TITLES.Count)]}&quot;;
}
Debug.Log(&quot;NameGen GameController: &quot; + name);  
Stats stats = new Stats() { Health = Random.Range(50, 150), Strength = Random.Range(3, 12), Agility = Random.Range(3, 12), Intelligence = Random.Range(3, 12) };
Weapon weapon = new Weapon() { Name = bb.WEAPONS[UtilityFunctions.GetRandomInt(0, bb.WEAPONS.Count)], Damage = Random.Range(5, 120) };
List&lt;Trait&gt; traits = new List&lt;Trait&gt;() { new Trait() { Name = &quot;Brave&quot;, Description = &quot;Never backs down from a fight&quot; } };
//GameObject characterGO = Instantiate(CharacterPrefab);
character = new Character(name, stats, weapon, traits);
characters.Add(character);
Debug.Log(&quot;Character: &quot; + character.Name);
Debug.Log(&quot;Characters array: &quot; + characters);
}
displayName();
displayStory();
}
public void displayName()
{
// Check if the characters array has enough characters to choose from
if (characters.Count &lt; 2)
{
Debug.LogError(&quot;Not enough characters to select from.&quot;);
return;
}
foreach (Character character in characters) 
{
Debug.Log(&quot;Character Name in list: &quot; + character.Name);
}
// Select two different random indexes
int indexA = UtilityFunctions.GetRandomInt(0, characters.Count - 1);
int indexB;
do
{
indexB = UtilityFunctions.GetRandomInt(0, characters.Count - 1);
} while (indexA == indexB);
Debug.Log(&quot;IndexA: &quot; + indexA + &quot;, IndexB: &quot; + indexB);
Debug.Log(&quot;characters[indexA]: &quot; + characters[indexA]);
Debug.Log(&quot;characters[indexB]: &quot; + characters[indexB]);
charA = characters[indexA];
charB = characters[indexB];
Debug.Log(&quot;CharA: &quot; + charA);
Debug.Log(&quot;CharB: &quot; + charB);
// Set the names to the TextMeshPro components
combatantNameA.text = charA.Name;
Debug.Log(&quot;Combatant A: &quot; + combatantNameA.text);
combatantNameB.text = charB.Name;
Debug.Log(&quot;Combatant B: &quot; + combatantNameB.text);
// Set the HP to the TextMeshPro components
combatanthpA.text = $&quot;Health: {charA.CharacterStats.Health.ToString()}&quot;;
combatanthpB.text = $&quot;Health: {charB.CharacterStats.Health.ToString()}&quot;;
// Set the Weapon name to the TextMeshPro components
combatantWpnA.text = $&quot;{charA.CharacterWeapon.Name}, DMG: ({charA.CharacterWeapon.Damage})&quot;;
combatantWpnB.text = $&quot;{charB.CharacterWeapon.Name}, DMG: ({charB.CharacterWeapon.Damage})&quot;;
// Set the Strength to the TextMeshPro components
combatantstrA.text = $&quot;STR: {charA.CharacterStats.Strength.ToString()}&quot;;
combatantstrB.text = $&quot;STR: {charB.CharacterStats.Strength.ToString()}&quot;;
// Set the Agility to the TextMeshPro components
combatantdexA.text = $&quot;DEX: {charA.CharacterStats.Agility.ToString()}&quot;;
combatantdexB.text = $&quot;DEX: {charB.CharacterStats.Agility.ToString()}&quot;;
}
public void displayStory()
{
/**
* * STORY GENERATION:
* * {COMBATANT A} met {COMBATANT B}
* * AT THE {PLACE} OF {wordGen()}
* * {whatHappened}
**/
string randomPlace = bb.PLACES[UtilityFunctions.GetRandomInt(0, bb.PLACES.Count)];
string randomPlaceName = generatorWord.wordGen(10);
string paragraph = &quot;&quot;;
string whatHappened = &quot;&quot;;
if(charA != null &amp;&amp; charB != null)
{
whatHappened = combatEncounter(charA, charB);
}
else
{
Debug.LogError(&quot;charA or charB is null, cannot call combatEncounter.&quot;);
}
paragraph += $&quot;Once upon a time...\n\n{combatantNameA.text}\n met\n {combatantNameB.text}\n in battle at the {randomPlace} of {UtilityFunctions.CapitalizeFirstLetter(randomPlaceName)}.\n\n\n{whatHappened}&quot;;
storyPanel.text = paragraph;
}
private string combatEncounter(Character A, Character B)
{
if(A == null || B == null) 
{
Debug.Log(&quot;A: &quot; + A);
Debug.Log(&quot;B: &quot; + B);
Debug.LogError(&quot;One or both of the characters are null.&quot;);
return &quot;&quot;;
}
string whatHappened = &quot;&quot;;
string turn = &quot;&quot;;
int damageA;
int damageB;
int hpA;
int hpB;
damageA = A.CharacterWeapon.Damage;
damageB = B.CharacterWeapon.Damage;
hpA = A.CharacterStats.Health;
hpB = B.CharacterStats.Health;
while (hpA &gt; 0 &amp;&amp; hpB &gt; 0)
{
turn += $&quot;{A.Name} attacked {B.Name} with a {A.CharacterWeapon.Name} for {damageA} damage.\n{B.name} had {hpB} health left.\n&quot;;
hpA -= damageB;
turn += $&quot;{B.Name} attacked ${A.Name} with a {B.CharacterWeapon.Name} for {damageB} damage.\n{A.name} had {hpA} health left.\n&quot;;
hpB -= damageA;
whatHappened += $&quot;{turn}\n\n&quot;;
}
Debug.Log(&quot;What happened: &quot; + whatHappened);
return whatHappened;
}
}

Here is my log showing that charA and characters[indexA] are both null, and yet charA.Name ("CombatantA") is logging fine:
实例赋值为null但仍能引用属性?

EDIT: Adding my Character type implementation, if that helps.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Stats
{
public int Health;
public int Strength;
public int Agility;
public int Intelligence;
}
[System.Serializable]
public class Weapon
{
public string Name;
public int Damage;
}
[System.Serializable]
public class Trait
{
public string Name;
public string Description;
}
public class Character : MonoBehaviour
{
public string Name { get; set; }
public Stats CharacterStats { get; set; }
public Weapon CharacterWeapon { get; set; }
public List&lt;Trait&gt; CharacterTraits { get; set; }
public Character(string name, Stats stats, Weapon weapon, List&lt;Trait&gt; traits) 
{
this.Name = name;
this.CharacterStats = stats;
this.CharacterWeapon = weapon;
this.CharacterTraits = traits;
}
}

答案1

得分: 1

这可能是因为Character继承了MonoBehaviour。Unity为其自己的对象(如MonoBehavioursGameObjects和其他对象)重写了相等性,可以在正常的C#中不会发生的情况下表示对象与null相等(查看此文章以获取更多信息:https://matheusamazonas.net/blog/2020/04/01/null-check-and-equality-in-unity/)。这有点令人困扰和混淆,但他们有他们的原因。

看起来你没有将Character对象附加到任何GameObject上,而是使用new关键字实例化它们,而不是像obj.AddComponent<Character>()这样的方式,所以我认为Unity内部可能会混淆,因为创建的Character行为未附加到任何对象上,所以它们的自定义等式运算符会将其视为null,尽管它实际上不是null

我没有测试过,但我认为如果你将Character更改为不继承MonoBehaviour,那么它将解决此问题。

或者,如果你想将其保留为MonoBehaviour,你可以保持不变,但通过使用obj.AddComponent<Character>()而不是new Character()来附加它到对象上来实例化它。

此外,我对你的代码能够运行而不会在早期引发错误感到惊讶,因为通常尝试使用new创建MonoBehaviour实例会引发错误(根据我所见,无论如何)。

英文:

This is probably happening because Character extends MonoBehaviour. Unity has it's own equality override for it's own objects (like MonoBehaviours, GameObjects and other things) which can say things are equal to null in scenarios where normal C# wouldn't (check out this article for more info about that: https://matheusamazonas.net/blog/2020/04/01/null-check-and-equality-in-unity/). It's a bit annoying and confusing but they had their reasons for it.

It doesn't look like you're attaching the Character objects to any GameObjects, and you are instantiating them with the new keyword rather than something like obj.AddComponent&lt;Character&gt;();, so I assume that internally Unity is getting confused because the created Character behaviours aren't attached to any object, so their custom equality operator is saying it's null, even though it's not actually null.

I haven't tested it, but I think if you change Character to not extend MonoBehaviour then it will fix this issue.

Alternatively, if you want to keep it as a MonoBehaviour, you could keep it as-is but instantiate it by attaching it to an object by using obj.AddComponent&lt;Character&gt;() instead of new Character().

Also, I am surprised your code runs without throwing errors earlier, as normally trying to create a MonoBehaviour instance using new causes an error (from what I've seen, anyway).

huangapple
  • 本文由 发表于 2023年8月4日 07:58:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/76832233.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定