Fat Old Yeti

Fat Old Yeti

Being a blog of thoughts and tutorials from a hobby game developer.

08 Feb 2021

Roguelike Tutorial 8

Roguelike in Go - Part 8 (Field of View)

All of the code for this tutorial can be found here.

Ok, maps are all well and good. However, letting the player see everything makes things too simple. Let’s add some Field fo View detection to the game. I considered writing a tutorial on FOV, but Red Blob Games still has the absolute best one that exists (along with quite a few more tutorials). I highely recommend taking the time to read this article.

I was about to write my own based off of this, but then I discovered that norendren already created a great library that is incredibly simple to use. So, rather than reinvent the wheel, let’s use that.

So, in order to use it, we need to have a type which implements InBounds and IsOpaque. So, let’s do that. Since the Field Of View tests are going to be on our level (one at a time), that seems like the appropriate place to put it. Open level.go and add the following:

func (level Level) InBounds(x, y int) bool {
	gd := NewGameData()
	if x < 0 || x > gd.ScreenWidth || y < 0 || y > gd.ScreenHeight {
		return false
	}
	return true
}

 
//TODO: Change this to check for WALL, not blocked
func (level Level) IsOpaque(x, y int) bool {
	idx := level.GetIndexFromXY(x, y)
	return level.Tiles[idx].Blocked
}

Checking InBounds is easy. It it on the screen? Checking Opacity is a bit harder. Right now, we check for Blocked which currently equates to it being a wall. Later on, it will mean more (like if a monster is there), which may not block LOS. So, we will change this later when we need to, but know that for now, this is good enough.

Before we can add any references to the FOV library, we need to import it. Add this to the imports:

"github.com/norendren/go-fov/fov"

Then, on the Level struct itself, add this to the bottom:

PlayerVisible *fov.View

This means in the level Constructor function, we should initialize this value. At the bottom of the NewLevel function, before the return, add this:

l.PlayerVisible  =  fov.New()

Now DrawLevel needs to change to only draw what we can see:

func (level *Level) DrawLevel(screen *ebiten.Image) {
	gd := NewGameData()
	for x := 0; x < gd.ScreenWidth; x++ {
		for y := 0; y < gd.ScreenHeight; y++ {
			if level.PlayerVisible.IsVisible(x, y) {
				tile := level.Tiles[level.GetIndexFromXY(x, y)]
				op := &ebiten.DrawImageOptions{}
				op.GeoM.Translate(float64(tile.PixelX), float64(tile.PixelY))
				screen.DrawImage(tile.Image, op)
			}
		}
	}
}

Lastly we need to update the player’s view each time they move. So, open player_move_system.go and look at the TryMovePlayer system function. Find the block that reads:

if  tile.Blocked  !=  true {
	...
}

Replace that block with this instead:

if tile.Blocked != true {
	pos.X += x
	pos.Y += y
	level.PlayerVisible.Compute(level, pos.X, pos.Y, 8)
}

That tells the system to recalculate the player’s FOV every time the player moves.

fov

If you have any questions, feel free to contact me at fatoldyeti@gmail.com or @idiotcoder on the gophers slack. I also added a discord button.