Skip to content

Commit

Permalink
Improved examples, added a Tiled example
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Dec 14, 2024
1 parent ed0ea23 commit 940c18d
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 9 deletions.
46 changes: 46 additions & 0 deletions assets/terrain-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"compressionlevel": 0,
"editorsettings": {
"export": {
"format": "json",
"target": "level.json"
}
},
"height": 11,
"infinite": false,
"layers": [{
"data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 19, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35, 36, 36, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 69, 70, 70, 62, 2, 2, 2, 2, 61, 71, 0, 0, 0, 0, 69, 20, 0, 0, 0, 0, 0, 0, 35, 36, 36, 8, 7, 37, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 20, 0, 0, 0, 0, 0, 0, 0],
"height": 11,
"id": 1,
"name": "Tile Layer 1",
"opacity": 1,
"type": "tilelayer",
"visible": true,
"width": 19,
"x": 0,
"y": 0
}],
"nextlayerid": 2,
"nextobjectid": 1,
"orientation": "orthogonal",
"renderorder": "right-down",
"tiledversion": "1.3.2",
"tileheight": 32,
"tilesets": [{
"columns": 17,
"firstgid": 1,
"image": "..\/Graphics\/Palm Tree Island\/Sprites\/Terrain\/Terrain (32x32).png",
"imageheight": 160,
"imagewidth": 544,
"margin": 0,
"name": "Palm Island",
"spacing": 0,
"tilecount": 85,
"tileheight": 32,
"tilewidth": 32
}],
"tilewidth": 32,
"type": "map",
"version": 1.2,
"width": 19
}
Binary file added assets/terrain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 11 additions & 2 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import mill.scalalib._
import mill.scalajslib._
import mill.scalajslib.api._

import $file.scripts.shadermodule
import $file.scripts.gamemodule

import indigoplugin._

