📄 gameclass.pas
字号:
unit GameClass;
interface
uses SysUtils, Graphics, DirectDraw, Windows, Messages, ClassCallback, ObjFastBlt;
const
// MainWindow Class
APPNAME = 'APPLICATION';
// 全屏/窗口模式的窗口风格
FULLSCREEN_WINDOWSTYLE = WS_VISIBLE or WS_POPUP;
WINDOWMODE_WINDOWSTYLE = WS_VISIBLE or WS_SYSMENU or WS_MINIMIZEBOX or
WS_POPUP or WS_DLGFRAME or WS_CAPTION;
type
TGame = class(TObject)
private
FTerminated: Boolean; // 程序已终止(用于主窗口 Destroy)
FBackDxFastBlt: TDxFastBlt;
FDirectDraw: IDirectDraw7; // DirectDraw 句柄
FPrimarySurface: IDirectDrawSurface7; // 主表面
FBufferSurface: IDirectDrawSurface7; // 缓冲表面
FClipper: IDirectDrawClipper; // 裁剪器
FFullScreen: Boolean; // 全屏模式
FCallbackInstance: TCallbackInstance; // 窗口回调转换代码
FMainWindow: HWND; // 主窗口句柄
FClientRect: TRect; // 主客户区矩形(屏幕坐标,用于 Blt)
FActive: Boolean; // 窗口是否激活
FCaption: string; // 窗口标题
FWidth: Integer; // 客户区宽度
FHeight: Integer; // 客户区高度
FBackgroundColor: TColorRef; // 窗口背景色
FBackgroundBrush: HBrush;
FIsShowFPS: Boolean; // 背景画刷
procedure SetCaption(const Value: string);
procedure SetHeight(const Value: Integer);
procedure SetWidth(const Value: Integer);
procedure SetFullScreen(const Value: Boolean);
procedure SetBackgroundColor(const Value: TColorRef);
protected
// 注意: WndProc 不能为虚函数,详见 ClassCallback
function WndProc(HWND: HWND; uMsg: UINT; wParam: wParam;
lParam: lParam): LResult; stdcall;
function WMMouseMove(HWND: HWND; uMsg: UINT; wParam: wParam; lParam: lParam): LResult; virtual; abstract;
function WMLButtonDown(HWND: HWND; uMsg: UINT; wParam: wParam; lParam: lParam): LResult; virtual; abstract;
function WMLButtonUp(HWND: HWND; uMsg: UINT; wParam: wParam; lParam: lParam): LResult; virtual; abstract;
function WMRButtonDown(HWND: HWND; uMsg: UINT; wParam: wParam; lParam: lParam): LResult; virtual; abstract;
function WMRButtonUp(HWND: HWND; uMsg: UINT; wParam: wParam; lParam: lParam): LResult; virtual; abstract;
procedure OnIdle;
procedure RegisterMainWindowClass;
procedure CreateMainWindow;
procedure InitDirectDraw;
procedure FreeDirectDraw;
procedure UpdateBounds;
function ReloadAllSurfaceImages: Boolean; virtual; abstract;
function ProcessNextFrame: Boolean; virtual; abstract;
public
constructor Create(ACaption: string; AFullScreen: Boolean;
AWidth, AHeight: Integer; ABackgroundColor: TColorRef); virtual;
destructor Destory; virtual;
procedure Init;
procedure Run;
procedure Flip;
function ClearBufferSurface: Boolean;
function CreateSurfaceFromText(const AText: string; AFont: HFont = 0): IDirectDrawSurface7;
function CreateSurfaceFromBitmap(const ABitmapName: string; AColorKey: TColorRef): IDirectDrawSurface7;
property BackDxFastBlt: TDxFastBlt read FBackDxFastBlt;
property MainDirectDraw: IDirectDraw7 read FDirectDraw;
property PrimarySurface: IDirectDrawSurface7 read FPrimarySurface;
property BufferSurface: IDirectDrawSurface7 read FBufferSurface;
property FullScreen: Boolean read FFullScreen write SetFullScreen;
property MainWindow: HWND read FMainWindow;
property Caption: string read FCaption write SetCaption;
property Height: Integer read FHeight write SetHeight;
property Width: Integer read FWidth write SetWidth;
property BackgroundColor: TColorRef read FBackgroundColor write SetBackgroundColor;
end;
// 检查 DirectDraw 执行结果
procedure CheckResult(Result: HRESULT; Info: string);
implementation
procedure CheckResult(Result: HRESULT; Info: string);
var
F: TextFile;
begin
if Result <> DD_OK then
begin
// raise Exception.Create('DirectDraw Error');
// Halt;
FileMode := fmOpenWrite;
AssignFile(F, ChangeFileExt(ParamStr(0), '.err'));
Rewrite(F);
Writeln(F, Info);
CloseFile(F);
Halt;
end;
end;
{ TGame }
constructor TGame.Create(ACaption: string; AFullScreen: Boolean;
AWidth, AHeight: Integer; ABackgroundColor: TColorRef);
begin
FCaption := ACaption;
FFullScreen := AFullScreen;
FWidth := AWidth;
FHeight := AHeight;
FBackDxFastBlt := TDxFastBlt.Create;
FBackDxFastBlt.Width := FWidth;
FBackDxFastBlt.Height := FHeight;
FBackgroundColor := ABackgroundColor;
FBackgroundBrush := CreateSolidBrush(FBackgroundColor);
FIsShowFPS := True;
// 生成类回调函数
MakeCallbackInstance(FCallbackInstance, Self, @TGame.WndProc);
// 注册主窗口类
RegisterMainWindowClass;
// 初始化 DirectDraw
InitDirectDraw;
end;
destructor TGame.Destory;
begin
// 清除 DirectDraw 对象
FreeDirectDraw;
// 清除 背景画刷
DeleteObject(FBackgroundBrush);
FBackDxFastBlt.Free;
end;
procedure TGame.InitDirectDraw;
var
DDSD: TDDSurfaceDesc2;
begin
// 删除原来的窗口,由 WM_DESTROY 负责删除原 DirectDraw 对象
if IsWindow(FMainWindow) then DestroyWindow(FMainWindow);
// 创建主窗口
CreateMainWindow;
// 创建 FDirectDraw
CheckResult(DirectDrawCreateEx(nil, FDirectDraw, IID_IDirectDraw7, nil),
'DirectDrawCreateEx');
// 根据全屏或窗口模式创建表面
// 全屏模式
if FFullScreen then
begin
CheckResult(FDirectDraw.SetCooperativeLevel(FMainWindow, DDSCL_EXCLUSIVE or
DDSCL_FULLSCREEN), ' SetCooperativeLevel');
CheckResult(FDirectDraw.SetDisplayMode(FWidth, FHeight, 8, 0, 0),
'SetDisplayMode');
FillChar(DDSD, SizeOf(DDSD), #0);
DDSD.dwSize := SizeOf(DDSD);
DDSD.dwFlags := DDSD_CAPS { or DDSD_BACKBUFFERCOUNT};
// DirectX 例子中的写法
// DDSD.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or
// DDSCAPS_COMPLEX or DDSCAPS_3DDEVICE;
DDSD.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE {or DDSCAPS_FLIP or DDSCAPS_COMPLEX};
// DDSD.dwBackBufferCount := 1;
CheckResult(FDirectDraw.CreateSurface(DDSD, FPrimarySurface, nil),
'CreateSurface: FPrimarySurface');
// FillChar(DDSCaps, SizeOf(DDSCaps), #0);
// DDSCaps.dwCaps := DDSCAPS_BACKBUFFER;
// CheckResult(FPrimarySurface.GetAttachedSurface(DDSCaps, FBufferSurface),
// 'FPrimarySurface.GetAttachedSurface');
DDSD.dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;
DDSD.ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN or DDSCAPS_SYSTEMMEMORY; //or DDSCAPS_3DDEVICE;
DDSD.dwHeight := FHeight;
DDSD.dwWidth := FWidth;
CheckResult(FDirectDraw.CreateSurface(DDSD, FBufferSurface, nil),
'CreateSurface(CreateSurface(FBufferSurface in Window Mode)');
end
// 窗口模式
else begin
CheckResult(FDirectDraw.SetCooperativeLevel(FMainWindow, DDSCL_NORMAL),
'SetCooperativeLevel(FMainWindow, DDSCL_NORMAL)');
// TODO: 修正
{CheckResult(FDirectDraw.SetDisplayMode(1024, 768, 8, 0, 0), 修改 窗口模式不修改分辨率
'SetDisplayMode'); }
FillChar(DDSD, SizeOf(DDSD), #0);
DDSD.dwSize := SizeOf(DDSD);
DDSD.dwFlags := DDSD_CAPS;
DDSD.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE;
CheckResult(FDirectDraw.CreateSurface(DDSD, FPrimarySurface, nil),
'CreateSurface(FPrimarySurface in Window Mode)');
DDSD.dwFlags := DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH;
DDSD.ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN or DDSCAPS_SYSTEMMEMORY; //or DDSCAPS_3DDEVICE;
DDSD.dwHeight := FHeight;
DDSD.dwWidth := FWidth;
CheckResult(FDirectDraw.CreateSurface(DDSD, FBufferSurface, nil),
'CreateSurface(CreateSurface(FBufferSurface in Window Mode)');
CheckResult(FDirectDraw.CreateClipper(0, FClipper, nil),
'CreateClipper in Window Mode');
CheckResult(FClipper.SetHWnd(0, FMainWindow), 'FClipper.SetHWnd');
CheckResult(FPrimarySurface.SetClipper(FClipper), 'FPrimarySurface.SetClipper');
FClipper := nil;
end;
end;
procedure TGame.RegisterMainWindowClass;
var
AWndClass: TWndClassEx;
begin
FillChar(AWndClass, SizeOf(AWndClass), #0);
AWndClass.cbSize := SizeOf(AWndClass);
AWndClass.Style := 0;
AWndClass.lpfnWndProc := @FCallbackInstance;
AWndClass.cbClsExtra := 0;
AWndClass.cbWndExtra := 0;
AWndClass.hInstance := hInstance;
AWndClass.hIcon := LoadIcon(hInstance, 'MAINICON');
AWndClass.hIconSm := AWndClass.hIcon;
AWndClass.hCursor := LoadCursor(0, IDI_APPLICATION);
AWndClass.hbrBackground := FBackgroundBrush;
AWndClass.lpszMenuName := nil;
AWndClass.lpszClassName := APPNAME;
//TODO: exception handler
if RegisterClassEx(AWndClass) = INVALID_ATOM then
raise Exception.Create('Can not register window class!');
end;
procedure TGame.CreateMainWindow;
var
Style: Cardinal;
BorderX, BorderY, CaptionY: Integer;
ALeft, ATop, AWidth, AHeight: Integer;
begin
if FFullScreen then
begin
Style := FULLSCREEN_WINDOWSTYLE;
ALeft := 0;
ATop := 0;
AWidth := GetSystemMetrics(SM_CXSCREEN);
AHeight := GetSystemMetrics(SM_CYSCREEN);
end
else begin
Style := WINDOWMODE_WINDOWSTYLE;
BorderX := GetSystemMetrics(SM_CXDLGFRAME);
BorderY := GetSystemMetrics(SM_CYDLGFRAME);
CaptionY := GetSystemMetrics(SM_CYCAPTION);
AWidth := FWidth + BorderX * 2;
AHeight := FHeight + BorderY * 2 + CaptionY;
ALeft := (GetSystemMetrics(SM_CXSCREEN) - AWidth) div 2;
ATop := (GetSystemMetrics(SM_CYSCREEN) - AHeight) div 2;
end;
FMainWindow := CreateWindow(APPNAME, PChar(FCaption), Style,
ALeft, ATop, AWidth, AHeight, 0, 0, hInstance, nil);
// TODO: Exception
if not IsWindow(FMainWindow) then
raise Exception.Create('Can not create window!');
UpdateBounds;
end;
procedure TGame.Flip;
var
Result: HRESULT;
DDSD: TDDSurfaceDesc2;
//TDDSurfaceDesc
begin
// 检查指针合法性
// if (FPrimarySurface = nil) or (FBufferSurface = nil) then Exit;
// 全屏模式
// TODO: 为什么全屏模式下 flip 比 blt 更慢?
// if FFullScreen then
// Result := FPrimarySurface.Flip(nil, DDFLIP_WAIT)
// else
FillChar(DDSD, SizeOf(DDSD), #0);
DDSD.dwSize := SizeOf(DDSD);
if FBufferSurface.Lock(nil, DDSD, DDLOCK_WAIT, 0) = DD_OK then begin
FBackDxFastBlt.DrawOnSurface(DDSD, False); //FBackDxFastBlt画到 FBufferSurface
FBufferSurface.Unlock(nil);
end;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -