我知道如何访问其他脚本的变量,但我只能通过function calls
或delegates
进行操作。第一个很容易,但是一旦我编辑原始代码就会使代码变得脆弱,我必须再次编辑。
第二个是更好的,但由于我有很多函数,具有不同类型的返回值和参数,它会使事情复杂化很多。
说我想在游戏开始时做一些事情。到目前为止,我在相应的脚本中创建了一个名为OnGameStart()
的函数,并从那里调用了我需要的所有内容,OnGameStart()
成为public
并从另一个脚本调用。
我需要在开始时播放声音,检查保存数据,播放UI和其他动画等,但我不想让我的代码成为灾难。 我在网上找了这个,但发现只有最简单的"如何在脚本之间进行通信"东西,它与基本函数调用,有时事件。有经验的人可以指导我获取有关如何制作紧凑,隔离的类来支持 Demeter's law 的资源吗?
答案 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>
结论是,发现/尝试模式,适应它们,随着时间的推移,你会发现什么是最适合你的。
答案 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并且必须附加到游戏对象是很常见的。那不是Unity的实际运作方式;当一切都是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");
对于它实际预期会做什么变得相当不言自明。