From ea2748fd8cc9c66b867dab56c044a4f70e8fa2ab Mon Sep 17 00:00:00 2001 From: Milan Nikolic Date: Fri, 3 Feb 2017 00:26:27 +0100 Subject: [PATCH] Pimp floppy example --- examples/games/floppy/main.go | 321 ++++++++++++++++++++++++++-------- 1 file changed, 245 insertions(+), 76 deletions(-) diff --git a/examples/games/floppy/main.go b/examples/games/floppy/main.go index 4290946..831e698 100644 --- a/examples/games/floppy/main.go +++ b/examples/games/floppy/main.go @@ -2,148 +2,305 @@ package main import ( "fmt" + "os" + "runtime" + "unsafe" "github.com/gen2brain/raylib-go/raylib" ) const ( - maxTubes = 100 - floppyRadius = 24.0 - tubesWidth = 80 + // Maximum number of pipes + maxPipes = 100 + // Pipes width + pipesWidth = 60 + // Sprite size + spriteSize = 48 ) +// Floppy type Floppy struct { Position raylib.Vector2 - Radius float32 - Color raylib.Color } -type Tubes struct { +// Pipe +type Pipe struct { Rec raylib.Rectangle Color raylib.Color Active bool } +// Game type Game struct { ScreenWidth int32 ScreenHeight int32 - FramesCounter int32 - GameOver bool - Pause bool - Score int - HiScore int + FxFlap raylib.Sound + FxSlap raylib.Sound + FxPoint raylib.Sound + FxClick raylib.Sound - Floppy Floppy - Tubes []Tubes - TubesPos []raylib.Vector2 - TubesSpeedX int32 - SuperFX bool + Texture raylib.Texture2D + FrameRec raylib.Rectangle + + GameOver bool + Dead bool + Pause bool + SuperFX bool + + Score int + HiScore int + FramesCounter int32 + WindowShouldClose bool + + Floppy Floppy + + Pipes []Pipe + PipesPos []raylib.Vector2 + PipesSpeedX int32 } +// New Game +func NewGame() (g Game) { + g.Init() + return +} + +// On Android this sets callback function to be used for android_main +func init() { + raylib.SetCallbackFunc(run) +} + +// Main function, not used on Android, used on desktop platform func main() { - game := Game{} - game.Init() + run(nil) +} - raylib.InitWindow(game.ScreenWidth, game.ScreenHeight, "sample game: floppy") +// Callback function on Android, not needed on desktop platform +func run(app unsafe.Pointer) { + // Initialize game + game := NewGame() + // Initialize window + if runtime.GOOS != "android" { + raylib.InitWindow(game.ScreenWidth, game.ScreenHeight, "Floppy Gopher") + } else { + raylib.InitWindow(game.ScreenWidth, game.ScreenHeight, app) + } + + // Initialize audio + raylib.InitAudioDevice() + + // NOTE: Textures and Sounds MUST be loaded after Window/Audio initialization + game.Load() + + // Limit FPS raylib.SetTargetFPS(60) - for !raylib.WindowShouldClose() { + // Main loop + for !game.WindowShouldClose { + // Update game game.Update() + // Draw game game.Draw() } + // Free resources + game.Unload() + + // Close audio + raylib.CloseAudioDevice() + + // Close window raylib.CloseWindow() + + // Exit + os.Exit(0) } // Initialize game func (g *Game) Init() { - g.ScreenWidth = 800 - g.ScreenHeight = 450 + // Window resolution + g.ScreenWidth = 504 + g.ScreenHeight = 896 g.Floppy = Floppy{} - g.Floppy.Radius = floppyRadius - g.Floppy.Position = raylib.NewVector2(80, float32(g.ScreenHeight)/2-g.Floppy.Radius) - g.TubesSpeedX = 2 + g.Floppy.Position = raylib.NewVector2(80, float32(g.ScreenHeight)/2-spriteSize/2) + g.PipesSpeedX = 2 - g.TubesPos = make([]raylib.Vector2, maxTubes) + // Sprite rectangle + g.FrameRec = raylib.NewRectangle(0, 0, spriteSize, spriteSize) - for i := 0; i < maxTubes; i++ { - g.TubesPos[i].X = float32(400 + 280*i) - g.TubesPos[i].Y = -float32(raylib.GetRandomValue(0, 120)) + // Pipes positions + g.PipesPos = make([]raylib.Vector2, maxPipes) + for i := 0; i < maxPipes; i++ { + g.PipesPos[i].X = float32(480 + 360*i) + g.PipesPos[i].Y = -float32(raylib.GetRandomValue(0, 240)) } - g.Tubes = make([]Tubes, maxTubes*2) + // Pipes colors + colors := []raylib.Color{ + raylib.Orange, raylib.Red, raylib.Gold, raylib.Lime, + raylib.Violet, raylib.Brown, raylib.LightGray, raylib.Blue, + raylib.Yellow, raylib.Green, raylib.Purple, raylib.Beige, + } - for i := 0; i < maxTubes*2; i += 2 { - g.Tubes[i].Rec.X = int32(g.TubesPos[i/2].X) - g.Tubes[i].Rec.Y = int32(g.TubesPos[i/2].Y) - g.Tubes[i].Rec.Width = tubesWidth - g.Tubes[i].Rec.Height = 255 + // Pipes + g.Pipes = make([]Pipe, maxPipes*2) + for i := 0; i < maxPipes*2; i += 2 { + g.Pipes[i].Rec.X = int32(g.PipesPos[i/2].X) + g.Pipes[i].Rec.Y = int32(g.PipesPos[i/2].Y) + g.Pipes[i].Rec.Width = pipesWidth + g.Pipes[i].Rec.Height = 550 + g.Pipes[i].Color = colors[raylib.GetRandomValue(0, int32(len(colors)-1))] - g.Tubes[i+1].Rec.X = int32(g.TubesPos[i/2].X) - g.Tubes[i+1].Rec.Y = int32(600 + g.TubesPos[i/2].Y - 255) - g.Tubes[i+1].Rec.Width = tubesWidth - g.Tubes[i+1].Rec.Height = 255 + g.Pipes[i+1].Rec.X = int32(g.PipesPos[i/2].X) + g.Pipes[i+1].Rec.Y = int32(1200 + g.PipesPos[i/2].Y - 550) + g.Pipes[i+1].Rec.Width = pipesWidth + g.Pipes[i+1].Rec.Height = 550 - g.Tubes[i/2].Active = true + g.Pipes[i/2].Active = true } g.Score = 0 g.FramesCounter = 0 + g.WindowShouldClose = false g.GameOver = false + g.Dead = false g.SuperFX = false g.Pause = false } +// Load resources +func (g *Game) Load() { + g.FxFlap = raylib.LoadSound("sounds/flap.wav") + g.FxSlap = raylib.LoadSound("sounds/slap.wav") + g.FxPoint = raylib.LoadSound("sounds/point.wav") + g.FxClick = raylib.LoadSound("sounds/click.wav") + g.Texture = raylib.LoadTexture("images/sprite.png") +} + +// Unload resources +func (g *Game) Unload() { + raylib.UnloadSound(g.FxFlap) + raylib.UnloadSound(g.FxSlap) + raylib.UnloadSound(g.FxPoint) + raylib.UnloadSound(g.FxClick) + raylib.UnloadTexture(g.Texture) +} + // Update game func (g *Game) Update() { + if raylib.WindowShouldClose() { + g.WindowShouldClose = true + } + if !g.GameOver { - if raylib.IsKeyPressed(raylib.KeyP) { + if raylib.IsKeyPressed(raylib.KeyP) || raylib.IsKeyPressed(raylib.KeyBack) { + raylib.PlaySound(g.FxClick) + + if runtime.GOOS == "android" && g.Pause { + g.WindowShouldClose = true + } + g.Pause = !g.Pause } if !g.Pause { - for i := 0; i < maxTubes; i++ { - g.TubesPos[i].X -= float32(g.TubesSpeedX) + if !g.Dead { + // Scroll X + for i := 0; i < maxPipes; i++ { + g.PipesPos[i].X -= float32(g.PipesSpeedX) + } + + for i := 0; i < maxPipes*2; i += 2 { + g.Pipes[i].Rec.X = int32(g.PipesPos[i/2].X) + g.Pipes[i+1].Rec.X = int32(g.PipesPos[i/2].X) + } + + // Movement/Controls + if raylib.IsKeyDown(raylib.KeySpace) || raylib.IsMouseButtonDown(raylib.MouseLeftButton) && !g.GameOver { + raylib.PlaySound(g.FxFlap) + + // Switch flap sprites every 8 frames + g.FramesCounter++ + if g.FramesCounter >= 8 { + g.FramesCounter = 0 + g.FrameRec.X = spriteSize * 3 + } else { + g.FrameRec.X = spriteSize * 2 + } + + // Floppy go up + g.Floppy.Position.Y -= 3 + } else { + // Default sprite + //g.FrameRec.X = spriteSize + + // Switch flap sprites every 8 frames + g.FramesCounter++ + if g.FramesCounter >= 8 { + g.FramesCounter = 0 + g.FrameRec.X = spriteSize + } else { + g.FrameRec.X = 0 + } + + // Floppy fall down + g.Floppy.Position.Y += 1 + } } - for i := 0; i < maxTubes*2; i += 2 { - g.Tubes[i].Rec.X = int32(g.TubesPos[i/2].X) - g.Tubes[i+1].Rec.X = int32(g.TubesPos[i/2].X) - } + if !g.Dead { + // Check Collisions + for i := 0; i < maxPipes*2; i++ { + if raylib.CheckCollisionRecs(raylib.NewRectangle(int32(g.Floppy.Position.X), int32(g.Floppy.Position.Y), spriteSize, spriteSize), g.Pipes[i].Rec) { + // OMG You killed Gopher you bastard! + g.Dead = true - if raylib.IsKeyDown(raylib.KeySpace) && !g.GameOver { - g.Floppy.Position.Y -= 3 - } else { - g.Floppy.Position.Y += 1 - } + raylib.PlaySound(g.FxSlap) + } else if (g.PipesPos[i/2].X < g.Floppy.Position.X-spriteSize) && g.Pipes[i/2].Active && !g.GameOver { + // Score point + g.Score += 1 + g.Pipes[i/2].Active = false - // Check Collisions - for i := 0; i < maxTubes*2; i++ { - if raylib.CheckCollisionCircleRec(g.Floppy.Position, g.Floppy.Radius, g.Tubes[i].Rec) { - g.GameOver = true - g.Pause = false - } else if (g.TubesPos[i/2].X < g.Floppy.Position.X) && g.Tubes[i/2].Active && !g.GameOver { - g.Score += 100 - g.Tubes[i/2].Active = false + // Flash screen + g.SuperFX = true - g.SuperFX = true + // Update HiScore + if g.Score > g.HiScore { + g.HiScore = g.Score + } - if g.Score > g.HiScore { - g.HiScore = g.Score + raylib.PlaySound(g.FxPoint) } } + } else { + // Wait 60 frames before GameOver + g.FramesCounter++ + if g.FramesCounter >= 60 { + g.GameOver = true + } + + g.FrameRec.X = spriteSize * 4 + } + } else { + if raylib.IsMouseButtonDown(raylib.MouseLeftButton) { + g.Pause = !g.Pause } } } else { - if raylib.IsKeyPressed(raylib.KeyEnter) { + if raylib.IsKeyPressed(raylib.KeyEnter) || raylib.IsMouseButtonDown(raylib.MouseLeftButton) { + raylib.PlaySound(g.FxClick) + + // Return of the Gopher! g.Init() - g.GameOver = false + } else if runtime.GOOS == "android" && raylib.IsKeyDown(raylib.KeyBack) { + g.WindowShouldClose = true } + } } @@ -151,31 +308,43 @@ func (g *Game) Update() { func (g *Game) Draw() { raylib.BeginDrawing() - raylib.ClearBackground(raylib.RayWhite) + raylib.ClearBackground(raylib.SkyBlue) if !g.GameOver { - raylib.DrawCircle(int32(g.Floppy.Position.X), int32(g.Floppy.Position.Y), g.Floppy.Radius, raylib.DarkGray) + // Draw Gopher + raylib.DrawTextureRec(g.Texture, g.FrameRec, g.Floppy.Position, raylib.RayWhite) // Draw part of the texture - // Draw tubes - for i := 0; i < maxTubes; i++ { - raylib.DrawRectangle(g.Tubes[i*2].Rec.X, g.Tubes[i*2].Rec.Y, g.Tubes[i*2].Rec.Width, g.Tubes[i*2].Rec.Height, raylib.Gray) - raylib.DrawRectangle(g.Tubes[i*2+1].Rec.X, g.Tubes[i*2+1].Rec.Y, g.Tubes[i*2+1].Rec.Width, g.Tubes[i*2+1].Rec.Height, raylib.Gray) + // Draw pipes + for i := 0; i < maxPipes; i++ { + raylib.DrawRectangle(g.Pipes[i*2].Rec.X, g.Pipes[i*2].Rec.Y, g.Pipes[i*2].Rec.Width, g.Pipes[i*2].Rec.Height, g.Pipes[i*2].Color) + raylib.DrawRectangle(g.Pipes[i*2+1].Rec.X, g.Pipes[i*2+1].Rec.Y, g.Pipes[i*2+1].Rec.Width, g.Pipes[i*2+1].Rec.Height, g.Pipes[i*2].Color) + + // Draw borders + raylib.DrawRectangleLines(g.Pipes[i*2].Rec.X, g.Pipes[i*2].Rec.Y, g.Pipes[i*2].Rec.Width, g.Pipes[i*2].Rec.Height, raylib.Black) + raylib.DrawRectangleLines(g.Pipes[i*2+1].Rec.X, g.Pipes[i*2+1].Rec.Y, g.Pipes[i*2+1].Rec.Width, g.Pipes[i*2+1].Rec.Height, raylib.Black) } - // Draw flashing fx (one frame only) + // Draw Super Flashing FX (one frame only) if g.SuperFX { raylib.DrawRectangle(0, 0, g.ScreenWidth, g.ScreenHeight, raylib.White) g.SuperFX = false } - raylib.DrawText(fmt.Sprintf("%04d", g.Score), 20, 20, 40, raylib.Gray) - raylib.DrawText(fmt.Sprintf("HI-SCORE: %04d", g.HiScore), 20, 70, 20, raylib.LightGray) + // Draw HI-SCORE + raylib.DrawText(fmt.Sprintf("%02d", g.Score), 20, 20, 32, raylib.RayWhite) + raylib.DrawText(fmt.Sprintf("HI-SCORE: %02d", g.HiScore), 20, 64, 20, raylib.RayWhite) if g.Pause { - raylib.DrawText("GAME PAUSED", g.ScreenWidth/2-raylib.MeasureText("GAME PAUSED", 40)/2, g.ScreenHeight/2-40, 40, raylib.Gray) + // Draw PAUSED text + raylib.DrawText("PAUSED", g.ScreenWidth/2-raylib.MeasureText("PAUSED", 24)/2, g.ScreenHeight/2-50, 20, raylib.RayWhite) } } else { - raylib.DrawText("PRESS [ENTER] TO PLAY AGAIN", raylib.GetScreenWidth()/2-raylib.MeasureText("PRESS [ENTER] TO PLAY AGAIN", 20)/2, raylib.GetScreenHeight()/2-50, 20, raylib.Gray) + // Draw PLAY AGAIN text + if runtime.GOOS == "android" { + raylib.DrawText("[TAP] TO PLAY AGAIN", raylib.GetScreenWidth()/2-raylib.MeasureText("[TAP] TO PLAY AGAIN", 20)/2, raylib.GetScreenHeight()/2-50, 20, raylib.RayWhite) + } else { + raylib.DrawText("[ENTER] TO PLAY AGAIN", raylib.GetScreenWidth()/2-raylib.MeasureText("[ENTER] TO PLAY AGAIN", 20)/2, raylib.GetScreenHeight()/2-50, 20, raylib.RayWhite) + } } raylib.EndDrawing()