Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Bringing existing POCOs into Unity

Discussion in 'Getting Started' started by DMFirmy, Aug 13, 2016.

  1. DMFirmy

    DMFirmy

    Joined:
    Aug 13, 2016
    Posts:
    11
    Hello all, this is my first post here on the Unity forums, so firstly let me send my greetings out to this community. I have recently been learning some of the basics of working in Unity, and though I feel as though I am grasping most of the concepts that I have learned, I must admit that working in Unity requires a pretty major shift away from the style of code that I usually write. In my daily career I am a C# programmer who works largely with data driven client applications and web based systems, but I do have a fair bit of experience working with the XNA Framework and its successor, MonoGame.

    I have already built out several parts of a game system that are written as a series of standard .NET class libraries, and after doing a little bit of tinkering and research, I have figured out how to recompile those .dll files so they can be used in Unity and to bring these objects into the Unity game environment.

    As a single example, here is a sample of the code for my ExperienceLevel class:
    Code (CSharp):
    1.  
    2. public class ExperienceLevel : IExperienceLevel
    3. {
    4.    public ExperienceLevel()
    5.    {
    6.      ...
    7.    }
    8.  
    9.    public ExperienceLevel(int startingExp) : this()
    10.    {
    11.      ...
    12.    }
    13.  
    14.    public ExperienceLevel(int startingExp, ExpRequiredForNextLevelCalculationDelegate calculation) : this()
    15.    {
    16.      ...
    17.    }
    18.    
    19.    public event LevelChangedHandler LevelChanged
    20.    {
    21.      add { ... }
    22.      remove { ... }
    23.    }
    24.    
    25.    public uint Current
    26.    {
    27.      get { ... }
    28.    }
    29.  
    30.    public uint Level
    31.    {
    32.      get { ... }
    33.    }
    34.  
    35.    public uint ExpRequiredForNextLevel
    36.    {
    37.      get { ... }
    38.    }
    39.  
    40.    public ExpRequiredForNextLevelCalculationDelegate ExpRequiredForNextLevelCalculation
    41.    {
    42.      get { ... }
    43.      set { ... }
    44.    }
    45.    
    46.    public void Adjust(int amount)
    47.    {
    48.      ...
    49.    }
    50. }
    51.  
    The full ExperienceLevel class is thoroughly covered by unit tests and has been tested in some of my previous game creations, and it is also utilized by other parts of my game system such as the attribute system that I have built. There are a few points about the above code snippet that I can use to really illustrate some of the issues I am having, and it is my hope that the Unity community will be able to point me in the right direction towards learning how to resolve some of these pains.

    This class exposes 3 properties of type uint, an event that is not a standard .NET event handler, and a void method which are all part of the IExperienceLevel interface. The values for all three of these properties are read-only and can only be modified through the use of the Adjust(int) method. You may also note that there is a custom delegate type property that allows you to set a custom calculation delegate, which allows me to attach this same ExperienceLevel class to many different types of objects but change the way that the experience points are applied towards advancing in level.

    Of course, the ExperienceLevel class is not a Unity MonoBehavior, and we might assume for the purposes of this discussion that it is part of a closed code base which I do not have direct access to. If I were to add an ExperienceLevel object to a MonoBehavior object, it wouldn't get displayed in the Unity Editor because the ExperienceLevel class is not itself serializable. I have been experimenting with this for a few days straight now, and after a fair bit of trial and error I was actually able to create a ExperienceLevelBehavior extension of the MonoBehavior class that utilizes Unity ISerializationCallbackReceiver, a custom editor that is associated with the ExperienceLevelBehavior class, and a simplified data structure I was able to put together the following simple editor where you can use the slider to set the current XP and everything is being handled under-the-hood by my ExperienceLevel object:
    Experience.png

    Now I realize this post is getting long and that I haven't actually gotten to my questions yet, so if you are still hanging in with me I want to thank you :). On its own, this seems to be a pretty decent solution, which allows me to attach my custom behavior to any game objects and have them have access to my ExperienceLevel class for managing experience points and levels. There are 2 main issues that I am encountering with the above implementation that I simply can't seem to figure out how to address.

    The first issue I have is with my delegate property. Remember that my ExperienceLevel class exposes a property of type ExpRequiredForNextLevelCalculationDelegate. How would I expose such a delegate in the inspector, and how would I point it at an appropriate delegate function to be used? The signature for this delegate is as follows:

    Code (CSharp):
    1.  
    2. public delegate uint ExpRequiredForNextLevelCalculationDelegate(uint currentLevel);
    3.  
    My second issue has to do with how other objects interact with my ExperienceLevel object. For example, I have created an attribute system that implements an ExperienceAttribute class which in utilizes the IExperienceLevel interface to track its advancement. You can supply the ExperienceAttribute class with an IExperienceLevel implementation to be used at run-time, but if you do not than an empty ExperienceLevel object will be created as a default.

    If I were to try to follow the same steps I used the first time around to create an ExperienceAttributeBehavior class and an associated custom editor, I would immediately realize that I have a small problem. Since my custom editor is tied directly to my ExperienceLevelBehavior class, it will not be re-used even though it exposes a property which, under the hood, is an ExperienceLevel object. Since the ExperienceLevel object itself is being stored and not a copy of the ExperienceLevelBehavior script, I would need to re-implement my editor somehow to expose the same functionality for my ExperienceAttribute objects.

    Certainly there must be a better way to handle such situations. I do not want to have to re-code an entire code base simply to make it more Unity friendly, nor do I want to throw away a great deal of well written and well tested code that I already have on hand. Any advice on these points would be greatly appreciated.

    Glad to be joining the Unity Community, and I hope that I will become an elite member some day offering my advice instead of seeking the advice of the current elites :p
     
  2. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,028
    DMFirmy likes this.
  3. DMFirmy

    DMFirmy

    Joined:
    Aug 13, 2016
    Posts:
    11
    Thanks for the response, I think that may indeed be exactly what I was looking for in regards to wiring up delegate functions. I will do some reading and hopefully come up with a working solution for my particular use case.

    That still leaves me with the questions I had about object composition and custom editors, but perhaps I will come up with an interesting solution here as I work on improving my existing work to hook up custom delegates.

    Thank you for the response. I appreciate the assistance.
     
  4. DMFirmy

    DMFirmy

    Joined:
    Aug 13, 2016
    Posts:
    11
    So I have been playing with the UnityEvent implementation for my editor for the last hour or so, and now I have couple of different questions. If I define a UnityEvent, I can call EditorGUILayout.PropertyField() to create an inspector to wire up the callback functions. However, if I use a generic UnityEvent<int> or UnityEvent<uint>, no inspector gets displayed. Am I missing something?

    Regardless, I am not actually sure how I would go about implementing the desired results using a UnityEvent in the first place. If I expose a public UnityEvent on my ExperienceLevelBehavior class, any callback functions defined will only be called if that event gets raised, which means I need to raise the event in my code somewhere. I don't really want to notify a subscriber of an event, I want to assign the callback as the delegate for my ExperienceLevel object. This would usually be set up at design-time before any experience points are applied so that the calculation can be applied for each level of advancement. Once the delegate is assigned, it will not usually get changed again.

    The desired result is that I could assign one delegate function to calculate the experience for my player and a different calculation for the experience of things such as individual attributes and skills. The more I play around with it the more it is looking like the solution will be to have to hard-code the delegate assignments.
     
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    It's probably just a limitation of the inspector. That one is easy to work around. The one that really annoys me is that if you define an event that takes an Enum parameter, you don't get a pop-up for the enum value, like you do anywhere else you inspect an enum value. Somebody was just lazy (and it's been that way since Unity 4.5!).

    True. That's the whole purpose of UnityEvents: a way for one bit of code (the sender) to notify zero or more receivers when something happens (button is clicked, hit is detected, enemy is slain, points are awarded, whatever).

    Well, I don't understand quite what you're saying here. But it's clear that you have a good understanding of interfaces, delegates, etc. in C#. And now I think you know what Unity offers on top of that. So, out of all that stuff, I bet you can find the right pattern for you in this case.