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

Add Properties to a GameObject

Discussion in 'Scripting' started by ThySpektre, Jul 22, 2019.

  1. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362

    LOL...I'm not saying it's bad. I'm saying it not good... smh
     
  2. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    JSON creates datasets that are much too large. .Net's built-in serializer was exceptionally slow. We saw approximately a 5000x speed increase and a significant reduction in data size serializing the data on our own. This was best accomplished at the GameManager level.
     
  3. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,124
    If you've already accomplished it what's the point of this thread again? :p
     
    Kurt-Dekker likes this.
  4. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    :) The first post covers it nicely.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    What? I thought you were saying WE said it's not good... are you now saying inheritance is "not good"?

    Wat?

    o_O

    Your dichotomies are so bizarre and pedantic.

    Also:
    OK... so using my ISaveable description I said above at:
    https://forum.unity.com/threads/add-properties-to-a-gameobject.714551/#post-4777715

    You basically get the same thing. If you want a similar syntax you just create an extension method like follows:

    Code (csharp):
    1.  
    2. public static class GameObjectExtensions
    3. {
    4.  
    5.     public static ReplayState GetStateSignature(this GameObject go)
    6.     {
    7.         var state = new ReplayState();
    8.         foreach(var c in go.GetComponents<ISaveable>())
    9.         {
    10.             c.GetStateSignature(state);
    11.         }
    12.         return state;
    13.     }
    14.  
    15.     public static void RestoreState(this GameObject go, ReplayState state)
    16.     {
    17.         foreach(var c in go.GetComponents<ISaveable>())
    18.         {
    19.             c.RestoreState(state);
    20.         }
    21.     }
    22.  
    23. }
    24.  
    And random component could be like:
    Code (csharp):
    1.  
    2. public class HealthMeter : MonoBehaviour, ISaveable
    3. {
    4.  
    5.     public float MaxHealth;
    6.     public float CurrentHealth;
    7.  
    8.     public void GetStateSignature(ReplayState state)
    9.     {
    10.         state.Add("MaxHealth", MaxHealth);
    11.         state.Add("CurrentHealth", CurrentHealth);
    12.     }
    13.  
    14.     public void RestoreState(ReplayState state)
    15.     {
    16.         MaxHealth = state.Get<float>("MaxHealth");
    17.         CurrentHealth = state.Get<float>("CurrentHealth");
    18.     }
    19.  
    20. }
    21.  
    Done.

    (not sure you ReplayState implementation... I just went with a generic string read/write like most serialization contexts. If you're concerned about name clashing... give identifiers to it like "HealthMeter.MaxHealth" instead.)
     
    Last edited: Jul 23, 2019
    Ryiah likes this.
  6. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    This appears to save state information for each component instead of for the GameObject
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Cause the GameObject has no state information to save directly!

    Components are what make the GameObject...

    The only thing missing might be:
    1) the 'name' of the GameOject, in which case... save that to the 'state' in the extension methods I shown.
    2) 'Transform' doesn't implement ISaveable, so if you want to save position/rotation/scale you'll need to also save that. Which again can be done via the extension method.
    3) any built in components like Rigidbody, Collider, etc that you might use... which would require special logic for.

    Mind you both of these things would have needed to be done in an inheritance model as well since you'd have to have something know to write these same name, position, rotation, scale values there as well. This isn't a problem unique to this solution... but relevant to all possible save systems.

    Your cognisant disconnect between "is a" vs "has a" is so bizarre to me.
     
  8. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    However that is what I am trying to emulate. Additional insults and/or examples doing things to the contrary will not help you (or me) here.

    The GameObjects are indexed in the manager.

    More goodies from this "best practice".

    I wouldn't. The object can easily know how to redistribute it's state information without writing a manager to do as such.

    As does seeing this:

    Code (CSharp):
    1.     public static ReplayState GetStateSignature(this GameObject go)
    2.     {
    3.         var state = new ReplayState();
    4.         foreach(var c in go.GetComponents<ISaveable>())
    5.         {
    6.             c.GetStateSignature(state);
    7.         }
    8.         return state;
    9.     }
    And thinking it returns the state signature for the GameObject. It returns a signature for a component of it. The indexing information is already large. Trying to serialize Object names and Component names just isn't feasible here.

    Again, I appreciate the help you are trying to give, but if you do not feel you can do so without insults, I'd prefer you simply refrain.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Yeah... then store that state. Inheritance vs Composition doesn't change this... especially since that information isn't part of the GameObject, but is in the game manager.

    As I stated, this doesn't change between inheritance vs composition. Transform/Rigidbody/Collider/etc are all always going to be components. You have to deal with that regardless of "practice".

    There's no manager doing it... the components do it.

    Except for the exceptions I listed... in which case, you have to write the code somewhere regardless of composition vs inheritance. What's the difference if it's in an extension method, or an inherited method?

    I don't know what you actually specifically need.

    I demonstrated pseudo-code for a general purpose save system.

    The fact you have this extra info... ok... then save it.

    Or what... are you trying to goad me into writing a fully functional save system that meets your needs?
    1) you need to define your needs, i don't read minds.
    2) no.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Or wait... in regards to the Transform/Collider/Rigidbody...

    Are you suggesting you should be able to "inherit" those abilities?

    And thusly your inheritance would create your states fine if Unity foregone the entire component model?

    Cause I have to say... the number of classes that would be created then would be insane.

    I have my BoxColliderGameObjectWithRigidbodyAndLight, I have my SphereColliderGameObjectWithLightAndNoRigidbody, CapsuleColliderGameObject, CapsuleColliderGameObjectWithLight, CapsuleColliderGameObjectWithLightAndRigidbody, CapsuleColliderGameObjectWithRigidbodyButNoLight..........
     
    Suddoha likes this.
  11. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    This isn't going anywhere.

    You've got that one specific idea how you like things to be, which cannot be done that way, yet you seem to want it so badly that you refuse to accept the alternatives.

    So, while trying to find a good example, I decided to take the one you've given earlier, because its description seems odd:

    Turn that first paragraph into pseudo code, please.
    How does inheritance specifically help here?

    Do we agree that a "bicycle without wheels" is either still a bicycle (in an invalid state) or not a bicycle at all?

    Wouldn't you just have a bicycle take the absence of its wheels into account?

    Sorry, perhaps I'm very tired, but for me your paragraph translates to this weird construct:
    type Bicycle // has wheel functionality
    type BicycleWithoutWheels : Bicycle // bicycle changes its type when wheels not installed !?!?! in order to suddenly be aware of and handle missing functionality?

    You could say it's the wrong order, but you were talking about a bicycle without wheels, which is a bicycle lacking some components... so, it's a bicycle.

    Obviously, this approach uses a wrong order to start with.
    For the sake of being less pedantic, another attempt:

    type BicycleLike // no wheel functionality?! Hu? Just a frame or just an abstraction? Why no wheel functionality?
    type Bicycle : BicycleLike // with wheel funtionality, erm okay?

    So if I remove wheels, my bicycle needs to change its type, and as a consequence its identity, i.e. a new object needs to be constructed?

    That does not make any sense. You wouldn't enable the ability of "taking the absence of missing wheels into account" with inheritance. It's clearly the wrong tool.

    What you can do with inheritance is to have multiple, equally or similarly ranked subclasses that handle it differently, for whatever reason... but they all need to handle it in some way and thus they all have that notion of "wheel functionality".

    A "bicycle without wheels" is either not a bicycle, or it is still a bicycle, but then it's malformed, you could say its in an invalid state. Perhaps it's a feature, but it's definitely part of a bicycle's state.

    If you were to model this state with inheritance , i.e. model that state using different types, you'd need to replace the object entirely, it's identity needed to change, because the type would no longer be the same. According to your logic, you could not just treat it as a more generalized type, because the actual type could use the wheels in any overriden behaviour, as it knows it has got wheels.

    Instead, it should be
    type Bicycle : X
    type Wheel : Y
    and Bicycle has 2 Wheels. It's composed of wheels and other stuff.

    That whole thing we composed, that's what we happen to call "bicycle". I'll get back to that later, let's take a look at your concerns about solving the missing wheels case:

    So, what if we removed those wheels?
    First, note that this operation implies that your bicycle can have wheels. Can have, as in "a bicycle has wheels". It's a component that makes up a bicycle, anything else does not make sense.

    So, how to deal with it?

    The bicycle could have ways to query whether wheels are available if you want. An attempt to "use" a bicycle without wheels (i.e. in the way a bicycle can normally be used) would be a case to handle: exceptional case a.k.a. invalid operation, or funny feature?

    How to fix its state? We install / add wheels, for example as components. We do not create an entirely new object, we do not re-instantiate the frame, the brakes and all the GOs.

    Now let me try to make a transition to your idea:

    You'd want to subclass GameObject in order to get X, then Y, perhaps Vehicle and finally Bicycle.

    What have you gained? You need type "Bicycle" in order to treat it as such, so why don't you use MonoBehaviour as base type? It's almost the same, because you rely on that specific type in order to access public members of Bicycle.

    A GameObject, or a hierarchy of multiple GOs, is that thing which is the container, the handle, the backbone, whatever you want to call it... that glues and holds components together to build an entity in your world. It also allows to associate an intuitive name for that particular entity, to tag it, to make it available / unavailable in your world. Those are fundamentals to manage that entities, the basics and some "meta" stuff that every entity must have in Unity.

    What makes that thing a bicycle?

    1) Visually, we turn that into a bicycle by using renderers and the other required components.
    Now, it does already look like a bicycle.

    2) For the behaviour part, we turn that "thing" into something that can behave like a bicycle
    Attach a handle bar component if we need behaviour for that, brake components, wheel components...

    You could stop here. But it's often inconvenient and impractical to orchestrate all these components when you want to call a seemingly trivial action that is internally quite complex.

    So here's the optional step 3) Make this "thing that qualifies as being a bicycle" an actual bicycle: Attach a bicycle component which hides the tideous details and complexity of orchestrating the modular components. It's not a must, like already stated, you can just go with GetComponent from literally any reference to the GO or one of its components.

    So here's that "controller" type that you seem to dislike... but why? If you were able to inherit GameObject, you'd just do that: implement BicycleGameObject. Either with compoments as an implementation detail as outlined above, or as a huge god class.

    Put all these design decisions aside and get to the meat of the whole thing:

    DO NOT
    Code (csharp):
    1. GameObject bicycle;
    2. bicycle.GetComponent<Bicycle>().MethodSpecificToBicycle().
    DO
    Code (csharp):
    1. Bicycle myBicycle;
    2. myBicycle.MethodSpecificToBicycle();
    If we gave you the latter, would YOU know whether Bicycle was

    - a god component implementing everything by itself
    - a controller component knowing N other components that are easy to exchange under the hood

    - a god subclass of GameObject (if you could inherit GameObject)
    - a controller subclass of GameObject orchestrating N components

    Would you know it? Would the caller care about it? Most likely not. Because these are implementation details hidden behind the type you're working with: Bicycle.

    I hate the word bicycle now...
     
    Last edited: Jul 24, 2019
    Ryiah and lordofduct like this.
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Wow, I thought I was the only one with that impression... tried to analize that in my lengthy reply. Lol
     
    Ryiah and lordofduct like this.
  13. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    No the components DON'T do what I originally asked. The components store their individual information, not the combined information for the GameObject. Storing off indexing information for which component data comes from is prohibitive.

    Except for the exceptions I listed... in which case, you have to write the code somewhere regardless of composition vs inheritance. What's the difference if it's in an extension method, or an inherited method?

    I described it in the first post.

    Yes, for one in which each component stores its data separately. This is not what was requested.

    What "extra information": are you going on about?

    I'm not trying to goad you at all. In fact, i think I have actively tried to discourage you from posting further twice.
     
  14. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I have asked for what is the best way to implement a type of functionality common to most OOP programming languages, without a receiving an emotive defense of a particular design methodology. Such an example was given for methods of a GameObject implemented via Interfaces.

    Psuedocode was given previously.

    I'm not the one defending composition as the be all design philosophy. I was responding an earlier query that Unity "Unity uses well-known OOP components pattern." Utilizing one design pattern, while leaving off another (handlebar, wheels) still leaves one with working out how to work around not having wheel on your bike.

    Which was the purpose of the first post. Restated in this metaphor, "Given that Unity does not have wheels, and that I still wish to use it to get from point A to point B, what methods exist to work around this?"

    ===a large portion snipped as the metaphor was misunderstood===
     
  15. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    The issue is, the functionality of the bicycle is not held strictly in the MethodSpecifictoBicycle class. Some functionality of a bicycle is in classes NOT specific to a bicycle, because it is not specific to bicycles. Other functionality is held in components associated with all GameObjects. Nevertheless, despite the fact that this functionality is not SPECIFIC to a bicycle, it does exist as a part of the bicycle, it influences it's state, and I would like to be able to access it as a member of the bicycle, without needing to have my external code keep a map of the internal structure of the bicycle, nor have a manager class on the bicycle itself to do the data collection/internal messaging.

    As I said previously, absent any better "kludge" I can create such an internal manager component that responds to Interfaces implemented in it.
     
    Last edited: Jul 24, 2019
  16. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    And for that generic part, you cannot use one extra component?

    It's really just that where I as well as the other guys here fail to follow the issue you seem to encounter.

    Let's re-iterate:
    Goal: You want to build a save system.

    Type Requirements: PlayStateManager, GameObjects, PlayState associated only with GameObjects via indexing / mapping.

    NO knowledge of specific component types in that system, but more specific types can exist outside of that system.

    Is that correct?

    Required operations:
    - SendReplayState
    - ReceiveReplayState

    The names are a little confusing. Based on your post, Send is for writing the state, whereas Receive is applying state.

    Anyway, is that still correct so far?

    Let's take a break and first of all appreciate that these sort of systems do already exist.

    Please bear with me, I know you've tried JSON and it was too slow for your needs. However, let's see what common serializers are capable of.

    They do not need to now how to handle instances of type X or Y as an X and Y respectively.
    But they do know how to treate an object when they've got an instance of the appropriate System.Type, which contains the meta information that makes up X and Y and which you can get for any type you want. typeof(X), typeof(Y) or x1.GetType() and y1.GetType() yield such type instances.

    In other words, they process type descriptions, type meta information such as member names, their types, attributes of members or the type itself and via that information, they can get down to the actual values for that meta information which is stored in an instance of type X. Sort of "another dimension" in which everything is possible (off topic: very interesting topic when you apply this to our physical world).

    So, here we go again.... I mean, someone already suggested using JSON serialization. The common .NET und Unity serializers for JSON and most other formats use reflection in order to look for relevant meta information. That's because these kind of systems shall be self-contained and they have to be built in a way that they do only work with their best friend: System.Type and some (custom) attributes and utilities that they either know or declare themselves. That's really everything they know.

    Does that sound at least similar to what you want to build? A system that is sort of type-agnostic? Which passes the stream of "state signatures" around, as you like to call it?

    The huge difference is that you obviously want to decide how to (de)serialize (send/receive as you call it) data within the types themselves as an additional part of their behaviour? They read/write to the stream themselves?

    Is that correct?

    It might be an intuitive approach, but it's also very intrusive system. But is it kinda what you aim for?

    Sorry if that's just a lengthy re-iteration of your posts and if i waste your worthy time here. I'm just trying to find out why you think inheriting GameObject would be way better to make this work... :confused:
     
    lordofduct likes this.
  17. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362

    Yes, I think that captures it.

    Yes. If you read above you will see that I have already implemented this (for a gameObject with a single component) in JSON and using the .NET Binary formatter. The results were exceptionally slow in the latter, and much too large in both cases. I've even dissected the output files with hex editors to learn much of its internal structure to understand why it is so large. (Although I am curious why the .NET serializer runs so slowly.)

    I have thus implemented a not-so-agnostic serializer utilizing information the application will know specific to the data stream. It has a list of GameObjects being serialized and each GO reports out its state. The speed increase was several orders of magnitude faster, and the size with optimization dropped a couple orders of magnitude.

    Yes, for the reasons given above. I wish to serialize data about a given GameObject. I do not wish to serialize data from a component of a GameObject. The properties are functionally properties of the GameObject, not of a the component.

    In any event, I wrote the manager class I referred to last night to accomplish this. It polls the various components attached to the GO such that one component (through an Interface) functionally behaves as "the object" to the outside world, allowing me to accomplish what I was looking for.

    I do consider this a kludge, but realize based on the posts here that opinions vary and seem very emotionally rooted in some toward this component model.

    Thanks for taking the time.
     
    Suddoha likes this.
  18. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    LOL. Here we go again. Another purely entertaining L'art pour l'art thread about a false premise.

    Unity C# is a scripting language, not an OOP framework. And it works just fine with even the best practices (with some tweak), but it has been built for a very specific way to work and it is not a mistake, it was an intent. End of story.
     
    Ryiah and Suddoha like this.
  19. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Okay, so you won't like my reply again I guess, but this is not a big problem at all with components. And there are various ways to do that.

    So that one way is the one you've already implemented in the meantime. Apparently similar to the ISaveable from Lord's framework. This detects all components that have the interface and your algorithm lets everyone read and write its thing. And for the GO part, you've got that special component.

    Also, Legacy Networking in Unity has done that similarly, except not relying on an interface, but on messages (was it OnNetworkSerialize and Deserialize?, too lazy to look it up).

    As these bring the serialization and deserialization logic to the receivers, from the perspective of the components, this is sort of a more imperative serialization system (step by step serialization on component side).

    Other serializers which utilize reflection, as described earlier, rely on a more declarative approach.

    We all know examples for the latter... common serializers (JSON; Binary, XML), Unity's serialization system, ...

    However, what's interesting:
    Unity's UNET used a combination of both. During development, you'd have that declarative approach using attributes, upon building they generated properties and some other stuff in order to turn this into a more imperative system.

    I'm repeating myself, I know. So let's get back to your idea, and let's figure out how to hide all the components for your manager, including the notion of having multiple components having ReplayState.

    (*Edit: added suffix 'Behaviour' to not confuse with the data we happen to call ReplayState here)

    In your system, you're always having at least GO_ReplayStateBehaviour, but not necessarily COMP_ReplayStateBehaviour & subclasses.

    According to you,
    type GO_ReplayStateBehaviour
    type COMP_ReplayStateBehaviour: GO_ReplayStateBehaviour
    type ReplayStateManager knows only GO_ReplayStateBehaviour

    is not what you desire 100%, as COMP_ReplayStateBehaviour is not necessarily a subclass of GO_ReplayStateBehaviour.

    Let's note: If you could inherit GameObject, you'd ironically still need at least one additional component, i.e. you need to use component based design. Otherwise, you'd not match your own requirement that COMP_ReplayStateBehaviour is not a GO_ReplayStateBehaviour. I hope you can follow...

    So, let's slightly change it, since we should agree now, that your own requirements demand component design, we actually end up just doing what has been said all the time.

    type GO_ReplayStateBehaviour
    type GO_CustomizedReplayStateBehaviour : GO_ReplayStateBehaviour // optional, or seal the class
    type COMP_ReplayStateBehaviour // we have that special seperate interface or base class

    ReplayStateManager knows GO_ReplayStateBehaviour:
    It accepts anything assignable to GO_ReplayStateBehaviour (a concrete GO_ReplayStateBehaviour, a subclassed one)
    It does not accept COMP_ReplayStateBehaviour, nor does it look for them, but a GO_ReplayStateBehaviour can have references 0 to N COMP_ReplayStateBehaviours.

    Requirement #1 is met: Manager only knows GO_ReplayStateBehaviour
    Requirement #2 is met: COMP_ReplayStateBehaviour is not necessarily a GO_ReplayStateBehaviour
    Requirement #3 is met: Manager is not aware that how many behaviours with replay state exist per GameObject

    Long story short:
    Fancy names and lengthy explanations aside, this is just a proxy. Just like your inherited GO would be a proxy, because someone would need to forward and route the buffer / stream of ReplayState to at least one additional component, if that target was not a allowed to subclass of the inherited GO itself...

    I hope that makes sense.
     
    Last edited: Jul 25, 2019
  20. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    It certainly had an intent. It is one that can however be worked around with creative kludges.
     
  21. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    No. You are misunderstanding the issue in this case. The ReplayState is object agnostic. The issue is the number of "objects" and their logical functionality. The properties are properties of a few GameObjects. They are not properties of relatively many components. They act as a functional block in this way and it promotes much more compact data structures.

    The rest snipped as you fundamentally misunderstood the requirement. The issue is simplicity itself for an inheritance design pattern.
     
  22. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Yes, but no sane developer would do it. It's easier, more reliable, more clean to actually use and utilize the services and the framework what the engine provides rather than to try to hack around just because you love a certain way to do things. If it bothers you then you're using the wrong tool (Unity), you need to revisit your decision when you chose it.

    But we debated this countless times, why we need to go through this again?
     
    Ryiah and lordofduct like this.
  23. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I'd agree. Why did you choose to bring it up?

    Sometimes a hammer is the right tool for the job, regardless of the fact that your toolbox only has a wrench.
     
  24. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Yeah, but then you aren't the right guy for the job if you don't have your hammer.

    BTW, this situation is like this: you have a screw, you're trying to hammer it in the wall with your screw-driver. We're asking why would you do that? And then you tell us that the screw-driver is a very bad hammer. Well.
     
    Ryiah, roojerry and lordofduct like this.
  25. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I disagree. I have a nail, and a wrench.

    On the other hand ... you don't truly seem to believe your earlier words

     
  26. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I like Lurking-Ninja's analogy.

    Component/Composition = screwdriver : the way Unity expects you to work with GameObjects
    Inheritance = hammer : the tool you want to use
    GameObject = screw : it's not a nail because it's designed by Unity and Unity made it use components/composition.

    You're trying to use inheritance to add functionality to your GameObject via components.
    You're hammering your screw with a screwdriver.
     
    Ryiah and roojerry like this.
  27. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Well, whatever then.
    So you're basically telling me to shut up? Or what? I was asking you (you obviously ignored the question): why we need to discuss this again? What new thing did you bring to the table?
     
  28. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362

    I prefer:
    Inheritance = hammer : the right tool for creating a derived class from GameObject
    Composition = wrench : The tool Unity provides
    Your continued responses = neither a useful tool, nor a solution.
     
  29. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I'm not telling you to shut up. I'm questioning why you are choosing to continue a conversation you have already deemed needs not occur.
     
  30. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Well, if you don't write an application, you don't write a game, but you want to write an inherited game object, then you're right.

    The rest of us see this differently, we're making software, not inherited classes. If the right tool is to use composition (like in the Unity environment), we don't try to force inheritance just because we like it (we don't BTW).
    Right tool for the right problem.

    You don't even have the right problem.
     
    lordofduct likes this.
  31. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    OK, fine...

    You're hammering your nut/bolt with a wrench.

    Also I gave plenty of solutions, as well as many other users. Your not liking them doesn't make them not useful. It makes them something you don't like.

    That's the entire thing going on here.

    You don't like composition, you like inheritance. And you call us dogmatic...
     
    Lurking-Ninja likes this.
  32. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    They do not solve the question asked. They solve an imagined question asked that you wish to be dogmatic about. That you continue along in a thread after having explained to you that you are not being helpful is demonstrative of this. To use your reasoning...this isn't the thread you want. Perhaps find another where your information is helpful?
     
  33. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Its curious that you seek to define my problem. You had such insight earlier in the thread when you knew it was not for you...What happened?
     
  34. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    9,903
    Well, good luck with your endeavors.
     
    ThySpektre likes this.
  35. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    My mother was down on her luck and so I let her live with me. I said she doesn't have to pay me anything and she can get a job and just save up money.

    1st job, after a month, she quits. I ask her why and she says:
    "Those ladies were crazy and out to get me."

    2nd job, after a month, she quits. I ask her why and she says:
    "Those ladies were crazy and out to get me."

    3rd job, after a month, she quits. I ask her why and she says:
    "Those ladies were crazy and out to get me."

    Is that so mother? Because I'm noticing a trend here. A common denominator. Is it that everyone is crazy? Or that you are?

    "Everyone else of course!"

    Yes mother, it's certainly everyone else.
     
    Ryiah and Suddoha like this.
  36. WallaceT_MFM

    WallaceT_MFM

    Joined:
    Sep 25, 2017
    Posts:
    394
    The problem here is that your question has already been answered
    "In a related question. Since Unity does not treat GameObjects like objects, how would one create a "property" of the game object, not of a component of the object."
    "You don't. Use composition."
     
  37. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824

    Sorry, but I don't think so. I'll just insist on it now. Unless you want magic to happen. I don't believe in magic.

    Sure, the naming I used for the types is not that good, but the type names i used are the behaviour types that want to read and write data.

    They weren't meant to be the data type of the information you want to read and write. Just imagine there was a "behaviour" suffix everywhere.

    The argument for their read/write methods would just be a stream or buffer for that replay state that you can place any values in, so that state (the outcome itself) can be agnostic to a particular source's (i.e. components) type defintion... and as long as you get the same data blob back when applying the replay state, your components can apply it, as they are the only ones who know how to apply it. So in the system it is a very loose format... but someone needs to know what it contains, at the very least the one who reads and writes it.

    If you still say no, that's not it... then please, for the sake of saving us some time, post an implementation of such read / write operations. Because in some way it has to be done, and you even agreed it's like reading from and writing to a stream or buffer. But you're attempting to keep it that much abstract that I get the impression you don't even know 100% what you're looking for. Do you even have any code for that, or are you just trying to solve this in theory?

    Post these pieces of the puzzles, and we'll fill the gaps. We're all very patient, but we cannot read your mind... you're keeping this on a level which is far too much theory, with a very wishy washy abstract description although you seem to have an implementation at hand... there's no practical information, so it's your turn now.

    Again, we cannot read your mind.
     
    Last edited: Jul 25, 2019
  38. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Thanks. Now that is a reasonable reply!
     
  39. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I overheard two of my friends talking one day.
    One stated he wanted to go buy a Coldplay album, and asked where the best place to buy one.

    The other stated Coldplay sucks and he shouldn't go buy a Coldplay album, classical music was much better.

    My friend stated he did not want to listen to classical music, he wanted to listen to Coldplay, where was the best place to buy a Coldplay album?

    The other berated him. Haven't you heard of jazz or swing? Any of those let you listen to music, and better music than Coldplay!

    "But I'm not looking to listen to any music", the first said, "I want to buy a Coldplay album and listen to Coldplay."

    "I've already given you multiple answers to your question", the second roared. Coldplay is OLD music. Good musical tastes dictate you listen to some other group, the second persisted.

    So my friend asked me.

    "Eh, The only local store I know that might carry those is on the corner of 4th and Broad.", I told him.

    He went to the store, and bought a Coldplay album and listened to it.

    The second friend came to this thread evidently.
     
    Last edited: Jul 26, 2019
  40. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Nonsense. One method is to build a manager class and through Interfaces kludge it.
     
  41. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    As I mentioned some posts back...

    "In any event, I wrote the manager class I referred to last night to accomplish this. It polls the various components attached to the GO such that one component (through an Interface) functionally behaves as "the object" to the outside world, allowing me to accomplish what I was looking for.

    I do consider this a kludge, but realize based on the posts here that opinions vary and seem very emotionally rooted in some toward this component model.

    Thanks for taking the time."
     
  42. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    As for mind reading, I've been very clear what my desired goal is.

    "In a related question. Since Unity does not treat GameObjects like objects, how would one create a "property" of the game object, not of a component of the object.

    If I have a GameObject that is to report it's state to a Game Manager at regular intervals, and this state is split among variables in the many components implementing the functionality, is there a kludge similar to using Interfaces to obtain method fucntionality that allows these variables to be grouped together for reporting out?

    Something that does not involve one of the compoents having a listing of GetComponent<"scriptname"> statements in order to access the various variables split among components (that should simply be properties of an inherited from GameObject)?"

    The aforementioned manager is the current kludge selected.
     
  43. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    And we all said that you can easily accomplish that by adding that functionality through a component which does the task that you'd add to a subclass of GO.

    We're running circles. Don't you realize?

    Thats all you need. You can solve it via interface per component and use GetComponents (plural), but you could as well make this part be a subtask of that one component that serves as the GO extension. Your manager wouldn't ever know that it has to deal with multiole components and it does not need a mapping for N comps for a given GO, which was one of your major concerns.

    Both is possible. Now you choose the former which is fine, although the latter comes a little closer to your requirements. I'm not saying that one is better than the other, just that it is closer to what you described.

    This is not much of a difference to what you seeked for. And that's what everyone was saying here.
    Simple and straightforward.

    And yes, you described your theory all over the place, and every time you get the same response... either tske it as it is, or get into real and concrete details, not some generalized description that you keep posting. This is not how it works. Sometimes you just need more details to come up with a solution aka implemention... in the end, thats what programming is all about.
     
    Last edited: Jul 26, 2019
  44. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    I'd agree we are running in circles, just probably not the same ones you perceive.

    Somewhere must. Either a manager on the GameObject, the Game Manager, or the dataset must handle the mapping. Within the GameManager would be a nightmare for maintenance, and within the dataset leads to an impossibly large dataset.

    Thanks again for your time. The current kludge is the optimal one so far to work around this limitation.
     
  45. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Quickly outlined, yours could look as follows. Used some inlined instructions that I wouldn't do in real code, but it's for demonstration purposes, and I wanted to keep it short.

    The manager, which you always keep talking about
    Code (CSharp):
    1. public class ReplayStateManager : MonoBehaviour // doesn't need to be a MonoBehaviour
    2. {
    3.     public void ApplyReplayStates()
    4.     {
    5.         // TODO: Load your replay states from anywhere
    6.    
    7.         foreach (var behaviour in FindObjectsOfType<GOReplayStateHandler>())
    8.         {
    9.             // pass the buffer, the stream, the dictionary, whatever you're data is stored in
    10.             behaviour.ReadFrom(buffer_srream_dictionary_whatever);
    11.         }
    12.     }
    13.  
    14.     public void GatherReplayStates()
    15.     {
    16.         var buffer_srream_dictionary_whatever = ...
    17.         foreach (var behaviour in FindObjectsOfType<GOReplayStateHandler>())
    18.         {
    19.             behaviour.WriteTo(buffer_srream_dictionary_whatever);
    20.  
    21.             // TODO: handle the object filled by the handler
    22.             // e.g. map to the objects Identifier
    23.         }
    24.     }
    25. }
    And here's the thing you wanted to subclass GO for...
    Note that you can subclass this component instead, in order to implement everything you wanted to implement for your custom GO type...

    Code (CSharp):
    1. // This is the only type your manager works with directly.
    2. // It's either: GO has exactly 1 one that, or NONE, hence the attribute
    3. [DisallowMultipleComponent]
    4. public class GOReplayStateHandler : MonoBehaviour
    5. {
    6.     // Manager passes a buffer that only contains the data that was written for this GO
    7.     public virtual void ReadFrom(byte[] c, int dataLength) // dataLength is <= c.Length
    8.     {
    9.         // TODO: Recover GameObject's Replay State
    10.         // e.g. read its position, its rotation, its scale...
    11.     }
    12.  
    13.     // Manager passes a buffer that can be written to (will be copied when done)
    14.     // Manager can thus constrain max len to c.Length or pass an over-sized buffer
    15.     public virtual int WriteTo(byte[] c)
    16.     {
    17.         // TODO: Save GameObject's ReplayState
    18.         // e.g. read its position, its rotation, its scale...
    19.  
    20.         return 0; // return the number of bytes written to the buffer
    21.     }
    22.  
    23.  
    24.     // do you prefer dictionaries?
    25.  
    26.     // dict version with int keys
    27.  
    28.     // some constants
    29.     private const int POS = 0;
    30.     private const int ROT = 1;
    31.  
    32.     // Manager passes a dictionary containing all values that were stored for this GO
    33.     public void ReadFrom(IReadOnlyDictionary<int, object> dataMap)
    34.     {
    35.         var position = (Vector3)dataMap[POS];
    36.         var rotation = (Vector3)dataMap[ROT];
    37.  
    38.     }
    39.  
    40.     // Manager passes a ready-to-use dict
    41.     public void WriteTo(Dictionary<int, object> dataMap)
    42.     {
    43.         dataMap[POS] = transform.position;
    44.         dataMap[ROT] = transform.rotation.eulerAngles;
    45.     }
    46.  
    47.     // Manager passes a dictionary containing all values that were stored for this GO
    48.     public void ReadFrom(IReadOnlyDictionary<object, object> dataMap)
    49.     {
    50.         var position = (Vector3)dataMap["go.pos"];
    51.         var rotation = (Vector3)dataMap["go.rot.euler"];
    52.         //... whatever
    53.     }
    54.  
    55.     // Manager passes a ready-to-use dict
    56.     public void WriteTo(Dictionary<object, object> dataMap)
    57.     {
    58.         dataMap["go.pos"] = transform.position;
    59.         dataMap["go.pos.euler"] = transform.rotation.eulerAngles;
    60.         // or c["go.pos.quats"] = transform.rotation;
    61.     }
    62. }
    If you keeping telling this doesn't work as you desired, I can tell you for sure that your original idea WOULD NOT have worked either. That's a fact, because this is a matter of a simple type replacement.

    Now you can either subclass this down to any level you desire and do what you wanna do.
    Or you keep the hierarchy flat (note the base class for other replay state components):

    Code (CSharp):
    1.  
    2. public sealed class CustomizedGOReplayStateHandler : GOReplayStateHandler
    3. {
    4.     [SerializeField]
    5.     ComponentReplayStateBase[] _blackboxComponents = null;
    6.  
    7.     public override void ReadFrom(byte[] c, int dataLength)
    8.     {
    9.         base.ReadFrom(c, dataLength);
    10.  
    11.         // TODO: write additional state for this customized one
    12.         // and / or
    13.         // TODO: let other components read from it (like those in the array which is declared as field)
    14.     }
    15.  
    16.     public override int WriteTo(byte[] c)
    17.     {
    18.         var goDataLength = base.WriteTo(c);
    19.  
    20.         // TODO: write additional state for this specialized implementation
    21.         // and / or
    22.         // TODO: let other components write to it (like those in the array which is declared as field)
    23.  
    24.         return goDataLength + // whatever has been writte to it;
    25.     }
    26. }
    27.  
    28. // base class for anything else that needs replay state
    29. public class ComponentReplayStateBase : MonoBehaviour
    30. {
    31.     // your methods, they have different requirements based on what container you use
    32. }
    33.  
     
    Last edited: Jul 27, 2019
  46. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    The post above demonstrates that all you need is a simple substitution, whereas you claim it's a completely different thing.

    But anyway, I guess you won't care since your new attitude is somewhat naive, not to say ignorant, by saying indirectly that you're forced to live with that ugly "klutch" you had to implement to make such an easy system work. And that you're unfortunately falling a victim to Unity's limitation of GO... This is not professional. This is nonsense.

    I don't understand why you do not realize the answer is "use one component type instead of a subclassed GO". It meets all the requirements you've thrown in here, except for GO inheritance. Besides that, this is a task you'd be able to accomplish just after a couple of weeks of learning OOP.
     
    Last edited: Jul 26, 2019
  47. WallaceT_MFM

    WallaceT_MFM

    Joined:
    Sep 25, 2017
    Posts:
    394
    Here:
    Code (csharp):
    1.  // Fiction
    2. public class ResettableGameObject : GameObject
    3. {
    4.     public void Reset()
    5.     {
    6.         // Some logic
    7.     }
    8. }
    9.  
    10. // Reality
    11. public class ResettableGameObject : MonoBehavior
    12. {
    13.     public void Reset()
    14.     {
    15.         GameObject go = this.gameObject;
    16.         // The exact same logic as above using go instead
    17.      }
    18. }
     
    Lurking-Ninja likes this.
  48. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    The above code is similar to what I implemented as my kludge. However, what your code leaves out in the manager is that you read state info (you've chosen a stream, but as you noted other structures work as well) and write state info but you have not concatenated that stream anywhere to form the stream sent from the GO, nor have you maintained the list of components each stream has come from in order to redistribute the information.

    Thus as I stated in an earlier post, you could maintain that list in the object manager component. Alternately, you could place this indexing information into the stream itself. Both of which I consider a kludge and not good OOP practice. The latter of these causing the data size to balloon.

    Now, the task I have desired has been laid out sufficiently. If you can help solve it great. But suggesting that methodologies that do NOT solve the problem at hand somehow indicates a lack of professionalism on my part is maybe something that should implement a bit of reflection. :)
     
    Last edited: Jul 26, 2019
  49. ThySpektre

    ThySpektre

    Joined:
    Mar 15, 2016
    Posts:
    362
    Well, not really though is it? This is an answer to the question posed in an earlier thread. IN that thread I made it fairly clear the Door, Window, etc do not all Reset with the same code. I think the post demonstrates the usefulness for inheritance I have been talking about. Making a generic "reset" component is problematic as each object likely resets in different ways.

    So you could either:

    Make the "some logic" listed above know about each and every type and implement them all in one huge reset component

    Work through an interface to kludge the functionality the last option gives you

    Use inheritance with the Reset function overridden.
     
  50. WallaceT_MFM

    WallaceT_MFM

    Joined:
    Sep 25, 2017
    Posts:
    394
    The inheritance method has the same problem. Why do you keep saying interfaces are "kludges"? Both Java and C# use them in their standard libraries in the same way Unity does. You keep getting "emotional" responses from people because you keep using inflammatory language.
     
    Lurking-Ninja likes this.