Java Roguelike notes (5/13): How to create a Field Of View or Fog Of War

Creating a FOV: The dungeon unwraps as we walk through

1) Editing the Class: Tile.java

  • We add new isVisible and isDiscovered parameters to the Tile class, as well as initialize them within the constructor as false by default (once the game starts, no tiles are discovered or visible)
import java.awt.*;
import java.awt.geom.Point2D;

public class Tile extends Rectangle {

    public Point2D tilePosition;
    public boolean isDiscovered;
    public boolean isVisible;
    final public String _tileName;

    public Tile(String _tileName){

        this._tileName = _tileName;
        this.isDiscovered = false; // by default, all tiles are not discovered yet.
        this.isVisible = false; // by default, all tiles are not visible yet.
    }
}

2) Calculating the Field Of View:

  • I’m calling this new method inside the move() method and passing the current player coordinates, so is called each time the player moves with the new player position
  • The int = 7 we can see in the 2-dimensional loop needs better naming in future iterations, is basically (FOV_RADIUS*2 + 1) , being the +1 the player tile position so is not quite the diameter what we’re calculating here.
reCalculateFOV(playerLocX,playerLocY);
    void reCalculateFOV(int _playerLocX, int _playerLocY){
        allFovPositions.clear();
        allFovTiles.clear();
        // We create the FOV grid to loop through:
        for(int x=0; x <= 7; x++){ 
            for(int y=0; y <= 7; y++){
                Tile _fovTile = new Tile("FOV" + x + y);
                // We recalculate Tile coordinates based on which FOV-row are they


                    Point2D _fovPos = new Point2D.Double((_playerLocX - FOV_RADIUS + x),(_playerLocY+FOV_RADIUS - y));
                    _fovTile.tilePosition = _fovPos;

                    allFovTiles.add(_fovTile);
                    allFovPositions.add(_fovTile.tilePosition);
                
            }
        }

        checkFOVCollisions(allFovTiles);
    }

4) Switch Tiles that are within our FOV

  • Once we know where our FOV is, let’s switch the Tile’s state within:
    void checkFOVCollisions(List<Tile> _fovTiles){

        // contains method

        for(int i=0; i< listOfWallTiles.size();i++){
            if(allFovPositions.contains(listOfWallTiles.get(i).tilePosition)){
                //System.out.println("FOV match: " + listOfWallTiles.get(i).tilePosition);
                listOfWallTiles.get(i).isDiscovered = true;
                listOfWallTiles.get(i).isVisible = true;
            }
        }
        for(int i=0; i< listOfFloorTiles.size();i++){
            if(allFovPositions.contains(listOfFloorTiles.get(i).tilePosition)){
                //System.out.println("FOV match: " + listOfWallTiles.get(i).tilePosition);
                listOfFloorTiles.get(i).isDiscovered = true;
                listOfFloorTiles.get(i).isVisible = true;
            }
        }
        for(int i=0; i< listOfItemTiles.size();i++){
            if(allFovPositions.contains(listOfItemTiles.get(i).tilePosition)){
                //System.out.println("FOV match: " + listOfWallTiles.get(i).tilePosition);
                listOfItemTiles.get(i).isDiscovered = true;
                listOfItemTiles.get(i).isVisible = true;
            }
        }
        for(int i=0; i< listOfEnemyTiles.size();i++){
            if(allFovPositions.contains(listOfEnemyTiles.get(i).tilePosition)){
                //System.out.println("FOV match: " + listOfWallTiles.get(i).tilePosition);
                listOfEnemyTiles.get(i).isDiscovered = true;
                listOfEnemyTiles.get(i).isVisible = true;
            }
        }
    }

5) Final step: Redrawing.

  • Finally we check each tile status to see if are discovered or not, and update the sprite on each repaint within the draw() method. This is a very similar block of code for all elements so it can be refactored for simplicity and clarity in future iterations.

Floors:

        for(int i = 0; i< listOfFloorTiles.size(); i++){
            int _floorXPos = (int) listOfFloorTiles.get(i).tilePosition.getX();
            int _floorYPos = (int) listOfFloorTiles.get(i).tilePosition.getY();
            if(listOfFloorTiles.get(i).isDiscovered == false) {
                g.setColor(Color.gray);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("·", _floorXPos * UNIT_SIZE, _floorYPos * UNIT_SIZE);
            } else {
                g.setColor(Color.black);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("·", _floorXPos * UNIT_SIZE, _floorYPos * UNIT_SIZE);
            }
        }

Walls:


        for(int i = 0; i< listOfWallTiles.size(); i++){

            int _wallXPos = (int) listOfWallTiles.get(i).tilePosition.getX();
            int _wallYPos = (int) listOfWallTiles.get(i).tilePosition.getY();

            if(listOfWallTiles.get(i).isDiscovered == false){
                g.setColor(Color.gray);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("·",_wallXPos * UNIT_SIZE, _wallYPos * UNIT_SIZE );
            } else {
                g.setColor(Color.black);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("#",_wallXPos * UNIT_SIZE, _wallYPos * UNIT_SIZE );
                //System.out.println("Set wall at: " + _wallXPos + "|" + _wallYPos);
            }
        }

Items:

        for (int i = 0; i < listOfItemTiles.size(); i++){

            int _itemXPos = (int) listOfItemTiles.get(i).tilePosition.getX();
            int _itemYPos = (int) listOfItemTiles.get(i).tilePosition.getY();
            if(listOfItemTiles.get(i).isDiscovered == false) {
                g.setColor(Color.gray);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("·", _itemXPos * UNIT_SIZE, _itemYPos * UNIT_SIZE);
            } else {
                g.setColor(Color.yellow);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("O", _itemXPos * UNIT_SIZE, _itemYPos * UNIT_SIZE);
            }
        }

Enemies:

        for (int i = 0; i < listOfEnemyTiles.size(); i++){

            int _enemyXPos = (int) listOfEnemyTiles.get(i).tilePosition.getX();
            int _enemyYPos = (int) listOfEnemyTiles.get(i).tilePosition.getY();

            if(listOfEnemyTiles.get(i).isDiscovered == false) {
                g.setColor(Color.gray);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("·", _enemyXPos * UNIT_SIZE, _enemyYPos * UNIT_SIZE);
            } else {
                g.setColor(Color.red);
                g.setFont(new Font("Ink Free", Font.BOLD, 25));
                g.drawString("E", _enemyXPos * UNIT_SIZE, _enemyYPos * UNIT_SIZE);
            }
        }

Leave a Reply