www.pudn.com > Tank坦克游戏代码.rar > main.pas
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
MMSystem, ExtCtrls, ComCtrls, Menus, StdCtrls, Sprite;
type
TGameStatus = (gsTitle, // 欢迎画面
gsPlaying, // 游戏进行中
gsSuccess, // 过关画面
gsOver, // GAME OVER
gsTerminate); // 程式即将关闭
TMainForm = class(TForm)
pnlView: TPanel;
pbxView: TPaintBox;
mnuMain: TMainMenu;
mnuGame: TMenuItem;
mnuExit: TMenuItem;
N1: TMenuItem;
mnuChooseLevel: TMenuItem;
mnuHelp: TMenuItem;
mnuAbout: TMenuItem;
mnuStartGame: TMenuItem;
N2: TMenuItem;
mnuRestart: TMenuItem;
mnuPrevLevel: TMenuItem;
mnuNextLevel: TMenuItem;
N3: TMenuItem;
tmrTitle: TTimer;
M1: TMenuItem;
mnuSuperMode: TMenuItem;
mnuUnlimitedBullet: TMenuItem;
N4: TMenuItem;
mnuDrawLayer1: TMenuItem;
mnuDrawLayer2: TMenuItem;
mnuDrawLayer3: TMenuItem;
mnuDrawLayer4: TMenuItem;
procedure pbxViewPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure mnuStartGameClick(Sender: TObject);
procedure mnuChooseLevelClick(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure mnuAboutClick(Sender: TObject);
procedure mnuExitClick(Sender: TObject);
procedure mnuRestartClick(Sender: TObject);
procedure mnuNextLevelClick(Sender: TObject);
procedure tmrTitleTimer(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure mnuSuperModeClick(Sender: TObject);
procedure mnuDrawLayerClick(Sender: TObject);
private
FGameStatus: TGameStatus; // 游戏状态
FLevelNo: Integer; // 目前关卡
FTank: TMyTank; // 主角物件
FETankUsed: Integer; // 本关卡已出现的敌方坦克
FScore: Integer; // 分数
FBackBitmap: TBitmap; // 用来做 dobule-buffering 的 bitmap
procedure DrawBackBitmap; // 绘制背景 bitmap
procedure UpdateView; // 更新游戏画面
procedure UpdateControlStatus;
// 产生敌方坦克及宝物
procedure CreateETank(Kind: Integer);
procedure CreateGem(Kind: Integer);
procedure GameLoop; // 游戏主回圈
// 在游戏画面中央或正上面绘出字串及外围方框
procedure DrawStatusBox(S: string; TopOrCenter: Boolean);
procedure SetGameStatus(Value: TGameStatus);
procedure SetLevelNo(Value: Integer);
property GameStatus: TGameStatus read FGameStatus write SetGameStatus;
property LevelNo: Integer read FLevelNo write SetLevelNo;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
uses util, tileunit, Mapunit, about, xFiles;
{$R *.DFM}
procedure TMainForm.UpdateView;
begin
DrawBackBitmap; // 先画在内部的 bitmap
pbxView.Canvas.Draw(0, 0, FBackBitmap); // 再拷贝到画面上
end;
procedure TMainForm.pbxViewPaint(Sender: TObject);
begin
UpdateView;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FTank := TMyTank.Create; // 产生主角物件
Tiles := TTiles.Create; // 产生图库物件
Map := tmap.Create; // 关卡地图
try
FTank.LoadBits; // 读入主角图形
Tiles.LoadFromFile(AppDir + FN_TILE_ARCHIVE); // 读入图库
except
on E: Exception do
begin
ShowMessage(E.message);
PostMessage(Handle, WM_CLOSE, 0, 0); // 以 asynchronous 方式关闭视窗
end;
end;
// 根据常数来调整 pnlView 及视窗的大小
pnlView.ClientWidth := WORLD_WIDTH;
pnlView.ClientHeight := WORLD_HEIGHT;
ClientWidth := pnlView.Width;
ClientHeight := pnlView.Height;
FBackBitmap := TBitmap.Create; // 缓冲用 bitmap
FBackBitmap.Width := WORLD_WIDTH;
FBackBitmap.Height := WORLD_HEIGHT;
// 预备工作都做好了, 进入 title 画面
GameStatus := gsTitle;
end;
// 先将画面绘制在缓冲用 bitmap, 再复制到视窗上
procedure TMainForm.DrawBackBitmap;
var
Str: string;
R : TRect;
begin
if mnuDrawLayer1.Checked then // 绘制地形层
Map.DrawMap(LAYER_TERR, FBackBitmap.Canvas)
else begin // 若没画地形层, 就涂黑
FBackBitmap.Canvas.Brush.Color := clBlack;
FBackBitmap.Canvas.FillRect(FBackBitmap.Canvas.ClipRect);
end;
if mnuDrawLayer2.Checked then // 绘制地形物层
Map.DrawMap(LAYER_TERRITEM, FBackBitmap.Canvas);
if mnuDrawLayer3.Checked then // 绘制物品层
Map.DrawMap(LAYER_ITEM, FBackBitmap.Canvas);
// 若有宝物, 画出宝物
if Assigned(Gem) then Gem.Draw(FBackBitmap.Canvas);
DrawTanks(FBackBitmap.Canvas, False); // 画出地面上所有坦克
DrawBullets(FBackBitmap.Canvas); // 画出子弹
if mnuDrawLayer4.Checked then // 绘制高地形物层
Map.DrawMap(LAYER_HITERRITEM, FBackBitmap.Canvas);
// 画出"空中"所有坦克
DrawTanks(FBackBitmap.Canvas, True);
with FBackBitmap.Canvas do
begin
Font.Color := clWhite;
Font.name := 'FixedSys';
Font.Style := [fsBold];
Font.Size := 12;
Brush.Style := bsClear;
case FGameStatus of
gsTitle: // 画出上面的标题大字及下方的作者名称
begin
R := Rect(0, 0, WORLD_WIDTH,
WORLD_HEIGHT - TextHeight('我'));
DrawText(Handle, '作者: 陈宽达', - 1, R,
DT_BOTTOM or DT_CENTER or DT_SINGLELINE);
DrawStatusBox('坦克大决战', True);
end;
gsOver: // Ouch, 军旗被干掉或主角死掉了
DrawStatusBox('任务失败', False);
else
begin
// 在右上角显示生命力及分数
{$IFDEF DEBUG}
Str := Format('(%d, %d) (%d, %d)',[FTank.X, FTank.Y,
FTank.tile_x, FTank.tile_y]);
{$ELSE}
Str := Format('装甲 %.2d 得分 %.4d',[FTank.HP, FScore]);
{$ENDIF}
TextOut(WORLD_WIDTH - TextWidth(Str) - 5, 5, Str);
// 在左上角显示关卡
Str := Format('任务 %d',[FLevelNo]);
TextOut(5, 5, Str);
end;
end;
// 过关画面
if FGameStatus = gsSuccess then
DrawStatusBox('任务成功 !!', False);
end;
end;
procedure TMainForm.SetGameStatus(Value: TGameStatus);
var
I: Integer;
begin
FGameStatus := Value;
// 根据新的游戏状态开关计时器
tmrTitle.Enabled := FGameStatus = gsTitle;
case FGameStatus of
gsTitle:
begin
LevelNo := 1; // 欢迎画面显示第一关地图
// 随机产生 MAX_TANK_ON_SCREEN 辆敌方坦克
for I := 1 to MAX_TANK_ON_SCREEN do
CreateETank(Random(5));
FTank.Visible := False; // 将自己坦克藏起来
FTank.Active := False; // 自己坦克不要动
end;
gsPlaying:
begin
UpdateControlStatus; // 更新标题列及其它控制项
UpdateView; // 更新游戏画面
GameLoop; // 进入游戏主回圈 (游戏进行中都一直在此回圈内)
end;
gsSuccess:
begin
end;
end;
if FGameStatus <> gsTerminate then // 使用者是否欲关闭视窗 ??
begin
UpdateControlStatus; // 更新标题列及其它控制项
UpdateView; // 更新游戏画面
end else
Close; // 关闭视窗
end;
procedure TMainForm.SetLevelNo(Value: Integer);
begin
Map.LevelNo := Value; // 载入关卡地图
FTank.X := Map.Role_X; // 将主角摆在关卡指定的位置
FTank.Y := Map.Role_Y;
FLevelNo := Value;
end;
procedure TMainForm.UpdateControlStatus;
const
StatusStr: array[TGameStatus] of string = ('Welcome', '', '任务成功 !!', 'Game Over', 'Terminating');
begin
if FGameStatus <> gsPlaying then
Caption := Format('%s - %s',[Application.Title, StatusStr[FGameStatus]])
else
Caption := Format('%s - 任务 %d',[Application.Title, FLevelNo]);
// 开关各控制项的致能状态
mnuRestart.Enabled := FGameStatus = gsPlaying;
mnuStartGame.Enabled := FGameStatus <> gsPlaying;
mnuPrevLevel.Enabled := (FGameStatus = gsPlaying) and (LevelNo > 1);
mnuNextLevel.Enabled := (FGameStatus = gsPlaying);
end;
// 绘出状态框框, 同时在里面写字
procedure TMainForm.DrawStatusBox(S: string; TopOrCenter: Boolean);
var
R : TRect;
TextSize: TSize;
begin
with FBackBitmap, FBackBitmap.Canvas do
begin
Font.Color := clWhite; // 雄壮的字形
Font.name := 'FixedSys';
Font.Style := [fsBold];
Font.Size := 20;
TextSize := TextExtent(S);
// first calculate the necessary rectangle
R := Rect(0, 0, TextSize.cx, TextSize.cy);
OffsetRect(R, (Width - TextSize.cx) div 2, (Height - TextSize.cy) div 2); // 将框框置于画面中央
if TopOrCenter then
begin
R.Top := 24; // 将框框摆在上方中间
R.Bottom := R.Top + TextSize.cy;
end;
InflateRect(R, 10, 10); // 描绘外框, 宽度为 10
Pen.Width := 4;
Pen.Color := clWhite;
Pen.Style := psSolid;
Brush.Color := clBlue;
Brush.Style := bsSolid;
Rectangle(R.Left, R.Top, R.Right, R.Bottom);
// 将字画出来, 置中对齐
DrawText(Canvas.Handle, PChar(S), Length(S), R, DT_CENTER or DT_SINGLELINE or DT_VCENTER);
end;
end;
procedure TMainForm.mnuStartGameClick(Sender: TObject);
begin
LevelNo := LevelNo;
GameStatus := gsPlaying;
end;
// 让使用者选择关卡
procedure TMainForm.mnuChooseLevelClick(Sender: TObject);
var
S: string;
begin
S := IntToStr(FLevelNo);
if InputQuery(Application.Title, 'Input Scenario No:', S) then
begin
LevelNo := StrToInt(S);
if GameStatus <> gsPlaying then
GameStatus := gsPlaying
else
PostMessage(0, WM_INIT_LEVEL, 0, 0); // 开始此关卡
end;
end;
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if FGameStatus = gsPlaying then // 注意, 只有游戏中状态, 键盘控制才有效
begin
case Key of
VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT:
begin
case Key of
VK_UP: FTank.Direction := drUp; // 向上走
VK_DOWN: FTank.Direction := drDown; // 向下走
VK_LEFT: FTank.Direction := drLeft; // 向左走
VK_RIGHT: FTank.Direction := drRight; // 向右走
end;
FTank.Active := True; // 主角开始"动"
end;
VK_SPACE:
begin
FTank.FireBullet; // 发射子弹, 咻 ~~
end;
end;
end;
end;
procedure TMainForm.mnuAboutClick(Sender: TObject);
begin
with TAboutBox.Create(self) do
try
ShowModal;
finally
Free;
end;
end;
procedure TMainForm.mnuExitClick(Sender: TObject);
begin
Close;
end;
procedure TMainForm.mnuRestartClick(Sender: TObject);
begin
LevelNo := LevelNo;
PostMessage(0, WM_INIT_LEVEL, 0, 0); // 重新开始此关卡
end;
procedure TMainForm.mnuNextLevelClick(Sender: TObject);
begin
case (Sender as TComponent).Tag of
0: LevelNo := LevelNo - 1;
1: LevelNo := LevelNo + 1;
end;
PostMessage(0, WM_INIT_LEVEL, 0, 0); // 开始新关卡
end;
// 欢迎画面时使用的计时器, 每 10 毫秒呼叫一次
procedure TMainForm.tmrTitleTimer(Sender: TObject);
begin
// 移动坦克及子弹
MoveTanks;
MoveBullets;
UpdateView; // 更新画面
end;
// 产生敌方坦克
procedure TMainForm.CreateETank(Kind: Integer);
type
TETankClass = class of TETank; // metaclass
const
ETankClasses: array[0..4] of TETankClass =
(TETank1, TETank2, TETank3, TETank4, TETank5);
var
ETank: TETank;
begin
Inc(FETankUsed); // 递增此关卡已产生的敌方坦克
// 由 metaclass 变数建立新的敌方坦克
ETank := ETankClasses[Kind].Create;
ETank.LoadBits;
end;
// 产生宝物
procedure TMainForm.CreateGem(Kind: Integer);
begin
Gem := TGem.Create(TGemKind(Kind));
Gem.LoadBits;
Gem.RandomPosition; // 随意摆置
// 宝物出现 30 秒后自动消失
SetTimer(Handle, TIMER_ID_GEM, 30 * 1000, nil);
end;
procedure TMainForm.GameLoop;
var
bStopEnemy: Boolean; // 是否暂停敌方行动
// 关卡开始前的初始动作
procedure InitLevel;
begin
FScore := 0;
FETankUsed := 0;
// 主角出现, 不动
FTank.Visible := True;
FTank.Active := False;
FTank.Speed := MYTANK_DEFAULT_SPEED;
FTank.ResetStatus;
{$IFDEF DEBUG} // 测试时让主角不死身
FTank.MaxBulletNum := 100;
FTank.HP := 100;
{$ENDIF}
// 按目前选项设定坦克状态
FTank.SuperMode := mnuSuperMode.Checked; // 无敌模式
if mnuUnlimitedBullet.Checked then // 能够连发子弹
FTank.MaxBulletNum := 100
else
FTank.MaxBulletNum := 1;
// 将现存的子弹, 敌方坦克及宝物释放掉
FreeBullets;
FreeTanks;
if Assigned(Gem) then Gem.Free;
// 不要暂停敌人
bStopEnemy := False;
// 将所有计时器取消
KillTimer(Handle, TIMER_ID_GEM);
KillTimer(Handle, TIMER_ID_GEM_HAT);
KillTimer(Handle, TIMER_ID_GEM_HAT);
KillTimer(Handle, TIMER_ID_GEM_ARROW);
end;
var
Msg : TMsg;
iStopTime: Integer;
begin
InitLevel; // 初始化
// 游戏回圈
while (FGameStatus = gsPlaying) do
begin
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then // 取得讯息
begin
case Msg.message of
WM_QUIT: // 程式结束, 离开游戏回圈
begin
FGameStatus := gsTerminate;
Break;
end;
WM_DESTROY_OBJECT: // 要求释放某物件
begin
if TObject(Msg.WPARAM) = FTank then // 若死掉的是主角
begin
FTank.Visible := False;
FGameStatus := gsOver; // Game over 啰 ~~
Break; // 离开游戏回圈
end;
// 若被摧毁的是敌方坦克, 则 Msg.LPARAM 代表其分数
Inc(FScore, Msg.LPARAM);
// 将坦克与与其有关系的子弹解除关系
FreeBulletsForTank(TTank(Msg.WPARAM));
// 释放物件
TObject(Msg.WPARAM).Free;
// 若敌方坦克全部出现且死光光了, 则到下一关去
if (FETankUsed = MAX_ETANK_PER_SCENARIO) and (Length(Tanks) = 1) then
begin
// 这里应该加一些过场画面...
LevelNo := LevelNo + 1;
InitLevel;
end;
end;
WM_GAMEOVER: // 军旗被打掉了, 游戏结束
begin
FGameStatus := gsOver;
Break;
end;
WM_SPECIAL_CONDITION: // 进入特殊状态
begin
iStopTime := 10; // 特殊状态预设有效时间十秒钟
case Msg.WPARAM of
TIMER_ID_GEM_CLOCK: bStopEnemy := True;// 暂停敌人行动
TIMER_ID_GEM_HAT: FTank.SuperMode := True; // 无敌
TIMER_ID_GEM_ARROW:
begin // 在军旗周围摆上打不破的铁墙, 且修复它
Map[LAYER_TERRITEM, 5, 11].TileNo := 3;
Map[LAYER_TERRITEM, 5, 11].DisposeBreakMap;
Map[LAYER_TERRITEM, 6, 11].TileNo := 3;
Map[LAYER_TERRITEM, 6, 11].DisposeBreakMap;
Map[LAYER_TERRITEM, 7, 11].TileNo := 3;
Map[LAYER_TERRITEM, 7, 11].DisposeBreakMap;
Map[LAYER_TERRITEM, 5, 12].TileNo := 3;
Map[LAYER_TERRITEM, 5, 12].DisposeBreakMap;
Map[LAYER_TERRITEM, 7, 12].TileNo := 3;
Map[LAYER_TERRITEM, 7, 12].DisposeBreakMap;
iStopTime := 20; // 铁墙持续二十秒
end;
end;
// 设定计时器
SetTimer(Handle, Msg.WPARAM, iStopTime * 1000, nil);
end;
WM_TIMER: // 计时器时间到 (特殊状态时间到)
begin
KillTimer(Handle, Msg.WPARAM); // 取消计时器
case Msg.WPARAM of
TIMER_ID_GEM: // 宝物摆够久了, 还不吃, 拿掉
if Assigned(Gem) then Gem.Free;
TIMER_ID_GEM_CLOCK: bStopEnemy := False;
TIMER_ID_GEM_HAT:
FTank.SuperMode := mnuSuperMode.Checked;
TIMER_ID_GEM_ARROW: // 在军旗周围摆回砖墙
begin
Map[LAYER_TERRITEM, 5, 11].TileNo := 2;
Map[LAYER_TERRITEM, 6, 11].TileNo := 2;
Map[LAYER_TERRITEM, 7, 11].TileNo := 2;
Map[LAYER_TERRITEM, 5, 12].TileNo := 2;
Map[LAYER_TERRITEM, 7, 12].TileNo := 2;
end;
end;
end;
WM_INIT_LEVEL:
begin
InitLevel; // 关卡重新开始
end;
end;
// 正常的讯息处理程序
TranslateMessage(Msg);
DispatchMessage(Msg);
end else if Focused then // 若视窗拥有输入焦点才动作
begin
// 若此关卡及目前敌方坦克都没达到上限,
// 则按照乱数"可能"出现敌方坦克
if (FETankUsed < MAX_ETANK_PER_SCENARIO) and
(Length(Tanks) < MAX_TANK_ON_SCREEN) and
(Random < PROBAB_ETANK_BORN) then
CreateETank(Random(5)); // 五种坦克任选一种
// 若目前没有宝物, 则按照乱数"可能"出现宝物
if not Assigned(Gem) and (Random < PROBAB_GEM_BORN) then
CreateGem(Random(6)); // 六种宝物任选一种
// 移动我方坦克及/或敌方坦克
if bStopEnemy then
FTank.Move
else
MoveTanks;
// 移动子弹
MoveBullets;
// 更新画面
UpdateView;
end else begin
// 若视窗没有取得输入焦点, 则将控制权交给其它执行绪,
// 直到有讯息进来
WaitMessage;
end;
end;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
// 释放所有资源
FBackBitmap.Free;
FTank.Free;
Tiles.Free;
Map.Free;
end;
procedure TMainForm.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// 注意, 只有游戏中状态, 键盘控制才有效
if FGameStatus = gsPlaying then
begin
case Key of // 放开键盘, 我方坦克就停止动作
VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT: FTank.Active := False;
end;
end;
end;
procedure TMainForm.mnuSuperModeClick(Sender: TObject);
begin
with (Sender as TMenuItem) do
begin
Checked := not Checked;
case Tag of
0: FTank.SuperMode := Checked; // 无敌模式
1: if Checked then // 能够连发子弹
FTank.MaxBulletNum := 100
else
FTank.MaxBulletNum := 1;
end;
end;
end;
procedure TMainForm.mnuDrawLayerClick(Sender: TObject);
begin
(Sender as TMenuItem).Checked := not (Sender as TMenuItem).Checked;
end;
initialization
Randomize; // 重播乱数种子
end.