Blitzmax object linking

Started by Ashmoor, April 01, 2018, 03:07:31

Previous topic - Next topic

Ashmoor

How do you link an object to another? For example I have a ship and I want to link a shield to it, the shield should take it's x and y coords from the ship coords, is there an easy way to set the ship as parent? The same thing goes for some gui elements and I want to have the whole dialog box sliding in/out and the buttons should take coords from the dialog object.

Can I  have a field in the shield type called

Field parentObj:TShip=PlayerShip

and access the PlayerShip properties from there? If I do that is parentObj a reference to PlayerShip or something else?

GW

The shield would be an object that only contains 'shield things'(image, health, color, name, etc)  not ship things.
The shield object would be a field the ship object.


pseudo example

type tShield
  field health%
  field image:tImage
'etc
EndType


Type tShip
  Field name$
  field image:tImage
  field x#,y#
  field shield:tShield
'etc
EndType
   

Ashmoor

Yea, that makes perfect sense if you know that you would have exactly one shield/addon etc.

What about the dialog box that is a generic type and may have a random number of buttons, check boxes, sliders etc? How would you handle that?

Derron

Code (BlitzMax) Select

TGuiobject
Field x:int, y:int
Field parent:TGuiobject

Method GetX()
  if parent then parent.GetX() + parent.GetChildX(self)
  return x
End Method

Method GetChildX:int(child:TGUIObject)
  return someIndividualOffsetCalculation + child.x
End Method

...

(untested and not complete - just to get the idea)

There are plenty of approaches to it:
- move a parent and it moves attached children (so no "field parent:TGUIObject" but "field children:TList")
- if a child can only have one parent, let it ask its parent where it actually is (this is above's sample)
- ...

In all cases you need to avoid manual adjustments like "obj.x = 10", you need to make sure to use "setters" (obj.moveXY(10, 0)) as they then could for example adjust locations of child elements (approach 1). Or you need to use "obj.getX()" when accessing, so elements could ask potential parents about offsets, absolute positions ... (approach 2).

Instead of storing absolute screen coords you do everything "relatively". No parent: relative to screen origin, with parent: relative to parent (like an "offset"). That way you could even animate the button in the dialogue container.



I know my code is a mixture of "10 years ago and now" and contains pretty much flaws but to get the idea:
https://github.com/GWRon/Dig
-> check the base.gfx.gui.***.bmx files


bye
Ron

Ashmoor

@Derron thanks.

I take it that the parent field in your example is a reference to the actual parent object not a duplicate of it, is that correct?


Yellownakji

Must the shield be an individual object?    Why not just make a variable for the ship that is 'global ship_hasshield' and if true, draw the ship with a shield over it or what not.

Then you could do "if ship takes damage, lower shield HP instead" via and if statement.  Then if shield hp is 0,  hasshield = false.

--

A rough example but very straight forward.

Derron

Because there might be multiple variants of a shield.

SpikedShield - makes damages when hit by melee attack
SpiritualShield - defends against any magic attack

... Aside of that you can of course have all of this in your unit/ship-class:
shieldFlags:int =

Const SHIELD_SPIKED:int = 1
Const SHIELD_SPIRITUAL:int = 2
Const SHIELD_UNBREAKABLE:int = 4

shieldFlags = SHIELD_SPIKED + SHIELD_UNBREAKABLE
...



It all depends on how you use your shields. If ships can have shields and buildings too, and ... and the shield might have active functionalities, individual powerups, ... then you should use an own object for it.
Just think of it that way: does the unit need to know about the shields inner or does it just need to know "I have a shield" and "I get attacked, how much damage does the shield block".
Especially with "active elements" I would prefer objects. If it is some kind of simple "struct" (shield_defense=1, shield_durability=10, ...) then you might of course add it directly to the parent. Elements "shared" between parents are also nicer if done as objects. So eg. a shield could be "shared" between ships ("ship A can sacrifice energy via a interwarp tunnel to ship B if ship B needs it"). But such objects would need to know about their parents ... means you could also do it via simple properties of a ship ("shipEnergySharedWithShip:TShip"). As said, if the ship does not need to know if it shares energy with something - or how this sharing is implemented, then you do it as an attached object taking care of all this stuff.

Do not forget that it decreases complexity (code size) of an object if you delegate stuff to attached objects. And if you think of "component based approach" (everything is a component - even behaviour like "Walk", "Attack" - so you could replace "drunken walk" with "normal walk" etc) then a shield is of course a component.


bye
Ron

Yellownakji

I would just do "ship_shieldtype" = 0 or 1... etc

Derron

ok and then? "UpdateShield()" with 100 different if-then-else just because you decided to have 100 different shields?

As said, it depends on how you intend to use stuff. If you just have a "has shield or does not have shield" thingy, then your simple flag will do. But as soon as there is complex interaction between "attack effect" and "shield", you will use objects.


bye
Ron

Ashmoor

The shield is just an example. It could be a ship add-on/bot, that fires extra lasers or collects gold and animates around the ship, fires it's own projectiles, etc. and I doubt it would be better to implement all that with just a variable instead of an object linked to a parent.

As far as I understand it at the moment, the actual way it is implemented (be it a child in a parent list or a child with a parent reference) is case dependent and less important than the object methods which would make interacting with it easier and clearer.

Derron

If the "bot" has only one parent, then just add the bot/shield to its parent via a "list" in the parent.

You could then either have a weak connection (approach A) or a strong connection (approach B).

A)
A weak connection means that it does not directly link to the parent. Why? Your parent has a list of children. Each children stores the parent object reference in "Field parent:TShip". Now you loose your shield. What do you do? You do a "ship.children.remove(shield)". But what happens then?. Parent does no longer know about that shield. So nobody is able to reference the shield (access its fields, methods). So does it get removed from memory? Nope ... as the GC reference counter sees, that the shield still references the parent - which is used by more than just the shield (so count > 1). At the end you produced a memory leak as the shield will not get removed from memory.

So in that case you always need to make sure to remove the parent too (call a "method CleanUp()" on the shield - the method "delete()" is already defined and called when it gets deleted by the GC which is not happening as described).

So either do a

shield.parent = null
ship.children.remove(shield)

or do a similar thing (and maybe even more connection-cuts) in a custom CleanUp() method of the children. You could also store all the shields in a shield collection and do auto-cleanups there (see approach B).



B)
Weak references. We do not have weak pointers in BlitzMax, but you could store a unique ID (GUID - you need to generate it) of the parent as "field parentGUID:string" (or int or ...depends on your GUID approach). Each parent needs to be in a globally available collection (ShipCollection) which has getters like ShipCollection.GetShip(guid:string).

Now each time you need an information or interaction with the ship you can request the current ship within your shield class:

Code (BlitzMax) Select

Method update:int()
  local ship:TShip = ShipCollection.GetShip(parentGUID)
  if not ship then throw "My ship is missing"
 
  ...
End Method



Instead of a "ShipCollection" it could be a "EntityCollection" (TShip extends TEntity) so you can get rid of circular dependencies (tshield-file imports tshipcollection-file which imports tship-file which imports tshield-file).



bye
Ron



Ashmoor

Thanks Derron, things are pretty clear now. I never used the GC so I will have to look into that as well. There is still so much I need to learn.