From c5b8fc40cb33b28148776038e0343eb1f537aa3e Mon Sep 17 00:00:00 2001 From: Senad Uka Date: Thu, 30 Nov 2023 14:49:01 +0100 Subject: [PATCH] Zombie and health bar --- configuration/config.go | 7 +++ hero/hero.go | 32 +++++++++++ main.go | 21 ++++++++ resources/constants.go | 1 + resources/embed.go | 3 ++ resources/zombie1.png | Bin 0 -> 653 bytes resources/zombie2.png | Bin 0 -> 691 bytes resources/zombie3.png | Bin 0 -> 830 bytes terrain/terrain.go | 20 +++---- zombie/zombie.go | 114 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 resources/zombie1.png create mode 100644 resources/zombie2.png create mode 100644 resources/zombie3.png create mode 100644 zombie/zombie.go 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 0000000000000000000000000000000000000000..7d9738438bd9b0e917506aa63fb171a47e6f6822 GIT binary patch literal 653 zcmV;80&@L{P)Px%M@d9MRCt{2nz2g5Kp4mW5osY0P{fRzz^{9WwMKGPuO)!ZkGZ?!3!)#_vCcOVZ!}uK#@bniOEMSS%JxU;CYQzTau* z%>KI`0@D+xQIrF~*KH2~aMhm~e-^XpJnWdBDBu+OuWT0{i~|7t-!Gy`AutYrTz2X% zgTAn-WYdFj0Fopr8W3?$s!Nn?0GN;8ifszMO%Fx^5I~BBpk({Nx9veY05?zPIRKnZ zjv$-355u)9(}gJ6KJZ<5&=zpHbwTz*=k1@=y_*Y>A(7l`9Ha8(Ak@#|t! z@XH>m)_;@NpJeZNSd6{gT?7Dda`{lC{`lYnfc=*ylzhRy z>dyk1Rpz^%y9@rWUI$mH+_Bd!N`B+#escMc%PHJ8&V0Z5+7&>Q8P##yVm={%8bv>* z%=J~keEWQ@3PAl=DCXPeYXv~=z$wIhT0T_Di^~$oeEWPSKLc3}RFWjQ$_o$GPx%ZAnByRCt{2nL%p8Koo|brL>T)MY5iQ2to_u6$H_ZC#WkygskET+$cis(4F9- z$H>B^bX}n-bh;=L(hzkfGjCor^MeSQ%=b_HGkNm_aB?cEaM0q8ecc`!I^fCXpnSfL z>dMCSP->8H(BdDTkI;PjGHS-L?R&T@V_>p5G@9?<0059Y_pB|6b!B6EP!2$vxR)PD zo_nbg8>%ZC(}QvVgtFj=*EcN_j(vR|001ELeIEKgNkvS_4x$mKDLoxH`=stv&z%z;?U6_)IdTqV(J37pY2o)Kw^-!0Z-8>`8?%I%KkZ8*JPB zA`hnc(urY=agnf7%Te}w(`l6DW3wsvc@HM}+K&LI>y~r?hVypXg*tT%V~mHs&vmL& zUH(x&ZwZ2qW3=M4>e8 z$Ew3RJ`rE}&mc-UJ}qBrdu4hH=J>dL*9fY)qywOmER4Ir@p1T40Vp#elNZ79Y57h! ZtY6I^-2@JXtGWOH002ovPDHLkV1i&JIw$}D literal 0 HcmV?d00001 diff --git a/resources/zombie3.png b/resources/zombie3.png new file mode 100644 index 0000000000000000000000000000000000000000..0111fb733e0b486807a0272e5ca9344aa711358b GIT binary patch literal 830 zcmV-E1Ht@>P)Px%_(?=TRCt{2nlX~2Fc3wXtu5|AL_p{ZIGF=Daq7UHggfBKRj#cfvhUyx)`?F* zytN6}AmR>Aw84&;Mu<^M(}EpaKN$*(e%-C6L1F-cf`Xn`5oJ($%>@9!VR!K|o?=VN zWYc!Ms#B75*Wb^p!SA+w0D!oty5oJ@TKVHUe(PYc!Sb4mxTtzTnXaMglqBMQUO`+` zgNcy#RKCiV*Q!3c_~FX`EZ2AZ-^Uz~#_#799CnvTHGWDGaYx50Kwc9U6_nRpP>Vp` zf%~?NV0Za<^6fA7;Uj*J^>%H~u7$84PJByE9sjp6CuG$~W4P)7-rFOtPPO$Pc9&@1 zwozPEz1I4X_H7$cCP~NVuuGLKpQ@?j9~6MBCTg;DEoux-Edm=+_&)V?{PLO$d8dyZ z2maULYaan7#YF{8({!+=X#{@L^{v3?<69R12ED!<;pg$U6WCRO&&#(i0QtQ6_2)-- z?zUVGob#ckX@rT7?~F&|1MuhHssLoGk!wq=Om_nxY~AcjfFHsnLP<)ewuc^A&j*NH3tqyoSR z7^|#JUH5+Tk$^N!BNLyy^Qf`g@VRo}Gyhf{fRbDvt$$P3UOSA54PX}+XAUTnKgOmM z0jU7yoB%1CZCvgmblF1Y^1*ijk~_eh6Y%WvS%6M2fp`tNSS(yjSo82Ur-@%)bAiWj zvMGFSA9OyM_*9V!Kuv;a!@)=Jm^tOPtByA%Usr$>K6MVr`v4MpN)myWAlvP>JNC8y z$KSUpe2)J_(y^(0-F2T4o^bB`lL`O_qxvw?H zm*e2vRLKu~e!lZgV9;a8`eP8s7*_>8KcB4w%)L<*_#VFnP&DOpF!1^K;T<4|uJw9t ze7;b|1pXJ~XIUn)EHfIXjtl(%gHL({xCF$RgutJdAN0Wb3kNPcIxTuyYXATM07*qo IM6N<$f_E>Ke*gdg literal 0 HcmV?d00001 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 +}