简体   繁体   中英

C# Easily removable class, MonoGame framework, Visual Studio 2013

I am developing a 2D game in MonoGame with Visual Studio 2013. Due to the ease of implementation, I also choose to implement the editor as a static class that contains ALL the editor functionality (key/mouse events, draw calls, other logic). I used Windows Forms as it was quite easy to do and I do not care much for performance for this task.

The way it works is that the Editor instances a form with the editor controls and that allows me to perform operations directly to the game data in memory for each draw layer, such as adding/moving/removing tiles or other elements. I found it quite convenient and it works fine so far with only a minimal amount of code brought to the main game/rendering loop. At the same time, I am able to view and work on the game window directly. To exclude the editor at any point, all I need to do is to delete the folder from the project that contains the Editor class along with any referenced classes and comment-out a few lines of code, giving me the clean version of the game.

However, I recently discovered I need to add more logic to the draw loop, the first issue being I need to visually indicate the selected tiles with a rectangle border around. This would be easy to do if I would interfere with the main game draw logic (the Renderer class), but I certainly do not want to keep the code there because it may get complex.

I could come over the drawn result (see below code, at the Draw override) and paint over, but that would force me to re-use a part of the rendering code in the editor. Also, that would loop again over all tiles and logic and I find that inefficient. The best way I thought about implementing this is to call Editor.DrawLayer() from the game's DrawLayer() 's own method if it exists. If it does not exist, do nothing. This way I would not need to remove anything else from the code, just delete the Editor class.

namespace Main
{

    public class Main : Game
    {
        ...

        public Main()
        {
            ...
        }

        protected override void Initialize()
        {
            ...
            Editor.Initialize();    // TODO remove these on publish
        }

        protected override void LoadContent()
        {
            ...
            Editor.LoadContent();   // TODO remove these on publish
        }

        protected override void UnloadContent()
        {
            ...
            Editor.Unload();        // TODO remove these on publish
        }

        protected override void Update(GameTime gameTime)
        {


            Editor.Update();        // TODO remove these on publish
            ...
            Renderer.Instance.Update(gameTime);
            ...
        }

        protected override void Draw(GameTime gameTime)
        {
            Renderer.Instance.Draw(spriteBatch, gameTime);
            ...
            // Editor.Draw();       // I would avoid this
        }
    }
}

I am able to recognize if the Editor class exists by using this method:

private static void GetAssemblies()
{
    Assembly projectAssemblies = Assembly.GetExecutingAssembly();
    namespaceList = new List<string>();

    foreach (Type type in projectAssemblies.GetTypes())
    {
        namespaceList.Add(type.FullName);
    }
}

public void TryStartingEditor()
{
    if (namespaceList.IndexOf("Main.Editor") > -1)
    {
        Config.EditorExists = true;

    }
}

However, I cannot add any code that would survive the Editor class removal as any namespace such as:

Editor.whatever();

would not be legal any more.

My main question is: What would be a good way of calling this class's methods that may not exist at a later point in time without the compiler going crazy?

I am open for any OTHER suggestion that would allow me to implement the editor as less-intrusive as possible relative to the main game loop.

Note. I know that removing a class is a simple matter of also deleting all pieces of code that reference it and that's not a big deal, maybe 5 minutes when the project is done, so know I am not determined to use this method, I am curious if it can be done with ease.

Later edit . I believe I can summarize the problem simply by showing these two scenarios. I would like scenario 2 to be possible somehow.

Scenario 1 . Editor class exists

public static class Editor {
    ... everything about editor
}


public function AnyFunctionAnywhere() {
    ...
    if (EditorExists) {
        // Ok, we run this, class exists, no problem.
        Editor.callSomeMethod();
    }
    ....
}

Scenario 2 . Editor class is suddenly missing and we don't necessarily want to remove all calls to it.

public function AnyFunctionAnywhere() {
    ...
    if (EditorExists) {
        // Somehow don't throw "The name 'Editor' does not exist in the current context".
        Editor.callSomeMethod();
    }
    ...
}

Thank you kindly and let me know if I should explain more clearly any aspects.

I believe I found a good solution using System.Reflection. I created a new singleton class that handles calling in the manner I wanted and it seems to work fine. Here it is below.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace Main
{
    public class LocalSystem
    {
        // Use this to store the list of classes/methods. bool is redundant
        public static Dictionary<Tuple<string, string>, bool> classMethods = new Dictionary<Tuple<string, string>, bool>();

        // Singleton
        private static LocalSystem instance;

        public static LocalSystem Instance
        {
            get
            {
                if (instance == null)
                {
                    GetAssemblies();    // We do this once. I suspect this to be slow.
                    instance = new LocalSystem();
                }

                return instance;
            }
        }

        // Editor specific callers
        public void InvokeEditorMethod(string methodName)                               // Call function
        {
            invokeClassMethod("Main.Editor", methodName, null);
        }

        public void InvokeEditorMethod(string methodName, params object[] values)       // Call function with some arguments
        {
            invokeClassMethod("Main.Editor", methodName, values);
        }

        // This tries to invoke the class.method
        private void invokeClassMethod(string className, string methodName, params object[] values)
        {
            if (!ClassHasMethod(className, methodName))     // We check if the class name and method exist. If not, we bail out.
                return;

            try
            {
                Type.GetType(className).GetMethod(methodName).Invoke(null, values);
            }
            catch(Exception e)
            {
                if (e is TargetParameterCountException)     // This error might be more common than others
                {
                    throw new Exception("Wrong number of parameters provided for method " + methodName + " within " + className);
                }
                throw;    // Something else went wrong
            }

        }

        private static void GetAssemblies()
        {
            Assembly asm = Assembly.GetExecutingAssembly();

            foreach (Type type in asm.GetTypes())
            {
                string discoveredClass = type.FullName;
                foreach (MethodInfo method in Type.GetType(discoveredClass).GetMethods())
                {
                    if (!classMethods.ContainsKey(new Tuple<string, string>(discoveredClass, method.Name)))
                        classMethods.Add(new Tuple<string, string>(discoveredClass, method.Name), true);
                }
            }
        }

        private static bool ClassHasMethod(string className, string methodName)
        {
            return classMethods.ContainsKey(new Tuple<string, string>(className, methodName));
        }

    }
}

Usage:

LocalSystem.Instance.InvokeEditorMethod("Initialize");

or

LocalSystem.Instance.InvokeEditorMethod("MethodWithParams", 1, "foo");

Notes.

  1. I haven't covered returning values from invoking functions. Tried for a few minutes but it seems a bit more complicated than I am willing to invest time for. Considering my working model, I should never expect a return value. If I would, it would mean I am moving the editor logic to where it isn't supposed to be, therefore I abandoned this aspect.

  2. You will notice that the exposed methods use the "Main.Editor" class. This is particular for my needs, but if you need to handle multiple arbitrary classes, you can expose a more broad implementation of invokeClassMethod.

Thanks!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM