How to manage multiple scenes?

RayKoopaRayKoopa DEMember ✭✭

I'm totally confused on how multiple scenes should be handled in UrhoSharp.
I don't even know what I am allowed to do in C# and what not when using UrhoSharp (since it's still a wrapper around Urho3D).

I want to have a scene for the main menu, another scene for the in-game part, and another one for, let's say, a level editor. I want to create those scenes by code rather than loading them from XML.

  • Can I create classes like MenuScene, IngameScene or EditorScene, all inheriting from Scene and create a new constructor for it where I instantiate my scene objects? I'm not sure if I should inherit from Scene - the Urho3D documentation suggests not to inherit from Node (funnily enough Scene itself is an extended Node class), and rather solve that with components.
  • When solving it with components, how would I create one so that it can do actions when the scene is created for the first time, activated, and deactivated again? I'd need a base component from which I inherit then to override my abstract methods of the base class OnLoad, OnActivated, OnDeactivated, or how?
  • How do I implement switching scenes at all? So far I was told to simply switch the scene given to the Viewport so another one is displayed, then prevent the previous scene from getting updates with UpdateEnabled = false. But what when I want to completely destroy the previous scene? Is it even a good idea to have it lieing around in memory, probably not?

Sorry I'm utterly confused. I just can't really get started figuring out how to create a mulit-scene game with UrhoSharp. The samples simply just have one scene, so they didn't help me much.

Best Answer

