www.pudn.com > Tank拊親蚔牁測鎢.rar > SPRITE.PAS


unit sprite; 
 
interface 
 
uses Windows, MMSystem, SysUtils, Graphics, Classes, Math, mapunit, util; 
 
type 
  TDirection = (drUp, drDown, drLeft, drRight); // 方向 
 
  // 屬性 
  TSpriteAttr = set of ( 
    saUndirectionalBitmap, // 圖形不具方向性 
    saNoCellCollision, // 不跟圖格碰撞 
    saNoTankCollision, // 不跟坦克碰撞 
    saNoBulletCollision, // 不跟子彈碰撞 
    saAlignWithTerrItem); // 走動時會自動對齊地形物, 走起來比較順 
 
  // 動畫結束後是否停止或重頭開始 
  TAdvanceFrameMode = (afWrap, afStop); 
 
  // 寶物種類 
  TGemKind = (gkClock, gkHat, gkArrow, gkStar, gkBlow, gkApple); 
 
  // forward declaration 
  TTank = class; 
  TBullet = class; 
  TGem = class; 
 
  // 傳回碰撞結果的陣列型態 
  TCellArray = array of TCell; 
  TTankArray = array of TTank; 
  TBulletArray = array of TBullet; 
 
  // 在畫面上行走活動的角色物件 
  TSprite = class 
  private 
    FX, fy: Integer; // 座標 
    FFrameNo: Integer; // 目前顯示的 frame 編號 
    FSpeed: Integer; // 行進速度 
    FCollisionCount: Integer; // 連續碰撞次數 
    FMoveDelay, FMoveDelayCount: Integer; // 下次動作前的延遲次數 
 
    FActive: Boolean; // 是否進行動作 
    FVisible: Boolean; // 是否可見 
    FOnAir: Boolean; // 是否在天空 
    FPostToDead: Boolean; // 是否已登記要摧毀 
 
    FRect: TRect; // 佔用矩形區域 
    FDirection: TDirection; // 行進方向 
    FAttr: TSpriteAttr; // 屬性 
    FAdvanceFrameMode: TAdvanceFrameMode; // 動畫結束後處理方式 
 
    FBits, FInvBitmap: TBitmap; // 圖片及貼圖用圖片 
 
    // 角色中心點所在的圖格位置 
    function GetTile_X: Integer; 
    function GetTile_Y: Integer; 
 
    // 取得角色的寬及高度 
    function GetObjectWidth: Integer; 
    function GetObjectHeight: Integer; 
 
    procedure SetDirection(Value: TDirection); 
  protected 
    // 取得角色圖形檔名 
    function GetFileName: string; virtual; abstract; 
    // 取得動畫框數 
    function GetFrameMax: Integer; virtual; abstract; 
    // 取得角色尺寸 
    function GetObjectRect: TRect; virtual; 
 
    // 碰撞檢查觸發方法, 負責呼叫所有的碰撞檢查方法 
    function CheckCollisions(var X, Y: Integer): Boolean; virtual; 
 
    // 邊界碰撞檢查 
    procedure CheckBoundCollisions(var X, Y: Integer; 
      var bCollision: Boolean); virtual; 
    // 地形物碰撞檢查 
    function CheckCellCollisions(Layer: Integer; var X, Y: Integer; 
      var bCollision: Boolean): TCellArray; virtual; 
    // 坦克碰撞檢查 
    function CheckTankCollisions(var X, Y: Integer; 
      var bCollision: Boolean): TTankArray; virtual; 
  public 
    constructor Create; virtual; 
    destructor Destroy; override; 
 
    procedure LoadBits; // 載入角色圖形 
 
    procedure Draw(Canvas: TCanvas); virtual; // 繪製角色 
 
    procedure Move; virtual; // 進行下一步動作 
 
    procedure PostToDie(Param: WPARAM = 0); // 登記欲摧毀本身 
 
    procedure RandomDirection; // 隨意選擇方向 
    procedure RandomPosition; // 隨意擺置 
    procedure CenterWith(ASprite: TSprite); // 與另一角色置中對齊 
    procedure CenterBy(X, Y: Integer); // 使中心點為 (X, Y) 
 
    property X: Integer read FX write FX; 
    property Y: Integer read fy write fy; 
 
    property Tile_X: Integer read GetTile_X; 
    property Tile_Y: Integer read GetTile_Y; 
 
    property Direction: TDirection read FDirection write SetDirection; 
    property Speed: Integer read FSpeed write FSpeed; 
    property Active: Boolean read FActive write FActive; 
    property Visible: Boolean read FVisible write FVisible; 
    property OnAir: Boolean read FOnAir write FOnAir; 
 
    property Rect: TRect read FRect; 
    property Attr: TSpriteAttr read FAttr write FAttr; 
 
    property ObjectRect: TRect read GetObjectRect; 
    property ObjectWidth: Integer read GetObjectWidth; 
    property ObjectHeight: Integer read GetObjectHeight; 
  end; 
 
  // 坦克類別, 我方及敵方坦克皆從此類別繼承 
  TTank = class(TSprite) 
  private 
    FHP: Integer; // 裝甲 (生命力) 
    FScore: Integer; // 被摧毀後, 主角的得分 
    FBulletNum: Integer; // 目前子彈數 
    FMaxBulletNum: Integer; //   同一時間子彈數上限 
    FBulletBlowRange: Integer; // 子彈爆炸威力 (範圍) 
 
    FSuperMode: Boolean; // 無敵模式 
  protected 
    function GetFrameMax: Integer; override; 
 
    // 改寫坦克碰撞檢查 
    function CheckTankCollisions(var X, Y: Integer; 
      var bCollision: Boolean): TTankArray; override; 
  public 
    constructor Create; override; 
    destructor Destroy; override; 
 
    procedure ResetStatus; virtual; // 重設坦克狀態 
    function FireBullet: TBullet; virtual; // 發射子彈 
 
    property HP: Integer read FHP write FHP; 
    property MaxBulletNum: Integer read FMaxBulletNum 
      write FMaxBulletNum; 
    property SuperMode: Boolean read FSuperMode write FSuperMode; 
  end; 
 
  // 我方坦克類別 
  TMyTank = class(TTank) 
  protected 
    function GetFileName: string; override; 
 
    // 改寫碰撞檢查觸發方法 
    function CheckCollisions(var X, Y: Integer): Boolean; override; 
 
    // 寶物碰撞檢查 
    function CheckGemCollisions(var X, Y: Integer; var bCollision: Boolean): TGem; virtual; 
  public 
    constructor Create; override; 
 
    procedure ResetStatus; override; // 重設坦克狀態 
  end; 
 
  // 敵方坦克出生時金光閃閃的效果類別 
  TStar = class(TSprite) 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
    function GetObjectRect: TRect; override; 
  public 
    constructor Create; override; 
  end; 
   
  // 敵方坦克類別, 會在出生時先利用 TStar 類別製造金光閃閃效果 
  TETank = class(TTank) 
  private 
    FStar: TStar; // 所使用的金光閃閃物件  
  protected 
    function GetFrameMax: Integer; override; 
  public 
    constructor Create; override; 
     
    procedure Draw(Canvas: TCanvas); override; 
    procedure Move; override; 
  end; 
   
  // 以下分別為五種敵方坦克類別 
  TETank1 = class(TETank) 
  protected 
    function GetFileName: string; override; 
  public 
    constructor Create; override; 
  end; 
   
  TETank2 = class(TETank) 
  protected 
    function GetFileName: string; override; 
  public 
    constructor Create; override; 
  end; 
   
  TETank3 = class(TETank) 
  protected 
    function GetFileName: string; override; 
  public 
    constructor Create; override; 
     
    function FireBullet: TBullet; override; 
  end; 
   
  TETank4 = class(TETank) 
  protected 
    function GetFileName: string; override; 
  public 
    constructor Create; override; 
  end; 
   
  TETank5 = class(TETank) 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
    function GetObjectRect: TRect; override; 
  public 
    constructor Create; override; 
  end; 
   
  // 爆炸效果類別, 為抽象類別 
  TExplosion = class(TSprite) 
  private 
    FLeadToOver: Boolean; // 會不會導致 Game Over 
  public 
    constructor Create; override; 
  end; 
  TExplosionClass = class of TExplosion; 
   
  // 打到邊界及地形物的小爆炸 
  TSmallExplosion = class(TExplosion) 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
  end; 
   
  // 打到坦克的大爆炸 
  TBigExplosion = class(TExplosion) 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
    function GetObjectRect: TRect; override; 
  end; 
   
  // 子彈類別, 子彈物件由坦克產生, 撞到東東後自行摧毀 
  TBullet = class(TSprite) 
  private 
    FTank: TTank; // 產生此子彈的坦克 
    FExplosion: TExplosion; // 此子彈所產生的爆炸物件 
    FTankBulletNum_Dropped: Boolean; // 是否已將坦克的子彈數目遞減 
 
    // 啟動爆炸效果 (建立爆炸物件) 
    procedure FireExplosion(ExplosionClass: TExplosionClass); 
    procedure LocateExplosion(R: TRect); // 將爆炸置於矩形區域中心 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
    function GetObjectRect: TRect; override; 
 
    // 改寫碰撞檢查觸發方法 
    function CheckCollisions(var X, Y: Integer): Boolean; override; 
 
    // 改寫所有的碰撞處理方法, 同時新增與子彈的碰撞處理方法 
    procedure CheckBoundCollisions(var X, Y: Integer; 
      var bCollision: Boolean); override; 
    function CheckCellCollisions(Layer: Integer; var X, Y: Integer; 
       var bCollision: Boolean): TCellArray; override; 
    function CheckTankCollisions(var X, Y: Integer; 
      var bCollision: Boolean): TTankArray; override; 
    function CheckBulletCollisions(var X, Y: Integer; 
      var bCollision: Boolean): TBulletArray; virtual; 
  public 
    constructor Create(ATank: TTank); 
    destructor Destroy; override; 
 
    procedure Draw(Canvas: TCanvas); override; 
    procedure Move; override; 
  end; 
 
  // 寶物類別, 會不定時出現在畫面上 
  TGem = class(TSprite) 
  private 
    FGemKind: TGemKind; // 寶物種類 
  protected 
    function GetFileName: string; override; 
    function GetFrameMax: Integer; override; 
  public 
    constructor Create(AGemKind: TGemKind); 
    destructor Destroy; override; 
  end; 
   
  // 坦克處理函式 (行動, 繪製, 釋放) 
