bullet-hell/main.go

534 lines
13 KiB
Go

package main
import (
"fmt"
"math"
// "path"
"strconv"
rl "github.com/gen2brain/raylib-go/raylib"
)
// diz para um inimigo como ele deve se mover
type movementPattern func(*enemy)rl.Vector2
// 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 hazard interface { // inimigos e projéteis
Pos() rl.Vector2
}
type game struct {
arenaWidth int32
arenaHeight int32
interfaceWidth int32
frame float32
enemies []*enemy
bullets []*bullet
score int
gameSpeed float32 // 1 = velocidade normal
backgroundColor rl.Color
}
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, 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 {
return func(e *enemy) {
if int(g.frame) % rate != 0 {
return
}
// radius := e.hitBoxRadius
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 int, bulletMoveSpeed float32) shootingPattern {
flag := true
return func(e *enemy) {
if int(g.frame) % 100 == 0 {
flag = !flag
}
if !flag {
return
}
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 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 shootStill() movementPattern {
return func(e *enemy) rl.Vector2 {
e.shoot(e)
return rl.Vector2{X: 0, Y: 0}
}
}
func horizonalPattern(g *game) movementPattern {
direction := rl.Vector2{X: 4, Y: 0}
return func(e *enemy) rl.Vector2 {
e.shoot(e)
result := rl.Vector2Add(direction, e.pos)
if !g.insideArena(result) {
direction = rl.Vector2Negate(direction)
}
return direction
}
}
// 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 = rl.Vector2Add(rl.Vector2Scale(e.move(e), g.gameSpeed), e.pos)
// 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)
}
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 int(g.frame) % 3 != 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,
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,
}
// rl.SetTraceLog(rl.LogWarning | rl.LogDebug)
rl.SetConfigFlags(rl.FlagMsaa4xHint)
rl.InitWindow(state.arenaWidth + state.interfaceWidth, state.arenaHeight, "danmaku")
rl.SetTargetFPS(60)
// 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,
// )
state.enemies = []*enemy{
{
pos: rl.Vector2{X: 200, Y: 200},
health: 100,
hitBoxRadius: 20,
move: horizonalPattern(state),
shoot: bulletExplosion(state, 60, 20, 2, 11),
},
{
pos: rl.Vector2{X: 100, Y: 100},
health: 100,
hitBoxRadius: 20,
move: horizonalPattern(state),
shoot: burstShootAtPlayer(state, &player, 20, 4),
},
{
pos: rl.Vector2{X: 50, Y: 250},
health: 100,
hitBoxRadius: 20,
move: horizonalPattern(state),
shoot: bulletExplosion(state, 60, 20, 2, 11),
},
}
currectScore := 0
for ; !rl.WindowShouldClose(); state.frame += state.gameSpeed {
rl.BeginDrawing()
// rl.BeginTextureMode(target)
rl.ClearBackground(state.backgroundColor)
state.backgroundColor = rl.Black
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),
)
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(strconv.Itoa(currectScore), state.arenaWidth, 0, 50, rl.White)
rl.DrawText(
strconv.Itoa(len(state.bullets)), state.arenaWidth, 50, 50, rl.White,
)
rl.DrawText(
fmt.Sprintf("%f", state.frame), state.arenaWidth, 100, 50, rl.White,
)
rl.DrawText(
fmt.Sprintf("%d", int(state.frame)), state.arenaWidth, 150, 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()
}