659 lines
14 KiB
Go
659 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"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
|
|
waves []*wave
|
|
currentWave int
|
|
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
|
|
}
|
|
|
|
type wave struct {
|
|
enemies []*enemy
|
|
entrance movementPattern
|
|
}
|
|
|
|
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)
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
|
|
type timer struct {
|
|
time, ttl float32
|
|
start float64
|
|
}
|
|
|
|
func (t *timer) tick(g *game) {
|
|
t.time += rl.GetFrameTime() * g.gameSpeed
|
|
}
|
|
|
|
func (t *timer) isTimeout() bool {
|
|
return t.time >= t.ttl
|
|
}
|
|
|
|
func (t *timer) reset() {
|
|
t.start = float64(t.time)
|
|
t.time = 0
|
|
}
|
|
|
|
func newTimer(duration float32) *timer {
|
|
return &timer {
|
|
time: 0,
|
|
ttl: duration,
|
|
start: rl.GetTime(),
|
|
}
|
|
}
|
|
|
|
var second float32 = 1
|
|
|
|
func burstShootAtPlayer(g *game, p *player, rate float32, bulletMoveSpeed float32) shootingPattern {
|
|
flag := true
|
|
off := newTimer(second)
|
|
on := newTimer(second*rate)
|
|
return func(e *enemy) {
|
|
|
|
fmt.Printf("\r %f %f", rl.GetTime(), rl.GetFrameTime())
|
|
|
|
off.tick(g)
|
|
|
|
if off.isTimeout() {
|
|
flag = !flag
|
|
fmt.Println()
|
|
fmt.Println(off)
|
|
off.reset()
|
|
fmt.Println(off)
|
|
fmt.Println()
|
|
}
|
|
|
|
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 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 sineHorizonalPattern(g *game) movementPattern {
|
|
direction := rl.Vector2{X: 4, Y: 0}
|
|
|
|
return func(e *enemy) rl.Vector2 {
|
|
if e.shoot != nil {
|
|
e.shoot(e)
|
|
}
|
|
|
|
|
|
result := rl.Vector2Add(direction, e.pos)
|
|
if !g.insideArena(result) {
|
|
direction = rl.Vector2Negate(direction)
|
|
}
|
|
|
|
sine := rl.Vector2{
|
|
X: 0,
|
|
Y: float32(math.Sin(float64(g.frame*0.04))*2),
|
|
}
|
|
|
|
return rl.Vector2Add(sine, 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) {
|
|
|
|
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.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 (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 (g *game) fullClear() bool {
|
|
for _, e := range g.waves[g.currentWave].enemies {
|
|
if e.health != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
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")
|
|
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,
|
|
// )
|
|
|
|
state.waves = []*wave{
|
|
{
|
|
enemies: []*enemy{
|
|
{
|
|
pos: rl.Vector2{X: 100, Y: 100},
|
|
health: 100,
|
|
hitBoxRadius: 20,
|
|
move: sineHorizonalPattern(state),
|
|
shoot: burstShootAtPlayer(state, &player, 0.1, 4),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
enemies: []*enemy{
|
|
{
|
|
pos: rl.Vector2{X: 100, Y: 100},
|
|
health: 100,
|
|
hitBoxRadius: 20,
|
|
move: horizonalPattern(state),
|
|
shoot: bulletExplosion(state, targetFPS, 20, 2, 11),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
enemies: []*enemy{
|
|
{
|
|
pos: rl.Vector2{X: 200, Y: 200},
|
|
health: 100,
|
|
hitBoxRadius: 20,
|
|
move: horizonalPattern(state),
|
|
shoot: bulletExplosion(state, targetFPS, 20, 2, 11),
|
|
},
|
|
{
|
|
pos: rl.Vector2{X: 100, Y: 100},
|
|
health: 100,
|
|
hitBoxRadius: 20,
|
|
move: horizonalPattern(state),
|
|
shoot: burstShootAtPlayer(state, &player, 0.2, 4),
|
|
},
|
|
{
|
|
pos: rl.Vector2{X: 50, Y: 250},
|
|
health: 100,
|
|
hitBoxRadius: 20,
|
|
move: horizonalPattern(state),
|
|
shoot: bulletExplosion(state, targetFPS, 20, 2, 11),
|
|
// shoot: shootStraightDown(state),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
|
|
if state.fullClear() {
|
|
state.currentWave++
|
|
}
|
|
|
|
if state.currentWave >= len(state.waves) {
|
|
break
|
|
}
|
|
state.enemies = state.waves[state.currentWave].enemies
|
|
for i := 0; i < len(state.enemies); i++ {
|
|
state.enemies[i].update(state)
|
|
}
|
|
|
|
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()
|
|
}
|