procedure MoveTanks; 
procedure DrawTanks(Canvas: TCanvas; OnAir: Boolean); 
procedure FreeTanks; 
 
// 子彈處理函式 (行動, 繪製, 釋放) 
procedure MoveBullets; 
procedure DrawBullets(Canvas: TCanvas); // 繪製所有子彈 
procedure FreeBullets; 
procedure FreeBulletsForTank(ATank: TTank); 
 
var 
  Tanks  : array of TTank;   // 所有坦克 ( 包括我方坦克 ) 
  Bullets: array of TBullet; // 所有子彈 
  Gem    : TGem;             // 寶物 (同一時間只有一個) 
   
implementation 
 
uses tileunit, xFiles, xDArray; 
 
constructor TSprite.Create; 
begin 
  inherited Create; 
   
  // 初始化 
  FX := 0; 
  fy := 0; 
  FFrameNo := 0; 
 
  FMoveDelay := 0; 
  FMoveDelayCount := 0; 
   
  FActive := True; 
  FVisible := True; 
  FPostToDead := False; 
  FOnAir := False; 
   
  FAttr := []; 
  FAdvanceFrameMode := afWrap; 
  FSpeed := SPRITE_DEFAULT_SPEED; 
   
  // 角色的 bitmap 
  FBits := TBitmap.Create; 
 
  // 透明貼圖用的 bitmap 
  FInvBitmap := TBitmap.Create; 
  FInvBitmap.Width := ObjectWidth; 
  FInvBitmap.Height := ObjectHeight; 
  FInvBitmap.Transparent := True; 
  FInvBitmap.TransparentColor := TRANSPARENT_COLOR; 
end; 
 
destructor TSprite.Destroy; 
begin 
  FInvBitmap.Free; 
  FBits.Free; 
 
  inherited Destroy; 
end; 
 
