How to transfer/manage objects from one class to another

Started by
8 comments, last by haegarr 9 years, 6 months ago
I'm making a game using libgdx. I'm kinda new to making games but I have experience in programming (production machines mainly).

In my game my player walks around in a 2d world with wall objects and machine objects (that's all for now, I'm taking things step by step). The object is for the player to move products from one machine to the other.

All the wall objects, machine objects and player are all in one linked list. Each object has a render and an update method. So in my gameloop I can loop through all my objects and update and render them.

Now I'm kinda stuck with what to do with the products!
The player has a private product and the machine has a private linked list of products. When the player notices he enters the drop-off area of a machine, the product has to go from the player to the machine, and vise versa when the player enters pick-up zone.
An extra problem:A machine can refuse a product when it's full, or when the product not the right type for the machine.

Detecting the player is in the zones is no problem. It's the transfer that bugs me.
I want to do the transfer in the player class. Because the player noticed it was in one of the zones of a machine.

How I do the transfer from player to machine now:
If my player detects it's in a machinesobjects drop-off zone it calls the add method of the machine object:
succeeded=machineobject.AddProduct(playerProduct);
If (succeeded) playerProduct = null;

If the machine accepts the product, and it can be deleted from the players product.

This works, but it doesn't seem like the best practice...

How I do the transfer from machine to player now:
If my player detects it's in a machinesobjects pick-up zone it calls the get method of the machine object:

playerProduct=machineobject.GetProduct();

This look kinda ok. But the machine object method does look kinda messy:
(for example sake I used one object instead of the linked list the machine normally has)

Public Product GetProduct() {
If (product!= null) {
Product tempProduct = product;
product = Null;
Return tempProduct;
}
Else{
Return Null;
}
}

Again
if I recall correctly, this works, but it doesn't seem like the best practice...


So my actual question: what is a good way to transfer objects from one class to another, and I what is the best way to manage these transferable objects in a game (eg put them in a separate list, put them in the list with all the other objects...)

I'm sorry if my text looks kinda messy or my fonts aren't all correct, but I'm on a vacation with my gf and all I have to my disposal is my cell phone :-) This just kept bugging me so I couldn't wait till I got home :-)
Thanks in advance for any replies!
Advertisement

Several possibilities exist, and there is perhaps no best way. However, these are my thoughts:

Four objects are involved when transferring a product:

1.) the player avatar

2.) the machine

3.) the product

4.) the zone

A machine is a container of products with a provider and consumer behavior. The player avatar is also a container of products with a provider and consumer behavior. A zone is a collider volume with a belonging collision handler, depending on its purpose either an onEnteredDropZone or an onEnteredPickZone. The player avatar is the collidee for a zone, and a machine is fixed attached to a zone either as provider (PickZone) or as consumer (DropZone).

Now, if the transfer happens automatically simply because of the collision, the zone is well suited to perform the transfer:


void DropZone.onEntered( Avatar player ) {
    if ( player.providesProduct() && this.machine.acceptsProduct() ) {
        this.machine.consume( player.releaseProduct() );
    }
}
void PickZone.onEntered( Avatar player ) {
    if ( this.machine.providesProduct() && player.acceptsProduct() ) {
        player.consume( this.machine.releaseProduct() );
    }
}

Of course, it looks more natural to put the behavior into the player avatar class, but notice that above solution is fine because

a.) it does not introduce machine stuff into the player avatar class (or vice versa)

b.) allows to come up with more kinds of zones perhaps without changes to the interface of the player and/or machine classes

Things get slightly more complicated if the player actively triggers the transfer by some kind of input. In this case the zone has to consider whether the player wants to transfer the product.


void DropZone.onEntered( Avatar player ) {
    if (this.input.playerAction()==Action.TransferDrop) {
        ...
    }
}

However, this seems not sufficient because it requires the input situation to be valid at the moment when the zone is entered. Here comes into play that a game usually runs its loop not based on events but continuously. I.e. the update() method of the zone calls onEntered as many times as it is itself called when the collision exists.

As said, the above is just one solution. I wanted to show another way than the one you already be aware of. Your current way isn't essentially wrong either. However, you should notice that letting the objects itself handle things (and I also mean rendering here) seems natural but isn't necessarily the best way. This is because it usually enforces a tighter coupling of classes (e.g. in your case the Player class needs to know Product, Machine, and perhaps Zone).

Gratitude for your quick response!

I see your point about not doing the collisions in the player. I think I'll change it that way.

It also didn't occur to me before to see the drop-off/pick-up area's as separate objects. It seems like a good idea!
One thing though: should the machine and the area-objects not stay connected?
For instance let's say one of the machines is mobile and it moved around. The areas have to move along, or the player sells a machine, the areas have to be destroyed. It seems like if I write all that outside the machine class it's gonna get messy!

