1. Hello Guest! It's with a heavy heart that we must announce the removal of the Legacy GMC Archive. If you wish to save anything from it, now's the time! Please see this topic for more information.
    Dismiss Notice

GML Parenting vs Composition... (When to use what - Examples)

Discussion in 'Programming' started by DarklinkFighter, Apr 9, 2018.

  1. DarklinkFighter

    DarklinkFighter Member

    Joined:
    Mar 25, 2018
    Posts:
    26
    Hi,

    as a C++ / C# / Java Programmer I struggle a bit now when I want to refactor some stuff from my first Test Project with GMS2...
    I tend to break my head when it's better to use composition and when it's better to use parenting in Game Maker...

    In other languages I can just implement multiple interfaces for different Game Objects but with Game Maker I am scared to decide what code to put where now...
    Because if I use parenting very heavily but all of the sudden need some different logic for some childs... I can only turn of the complete inherited logic or run it... And with that there could be multiple overrrides of variables which are maybe already handled with instance_creations in parents... or some order issues...

    My current problem...
    • OBJECTS
      • obj_player -- Uses hurtable_entity & movement_entity component, has lifepoints, has own loot drop logic, uses own state machine
      • obj_enemy -- hurtable_entity & movement_entity, has lifepoint, has own loot drop logic
        • obj_bat -- uses own state machine
        • obj_skeleton -- uses own state machine
      • obj_destroyable -- Uses hurtable_entity component, has no lifepoints, blocks movements, has a own multi loot drop logic
        • obj_grass -- does not block movements
        • obj_bush
        • obj_tree -- But this one turns off the destroyable...
      • obj_hitbox -- all objects which use hurtable_entity use this with a collision event
        • obj_projectile -- movement_entity
          • obj_arrow
    • COMPONENT SCRIPTS (initialises or use Variables and control behaviour relative to that)
      • movement_entity -- speed, direction, friction, bounces
      • hurtable_entity -- for example isInvincible, invincible_time, canHitBy
    It's pretty hard to explain but I just tried to give a realy simple overview...
    For example I think of just making player, destructable and enemy a child of a new object like obj_game_object but I am scared if some childs need other behaviour later that I break everything for them because event_inherited is not called anymore :/

    Do you guys have some examples, what good fits are for parenting and what are not?
     
    Antera likes this.
  2. drowned

    drowned Member

    Joined:
    Mar 21, 2017
    Posts:
    351
    My programming background is pretty similar to yours and I just got back into game maker relatively recently... I have been struggling with this exact same issue from the start. I would kill a man for interfaces in GMS2; I'm interested to hear how others deal with this.
     
    Antera and breakmt like this.
  3. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,688
    Parents in GM are generally for "A is a B" relationships, which you are already doing on the most part. For the obj_destroyable lineage where you need to turn off behaviours for certain children, use variables to indicate whether those behaviours, then toggle them in the children's Create event.

    And when you do have time, read the article The Perils of JavaSchools. Being a good programmer doesn't mean being a slave to C++, C# or Java.
     
  4. drowned

    drowned Member

    Joined:
    Mar 21, 2017
    Posts:
    351
    I've done this quite a bit and frankly I think it sucks.
    I feel as if this is opposite to the point. That article is about going from "difficult" languages (C, Haskell) to "easy" ones (Java). I don't think I would ever say that GML is closer to C than to Java.
     
  5. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,688
    It's not opposite to the point. The real point of the article isn't about going from supposedly "difficult" languages to supposedly "easy" languages, but the ability to adapt between languages/paradigms and not freeze in face of petty differences.

    Personally I have not found C or Haskell to be that different in difficulty compared to Java. More prone to error in certain areas? Perhaps. Less suited for larger projects? Debatable. But it's not that much more difficult to learn, it's just different.

    I have done coursework on C, Scheme, Haskell and MIPS assembly as an undergraduate, so don't tell me I don't know what it's like. I do, and I did fine. But like the author of that article, I also witnessed over half the class floundering. They just can't function in lower-level logic, without their beloved classes, interfaces, methods and the book titled Object-Oriented Design Patterns that is basically their bible. They aren't masters of OOP, they're slaves to OOP.

    When someone is exclusively exposed to C++, C# or Java and its brand of OOP being taught as the "one right way to code", an otherwise simple new language can become a major hurdle just for being different. It's a case of "every problem is a nail for someone whose only tool is a hammer", which is clearly evident in the opening post. He basically freaked out at not being able to apply OOP design patterns verbatim and use the interface keyword, instead of modifying the architecture to suit what is basically a data-oriented environment. With its heavy emphasis on primitive types and pointer-like handles, GML definitely should be treated more like C than C++, C# or Java.
     
  6. drowned

    drowned Member

    Joined:
    Mar 21, 2017
    Posts:
    351
    I would say that C is objectively much more difficult to learn than Java, and I don't think I'd entertain an argument to the contrary. But anyway that's not the issue.

    I think it is precisely the point (difficult to easy), and you seem to be agreeing:
    Isn't "they just can't function in lower-level logic" essentially the same as "going from an easy language to a difficult one"?

    I don't agree with this ad hominem nonsense at all. He didn't "freak out" and we're clearly and obviously capable of writing GML code. The point (in my mind at least) is that it feels archaic, and we want to know how others in similar situations have dealt with that aspect of it. Your reply of "too bad just deal with it" is unhelpful at best and a stupid waste of time at worst. Why bother?
     
  7. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    @DarklinkFighter - There are tools you can use to solve this issue:
    Code:
    event_perform_object( object_index, event_type, event_number );
    event_perform( event_type, event_number );
    script_execute( script_index, arguments... );
    
    While I think once you get into the position of multiple inheritance you start having a scope issue, that is to say what behavior should be inherited versus simply what behavior should be available, it's fair to say exceptions exist everywhere. In this case you can use event_perform_object to a) 'jump' over an inherited event to one further up the chain, or b) insert behavior from another object that is relevant. I have a similar hierarchy to yours, the difference is in cases where behavior isn't clearly in one path or another I simply add a variable and then push the behavior there, or leave that link in the relevant code which is unique to the object. For example, collisions. All the obj_entities in my game have the potential for collisions, but the collisions are not necessarily the same between all of them. Rather than making that code a function of the obj_entity, I push it off into a script and then simply assign that script to the object. obj_entity will run that code as part of inheritance, but what code it runs is object, or even instance, dependent. On the other side of the fence, some enemies have similar behavior, but not all enemies share it. While I could just make ANOTHER inheritence and add it there, and blah blah blah, instead I can just insert it as needed into the state from a common parent.

    I'm sure you'll find plenty of creative uses, but I believe these will help you find a solution. In many ways it's no different than C++, where you might have done:
    Code:
    class Actor {
      private:
        InputComponent input_;
        PhysicsComponent physics_;
        GraphicsComponent graphics_;
    
    };
    
    In GML you can:
    Code:
    event_perform_object( obj_input, ev_step, ev_step_normal );
    event_perform_object( obj_physics, ev_step, ev_step_normal );
    event_perform_object( obj_graphics, ev_draw, ev_draw_normal );
    
    Just some food for thought. If you get particularly crazy, you can think of objects as a static class, and an instance as... well, and instanced one. If you think of objects like C++ classes with predefined methods, you can pretty easily find ways to work with and around them.
     
    Last edited: Apr 9, 2018
    DarklinkFighter likes this.
  8. drowned

    drowned Member

    Joined:
    Mar 21, 2017
    Posts:
    351
    @Hyomoto this is pretty interesting. Mind posting a concrete example?
     
  9. Neptune

    Neptune Member

    Joined:
    Jun 21, 2016
    Posts:
    1,042
    I'm not going into detail about it all, but my parenting system is working out good so far (a bit over a year into a project).
    Some parents:
    o_ui_parent
    o_mirror_parent
    o_interact_parent
    o_enemy_parent
    o_npc_parent
    o_monster_parent
    o_door_parent
    o_action_parent
    etc etc

    This works well for things like enemies, because they inherit the core code, and then you can have each enemy's specifics inside their respective objects.
    Hope that helps some...

    @drowned Only place I've used event_perform, is for drawing (render order and reflective mirrors)

    Event Perform use:
    if !global.render {exit;}
    if instance_exists(o_render_parent)
    {
    checkA += 1;
    if checkA > 5 //CHECKS FOR NEW ADDITIONS...
    {
    checkA = 0;
    if !ds_exists(global.DS_render,ds_type_grid)
    {
    global.DS_render = ds_grid_create(2,1);
    }
    var ds_size = ds_grid_height(global.DS_render);
    ds_grid_clear(global.DS_render,noone);


    var count = 0;
    with(o_render_parent)
    {
    global.DS_render[# 0,count] = id;
    global.DS_render[# 1,count] = round(y);
    count += 1;
    if count >= (ds_size - 1) {ds_grid_resize(global.DS_render,2,ds_grid_height(global.DS_render)+1);}
    }
    }
    ds_grid_sort(global.DS_render,1,true);

    var count = 0;
    repeat(ds_grid_height(global.DS_render))
    {
    var inst = ds_grid_get(global.DS_render,0,count);
    if inst != noone
    {
    with(inst)
    {
    render = true;
    event_perform(ev_draw,0);
    }
    }
    count += 1;
    }

    }
    else
    {
    if ds_exists(global.DS_render,ds_type_grid)
    {
    ds_grid_destroy(global.DS_render);
    }
    }
     
    Last edited: Apr 9, 2018
  10. FrostyCat

    FrostyCat Member

    Joined:
    Jun 26, 2016
    Posts:
    4,688
    Here's a simple example.

    obj_destructible Create:
    Code:
    max_hp = hp;
    
    obj_destructible Step:
    Code:
    if (hp <= 0) instance_destroy();
    if (hp > max_hp) hp = max_hp;
    
    obj_destructible Draw:
    Code:
    draw_healthbar(bbox_left, bbox_top-32, bbox_right, bbox_top-16, hp/max_hp*100, c_black, c_red, c_lime, 0, true, true);
    
    acts_as(object)
    Code:
    gml_pragma("forceinline");
    event_object_perform(argument0, event_type, event_number);
    
    obj_implementor Create:
    Code:
    hp = 50;
    hat = choose(spr_cowboy_hat, spr_bike_helmet, spr_baseball_cap);
    acts_as(obj_destructible);
    obj_implementor Step:
    Code:
    acts_as(obj_destructible);
    obj_implementor Draw:
    Code:
    draw_self();
    draw_sprite(hat, 0, x, y);
    acts_as(obj_destructible);
     
  11. Neptune

    Neptune Member

    Joined:
    Jun 21, 2016
    Posts:
    1,042
    The crap is that code... "here is a simple example" and you got stuff like 'gml_pragma' in there for... performance boost?
     
  12. drowned

    drowned Member

    Joined:
    Mar 21, 2017
    Posts:
    351
    Maybe a bit oversimplified. I was imagining something almost like a state machine replacement.
     
  13. DarklinkFighter

    DarklinkFighter Member

    Joined:
    Mar 25, 2018
    Posts:
    26
    @Hyomoto
    Thanks Hyomoto!
    Did not yet know these functions. This realy sounds like it could help me a lot :)

    @FrostyCat
    Yeah this is what I currently do for the destroyables :)
    I think the issues I have come from the fact that I can not make my own events (methods) which I could use during parent calls like an abstract class (except 16 custom ones which I save for more compex stuff...)
    With that I could do things like:
    Code:
    // Parent - Create
    initilize_movement_entity() // can be overridden in child
    ...
    ...
    finish_movement_entity() // Parent finishes stuff initialized from child... (for example spawn instances if needed so that I do not have to use it with if checks in step begin all the time)
    
    But I think Hyomoto's aproach can kinda help me with that issue a bit.
    Thanks a lot for all the responses :)
     
    Last edited: Apr 10, 2018
  14. DarklinkFighter

    DarklinkFighter Member

    Joined:
    Mar 25, 2018
    Posts:
    26
    Are there any performance relevant reasons to not use the event_perform[_object] in some cases?
    I finaly started my refactoring and so far it looks very promising (only started testing this way out for the obj_destroyables)

    What I did is renaming the obj_destroyable to obj_prop and created a new Object Grouping called "Components"
    And even if my refactoring is still in progress the overview already increased drasticly so... so far I like this way... It's realy a bit like working with interfaces with the exception that you have to check if a instance uses the component...
    So for now (work in progress) it's like:

    OBJECTS:
    • Props (Group)
      • obj_prop -- Is (uses) a obj_destroyable, obj_hurtable, obj_blockable and obj_lootable
        • obj_grass -- one liner switches the destroyable off in create event
        • obj_bush
        • obj_tree -- one liner switches the hurtable off in create create event
    • Components (Group) -- These are the Components used by event_perform_object(...) and handle the important game mechanics in chunks... So no parenting...
      • obj_destroyable -- Only does destroy a event and plays a destruction effect
      • obj_moveable -- Handles movement with collision detection
      • obj_hurtable -- Manages Life points and triggers obj_destroyable if instance is a destroyable and obj_lootable if instance is a obj_lootable
      • obj_blockable -- Object will be solid for collision detection
      • obj_lootable -- Handles chest (transfer loot) and pickup (drop loot) logic

    So every game object in my game knows which components they use but they may call the perform functions without checks because the if logic is encapsulated within the components.
    So for example for the tree a hurtbox collision will appear and the hurtable component will be triggered but the collision code in the component checks if(isHurtable) and does not do something becaus the tree create event deactivated this flag.
    Also if I now need to change some logic for different game objects I can use parenting with the components like obj_followable <--- obj_moveable and achieve kind of abstract class method overrides with that which I realy like.
    I achieve this with a own variable every component has (for example 'moveableObject' get declared in obj_moveable and obj_followable can override it with its own obect type)
    The gameobject then only uses at it's lowest parenting point event_perform_object(moveableObject, ev_step, ev_step_normal)...

    So so far I love the idea you had @Hyomoto :)
    The only thing left is looking for the performance cost of this and how I could lower the costs if it will come to problems later maybe
     
  15. Hyomoto

    Hyomoto Member

    Joined:
    Jul 7, 2016
    Posts:
    1,078
    Naturally everything has a cost, and if you are really worried about it you can always check the Profiler. Over hundreds of instances it will definitely add up, but it comes down to what code you run is likely more expensive than the call itself. However, if performance savings are vital for one reason or another, @FrostyCat"s implementation is to inline those calls, thus avoiding event_perform_object altogether. GML_pragma and inline are much higher level usage, but when using calls as a component system like you want to do, it is definitely a cool idea. You'll use more RAM but depending on the number of instances, you'll definitely earn some performance back.

    There is something else, you could skip the checks by pushing the component behavior to the relevant object. Since that maintainability is simple, pushing the methods into the classes themselves, rather than the parent, avoids having to toggle anything. You can just use the parent to define behavior they all share, such as destroy able and block able, add hurt able and loot able as behaviors specific to branch that needs them.

    Since variables are cheap, it's easy to still provide the variable framework in the parent, but it you are performance obsessed there's something to be gained there. You can use the basic inheritance to extend each objects personal behavior. Of course, again, since we've reduce all component calls to a single line it's not unreasonable to just put the calls in the objects themselves, skip the inheritance call and use the parent as a category and variable definition.
     
    Last edited: Apr 11, 2018
    atmobeat and DarklinkFighter like this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice