Files
survive/game.go
2024-11-05 06:28:08 -05:00

309 lines
8.3 KiB
Go

package main
import (
"fmt"
"image/color"
"log"
"math"
"math/rand/v2"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/vector"
)
const (
MOVER_WIDTH = 48
MOVER_HEIGHT = 48
)
type Game struct {
collisionMask *ebiten.Image
projectileMask *ebiten.Image
Pos Coordinates
initialized bool
mover *Mover
projectiles map[int]*Projectile
explosion *Explosion
counter int
targets []*Mover
gamepadIDsBuf []ebiten.GamepadID
gamepadIDs map[ebiten.GamepadID]struct{}
//axes map[ebiten.GamepadID][]string
//pressedButtons map[ebiten.GamepadID][]string
}
func (g *Game) Initialize() {
origin := Coordinates{X: 640 / 2, Y: 480 / 2}
g.mover = NewMover()
g.mover.SetOrigin(origin)
g.mover.ToggleRotate()
g.collisionMask = ebiten.NewImage(screenWidth, screenHeight)
g.projectileMask = ebiten.NewImage(screenWidth, screenHeight)
/* single target
g.target = NewMover()
g.target.SetOrigin(Coordinates{X: rand.Float64() * 640, Y: rand.Float64() * 480})
*/
//g.projectiles = append(g.projectiles, NewProjectile(Coordinates{X: 640 / 2, Y: 480 / 2}, 0., 5.))
g.explosion = NewExplosion()
g.explosion.SetOrigin(origin)
}
func (g *Game) Update() error {
if g.gamepadIDs == nil {
g.gamepadIDs = map[ebiten.GamepadID]struct{}{}
}
g.gamepadIDsBuf = inpututil.AppendJustConnectedGamepadIDs(g.gamepadIDsBuf[:0])
for _, id := range g.gamepadIDsBuf {
log.Printf("gamepad connected: id: %d, SDL ID: %s", id, ebiten.GamepadSDLID(id))
g.gamepadIDs[id] = struct{}{}
}
for id := range g.gamepadIDs {
if inpututil.IsGamepadJustDisconnected(id) {
log.Printf("gamepad disconnected: id: %d", id)
delete(g.gamepadIDs, id)
}
}
//handle gamepad input
inpx := ebiten.GamepadAxisValue(0, 0)
inpy := ebiten.GamepadAxisValue(0, 1)
if inpx >= 0.15 || inpx <= -0.15 {
g.mover.Pos.X += ebiten.GamepadAxisValue(0, 0) * 5
}
if inpy >= 0.15 || inpy <= -0.15 {
g.mover.Pos.Y += ebiten.GamepadAxisValue(0, 1) * 5
}
if !g.initialized {
g.Initialize()
g.projectiles = make(map[int]*Projectile)
g.initialized = true
} else {
if len(g.gamepadIDs) > 0 {
xaxis := ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickHorizontal)
yaxis := ebiten.StandardGamepadAxisValue(0, ebiten.StandardGamepadAxisRightStickVertical)
maxButton := ebiten.GamepadButton(ebiten.GamepadButtonCount(0))
for b := ebiten.GamepadButton(0); b < maxButton; b++ {
if ebiten.IsGamepadButtonPressed(0, ebiten.GamepadButton7) {
if !g.explosion.Active {
g.explosion.SetOrigin(g.mover.Pos)
g.explosion.Reset()
g.explosion.ToggleActivate()
}
}
}
if yaxis <= 0.09 && yaxis >= -0.09 {
yaxis = 0
}
if xaxis <= 0.09 && xaxis >= -0.09 {
xaxis = 0
}
inputangle := math.Atan2(yaxis, xaxis)
g.mover.SetAngle(inputangle)
}
/*
for id := range g.gamepadIDs {
xaxis := ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickHorizontal)
yaxis := ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickVertical)
inputangle := math.Atan2(yaxis, xaxis)
g.mover.SetAngle(inputangle)
}
*/
g.mover.Update()
g.explosion.Update()
for _, target := range g.targets {
if !target.Hit {
dx := g.mover.Pos.X - target.Pos.X
dy := g.mover.Pos.Y - target.Pos.Y
//dist := math.Sqrt(dx*dx + dy + dy)
angle := math.Atan2(dy, dx)
maxspeed := 3.
target.Pos.X += maxspeed * math.Cos(angle)
target.Pos.Y += maxspeed * math.Sin(angle)
}
target.Update()
}
for k, p := range g.projectiles {
//for i := 0; i < len(g.projectiles); i++ {
// g.projectiles[i].Update()
p.Update()
//if g.projectiles[i].Pos.X < 5 || g.projectiles[i].Pos.X > 635 || g.projectiles[i].Pos.Y < 5 || g.projectiles[i].Pos.Y > 475 {
if p.Pos.X < 5 || p.Pos.X > 635 || p.Pos.Y < 5 || p.Pos.Y > 475 {
p.Velocity = 0
delete(g.projectiles, k)
}
//compute collisions
for _, target := range g.targets {
//first, boundary check
if p.Pos.X >= target.Pos.X-MOVER_WIDTH/2 && p.Pos.X <= target.Pos.X+MOVER_WIDTH/2 && p.Pos.Y >= target.Pos.Y-MOVER_HEIGHT/2 && p.Pos.Y <= target.Pos.Y+MOVER_HEIGHT/2 && target.Action == MoverActionDamaged {
fmt.Println("potential collision")
g.collisionMask.Clear()
g.collisionMask.DrawImage(g.projectileMask, nil)
op := &ebiten.DrawImageOptions{}
op.GeoM.Reset()
op.Blend = ebiten.BlendSourceIn
op.GeoM.Translate(target.Pos.X-MOVER_WIDTH/2, target.Pos.Y-MOVER_HEIGHT/2)
g.collisionMask.DrawImage(target.Sprite, op)
//var pixels []byte = make([]byte, MOVER_WIDTH*MOVER_HEIGHT*4)
var pixels []byte = make([]byte, screenWidth*screenHeight*4)
g.collisionMask.ReadPixels(pixels)
for i := 0; i < len(pixels); i = i + 4 {
if pixels[i+3] != 0 {
fmt.Println("pixel collision")
delete(g.projectiles, k)
//target.ToggleColor()
target.SetHit()
//target.SetOrigin(Coordinates{X: rand.Float64() * 640, Y: rand.Float64() * 480})
target.Hit = true
break
}
}
}
}
}
//append new projectiles
if g.counter%14 == 0 {
//g.projectiles = append(g.projectiles, NewProjectile(Coordinates{X: g.mover.Pos.X, Y: g.mover.Pos.Y}, g.mover.Angle, 5.))
//g.projectiles = append(g.projectiles, NewProjectile(Coordinates{X: g.mover.Pos.X, Y: g.mover.Pos.Y}, g.mover.Angle+math.Pi, 5.))
g.projectiles[g.counter] = NewProjectile(Coordinates{X: g.mover.Pos.X, Y: g.mover.Pos.Y}, g.mover.Angle, 5.)
g.projectiles[g.counter+1] = NewProjectile(Coordinates{X: g.mover.Pos.X, Y: g.mover.Pos.Y}, g.mover.Angle+math.Pi, 5.)
}
//add new target with increasing frequency
f := 40000 / (g.counter + 1)
if g.counter%f == 0 {
g.targets = append(g.targets, NewMover())
g.targets[len(g.targets)-1].SetOrigin(Coordinates{X: rand.Float64() * 640, Y: rand.Float64() * 480})
}
//handle explosion updates
if g.explosion.Active {
if g.explosion.Radius > math.Sqrt(640*640+480*480) {
g.explosion.ToggleActivate()
g.explosion.Reset()
}
//check collisions
for _, target := range g.targets {
dx := target.Pos.X - g.mover.Pos.X
dy := target.Pos.Y - g.mover.Pos.Y
r := math.Sqrt(dx*dx + dy*dy)
if r >= g.explosion.Radius-5 && r <= g.explosion.Radius+5 && target.Action <= MoverActionDefault {
//target.ToggleColor()
target.SetHit()
}
}
}
g.counter++
}
g.CleanupTargets()
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
g.mover.Draw()
op := &ebiten.DrawImageOptions{}
/*
dx := 40 * math.Cos(float64(g.counter)/16)
dy := 40 * math.Sin(float64(g.counter)/16)
a := float64(g.counter) / (math.Pi * 2)
*/
op.GeoM.Translate(-MOVER_WIDTH/2, -MOVER_HEIGHT/2)
op.GeoM.Rotate(g.mover.Angle)
op.GeoM.Translate(g.mover.Pos.X, g.mover.Pos.Y)
screen.DrawImage(g.mover.Sprite, op)
for _, target := range g.targets {
target.Draw()
op.GeoM.Reset()
op.GeoM.Translate(-MOVER_WIDTH/2, -MOVER_HEIGHT/2)
op.GeoM.Rotate(target.Angle)
op.GeoM.Translate(target.Pos.X, target.Pos.Y)
screen.DrawImage(target.Sprite, op)
}
g.projectileMask.Clear()
//ebitenutil.DrawCircle()
for _, p := range g.projectiles {
//vector.DrawFilledCircle(screen, float32(p.Pos.X), float32(p.Pos.Y), 3, color.White, true)
vector.DrawFilledCircle(g.projectileMask, float32(p.Pos.X), float32(p.Pos.Y), 3, color.White, true)
}
screen.DrawImage(g.projectileMask, nil)
vector.StrokeCircle(screen, float32(g.explosion.Origin.X), float32(g.explosion.Origin.Y), float32(g.explosion.Radius), 3, color.White, true)
/*for _, gamepad ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickHorizontal),
ebiten.StandardGamepadAxisValue(id, ebiten.StandardGamepadAxisRightStickVertical))*/
}
func (g *Game) Layout(width, height int) (int, int) {
return screenWidth, screenHeight
}
func (g *Game) CleanupTargets() {
// remove dead targets by 1) iterating over all targets
i := 0
for _, target := range g.targets {
//moving valid targets to the front of the slice
if target.Action < MoverActionDead {
g.targets[i] = target
i++
}
}
//then culling the last elements of the slice
if len(g.targets)-i > 0 {
fmt.Printf("Removing %d elements\n", len(g.targets)-i)
}
for j := i; j < len(g.targets); j++ {
g.targets[j] = nil
}
g.targets = g.targets[:i]
}