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.