procedure TSprite.LoadBits; 
begin 
  FBits.LoadFromFile(GetFileName); // 讀取 BMP 檔 
 
  // 檢查載入的 bitmap 尺寸是否合法 
  if not (saUndirectionalBitmap in Attr) then 
    if FBits.Width < ObjectWidth * 4 then // 四個方向 
      raise Exception.Create('Width of bitmap is invalid'); 
 
  if FBits.Height < ObjectHeight * (GetFrameMax + 1) then 
    raise Exception.Create('Height of bitmap is invalid'); 
end; 
 
procedure TSprite.Draw(Canvas: TCanvas); 
begin 
  //若不可見或已登記摧毀則不畫 
  if not FVisible or FPostToDead then Exit; 
 
  // 動畫框編號不合法則不畫 
  if (FFrameNo > GetFrameMax) or (FFrameNo < 0) then Exit; 
 
  // 先將要秀出的區域拷至 FInvBitmap 
  // 若只有一張圖形, 不分方向性 
  if saUndirectionalBitmap in FAttr then 
    FInvBitmap.Canvas.CopyRect(ObjectRect, FBits.Canvas, 
    Classes.Rect(0, FFrameNo * ObjectHeight, ObjectWidth, 
    (FFrameNo + 1) * ObjectHeight)) 
  else  // 依目前方向 
    FInvBitmap.Canvas.CopyRect(ObjectRect, FBits.Canvas, 
      Classes.Rect(Ord(FDirection) * ObjectWidth, 
      FFrameNo * ObjectHeight, (Ord(FDirection) + 1) * ObjectWidth, 
      (FFrameNo + 1) * ObjectHeight)); 
 
  // 再畫出 FInvBitmap 
  Canvas.Draw(FX, fy, FInvBitmap); 
end; 
 
procedure TSprite.CheckBoundCollisions(var X, Y: Integer;  
  var bCollision: Boolean); 
var 
  OrgX, OrgY: Integer; 
begin 
  // 邊界檢查 
  OrgX := X; OrgY := Y; 
  if Y < 0 then Y := 0; // 是否超出上方 
  if X < 0 then X := 0; // 是否超出左方 
  if X + ObjectWidth >= WORLD_WIDTH then // 是否超出右方 
    X := WORLD_WIDTH - ObjectWidth; 
  if Y + ObjectHeight >= WORLD_HEIGHT then // 是否超出下方 
    Y := WORLD_HEIGHT - ObjectHeight; 
 
  // 是否撞到邊界 ? 
  if (OrgX <> X) or (OrgY <> Y) then bCollision := True; 
end; 
 
function TSprite.CheckCellCollisions(Layer: Integer; var X, Y: Integer; var bCollision: Boolean): TCellArray; 
var 
  NewRect: TRect; 
   
  function IsRealCellCollision(CellXPos, CellYPos: Integer): Boolean; 
  begin 
    Result := False; 
     
    // 判斷是否為真的碰撞到地形物 
    case FDirection of 
      drUp: 
      begin 
        if CellYPos <> Tile_Y + 1 then // 向上走時不可能因此碰到下方地形 
        begin 
          Y := Map[Layer, CellXPos, CellYPos].Rect.Bottom; 
          Result := True; 
        end; 
      end; 
 
      drDown: 
      begin 
        if CellYPos <> Tile_Y - 1 then 
        begin 
          Y := Map[Layer, CellXPos, CellYPos].Rect.Top - ObjectHeight; 
          Result := True; 
        end; 
      end; 
       
      drLeft: 
      begin 
        if CellXPos <> Tile_X + 1 then 
        begin 
          X := Map[Layer, CellXPos, CellYPos].Rect.Right; 
          Result := True; 
        end; 
      end; 
 
      drRight: 
      begin 
        if CellXPos <> Tile_X - 1 then 
        begin 
          X := Map[Layer, CellXPos, CellYPos].Rect.Left - ObjectWidth; 
          Result := True; 
        end; 
      end; 
    end; 
     
    // 走動時自動對齊地形物, 走起來比較順     
    if Result and (saAlignWithTerrItem in FAttr) then 
      case FDirection of 
        drUp, drDown: 
        begin 
          if (CellXPos = Tile_X) and (Abs(X + ObjectWidth - Map[Layer, CellXPos, CellYPos].Rect.Left) <= SMOOTH_MOVE_THRESHOLD) then 
            X := Map[Layer, CellXPos, CellYPos].Rect.Left - ObjectWidth 
          else if (CellXPos = Tile_X) and (Abs(X - Map[Layer, CellXPos, CellYPos].Rect.Right) <= SMOOTH_MOVE_THRESHOLD) then 
            X := Map[Layer, CellXPos, CellYPos].Rect.Right 
          else if (CellXPos = Tile_X - 1) and (Abs(X - TILEWIDTH[Tile_X]) <= SMOOTH_MOVE_THRESHOLD) and Map[Layer, Tile_X, Tile_Y].CanPass then 
            X := TILEWIDTH[Tile_X] 
          else if (CellXPos = Tile_X + 1) and (Abs(X + ObjectWidth - TILEWIDTH[CellXPos]) <= SMOOTH_MOVE_THRESHOLD) and Map[Layer, CellXPos - 1, Tile_Y].CanPass then 
            X := TILEWIDTH[CellXPos] - ObjectWidth; 
        end; 
         
        drLeft, drRight: 
        begin 
          if (CellYPos = Tile_Y) and (Abs(Y + ObjectHeight - Map[Layer, CellXPos, CellYPos].Rect.Top) <= SMOOTH_MOVE_THRESHOLD) then 
            Y := Map[Layer, CellXPos, CellYPos].Rect.Top - ObjectHeight 
          else if (CellYPos = Tile_Y) and (Abs(Y - Map[Layer, CellXPos, CellYPos].Rect.Bottom) <= SMOOTH_MOVE_THRESHOLD) then 
            Y := Map[Layer, CellXPos, CellYPos].Rect.Bottom 
          else if (CellYPos = Tile_Y - 1) and (Abs(Y - TILEHeight[Tile_Y]) <= SMOOTH_MOVE_THRESHOLD) and Map[Layer, Tile_X, Tile_Y].CanPass then 
            Y := TILEHeight[Tile_Y] 
          else if (CellYPos = Tile_Y + 1) and (Abs(Y + ObjectHeight - TILEHeight[CellYPos]) <= SMOOTH_MOVE_THRESHOLD) and Map[Layer, Tile_X, CellYPos - 1].CanPass then 
            Y := TILEHeight[CellYPos] - ObjectHeight; 
        end; 
      end; 
  end; 
   
  procedure CheckCellCollisionsPos(XPos, YPos: Integer); 
  var 
    R: TRect; 
  begin 
    if (XPos < 0) or (YPos < 0) or (XPos >= TILE_NUM_X) or (YPos >= TILE_NUM_Y) then 
      Exit; 
     
    // 將不能通過的地形 Cell 加入碰撞結果陣列 
    if not Map[Layer, XPos, YPos].CanPass and 
      (IntersectRect(R, NewRect, Map[Layer, XPos, YPos].Rect)) and 
      IsRealCellCollision(XPos, YPos) then 
    begin 
      SetLength(Result, Length(Result) + 1); 
      Result[Length(Result) - 1] := Map[Layer, XPos, YPos]; 
    end; 
  end; 
 
