Unity - 访问其他脚本的变量 - 正确放置

Unity - Accessing other scripts' variables - put properly

我知道如何访问另一个脚本的变量,但我只能通过 function callsdelegates 访问。第一个很简单,但会使代码变得脆弱,因为一旦我编辑了原始代码,我就必须再次编辑。 第二个更好,但因为我有很多功能,具有不同种类的 return 值和参数,它会使事情复杂化很多。 假设我想在游戏开始时做一些事情。到目前为止,我在适当的脚本中创建了一个名为 OnGameStart() 的函数,并从那里调用了我需要的所有内容,并且 OnGameStart()public 并从另一个脚本调用的。

一开始我需要播放声音、检查保存数据、播放 UI 和其他动画等等,但我不想让我的代码成为一场灾难。 我在网上找了这个,但只找到了最简单的 "how to communicate between scripts" 东西,它伴随着基本的函数调用,有时是事件。有经验的人可以指导我如何制作紧凑、隔离的 类 来支撑 Demeter's law 吗?

使用静态 classes

静态 classes 非常适合您需要广泛访问程序的某些子系统的任何时候,因此它们在游戏中被广泛使用。

/// <summary>Save/load is a good example because it 
/// doesn't need any settings and it's 
/// useful to call it from almost anywhere.</summary>
public static class GameSaver {

    /// <summary>I save the game when I'm run.</summary>
    public static void Save() {
       // Save the game!
    }

}

要使用静态 class,您只需直接使用成员 - 例如 GameSaver.Save(); 将从 "anywhere" 开始工作。属性、字段、事件等都可以设为静态,但请先查看下面的注释。

这是避免某种 "god class" 的最简单方法 - 这似乎就是您所描述的 (是的,它们通常是代码灾难!) - 那是一个 class ,它过于复杂并且可以做任何事情。将它分解成一系列小的、独立的模块。

不过不要过度使用静态字段!

为此使用单例。

特别是在游戏中,只实例化一次的东西(比如播放器或音频系统)也很常见,这些东西也需要易于重置或具有大量属性。

重要的是不要将它们都作为静态字段 - 这将难以重置且难以调试。这就是您使用静态字段并实例化普通 class 的地方 - 这称为 singleton:

/// <summary>There's only ever one background music source!
/// It has instance properties though (i.e. an AudioSource)
/// so it works well as a singleton.</summary>
public class BackgroundMusic {

    /// <summary>The static field - use the Play method from anywhere.</summary>
    private static BackgroundMusic Current;

    /// <summary>Plays the given clip.</summary>
    public static void Play(AudioClip clip) {

        if (Current == null) {
            // It's not been setup yet - create it now:
            Current = new BackgroundMusic();
        }

        // E.g. Current.Source.Play(clip);

    }

    public BackgroundMusic() {
        // Instance a source now. 
    }

}

这仅意味着 BackgroundMusic.Play(..); 可从任何地方获得。这种方法意味着您不需要在检查器中设置任何东西——只需调用该方法即可按需自行设置。

当 MonoBehaviour 很棒时

通常认为所有代码都必须是 MonoBehaviour 并且必须附加到游戏对象。这不是 Unity 的实际工作方式;当一切都是 MonoBehaviour 并且必须手动实例化和连接时,这只会导致使用编辑器的人做更多的工作。

明确地说,我并不是说根本不要使用 MonoBehaviour。相反,您应该根据代码实际表示的内容使用组件模型和静态的适当组合。

总的来说:

  • 如果某物只有一个实例,请使用单例。
  • 但是 如果只有一个 它具有在检查器中编辑有用的属性,使用 MonoBehaviour 并保留参考也将单个对象作为静态字段。

例如,玩家(在单人游戏中)具有您想要更改的一系列默认设置。您可以将播放器设置为预制件并具有某种引用当前实例的 PlayerSettings.Current 静态字段:

/// <summary>Add this to a player prefab.</summary>
public class PlayerSettings : MonoBehaviour{

    /// <summary>Still following the singleton pattern.</summary>
    public static PlayerSettings Current;

    /// <summary>Player speed. This can be edited in the inspector.</summary>
    public float Speed;


    public void Awake() {
        // Update the static field:
        Current = this;
    }

}

这种方法提供了两全其美的方法 - 您可以在任何地方使用 PlayerSettings.Current(在播放器预制件实例化之后),而不必放弃检查器。它也比 GameObject.Find("Player/Body").GetComponent<PlayerSettings>(); 之类的东西更容易重构,从而更容易维护。

多个实例

如果某物有多个实例,比如 NPC,那么通常您总是会使用带有 MonoBehaviour 的预制件。但是,使用静态方法对这些非常有用:

public class NPC : MonoBehaviour{

    /// <summary>Gets an NPC by their name.</summary>
    public static NPC Locate(string name){
        // E.g. get all GameObject instances with an NPC component.
        // Return the first one which has a 'Name' value that matches.
    }

    /// <summary>The name of this NPC (editable in the inspector).
    public string Name;

}

NPC.Locate("Dave"); 变得相当 self-explanatory 它的实际预期功能。

解决此类问题肯定有很多可能性,例如,您可以从 Hollywood principle.

中获得一些灵感

与其 Player 搜索某物,不如在初始化时将其提供给他。

这是一个非常简单的例子:

游戏管理器接口的定义:

using UnityEngine;

namespace Assets.Scripts
{
    public interface IGameManager
    {
        void PlayAudioClip(AudioClip audioClip);
    }
}

游戏管理员的定义:

using UnityEngine;

namespace Assets.Scripts
{
    public class GameManager : MonoBehaviour, IGameManager
    {
        #region IGameManager Members

        public void PlayAudioClip(AudioClip audioClip)
        {
            // TODO
        }

        #endregion
    }
}

一个例子:

using System;
using UnityEngine;

namespace Assets.Scripts
{
    public class Player : MonoBehaviour
    {
        public GameManager GameManager; // TODO assign this in Inspector

        public void Start()
        {
            if (GameManager == null)
                throw new InvalidOperationException("TODO");
        }

        public void Update()
        {
            // demo
            var wounded = true;
            var woundedAudioClip = new AudioClip();
            if (wounded)
            {
                GameManager.PlayAudioClip(woundedAudioClip);
            }
        }
    }
}

您也可以在 Unity 中使用某种 Singleton(或任何合适的)。

备注:

上面的例子实际上只是一个给你提示如何思考的例子,你没有提供所有的细节,即使你提供了,我们也很难进一步帮助你(只有你会随着时间的推移找到真正适合你当前问题的东西)。

在推进您的游戏项目时,您会发现没有硬性规则可循,显然模式很重要,但您可能会发现自己最终会得到自己的(即 finely-grained 多个组合使用模式)。

也许“Who wrote this programing saying? "Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live."”也会给你一些灵感。

结论是,discover/try 模式,适应它们,随着时间的推移,您会发​​现最适合您的模式。