diff --git a/configuration/config.go b/configuration/config.go index fa2621f..2082025 100644 --- a/configuration/config.go +++ b/configuration/config.go @@ -1,5 +1,7 @@ package configuration +import "math/rand" + type Direction int const ( @@ -13,3 +15,8 @@ const ( NorthWest PreviouslyHeld ) + +func Random(min, max int) float64 { + result := min + rand.Intn(max-min) + return float64(result) +} diff --git a/hero/hero.go b/hero/hero.go index 380eab7..d411f19 100644 --- a/hero/hero.go +++ b/hero/hero.go @@ -6,6 +6,7 @@ import ( "gitlab.com/kbr4/9heroja/configuration" "gitlab.com/kbr4/9heroja/resources" "image" + "image/color" "log" "time" ) @@ -21,6 +22,7 @@ type Hero struct { step int direction configuration.Direction IsWalking bool + Health int // Health as a percentage (0-100) } type spritePosition struct { @@ -132,6 +134,35 @@ func (h *Hero) DrawHero(screen *ebiten.Image) { // 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(screenWidth/2 - 16) // Align with the hero + healthBarY := float64(screenHeight/2 + 20) // 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() { @@ -149,6 +180,7 @@ func NewHero() *Hero { hero := &Hero{ step: 0, IsWalking: false, + Health: 33, } go func() { diff --git a/main.go b/main.go index 19cc49c..2d74b73 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "gitlab.com/kbr4/9heroja/hero" "gitlab.com/kbr4/9heroja/input" "gitlab.com/kbr4/9heroja/terrain" + "gitlab.com/kbr4/9heroja/zombie" _ "image/png" "log" ) @@ -49,6 +50,7 @@ type Game struct { hero *hero.Hero terrain *terrain.Terrain + zombies []*zombie.Zombie } func (g *Game) Update() error { @@ -67,6 +69,11 @@ func (g *Game) Update() error { func (g *Game) Draw(screen *ebiten.Image) { g.terrain.DrawTerrain(screen) g.hero.DrawHero(screen) + for _, z := range g.zombies { + z.OffsetX = g.terrain.PositionX + z.OffsetY = g.terrain.PositionY + z.DrawZombie(screen) + } } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { @@ -81,7 +88,21 @@ func init() { GameInstance = &Game{} GameInstance.control = &input.Keyboard{} GameInstance.hero = hero.NewHero() + GameInstance.zombies = []*zombie.Zombie{zombie.NewZombie(), zombie.NewZombie(), zombie.NewZombie(), zombie.NewZombie(), zombie.NewZombie()} + + // put zombies in random places on the screen but not too close to the hero or each other + for _, z := range GameInstance.zombies { + z.X = float64(configuration.Random(50, screenWidth-50)) + z.Y = float64(configuration.Random(50, screenHeight-50)) + for z.X > float64(screenWidth/2-64) && z.X < float64(screenWidth/2+64) && z.Y > float64(screenHeight/2-64) && z.Y < float64(screenHeight/2+64) { + z.X = float64(configuration.Random(0, screenWidth)) + z.Y = float64(configuration.Random(0, screenHeight)) + } + z.Walk() + } + GameInstance.terrain = terrain.NewTerrain() + GameInstance.hero.ChangeDirection(configuration.North) GameInstance.terrain.ChangeDirection(configuration.North) GameInstance.hero.Walk() diff --git a/resources/constants.go b/resources/constants.go index 91246f2..a3ea203 100644 --- a/resources/constants.go +++ b/resources/constants.go @@ -1,3 +1,4 @@ package resources const HeroTileSize = 33 +const ZombieTileSize = 32 diff --git a/resources/embed.go b/resources/embed.go index 1d1f45c..53e94d7 100644 --- a/resources/embed.go +++ b/resources/embed.go @@ -10,4 +10,7 @@ var ( //go:embed grass.png Grass_png []byte + + //go:embed zombie3.png + Zombie_png []byte ) diff --git a/resources/zombie1.png b/resources/zombie1.png new file mode 100644 index 0000000..7d97384 Binary files /dev/null and b/resources/zombie1.png differ diff --git a/resources/zombie2.png b/resources/zombie2.png new file mode 100644 index 0000000..6d89e83 Binary files /dev/null and b/resources/zombie2.png differ diff --git a/resources/zombie3.png b/resources/zombie3.png new file mode 100644 index 0000000..0111fb7 Binary files /dev/null and b/resources/zombie3.png differ diff --git a/terrain/terrain.go b/terrain/terrain.go index 418af0e..2e883cd 100644 --- a/terrain/terrain.go +++ b/terrain/terrain.go @@ -15,16 +15,16 @@ var ( ) type Terrain struct { - positionX float64 - positionY float64 + PositionX float64 + PositionY float64 direction configuration.Direction } func (t *Terrain) DrawTerrain(screen *ebiten.Image) { screenWidth := screen.Bounds().Max.X screenHeight := screen.Bounds().Max.Y - offsetX := int(t.positionX) % 64 - offsetY := int(t.positionY) % 64 + offsetX := int(t.PositionX) % 64 + offsetY := int(t.PositionY) % 64 op := &ebiten.DrawImageOptions{} for i := -1; i <= screenWidth/64+1; i++ { for j := -1; j <= screenHeight/64+1; j++ { @@ -47,23 +47,23 @@ func init() { func NewTerrain() *Terrain { return &Terrain{ - positionX: 0, - positionY: 0, + PositionX: 0, + PositionY: 0, } } func (t *Terrain) Move() { if slices.Contains([]configuration.Direction{configuration.North, configuration.NorthEast, configuration.NorthWest}, t.direction) { - t.positionY += 1 + t.PositionY += 1 } if slices.Contains([]configuration.Direction{configuration.South, configuration.SouthEast, configuration.SouthWest}, t.direction) { - t.positionY -= 1 + t.PositionY -= 1 } if slices.Contains([]configuration.Direction{configuration.East, configuration.NorthEast, configuration.SouthEast}, t.direction) { - t.positionX -= 1 + t.PositionX -= 1 } if slices.Contains([]configuration.Direction{configuration.West, configuration.NorthWest, configuration.SouthWest}, t.direction) { - t.positionX += 1 + t.PositionX += 1 } } diff --git a/zombie/zombie.go b/zombie/zombie.go new file mode 100644 index 0000000..4c17d5a --- /dev/null +++ b/zombie/zombie.go @@ -0,0 +1,114 @@ +package zombie + +import ( + "bytes" + "github.com/hajimehoshi/ebiten/v2" + "gitlab.com/kbr4/9heroja/configuration" + "gitlab.com/kbr4/9heroja/resources" + "image" + "log" + "time" +) + +const WalkSpeedMs = 50 + +var ( + zombieImage *ebiten.Image + walkTicker *time.Ticker +) + +type Zombie struct { + step int + direction configuration.Direction + IsWalking bool + X float64 + Y float64 + OffsetX float64 + OffsetY float64 +} + +type spritePosition struct { + x int + y int +} + +func (z *Zombie) DrawZombie(screen *ebiten.Image) { + + // set movement positions to be hashmap of sprite positions for each direction + + movementPositions := map[configuration.Direction][]spritePosition{} + movementPositions[configuration.North] = []spritePosition{ + {0, 0}, + {32, 0}, + {64, 0}, + {96, 0}, + } + movementPositions[configuration.NorthEast] = movementPositions[configuration.North] + movementPositions[configuration.East] = movementPositions[configuration.North] + movementPositions[configuration.SouthEast] = movementPositions[configuration.North] + movementPositions[configuration.South] = movementPositions[configuration.North] + movementPositions[configuration.SouthWest] = movementPositions[configuration.North] + movementPositions[configuration.West] = movementPositions[configuration.North] + movementPositions[configuration.NorthWest] = movementPositions[configuration.North] + + p := movementPositions[z.direction][z.step%4] + + op := &ebiten.DrawImageOptions{} + op.GeoM.Reset() + op.GeoM.Translate(z.X+z.OffsetX, z.Y+z.OffsetY) + op.GeoM.Scale(1, 1) + + screen.DrawImage(zombieImage.SubImage(image.Rect(p.x+1, p.y, p.x+resources.ZombieTileSize, p.y+resources.HeroTileSize)).(*ebiten.Image), op) +} + +func init() { + img, _, err := image.Decode(bytes.NewReader(resources.Zombie_png)) + if err != nil { + log.Fatal(err) + } + zombieImage = ebiten.NewImageFromImage(img) + walkTicker = time.NewTicker(WalkSpeedMs * time.Millisecond) + // go routine that runs on every tick and increases step + +} + +func NewZombie() *Zombie { + hero := &Zombie{ + step: 0, + IsWalking: false, + } + + go func() { + for { + select { + case <-walkTicker.C: + hero.step++ + if hero.step > 3 { + hero.step = 0 + } + } + } + }() + return hero +} + +func (z *Zombie) Walk() { + + if !z.IsWalking { + walkTicker.Reset(WalkSpeedMs * time.Millisecond) + z.IsWalking = true + } + +} + +func (z *Zombie) ChangeDirection(d configuration.Direction) { + if d != configuration.PreviouslyHeld { + z.direction = d + } +} + +func (z *Zombie) Stop() { + z.step = 2 + walkTicker.Stop() + z.IsWalking = false +}