事件函数不按预期工作

7

首先我会解释现在发生了什么,然后是我期望发生的事情,最后是背后的代码

所以现在发生的事情是当我按下回车键时,文本的颜色变成了绿色

我期望发生的事情是文本的颜色变成红色

这基于我在字段中输入"Bad"的情况

//Please note I have edited uni9mportant code out

//Event Listener
inputField.onEndEdit.AddListener (delegate {
            VerifyWords();
});

//Clss that handles the dictionary
public abstract class WordDictionary: MonoBehaviour{
    public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();

    private void Start(){
        _wordDictionary.Add ("Bad",true);
    }
}

//Function that handles the word verification
private void VerifyWords(){
        if (openChat == false) { //If we done have open chat
            bool hasBadWords = false; //Reset boolean
            string[] stringSplit = inputField.text.Split (' '); //Split text string

            for (int i = 0; i < stringSplit.Length; i++) { // Go through each word in the string array
                if (WordDictionary._wordDictionary.ContainsKey (stringSplit[i])) { //If the word is in the dictionary
                    hasBadWords = true; //Then there is a bad word
                }
            }

            if (hasBadWords == true) { //If a bad word was found
                inputField.textComponent.color = Color.red; //Then the text should be red
            } else {
                inputField.textComponent.color = Color.green; //The text should be green
            }
        }
    }

编辑 我加入了注释以表达我的思路


在执行过程中,您是否调用了start方法?如果没有,则字典从未初始化,其中没有任何内容。 - Quima
1
@AugustoQ 如果您从MonoBehaviour派生脚本,则在Unity中会自动调用Start函数。 - Programmer
我还发现,如果在代码中加入!,比如说“如果字典中不包含这个键”,那么文本总是会变成红色……真的让我感到困惑,为什么会出现这种情况,我觉得代码看起来没问题。 - FlamingGenius
1
你尝试过从WordDictionary中移除abstract吗?我在想Unity无法调用Start,因为它无法实例化一个抽象类。 - Ron Beyer
啊,你说得对,我从来没有把这些单词添加到字典里!谢谢你提醒。 - FlamingGenius
3个回答

4
问题在于该类被标记为abstract。抽象类不能被实例化,因此Unity无法调用不能实例化的类上的Start方法。最简单的解决方法是从类定义中删除abstract即可:
public class WordDictionary: MonoBehaviour{
    public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();

    private void Start(){
        _wordDictionary.Add ("Bad",true);
    }
}

然而,您现在面临一个新的问题。 WordDictionary 有一个静态成员,由非静态方法初始化。这意味着每次创建新的 WordDictionary 时,都会调用 Start() 并将所有单词再次添加到字典中 (或者至少尝试这样做,在这种情况下,您将获得重复键异常,为避免这种情况,还可以编写 _wordDictionary ["Bad"] = true,如果存在,则替换现有键)
更好的选择是使用静态构造函数。这将确保字典仅被初始化一次:
public class WordDictionary: MonoBehaviour{
    public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();

    static WordDictionary() {
        _wordDictionary.Add ("Bad",true);
    }

    private void Start(){
    }
}

现在你可以放心使用WordDictionary,无需担心每次实例化类时词典增长。但在这一点上,将WordDictionary设置为MonoBehavior没有任何用处,因为它真的只是一个单词的容器。所以你的类现在变成了:

public class WordDictionary: {
    private static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();

    public static Dictionary<string, bool> Words {
        get { return _wordDictionary; }
    }

    static WordDictionary() {
        _wordDictionary.Add ("Bad",true);
    }
}

我在这里添加了一个属性,因为你应该使用属性,并且在我的代码世界中,下划线名称意味着它是一个私有字段。你可以扩展你的字典以执行其他操作:

public class WordDictionary: {
    private static List<string> _wordList = new List<string> ();

    static WordDictionary() {
        _wordList.Add ("Bad");
    }

    public static Contains(string word) {
        return _wordList.Contains(word);
    }

    public static ContainsAny(IEnumerable<string> words) {
        return words.Any(w => Contains(w));
    }
}

我认为在这里使用字典没有必要,因为它包含了单词“bad”,如果不包含则是“good”。将其更改为列表可以使事情变得更简单。如果您隐藏“字典”在后台的工作方式,只公开“包含”和“任意包含”方法,那么您将获得两个优势:使用变得更简单,并且可以在不更改接口和下游代码的情况下更改底层“引擎”。
现在您的着色功能变得更加简单:
private void VerifyWords() {
    if (openChat)
        return;

    var stringSplit = inputField.text.Split(' ');

    if (WordDictionary.ContainsAny(stringSplit))                
        inputField.textComponent.color = Color.red;
    else
        inputField.textComponent.color = Color.green;
}

有点晚看到这个,但是无法用言语形容这个答案有多美妙! - FlamingGenius

2

首先,我建议您不要使用MonoBehaviour作为WordDictionary的基类。我认为没有必要这样做,而应该使用ScriptableObject。

其次,它不需要是抽象的,我认为您根本不需要一个字典,只需要使用列表即可,假设您只存储“坏”单词。

例如(这里我使用了字典,但如果您的要求仅适用于存储坏单词,则可以使用列表)

[CreateAssetMenu(menuName = "Custom Object/Word Dictionary")]
public class WordDictionary : ScriptableObject
{
    public Dictionary<string, bool> Values = new Dictionary<string, bool>();
}

可脚本化对象是在您的资产中创建的,并且可以被场景中的游戏对象引用。因此,在需要使用字典的游戏对象中添加即可。

public WordDictionary gameDictionary;

将其设置为您在资源中创建的字典。通常,单例 / 静态类和类似方法使得MonoBehaviour表现为静态对象会导致问题。

Scriptable Objects是一个很好的解决方法。它们在游戏启动时就像单例那样可用,无需初始化;它们可以像任何其他类一样包含数据或函数,但与MonoBehaviour不同,它们没有Start、Update等功能……但这并不意味着您无法从某些特定的初始化行为中调用您在Scriptable object中编写的AtStart()。

重要提示:在编辑器中更新的Scriptable object数据会持久存在,但在运行时不会。例如,在编辑器中测试游戏时,“坏”单词将在会话之间保留...但在构建过程中不会保留,但我假设您正在游戏初始化时准备字典,因此这应该不是问题。


1
也许这会对您有所帮助。
//Please note I have edited uni9mportant code out

//Event Listener
inputField.onEndEdit.AddListener (delegate {
            VerifyWords();
});

//Clss that handles the dictionary
public abstract class WordDictionary: MonoBehaviour{
    public static Dictionary<string,bool> _wordDictionary = new Dictionary<string,bool> ();

    private void Start(){
        _wordDictionary.Add ("Bad",true);
    }
}

//Function that handles the word verification
private void VerifyWords(){
        if (openChat == false) { //If we done have open chat
            bool hasBadWords = false; //Reset boolean
            string[] stringSplit = inputField.text.Split (' '); //Split text string
            int i=0;
            for (i = 0; i < stringSplit.Length; i++) { // Go through each word in the string array
                if (WordDictionary._wordDictionary.ContainsKey (stringSplit[i])) { //If the word is in the dictionary
                     inputField.textComponent.color = Color.red; //Then the text should be red
                }
            }

            if (i == stringSplit.Length) { //If a bad word was found
             inputField.textComponent.color = Color.green; //The text should be green  
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接