commit c234616fdfaeb6c60c758bcefae05b9d4cbd23d7 Author: iegod Date: Tue Nov 5 06:28:08 2024 -0500 First commit. diff --git a/coordinates.go b/coordinates.go new file mode 100644 index 0000000..2265753 --- /dev/null +++ b/coordinates.go @@ -0,0 +1,6 @@ +package main + +type Coordinates struct { + X float64 + Y float64 +} diff --git a/explosion.go b/explosion.go new file mode 100644 index 0000000..6d9f101 --- /dev/null +++ b/explosion.go @@ -0,0 +1,40 @@ +package main + +type Explosion struct { + Radius float64 + Origin Coordinates + cycle int + Active bool +} + +func NewExplosion() *Explosion { + return &Explosion{ + cycle: 1, + Active: false, + } +} + +func (e *Explosion) Draw() { + +} + +func (e *Explosion) Update() { + + if e.Active { + e.Radius = float64(e.cycle) / 0.15 + e.cycle++ + } + +} + +func (e *Explosion) SetOrigin(origin Coordinates) { + e.Origin = origin +} + +func (e *Explosion) ToggleActivate() { + e.Active = !e.Active +} + +func (e *Explosion) Reset() { + e.cycle = 1 +} diff --git a/fly-eye.png b/fly-eye.png new file mode 100644 index 0000000..ab706ab Binary files /dev/null and b/fly-eye.png differ diff --git a/fly-eye2.png b/fly-eye2.png new file mode 100644 index 0000000..2b4d6c7 Binary files /dev/null and b/fly-eye2.png differ diff --git a/fly-eye3.png b/fly-eye3.png new file mode 100644 index 0000000..ab9aee5 Binary files /dev/null and b/fly-eye3.png differ diff --git a/game.go b/game.go new file mode 100644 index 0000000..fa02028 --- /dev/null +++ b/game.go @@ -0,0 +1,308 @@ +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] +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..72e262c --- /dev/null +++ b/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "log" + + "github.com/hajimehoshi/ebiten/v2" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +func main() { + ver := "Mover Test v0.05" + + fmt.Println(ver) + + moverGame := &Game{} + + ebiten.SetWindowSize(screenWidth*1.5, screenHeight*1.5) + ebiten.SetWindowTitle(ver) + + if err := ebiten.RunGame(moverGame); err != nil { + log.Fatal(err) + } + +} diff --git a/mover.go b/mover.go new file mode 100644 index 0000000..7f637b7 --- /dev/null +++ b/mover.go @@ -0,0 +1,168 @@ +package main + +import ( + "bytes" + "image" + "log" + + _ "embed" + "image/color" + _ "image/png" + + "github.com/hajimehoshi/ebiten/v2" +) + +var ( + flyeyeImage *ebiten.Image + flyeyeImage2 *ebiten.Image + flyeyeImage3 *ebiten.Image + + //go:embed fly-eye.png + flyeye_img []byte + //go:embed fly-eye2.png + flyeye_img2 []byte + //go:embed fly-eye3.png + flyeye_img3 []byte +) + +const ( + MoverActionDefault = iota + MoverActionDamaged + MoverActionDying + MoverActionExploding + MoverActionDead + MoverActionMax +) + +type MoverAction uint + +func init() { + img, _, err := image.Decode(bytes.NewReader(flyeye_img)) + if err != nil { + log.Fatal(err) + } + flyeyeImage = ebiten.NewImageFromImage(img) + + img, _, err = image.Decode(bytes.NewReader(flyeye_img2)) + if err != nil { + log.Fatal(err) + } + flyeyeImage2 = ebiten.NewImageFromImage(img) + + img, _, err = image.Decode(bytes.NewReader(flyeye_img3)) + if err != nil { + log.Fatal(err) + } + flyeyeImage3 = ebiten.NewImageFromImage(img) +} + +type Mover struct { + Sprite *ebiten.Image + Maks *ebiten.Image + MaksDest *ebiten.Image + Angle float64 + Pos Coordinates + Origin Coordinates + Action MoverAction + cycles int + rotating bool + Toggled bool + Hit bool + dyingcount int +} + +func NewMover() *Mover { + m := &Mover{ + Sprite: ebiten.NewImage(48, 48), + Maks: ebiten.NewImage(48, 48), + MaksDest: ebiten.NewImage(48, 48), + Action: MoverActionDefault, + cycles: 4, + Angle: 0, + rotating: false, + Toggled: false, + dyingcount: 0, + } + + m.Maks.Fill(color.White) + return m +} + +func (m *Mover) ToggleRotate() { + m.rotating = !m.rotating +} + +func (m *Mover) SetAngle(a float64) { + m.Angle = a +} + +func (m *Mover) SetOrigin(coords Coordinates) { + m.Origin = coords + m.Pos = coords +} + +func (m *Mover) Draw() { + m.Sprite.Clear() + m.MaksDest.Clear() + + idx := (m.cycles / 8) % 4 + + y0 := 0 + y1 := 48 + x0 := 48 * idx + x1 := x0 + 48 + + switch m.Action { + case MoverActionDefault: + m.Sprite.DrawImage(flyeyeImage.SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) + case MoverActionDamaged: + m.Sprite.DrawImage(flyeyeImage2.SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) + case MoverActionDying: + m.dyingcount++ + if (m.cycles/5)%2 == 0 { + + m.MaksDest.DrawImage(flyeyeImage2.SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) + op := &ebiten.DrawImageOptions{} + op.GeoM.Reset() + op.Blend = ebiten.BlendSourceAtop + m.MaksDest.DrawImage(m.Maks, op) + m.Sprite.DrawImage(m.MaksDest, nil) + + } else { + m.Sprite.DrawImage(flyeyeImage2.SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) + } + if m.dyingcount > 60 { + m.cycles = 0 + m.SetHit() + } + case MoverActionExploding: + m.Sprite.DrawImage(flyeyeImage3.SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) + if idx == 3 { + m.SetHit() + } + default: + } +} + +func (m *Mover) Update() { + /* + dx := 0. //40 * math.Cos(float64(m.cycles)/16) + dy := 0. //40 * math.Sin(float64(m.cycles)/16) + + m.Pos = Coordinates{X: m.Origin.X + dx, Y: m.Origin.Y + dy} + */ + /* + if m.rotating { + m.Angle = float64(m.cycles) / (math.Pi * 2) + } + */ + m.cycles++ +} + +func (m *Mover) SetHit() { + m.Action++ // = (m.Action + 1) % MoverActionMax +} + +func (m *Mover) ToggleColor() { + m.Toggled = !m.Toggled +} diff --git a/projectile.go b/projectile.go new file mode 100644 index 0000000..4e526d4 --- /dev/null +++ b/projectile.go @@ -0,0 +1,31 @@ +package main + +import "math" + +type Projectile struct { + Pos Coordinates + Velocity float64 + a float64 +} + +func NewProjectile(origin Coordinates, angle, velocity float64) *Projectile { + return &Projectile{ + Velocity: velocity, + a: angle, + Pos: origin, + } +} + +func (p *Projectile) Update() { + + dx := p.Velocity * math.Cos(p.a) + dy := p.Velocity * math.Sin(p.a) + + p.Pos.X += dx + p.Pos.Y += dy + +} + +func (p *Projectile) Draw() { + +}