diff --git a/assets/hot.png b/assets/hot.png new file mode 100644 index 0000000..abfc269 Binary files /dev/null and b/assets/hot.png differ diff --git a/assets/imagebank.go b/assets/imagebank.go index 5e7b7db..00854df 100644 --- a/assets/imagebank.go +++ b/assets/imagebank.go @@ -26,6 +26,7 @@ const ( WormDamaged ImgAssetName = "WormDamaged" Worm ImgAssetName = "WormDefault" Cloud ImgAssetName = "Cloud" + Fireball ImgAssetName = "Fireball" ) var ( @@ -57,6 +58,8 @@ var ( wormdefault_img []byte //go:embed cloud.png cloud_img []byte + //go:embed hot.png + fireball_img []byte ) func LoadImages() { @@ -75,6 +78,7 @@ func LoadImages() { ImageBank[WormDamaged] = LoadImagesFatal(worm_img) ImageBank[Worm] = LoadImagesFatal(wormdefault_img) ImageBank[Cloud] = LoadImagesFatal(cloud_img) + ImageBank[Fireball] = LoadImagesFatal(fireball_img) } diff --git a/elements/enemies.go b/elements/enemies.go index f4b553d..bf8d5f9 100644 --- a/elements/enemies.go +++ b/elements/enemies.go @@ -24,4 +24,5 @@ type Enemies interface { SetExplosionInitiated() Health() int MaxHealth() int + GetAngle() float64 } diff --git a/elements/fireball.go b/elements/fireball.go new file mode 100644 index 0000000..603a82d --- /dev/null +++ b/elements/fireball.go @@ -0,0 +1,118 @@ +package elements + +import ( + "image" + "math" + "mover/assets" + "mover/gamedata" + + "github.com/hajimehoshi/ebiten/v2" +) + +type FireBall struct { + Sprite *ebiten.Image + position gamedata.Coordinates + angle float64 + velocity float64 + cycle int +} + +func NewFireBall(angle, velocity float64) *FireBall { + fb := &FireBall{ + Sprite: ebiten.NewImage(50, 50), + cycle: 0, + angle: angle, + velocity: velocity, + } + return fb +} + +func (fb *FireBall) Update() error { + + fb.position.X += fb.velocity * math.Cos(fb.angle) + fb.position.Y += fb.velocity * math.Sin(fb.angle) + + fb.cycle++ + return nil +} + +func (fb *FireBall) Draw() { + fb.Sprite.Clear() + //fb.Sprite.Fill(color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xff}) + + idx := fb.cycle / 8 % 5 + x0 := idx * 50 + x1 := x0 + 50 + y0 := 0 + y1 := 50 + + fb.Sprite.DrawImage(assets.ImageBank[assets.Fireball].SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), nil) +} + +func (fb *FireBall) GetPosition() gamedata.Coordinates { + return fb.position +} + +func (fb *FireBall) SetPosition(pos gamedata.Coordinates) { + fb.position = pos +} + +func (fb *FireBall) SetTarget(gamedata.Coordinates) { + +} + +func (fb *FireBall) GetAngle() float64 { + return fb.angle +} + +func (fb *FireBall) GetVelocity() float64 { + return fb.velocity +} + +func (fb *FireBall) GetSprite() *ebiten.Image { + return fb.Sprite +} + +func (fb *FireBall) ClearTouched() { + +} + +func (fb *FireBall) ExplosionInitiated() bool { + return false +} + +func (fb *FireBall) GetEnemyState() gamedata.EnemyState { + return gamedata.EnemyStateDefault +} + +func (fb *FireBall) SetHit() { + +} + +func (fb *FireBall) SetToggle() { + +} + +func (fb *FireBall) IsToggled() bool { + return false +} + +func (fb *FireBall) SetTouched() { + +} + +func (fb *FireBall) IsTouched() bool { + return false +} + +func (fb *FireBall) SetExplosionInitiated() { + +} + +func (fb *FireBall) Health() int { + return 0 +} + +func (fb *FireBall) MaxHealth() int { + return 1 +} diff --git a/elements/flyeye.go b/elements/flyeye.go index 717f3ca..cf67901 100644 --- a/elements/flyeye.go +++ b/elements/flyeye.go @@ -171,3 +171,7 @@ func (f *FlyEye) Health() int { func (f *FlyEye) MaxHealth() int { return 1 } + +func (f *FlyEye) GetAngle() float64 { + return 0 +} diff --git a/elements/flygoblin.go b/elements/flygoblin.go index 5e51cfb..98a0aa4 100644 --- a/elements/flygoblin.go +++ b/elements/flygoblin.go @@ -3,6 +3,7 @@ package elements import ( "image" "image/color" + "math/rand/v2" "mover/assets" "mover/gamedata" @@ -14,20 +15,23 @@ const ( ) type FlyGoblin struct { - Sprite *ebiten.Image - Maks *ebiten.Image - MaksDest *ebiten.Image - position gamedata.Coordinates - target gamedata.Coordinates - state gamedata.EnemyState - cycle int - health int - damageduration int - right bool - touched bool - toggle bool - sploding bool - damage bool + Sprite *ebiten.Image + Maks *ebiten.Image + MaksDest *ebiten.Image + position gamedata.Coordinates + target gamedata.Coordinates + state gamedata.EnemyState + cycle int + health int + damageduration int + right bool + touched bool + toggle bool + sploding bool + damage bool + called bool + deathcallback func() + fireballcallback func() } func NewFlyGoblin() *FlyGoblin { @@ -37,6 +41,7 @@ func NewFlyGoblin() *FlyGoblin { MaksDest: ebiten.NewImage(96, 96), health: FG_MAXHEALTH, damageduration: 0, + called: false, } fg.Maks.Fill(color.White) return fg @@ -61,6 +66,22 @@ func (f *FlyGoblin) Update() error { f.position.X += dx / 48 f.position.Y += dy / 48 + //10% chance to summon fireball + fb := rand.Float64() >= 0.9 + + if (f.cycle/8)%4 == 0 && fb { + if f.fireballcallback != nil { + f.fireballcallback() + } + } + + } + + if f.state == gamedata.EnemyStateDead && !f.called { + f.called = true + if f.deathcallback != nil { + f.deathcallback() + } } f.cycle++ @@ -107,6 +128,7 @@ func (f *FlyGoblin) Draw() { f.Sprite.DrawImage(assets.ImageBank[assets.FlyEyeDying].SubImage(image.Rect(x0, y0, x1, y1)).(*ebiten.Image), op) if idx == 3 { f.state = gamedata.EnemyStateDead + } } @@ -182,3 +204,15 @@ func (f *FlyGoblin) Health() int { func (f *FlyGoblin) MaxHealth() int { return FG_MAXHEALTH } + +func (f *FlyGoblin) SetDeathEvent(somefunc func()) { + f.deathcallback = somefunc +} + +func (f *FlyGoblin) SetFireballCallback(somefunc func()) { + f.fireballcallback = somefunc +} + +func (f *FlyGoblin) GetAngle() float64 { + return 0 +} diff --git a/gameelement/canvas.go b/gameelement/canvas.go index 921aa8b..0fdb30d 100644 --- a/gameelement/canvas.go +++ b/gameelement/canvas.go @@ -26,12 +26,14 @@ type Canvas struct { initialized bool goblinspawned bool + goblindead bool lastInputs gamedata.GameInputs runtime float64 counter int score int hero *elements.Hero charge *elements.Explosion + goblin *elements.FlyGoblin enemies []elements.Enemies projectiles []*elements.Projectile gameover bool @@ -49,6 +51,7 @@ func NewCanvas(a gamedata.Area) *Canvas { initialized: false, gameover: false, goblinspawned: false, + goblindead: false, score: 0, runtime: 0., counter: 0, @@ -97,10 +100,35 @@ func (c *Canvas) Draw(drawimg *ebiten.Image) { c.Sprite.DrawImage(assets.ImageBank[assets.Weapon], op) } + for _, es := range c.enemies { + if es.GetEnemyState() < gamedata.EnemyStateExploding { + + dx := float64(assets.ImageBank[assets.FlyEyeShadow].Bounds().Dx()) / 2 + dy := float64(assets.ImageBank[assets.FlyEyeShadow].Bounds().Dy()) / 2 + sx := float64(es.GetSprite().Bounds().Dx()) / 48 + sy := float64(es.GetSprite().Bounds().Dy()) / 48 + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(-dx, -dy) + op.GeoM.Scale(sx, sy) + op.GeoM.Translate(es.GetPosition().X, es.GetPosition().Y+float64(es.GetSprite().Bounds().Dx())/2) + c.Sprite.DrawImage(assets.ImageBank[assets.FlyEyeShadow], op) + } + } + for _, e := range c.enemies { e.Draw() + + xshift := float64(e.GetSprite().Bounds().Dx() / 2) + yshift := float64(e.GetSprite().Bounds().Dy() / 2) + op := &ebiten.DrawImageOptions{} - op.GeoM.Translate(e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2, e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2) + op.GeoM.Translate(-xshift, -yshift) + op.GeoM.Rotate(e.GetAngle()) + op.GeoM.Translate(e.GetPosition().X, e.GetPosition().Y) + + //op := &ebiten.DrawImageOptions{} + //op.GeoM.Translate(e.GetPosition().X-float64(e.GetSprite().Bounds().Dx())/2, e.GetPosition().Y-float64(e.GetSprite().Bounds().Dy())/2) c.Sprite.DrawImage(e.GetSprite(), op) //do we need a health bar for this enemy? @@ -152,6 +180,7 @@ func (c *Canvas) Initialize() { c.counter = 0 c.runtime = 0. c.goblinspawned = false + c.goblindead = false //temporary c.hero.Action = elements.HeroActionDefault @@ -354,43 +383,70 @@ func (c *Canvas) UpdateEnemies() { } if !c.gameover { - if !c.goblinspawned { - //spawn new enemies - f := 40000 / (c.counter + 1) - - if c.counter%f == 0 { - newenemy := elements.NewFlyEye() - - x0 := rand.Float64() * 640 - y0 := rand.Float64() * 480 - quadrant := rand.IntN(3) - - switch quadrant { - case 0: - newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: -48}) - case 1: - newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: 480 + 48}) - case 2: - newenemy.SetPosition(gamedata.Coordinates{X: -48, Y: y0}) - case 3: - newenemy.SetPosition(gamedata.Coordinates{X: 640 + x0, Y: y0}) - } - - newenemy.SetTarget(c.hero.Pos) - - c.enemies = append(c.enemies, newenemy) - } + if !c.goblinspawned || c.goblindead { + c.SpawnFlyEyes() } - if !c.goblinspawned && c.counter > 1200 { - newfg := elements.NewFlyGoblin() - c.enemies = append(c.enemies, newfg) - c.goblinspawned = true + if !c.goblinspawned { //&& c.counter > 1200 && !c.goblindead { + c.SpawnGoblin() } } } +func (c *Canvas) SpawnFlyEyes() { + //spawn new enemies + f := 40000 / (c.counter + 1) + + if c.counter%f == 0 { + newenemy := elements.NewFlyEye() + + x0 := rand.Float64() * 640 + y0 := rand.Float64() * 480 + quadrant := rand.IntN(3) + + switch quadrant { + case 0: + newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: -48}) + case 1: + newenemy.SetPosition(gamedata.Coordinates{X: x0, Y: 480 + 48}) + case 2: + newenemy.SetPosition(gamedata.Coordinates{X: -48, Y: y0}) + case 3: + newenemy.SetPosition(gamedata.Coordinates{X: 640 + x0, Y: y0}) + } + + newenemy.SetTarget(c.hero.Pos) + + c.enemies = append(c.enemies, newenemy) + } +} + +func (c *Canvas) SpawnGoblin() { + newfg := elements.NewFlyGoblin() + newfg.SetDeathEvent(c.GoblinDeathEvent) + newfg.SetFireballCallback(c.GoblinFireballEvent) + + x0 := rand.Float64() * 640 + y0 := rand.Float64() * 480 + quadrant := rand.IntN(3) + + switch quadrant { + case 0: + newfg.SetPosition(gamedata.Coordinates{X: x0, Y: -96}) + case 1: + newfg.SetPosition(gamedata.Coordinates{X: x0, Y: 480 + 48}) + case 2: + newfg.SetPosition(gamedata.Coordinates{X: -96, Y: y0}) + case 3: + newfg.SetPosition(gamedata.Coordinates{X: 640 + x0, Y: y0}) + } + + c.goblin = newfg + c.enemies = append(c.enemies, newfg) + c.goblinspawned = true +} + func (c *Canvas) HasCollided(mask *ebiten.Image, size int) bool { var result bool = false var pixels []byte = make([]byte, size) @@ -430,3 +486,27 @@ func (c *Canvas) CleanupTargets() { } c.enemies = c.enemies[:i] } + +func (c *Canvas) GoblinDeathEvent() { + c.goblindead = true + c.goblinspawned = false + c.score += 10 +} + +func (c *Canvas) GoblinFireballEvent() { + + if !c.gameover { + velocity := 8. + dx := c.hero.Pos.X - c.goblin.GetPosition().X + dy := c.hero.Pos.Y - c.goblin.GetPosition().Y + angle := math.Atan2(dy, dx) + + //add some randomness to the angle + arand := rand.Float64() * math.Pi / 3 + + newfb := elements.NewFireBall(angle+arand, velocity) + newfb.SetPosition(c.goblin.GetPosition()) + c.enemies = append(c.enemies, newfb) + + } +} diff --git a/screenmanager/manager.go b/screenmanager/manager.go index 610346c..2cc87ee 100644 --- a/screenmanager/manager.go +++ b/screenmanager/manager.go @@ -26,7 +26,7 @@ func NewManager() Manager { return Manager{ Info: gamedata.GameInfo{ Name: "survive", - Version: "0.26", + Version: "0.30", Dimensions: gamedata.Area{ Width: defaultWidth, Height: defaultHeight,