package hero import ( "bytes" "github.com/hajimehoshi/ebiten/v2" "gitlab.com/kbr4/9heroja/collision" "gitlab.com/kbr4/9heroja/configuration" "gitlab.com/kbr4/9heroja/resources" "gitlab.com/kbr4/9heroja/weapons" "image" "image/color" "log" "time" ) const WalkSpeedMs = 300 const ReloadSpeedMs = 1000 var ( heroImage *ebiten.Image walkTicker *time.Ticker reloadTicker *time.Ticker ) type Hero struct { step int direction configuration.Direction IsWalking bool Health int // Health as a percentage (0-100) X float64 Y float64 gunLoaded bool } type spriteFramePosition struct { x int y int } func (h *Hero) DrawHero(screen *ebiten.Image) { // set movement positions to be hashmap of sprite positions for each direction movementPositions := map[configuration.Direction][]spriteFramePosition{} movementPositions[configuration.North] = []spriteFramePosition{ {0, 166}, {33, 166}, {66, 166}, {99, 166}, {132, 166}, {99, 166}, {66, 166}, {33, 166}, } movementPositions[configuration.NorthEast] = []spriteFramePosition{ {168, 0}, {201, 0}, {0, 33}, {33, 33}, {66, 33}, {33, 33}, {0, 33}, {201, 0}, } movementPositions[configuration.East] = []spriteFramePosition{ {132, 99}, {165, 99}, {198, 99}, {0, 132}, {33, 132}, {0, 132}, {198, 99}, {165, 99}, } movementPositions[configuration.SouthEast] = []spriteFramePosition{ {33, 66}, {66, 66}, {99, 66}, {132, 66}, {165, 66}, {132, 66}, {99, 66}, {66, 66}, } movementPositions[configuration.South] = []spriteFramePosition{ {66, 132}, {0, 99}, {33, 99}, {66, 99}, {99, 99}, {66, 99}, {33, 99}, {0, 99}, } movementPositions[configuration.SouthWest] = []spriteFramePosition{ {99, 33}, {33, 0}, {66, 0}, {99, 0}, {132, 0}, {99, 0}, {66, 0}, {33, 0}, } movementPositions[configuration.West] = []spriteFramePosition{ {198, 66}, {99, 132}, {132, 132}, {165, 132}, {198, 132}, {165, 132}, {132, 132}, {99, 132}, } movementPositions[configuration.NorthWest] = []spriteFramePosition{ {0, 0}, {132, 33}, {165, 33}, {198, 33}, {0, 66}, {198, 33}, {165, 33}, {132, 33}, } p := movementPositions[h.direction][h.step%8] op := &ebiten.DrawImageOptions{} screenWidth := screen.Bounds().Max.X screenHeight := screen.Bounds().Max.Y // ground op.GeoM.Reset() h.X = float64(screenWidth/2 - 16) h.Y = float64(screenHeight/2 - 16) op.GeoM.Translate(h.X, h.Y) //op.GeoM.Translate(float64(i*tileSize-floorMod(g.cameraX, tileSize)), // float64((ny-1)*tileSize-floorMod(g.cameraY, tileSize))) screen.DrawImage(heroImage.SubImage(image.Rect(p.x+1, p.y, p.x+resources.HeroTileSize, p.y+resources.HeroTileSize)).(*ebiten.Image), op) // Drawing the dynamic health bar below the hero maxHealthBarWidth := resources.HeroTileSize // Maximum width of the health bar (same as the character width) healthBarHeight := 5 // Height of the health bar healthBarX := float64(h.X) // Align with the hero healthBarY := float64(h.Y + 37) // Position below the hero // Calculate the current width of the health bar based on the hero's health currentHealthBarWidth := int(float64(maxHealthBarWidth) * (float64(h.Health) / 100.0)) // Determine health bar color based on health var healthColor color.Color switch { case h.Health >= 70: healthColor = color.RGBA{0, 255, 0, 255} // Green case h.Health >= 30: healthColor = color.RGBA{255, 165, 0, 255} // Orange default: healthColor = color.RGBA{255, 0, 0, 255} // Red } // Create health bar image with the current width healthBarImg := ebiten.NewImage(currentHealthBarWidth, healthBarHeight) healthBarImg.Fill(healthColor) // Draw health bar hbop := &ebiten.DrawImageOptions{} hbop.GeoM.Translate(healthBarX, healthBarY) screen.DrawImage(healthBarImg, hbop) } func init() { img, _, err := image.Decode(bytes.NewReader(resources.Hero_png)) if err != nil { log.Fatal(err) } heroImage = ebiten.NewImageFromImage(img) walkTicker = time.NewTicker(WalkSpeedMs * time.Millisecond) reloadTicker = time.NewTicker(ReloadSpeedMs * time.Millisecond) // go routine that runs on every tick and increases step } func NewHero() *Hero { hero := &Hero{ step: 0, IsWalking: false, Health: 100, gunLoaded: true, } go func() { for { select { case <-walkTicker.C: hero.step++ if hero.step > 7 { hero.step = 0 } } } }() go func() { for { select { case <-reloadTicker.C: hero.gunLoaded = true } } }() return hero } func (h *Hero) Walk() { if !h.IsWalking { walkTicker.Reset(WalkSpeedMs * time.Millisecond) h.IsWalking = true } } func (h *Hero) ChangeDirection(d configuration.Direction) { if d != configuration.PreviouslyHeld { h.direction = d } } func (h *Hero) Stop() { h.step = 2 walkTicker.Stop() h.IsWalking = false } func (h *Hero) StopShooting() { h.gunLoaded = false reloadTicker.Stop() } func (h *Hero) CollisionShape() collision.Polygon { return collision.Polygon{ Points: []collision.Point{ {X: h.X, Y: h.Y}, {X: h.X + 33, Y: h.Y}, {X: h.X + 33, Y: h.Y + 33}, {X: h.X, Y: h.Y + 33}, }, } } func (h *Hero) CollisionObjectType() collision.ObjectType { return collision.Hero } func (h *Hero) HandleCollisionEvent(other collision.Collidable) { coll := other.CollisionObjectType() switch coll { case collision.Zombie: h.Health -= 25 if h.Health <= 0 { h.Health = 10 } } } func (h *Hero) Fire(offsetX float64, offsetY float64) (bullet *weapons.Handgun) { if h.gunLoaded { h.gunLoaded = false bullet = weapons.NewHandgun() bullet.X = h.X + (resources.HeroTileSize / 2) bullet.Y = h.Y + (resources.HeroTileSize / 2) bullet.OffsetXAtStart = offsetX bullet.OffsetYAtStart = offsetY bullet.Fire(h.direction) return bullet } else { return nil } }