From e049e8c3d0618ccdc90462f36eeac964b82d4cc7 Mon Sep 17 00:00:00 2001 From: iegod Date: Sat, 16 Nov 2024 19:03:07 -0500 Subject: [PATCH] Fireballs and shadows. --- assets/hot.png | Bin 0 -> 4390 bytes assets/imagebank.go | 4 ++ elements/enemies.go | 1 + elements/fireball.go | 118 ++++++++++++++++++++++++++++++++ elements/flyeye.go | 4 ++ elements/flygoblin.go | 62 +++++++++++++---- gameelement/canvas.go | 142 ++++++++++++++++++++++++++++++--------- screenmanager/manager.go | 2 +- 8 files changed, 287 insertions(+), 46 deletions(-) create mode 100644 assets/hot.png create mode 100644 elements/fireball.go diff --git a/assets/hot.png b/assets/hot.png new file mode 100644 index 0000000000000000000000000000000000000000..abfc2696ba9e26a02d200292bab93321460e9106 GIT binary patch literal 4390 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 literal 0 HcmV?d00001 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,