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.