PDA

View Full Version : Joe wants to know the world and the world about Joe



Traveler
10-08-2005, 07:56 AM
I'm having some problems with my objects. Simply put I need my objects to know of eachother.

Suppose have the following code. (/////////// marks a new unit)


unit windowUnit;

interface

uses worldUnit, (someMoreUnits)

type TGameWindow = class(TObject)
private
(..)
public
(..)
world : TWorld;
end;

var
Window : TGameWindow;

///////////////////
unit worldUnit;

interface

uses spriteUnit, (someMoreUnits);


type TWorld = class(TObject)
private
(..)
public
Joe : TSprite;
height : word;
width : word;
(...)
end;

/////////////////////////
unit spriteUnit;

interface

uses (someMoreUnits)

TDirection = (dLeft, dRight, dUp, dDown, dNone);

type TSprite = class(Tobject)
private
x : single;
y : single;
direction : TDirection;
(etc)
public
(etc)
procedure move();
end;

(etc)

procedure TSprite.move();
begin
if x < 0 then direction := dRight
else if x > window.world.width then direction := dLeft;

if y < 0 then direction := dUp
else if y > window.world.height then direction := dDown;
end;


Ok, so I have a world that gets created in a window and in the world we have Joe. Suppose the world gives the order to move Joe from point A to B, then the move procedure of the sprite would be called.

Here's where the problem arises. Joe has to know what the limits are of the world, otherwise he'd fall off. At some point he'll also want to know where he can walk and where he can't. Point is, currently Joe doesn't know of the world. Variable Window isn't known to him.

How can I solve this? I've tried several things, but most of the time I got problems with circular referencing my units :?

Thanks.

cairnswm
10-08-2005, 08:06 AM
I asked this before as well.

The best way I have found is.

Unit 1
BaseDefinition of a World

Unit 2
Definition of the World

Unit 3
Uses Unit 1
Declares Sprite
Has property of Type BaseWorld.

Main game - creates World, creates Sprite and passes World to Sprite.

I'll try put together a small example later.

Traveler
10-08-2005, 08:15 AM
I'll try put together a small example later.

Thanks, that would be great!

{MSX}
10-08-2005, 08:30 AM
How can I solve this? I've tried several things, but most of the time I got problems with circular referencing my units :?


In my opinion this is one of the worst problems of ObjectPascal. You can't have classes referencing each other from different units. This badly limits how you can structure a program.

One solution i've used is declaring an abstract supertype for each class. I put all this abstract declarations on a single unit (kind of an interface to the game logic), and then i make different units for each class that derive and implement the single abstract classes.



unit main;

TWorld = abstract class
items&#58;list of TItem
....
end
TItem = abstract class
world&#58; TWorld
...
end;

Then:


unit worldunit
uses main;
TWorldImpl = class &#40;TWorld&#41;
...
end;
//////////////////
unit itemunit
uses main;
TItemImpl = class &#40;TItem&#41;
...
end;


This works and also helps keep things decoupled, but is a little verbose.. So sometimes i just end up having all in the same unit.. So much for modularization!

Sly
10-08-2005, 08:57 AM
Ok, so I have a world that gets created in a window and in the world we have Joe. Suppose the world gives the order to move Joe from point A to B, then the move procedure of the sprite would be called.

Here's where the problem arises. Joe has to know what the limits are of the world, otherwise he'd fall off. At some point he'll also want to know where he can walk and where he can't. Point is, currently Joe doesn't know of the world. Variable Window isn't known to him.

How can I solve this? I've tried several things, but most of the time I got problems with circular referencing my units :?

Thanks.

In this case, it would be solved by adding

uses worldunit, windowunit;

immediately after the implementation keyword in spriteunit. You are getting circular references because you are adding to the uses clause in the interface section when actually half of the units could be specified in the uses clause in the implementation section.

The uses clause in the interface section should only include units that are required for type, variable and procedure/function declarations that appear in the interface section. All other units that are only required in the implementation go in the uses clause of the implementation section.

If you have the case where two classes reference each other, eg.
type
TClass1 = class
FClass2: TClass2;
end;

TClass2 = class
FClass1: TClass1;
end;
then you have no choice but to place both classes in the same unit with a forward declaration for TClass2 prior to the declaration of TClass1.

JSoftware
10-08-2005, 11:28 AM
just have the sprite to have a pointer reference to the window and then typecast it

You can then have a line just under implementation which says:

uses windowunit;

this way you avoid abstracted classes and cirkular references

Traveler
10-08-2005, 03:47 PM
Thanks for all the replies.
I can't believe the answer in this case was so simple!

cairnswm
11-08-2005, 07:19 AM
MSX's reply is exactly what I was going to suggest.

I dont like the pointer idea as it make the code more difficult to read and maintain. (You may as well use C++ then) :)

If you one class has a single global reference its also possible to refer to it by variable instead of a property in the child class (usefull for Screen manipulation type stuff).

Traveler
11-08-2005, 07:30 AM
Thanks! I'll have a look at that solution as well.

Chebmaster
23-05-2006, 08:36 AM
Look at the sources of the FreePascal RTL. The actual units there are split to multitudes of .inc files.

I often use the strategies borrowed from C. The class declaration in one .inc file, the class implementation in the other .inc file, and a lot of these {$include ...}d into the same unit. With Pascal's famous compilation speed this strategy works just finely.

WILL
24-05-2006, 10:15 AM
Oh man, if I'm right you guys are REALLY gonna hate me. :P

TJoe = class;

TWorld = class()
procedure Move(SomeDude: TJoe);
end;

TJoe = class(TObject)
{blah blah blah}
function DoWhatYourTold(World: TWorld);
end;


I think this is basically what you're looking for. If you're using Laz or FPC then you may have to check/add 'Delphi' compatability, but it work just fine for my own purposes in the past.

However your problem shouldn't really require this kind of reference I don't think. It's really a simple matter of either having TWorld pass then end of the world to Joe aswell or if you want to flip the order that you creat your objects, just have the world reference TJoe and pass it to TWorld as a 'var'-ed parameter.

Then again not understanding your project fully there may be other factors that I'm unaware of. ;)

Traveler
24-05-2006, 11:06 AM
Ugh, is it really that simple? :?
I could have thought about that solution myself.

The origional problem (dated aug 05) was solved using sly's solution , but I'll keep this one in mind for future issues.

WILL
24-05-2006, 11:51 AM
Oh wow, I didn't even notice how old this thread was. :lol:

Yeah, nothing to be embarassed about though. Sometimes we just overthink a problem or don't have the right angle in our minds. I have gone through whole stints like that where all I had to do was A, B and C and I ended up doing Q, L, W, X and P instead. :P

cairnswm
24-05-2006, 12:48 PM
That solution only works if they are in the same unit - the problem was based around doing it accross units rather than in a single unit.