About the products: Is it better to let them only exist in the player/machine, or should I also put them in a collection of some kind. I'm not sure what is best. I'm leaning more to the first option, because it is less overhead and simpler, but then what to do when the player drops it's product, it should still exist on it's own.

Thanks again for your answer! It has already been great advice. When I get home, I'll implement the changes and check things out right away!


One thing though: should the machine and the area-objects not stay connected?
For instance let's say one of the machines is mobile and it moved around. The areas have to move along, or the player sells a machine, the areas have to be destroyed. It seems like if I write all that outside the machine class it's gonna get messy!

What do you mean with "connected"? I think it is better when zones and machines are distinct classes. This allows a different hierarchy for zones and for machines. They can logically be connected (see the members DropZone::machine and PickZone::machine in the code examples above), and they can be connected spatially if wanted (by using a 2D or 3D parenting placement mechanism).

Selling a machine is a procedure outside of the scope of the machine anyway. Relocating a machine it is, too. I don't see a problem ATM. However, I also don't have an overview belonging to your game design, so may be I'm too rigorous.


About the products: Is it better to let them only exist in the player/machine, or should I also put them in a collection of some kind. I'm not sure what is best. I'm leaning more to the first option, because it is less overhead and simpler, but then what to do when the player drops it's product, it should still exist on it's own.

Player avatar and machines are containers for products. Internally they use a collection if necessary. A more global collection of all products may be useful for the game mechanics, but from what you've written so far I don't see a reason for that.

If a player drops a product it is no longer contained but an objects in the world, e.g. like a machine or something. It may become a bounding volume and collision handler, whatever fits the game design.

Thanks again for your quick response. You're great!

What do you mean with "connected"? I think it is better when zones and machines are distinct classes. This allows a different hierarchy for zones and for machines. They can logically be connected (see the members DropZone::machine and PickZone::machine in the code examples above), and they can be connected spatially if wanted (by using a 2D or 3D parenting placement mechanism).

I would have two variables in my machine class:
Public Zone pickZone;
Public Zone dropZone;
Which are references to the zone objects. They are initialized when a machine is created.

Let's say the selling of a machine is done outside the machine class, how would you find out which zones to destroy together with the machine if you don't have these two variables?

I really like the idea of keeping everything as separate as possible, but atm I don't see how to know what belongs to what, how do you cross-reference everything together?


they can be connected spatially if wanted (by using a 2D or 3D parenting placement mechanism).

I'm not familiar with this. I've been trying to Google it, but for now only results about divorcing parents :-) I keep looking! (If anyone has good sources about that you may always let me know) .

I'm sorry if all this is obvious or basic stuff that I should know, I'm just learning atm. I try to soak up as much as I can :-)


I would have two variables in my machine class:
Public Zone pickZone;
Public Zone dropZone;
Which are references to the zone objects. They are initialized when a machine is created.

This is a valid way. It does not differ significantly from using the reference Zone.machine as mentioned already. Merely the question whether the members should be public may arise.


Let's say the selling of a machine is done outside the machine class, how would you find out which zones to destroy together with the machine if you don't have these two variables?

Well, it isn't so that no possibilities exist:

a.) Indirect coupling: Classes / objects in programming can represent data about anything, e.g. also a concept or an idea or, in this case, a relation. For example, a class ProductionUnit can be used to couple a Machine and two Zone objects together.

b.) Reverse coupling: A simple iteration over all Zone objects can be done to look up for all zones docked to the machine in question. (This option becomes more and more unattractive the more zones there are and the more sales are done per time unit, of course. If you think "what the hell should this be good for?": Relational databases use this way to express one-to-many relations. To overcome the efficiency problem, the related rows are indexed. In Java something similar could be yielded in.)

However, I don't say that you should use any of the above possibilities. Using direct coupling is fine in principal. The critique I've made was with respect to the interaction between objects. E.g.: How many classes need to be adapted if you want to introduce a new kind of zone? Or does a machine need to know what a sale is? Just such things, you know.


I'm not familiar with this. I've been trying to Google it, but for now only results about divorcing parents :-) I keep looking! (If anyone has good sources about that you may always let me know) .

What I mean is a relation that is commonly used in rendering graphics: Placing a graphical representation (often called the "child") not directly into the world but relative to a reference object (often called the "parent"). For the actual rendering the world placement of the child is then computed by concatenation of its local placement with the world placement of the parent.

While "the pickZone A is docked to the machine B", perhaps expressed by a reference like

A.machine = B;

denotes a logical relation, a parenting in the above sense means a spatial relation in that it defines e.g. on what side and how narrow the graphical representation of the zone is attached to the graphical representation of the machine. So, say when you rotate the machine then the zone will rotate with it.

