英文:
Instance assignment is null but still able to reference properties?
问题
感谢阅读我的帖子。我正在为一个文本游戏编写一个战斗脚本。在编写时,它曾经能够正常工作,但现在出现了最奇怪的错误。
我创建了两个名为 Character
的对象,称为 charA
和 charB
。我从已实例化的 Character
对象数组中随机选择了两个索引分配给 charA
和 charB
。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<Character> characters = new List<Character>();
// Start is called before the first frame update
void Start()
{
generatorName = GetComponent<GenerateName>();
generatorWord = GetComponent<GenerateWord>();
character = GetComponent<Character>();
bb = GetComponent<BuildingBlocks>();
// Initialize GameController
gc = GetComponent<GameController>();
for (int i = 0; i < NumberOfCharacters; i++)
{
if (generatorName != null)
{
Debug.Log($"Loop `{i}` is entered!");
/**
* * NAME GENERATION:
* * PREFIX => FIRST NAME => LAST NAME (DYNASTY) => SUFFIX => TITLE
**/
name = $"{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)]}";
}
Debug.Log("NameGen GameController: " + 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<Trait> traits = new List<Trait>() { new Trait() { Name = "Brave", Description = "Never backs down from a fight" } };
//GameObject characterGO = Instantiate(CharacterPrefab);
character = new Character(name, stats, weapon, traits);
characters.Add(character);
Debug.Log("Character: " + character.Name);
Debug.Log("Characters array: " + characters);
}
displayName();
displayStory();
}
public void displayName()
{
// Check if the characters array has enough characters to choose from
if (characters.Count < 2)
{
Debug.LogError("Not enough characters to select from.");
return;
}
foreach (Character character in characters)
{
Debug.Log("Character Name in list: " + 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("IndexA: " + indexA + ", IndexB: " + indexB);
Debug.Log("characters[indexA]: " + characters[indexA]);
Debug.Log("characters[indexB]: " + characters[indexB]);
charA = characters[indexA];
charB = characters[indexB];
Debug.Log("CharA: " + charA);
Debug.Log("CharB: " + charB);
// Set the names to the TextMeshPro components
combatantNameA.text = charA.Name;
Debug.Log("Combatant A: " + combatantNameA.text);
combatantNameB.text = charB.Name;
Debug.Log("Combatant B: " + combatantNameB.text);
// Set the HP to the TextMeshPro components
combatanthpA.text = $"Health: {charA.CharacterStats.Health.ToString()}";
combatanthpB.text = $"Health: {charB.CharacterStats.Health.ToString()}";
// Set the Weapon name to the TextMeshPro components
combatantWpnA.text = $"{charA.CharacterWeapon.Name}, DMG: ({charA.CharacterWeapon.Damage})";
combatantWpnB.text = $"{charB.CharacterWeapon.Name}, DMG: ({charB.CharacterWeapon.Damage})";
// Set the Strength to the TextMeshPro components
combatantstrA.text = $"STR: {charA.CharacterStats.Strength.ToString()}";
combatantstrB.text = $"STR: {charB.CharacterStats.Strength.ToString()}";
// Set the Agility to the TextMeshPro components
combatantdexA.text = $"DEX: {charA.CharacterStats.Agility.ToString()}";
combatantdexB.text = $"DEX: {charB.CharacterStats.Agility.ToString()}";
}
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 = "";
string whatHappened = "";
if(charA != null && charB != null)
{
whatHappened = combatEncounter(charA, charB);
}
else
{
Debug.LogError("charA or charB is null, cannot call combatEncounter.");
}
paragraph += $"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}";
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("One or both of the characters are 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)
{
turn += $"{A.Name} attacked {B.Name} with a {A.CharacterWeapon.Name} for {damageA} damage.\n{B.name} had {hpB} health left.\n";
hpA -= damageB;
turn += $"{B.Name} attacked ${A.Name} with a {B.CharacterWeapon.Name} for {damageB} damage.\n{A.name} had {hpA} health left.\n";
hpB -= damageA;
whatHappened += $"{turn}\n\n";
}
Debug.Log("What happened: " + whatHappened);
return whatHappened;
}
}
Here is my log showing that charA and characters[indexA] are both null, and yet charA.Name ("CombatantA") is logging fine:
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<Trait> CharacterTraits { get; set; }
public Character(string name, Stats stats, Weapon weapon, List<Trait> traits)
{
this.Name = name;
this.CharacterStats = stats;
this.CharacterWeapon = weapon;
this.CharacterTraits = traits;
}
}
答案1
得分: 1
这可能是因为Character
继承了MonoBehaviour
。Unity为其自己的对象(如MonoBehaviours
、GameObjects
和其他对象)重写了相等性,可以在正常的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 GameObject
s, and you are instantiating them with the new
keyword rather than something like obj.AddComponent<Character>();
, 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<Character>()
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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论