{------------------------------------------------------------------------------} { Unit Name: sprite Purpose : The basic class for sprites Author : Vesa Lappalainen Date : 03.11.1999 Changed : 5.11.1999 - Avaruuteen Spriteille oma lista, jossa on kaikki avaruudessa olevat spritet - Törmäysten tunnistaminen ja reagointi - lisätty vielä törmäysten takia oma lista : FDead, johon siirreään ne Spritet, jotka ovat TSpriteSpace.UnJoin -metodilla ilmoittautuneet kuolevansa. Lista tyhjennetään kun jokainen Sprite on saanut mahdollisuuden reagoida toisen kanssa. - vastaavasti jokainen sprite pitää liitää Sprite-listaan Join-metodilla (TSprite.Create-tekee tämän). 14.11.1999 - äly (TBrain). Jokaisella TSpritella voi olla vain yksi äly ja kukin äly voi ohjata vain yhtä spriteä. Sprite tulee toimeen ilmankin älyä Äly saa ensin tarkistaa törmäyksen, jos ei tarkista, niin sitten itse sprite saa tehdä homman. Samoin React menee ensin älylle, jos ei käsitellä, niin Sprite saa käsitellä ToDo : - kannattaisi tehdä mielluumin ensin suorakaide ja piste tyypit ja näille operaatiot. Vähenisi nuo joku.x tutkiskelut. - muunnosmatriisin voisi ujuttaa avaruuteen, jolloin mukaan saisi vaikka kolmiulotteisuuden Muutettu (Jarmo Louet, 20.11.1999) - Lisäsin eventit OnTimerFirst, OnTimerAfterJobs, OnTimerAfterHits ja OnTimerLast. Kaikki tapahtuvat DoTimer-silmukan aikana. 1) Näistä OnTimerFirst tapahtuu aivan ensimmäiseksi, ennen mitään muuta. 2) OnTimerAfterJobs tapahtuu nimensä mukaan heti DoJobeja kutsuvan silmukan jälkeen (mutta ennen kuin poistutaan DoTimeristä, jos ei tehty yhtään jobia). 3) Vastaavasti OnTimerAfterHits tapahtuu IsHit ja React silmukoiden jälkeen, mutta ennen kuin kuolleet on poistettu. 4) OnTimerLast tapahtuu aivan DoTimerin lopuksi. - Huvitti tehdä näistä eventtejä, jotta käyttäjä voi itse määritellä mitä niiden aikana tehdään ilman, että tarvitsee aina ruveta perimään ja overrideamaan. Toisaalta olisi voinut olla parempi käyttää nimenomaan overridea. Tiedä häntä... On tässä nyt ainakin se vaara olemassa, että onnistutaan katkaisemaan DoTimerin suoritus kokonaan ennen aikojaan. Muutettu (Vesa Lappalainen, 19.10.2000) - lisätty procedure DrawBackGround(bitmap: TBitmap); virtual; jotta jokainen voi piirtää oman taustakuvan ennen spritejen piirtoa. Muutettu (Vesa Lappalainen, 08.09.2001) - VCL/CLX -käännös Muutettu (Vesa Lappalainen, 08.12.2002) - lisätty procedure DrawForeGround(bitmap: TBitmap); virtual; jotta jokainen voi piirtää oman edustakuvan spritejen piirton jälkeen. } {------------------------------------------------------------------------------} unit sprite; interface uses Classes, {$ifdef CLX} QGraphics,QExtCtrls {$else} Graphics,ExtCtrls {$endif} ; type TSpriteSpace = class; TBrain = class; TOnTimerFirst = procedure(sender:TSpriteSpace) of object; TOnTimerAfterJobs = procedure(sender:TSpriteSpace) of object; TOnTimerAfterHits = procedure(sender:TSpriteSpace) of object; TOnTimerLast = procedure(sender:TSpriteSpace) of object; TSprite = class(TComponent) private Fx: double; Fy: double; Fw: double; Fh: double; FParent : TSpriteSpace; FBrain: TBrain; procedure Setx(const Value: double); procedure Sety(const Value: double); procedure Seth(const Value: double); procedure Setw(const Value: double); function GetParent: TSpriteSpace; function GetIx: integer; function GetIy: integer; procedure SetBrain(const Value: TBrain); function TakeBrain : TBrain; virtual; function GetIh: integer; function GetIw: integer; public constructor Create(AOwner:TComponent); override; destructor Destroy; override; function DoJob : boolean; virtual; function IsHit(sprite:TSprite):boolean; virtual; function React(sprite:TSprite):boolean; virtual; function ReactTo(sprite:TSprite):boolean; virtual; function PaintTo(canvas:TCanvas):boolean; virtual; property Parent : TSpriteSpace read GetParent; property Brain : TBrain read FBrain write SetBrain; published property x:double read Fx write Setx; property y:double read Fy write Sety; property h:double read Fh write Seth; property w:double read Fw write Setw; property ix : integer read GetIx; property iy : integer read GetIy; property iw : integer read GetIw; property ih : integer read GetIh; end; TBrain = class private FOwner: TSprite; procedure SetOwner(const Value: TSprite); public constructor Create(AOwner:TSprite); virtual; destructor Destroy; override; function DoJob : boolean; virtual; function IsHit(sprite:TSprite):boolean; virtual; function React(sprite:TSprite):boolean; virtual; function ReactTo(sprite:TSprite):boolean; virtual; property Owner : TSprite read FOwner write SetOwner; end; TSpriteSpace = class(TPanel) private bitmap : TBitmap; PaintBox : TPaintBox; timer : TTimer; FSprites : TList; FKill : Tlist; FRemove : Tlist; FOnTimerFirst : TOnTimerFirst; FOnTimerAfterJobs : TOnTimerAfterJobs; FOnTimerAfterHits : TOnTimerAfterHits; FOnTimerLast : TOnTimerLast; procedure SetInterval(const Value: integer); function GetInterval: integer; function GetRunning: boolean; procedure SetRunning(const Value: boolean); procedure DoTimer(sender: TObject); protected procedure Display; virtual; procedure SpacePaint(sender: TObject); virtual; procedure SpaceResize(sender: TObject); virtual; procedure ReDraw; virtual; procedure DrawBackGround(bitmap: TBitmap); virtual; procedure DrawForeGround(bitmap: TBitmap); virtual; public constructor Create(AOwner:TComponent); override; destructor Destroy; override; property Sprites : TList read FSprites; procedure Join(s:TSprite); virtual; procedure UnJoin(s:TSprite; LicenceToKill:boolean); virtual; published property Interval : integer read GetInterval write SetInterval; property Running : boolean read GetRunning write SetRunning; property OnTimerFirst:TOnTimerFirst read FOnTimerFirst write FOnTimerFirst; property OnTimerAfterJobs:TOnTimerAfterJobs read FOnTimerAfterJobs write FOnTimerAfterJobs; property OnTimerAfterHits:TOnTimerAfterHits read FOnTimerAfterHits write FOnTimerAfterHits; property OnTimerLast:TOnTimerLast read FOnTimerLast write FOnTimerLast; end; implementation { TSprite } constructor TSprite.Create(AOwner: TComponent); begin inherited; x := 0; y := 0; h := 20; w := 20; if ( AOwner is TSpriteSpace ) then begin FParent := TSpriteSpace(AOwner); Parent.Join(self); end; end; destructor TSprite.Destroy; begin if ( Assigned(Brain) ) then Brain.Free; inherited; end; function TSprite.DoJob: boolean; begin // if ( Brain <> nil ) and ( Brain.DoJob(sprite) ) then begin Result := true; exit; end; // Brain.DoJob(sprite) sanoo "too many actual parameters" if ( Brain <> nil ) and ( Brain.DoJob ) then begin Result := true; exit; end; Result := false; end; function TSprite.GetIh: integer; begin Result := Round(h); end; function TSprite.GetIw: integer; begin Result := Round(w); end; function TSprite.GetIx: integer; begin Result := Round(x); end; function TSprite.GetIy: integer; begin Result := Round(y); end; function TSprite.GetParent: TSpriteSpace; begin Result := FParent; end; function TSprite.IsHit(sprite: TSprite): boolean; begin Result := false; if ( Brain <> nil ) and ( Brain.IsHit(sprite) ) then begin Result := true; exit; end; if ( x + w < sprite.x ) then exit; if ( x > sprite.x + sprite.w ) then exit; if ( y + h < sprite.y ) then exit; if ( y > sprite.y + sprite.h ) then exit; Result := true; end; function TSprite.PaintTo(canvas: TCanvas): boolean; begin Result := false; end; function TSprite.React(sprite: TSprite): boolean; begin if ( Brain <> nil ) and ( Brain.React(sprite) ) then begin Result := true; exit; end; Result := sprite.ReactTo(self); end; function TSprite.ReactTo(sprite: TSprite): boolean; begin if ( Brain <> nil ) and ( Brain.ReactTo(sprite) ) then begin Result := true; exit; end; //?? Result := false; end; procedure TSprite.SetBrain(const Value: TBrain); begin if ( Brain = value ) then exit; if ( Brain <> nil ) then Brain.Free; // Del old brains FBrain := Value; if ( Brain = nil ) then exit; Brain.Owner := self; end; procedure TSprite.Seth(const Value: double); begin Fh := Value; end; procedure TSprite.Setw(const Value: double); begin Fw := Value; end; procedure TSprite.Setx(const Value: double); begin Fx := Value; end; procedure TSprite.Sety(const Value: double); begin Fy := Value; end; function TSprite.TakeBrain: TBrain; begin Result := Brain; FBrain := nil; end; { TBrain } constructor TBrain.Create(AOwner: TSprite); begin inherited Create; Owner := AOwner; end; destructor TBrain.Destroy; begin if ( Owner <> nil ) then begin Owner.TakeBrain; FOwner := nil; end; inherited; end; function TBrain.DoJob: boolean; begin Result := false; end; function TBrain.IsHit(sprite: TSprite): boolean; begin Result := false; end; function TBrain.React(sprite: TSprite): boolean; begin Result := false; end; function TBrain.ReactTo(sprite: TSprite): boolean; begin Result := false; end; procedure TBrain.SetOwner(const Value: TSprite); begin if ( Owner = Value ) then exit; if ( FOwner <> nil ) then FOwner.TakeBrain; // Old owner looses these brains FOwner := Value; if ( Owner = nil ) then exit; Owner.Brain := self; // Owner take's care of deleting old brains end; { TSpriteSpace } constructor TSpriteSpace.Create(AOwner: TComponent); begin Timer := TTimer.Create(self); inherited; bitmap := TBitmap.Create; PaintBox := TPaintBox.Create(self); FSprites := TList.Create; FKill := TList.Create; FRemove := TList.Create; OnResize := SpaceResize; PaintBox.Parent := self; PaintBox.OnPaint := SpacePaint; Timer.OnTimer := DoTimer; Interval := 50; end; destructor TSpriteSpace.Destroy; begin Sprites.Free; FKill.Free; FRemove.Free; bitmap.Free; PaintBox.Free; Timer.Free; inherited; end; procedure TSpriteSpace.SpaceResize(sender:TObject); begin bitmap.Width := Width; bitmap.Height := Height; Paintbox.Width := Width; Paintbox.Height := Height; ReDraw; end; procedure TSpriteSpace.Display; begin PaintBox.Canvas.Draw(0,0,bitmap); end; procedure TSpriteSpace.DrawBackGround(bitmap:TBitmap); begin bitmap.Canvas.Rectangle(0,0,bitmap.Width,bitmap.Height); end; procedure TSpriteSpace.DrawForeGround(bitmap:TBitmap); begin // end; procedure TSpriteSpace.ReDraw; var i:integer; s:TSprite; begin bitmap.Canvas.Brush.Color := Color; DrawBackGround(bitmap); for i:=0 to Sprites.Count-1 do begin s := Sprites[i]; s.PaintTo(bitmap.canvas); end; DrawForeGround(bitmap); Display; end; procedure TSpriteSpace.DoTimer(sender:TObject); var n,i,j:integer; s,s1:TSprite; begin if Assigned(FOnTimerFirst) then FOnTimerFirst(self); n := 0; for i:=0 to Sprites.Count-1 do begin s := Sprites[i]; if s.DoJob then inc(n); end; if Assigned(FOnTimerAfterJobs) then FOnTimerAfterJobs(self); if ( n = 0 ) then exit; for i:=0 to Sprites.Count-1 do begin s := Sprites[i]; for j:=i+1 to Sprites.Count-1 do begin s1 := Sprites[j]; if s.IsHit(s1) then s.React(s1); end; end; if Assigned(FOnTimerAfterHits) then FOnTimerAfterHits(self); for i:=0 to FRemove.Count-1 do begin Sprites.Remove(FRemove[i]); end; for i:=0 to FKill.Count-1 do begin s := FKill[i]; s.Free; end; FRemove.Clear; FKill.Clear; if Assigned(FOnTimerLast) then FOnTimerLast(self); ReDraw; end; procedure TSpriteSpace.SpacePaint(sender:TObject); begin Display; end; procedure TSpriteSpace.Setinterval(const Value: integer); begin Timer.Interval := Value; end; function TSpriteSpace.GetInterval: integer; begin Result := Timer.Interval; end; function TSpriteSpace.GetRunning: boolean; begin Result := Timer.Enabled; end; procedure TSpriteSpace.SetRunning(const Value: boolean); begin Timer.Enabled := Value; end; procedure TSpriteSpace.Join(s: TSprite); begin Sprites.Add(s); end; procedure TSpriteSpace.UnJoin(s: TSprite; LicenceToKill:boolean); begin if LicenceToKill and ( FKill.IndexOf(s) < 0 ) then FKill.Add(s); FRemove.Add(s); end; end.