var 
  tX, ty: Integer; 
begin 
  NewRect := ObjectRect; 
  OffsetRect(NewRect, X, Y); 
   
  tX := (X + ObjectWidth div 2) div TILE_WIDTH; 
  ty := (Y + ObjectHeight div 2) div TILE_Height; 
   
  // 分別測試角色周圍八塊圖格及本身所在圖格, 是否發生碰撞 
  dec(tX); dec(ty); 
  CheckCellCollisionsPos(tX, ty); // (tx-1, ty-1) 
 
  Inc(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx, ty-1) 
 
  Inc(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx+1, ty-1) 
 
  Inc(ty); 
  CheckCellCollisionsPos(tX, ty); // (tx+1, ty) 
 
  dec(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx, ty) 
 
  dec(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx-1, ty) 
 
  Inc(ty); 
  CheckCellCollisionsPos(tX, ty); // (tx-1, ty+1) 
   
  Inc(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx, ty+1) 
 
  Inc(tX); 
  CheckCellCollisionsPos(tX, ty); // (tx+1, ty+1) 
 
  bCollision := Length(Result) > 0; // 有碰撞發生 
end; 
 
function TSprite.CheckTankCollisions(var X, Y: Integer; 
  var bCollision: Boolean): TTankArray; 
var 
  I         : Integer; 
  NewRect, R: TRect; 
begin 
  // 計算新的矩形區域 
  NewRect := ObjectRect; 
  OffsetRect(NewRect, X, Y); 
 
  // 一一尋訪畫面上所有坦克 
  for I := 0 to Length(Tanks) - 1 do 
    if (Self <> Tanks[I]) and (not Tanks[I].FPostToDead) and 
      IntersectRect(R, NewRect, Tanks[I].Rect) then 
    begin 
      // 將撞到的坦克加入碰撞結果陣列 
      SetLength(Result, Length(Result) + 1); 
      Result[Length(Result) - 1] := Tanks[I]; 
    end; 
 
  bCollision := Length(Result) > 0; // 有碰撞發生 
end; 
 
// (X, Y) 是欲使用的新座標, 傳回值表示是否發生任何碰撞 
function TSprite.CheckCollisions(var X, Y: Integer): Boolean; 
begin 
  Result := False; // 先假設沒有碰到東西 
   
  // 檢查是否撞到坦克 
  if not (saNoTankCollision in FAttr) then 
  begin 
    CheckTankCollisions(X, Y, Result); 
    if Result then Exit; 
  end; 
   
  // 檢查是否撞到地形物 
  if not (saNoCellCollision in FAttr) then 
  begin 
    CheckCellCollisions(LAYER_TERRITEM, X, Y, Result); 
    if Result then Exit; 
  end; 
 
  // 檢查是否撞到邊界 
  CheckBoundCollisions(X, Y, Result); 
  if Result then Exit; 
end; 
 
// 最重要的動作函式, 控制角色的動作 
procedure TSprite.Move; 
var 
  X, Y: Integer; 
begin 
  if FPostToDead then Exit; // 已經登記準備被摧毀了 
  if not FActive then Exit; // 不進行任何動作 
 
  // 每次動作前的延遲次數 
  if (FMoveDelay <> 0) then 
  begin 
    // 每 FMoveDelay 次才真正 Move 一次 
    Inc(FMoveDelayCount); 
    if FMoveDelayCount <> FMoveDelay then Exit; 
    FMoveDelayCount := 0; // 歸零 
  end; 
 
  if GetFrameMax > 0 then // 若有動畫的話 
  begin 
    Inc(FFrameNo); // 遞增動畫編號 
    if FFrameNo > GetFrameMax then // 播放一個輪迴了 
    begin 
      if FAdvanceFrameMode = afWrap then FFrameNo := 0 // 重新再來 
      else 
      begin 
        FFrameNo := GetFrameMax;  // 維持在最後一張動畫 
        FActive := False; // 停止動作 
      end; 
    end; 
  end; 
 
  if FSpeed = 0 then Exit; // 若速度為零, 不必移動 
 
  X := FX; Y := fy; 
  // 計算下一步的位置 
  case FDirection of 
    drUp: dec(Y, FSpeed); // 往上走 
    drDown: Inc(Y, FSpeed); // 往下走 
    drLeft: dec(X, FSpeed); // 往左走 
    drRight: Inc(X, FSpeed); // 往右走 
  end; 
 
  // 檢查所有碰撞 
  if CheckCollisions(X, Y) then 
    Inc(FCollisionCount) //  撞到什麼東西了... 
  else 
    FCollisionCount := 0; // 什麼都沒撞到, 連續碰撞計數器歸零 
 
  FX := X; // 更新位置 
  fy := Y; 
 
  // 更新佔用的矩形區域 
  FRect := ObjectRect; 
  OffsetRect(FRect, FX, fy); 
end; 
 
// 隨意選擇方向 
procedure TSprite.RandomDirection; 
begin 
  Direction := TDirection(Random(Ord(High(TDirection)) + 1)); 
end; 
 
// 隨意擺置 
procedure TSprite.RandomPosition; 
begin 
  FX := Random(WORLD_WIDTH - ObjectWidth); 
  fy := Random(WORLD_HEIGHT - ObjectHeight); 
   
  FRect := ObjectRect; 
  OffsetRect(FRect, FX, fy); 
end; 
 