object examples extends mill.Module {

object primitives extends mill.Module {

object graphic extends shadermodule.ShaderModule {
object graphic extends gamemodule.GameModule {
val indigoOptions: IndigoOptions =
makeIndigoOptions("Graphic")
}

}

object importers extends mill.Module {

object `tiled-loaded` extends gamemodule.GameModule {
val indigoOptions: IndigoOptions =
makeIndigoOptions("Tiled (Loaded)")
}

}

}
9 changes: 9 additions & 0 deletions examples/importers/tiled-loaded/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Loading Tiled maps at runtime

Tiled is a piece of software used to design grid based levels, there are others, and all are easily supported provided they export some readable data format. _Limited_ basic support for the Tiled format is built into Indigo.

In this example, we can see how to load tiled data at runtime as a JSON asset, and parse it into a usable map that can be rendered with the accompanying image asset.

Loading data like this as runtime has the advantage of lowering the initial payload size of your game. The drawback of this approach is the added game logic complexity needed to do the load - and if you are using the asset loader, to wait for the loading to happen, deal with any errors that might occur and store the data in your model / view model some where.

The other approach is to bake the pre-loaded tiled data into your game at build / compile time using a custom generator.
125 changes: 125 additions & 0 deletions examples/importers/tiled-loaded/src/TiledLoadedExample.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package indigoexamples

import indigo.*
import indigo.json.Json
import generated.Config
import generated.Assets

import scala.scalajs.js.annotation.*

/** ## How to load and use a Tiled map
*/

/** In this example we're going to need a bit of a model to help us organise our data.
*
* The top level model will contain the loaded tilemap, which we'll use later directly during
* rendering, and a custom game map.
*
* Normally, the tiledMap would probably make more sense to live in the view model, since it is
* purely a rendering concern, but since this example is build in the sandbox we don't have that
* option. You'll have to use your imagination!
*
* The game map is a custom representation of the tilemap that we can use to drive game logic. It
* is all very well importing a game level from Tiled, but you will almost certainly want a model
* representation so that you can, for example, check for collisions, or whether moves are valid,
* or ask what lives where on a map, etc. In this example, we only have two types of tile.
*
* Note the useful helpers in the GameMap companion object.
*/
// ```scala
final case class Model(tiledMap: TiledMap, gameMap: GameMap)

final case class GameMap(grid: List[List[MapTile]])
object GameMap:
val empty: GameMap = GameMap(List.empty)

def fromTiledGrid(grid: TiledGridMap[MapTile]): GameMap =
GameMap(grid.toList2DPerLayer.head.map(_.map(_.tile)))

enum MapTile:
case Platform, Empty
// ```

@JSExportTopLevel("IndigoGame")
object TiledLoadedExample extends IndigoSandbox[TiledMap, Model]:

val config: GameConfig =
Config.config.noResize

val assets: Set[AssetType] =
Assets.assets.assetSet

val fonts: Set[FontInfo] = Set()
val animations: Set[Animation] = Set()
val shaders: Set[Shader] = Set()

/** ### Loading the data
*
* To make use of the tiled data, we need to load and parse it, using the Tiled Json helper. In
* this example that will happen during the initial loading phase, but in a real game you might
* want to load it on demand using the asset loader.
*/
// ```scala
def setup(assetCollection: AssetCollection, dice: Dice): Outcome[Startup[TiledMap]] =
Outcome {
val maybeTiledMap = for {
j <- assetCollection.findTextDataByName(Assets.assets.terrainData)
t <- Json.tiledMapFromJson(j)
} yield t

maybeTiledMap match {
case None =>
Startup.Failure("Could not generate TiledMap from data.")

case Some(tiledMap) =>
Startup.Success(tiledMap)
}
}
// ```

/** ### Initialising the model
*
* Our model is then initialised with the tilemap (which would normally live in the view model,
* probably), and we make use of the `toGrid` method on the tilemap to convert it into our custom
* game map.
*
* We're not using the custom game map in this example, but it's being println'd out so that you
* can see it in the JS console if you run the example.
*/
// ```scala
def initialModel(startupData: TiledMap): Outcome[Model] =
val gameMap = startupData
.toGrid {
case 0 => MapTile.Empty
case _ => MapTile.Platform
}
.map(GameMap.fromTiledGrid)
.getOrElse(GameMap.empty)

println(gameMap)

Outcome(
Model(startupData, gameMap)
)
// ```

def updateModel(context: FrameContext[TiledMap], model: Model): GlobalEvent => Outcome[Model] =
_ => Outcome(model)

/** ### Rendering the tilemap
*
* To display the tiled map, we just use the `toGroup` method on the tilemap to convert it into
* an Indigo group of primitives that reference the terrain image asset.
*
* `toGroup` is a simple convenience method that will create a `Graphic` for each tile in the
* map. If you want more control you'll either need to interpret the `TiledMap` yourself, or make
* use of the custom version we made earlier.
*/
// ```scala
def present(context: FrameContext[TiledMap], model: Model): Outcome[SceneUpdateFragment] =
Outcome(
SceneUpdateFragment(
model.tiledMap.toGroup(Assets.assets.terrain)
)
)
// ```
7 changes: 7 additions & 0 deletions examples/primitives/graphic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# The Graphic Primitive

Indigo has a number of primitives that all serve the same purpose, which is to help you tell Indigo how to fill a space on the screen.

The graphic primitive is probably the simplest, its job is to take a texture and render it. Depending on the material type you choose, you can achieve different effects, but materials apply to most primitives universally.

Graphic's special ability is that it can crop images.
37 changes: 31 additions & 6 deletions examples/primitives/graphic/src/GraphicExample.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package indigoexamples

import indigo._
import indigo.*
import generated.Config
import generated.Assets

import scala.scalajs.js.annotation._
import scala.scalajs.js.annotation.*

/** This example shows how to create a simple graphic and display it on the screen. It demonstrates
* a number of transformations.
*/
@JSExportTopLevel("IndigoGame")
object GraphicExample extends IndigoSandbox[Unit, Unit]:

val config: GameConfig =
GameConfig.default.noResize
.withViewport(550, 400)
Config.config.noResize

val assets: Set[AssetType] =
Set(AssetType.Image(AssetName("graphics"), AssetPath("assets/graphics.png")))
Assets.assets.assetSet

val fonts: Set[FontInfo] = Set()
val animations: Set[Animation] = Set()
Expand All @@ -27,16 +31,37 @@ object GraphicExample extends IndigoSandbox[Unit, Unit]:
def updateModel(context: FrameContext[Unit], model: Unit): GlobalEvent => Outcome[Unit] =
_ => Outcome(model)

/** The graphic in this example has been setup using an ImageEffects material that allows you to
* set properties like transparency (alpha). However you have also use the Bitmap material for
* simpler rendering use cases.
*
* The graphic also has a reference point set, which is used to determine the origin of the
* graphic. By default the origin is in the top left corner of the graphic, meaning that if you
* do `graphic.moveTo(10, 10)`, the graphics top left will be placed at 10, 10. If you set the
* reference point to the center of the graphic, then the center of the graphic will be placed at
* pixel coordinates (10, 10). In the example below, the reference point is set to (48, 48), so
* that if we did the `moveTo` again it would place the graphic at (10, 10), offset by 48 pixels
* in both directions. This is very handy for placing characters in a game. If you set the
* reference point to be at their feet, the character will 'stand' on the moveTo coordinate.
*/
// ```scala
val graphic: Graphic[Material.ImageEffects] =
Graphic(0, 0, 256, 256, 1, Material.ImageEffects(AssetName("graphics")))
Graphic(0, 0, 256, 256, 1, Material.ImageEffects(Assets.assets.graphics))
.withRef(48, 48)
// ```

/** This is another graphic that is based on the first graphic, but has had some transformations
* including graphic's party piece, the `crop`. Setting the crop allows you to take a sub-section
* of the image and display it.
*/
// ```scala
val basic: Graphic[Material.ImageEffects] =
graphic
.withCrop(128, 0, 96, 96)
.moveTo(200, 200)
.scaleBy(1.5, 1.5)
.withRef(96, 96)
// ```

def present(context: FrameContext[Unit], model: Unit): Outcome[SceneUpdateFragment] =
Outcome(
Expand Down
2 changes: 1 addition & 1 deletion scripts/shadermodule.sc → scripts/gamemodule.sc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import mill.scalajslib.api._
import $ivy.`io.indigoengine::mill-indigo:0.17.0`, millindigo._
import $ivy.`org.typelevel::scalac-options:0.1.7`, org.typelevel.scalacoptions._

trait ShaderModule extends MillIndigo {
trait GameModule extends MillIndigo {
def scalaVersion = "3.5.0"
def scalaJSVersion = "1.17.0"

Expand Down

0 comments on commit 940c18d

Please sign in to comment.