Answers

  • EsaKEsaK SEMember ✭✭

    This is a good question. I had the same concern in my question/thread "Internal game structure".
    I wish however that Xamarin could create an example for this.

    My solution to this problem is:
    I have a Game class which inherits from Application. On startup I create viewport/scene/camera in it.
    Then I have created two different components which are responsible for different parts/scenes in my game.
    Only one of these are active at a time.
    These components access the Scene from the Game class, in order to create nodes into the scene.
    Before I remove a component I make sure to remove any nodes I have created.
    The components also creates some UI elements, which I also have to make sure to remove when "exiting" a component.

  • RayKoopaRayKoopa DEMember ✭✭

    So basically you wrap up the "Scene" in a component as it looks like, which creates and removes the Nodes it requires for the scene? That's an interesting idea. How did you tell the component its time to remove its nodes so another scene can be loaded?
    (I just set the post as "Not the answer" to maybe get an official reply about how to do this, no offense ;D)

  • EsaKEsaK SEMember ✭✭

    Yes, the game (application) class owns the scene and the components access it to fill it with content.
    The removing of the active component is triggered via user action (button click).
    The component then "cleans up" it's own things and finally calls a method in the game class for starting up (creating) the next component.

  • RayKoopaRayKoopa DEMember ✭✭

    Hmm, that surely is a solution. But it still feels like there should be an easier way, like just exchanging a Scene instance with another...

  • RayKoopaRayKoopa DEMember ✭✭

    I solved it with a custom Stage management now. As far as I understand the Scene class, it should better be just cleared and populated with nodes of another "scene" then.

    My Game class (inheriting from Application has a property to set the current StageBase (only one can be active at a time):

    public class Game : Application
    {
        private StageBase _currentStage;
        private bool _isBoxStage;
    
        public Game()
            : base(new ApplicationOptions("Data"))
        {
        }
    
        /// <summary>
        /// Gets the <see cref="Scene"/> instance in which nodes are created.
        /// </summary>
        internal Scene Scene
        {
            get;
            private set;
        }
    
        /// <summary>
        /// Gets or sets the currently activate <see cref="StageBase"/> instance.
        /// </summary>
        internal StageBase CurrentStage
        {
            get
            {
                return _currentStage;
            }
            set
            {
                // Give the stage a chance to cleanup (and possibly store its status later on). Then destroy all nodes.
                if (_currentStage != null)
                {
                    _currentStage.Stop(this);
                    Scene.Clear(true, true);
                }
    
                // Activate the new stage and call its method to set itself up.
                _currentStage = value;
                _currentStage.Start(this);
            }
        }
    
        /// <summary>
        /// Method invoked to start your application, this is where you would typically create your scene.
        /// </summary>
        protected override void Start()
        {
            Input.SetMouseVisible(true, false);
    
            // Create global objects.
            Scene = new Scene();
    
            // Create some UI to toggle the scenes.
            XmlFile style = ResourceCache.GetXmlFile("UI/DefaultStyle.xml");
            UI.Root.SetDefaultStyle(style);
    
            Text buttonText = new Text();
            buttonText.SetFont("Fonts/Font.ttf", 30);
            buttonText.Value = "Toggle Scene";
            buttonText.SetAlignment(HorizontalAlignment.Center, VerticalAlignment.Center);
    
            Button button = new Button();
            button.SetStyleAuto(null);
            button.Size = new IntVector2(300, 48);
            button.Released += Button_Released;
            button.AddChild(buttonText);
            UI.Root.AddChild(button);
    
            ToggleScene();
        }
    
        private void ToggleScene()
        {
            _isBoxStage = !_isBoxStage;
            if (_isBoxStage)
            {
                CurrentStage = new BoxStage();
            }
            else
            {
                CurrentStage = new SphereStage();
            }
        }
    
        private void Button_Released(ReleasedEventArgs obj)
        {
            ToggleScene();
        }
    }
    

    The StageBase class itself just has a Start() and Stop() method.

    /// <summary>
    /// Represents a programmed game scene.
    /// </summary>
    internal abstract class StageBase
    {
        /// <summary>
        /// Called when this stage is getting activated and becomes the current one.
        /// </summary>
        /// <param name="game">The <see cref="Game"/> instance hosting this stage.</param>
        internal abstract void Start(Game game);
    
        /// <summary>
        /// Called when this stage is getting deactivated and about to be replaced by another one.
        /// </summary>
        /// <param name="game">The <see cref="Game"/> instance hosting this stage.</param>
        internal abstract void Stop(Game game);
    }
    

    Then, I inherit from this base class, and fill the logic into the Start() and Stop() methods. Here just two dummy scenes either having a box as a model or a sphere.

    internal class BoxStage : StageBase
    {
        internal override void Start(Game game)
        {
            game.Scene.CreateComponent<Octree>();
    
            Node cameraNode = game.Scene.CreateChild("Camera");
            cameraNode.Position = new Vector3(0, 0, -3);
            Camera camera = cameraNode.CreateComponent<Camera>();
            game.Renderer.SetViewport(0, new Viewport(game.Context, game.Scene, camera, null));
    
            Node lightNode = game.Scene.CreateChild("Light");
            Light light = lightNode.CreateComponent<Light>();
            light.LightType = LightType.Directional;
    
            // Create a dumb box here for visualization purposes.
            Node boxNode = game.Scene.CreateChild("Box");
            StaticModel boxModel = boxNode.CreateComponent<StaticModel>();
            boxModel.Model = game.ResourceCache.GetModel("Models/Box.mdl");
            boxModel.SetMaterial(game.ResourceCache.GetMaterial("Materials/BoxMaterial.xml"));
        }
    
        internal override void Stop(Game game)
        {
        }
    }
    
    internal class SphereStage : StageBase
    {
        internal override void Start(Game game)
        {
            game.Scene.CreateComponent<Octree>();
    
            Node cameraNode = game.Scene.CreateChild("Camera");
            cameraNode.Position = new Vector3(0, 0, -3);
            Camera camera = cameraNode.CreateComponent<Camera>();
            game.Renderer.SetViewport(0, new Viewport(game.Context, game.Scene, camera, null));
    
            Node lightNode = game.Scene.CreateChild("Light");
            Light light = lightNode.CreateComponent<Light>();
            light.LightType = LightType.Directional;
    
            // Create a dumb sphere here for visualization purposes.
            Node boxNode = game.Scene.CreateChild("Sphere");
            StaticModel boxModel = boxNode.CreateComponent<StaticModel>();
            boxModel.Model = game.ResourceCache.GetModel("Models/Sphere.mdl");
            boxModel.SetMaterial(game.ResourceCache.GetMaterial("Materials/BoxMaterial.xml"));
        }
    
        internal override void Stop(Game game)
        {
        }
    }
    

    What do you think?

  • EsaKEsaK SEMember ✭✭

    Your solution (from August 22) seems very capable.
    It would be interesting to see if you experience any memory leaks when switching scenes... if you could add MonoDebugHud and watch MCW, if it stays constant or if it increases when switching scenes multiple times...

  • RayKoopaRayKoopa DEMember ✭✭
    edited August 2016

    Good idea, I didn't know how to check for such, now I do.

    Actually, it looks like there is one more object kept in memory with every switch. Can I print detailled information somewhere to identify it?

    EDIT1: It's obviously the scene object itself. It looks like when I replace CurrentScene.Remove() with CurrentScene.Dispose(), I do not get new MCW's with every switch. However, switching suddenly stops working after the fourth or fifth switch!

    EDIT2: Using SubscribeToSceneUpdate rather than attaching to the C# event and unsubscriping in Stop() fixes this unpredictable behavior. Is this just an issue with C# events or a bug?

Sign In or Register to comment.