PurpleKingdomGames / indigo

An FP game engine for Scala.
https://indigoengine.io/
MIT License
630 stars 58 forks source link

Graphical glitch with cropped clonetiles and camera when height is odd #622

Closed Akkete closed 10 months ago

Akkete commented 11 months ago

Whenever the viewport height is an odd number, the cropping of my tile graphics (clone tiles) is shifted so that they show a one pixel line of the below tule from the spritesheet. This only happens with height, not width for some reason. This problem goes away if I don't use .withCamera(Camera.LookAt(Point(...).

Pictures:

Even Odd

Here's my GameScene.present that produces the issue. Cleaned to be as minimal as I could easily get.

def present(
      context: SceneContext[Dice],
      model: Model,
      viewModel: ViewModel
  ): Outcome[SceneUpdateFragment] =

    val gridSize     = 32
    val tileSize     = gridSize

    val tileCloneBlank = CloneBlank(
      CloneId("tile"),
      Graphic(
        bounds = Rectangle(0, 0, tileSize, tileSize),
        depth = 1,
        material = Bitmap(AssetName("tiles"))
      )
    )

    val viewPortWidthTiles = 50
    val viewPortHeightTiles = 50

    val cameraX = 5.0
    val cameraY = 11.0

    /** Maps xy-coordinates to spritesheet columns and rows. */
    def tileGraphic(x: Int, y: Int): (Int, Int) = ...

    val tiles = CloneTiles(
      CloneId("tile"),
      Batch.fromSet(
        // List out xy-coordinates visible on screen
        (for {
          x <- cameraX.toInt - viewPortWidthTiles/2 to 
               cameraX.toInt + viewPortWidthTiles/2 + 1
          y <- cameraY.toInt - viewPortHeightTiles/2 to 
               cameraY.toInt + viewPortHeightTiles/2 + 1 
        }
        yield
          val tile = model.floor.getOrElse((x, y), Fall)
          val (col, row) = tileGraphic(x, y)
          CloneTileData(
            x          = gridSize * x,
            y          = gridSize * y,
            cropX      = tileSize * col,
            cropY      = tileSize * row,
            cropWidth  = tileSize,
            cropHeight = tileSize
          )
        ).toSet
      )
    )

    Outcome(
      SceneUpdateFragment(tiles).addCloneBlanks(tileCloneBlank)
        .withCamera(Camera.LookAt(Point(
          gridSize/2 + tileSize/2 + (cameraX * gridSize).toInt,
          gridSize/2 + tileSize/2 + (cameraY * gridSize).toInt
        )))
    )

Note that the viewport size is not actually referenced here at all.

davesmith00000 commented 11 months ago

Hi @Akkete, thank you for the excellent bug report!

I've read through it, and my gut is screaming rounding error... somewhere.

I'll see if I can replicate it, but in the meantime, I know you said it doesn't happen if you don't use Camera.LookAt, does that mean you tried Camera.Fixed and that was ok? I was wondering because I use Camera.Fixed in the roguelike demo and the CloneTiles work perfectly.

If that works, then probably it's due to loss of precision in the camera projection matrix calculation. One of the dangers of pixel art and rendering at Int / whole pixel precision.

Akkete commented 11 months ago

Yes, changing to .withCamera(Camera.Fixed(Point(...)) or just deleting the whole line gets rid of the problem.