Unity - 访问其他脚本的变量 - 正确放置
Unity - Accessing other scripts' variables - put properly
我知道如何访问另一个脚本的变量,但我只能通过 function calls
或 delegates
访问。第一个很简单,但会使代码变得脆弱,因为一旦我编辑了原始代码,我就必须再次编辑。
第二个更好,但因为我有很多功能,具有不同种类的 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 模式,适应它们,随着时间的推移,您会发现最适合您的模式。
我知道如何访问另一个脚本的变量,但我只能通过 function calls
或 delegates
访问。第一个很简单,但会使代码变得脆弱,因为一旦我编辑了原始代码,我就必须再次编辑。
第二个更好,但因为我有很多功能,具有不同种类的 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 模式,适应它们,随着时间的推移,您会发现最适合您的模式。