-Incorporating 2D elements- |
All the 3D scenes rendered, are quite unusable without a decent interface to present to the user. True, the interface itself could be a 3D scene, but I am talking about the norms, and not about changing convention. One of the most basic 2D elements is text. One could use wglUseFontBitmaps, but it is rather slow. One of the fastest ways to draw text onscreen is to use the 3D rasterizer to display texturemapped quads, each quad of which has a different character mapped onto it. An excellent tutorial on the subject is on NeHe's website. If you want to check it out, click here. In this tutorial, I will describe an architecture for rendering interface elements (and using them). |
First off, we all need a base class that all UI objects will use. I have called this not quite surprisingly, TUIElement. The declaration is given below. |
TUIClickEvent=procedure (Sender:TUIElement) of object; TUIElement=class private //Top,Left, Width and Height of the element fX,fY,fWidth,fHeight:integer; //Is the mouse over it? fHighlighted:boolean; //Frame Update stuff fFrameInterval,fLastTickCount:cardinal; //Enabled/Disabled fEnabled:boolean; //Click Event fOnClick:TUIClickEvent; //The name of the object fName:string; public constructor Create; //Desscendants can override Update to perform //Time keyed animation or whatever function Update(TickCount:cardinal):boolean;virtual; //All descendants MUST render themselves procedure Render;virtual;abstract; //Call this if you want to simulate //a click on the button procedure DoClick;virtual; //DUH!! property Name:string read fName write fName; property X:integer read fX write fX; property Y:integer read fY write fY; property Width:integer read fWidth write fWidth; property Height:integer read fHeight write fHeight; property Highlighted:boolean read fHighLighted write fHighlighted; property Enabled:boolean read fEnabled write fEnabled; property OnClick:TUIClickEvent read fOnClick write fOnClick; end; |
Two methods merit description. One is the Update method, and the other is DoClick. |
procedure TUIElement.DoClick; begin //If the click event handler is assigned, //then it is invoked if assigned(fOnClick) then fOnClick(Self); end;function TUIElement.Update(TickCount: cardinal): boolean; begin //Set result to false initially result:=false; //If the object is disabled, then dont update. //This means successors will also not Update since //all of them should check inherited result before //proceeding with their Update if not fEnabled then exit; if (TickCount-fLastTickCount)>=fFrameInterval then begin //fFrameInterval ticks have passed since the lase Update //Updation can now take place fLastTickCount:=TickCount; result:=true; end; end; |
Now that we are done with the base class, lets see how a basic rollover is implemented as a descendant. Basically, a rollover must do two things. Determine whether the mouse is on itself, and change the image accordingly, and respond to a click in its client area. Here comes the declaration. |
TRollOverImage=class(TUIElement) private //The texture that is not highlighted fBackTex, //The highlight texture fForeTex:TTexture; public constructor Create; destructor Destroy;override; procedure Render;override; property BackTex:TTexture read fBackTex write fBackTex; property ForeTex:TTexture read fForeTex write fForeTex; end; |
Looks pretty simple, since almost all the properties/methods required have been taken from the base class. We shall take a look at the Render method implementation as well, since to write subsequent classes we require knowlodge of how this is rendered. All this method does is to render the background quad, and if the Hilighted property is set, then it renders another quad on top of this one, using the foreground texture Alpha channel information to blend the two together. That translates to a TTexture.Transparency call or a glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) call. Take a look at the code now. |
fBackTex.Enable; fBackTex.Opaque; DrawQuad(fX,fY,fWidth,fHeight); if fHighlighted then begin fForeTex.Enable; fForeTex.Transparency; DrawQuad(fX,fY,fWidth,fHeight); end; |
From here, we can go anywhere. Take a look at TRolloverButton.Render. It's almost exactly the same as TRolloverImage. Pretty darn simple to modify, wouldnt you say! |
if Enabled then inherited Render else begin fDisabledTex.Enable; fDisabledTex.Opaque; DrawQuad(X,Y,Width,Height); end; |
I'm not going to explain TRolloverCheckBox. I leave that as an excercise to the reader. What I will do, however, is to explain another class that is crucial to the entire setup. |
Alright, we now have some interface elements. If we are going update each element separately whenever the mouse moved, or was clicked, then we'd have some extremely painful code on our hands. So, what we do is write another class called TUIManager. This class will be responsible for managing the objects once added. So, the mouse events have only to be given to the manger, and this will make sure it gets filtered to the element on which the event occurred. Another declaration? |
TUIManager=class private //The elements are maintained as a TList fElements:TList; public constructor Create; destructor Destroy;override; //Mouse move should be called when the mouse moves procedure MouseMove(X,Y:integer); |
That was pretty small, was'nt it? There! We're done. Now we can show some stuff on the screen so the user can make choices. Lucifer will have a lot more input classes, I just wanted to keep the tutorial simple. The tut sample is very spartan, so I'll sxplain it a bit. There are two interface elements, a button and a checkox (yeah, the round thingy). Checking the checkbox, disables the button and vice versa. Clicking on the button invokes a dialog. simple, huh! Its really late. I'm off to bed. Good thing todays Sunday. I ca nsleep all day! :) ZZZZZZZ |
>>Download the
tutorial source You'll also need, in case you dont have it already >>Download the Lucifer units (required) |