Unity游戏经理.脚本只工作一次

Dim*_*zyr 46 c# unity-game-engine

我正在制作简单的游戏经理.我有一个脚本,可以从游戏中的所有场景访问.我需要在加载新场景后检查其变量的值.但是我的代码在开始模拟后只运行一次,而具有此脚本的对象存在于所有场景中.怎么了?装载新场景后为什么不起作用?

Fat*_*tie 91

在每个Unity项目中,您必须拥有A PRELOAD SCENE.

Unity没有"内置"的预加载场景,这让人很困惑.

他们将来会添加这个概念.

重复一遍,你必须有一个预加载场景.

就这么简单.

对于尝试使用Unity的新程序员来说,这是最令人难以置信的错误!


幸运的是,拥有预加载场景非常容易.

步骤1.

制作一个名为"preload"的场景.它必须是构建管理器中的场景0.

在此输入图像描述

第2步.

在"预加载"场景中,创建一个名为"__app"的空GameObject.

简单地说,穿上DontDestroyOnLoad'__app'.

注意:

这是您使用的整个项目中唯一的位置DontDestroyOnLoad.

就这么简单.

在此输入图像描述

在示例中:开发人员制作了一行DDOL脚本.

将该脚本放在"__app"对象上.

你再也不用考虑DDOL了.

第3步

您的应用将具有(许多)"一般行为".所以,比如数据库连接,声音效果,评分等等.

您必须,并且只能将您的一般行为放在"_app"上.

这真的很简单.

当然,一般行为 - 在项目的任何地方,任何时候和所有场景中都可用.

你怎么能这样做?

在上面的图像示例中,请注意"Iap"("应用程序内购买")和其他内容.

所有"一般需要的行为" - 声音效果,得分等等 - 就在那个对象上.

重要...

这意味着 - 当然,自然 -

......当然,你的一般行为会像Unity中的其他所有人一样拥有普通的检查员.

您可以使用Unity的所有常用功能,您可以在其他所有游戏对象上使用它.

当然,您可以使用常用的Inspector变量(拖动连接),设置等.

(的确如此:假设你已经被聘请参与一个现有的项目.你要做的第一件事就是看一下预载现场.你会在一个地方看到项目的所有"一般行为".声音效果您将立即看到这些内容的所有设置,如Inspector变量...语音量,Playstore ID等.)

这是一个例子"声音效果"的一般行为:

在此输入图像描述

看起来还有一种"配音"的一般行为,以及"音乐"的一般行为".

重复.关于你的"一般"行为.(声音效果,得分,社交等等)这些只能在预加载场景中的游戏对象上进行.

不是可选的:没有其他选择!

你完成了.

说实话,这很简单.几乎没有什么可以做到的!

许多来自其他类型的工程师都会永远意识到这很简单,因为它简单易行,"不可能是真的".

重复一遍:整个问题是(奇怪)Unity只是简单地忘记了"内置"一个预加载场景.因此,您只需单击添加一个(不要忘记在预加载场景中添加DDOL).

现在在开发期间:

始终从Preload场景开始游戏.

就这么简单.

(重要的一点:你的应用程序肯定会有"早期"场景.例如,"启动画面"或"菜单".请注意,你不能使用"启动"或"菜单"作为预加载场景.你必须字面上有一个预加载场景.预加载场景将加载你的飞溅场景,菜单场景,或任何你想要的.)


核心问题:"找到"来自其他脚本的那些:

然后,您可以非常简单地从任何场景中的任何脚本中找到"SoundEffects"或"GameManager".

幸运的是它很容易,它只是一行代码.

这不是问题:

Sound sound = Object.FindObjectOfType<Sound>();
Game game = Object.FindObjectOfType<Game>();
Run Code Online (Sandbox Code Playgroud)

做到这一点的Awake,对于任何需要它的脚本.

说实话,这很简单.这里的所有都是它的.

Sound sound = Object.FindObjectOfType<Sound>();

由于在线看到的100个绝对错误的代码示例,出现了巨大的混乱.此外,新的Unity工程师"不敢相信"这很容易!

真的很容易 - 诚实!

Unity忘记添加一个内置的"预加载场景" - 在某个地方附加你的系统,如SoundEffects,GameManager等,这是完全奇怪的.这只是Unity的一个奇怪的事情.因此,您在任何Unity项目中所做的第一件事就是单击一次以制作预加载场景.

而已!


细节......

请注意,如果您真的想要输入更少(!)的代码行,那么它非常简单 - 您可以为这些内容使用全局代码!

这在详细解释这里(你可以用我的流行Grid.cs脚本)和@Frits回答以下.

(无论是使用全局变量,还是只需要在每个需要特定变量的组件中都有局部变量,只需要代码风格.在非常不寻常的情况下,"全局"方法可能具有高性能(与任何全局变量一样).)


DylanB问:" 在开发过程中,每次点击"播放"之前必须点击预加载场景是非常烦人的.这可以自动化吗?"

当然,每个团队都有不同的方式来做到这一点.这是一个简单的例子:

// this should run absolutely first; use script-execution-order to do so.
// (of course, normally never use the script-execution-order feature,
// this is an unusual case, just for development.)
...
public class DevPreload:MonoBehaviour
 {
 void Awake()
  {
  GameObject check = GameObject.Find("__app");
  if (check==null)
   { UnityEngine.SceneManagement.SceneManager.LoadScene("_preload"); }
  }
 }
Run Code Online (Sandbox Code Playgroud)

但不要忘记:"你还能做什么?" 游戏必须从预加载场景开始. 除了去预加载场景,你还可以做些什么来开始游戏?您也可以问"启动Unity以运行Unity是多么烦人 - 如何避免启动Unity?" 当然,游戏绝对必须从预装场景开始 - 它怎么可能呢?当然,在Unity中工作时,你必须"在点击播放之前点击预加载场景" - 它还有什么用呢?

  • "每个Unity项目都必须有一个PRELOAD SCENE"为什么?请解释原因.懒惰地初始化对象并从静态类/单例访问它们似乎更容易 - 特别是在测试场景时,因为您可以轻松地单独启动每个场景. (10认同)
  • @Fattie我不得不在这个问题上与你意见不一致.确实,预加载场景有时(很少)是必要的,但在大多数情况下,你所描述的一切都只是一种懒惰的方式.你已经提到过的第一个缺点是......你总是要从场景0开始.我犯了错误,就是以这种方式开发一款游戏,并因此而失去了几个小时.始终开发您的游戏,以便可以从任何场景开始.这样做的好处应该是显而易见的. (8认同)
  • @Fattie其次,您建议使用FindObjectOfType来获取您的实例.是的,这很简单,但又是一种懒惰的做法.调用非常慢,甚至Unity建议不要使用它:https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html使用Unity开发时,你提供了太多的自由,所以你必须是非常小心你如何设计你的架构.如果你不是,你以后会遇到无穷无尽的问题......相信我,我一直在那里.Unity有很多好的做法,但是,这不是其中之一. (6认同)
  • 所以在工作流程方面,这是如何工作的?如果你想测试你当前正在编辑的场景,那么你是否必须在编辑器中加载预加载场景并点击Play,因为预加载场景是初始对象传播到后续场景的?如果是这样,那作为工作流程似乎非常钝.我觉得我在这里错过了一些东西. (4认同)
  • @Fattie 如果您正在快速生成一些需要的东西怎么办?还是在 1000 次生成中爆发?或者任何其他无数场景中的任何其他场景,其中快速创建新对象?像这样的缓慢调用会随着时间的推移而堆积,特别是如果您正在为 Android / iOS 构建。即使频繁(例如在每次更新调用中)使用 Camera.main(基本上做同样的事情)也会大大降低复杂场景中的 FPS。 (3认同)
  • 这是一个很棒的问题.是的,在Unity中它是***绝对正常***你必须烦恼地点击回到预加载场景,来测试游戏.你什么都不缺.你已经完全击中了头部. (2认同)
  • @Fattie 请详细说明。我看到的唯一好处是它对新的 Unity 开发人员来说很容易理解。您是否看过这个最佳实践列表:http://devmag.org.za/2012/07/12/50-tips-for-working-with-unity-best-practices/ 那里有很多类似的,但你仍然是我能找到的唯一一个支持使用预加载场景的人。哦,你提到 Unity 将“在未来添加这个概念”。您有任何可以分享的博客文章或错误报告链接吗? (2认同)
  • 嗨@TLS!不要忘记Unity是一个ECS系统(它与OO完全没有任何联系。Unity场景中的每个GameObject都是一个“上帝对象” :))曾有过几次尝试“ monoBehavior构造函数” ,例如StrangeIOC,它试图将ECS系统(基于帧的系统)更改为“纯代码”范例。既然您是在这里提出这个问题的,所以我不想只是消除它,需要花费很短的讨论才能消除它!:)(顺便说一句,请注意,如果您使用奇怪的IIOC或类似方法,则必须将引导程序放置在预加载场景中。) (2认同)

小智 21

@Fattie:感谢您详细阐述这一切,这太棒了!有一点虽然人们试图接近你,我也会试一试:

我们不希望我们手机游戏中的所有实例都为每个"全局类"做一个"FindObjectOfType"!

