HGE的Delphi版本是國外網友編寫的,非官方發佈。最新版本是1.7,發佈於2009年,適用於Delphi2007及以下版本。
https://www.pascalgamedevelopment.com/showthread.php?5133-HGE(Haaf-s-Game-Engine)-port-for-Delphi
備用鏈接:https://download.csdn.net/download/aqtata/10958380
它在HGE之上添加了一些自己的東西,比如TSpriteEngine、THGECanvas、THGEImages等幫助類。
下面就從Example中的最簡單的一個Basic項目瞭解一下基本的使用。
program Basic;
{$R *.res}
uses
Windows, SysUtils, HGEImages, HGECanvas, HGEDef, HGE,
HGESpriteEngine;
type
TMonoSprite = class(TSprite)
private
FCounter: Double;
FLife: Integer;
FHit: Boolean;
public
procedure DoMove(const MoveCount: Single); override;
end;
TPlayerSprite = class(TSprite)
public
procedure DoCollision(const Sprite: TSprite); override;
procedure DoMove(const MoveCount: Single); override;
end;
var
HGE: IHGE = nil;
Images: THGEimages;
Canvas: THGECanvas;
Font: TSysFont;
SpriteEngine: TSpriteEngine;
Tile: array[0..80, 0..80] of TSprite;
PlayerSprite: TPlayerSprite;
procedure TMonoSprite.DoMove(const MoveCount: Single);
begin
inherited;
CollidePos := Point2(X + 80, Y + 80);
FCounter := FCounter + 1;
X := X + Sin256(Trunc(FCounter)) * 3;
Y := Y + Cos256(Trunc(FCounter)) * 3;
if FHit then
begin
FLife := FLife - 1;
if FLife < 0 then Dead;
end;
end;
procedure TPlayerSprite.DoCollision(const Sprite: TSprite);
begin
if Sprite is TMonoSprite then
begin
with TMonoSprite(Sprite) do
begin
ImageName := 'img1-2';
Collisioned := False;
FHit := True;
end;
end;
end;
procedure TPlayerSprite.DoMove(const MoveCount: Single);
begin
inherited;
CollidePos := Point2(X + 50, Y + 50);
if HGE.Input_GetKeyState(HGEK_UP) then
Y := Y - 3;
if HGE.Input_GetKeyState(HGEK_Down) then
Y := Y + 3;
if HGE.Input_GetKeyState(HGEK_Left) then
X := X - 3;
if HGE.Input_GetKeyState(HGEK_Right) then
X := X + 3;
Collision;
Engine.WorldX := X - 340;
Engine.WorldY := Y - 250;
end;
procedure CreateTiles;
var
I, J: Integer;
begin
for I := 0 to 80 do
begin
for J := 0 to 80 do
begin
Tile[I, J] := TSprite.Create(SpriteEngine);
Tile[I, J].ImageName := 't' + IntToStr(Random(20));
Tile[I, J].Width := Tile[I, J].PatternWidth;
Tile[I, J].Height := Tile[I, J].PatternHeight;
Tile[I,J].Moved := False;
Tile[I, J].X := I * 64;
Tile[I, J].Y := J * 64;
end;
end;
end;
procedure CreateSprites;
var
I: Integer;
begin
for I := 0 to 200 do
begin
with TMonoSprite.Create(SpriteEngine) do
begin
ImageName := 'img1';
Width := PatternWidth;
Height := PatternHeight;
Collisioned := True;
CollideRadius := 80;
X := Random(5000);
Y := Random(5000);
Z := 2;
FCounter := Random(1000);
FLife := 15;
FHit := False;
end;
end;
PlayerSprite := TPlayerSprite.Create(SpriteEngine);
with TPlayerSprite(PlayerSprite) do
begin
ImageName := 'img2';
Z := 2;
X := 2560;
Y := 2560;
Collisioned := True;
CollideRadius := 50;
end;
end;
function FrameFunc: Boolean;
begin
case HGE.Input_GetKey of
HGEK_ESCAPE:
begin
FreeAndNil(Canvas);
FreeAndNil(Images);
FreeAndNil(SpriteEngine);
FreeAndNil(Font);
Result := True;
Exit;
end;
end;
Result := False;
end;
function RenderFunc: Boolean;
begin
HGE.Gfx_BeginScene;
HGE.Gfx_Clear(0);
SpriteEngine.Draw;
SpriteEngine.Move(1);
SpriteEngine.Dead;
//Font.Print(100,100,IntToStr(HGE.Timer_GetFPS));
HGE.Gfx_EndScene;
Result := False;
end;
procedure Main;
var
I: Integer;
begin
HGE := HGECreate(HGE_VERSION);
HGE.System_SetState(HGE_FRAMEFUNC,FrameFunc);
HGE.System_SetState(HGE_RENDERFUNC,RenderFunc);
HGE.System_SetState(HGE_USESOUND, False);
HGE.System_SetState(HGE_WINDOWED,False);
HGE.System_SetState(HGE_SCREENWIDTH, 800);
HGE.System_SetState(HGE_SCREENHEIGHT,600);
HGE.System_SetState(HGE_SCREENBPP,16);
//HGE.System_SetState(HGE_TEXTUREFILTER,True);
HGE.System_SetState(HGE_FPS,HGEFPS_VSYNC);
//HGE.System_SetState(HGE_SHOWSPLASH, False);
Canvas := THGeCanvas.Create;
Images := THGEImages.Create;
SpriteEngine := TSpriteEngine.Create(nil);
Spriteengine.Images := Images;
SpriteEngine.Canvas := Canvas;
if (HGE.System_Initiate) then
begin
Font:=TSysFont.Create;
Font.CreateFont('arial',15,[]);
for I := 0 to 20 do
Images.LoadFromFile('Gfx\'+'t'+IntTostr(I)+'.png');
Images.LoadFromFile('Gfx\img1.png');
Images.LoadFromFile('Gfx\img1-2.png');
Images.LoadFromFile('Gfx\img2.png');
CreateSprites;
CreateTiles;
HGE.System_Start;
end
else
MessageBox(0,PChar(HGE.System_GetErrorMessage),'Error',MB_OK or MB_ICONERROR or MB_SYSTEMMODAL);
HGE.System_Shutdown;
HGE := nil;
end;
begin
ReportMemoryLeaksOnShutdown := True;
Main;
end.
第173行創建了一個THGECanvas實例,它是一個獨立的類,沒有父類,沒有子類,只是負責繪圖
所有公共方法都是以Draw開頭,內部都是調用HGE的Gfx_RenderQuad函數進行繪製的。
第174行創建了一個THGEImages實例,它也是一個獨立的類,用來管理遊戲中用到的所有紋理資源,內部用了一個數組來保存
Images: array of ITexture;
THGEImages類圖
接着176行創建了一個TSpriteEngine類,先看看精靈類之間的關係
(只是簡單的把類關係都列出來,成員方法都沒寫)
可以看到TSpriteEngine繼承自TSprite,也就是說TSpriteEngine本身就是一個精靈,爲什麼要單獨弄出一個Engine精靈呢?因爲作者將所有精靈設計爲一個樹形結構,處於最頂層(Root)的Sprite就要用TSpriteEngine來表示,整個遊戲生命週期中只有一個最頂層的Sprite,它就是TSpriteEngine。
177,178行就將前面創建的Canvas和Images和頂層精靈關聯起來,精靈內部會用到。
第184,185行就是加載瓦片地圖的圖片文件,一共有20張。
第189行調用CreateSprites來創建所有的精靈
procedure CreateSprites;
var
I: Integer;
begin
for I := 0 to 200 do
begin
with TMonoSprite.Create(SpriteEngine) do
begin
ImageName := 'img1';
Width := PatternWidth;
Height := PatternHeight;
Collisioned := True;
CollideRadius := 80;
X := Random(5000);
Y := Random(5000);
Z := 2;
FCounter := Random(1000);
FLife := 15;
FHit := False;
end;
end;
PlayerSprite := TPlayerSprite.Create(SpriteEngine);
with TPlayerSprite(PlayerSprite) do
begin
ImageName := 'img2';
Z := 2;
X := 2560;
Y := 2560;
Collisioned := True;
CollideRadius := 50;
end;
end;
第一個循環創建了200個大黑球精靈(TMonoSprite),第二個循環創建一個玩家控制的精靈(小黑球)。
回到190行,調用CreateTiles來創建瓦片地圖
procedure CreateTiles;
var
I, J: Integer;
begin
for I := 0 to 80 do
begin
for J := 0 to 80 do
begin
Tile[I, J] := TSprite.Create(SpriteEngine);
Tile[I, J].ImageName := 't' + IntToStr(Random(20));
Tile[I, J].Width := Tile[I, J].PatternWidth;
Tile[I, J].Height := Tile[I, J].PatternHeight;
Tile[I,J].Moved := False;
Tile[I, J].X := I * 64;
Tile[I, J].Y := J * 64;
end;
end;
end;
遊戲地圖用80x80張圖片拼接而成。這裏有個小BUG,Random(20)應該改爲Random(21),否則最後一張圖片永遠用不到。
接着引擎就開始工作了,主要看渲染函數
function RenderFunc: Boolean;
begin
HGE.Gfx_BeginScene;
HGE.Gfx_Clear(0);
SpriteEngine.Draw;
SpriteEngine.Move(1);
SpriteEngine.Dead;
//Font.Print(100,100,IntToStr(HGE.Timer_GetFPS));
HGE.Gfx_EndScene;
Result := False;
end;
渲染過程依次調用了最頂層精靈的Draw、Move、Dead方法
procedure TSprite.Draw;
var
i: Integer;
begin
if FVisible then
begin
if FEngine <> nil then
begin
if (X > FEngine.WorldX - Width ) and
(Y > FEngine.WorldY - Height) and
(X < FEngine.WorldX + FEngine.VisibleWidth) and
(Y < FEngine.WorldY + FEngine.VisibleHeight) then
begin
DoDraw;
Inc(FEngine.FDrawCount);
end;
end;
if FDrawList <> nil then
begin
for i := 0 to FDrawList.Count-1 do
TSprite(FDrawList[i]).Draw;
end;
end;
end;
這裏通過FEngine來判斷自己是不是頂層精靈,如果是頂層精靈則循環調用子精靈的Draw方法,也就是說頂層精靈本身是無法渲染的,它只用來管理子精靈。只有子精靈纔會進入到DoDraw方法中,這個方法是個virtual方法,所以渲染行爲可以被子類覆蓋。
procedure TSprite.DoDraw;
begin
if not FVisible then Exit;
if FEngine.FImages = nil then Exit;
case FTruncMove of
True:
FEngine.FCanvas.Draw(FEngine.FImages.Image[FImageName],
FPatternIndex,
Trunc(FX + FWorldX - FEngine.FWorldX),
Trunc(FY + FWorldY - FEngine.FWorldY),
FBlendMode);
False:
FEngine.FCanvas.Draw(FEngine.FImages.Image[FImageName],
FPatternIndex,
FX + FWorldX - FEngine.FWorldX,
FY + FWorldY - FEngine.FWorldY,
FBlendMode);
end;
end;
可以看出,要渲染的圖片是頂層精靈(TEngineSprite)所關聯的Images中提供的,子精靈本身只存儲一個紋理名稱即可。
然後通過頂層精靈(TEngineSprite)所關聯的Canvas來繪製。
至於遊戲的移動、碰撞邏輯可以看子精靈的DoMove和DoCollision方法,這裏不再深入。
通過這個例子可以大致瞭解到作者的設計思路,Delphi版本在官方版本上加了很多類來輔助開發,將精靈用樹形結構來管理(TEngineSprite),紋理資源統一管理(THGEImages),還在精靈上加了一些諸如Move、Collision、Dead的邏輯。