// 與另一角色置中對齊 
procedure TSprite.CenterWith(ASprite: TSprite); 
begin 
  FX := ASprite.FX + ASprite.ObjectWidth div 2 - ObjectWidth div 2; 
  fy := ASprite.fy + ASprite.ObjectHeight div 2 - ObjectHeight div 2; 
   
  FRect := ObjectRect; 
  OffsetRect(FRect, FX, fy); 
end; 
 
// 使中心點為 (X, Y) 
procedure TSprite.CenterBy(X, Y: Integer); 
begin 
  FX := X - ObjectWidth div 2; 
  fy := Y - ObjectHeight div 2; 
   
  FRect := ObjectRect; 
  OffsetRect(FRect, FX, fy); 
end; 
 
function TSprite.GetTile_X: Integer; 
begin 
  Result := (FX + ObjectWidth div 2) div TILE_WIDTH; 
end; 
 
function TSprite.GetTile_Y: Integer; 
begin 
  Result := (fy + ObjectHeight div 2) div TILE_Height; 
end; 
 
// 取得角色寬度 
function TSprite.GetObjectWidth: Integer; 
begin 
  with GetObjectRect do 
    Result := Right - Left; 
end; 
 
// 取得角色高度 
function TSprite.GetObjectHeight: Integer; 
begin 
  with GetObjectRect do 
    Result := Bottom - Top; 
end; 
 
procedure TSprite.SetDirection(Value: TDirection); 
begin 
  if Value <> FDirection then 
  begin 
    FCollisionCount := 0; // 更改方向後, 連續碰撞次數歸零 
    FDirection := Value; 
  end; 
end; 
 
function TSprite.GetObjectRect: TRect; 
begin 
  Result := TILE_RECT; 
end; 
 
// 登記欲摧毀本身 
procedure TSprite.PostToDie(Param: WPARAM = 0); 
begin 
  if FPostToDead then Exit; // 不重覆登記 
   
  // 傳送 WM_DESTROY_OBJECT 訊息及本身位址, 供遊戲迴圈處理 
  PostMessage(0, WM_DESTROY_OBJECT, WPARAM(Self), Param); 
  FPostToDead := True; 
end; 
 
{ TTank abstract class } 
 
constructor TTank.Create; 
begin 
  inherited Create; 
   
  ResetStatus; // 重置坦克狀態 (生命力, 速度, 等等) 
   
  // 將本身加入 Tanks 全域陣列 
  SetLength(Tanks, Length(Tanks) + 1); 
  Tanks[Length(Tanks) - 1] := Self; 
end; 
 
destructor TTank.Destroy; 
var 
  I: Integer; 
begin 
  // 將本身由 Tanks 全域陣列移除 (除了我方坦克) 
  for I := 1 to Length(Tanks) - 1 do 
    if Tanks[I] = Self then 
    begin 
      DynArrayDelete(Tanks, sizeof(TTank), I, 1); 
      Break; 
    end; 
   
  inherited Destroy; 
end; 
 
function TTank.GetFrameMax: Integer; 
begin 
  Result := 1; 
end; 
 
// 發射子彈, 傳回值為發射出去的子彈物件 
function TTank.FireBullet: TBullet; 
begin 
  Result := nil; 
 
  // 目前子彈數若已達上限則不容許再發射 
  if FBulletNum >= FMaxBulletNum then Exit; 
 
  Result := TBullet.Create(Self); // 建立子彈物件 
  Result.LoadBits; // 載入子彈圖形 
  Result.Direction := FDirection; // 子彈與坦克本身同樣方向 
  Result.Speed := BULLET_DEFAULT_SPEED; 
 
  Result.CenterWith(Self); // 先將子彈座標設定與坦克置中對齊 
 
  case FDirection of // 根據行進方向來調整子彈座標 
    drUp: Result.Y := Rect.Top; 
    drDown: Result.Y := Rect.Bottom - Result.ObjectHeight; 
    drLeft: Result.X := Rect.Left; 
    drRight: Result.X := Rect.Right - Result.ObjectWidth; 
  end; 
 
  Inc(FBulletNum); // 遞增坦克的目前子彈數 
end; 
 
function TTank.CheckTankCollisions(var X, Y: Integer; var bCollision: Boolean): TTankArray; 
var 
  I: Integer; 
begin 
  // 首先呼叫原始的 CheckTankCollisions 方法取得碰撞坦克列表 
  Result := inherited CheckTankCollisions(X, Y, bCollision); 
 
  if bCollision then // 若與任何坦克發生碰撞 
  begin 
    I := 0; 
    while I < Length(Result) do // 清掉"不會碰撞坦克"的坦克 
      if (saNoTankCollision in Result[I].FAttr) then 
        DynArrayDelete(Result, sizeof(TTank), I, 1) 
      else 
        Inc(I); 
 
    bCollision := Length(Result) > 0; // 重新裁決是否發生碰撞 
    if not bCollision then Exit; // 亡羊補牢, 不應該發生碰撞的.. 
     
    // 根據碰撞到的坦克及自己的方向來調整座標 
    for I := 0 to Length(Result) - 1 do 
      case FDirection of 
        drUp: Y := Result[I].Rect.Bottom; // 把自己放在對方的下方 
        drDown: Y := Result[I].Rect.Top - ObjectHeight; 
        drLeft: X := Result[I].Rect.Right; // 把自己放在對方的右方 
        drRight: X := Result[I].Rect.Left - ObjectWidth; 
      end; 
  end; 
end; 
 
// 重置坦克狀態 (生命力, 速度, 等等) 
procedure TTank.ResetStatus; 
begin 
  FSuperMode := False; // 正常模式 
   
  FHP := 1; // 被打一次就死掉 
   
  FMaxBulletNum := 1; 
  FBulletNum := 0; 
  FBulletBlowRange := BULLET_DEFAULT_BLOW_RANGE; 
   
  FPostToDead := False; 
  FAttr := [saAlignWithTerrItem]; // 坦克都具有自動調整位置屬性, 走起來比較順 
end; 
 
{ TMyTank class } 
 
constructor TMyTank.Create; 
begin 
  inherited Create; 
 
  FDirection := drUp; // 方向朝上 
end; 
 
procedure TMyTank.ResetStatus; 
begin 
  inherited ResetStatus; 
   
  FSpeed := MYTANK_DEFAULT_SPEED; // 設定我方坦克的速度 
end; 
 
function TMyTank.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'mytank.bmp'; 
end; 
 