Sorry for my late respons (being on holidays prevents me from constant phone access to write an answer :-P)

What I mean is a relation that is commonly used in rendering graphics: Placing a graphical representation (often called the "child") not directly into the world but relative to a reference object (often called the "parent"). For the actual rendering the world placement of the child is then computed by concatenation of its local placement with the world placement of the parent.

I think I understand what you mean, but I don't really see how to implement it.
Like I understand it: The machine is the parent and the zone is the child. So the machine does the rendering of the zone (for example sake the zone is a visible component).
But as you said earlier, an object doesn't do it's own rendering, so does the machine add it's sprite together with the zones' sprite and give it to the renderer as a 'spriteset' or so?


As we are talking about doing the rendering outside the object, how does this work? Does the renderer iterate over all the objects, take their sprites and draw them on the coordinates+rotation of the object?

As ever: We're discussing possibilities here. You need to decide what is helpful for your game and what can be ignored for now...


Like I understand it: The machine is the parent and the zone is the child. So the machine does the rendering of the zone (for example sake the zone is a visible component).
But as you said earlier, an object doesn't do it's own rendering, so does the machine add it's sprite together with the zones' sprite and give it to the renderer as a 'spriteset' or so?

The mechanism of parenting does nothing more than expressing a spatial placement relative to another one. This is outside of rendering, collision detection, or anything similar. Here is why and how:

In 2D a placement consists of a 2D position and an orientation angle. Those parameters can be used to compute a 3x3 homogenous matrix expressing the transform of the sprite. The transform defines what to do to come from the local space (also called model space) to the parent space. I'm used to define that every model / sprite in the scene is placed globally initially, so that the transform matrix inherent to the model / sprite is a world transform matrix. In other words, each model / sprite has a world placement.

In the case that a model / sprite is needed to be parented, I'm used to add an explicit tool for this: The Parenting component. The attachment of a Parenting makes the model / sprite the child. The Parenting includes a reference to another model / sprite, so that those other model / sprite becomes the parent of the child. The Parenting further includes a local Placement, i.e. a Placement that expresses the spatial relation to the referenced parent model / sprite.

In fact the Parent component introduces a constraint on the world Placement of the child model / sprite in that its world Placement is computed as said concatenation of the local Placement with the world Placement of the parent. This is a fancy description essentially of a matrix product.

