HGE port for Delphi

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的邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章