function TMyTank.CheckGemCollisions(var X, Y: Integer; var bCollision: Boolean): TGem; 
var 
  NewRect, R: TRect; 
begin 
  Result := nil; 
 
  if not Assigned(Gem) then Exit; // 寶物不存在 
 
  // 根據新座標計算坦克所佔用的矩形區域 
  NewRect := ObjectRect; 
  OffsetRect(NewRect, X, Y); 
 
  // 坦克是否與寶物所佔矩形區域產生交集 
  if IntersectRect(R, NewRect, Gem.Rect) then 
    Result := Gem; 
 
  // 無論結果為何, 不更改 bCollision 布林變數的值, 因為與寶物 
  // 碰撞(吃到寶物)並不是真正的碰撞 
end; 
 
function TMyTank.CheckCollisions(var X, Y: Integer): Boolean; 
begin 
  Result := inherited CheckCollisions(X, Y); 
 
  // 如果吃到寶物的話... 
  if Assigned(CheckGemCollisions(X, Y, Result)) then 
  begin 
    // 根據寶物的種類, 傳送訊息給遊戲迴圈處理, 或自己處理掉 
    case Gem.FGemKind of 
      gkClock: 
      PostMessage(0, WM_SPECIAL_CONDITION, TIMER_ID_GEM_CLOCK, 0); 
 
      gkHat: 
      PostMessage(0, WM_SPECIAL_CONDITION, TIMER_ID_GEM_HAT, 0); 
 
      gkArrow: 
      PostMessage(0, WM_SPECIAL_CONDITION, TIMER_ID_GEM_ARROW, 0); 
 
      gkStar: FMaxBulletNum := 10; // 能夠連發子彈 
 
      gkBlow: FBulletBlowRange := 32; // 超強子彈爆炸威力 
 
      gkApple: FSpeed := 2 * MYTANK_DEFAULT_SPEED; // 兩倍速度 
    end; 
 
    Gem.Free; // 將寶物摧毀(被吃掉了) 
  end; 
end; 
 
{ TStar class } 
 
// 敵方坦克出生時金光閃閃的效果類別 
constructor TStar.Create; 
begin 
  inherited Create; 
 
  FMoveDelay := 8; // 每八個 frame 才變一次 
  FSpeed := 0; // 不動 
  FAttr := [saUndirectionalBitmap]; // 沒有方向性 
  FAdvanceFrameMode := afStop; // 動畫播完後就停止 
end; 
 
function TStar.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'star.bmp'; 
end; 
 
function TStar.GetFrameMax: Integer; 
begin 
  Result := 2; // 三張動畫 
end; 
 
function TStar.GetObjectRect: TRect; 
begin 
  Result := Classes.Rect(0, 0, 38, 32); 
end; 
 
{ TETank class } 
 
// 敵方坦克類別, 
constructor TETank.Create; 
begin 
  inherited Create; 
 
  FDirection := drDown; // 方向一律朝下 
 
  // 在三個出生地點中擇一 (左上角, 正上方, 右上角) 
  fy := 0; 
  case Random(3) of 
    0: FX := 0; 
    1: FX := (WORLD_WIDTH - ObjectWidth) div 2; 
    2: FX := WORLD_WIDTH - ObjectWidth; 
  end; 
 
  // 先利用 TStar 類別製造金光閃閃效果 
  FStar := TStar.Create; 
  FStar.LoadBits; 
  FStar.CenterWith(Self); 
end; 
 
function TETank.GetFrameMax: Integer; 
begin 
  Result := 1; // 兩張動畫 (每個方向) 
end; 
 
// 敵方坦克的行動方法 
procedure TETank.Move; 
begin 
  if Assigned(FStar) then // 還在出生中.. 
  begin 
    FStar.Move; // 金光閃閃物件進行下一動 
 
    if not FStar.FActive then // 若金光閃閃動畫秀完了.. 
    begin 
      FStar.Free; // 摧毀金光閃閃物件, 下一動敵方坦克就會出現了.. 
      FStar := nil; 
    end; 
  end else 
  begin 
    // 依亂數"可能"發射子彈 
    if (Random < PROBAB_ETANK_SHOT_BULLET) then 
      FireBullet; 
 
    // 若連續碰撞次數過多, 或亂數許可, 則轉向 
    if (FCollisionCount > MAX_ETANK_COLLISION_COUNT) or 
      (Random < PROBAB_ETANK_RANDOM_TURN) then 
      RandomDirection; 
 
    inherited Move; // 進行下一動 
  end; 
end; 
 
procedure TETank.Draw(Canvas: TCanvas); 
begin 
  if not Assigned(FStar) then 
    inherited Draw(Canvas) // 正常的繪製動作 
  else 
    FStar.Draw(Canvas); // 畫出金光閃閃的星星 
end; 
 
{ TETank1 class } 
 
constructor TETank1.Create; 
begin 
  inherited Create; 
 
  FScore := 1000; 
end; 
 
function TETank1.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'etank1.bmp'; 
end; 
 
{ TETank2 class } 
 
constructor TETank2.Create; 
begin 
  inherited Create; 
   
  FSpeed := SPRITE_DEFAULT_SPEED * 2; // 速度特別快 
  FScore := 1200; 
end; 
 
function TETank2.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'etank2.bmp'; 
end; 
 
{ TETank3 class } 
 
constructor TETank3.Create; 
begin 
  inherited Create; 
   
  FScore := 1500; 
end; 
 
function TETank3.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'etank3.bmp'; 
end; 
 
function TETank3.FireBullet: TBullet; 
begin 
  Result := inherited FireBullet; 
   
  if Assigned(Result) then // 子彈特別快 
    Result.Speed := BULLET_DEFAULT_SPEED * 2; 
end; 
 
{ TETank4 class } 
 
constructor TETank4.Create; 
begin 
  inherited Create; 
 
  FSpeed := SPRITE_DEFAULT_SPEED - 1; // 稍微慢一點 
  FScore := 2000; 
  FHP := 4; // 打四下才會掛 
end; 
 
function TETank4.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'etank4.bmp'; 
end; 
 
{ TETank5 class } 
 
constructor TETank5.Create; 
begin 
  inherited Create; 
 
  FHP := 10; // 打 10 下才會掛, 超猛 
  FSpeed := SPRITE_DEFAULT_SPEED div 2; // 很慢 
  FScore := 10000; 
  // 沒有方向性, 不會與地形物及坦克相撞 (因為在空中) 
  FAttr := [saUndirectionalBitmap, saNoCellCollision, saNoTankCollision]; 
  FOnAir := True; // 在空中唷 
