Fog of War

Fog of War on kontseptsioon, millega kirjeldatakse kaardil varjatud alasid. Seda kasutatakse sageli reaalaja strateegiamängudes, kuid ka käigupõhistes mängudes.

../_images/civilization.png

Paljud 4X (eXplore, eXpand, eXploit, eXterminate) mängud kaasaarvatud “Civilization” kasutavad “Fog of war” efekti.

"Fog of war" lisab mängule:

  • Ajendi mängukaarti avastada, et suurendada oma piiratud teadmisi selle kohta.

  • Realistlikkust: Simuleeritakse lahinguvälja, kus maastiku omadused ja

vaenlaste asukohad selguvad alles luure käigus. - Põnevust: Tundmatud piirkonnad avanevad järk-järgult, samal ajal kui vaenlaste positsioon ja väesuurus on vaid ajutised teadmised, mis võivad muutuda, kui mängija nähtavusalast lahkub.

../_images/chess.png

"Dark chess" mängus näeb mängija vaid mänguruute, kuhu on lubatud oma malend viia.

Fog of War top-down tüüpi kaardil

Kaardi piiride loomiseks ja hiljem külastatud kohtade tähistamiseks on vaja teada tileide mõõtmeid (pikkust ja laiust) ning nende koguarvu. Selle teabe leiame TMX-faili algusest.

../_images/fow.png

Need mõõtmed saab salvestada kas eraldi muutujatena:

  • int mapWidth

  • int mapHeight

  • int tileWidth

  • int tileHeight

Või loome nende jaoks enumi klass:

public enum MapConfig {
   MAP_WIDTH(20),
   MAP_HEIGHT(15),
   TILE_WIDTH(16),
   TILE_HEIGHT(16);

   private final int value;

   MapConfig(int value) {
       this.value = value;
   }

   public int getValue() {
       return value;
   }
}

Nüüd saab näiteks Player klassis defineerida kaardi piirid. Selleks loome meetodi defineMapBounds ning kutsume seda meetodit välja Player klassi konstruktoris. Piiride määramiseks kasutame ChainShape klassi, mis moodustab suletud kontuuri. Selleks kasutame ChainShape liidese meetodit createLoop, mis ühendab tipud suletud ahelaks.

 private void defineMapBounds() {
    BodyDef bdef = new BodyDef();
    bdef.type = BodyDef.BodyType.StaticBody;

    ChainShape shape = new ChainShape();
    float rightDown = MapConfig.MAP_HEIGHT.getValue() *
     MapConfig.TILE_HEIGHT.getValue() / MyGame.PPM;
    float rightUp = MapConfig.MAP_WIDTH.getValue() *
     MapConfig.TILE_WIDTH.getValue() / MyGame.PPM;
    shape.createLoop(new float[]{
        0, 0,
        rightUp, 0,
        rightUp, rightDown,
        0, rightDown
    });

    FixtureDef fdef = new FixtureDef();
    fdef.shape = shape;

    world.createBody(bdef).createFixture(fdef);
    shape.dispose();
}

Lisame Player klassi ka meetodid mängija x- ja y-koordinaatide saamiseks, et neid kasutada põhimänguklassis (mis pärib Screen liidest), “Fog of War”’i efekti tekitamiseks.

public float getX() {
    return b2body.getPosition().x;
}
public float getY() {
    return b2body.getPosition().y;
}

Lisame põhimänguklassi järgmised muutujad ning ka mapWidth, mapHeight, tileWidth ja tileHeight muutujaid, kui ei loonud enum klassi.

 private static final int VISION_RADIUS = 1;  // määratlemiseks, kui mitut ``tile``\ ‘i mängija näeb igas suunas.
 private boolean[][] visibilityMap;  // näitamaks mis x- ja y-koordinaadi ``tile`` \’idel oleme käinud.
 //  int mapWidth;
 //  int mapHeight;
 //  int tileWidth = 16;
 //  int tileHeight = 16;
int mapWidth = MapConfig.MAP_WIDTH.getValue();
int mapHeight = MapConfig.MAP_HEIGHT.getValue();
int tileWidth = MapConfig.TILE_WIDTH.getValue();
int tileHeight = MapConfig.TILE_HEIGHT.getValue();

Põhimängukonstruktoris initsialiseerime visibilityMap muutuja vastavalt kaardi mõõtmetele ning ka kaardimõõtmete muutujad kui me ei võta neid enumi klassist.

public Maze(MyGame game) {
    //this.mapWidth = map.getProperties().get("width",
      Integer.class);  // mul 20
    //this.mapHeight = map.getProperties().get("height",
      Integer.class); // mul 15
    this.visibilityMap = new boolean[mapWidth][mapHeight];
}

renderFogOfWar meetodis kasutame ShapeRenderer klassi, et joonistada mustad ristkülikud tile‘idele, kus mängija pole veel käinud.

private void renderFogOfWar() {
    ShapeRenderer shapeRenderer = new ShapeRenderer();
    shapeRenderer.setProjectionMatrix(camera.combined);
    shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);

    for (int x = 0; x < mapWidth; x++) {
        for (int y = 0; y < mapHeight; y++) {
            if (!visibilityMap[x][y]) {
                shapeRenderer.setColor(0.0f, 0.0f, 0.0f, 0.1f);
                shapeRenderer.rect(x * tileWidth / game.getPPM(), y
                    * tileHeight / game.getPPM(),
                    tileWidth / game.getPPM(), tileHeight / game.getPPM());
            }
        }
    }
    shapeRenderer.end();
    shapeRenderer.dispose();
}

updateFogOfWar meetodis uuendame tile‘ide nähtavust mängija hetkese x- ja y-koordinaati järgi nii kaugele, kui on määratud VISION_RADIUS põhjal.

private void updateFogOfWar(float playerX, float playerY) {
    int tileX = (int) (playerX * game.getPPM() / tileWidth);
    int tileY = (int) (playerY * game.getPPM() / tileHeight);

    for (int x = tileX - VISION_RADIUS; x <= tileX + VISION_RADIUS; x++) {
        for (int y = tileY - VISION_RADIUS; y <= tileY + VISION_RADIUS;
                    y++) {
                       if (x >= 0 && x < visibilityMap.length && y >= 0
                        && y < visibilityMap[0].length) {
                           double distance = Math.sqrt((tileX - x) * (tileX - x) +
                            (tileY - y) * (tileY - y));
                if (distance <= VISION_RADIUS) {
                    visibilityMap[x][y] = true;
                }
            }
        }
    }
}

render funktsiooni on nüüd vaja lisada nii updateFogOfWar kui ka renderFogOfWar meetodi välja kutsumine. Kuna SpriteBatch ja ShapeRenderer ei saa korraga aktiivsed olla (SpriteBatch joonistab tekstuure ja ShapeRenderer kujundeid), peame renderFogOfWar meetodi välja kutsuma peale batch.end toimingut.

@Override
public void render(float delta) {
    updateFogOfWar(player.getX(), player.getY());
    // vahepeal on batch.begin() ja batch.end()
    renderFogOfWar();
}
../_images/fow.webp