So ... what you can see here is that the above solution does an external coupling (external to the models / sprites, because based on the introduced Parenting class) of models / sprites on a spatial level. The coupling updates the world transform of what is made the child model / sprite. This means that if all is up-to-date, every model / sprite has a valid world transform in its Placement, regardless whether it is a static one or computed by a Parenting (or computed by another mechanism; I've something like a dozen or so, including animation, of course).

Now, when the collision detection or rendering comes to work, there is a couple of models / sprites each with a valid Placement in the world. That is all what the respective sub-system needs to know. "Parenting? What's that?" is said by the renderer.


As we are talking about doing the rendering outside the object, how does this work? Does the renderer iterate over all the objects, take their sprites and draw them on the coordinates+rotation of the object?

Yes. The renderer iterates the scene. Maybe there is a subset of scene objects that denotes all "drawables" or so. However, the renderer finds the objects, and uses the sprite as visual representation and the (as we know being valid) world placement's transform matrix. It usually does frustum / viewport culling with the help of some bounding box first.

Back home!

In 2D a placement consists of a 2D position and an orientation angle. Those parameters can be used to compute a 3x3 homogenous matrix expressing the transform of the sprite. The transform defines what to do to come from the local space (also called model space) to the parent space. I'm used to define that every model / sprite in the scene is placed globally initially, so that the transform matrix inherent to the model / sprite is a world transform matrix. In other words, each model / sprite has a world placement.


When i first read "3x3 homogenous matrix" i had no clue of what were talking about. It has been a while since my last math class. So i did some research and now i think i understand what you mean. But i'm not sure how to implement all that.

How i understand it (i'm not saying this is the way it is, this is just what i made of it from the info i read and the info you gave):
In the different objects get an extra parameter Transformation:

private transformation AffineTransform;

This transformation is set to the world:

[ 1 0 0 ]
[ 0 1 0 ]
[ 0 0 1 ]

If i understood correctly: if you put your objects' coordinates x and y through this matrix, they come back the same. So your coordinates are just what they are in the world.

But now if you want your object to follow a parent, you concat the transformation (AffineTransform) of your object with the coordinates of your parent.

As i'm writing this i get more and more confused. :s For instance: Should the coordinates x and y and a (rotation) not all be replaced by a matrix by itself. But then an object has two transformation => one for it's coordinates and one for it's actual placement in the world/parent...


In the case that a model / sprite is needed to be parented, I'm used to add an explicit tool for this: The Parenting component. The attachment of a Parenting makes the model / sprite the child. The Parenting includes a reference to another model / sprite, so that those other model / sprite becomes the parent of the child. The Parenting further includes a local Placement, i.e. a Placement that expresses the spatial relation to the referenced parent model / sprite.

Again, how i understand it:

So you have an extra "coupling" object that has two parameters => the parent parameter and the child parameter. And you continuously concat the transformation of your child with the coordinates of your parent.

Here again i think i don't really understand it all the way, because it would make more sense if the coordinates of the parent would be in a transformation matrix. In that way you can just set the transformation with the coordinate-transformation of the parent and your done.

If the latter is correct, do you only have to call this extra object once at initialization? Or even further: is this object really necessary? Can't i just set the parents transformation at the child's construction?

I'm sorry if my response is rubbish, i'm just kinda confused about it atm!


...

For instance: Should the coordinates x and y and a (rotation) not all be replaced by a matrix by itself. But then an object has two transformation => one for it's coordinates and one for it's actual placement in the world/parent...

The homogenous co-ordinate is an extension, so to say, that allows to express a translation by a matrix multiplication. Without that, a translation would be expressed by an addition.The advantage of having it as multiplication is that you can compute a single matrix for any combination of translations, rotations, and scaling.

What I call a placement is in fact a position and an orientation of an object relative to its super-ordinated object (I'm used to ignore scaling for a placement). A global placement is for an object in the world, and a local placement for a child object relative to its parent object. When you apply such a transform matrix you actually multiply geometry (vertex positions, normals, tangents) with that matrix. It is interpreted as "the vertex position / normal / tangent is given in model local space, but I want it in the global / parent space, hence I multiply with the respective transform matrix". Mathematically it plays no role what position you transform; so instead of using a vertex position you can use the point (0,0,1); remember that (a) we use homogenous co-ordinates, hence the "1", and that we are in model local space. So (0,0,1) is actually the local origin of the model. And when we transform the origin to the global / parent space, we actually have computed the position of the model in the global / parent space. Therefore we have 1 transform for both its geometry and its placement!

An example: The position of the model in the world should be (x,y,1) and its rotation should be the identity:

M := R(0) * T(x,y) = I * T(x,y) = T(x,y)

The identity matrix (those where all elements are 0 but the main diagonal elements are 1) has no effect. Notice that I'm using row vectors here, so that the common order "geomtry is rotated in place, and the rotated geometry is translated" is written from left to right in the formula. Now, using this to transform the position (0,0,1) you get

(0,0,1) * M = (0,0,1) * T(x,y) = (0*1+0*0+1*x,0*0+0*1+1*y,0*0+0*0+1*1) = (x,y,1)

the said model origin in global / parent space. Try it on paper; it works. :)

Wat exactly gets stored with a Placement object is a question of practice. It is convenient to store the affine transform matrix, of course. It may also be okay to store other parameters, e.g. a position and an angle, so that the matrix is computed from those parameters.


So you have an extra "coupling" object that has two parameters => the parent parameter and the child parameter. And you continuously concat the transformation of your child with the coordinates of your parent.
Here again i think i don't really understand it all the way, because it would make more sense if the coordinates of the parent would be in a transformation matrix. In that way you can just set the transformation with the coordinate-transformation of the parent and your done.

All what is said above is only a prelude for parenting. As said, a placement defines a single spatial relation. For parenting, we have a defined spatial relation of the child w.r.t. its parent, say LC, and we want to compute the spatial relation of the child w.r.t. the world (for rendering purposes, for example), say MC. As we know, applying a transform matrix brings us from the local to the parent space. Here we want to go from the modal local to the parent space and further to the world space. So we do this in two steps and get a combined transform matrix

MC := LC * MP

where MP is the transform matrix of the parent.

Doing so means that the Parenting object has the following parameters:

a) A reference to the model's global Placement, perhaps indirectly by a reference to the model itself; within this Placement the matrix MC is stored.

b) A reference to the parent object's global Placement, perhaps indirectly by a reference to the parent itself; within this Placement the matrix MP is stored.

c) The model's local Placement where the matrix LC is stored.

When the Parenting is called to update, it requests MP from the parent's Placement, requests LC from the own parameters, computes MC, and stores the result in the model's global Placement. Voila.


If the latter is correct, do you only have to call this extra object once at initialization? Or even further: is this object really necessary? Can't i just set the parents transformation at the child's construction?

This extra object need to called whenever

a) the parent's global Placement matrix has changed, or

b) he model's local Placement matrix has changed.

c) and you need to access the current global Placement matrix of the child.

It is part of the update mechanism inside a game loop similar to (but not exactly the same as) those of, say, the animation sub-system.

This topic is closed to new replies.

Advertisement