转一篇火人论坛那边的一份学习文档,我简单排一下版,希望对入门者有帮助。
感谢China Yang,这份文档也帮助我快速入了门。
和我一起学
Asphyre Sphinx Framework v1.0.0
China Yang
Http://www.huosoft.com/bbs/ : ID:Installxp
当我准备用业余时间开始写这段文字,当你准备在Library Path里添上Asphyre的Source文件夹。那么我们一起学习的旅程就开始了,我会把我学习的过程写在这里,希望有一天,你也把你的学习过程形成文字,和其他人一起分享。
这些文字是面向初学者的,所以文字拖沓是必然的,请各路Delphi大仙们绕行。
我们的运行环境是:Delphi 2006。请将你的Asp Sphinx FrameWord v1.0.0 目录下的 Source文件夹添加到:
菜单 -> Tools -> Options -> library-Win32 –> Library Path。
我们要在屏幕上用Asphyre 画点。
在此之前你需要了解的基础知识:基本控件的“过程,方法和事件”。这些是必备的知识,如果你不清楚这些,请找一本DELPHI 基础书参照学习。
下面我们开始正式进入ASPHYRE学习,从现在开始我和你一样,一步一步探索。
在程序的开始,我们需要一个事件过程。
请使用Design,我们可以看到设计期的窗口。
我们在空窗口上鼠标左键连续点击两次,DELPHI将智能的帮我们建立下面这个事件:
procedure TForm1.FormCreate(Sender: TObject);
FormCreate 是窗口建立时必然运行的一个过程,你在这个过程所写的代码,将随着窗口的建立而被执行。我们将在这里Create我们程序需要的类和其他一些相关的东西。
FormCreate内的第一句代码:
ReportMemoryLeaksOnShutdown:= DebugHook <> 0;
{ 以下关于第一句代码的相关知识: }
DELPHI引入了FastMM替换掉早期的内存管理器。FastMM是一个开源的项目,你可以在互联网上找到它,在很长一段时间里有经验的程序员使用它来检测程序的内存泄漏。它可以帮助DELPHI IDE更快,更稳定的运行。当然,即使你不将ReportMemoryLeaksOnShutdown开关打开,FastMM也是在工作的,只是当你的程序出现内存问题时,DELPHI 将不会提示你。所以一个好的习惯就是将它打开,它只会帮助你更好的工作。也可以像下面这样:
ReportMemoryLeaksOnShutdown := True;
当程序是在IDE里运行时 DebugHook = 1 ;当程序在IDE外独立运行时 DebugHook = 0;
如果你是一个刚刚开始学习编程的爱好者,下面的方式可能会让你迷惑,不过不要紧。
ReportMemoryLeaksOnShutdown:= DebugHook <> 0;
( DebugHook <> 0 ) 是一个表达式,当DebugHook是1的时候。( DebugHook <> 0 )将返回一个False (假值)给ReportMemoryLeaksOnShutdown。如果DebugHook 的值是0的时候,( DebugHook <> 0 )将返回一个真值。
言归正传,我们要开始在FormCreate里定义一个DisplaySize看起来像下面这样
Procedure TForm1.FormCreate (Sender: TObject); Var DisplaySize: TPoint2px; { 我们定义的地方 } Begin
为了我们能正常的使用TPoint2px,我们要引入Vectors2px单元。所以我们要在Implementation 关键字下面添加第一个引入单元,看起来像下面这样
… …
Implementation
Uses
Vectors2px;
… …
当我们按住Ctrl键用鼠标去点击Vectors2px的时候,我们就会追踪到某个类和某个类型定义的起始处。
DisplaySize被定义成TPoint2px记录(Record);我们可以追踪到 Vectors2px单元第四十五行。
PPoint2px = ^TPoint2px; TPoint2px = record x, y: Integer;
关于 Record 请参看你手头的工具书,记录(Record);
当然DisplaySize:= Point2px(ClientWidth, ClientHeight);也可以换一个写法,例如下面这样:
DisplaySize.x := ClientWidth; { ClientWidth 窗口的宽 }
DisplaySize.y := ClientHeight; { ClientWidth 窗口的高 }
上面的句子似乎更容易理解一点,DisplaySize里将保存我们窗口的大小,我们后面会用到它。
接下来,我们还要再引入一个单元AsphyreFactory,该单元保存着接口信息.
因为我们的第一个目标并不复杂,所以DirectX7接口足够我们使用,当然我们也可以使用DirectX8, DirectX9;
… …
Implementation
Uses
Vectors2px, AsphyreFactory;
… …
添加完单元,我们的代码看起来是上面这个样子。接下来,我们要告诉Asphyre,我们要使用的DirectX版本。
Factory.UseProvider(idDirectX7);
在我们的第一个目标里,我们不会去涉及Asphyre的细节问题,那不便于我们学习,我们暂时只要知道,Factory.UseProvider可以让Asphyre在某个DirectX版本下工作。其实我暂时也不知道Factory.UseProvider的细节,完全是靠猜的,呵呵。
当我们将 Factory.UseProvider(idDirectX7);添加到DisplaySize.y := ClientHeight;的后面,我们会发现idDirectX7是错误的。所以我们要找到idDirectX7的定义。在DX7Providers单元的43行,idDirectX7被定义成常量。idDirectX7 = $10000700; $代表16进制操作符,当然你要原意的话,你也可以直接在括号内使用$10000700,不过当你再阅读的时候,不会容易读懂,假设你记不住的话。
在这里我们使用AsphyreFactory的定义。具体为什么要使用$10000700,你可以查找MSDN里面的详细说明,当我们在Windows下编写程序的时候,我们会经常使用msdn来查找一些windows特有的声明。
为了使用idDirectX7,我们又要引入一个新单元,DX7Providers单元。单元引用现在看起来应该是这样:
uses Vectors2px, AsphyreFactory, DX7Providers;
接下来我们要引入两个重要的设备:
GameDevice : TAsphyreDevice = nil; GameCanvas : TAsphyreCanvas = nil;
TAsphyreDevice和TAsphyreCanvas,它们所在的单元和Asphyre旧版本不同。分别在AbstractDevices, AbstractCanvas单元。
我们将在 Var Form1:TForm1下面声明它们,当然你可以在其它允许声明的位置声明它们。我们的声明是下面这个样子。
var Form1: TForm1; GameDevice : TAsphyreDevice = nil; GameCanvas : TAsphyreCanvas = nil;
请注意,在Form1:TForm1下面。这样,我们就在要程序的最开始位置引入单元,看起来像下面这个样子。
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, AbstractDevices, AbstractCanvas;
如果你是一个刚要进入Delphi的入门者,你可能会很可怪,为什么单元的引入位子在上面也有,在下面也有。很烦的,这个问题,你先不要考虑,讨论这个问题会偏离我们第一个目标的主题很远。所以你先试着接受它,在以后很长一段学习DELPHI的日子里,总有一天,我们会搞明白它们的每一个细节,只要你有足够的耐心学习下去。
言归正传,我假设你有一定的计算机基础知识,那么我们要说明上面两个设备的关系。
TAsphyreDevice,你可以把它想象成驱动设备,它是我们跟DirectX沟通的桥,所有我们要使用的DirectX都要在TAsphyreDevice里实现,它是我们使用DirectX的基础。
TAsphyreCanvas,画布,我们要在它上面涂鸦,当然不是用笔,而是用我们的代码。我们使用TAsphyreCanvas涂鸦的作品,最终会在窗口上显示出来。
没有TAsphyreDevice,我们的TAsphyreCanvas将不知道怎样将你的作品通过哪里才能让你看到。
由于我们是以类(Class)的方式使用TAsphyreDevice,TAsphyreCanvas,而不是我们常用的控件方式,所以我们在使用前一定要Create(建立)它们。
也许你还在使用控件搭积木的方式使用Delphi,也许你对类(Class)完全不了解。这些都没关系,从现在开始我们将一点一点学习和使用它们。随着越来越熟悉,你会发现潜藏在控件图标下的世界更精彩,从现在开始,你需要去补充类(Class)的知识,你需要找一本基础书去了解,什么是类、抽象类、类的实例化。它们可以帮助你在Delphi的世界里走的更远。
当然这完全不影响你随着我继续学习,你完全可以找一本Delphi基础书先偷偷的充电。
又说远了,继续回到我们的主题上来。我们将在下面的代码里Create TAsphyreDevice和TAsphyreCanvas。
procedure TForm1.FormCreate(Sender: TObject); Var DisplaySize :TPoint2px; begin ReportMemoryLeaksOnShutdown:= DebugHook <> 0; { DisplaySize := Point2px(ClientWidth, ClientHeight); 也可以换一种写法 } DisplaySize.x := ClientWidth; DisplaySize.y := ClientHeight; Factory.UseProvider(idDirectX7); GameDevice:= Factory.CreateDevice(); GameCanvas:= Factory.CreateCanvas();
上面两句GameDevice和GameCanvas,跟据前面的声明,我们将Create它们。当然你也可以将GameDevice和GameCanvas,换成你习惯的名子,但是同时需要修改var Form1: TForm1; 下面的关于GameDevice和GameCanvas的声明。
是不是有些累了,可以试当的休息一下。在Main的单元里有完整的代码,供对照着阅读。
我们下面要做的工作(Work)是要设置GameDevice,也就是TAsphyreDevice。在下面,我们是这样设置的:
GameDevice.WindowHandle:= Self.Handle; GameDevice.Size := DisplaySize; GameDevice.Windowed:= True; GameDevice.VSync := False;
Handle句柄,句柄这个东西不是Delphi生产出来的,而是Windows生产出来的。每个一控件,窗口,设备,都有一个句柄。除了使用名子访问外,还可以用句柄访问。你可以把Handle理解成数字化的名子,这个名子是唯一的,不重复的。
Self :自己。在这里 Self就是我们程序的窗口。我们希望所有的绘画都显示在我们的窗口上。所以我们把自己窗口的句柄告诉TAsphyreDevice。
就是这个样子,GameDevice.WindowHandle:= Self.Handle;
告诉TAsphyreDevice,我们窗口的大小 GameDevice.Size := DisplaySize;
告诉TAsphyreDevice,我们使用窗口模式,而不是全屏幕模式。
告诉TAsphyreDevice,是否打开垂直同步。啥是垂直同步?这个问题很高深。我就不多说了,请参看http://bbs.mydrivers.com/archiver/tid-265698.html,如果还是不同,请在Http://www.huosoft.com/bbs/论坛上提出,我将详细的回答你。
EventDeviceCreate.Subscribe(OnDeviceCreate, 0);
是我们在设置完成后需要写下的句子。这个句子和旧版本不同。
这里EventDeviceCreate.Subscribe将激活一个过程,你在这个过程里可以读取你的资源,然后确定是否让初始成功。
OnDeviceCreate ,我以经用红字标出,在上面的句子里。我们可以看到,EventDeviceCreate.Subscribe将通过OnDeviceCreate调用我们的过程。
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } procedure OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean);
从上面我们可以看到我们声明的过程是这个样子:
procedure OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean);
你可能会想到,为什么,一定要是这种参数方式,我们是不是可以换一种参数方式?在EventProviders单元的22行,Asphyre定义了这个过程的形参方式,如果你需要换一种方式,就要对Asphyre本身进行修改,如果你现在还没有那个能力,那么还是老老实实的跟着我断续学习。
单元 EventProviders.pas 第22行;
TEventCallback = procedure(Sender: TObject; Param: Pointer; var Handled: Boolean) of object;
由于我们的第一个目标是在屏幕上画一个点,所以我们不需要装载任何资料,所以我们的过程实现,看起来是下面这个样子。
procedure TForm1.OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean); var Success: Boolean; begin Success:= PBoolean(Param)^; PBoolean(Param)^:= Success; end;
我们把Param原原本本的再传回去,不做修改,如果我们有资源在这里装载失败的话,修改Param指向的地址。在我们第一目标中,我们不需要它。
回到 Form1.Create 过程 我们要在EventDeviceCreate.Subscribe(OnDeviceCreate, 0);下面正式初始化TAsphyreDevice
if (not GameDevice.Initialize()) then begin ShowMessage('Failed to initialize Asphyre device.'); Application.Terminate(); Exit; end;
细节先不要去思考,你先把它当成固定格式,以后我们会讨论,当然是在深入以后,才会讨论TAsphyreDevice的细节。
接下来是建立我们的计时器:
Timer.OnTimer := TimerEvent; Timer.OnProcess:= ProcessEvent; Timer.Speed := 60.0; Timer.MaxFPS := 4000; Timer.Enabled := True;
Timer的声明是在AsphyreTimer单元里,所以我们要引入AsphyreTimer单元。引入后是下面这个样子:
implementation uses Vectors2px, AsphyreFactory, DX7Providers, AsphyreTimer;
我们先简单的看一下计时器的设置。
Timer.OnTimer := TimerEvent;
我们要在TimerEvent事件过程里执行我们的绘画代码。后面我们要定义TimerEvent过程和实现这个过程。
Timer.OnProcess:= ProcessEvent;
我们要在ProcessEvent事件过程里放一个记数器。在我们第一目标中,这个记数器没什么作用。在以后的以后,我们会用到它。
Timer.Speed := 60.0;
计时器的速度,或者叫响应间隔也行,取决于你将来的理解,以后会慢慢理解的,别着急。
Timer.MaxFPS := 4000;
最大刷新,当真正执行一个大绘画时,基本达不到。
Timer.Enabled := True;
当这一句执行的时候,计时器将正式启动。
如果你不设置Timer.Speed,MaxFPS,那么它们的默认值将是:
Speed := 60.0; MaxFPS:= 100;
接下来我们要做的工作是 Timer.OnTimer := TimerEvent;我们要声明一个TimerEvent事件供计时器调用的时候使用。
private { Private declarations } GameTicks: Integer; procedure OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean); procedure TimerEvent(Sender: TObject);
看起来是上面这个样子,你需要在private里声明TimerEvent。然后在过程后面的分号处按键盘上的 Ctrl + Shift +C 转到过程的实现部分。
procedure TForm1.TimerEvent(Sender: TObject); begin GameDevice.Render(RenderEvent, $000000); Timer.Process(); end;
说明:
GameDevice.Render(绘画事件过程,背影色);
RenderEvent 我们要绘画的事件。
$000000 为黑色。
Timer.Process(); 调用 Timer.OnProcess:= ProcessEvent; 我们需要定义一个ProcessEvent事件过程,在里面实现一个简单的计数器,虽然在我们第一目标里没什么用,但以后的以后总会用到。
所以现在要声明ProcessEvent事件,声明看起来和下面差不多:
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } GameTicks: Integer; procedure OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean); procedure TimerEvent(Sender: TObject); procedure ProcessEvent(Sender: TObject);
上面最后一行就是我们对ProcessEvent的声明,你别忘了,顺手在{ Private declarations }下面,将GameTicks: Integer;写上,这个变量就是一个简单的记数器了。
在ProcessEvent过程后面的分号处Ctrl + Shift +C转到实现部分,要写成下面这个样子:
procedure TForm1.ProcessEvent(Sender: TObject); begin Inc(GameTicks); end;
GameDevice.Render画完一次就会调用Timer.Process(); 在一定的条件下,Timer.Process(),会在内部调用ProcessEvent事件过程,进行计数器累加操作。
Inc 等同于 GameTicks = GameTicks +1;但是inc更快。
最后,我们要把RenderEvent事件声明完成,并实现它。
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } GameTicks: Integer; procedure OnDeviceCreate(Sender: TObject; Param: Pointer; var Handled: Boolean); procedure TimerEvent(Sender: TObject); procedure ProcessEvent(Sender: TObject); procedure RenderEvent(Sender: TObject);
看上面最后一句,这是我们最终要绘画过程的声明,我们在分号部分Ctrl + Shift +C 转到过程的实现部分。
然后完成实现部分:
procedure TForm1.RenderEvent(Sender: TObject); begin GameCanvas.PutPixel(Point(10,10),$FFFFFFFF); end;
忙活了半天的时间,就为了中间的一句话,
GameCanvas.PutPixel(Point(10,10),$FFFFFF);
我们终于用到GameCanvas了,画布,还记得我们前面声明和建立的画布了么??
PutPixel画点,我们要在画布上画点,Point(x,y)在哪个位置画呢?$FFFFFF,我们要画的点是白色的。$FFFFFF 白色。
这篇文字希望会对一个入门者有帮助,China Yang,本次就到这里,休息,休息~~
demo下载