end; 
 
function TETank5.GetFrameMax: Integer; 
begin 
  Result := 0; // 一張 
end; 
 
function TETank5.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'etank5.bmp'; 
end; 
 
function TETank5.GetObjectRect: TRect; 
begin 
  Result := Classes.Rect(0, 0, 86, 48); // 好大的"浮蝶"~~ 
end; 
 
{ TExplosion } 
 
constructor TExplosion.Create; 
begin 
  inherited Create; 
 
  FLeadToOver := False; // 不會導致 Game Over 
   
  FMoveDelay := 4; // 每四個 frame 才變一次 
  FSpeed := 0; // 不動 
  FAttr := [saUndirectionalBitmap, saNoCellCollision, saNoTankCollision, saNoBulletCollision]; // 沒有方向性 
   
  FAdvanceFrameMode := afStop; // 動畫播完後就停止 
end; 
 
{ TSmallExplosion } 
 
function TSmallExplosion.GetFrameMax: Integer; 
begin 
  Result := 4; // 五張 
end; 
 
function TSmallExplosion.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'smexp.bmp'; 
end; 
 
{ TBigExplosion } 
 
function TBigExplosion.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'bigexp.bmp'; 
end; 
 
function TBigExplosion.GetFrameMax: Integer; 
begin 
  Result := 5; // 六張 
end; 
 
function TBigExplosion.GetObjectRect: TRect; 
begin 
  Result := Classes.Rect(0, 0, 64, 64); 
end; 
 
{ TBullet class } 
 
constructor TBullet.Create(ATank: TTank); 
begin 
  inherited Create; 
 
  FTank := ATank; // 記錄發射本身的坦克 
  FExplosion := nil; 
  FTankBulletNum_Dropped := False; 
 
  // 將本身加入 Bullets 全域陣列 
  SetLength(Bullets, Length(Bullets) + 1); 
  Bullets[Length(Bullets) - 1] := Self; 
end; 
 
destructor TBullet.Destroy; 
var 
  I, J: Integer; 
begin 
  // 將本身由 Bullets 全域陣列移除 
  for I := 0 to Length(Bullets) - 1 do 
    if Bullets[I] = Self then 
    begin 
      DynArrayDelete(Bullets, sizeof(TBullet), I, 1); 
      Break; 
    end; 
 
  if Assigned(FTank) and not FTankBulletNum_Dropped then 
    dec(FTank.FBulletNum); // 遞減坦克的子彈數目 
 
  // 摧毀爆炸物件 
  if Assigned(FExplosion) then FExplosion.Free; 
 
  inherited Destroy; 
end; 
 
procedure TBullet.FireExplosion(ExplosionClass: TExplosionClass); 
begin 
  if Assigned(FExplosion) then Exit; // 已經存在爆炸了.. 
 
  FExplosion := ExplosionClass.Create; // 產生爆炸物件 
  FExplosion.LoadBits; 
  FExplosion.CenterWith(Self); 
 
  if Assigned(FTank) then // 若發射它的坦克仍存在 
  begin 
    dec(FTank.FBulletNum); // 遞減坦克的子彈數目 
    FTankBulletNum_Dropped := True; 
  end; 
end; 
 
procedure TBullet.LocateExplosion(R: TRect); 
begin 
  if not Assigned(FExplosion) then Exit; 
   
  case FDirection of 
    drUp: FExplosion.CenterBy(X, R.Bottom); 
    drDown: FExplosion.CenterBy(X, R.Top); 
    drLeft: FExplosion.CenterBy(R.Right, Y); 
    drRight: FExplosion.CenterBy(R.Left, Y); 
  end; 
end; 
 
function TBullet.CheckCollisions(var X, Y: Integer): Boolean; 
begin 
  Result := inherited CheckCollisions(X, Y); 
 
  // 若之前都沒撞到其它東東, 再檢查是否碰撞其它子彈 
  if not Result then 
    CheckBulletCollisions(X, Y, Result); 
end; 
 
procedure TBullet.CheckBoundCollisions(var X, Y: Integer; var bCollision: Boolean); 
begin 
  inherited CheckBoundCollisions(X, Y, bCollision); 
   
  if bCollision then 
  begin 
    FireExplosion(TSmallExplosion); // 引發爆炸 
    FExplosion.CenterBy(X + ObjectWidth div 2, Y + ObjectHeight div 2); 
  end; 
end; 
 
function TBullet.CheckCellCollisions(Layer: Integer; var X, Y: Integer; var bCollision: Boolean): TCellArray; 
var 
  OrgX, OrgY: Integer; 
  I         : Integer; 
  ExpRect   : TRect;   // 爆炸影響範圍 
begin 
  OrgX := X; 
  OrgY := Y; 
   
  Result := inherited CheckCellCollisions(Layer, X, Y, bCollision); 
 
  if bCollision then 
  begin 
    I := 0; 
    while I < Length(Result) do // 清掉其實可以通過的 Cells 
      if (taBulletCanPass in Result[I].TileAttr) then 
        DynArrayDelete(Result, sizeof(TCell), I, 1) 
      else 
        Inc(I); 
 
    bCollision := Length(Result) > 0; 
    if not bCollision then 
    begin 
      // 亡羊補牢, 不應該發生碰撞的.. 
      X := OrgX; 
      Y := OrgY; 
      Exit; 
    end; 
     
    FireExplosion(TSmallExplosion); // 引發爆炸 
    FExplosion.CenterBy(X + ObjectWidth div 2, Y + ObjectHeight div 2); 
     
    ExpRect := ObjectRect; 
    OffsetRect(ExpRect, X, Y); 
     
    if Assigned(FTank) then 
      InflateRect(ExpRect, FTank.FBulletBlowRange, FTank.FBulletBlowRange) 
    else 
      InflateRect(ExpRect, BULLET_DEFAULT_BLOW_RANGE, BULLET_DEFAULT_BLOW_RANGE); 
     
    for I := 0 to Length(Result) - 1 do 
      if (taCanBreak in Tiles[Result[I].TileNo].Attr) then 
      begin 
        LocateExplosion(Result[I].Rect); 
        Result[I].BreakBy(ExpRect); 
      end else if (taFlag in Tiles[Result[I].TileNo].Attr) then 
      begin 
        Result[I].TileNo := 16; // 換成毀壞的旗子 
        FExplosion.FLeadToOver := True; 
      end; 
  end; 
end; 
 
function TBullet.CheckTankCollisions(var X, Y: Integer; var bCollision: Boolean): TTankArray; 
var 
  I: Integer; 
begin 
  Result := inherited CheckTankCollisions(X, Y, bCollision); 
   
  if not Assigned(FTank) then Exit; // 自己的坦克掛點了.. 
   
  if bCollision then 
  begin 
    for I := 0 to Length(Result) - 1 do 
      if (Result[I] <> FTank) and not (saNoBulletCollision in Result[I].Attr) and 
        (((Result[I] is TMyTank) and (FTank is TETank)) or 
        ((Result[I] is TETank) and (FTank is TMyTank))) then 
      begin 
        PlaySound(PChar(AppDir + 'bomb.wav'), 0, SND_FILENAME or SND_ASYNC); 
         
        FireExplosion(TBigExplosion); // 引發大爆炸 
         
        if Result[I].FSuperMode then continue; // 無敵模式 
         
        dec(Result[I].FHP); // 生命力減一 
        if Result[I].FHP = 0 then // 被打到的坦克翹掉了.. 
        begin 
          FExplosion.CenterWith(Result[I]); // 掛掉時在坦克中央爆炸 
          Result[I].PostToDie(Result[I].FScore); 
        end else 
          LocateExplosion(Result[I].Rect); // 沒掛掉的話, 在邊緣爆炸 
         
        Break; // 打到一輛坦克就夠了... 
      end; 
  end; 
end; 
 
function TBullet.CheckBulletCollisions(var X, Y: Integer; var bCollision: Boolean): TBulletArray; 
var 
  I         : Integer; 
  NewRect, R: TRect;   
begin 
  if not Assigned(FTank) then Exit; // 自己的坦克掛點了.. 
   
  NewRect := ObjectRect; 
  OffsetRect(NewRect, X, Y); 
   
  for I := 0 to Length(Bullets) - 1 do 
    if (Self <> Bullets[I]) and Assigned(Bullets[I].FTank) and 
      (((Bullets[I].FTank is TMyTank) and (FTank is TETank)) or 
      ((Bullets[I].FTank is TETank) and (FTank is TMyTank))) and 
      IntersectRect(R, NewRect, Bullets[I].Rect) then 
    begin 
      PostToDie; 
      Bullets[I].PostToDie; 
    end; 
end; 
 
procedure TBullet.Move; 
begin 
  if not Assigned(FExplosion) then // 還未爆炸 
    inherited Move 
  else 
  begin 
    FExplosion.Move; 
     
    if not FExplosion.Active then // 爆炸結束 
    begin 
      if FExplosion.FLeadToOver then 
        PostMessage(0, WM_GAMEOVER, 0, 0) 
      else 
        FExplosion.Visible := False; // 隱藏起來 
      // 釋放子彈 
      PostToDie; 
    end; 
  end; 
end; 
 
function TBullet.GetFileName: string; 
begin 
  Result := AppDir + DR_IMAGES + 'bullet.bmp'; 
end; 
 
function TBullet.GetFrameMax: Integer; 
begin 
  Result := 0; // 只有一張 
end; 
 
function TBullet.GetObjectRect: TRect; 
begin 
  Result := Classes.Rect(0, 0, 16, 16); 
end; 
 
procedure TBullet.Draw(Canvas: TCanvas); 
begin 
  if not Assigned(FExplosion) then // 若正常行進中, 則畫出子彈 
    inherited Draw(Canvas) 
  else 
    FExplosion.Draw(Canvas); // 否則畫出爆炸 
end; 
 
{ TGem class } 
 
constructor TGem.Create(AGemKind: TGemKind); 
begin 
  inherited Create; 
   
  FGemKind := AGemKind; 
   
  FSpeed := 0; 
  FAttr := [saUndirectionalBitmap]; 
   
  Gem := Self; 
end; 
 
destructor TGem.Destroy; 
begin 
  Gem := nil; 
   
  inherited Destroy; 
end; 
 
function TGem.GetFrameMax: Integer; 
begin 
  Result := 0; // 只有一張 
end; 
 
// 根據寶物種類讀取圖形 
function TGem.GetFileName: string; 
const 
  FileNames: array[TGemKind] of string = 
    ('gemClock.bmp', 'gemHat.bmp', 'gemArrow.bmp', 'gemStar.bmp', 'gemBlow.bmp', 'gemApple.bmp'); 
begin 
  Result := AppDir + DR_IMAGES + FileNames[FGemKind]; 
end; 
 
// 坦克處理函式 (行動, 繪製, 釋放) 
 
procedure MoveTanks; 
var 
  I: Integer; 
begin 
  for I := 0 to Length(Tanks) - 1 do 
    Tanks[I].Move; 
end; 
 
procedure DrawTanks(Canvas: TCanvas; OnAir: Boolean); 
var 
  I: Integer; 
begin 
  for I := 0 to Length(Tanks) - 1 do 
    if OnAir = Tanks[I].FOnAir then 
      Tanks[I].Draw(Canvas); 
end; 
 
procedure FreeTanks; 
begin 
  while Length(Tanks) > 1 do 
    Tanks[1].Free; 
end; 
 
// 子彈處理函式 (行動, 繪製, 釋放) 
 
procedure MoveBullets; 
var 
  I: Integer; 
begin 
  for I := 0 to Length(Bullets) - 1 do 
    Bullets[I].Move; 
end; 
 
procedure DrawBullets(Canvas: TCanvas); // 繪製所有子彈 
var 
  I: Integer; 
begin 
  for I := 0 to Length(Bullets) - 1 do 
    Bullets[I].Draw(Canvas); 
end; 
 
procedure FreeBullets; 
begin 
  while Length(Bullets) > 0 do 
    Bullets[0].Free; 
end; 
 
procedure FreeBulletsForTank(ATank: TTank); 
var 
  I: Integer; 
begin 
  for I := 0 to Length(Bullets) - 1 do 
    if Bullets[I].FTank = ATank then 
      Bullets[I].FTank := nil; 
end; 
 
end.