package main import ( "fmt" "strconv" rl "github.com/gen2brain/raylib-go/raylib" ) type movementPattern func(*enemy)rl.Vector2 type shootingPattern func(*enemy) type bulletMovementPattern func(*bullet)rl.Vector2 type hazard interface { // inimigos e projéteis Pos() rl.Vector2 } type game struct { arenaWidth int32 arenaHeight int32 interfaceWidth int32 frame int enemies []*enemy bullets []*bullet score int } 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 } 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, b.speed) if !g.insideArena(b.pos) { g.removeBullet(index) return } rl.DrawCircleV(b.pos, b.size, rl.Yellow) } func ShootAtPlayer(g *game, p *player, rate int, bulletMoveSpeed float32) shootingPattern { return func(e *enemy) { if 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 int, bulletMoveSpeed float32) shootingPattern { flag := true return func(e *enemy) { if g.frame % 100 == 0 { flag = !flag } if !flag { return } if 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 shootStraightDown(g *game) shootingPattern { return func(e *enemy) { if 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 shootStill() movementPattern { return func(e *enemy) rl.Vector2 { e.shoot(e) return e.pos } } func horizonalPattern(g *game) movementPattern { direction := rl.Vector2{X: 4, Y: 0} return func(e *enemy) rl.Vector2 { result := rl.Vector2Add(direction, e.pos) e.shoot(e) if !g.insideArena(result) { direction = rl.Vector2Negate(direction) } return result } } // func aimPlayerPattern(g *game, p *player) movementPattern { // } 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, index int) { enemyColor := rl.Blue if e.move != nil { 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 } if e.health <= 0 { g.killEnemy(index) return } rl.DrawCircleV(e.pos, e.hitBoxRadius, enemyColor) } func (p *player) move(g *game) { var moveSpeed float32 if p.focusMode { moveSpeed = p.moveSpeed * p.focusSpeedDecrease } else { moveSpeed = p.moveSpeed } p.speed = rl.Vector2{X: 0, Y: 0} if rl.IsKeyDown(rl.KeyW) { p.speed.Y -= 1 } if rl.IsKeyDown(rl.KeyS) { p.speed.Y += 1 } if rl.IsKeyDown(rl.KeyA) { p.speed.X -= 1 } if rl.IsKeyDown(rl.KeyD) { p.speed.X += 1 } if !(p.speed.X == 0 && p.speed.Y == 0) { // jogador se move mais rapido na diagonal caso o contrario p.speed = rl.Vector2Normalize(p.speed) p.speed = rl.Vector2Scale(p.speed, moveSpeed) } result := rl.Vector2Add(p.pos, p.speed) if result.Y - p.hitBoxRadius < 0 || result.Y + p.hitBoxRadius > float32(g.arenaHeight) { p.speed.Y = 0 } if result.X - p.hitBoxRadius < 0 || result.X + p.hitBoxRadius > float32(g.arenaWidth) { p.speed.X = 0 } p.pos = rl.Vector2Add(p.pos, p.speed) } func (p *player) shoot(g *game){ if p.focusMode { return } if g.frame % 5 != 0 { return } if rl.IsMouseButtonDown(rl.MouseLeftButton) { mouse := rl.GetMousePosition() direction := rl.Vector2Subtract(mouse, p.pos) direction = rl.Vector2Add(direction, p.speed) direction = rl.Vector2Normalize(direction) direction = rl.Vector2Scale(direction, p.bulletMoveSpeed) g.bullets = append(g.bullets, &bullet{ pos: p.pos, size: p.bulletSize, speed: direction, dmg: 1, enemy: false, }) } } func (p *player) checkHit(g *game) { for _, bullet := range g.bullets { if !bullet.enemy { continue } distance := rl.Vector2Distance(p.pos, bullet.pos) - bullet.size // fmt.Println(distance) if distance < p.hitBoxRadius { fmt.Println("hit!") } } } func (p *player) checkFocus() { // raylib não entende keybindings customizadas através do xmodmap? if rl.IsKeyDown(rl.KeyLeftShift) || rl.IsKeyDown(rl.KeyRightShift) { p.focusMode = true } else { p.focusMode = false } } func (p *player) update(g *game) { p.checkFocus() p.move(g) p.shoot(g) p.checkHit(g) // hitbox rl.DrawCircleV(p.pos, p.hitBoxRadius, rl.Red) if p.focusMode { return } // mira mouse := rl.GetMousePosition() inverted := rl.Vector2Subtract(p.pos, mouse) inverted = rl.Vector2Negate(inverted) inverted = rl.Vector2Normalize(inverted) inverted = rl.Vector2Scale(inverted, 2000) rl.DrawLineV(p.pos, rl.Vector2Add(mouse, inverted), rl.NewColor(255, 0, 0, 100)) } func main() { state := &game{arenaWidth: 450, arenaHeight: 700, interfaceWidth: 300} player := player{ pos: rl.Vector2{ X: float32(state.arenaWidth) / 2, Y: float32(state.arenaHeight) * 0.8, }, moveSpeed: 4, focusSpeedDecrease: 0.5, bulletMoveSpeed: 13, bulletSize: 8, hitBoxRadius: 5, } rl.SetTraceLog(rl.LogWarning | rl.LogDebug) rl.InitWindow(state.arenaWidth + state.interfaceWidth, state.arenaHeight, "danmaku") rl.SetTargetFPS(60) state.enemies = []*enemy{ { pos: rl.Vector2{X: 200, Y: 200}, health: 100, hitBoxRadius: 20, move: horizonalPattern(state), shoot: burstShootAtPlayer(state, &player, 8, 4), }, { pos: rl.Vector2{X: 169, Y: 285}, health: 10, hitBoxRadius:10, move: horizonalPattern(state), shoot: ShootAtPlayer(state, &player, 16, 3), }, // { // pos: rl.Vector2{X: 400, Y: 400}, // health: 10, // hitBoxRadius:10, // bulletMoveSpeed: 6, // move: foobarPattern(state), // shoot: shootStraightDown(state), // }, // { // pos: rl.Vector2{X: 200, Y: 200}, // health: 10, // hitBoxRadius:10, // bulletMoveSpeed: 6, // move: shootStill(), // shoot: shootStraightDown(state), // }, } currectScore := 0 for ; !rl.WindowShouldClose(); state.frame += 1 { rl.BeginDrawing() rl.ClearBackground(rl.Black) 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) player.update(state) for i := 0; i < len(state.enemies); i++ { state.enemies[i].update(state, i) } 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), ) currectScore += (state.score - currectScore) / 11 rl.DrawText(strconv.Itoa(currectScore), state.arenaWidth, 0, 50, rl.White) rl.EndDrawing() } rl.CloseWindow() }