550 lines
13 KiB
ObjectPascal
550 lines
13 KiB
ObjectPascal
unit UGame;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, sysutils, BGRAGraphics, BGRABitmap, BGRABitmapTypes,
|
|
BGRAOpenGL, Controls;
|
|
|
|
const
|
|
FrameDurationMs = 15;
|
|
|
|
type
|
|
TTux = class;
|
|
|
|
{ TGameContext }
|
|
|
|
TGameContext = class
|
|
protected
|
|
TexturesLoaded: boolean;
|
|
LeftKey,RightKey,UpKey: boolean;
|
|
texWalking,texGround: IBGLTexture;
|
|
tux,smallTux: TTux;
|
|
elapsedMs: single;
|
|
sun: IBGLTexture;
|
|
font, bubbleFont: IBGLRenderedFont;
|
|
CenterOnSmallTux: boolean;
|
|
infoTextAnimTime: single;
|
|
procedure AddGround(x,y,w: single);
|
|
function FindGround(x: single; var y: single): boolean;
|
|
|
|
public
|
|
FPS: integer;
|
|
constructor Create(ACenterOnSmallTux: boolean);
|
|
destructor Destroy; override;
|
|
|
|
procedure LoadTextures({%H-}ctx: TBGLContext);
|
|
procedure UnloadTextures({%H-}ctx: TBGLContext);
|
|
procedure Render(ctx: TBGLContext);
|
|
procedure Elapse(ctx: TBGLContext; ms: single);
|
|
|
|
procedure MouseDown({%H-}Button: TMouseButton; {%H-}X,{%H-}Y: integer);
|
|
procedure MouseMove({%H-}X,{%H-}Y: integer);
|
|
procedure MouseUp({%H-}Button: TMouseButton);
|
|
procedure KeyDown(var Key: Word; {%H-}Shift: TShiftState);
|
|
procedure KeyUp(var Key: Word; {%H-}Shift: TShiftState);
|
|
end;
|
|
|
|
{ TTextBubble }
|
|
|
|
TTextBubble = class
|
|
protected
|
|
FText: string;
|
|
FFont: IBGLFont;
|
|
public
|
|
constructor Create(AText: string; AFont: IBGLFont);
|
|
procedure Draw(AXCenter, AYBottom: single);
|
|
end;
|
|
|
|
{ TTux }
|
|
|
|
TTux = class(TBGLSprite)
|
|
Speed: TPointF;
|
|
LookingLeft: boolean;
|
|
Context: TGameContext;
|
|
Autoplay: boolean;
|
|
Parent: TTux;
|
|
OnTheGround: boolean;
|
|
Bubble,BubbleTooFar: TTextBubble;
|
|
BubbleTime: integer;
|
|
ShowBubbleTooFar: boolean;
|
|
goRight,goLeft,goUp: boolean;
|
|
waiting: integer;
|
|
JumpStrength: single;
|
|
procedure OnInit; override;
|
|
procedure OnDraw; override;
|
|
procedure OnTimer; override;
|
|
constructor Create(ATexture: IBGLTexture; AContext: TGameContext);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
{ TSmallTux }
|
|
|
|
TSmallTux = class(TTux)
|
|
procedure OnInit; override;
|
|
procedure OnTimer; override;
|
|
end;
|
|
|
|
{ TGround }
|
|
|
|
TGround = class(TBGLSprite)
|
|
constructor Create(ATexture: IBGLTexture; AX,AY: Single; AFrame: integer);
|
|
procedure OnInit; override;
|
|
end;
|
|
|
|
var
|
|
ResourceDir : string;
|
|
|
|
implementation
|
|
|
|
uses LCLType;
|
|
|
|
{ TTextBubble }
|
|
|
|
constructor TTextBubble.Create(AText: string; AFont: IBGLFont);
|
|
begin
|
|
FText := AText;
|
|
FFont := AFont;
|
|
end;
|
|
|
|
procedure TTextBubble.Draw(AXCenter, AYBottom: single);
|
|
const horizMargin = 6; vertMargin = 4;
|
|
var tw,x,y,tx,ty: integer;
|
|
begin
|
|
tw := round(FFont.TextWidth(FText));
|
|
tx := tw+horizMargin*2;
|
|
ty := round(FFont.TextHeight(FText))+2*vertMargin;
|
|
x := round(AXCenter)-tx div 2;
|
|
y := round(AYBottom)-ty;
|
|
BGLCanvas.RoundRect(x,y,x+tx,y+ty,12,12,BGRABlack, BGRA(255,255,250));
|
|
FFont.SetGradientColors(CSSBlack,CSSBlack,CSSDodgerBlue,CSSDodgerBlue);
|
|
FFont.TextOut(x+horizMargin,y+vertMargin,FText, taLeftJustify, tlTop, BGRABlack);
|
|
FFont.GradientColors := false;
|
|
end;
|
|
|
|
{ TSmallTux }
|
|
|
|
procedure TSmallTux.OnInit;
|
|
begin
|
|
inherited OnInit;
|
|
Autoplay:= true;
|
|
H := H*0.75;
|
|
W := W*0.75;
|
|
LookingLeft := true;
|
|
Bubble := TTextBubble.Create('Where are my parents?',Context.bubbleFont);
|
|
BubbleTooFar := TTextBubble.Create('Please stay close to me!',Context.bubbleFont);
|
|
//JumpStrength := 4;
|
|
end;
|
|
|
|
procedure TSmallTux.OnTimer;
|
|
begin
|
|
inherited OnTimer;
|
|
if Parent = nil then
|
|
begin
|
|
if sqr(X-Context.tux.X)+sqr(Y-Context.tux.Y) < sqr(50) then
|
|
begin
|
|
Parent := Context.tux;
|
|
FreeAndNil(Bubble);
|
|
Bubble := TTextBubble.Create('Hey my parent!',Context.bubbleFont);
|
|
BubbleTime := 400;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TGameContext }
|
|
|
|
constructor TGameContext.Create(ACenterOnSmallTux: boolean);
|
|
begin
|
|
LeftKey := false;
|
|
RightKey := false;
|
|
UpKey := false;
|
|
TexturesLoaded:= false;
|
|
CenterOnSmallTux:= ACenterOnSmallTux;
|
|
infoTextAnimTime := 0;
|
|
end;
|
|
|
|
destructor TGameContext.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TGameContext.LoadTextures({%H-}ctx: TBGLContext);
|
|
var sunBmp: TBGLBitmap;
|
|
begin
|
|
if TexturesLoaded then exit;
|
|
Randomize;
|
|
texWalking := BGLTexture(ResourceDir+'tux_walking.png');
|
|
texWalking.SetFrameSize(64,64);
|
|
|
|
texGround := BGLTexture(ResourceDir+'ground.png');
|
|
texGround.SetFrameSize(32,32);
|
|
|
|
font := BGLFont('Arial', 20, CSSLightYellow,CSSBlack, [fsBold]);
|
|
bubbleFont := BGLFont('Arial', 16);
|
|
bubbleFont.HorizontalAlign := taCenter;
|
|
bubbleFont.Justify:= true;
|
|
bubbleFont.Padding := RectF(10,10,10,10);
|
|
|
|
AddGround(32,128,200);
|
|
AddGround(400,128,200);
|
|
AddGround(200,250,200);
|
|
AddGround(-32,250,150);
|
|
AddGround(400,380,200);
|
|
AddGround(300,500,150);
|
|
AddGround(-32,600-32,800+64);
|
|
|
|
tux := TTux.Create(texWalking,self);
|
|
tux.Location := PointF(128,128);
|
|
|
|
smallTux := TSmallTux.Create(texWalking,self);
|
|
smallTux.Location := PointF(450,128);
|
|
|
|
sunBmp := TBGLBitmap.Create(100,100);
|
|
sunBmp.FillEllipseLinearColorAntialias(50,50,49,49,BGRA(255,255,random(100)),BGRA(255,random(200),0));
|
|
sun := sunBmp.MakeTextureAndFree;
|
|
TBGLSprite.Create(sun,-2).Location := PointF(random(200),0);
|
|
|
|
TexturesLoaded:= true;
|
|
end;
|
|
|
|
procedure TGameContext.Render(ctx: TBGLContext);
|
|
const infoText = 'Welcome to this demo showing how to use BGRABitmap with OpenGL';
|
|
var ofsX,ofsY,h: single;
|
|
r:TRectF;
|
|
alpha: byte;
|
|
begin
|
|
ctx.Canvas.FillRectLinearColor(Rect(0,0,ctx.Width,ctx.Height),
|
|
CSSSkyBlue,CSSSkyBlue,MergeBGRA(CSSSkyBlue,CSSBlue),MergeBGRA(CSSSkyBlue,CSSBlue));
|
|
|
|
if CenterOnSmallTux then
|
|
begin
|
|
ofsX := smallTux.X;
|
|
ofsY := smallTux.Y;
|
|
end else
|
|
begin
|
|
ofsX := tux.X;
|
|
ofsY := tux.Y;
|
|
end;
|
|
ofsX -= ctx.Width div 2;
|
|
ofsY -= ctx.Height div 2;
|
|
if ofsX > 800-ctx.Width then ofsX := 800-ctx.Width;
|
|
if ofsY > 600-ctx.Height then ofsY := 600-ctx.Height;
|
|
if ofsX < 0 then ofsX := 0;
|
|
if ofsY < 0 then ofsY := 0;
|
|
|
|
ctx.Canvas.Translate(-ofsX,-ofsY);
|
|
ctx.Sprites.OnDraw;
|
|
ctx.Canvas.Translate(ofsX,ofsY);
|
|
|
|
if infoTextAnimTime <= 500 then
|
|
alpha := round(infoTextAnimTime*255/500)
|
|
else if infoTextAnimTime <= 3500 then
|
|
alpha := 255
|
|
else if infoTextAnimTime <= 4000 then
|
|
alpha := round((4000-infoTextAnimTime)*255/500)
|
|
else
|
|
alpha := 0;
|
|
|
|
if alpha <> 0 then
|
|
begin
|
|
h := bubbleFont.TextHeight(infoText, 300)+bubbleFont.Padding.Top+bubbleFont.Padding.Bottom+4;
|
|
if infoTextAnimTime <= 500 then
|
|
h := h*infoTextAnimTime/500;
|
|
r.Left := ctx.Width/2-150;
|
|
r.Right := r.Left + 300;
|
|
r.Top := ctx.Height/2-h/2;
|
|
r.Bottom := r.Top + h;
|
|
ctx.Canvas.FillRect(r, BGRA(0,0,0,alpha div 2));
|
|
bubbleFont.Clipped:= true;
|
|
bubbleFont.TextRect(r, infoText, BGRA(255,255,255,alpha));
|
|
bubbleFont.Clipped:= false;
|
|
end;
|
|
|
|
if FPS <> 0 then
|
|
font.TextOut(ctx.Width-5,0,inttostr(FPS)+' FPS',taRightJustify);
|
|
end;
|
|
|
|
procedure TGameContext.UnloadTextures(ctx: TBGLContext);
|
|
begin
|
|
if TexturesLoaded then
|
|
begin
|
|
ctx.Sprites.Clear;
|
|
TexturesLoaded := false;
|
|
end;
|
|
end;
|
|
|
|
procedure TGameContext.AddGround(x, y, w: single);
|
|
begin
|
|
TGround.Create(texGround,x,y,1);
|
|
x += 32;
|
|
w -= 32;
|
|
while w > 32 do
|
|
begin
|
|
TGround.Create(texGround,x,y,2);
|
|
x += 32;
|
|
w -= 32;
|
|
end;
|
|
TGround.Create(texGround,x,y,3);
|
|
end;
|
|
|
|
procedure TGameContext.Elapse(ctx: TBGLContext; ms: single);
|
|
begin
|
|
infoTextAnimTime += ms;
|
|
elapsedMs += ms;
|
|
while elapsedMs > FrameDurationMs do
|
|
begin
|
|
ctx.Sprites.OnTimer;
|
|
elapsedMs -= FrameDurationMs;
|
|
end;
|
|
end;
|
|
|
|
function TGameContext.FindGround(x: single; var y: single): boolean;
|
|
var
|
|
i: Integer;
|
|
s: TBGLSprite;
|
|
g: TGround;
|
|
begin
|
|
for i := 0 to BGLSpriteEngine.Count-1 do
|
|
begin
|
|
s := BGLSpriteEngine.Sprite[i] as TBGLSprite;
|
|
if s is TGround then
|
|
begin
|
|
g := TGround(s);
|
|
if (x >= g.X-g.W/2) and (x <= g.X+g.W/2) then
|
|
begin
|
|
if (y >= g.Y-4) and (Y <= g.Y-4+16) then
|
|
begin
|
|
result := true;
|
|
y := g.Y-4;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
result := false;
|
|
end;
|
|
|
|
procedure TGameContext.MouseDown(Button: TMouseButton; X, Y: integer);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TGameContext.MouseMove(X, Y: integer);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TGameContext.MouseUp(Button: TMouseButton);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TGameContext.KeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
If Key = VK_LEFT then begin LeftKey := true; Key := 0; end;
|
|
If Key = VK_RIGHT then begin RightKey := true; Key := 0; end;
|
|
If Key = VK_UP then begin UpKey := true; Key := 0; end;
|
|
end;
|
|
|
|
procedure TGameContext.KeyUp(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
If Key = VK_LEFT then begin LeftKey := false; Key := 0; end;
|
|
If Key = VK_RIGHT then begin RightKey := false; Key := 0; end;
|
|
If Key = VK_UP then begin UpKey := false; Key := 0; end;
|
|
end;
|
|
|
|
{ TGround }
|
|
|
|
constructor TGround.Create(ATexture: IBGLTexture; AX, AY: Single;
|
|
AFrame: integer);
|
|
begin
|
|
inherited Create(ATexture, -1);
|
|
Location := PointF(AX,AY+4);
|
|
Frame := AFrame;
|
|
end;
|
|
|
|
procedure TGround.OnInit;
|
|
begin
|
|
HorizontalAlign := taCenter;
|
|
VerticalAlign := tlCenter;
|
|
end;
|
|
|
|
{ TTux }
|
|
|
|
procedure TTux.OnInit;
|
|
begin
|
|
HorizontalAlign := taCenter;
|
|
VerticalAlign := tlBottom;
|
|
Frame := 1;
|
|
Speed := PointF(0,0);
|
|
FrameLoopStart := 1;
|
|
FrameLoopEnd := 10;
|
|
LookingLeft:= false;
|
|
goRight:= false;
|
|
goLeft:= false;
|
|
goUp := false;
|
|
waiting := 0;
|
|
JumpStrength:= 5;
|
|
Parent := nil;
|
|
Autoplay:= false;
|
|
end;
|
|
|
|
procedure TTux.OnDraw;
|
|
begin
|
|
if lookingLeft then Texture.ToggleFlipX;
|
|
inherited OnDraw;
|
|
if lookingLeft then Texture.ToggleFlipX;
|
|
if Bubble <> nil then
|
|
begin
|
|
if BubbleTime mod 500 > 400 then
|
|
begin
|
|
if ShowBubbleTooFar then
|
|
BubbleTooFar.Draw(x,y-H)
|
|
else
|
|
Bubble.Draw(x,y-H);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TTux.OnTimer;
|
|
var curY: single;
|
|
nearParent: boolean;
|
|
begin
|
|
if not OnTheGround then
|
|
begin
|
|
//jumping
|
|
end else
|
|
begin
|
|
nearParent:= false;
|
|
if Autoplay then
|
|
begin
|
|
if waiting > 0 then
|
|
dec(waiting)
|
|
else
|
|
begin
|
|
goRight:= false;
|
|
goLeft:= false;
|
|
goUp := false;
|
|
if Parent<> nil then
|
|
nearParent := (abs(X-Parent.X) < 100) and (abs(Y-Parent.Y) < 150);
|
|
|
|
ShowBubbleTooFar := (Parent <> nil) and not nearParent;
|
|
if (Parent<> nil) and (abs(X-Parent.X) < 200) and (abs(Y-Parent.Y) < 200) then
|
|
begin
|
|
if BubbleTime > 500 then
|
|
begin
|
|
if not nearParent then
|
|
BubbleTime := 0
|
|
else
|
|
BubbleTime := 500;
|
|
end;
|
|
if (abs(X-Parent.X) > 40) then
|
|
begin
|
|
waiting := random(150);
|
|
if X > Parent.X then goLeft := true
|
|
else goRight := true;
|
|
end else
|
|
if (Parent.Y < Y-40) and (Parent.Y > Y-200) then
|
|
begin
|
|
waiting := random(150);
|
|
goUp := true;
|
|
waiting := 0;
|
|
end;
|
|
end else
|
|
begin
|
|
waiting := random(300);
|
|
case random(10) of
|
|
0..2: goLeft:= true;
|
|
3..5: goRight:= true;
|
|
9: begin goUp:= true; waiting := 0; end;
|
|
end;
|
|
end;
|
|
end;
|
|
curY := Y+5;
|
|
if goLeft and (X < 1) then
|
|
begin
|
|
goLeft := false;
|
|
waiting:= 0;
|
|
end;
|
|
if goRight and (X > 799) then
|
|
begin
|
|
goRight := false;
|
|
waiting:= 0;
|
|
end;
|
|
if not nearParent then
|
|
begin
|
|
if goLeft and not Context.FindGround(X-20,curY) then
|
|
begin
|
|
goLeft:= false;
|
|
waiting:= 0;
|
|
end;
|
|
if goRight and not Context.FindGround(X+20,curY) then
|
|
begin
|
|
goRight:= false;
|
|
waiting := 0;
|
|
end;
|
|
end else
|
|
if (Parent <> nil) and (abs(X-Parent.X) < 5) then
|
|
begin
|
|
goLeft:= false;
|
|
goRight:= false;
|
|
end;
|
|
end else
|
|
begin
|
|
goRight:= Context.RightKey;
|
|
goLeft:= Context.LeftKey;
|
|
goUp := Context.UpKey;
|
|
end;
|
|
if not goRight and not goLeft and not goUp then
|
|
begin
|
|
speed.X := speed.X*0.9;
|
|
if abs(speed.X)<0.1 then speed.X := 0;
|
|
end else
|
|
begin
|
|
//on the ground and can move
|
|
if goRight then Speed.X += 0.1;
|
|
if Speed.X > 1.3 then Speed.X := 1.3;
|
|
if goLeft then Speed.X -= 0.1;
|
|
if Speed.X < -1.3 then Speed.X := -1.3;
|
|
if goUp then Speed.Y := -JumpStrength;
|
|
|
|
if (Speed.X < 0) and not LookingLeft and (round(Frame) = 1) then
|
|
lookingLeft:= true;
|
|
if (Speed.X > 0) and LookingLeft and (round(Frame) = 1) then
|
|
lookingLeft := false;
|
|
end;
|
|
if LookingLeft xor (Speed.X < 0) then
|
|
Frame := Frame-Speed.X*0.5
|
|
else
|
|
Frame := Frame+Speed.X*0.5;
|
|
end;
|
|
|
|
Speed.Y += 0.1;
|
|
Location := Location+Speed;
|
|
curY := Y;
|
|
OnTheGround:= Context.FindGround(X,curY);
|
|
if OnTheGround then Speed.Y := 0;
|
|
Y := curY;
|
|
if X < 0 then X := 0;
|
|
if X > 800 then X := 800;
|
|
inc(BubbleTime);
|
|
end;
|
|
|
|
constructor TTux.Create(ATexture: IBGLTexture; AContext: TGameContext);
|
|
begin
|
|
Context := AContext;
|
|
inherited Create(ATexture, 0);
|
|
end;
|
|
|
|
destructor TTux.Destroy;
|
|
begin
|
|
FreeAndNil(Bubble);
|
|
FreeAndNil(BubbleTooFar);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
end.
|
|
|