package main import ( "fmt" "math" "strconv" rl "github.com/gen2brain/raylib-go/raylib" ) // diz para um inimigo em que condições atirar. acionado pelo movementPattern type shootingPattern func(*enemy) // diz para uma bala como ela deve se mover. type bulletMovementPattern func(*bullet) rl.Vector2 type duration float32 const second duration = 1 type hazard interface { // inimigos e projéteis Pos() rl.Vector2 } type game struct { arenaWidth int32 arenaHeight int32 interfaceWidth int32 frame float32 enemies []*enemy waves []*wave walls []plane currentWave int bullets []*bullet score int gameSpeed float32 // 1 = velocidade normal backgroundColor rl.Color } type plane struct { normal rl.Vector2 pos rl.Vector2 } type bullet struct { pos rl.Vector2 speed rl.Vector2 size float32 dmg int enemy bool } type player struct { pos rl.Vector2 speed rl.Vector2 moveSpeed float32 bulletMoveSpeed float32 hitBoxRadius float32 bulletSize float32 focusMode bool focusSpeedDecrease float32 } type enemy struct { pos rl.Vector2 health int move movementPattern shoot shootingPattern hitBoxRadius float32 } type wave struct { duration *timer enemies []*enemy entrance movementPattern } type timer struct { ttl duration time float32 start float64 } func (t *timer) tick(g *game) { t.time += rl.GetFrameTime() * g.gameSpeed } func (t *timer) isTimeout() bool { return t.time >= float32(t.ttl) } func (t *timer) reset() { t.start = float64(t.time) t.time = 0 } func (t *timer) unit() float32 { return t.time / float32(t.ttl) } func newTimer(duration duration) *timer { return &timer{ time: 0, ttl: duration, start: rl.GetTime(), } } func (g game) insideArena(v rl.Vector2) bool { return v.X >= 0 && v.Y >= 0 && v.Y <= float32(g.arenaHeight) && v.X <= float32(g.arenaWidth) } func (g *game) removeBullet(index int) { g.bullets[index] = g.bullets[len(g.bullets)-1] g.bullets = g.bullets[:len(g.bullets)-1] } func (b *bullet) update(g *game, index int) { b.pos = rl.Vector2Add(b.pos, rl.Vector2Scale(b.speed, g.gameSpeed)) if !g.insideArena(b.pos) { g.removeBullet(index) return } rl.DrawCircleV(b.pos, b.size, rl.Yellow) } func bulletExplosion(g *game, rate, amount int, bulletSpeed, size float32) shootingPattern { t := newTimer(second * duration(rate)) return func(e *enemy) { t.tick(g) if !t.isTimeout() { return } t.reset() var bullets []*bullet for i := 0; i < amount; i++ { angle := 2.0 * math.Pi * float64(i) / float64(amount) cos := math.Cos(angle) sin := math.Sin(angle) direction := rl.Vector2{X: float32(cos), Y: float32(sin)} direction = rl.Vector2Scale(direction, bulletSpeed) bullets = append(bullets, &bullet{ speed: direction, size: size, dmg: 1, enemy: true, pos: e.pos, }) } g.bullets = append(g.bullets, bullets...) } } func ShootAtPlayer(g *game, p *player, rate int, bulletMoveSpeed float32) shootingPattern { return func(e *enemy) { if int(g.frame)%rate != 0 { return } direction := rl.Vector2Subtract(p.pos, e.pos) direction = rl.Vector2Normalize(direction) direction = rl.Vector2Scale(direction, bulletMoveSpeed) g.bullets = append(g.bullets, &bullet{ speed: direction, size: 12, dmg: 1, enemy: true, pos: e.pos, }) } } func burstShootAtPlayer(g *game, p *player, rate float32, bulletMoveSpeed float32) shootingPattern { flag := true off := newTimer(second) on := newTimer(duration(float32(second) * rate)) return func(e *enemy) { off.tick(g) if off.isTimeout() { flag = !flag off.reset() } if !flag { return } on.tick(g) if !on.isTimeout() { return } on.reset() direction := rl.Vector2Subtract(p.pos, e.pos) direction = rl.Vector2Normalize(direction) direction = rl.Vector2Scale(direction, bulletMoveSpeed) g.bullets = append(g.bullets, &bullet{ speed: direction, size: 12, dmg: 1, enemy: true, pos: e.pos, }) } } func shootStraightDown(g *game) shootingPattern { return func(e *enemy) { if int(g.frame)%10 != 0 { return } g.bullets = append(g.bullets, &bullet{ speed: rl.Vector2{X: 0, Y: 5}, size: 12, dmg: 1, enemy: true, pos: e.pos, }) } } // func detectWallCollision(planes []plane, initialPos, direction rl.Vector2) float32 { // p := initialPos // w := direction // var closest = math.Inf(1) // for _, plane := range planes { // c := plane.pos // n := plane.normal // denominator := rl.Vector2DotProduct(w, n) // if denominator == 0 { // continue // } // t := rl.Vector2DotProduct(rl.Vector2Subtract(p, c), n) / denominator // closest = min(float64(t), closest) // } // return float32(-closest) // } // func foobarPattern(g *game) movementPattern { // // pos := rl.Vector2{} // state := 0 // wait := 0 // // return func(e *enemy) rl.Vector2 { // // switch state { // case 0: // init // pos.X = e.pos.X // state = 1 // return pos // case 1: // descer // pos.Y += 1 // if pos.Y >= 100 { state = 2; wait = 50 } // return pos // case 2: // atirar por um tempo // e.shoot(e) // wait -= 1 // if wait <= 0 { state = 3; wait = 60 } // return pos // case 3: // wait // wait -= 1 // if wait <= 0 { state = 4 } // return pos // case 4: // retornar // pos.Y -= 3 // if pos.Y - e.hitBoxRadius < 0 { // e.health = 0 // } // return pos // } // panic(state) // } // } func (e *enemy) checkHit(g *game) (bool, *bullet, int) { for index, bullet := range g.bullets { playerBullet := !bullet.enemy if !playerBullet { continue } distance := rl.Vector2Distance(e.pos, bullet.pos) - bullet.size if distance < e.hitBoxRadius { return true, bullet, index } } return false, nil, 0 } func (g *game) killEnemy(index int) { g.enemies[index] = g.enemies[len(g.enemies)-1] g.enemies = g.enemies[:len(g.enemies)-1] } func (e *enemy) update(g *game) { if e.health <= 0 { return } enemyColor := rl.Blue if e.move != nil { // e.pos = rl.Vector2Add(rl.Vector2Scale(e.move(e), g.gameSpeed), e.pos) e.move(e) // e.pos = e.move(e) } if hit, bullet, idx := e.checkHit(g); hit { g.score += 273 e.health -= bullet.dmg g.removeBullet(idx) enemyColor = rl.White g.backgroundColor = rl.NewColor(20, 20, 20, 255) } rl.DrawCircleV(e.pos, e.hitBoxRadius, enemyColor) } func (g *game) fullClear() bool { for _, e := range g.waves[g.currentWave].enemies { if e.health != 0 { return false } } return true } func (g *game) waveTimeout() bool { currentWave := g.waves[g.currentWave] if currentWave.duration == nil { return false } if currentWave.duration.isTimeout() { return true } currentWave.duration.tick(g) return false } func (g *game) wavesOver() bool { return g.currentWave == len(g.waves) } func (g *game) nextWave() bool { g.currentWave++ if g.wavesOver() { return false } g.addEnemy(g.waves[g.currentWave].enemies...) return true } func (g *game) addEnemy(e ...*enemy) { g.enemies = append(g.enemies, e...) } func main() { state := &game{ arenaWidth: 450, arenaHeight: 700, interfaceWidth: 400, gameSpeed: 1, backgroundColor: rl.NewColor(0, 0, 0, 100), } player := player{ pos: rl.Vector2{ X: float32(state.arenaWidth) / 2, Y: float32(state.arenaHeight) * 0.8, }, moveSpeed: 4, focusSpeedDecrease: 0.5, bulletMoveSpeed: 6, bulletSize: 9, hitBoxRadius: 5, } var arena = []plane{ { normal: rl.Vector2{X: 0, Y: -1}, pos: rl.Vector2{X: 1, Y: 0}, }, { normal: rl.Vector2{X: -1, Y: 0}, pos: rl.Vector2{X: float32(state.arenaWidth), Y: 0}, }, { normal: rl.Vector2{X: 0, Y: 1}, pos: rl.Vector2{X: 0, Y: float32(state.arenaHeight)}, }, { normal: rl.Vector2{X: 1, Y: 0}, pos: rl.Vector2{X: 0, Y: 1}, }, } state.walls = arena // rl.SetTraceLog(rl.LogWarning | rl.LogDebug) rl.SetConfigFlags(rl.FlagMsaa4xHint) rl.InitWindow(state.arenaWidth+state.interfaceWidth, state.arenaHeight, "danmaku") targetFPS := 120 rl.SetTargetFPS(int32(targetFPS)) // shader_path := path.Join("shaders", "trails.glsl") // shader := rl.LoadShader("", shader_path) // timeShaderLocation := rl.GetShaderLocation(shader, "time") // target := rl.LoadRenderTexture( // state.arenaWidth + state.interfaceWidth, // state.arenaHeight, // ) // descentAndSine := jjjjjj state.waves = []*wave{ { duration: newTimer(second * 10), enemies: []*enemy{ { pos: rl.Vector2{X: 100, Y: -20}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 1.1, move: sineDescentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 0), }, }.MovementPattern(state), shoot: burstShootAtPlayer(state, &player, 0.1, 4), }, { pos: rl.Vector2{X: 200, Y: -20}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 5, move: shootStill(), }, { duration: 1.1, move: descentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 1), }, }.MovementPattern(state), shoot: burstShootAtPlayer(state, &player, 0.1, 4), }, }, }, { duration: newTimer(second * 20), enemies: []*enemy{ { pos: rl.Vector2{X: 100, Y: 100}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 1.1, move: sineDescentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 0), }, }.MovementPattern(state), shoot: bulletExplosion(state, 1, 40, 2, 11), }, }, }, { enemies: []*enemy{ { pos: rl.Vector2{X: 200, Y: 200}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 1.1, move: sineDescentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 0), }, }.MovementPattern(state), shoot: bulletExplosion(state, 1, 20, 2, 11), }, { pos: rl.Vector2{X: 100, Y: 100}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 1.1, move: sineDescentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 0), }, }.MovementPattern(state), shoot: burstShootAtPlayer(state, &player, 0.2, 4), }, { pos: rl.Vector2{X: 50, Y: 250}, health: 100, hitBoxRadius: 20, move: statePipeline{ { duration: 1.1, move: sineDescentMove(state, -20, 200, 1), }, { move: sineHorizonalPattern(state, 0), }, }.MovementPattern(state), shoot: bulletExplosion(state, 1, 20, 2, 11), // shoot: shootStraightDown(state), }, }, }, } currectScore := 0 state.addEnemy(state.waves[0].enemies...) for ; !rl.WindowShouldClose(); state.frame += state.gameSpeed { rl.BeginDrawing() // rl.BeginTextureMode(target) rl.ClearBackground(state.backgroundColor) state.backgroundColor = rl.Black player.update(state) if state.fullClear() || state.waveTimeout() { if !state.nextWave() { break } } enemiesTotalLifeRemaining := 0 for i := 0; i < len(state.enemies); i++ { state.enemies[i].update(state) enemiesTotalLifeRemaining += state.enemies[i].health } for i := 0; i < len(state.bullets); i++ { state.bullets[i].update(state, i) } rl.DrawRectangle( state.arenaWidth, 0, state.interfaceWidth, state.arenaHeight, rl.NewColor(0, 33, 59, 255), ) if player.focusMode { state.gameSpeed = 0.3 } else { state.gameSpeed = 1 } // rl.EndTextureMode() // { // shaders // if rl.IsKeyPressed(rl.KeyF2) { // shader = rl.LoadShader("", shader_path) // fmt.Println("reloaded shader") // } // // rl.SetShaderValue( // shader, timeShaderLocation, []float32{float32(rl.GetTime())}, // rl.ShaderUniformFloat, // ) // rl.BeginShaderMode(shader) // // NOTE: Render texture must be y-flipped due to default // // OpenGL coordinates (left-bottom) // rl.DrawTextureRec( // target.Texture, // rl.NewRectangle( // 0, 0, float32(target.Texture.Width), float32(-target.Texture.Height), // ), // rl.NewVector2(0, 0), rl.White, // ) // // rl.EndShaderMode() // } { // UI currectScore += (state.score - currectScore) / 11 rl.DrawText( fmt.Sprint("score: ", strconv.Itoa(currectScore)), state.arenaWidth, 0, 50, rl.White, ) rl.DrawText( fmt.Sprint("bullets: ", strconv.Itoa(len(state.bullets))), state.arenaWidth, 50, 50, rl.White, ) rl.DrawText( fmt.Sprintf("wave: %d/%d", state.currentWave, len(state.waves)), state.arenaWidth, 100, 50, rl.White, ) rl.DrawText( fmt.Sprintf("t: %f", rl.GetTime()), state.arenaWidth, 150, 50, rl.White, ) rl.DrawText( fmt.Sprintf("sec/f: %.5f", rl.GetFrameTime()), state.arenaWidth, 200, 50, rl.White, ) rl.DrawText( fmt.Sprintf("total life: %d", enemiesTotalLifeRemaining), state.arenaWidth, 250, 50, rl.White, ) rl.DrawText( fmt.Sprintf("speed: %f", state.gameSpeed), state.arenaWidth, 300, 50, rl.White, ) rl.DrawText("danmaku babe bullet shoot shoot", 20, 20, 20, rl.DarkGray) rl.DrawLine(18, 42, state.arenaWidth-18, 42, rl.Black) rl.DrawFPS(0, 0) } rl.EndDrawing() } rl.CloseWindow() }