PDA

View Full Version : [Solved] Overriding a procedure



dazappa
13-09-2009, 04:33 AM
System: Ubuntu 9.04
Compiler/IDE: FPC/Lazarus 9.26-4
Libraries/API: None

Basically, in unit2 I have this object:

Sprite = Object
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
procedure Collide(); // Empty by default. I want to override it.
end;
That has the empty procedure "Collide". Now, my unit1 uses unit2 and I create a sprite object like so:


Player: Sprite;
Player := Sprite.Create;
Player.Collide := PlayerCollide; // Yells at me here


Where PlayerCollide is a procedure in unit1


procedure TForm1.PlayerCollide();
begin
Player.y := 32;
end;


It's a bit messy, and I've never done any overrides before, much less of my own procedures..

[Random]
Quite ambitious for me, and quite poorly coded, but I'm working on a simple 2d, software-rendered game engine. The idea behind what was mentioned above is so the person making their game could override the collision code to handle them how they want: Ex: Delete objects, Teleport places ETC.

paul_nicholls
13-09-2009, 07:02 AM
Hi dazappa :)

hmm...I think the sprite should be of type Class instead of Object?

Sprite = Class
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
procedure Collide(); // Empty by default. I want to override it.
end;

Also if you want to override a method you need to use "virtual" against the method like so:

Sprite = Class
public
...
procedure Collide(); Virtual; // Empty by default. I want to override it.
end;

class overriding Collide method:
MySprite = Class(TSprite)
procedure Collide(); Override; // override empty method
end;

Procedure TMySprite.Collide;
Begin
// do something new here
End;

I hope this helps :)

cheers,
Paul

JSoftware
13-09-2009, 11:31 AM
You could also just make it a procedure variable

Sprite = Object
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
Collide: procedure of object; // Empty by default. I want to override it.
end;


And then do:

Player: Sprite;
Player := Sprite.Create;
Player.Collide := @PlayerCollide; // Yells at me here


You can't do it the other way around unless you hack the vmt. And that's not bound to work the same way on all platforms

dazappa
13-09-2009, 12:14 PM
You could also just make it a procedure variable

Sprite = Object
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
Collide: procedure of object; // Empty by default. I want to override it.
end;


And then do:

Player: Sprite;
Player := Sprite.Create;
Player.Collide := @PlayerCollide; // Yells at me here


You can't do it the other way around unless you hack the vmt. And that's not bound to work the same way on all platforms

This works exactly as I want. How would I define the default procedure for Sprite.Collide though? (Is it possible?) Obviously "procedure Sprite.Collide begin end;" no longer worked ;)

This seems like it could be a solution: http://www.swissdelphicenter.ch/torry/showcode.php?id=799 , but is there a better solution?

AthenaOfDelphi
13-09-2009, 12:33 PM
In terms of object orientated programming, the most widely used way is to do what Paul has suggested. Declare the method in the base class as 'virtual' and then override it in the child classes.

By doing it that way, if your base class has functionality in it that will also be needed for the children, you can use the 'inherited' keyword to call the common functionality.

To use the procedure variable method (which is the standard eventing mechanism), you should really handle the situation where the variable is not initialised, which would result in an additional 'if assigned()' or 'if collide<>nil' style check to avoid attempting to call a routine at address 0x00000000 (which I'm guessing is why your code isn't working using that method). If the routine is being called alot, then those checks create an overhead that can be avoided by using real overrides.

de_jean_7777
13-09-2009, 01:48 PM
You could assign some procedure to the procedure variable in your unit in the
INITIALIZATION part. This way you can assign your procedure variable a default procedure, and you won't have to check if it is nil. This is what I do in my projects, though you do have to be careful that your procedure variables are always initialized if you do not want to do checking.

I usually assign procedure variables some dummy procedures in the INITIALIZATION part. These dummy routines do nothing, but they prevent crashes in case of a call to a nil procedure variable. Then later on I assign them some other procedure.

dazappa
13-09-2009, 03:53 PM
In terms of object orientated programming, the most widely used way is to do what Paul has suggested. Declare the method in the base class as 'virtual' and then override it in the child classes.

By doing it that way, if your base class has functionality in it that will also be needed for the children, you can use the 'inherited' keyword to call the common functionality.

To use the procedure variable method (which is the standard eventing mechanism), you should really handle the situation where the variable is not initialised, which would result in an additional 'if assigned()' or 'if collide<>nil' style check to avoid attempting to call a routine at address 0x00000000 (which I'm guessing is why your code isn't working using that method). If the routine is being called alot, then those checks create an overhead that can be avoided by using real overrides.

Well I have 2 main problems with this.
1. (I believe) I'd have to create a separate class each time I want different collision code
2. The way I'm writing this is engine is so the engine manages all of the objects



You could assign some procedure to the procedure variable in your unit in the
INITIALIZATION part. This way you can assign your procedure variable a default procedure, and you won't have to check if it is nil. This is what I do in my projects, though you do have to be careful that your procedure variables are always initialized if you do not want to do checking.

I usually assign procedure variables some dummy procedures in the INITIALIZATION part. These dummy routines do nothing, but they prevent crashes in case of a call to a nil procedure variable. Then later on I assign them some other procedure.

I never think of the simple things...

Ok, so now for the sake of organization and (somewhat) cleaner code, how can I pass variables into the collide procedure? (Because assigning procedures without using the way Paul showed means the procedure does not have access to the object's variables).

So I was thinking:


Sprite = Object
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
Collide: procedure(index: integer) of object; // Empty by default. I want to override it.
end;

...

Player: Sprite;
Player := Sprite.Create;
Player.Collide := @PlayerCollide; // Yells at me here

...

procedure PlayerCollide(index: Integer);
begin

end;

But it yells at the same line as before when I try that.

(PS: Might want to post somewhere how to use code highlighting on these forums)

AthenaOfDelphi
13-09-2009, 04:29 PM
To use code highlight, you enclose your code in PASCAL tags... like this, without the spaces (depending on which theme you are using, I believe Traveler has integrated the code highlighting into the message editor).

[ pascal ]
Your code here
[ /pascal ]

With regards to using the more correct OOP approach to overrides.... if the user of your engine has the code, they can override it (or modify it to suit their needs).

With regards to the compiler crying at the @ symbol... if FreePascal's eventing mechanisms are the same as those in Delphi, then you shouldn't need the @ but, the method you try and call must be a method of a class because you've defined the holding variable as 'procedure of object'.

pjpdev
13-09-2009, 06:24 PM
The @ is used for a pointer reference. Looking at your code, Player.Collide is not a pointer, so you should remove the @ sign.


Player.Collide := PlayerCollide; //@ is removed


I rarely make use of pointers, so I hope this helps.

dazappa
13-09-2009, 07:01 PM
After some toying, I've got it working perfectly as I imagined


Sprite = object
public
x,y,xspeed,yspeed,gravity,lastx,lasty,collision: integer;
solid: boolean;
img: TBitmap;
procedure Create();
procedure Destroy();
Collide: procedure of object;
Draw: procedure of object;
procedure DefaultDraw();
procedure DefaultCollision();
procedure LoadFromFile(F: String);
end;

...

procedure Sprite.Create();
begin
img := TBitmap.Create;
lastx := x;
lasty := y;
solid := false;
Collide := DefaultCollision;
Draw := DefaultDraw;
end;

Then I can just change Sprite.Collide to something else from the main game unit (oddly enough, this was giving me errors from within the engine unit, but as Sprite.Create is always called, this works wonderfull).