相反,你可以让它立即使用静态/单例的实例化,而不是寻找它!

它就像这样简单:将它写在你想从任何地方访问的类中,其中XXXXX是类的名称,例如"Sound"

public static XXXXX Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; } else { Debug.Log("Warning: multiple " + this + " in scene!"); }
}
Run Code Online (Sandbox Code Playgroud)

而不是你的例子

Sound sound = Object.FindObjectOfType<Sound>();
Run Code Online (Sandbox Code Playgroud)

只需简单地使用它,无需查看,也无需额外的变量,就像这样,从任何地方开始:

Sound.Instance.someWickedFunction();
Run Code Online (Sandbox Code Playgroud)

或者(技术上相同),只需使用一个全局类(通常称为Grid)来"保持"每个类.Howto.所以,

Grid.sound.someWickedFunction();
Grid.networking.blah();
Grid.ai.blah();
Run Code Online (Sandbox Code Playgroud)

  • 嘿弗里茨!**你 1000% 正确** :) 我想我发明了 Grid.cs 的想法,你可以在这里看到 https://answers.unity.com/answers/663681/view.html 它现在很普遍。这正是您所描述的。*过去我会向新的 Unity 用户描述“网格”系统*...正是您所描述的。但随着时间的推移,我意识到这很令人困惑——最好简单地解释一下你有一个预加载场景的事实。这就是问题的“核心”。当然,一旦您熟悉了(如果您愿意),您可以稍后添加一些语法糖。**你的代码是 10000% 可爱的。** (3认同)

kol*_*odi 8

这是您可以开始喜欢的任何场景的方法,并确保每次在Unity编辑器中单击“播放”按钮时都重新整合_preload场景。自Unity 2017起有新的属性可用RuntimeInitializeOnLoadMethod此处有更多信息。

基本上,您有一个简单的平面c#类,并带有一个静态方法RuntimeInitializeOnLoadMethod。现在,每次您开始游戏时,此方法都会为您加载预加载场景。

using UnityEngine;
using UnityEngine.SceneManagement;

public class LoadingSceneIntegration {

#if UNITY_EDITOR 
    public static int otherScene = -2;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void InitLoadingScene()
    {
        Debug.Log("InitLoadingScene()");
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (sceneIndex == 0) return;

        Debug.Log("Loading _preload scene");
        otherScene = sceneIndex;
        //make sure your _preload scene is the first in scene build list
        SceneManager.LoadScene(0); 
    }
#endif
}
Run Code Online (Sandbox Code Playgroud)

然后,在_preload场景中,您将具有另一个脚本(该脚本将从您开始的位置)加载回所需的场景:

...
#if UNITY_EDITOR 
    private void Awake()
    {

        if (LoadingSceneIntegration.otherScene > 0)
        {
            Debug.Log("Returning again to the scene: " + LoadingSceneIntegration.otherScene);
            SceneManager.LoadScene(LoadingSceneIntegration.otherScene);
        }
    }
#endif
...
Run Code Online (Sandbox Code Playgroud)


phy*_*att 7

2019 年 5 月的替代解决方案不包含_preload

https://low-scope.com/unity-tips-1-dont-use-your-first-scene-for-global-script-initialization/

我将上面的博客转述为下面的操作方法:

为所有场景加载静态资源预制件

Project > Assets创建一个名为 的文件夹Resources

Main从空创建一个预制件GameObject并将其放置在Resources文件夹中。

Main.cs在您或任何地方创建C# 脚本Assets > Scripts

using UnityEngine;

public class Main : MonoBehaviour
{
    // Runs before a scene gets loaded
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void LoadMain()
    {
        GameObject main = GameObject.Instantiate(Resources.Load("Main")) as GameObject;
        GameObject.DontDestroyOnLoad(main);
    }
    // You can choose to add any "Service" component to the Main prefab.
    // Examples are: Input, Saving, Sound, Config, Asset Bundles, Advertisements
}
Run Code Online (Sandbox Code Playgroud)

添加Main.cs到文件夹Main中的预制件Resources

请注意它如何RuntimeInitializeOnLoadMethodResources.Load("Main")和一起使用DontDestroyOnLoad

将需要跨场景全局的任何其他脚本附加到此预制件。

Start请注意,如果您将其他场景游戏对象链接到这些脚本,您可能希望在这些脚本的函数中使用类似的内容:

if(score == null)
    score = FindObjectOfType<Score>();
if(playerDamage == null)
    playerDamage = GameObject.Find("Player").GetComponent<HitDamage>();
Run Code Online (Sandbox Code Playgroud)

或者更好的是,使用资产管理系统,例如Addressable AssetsAsset Bundles