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

时间:2017-02-07 20:35:38

标签: c# design-patterns unity3d organization code-organization

我知道如何访问其他脚本的变量,但我只能通过function callsdelegates进行操作。第一个很容易,但是一旦我编辑原始代码就会使代码变得脆弱,我必须再次编辑。 第二个是更好的,但由于我有很多函数,具有不同类型的返回值和参数,它会使事情复杂化很多。 说我想在游戏开始时做一些事情。到目前为止,我在相应的脚本中创建了一个名为OnGameStart()的函数,并从那里调用了我需要的所有内容,OnGameStart()成为public并从另一个脚本调用。

我需要在开始时播放声音,检查保存数据,播放UI和其他动画等,但我不想让我的代码成为灾难。 我在网上找了这个,但发现只有最简单的"如何在脚本之间进行通信"东西,它与基本函数调用,有时事件。有经验的人可以指导我获取有关如何制作紧凑,隔离的类来支持 Demeter's law 的资源吗?

2 个答案:

答案 0 :(得分:2)

解决此类问题肯定有很多可能性,例如你可以从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(或任何适当的)。

备注:

上面的示例实际上只是一个示例,为您提供如何思考,但您没有提供所有细节,即使您这样做了,我们也几乎无法帮助您(只有你会随着时间的推移找到真正适合你当前问题的方法。)

当您在游戏项目中取得进展时,您会看到没有硬规则可以遵循,显然模式很重要,但您可能会发现自己最终会得到自己的结果(即多种模式的精细组合使用)。 / p>

也许'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."'也会给你一些启发。

结论是,发现/尝试模式,适应它们,随着时间的推移,你会发现什么是最适合你的。

答案 1 :(得分:1)

使用静态类

静态类非常适合您需要广泛访问程序的某个子系统,因此它们会在游戏中广泛使用。

/// <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!
    }

}

要使用静态类,您只需直接使用该成员 - 例如GameSaver.Save();将在&#34;任何地方使用&#34;。属性,字段,事件等都可以是静态的,但请先看下面的注释。

这是避免某种"god 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");对于它实际预期会做什么变得相当不言自明。