Tiled maps

1. Introduction

We feature an extensive support for maps designed in Tiled map editor.

Tiled snow map (with animations) Tiled beach map (with animations)

2. Basic usage

  1. Design your map in Tiled, and save the resulting map (.tmx) along with images (tilesets) somewhere in the project data subdirectory.

  2. Add to your viewport a TCastleTiledMap component and set TCastleTiledMap.Url to indicate your map (.tmx) file.

3. Example

4. Tiled features supported

Tiled isometric map
Tiled hexagonal map
Tiled map in 3D perspective
Choosing Tiled layers to show
  • Any number of layers, any map sizes, multiple tilesets.

  • All map types: Orthogonal, Isometric, IsometricStaggered, Hexagonal.

  • Flipping of tiles (diagonal, horizontal, vertical).

  • Optimized rendering with static batching of map layers.

  • Animations (all tile animations play automatically, you can also explicitly start and stop them).

5. Usage

5.1. Using map inside a viewport

The TCastleTiledMap is a TCastleTransform instance, and as such you can move, rotate and scale it.

You can also place other things in the viewport (like TCastleScene, TCastleImageTransform) in front or behind the map. Use the Z coordinate to control what is in front/behind. If you want to place something on top of a specific map tile, the TCastleTiledMap.TileRectangle method is useful.

Inside the viewport you have a regular camera. You can move the camera, you can change the camera orthographic height (TCastleOrthographic.Height) to zoom in/out. Add the TCastle2DNavigation component as a child of your viewport to allow user to move/zoom on the map too.

5.2. Important properties and methods

To load the map, set TCastleTiledMap.Url.

You can control texture filtering by TCastleTiledMap.SmoothScaling.

To show/hide specific layers use TCastleTiledMap.Layers.

  • Click on the button with 3 dots …​ at the Layers property in CGE editor to invoke a nice dialog where you can select layers by names.

  • TCastleTiledMap.Layers property can also be used to split map into 2 pieces, e.g. front and back, and place them at distinct Z values. Simply use 2 instances of TCastleTiledMap with the same URL (same map loaded) but showing disjoint layers.

The TCastleTiledMap.Data exposes map data (read-only) to query various information about the map, e.g. what tile type has been put at some map position.

TCastleTiledMap.PlayAnimations, TCastleTiledMap.StopAnimations control animations. By default, Tiled animations automatically play when the map is loaded.

5.3. Determine tile indicated by mouse

It’s a common task to determine what map tile is under the mouse (or, more generally, any coordinate on the screen).

5.3.1. Simple case (2D camera, no map transformation)

To determine the map tile under mouse coordinates, use TCastleTiledMapData.PositionToTile.

In the simplest and typical case, when the camera is standard for 2D (looks along -Z) and the map is not transformed, you can just use MyViewport.PositionToRay(…​, RayOrigin, RayDirection). Then

  • Ignore the returned RayDirection, knowing it is -Z ((0,0,-1)) in typical 2D games.

  • Knowing that map is not transformed, just use RayOrigin.XY and convert it into the map position.

In summary, do it like this:

var
  RayOrigin, RayDirection: TVector3;
  TileUnderMouseValid: Boolean;
  TileUnderMouse: TVector2Integer;
begin
  MyViewport.PositionToRay(Container.MousePosition, true, RayOrigin, RayDirection);
  TileUnderMouseValid := Map.Data.PositionToTile(RayOrigin.XY, TileUnderMouse);
  // ... now use TileUnderMouseValid and TileUnderMouse as you see fit
end;

5.3.2. General case (any camera, any map transformation)

An alternative solution is to perform a proper collision check "what does the ray (implied by the mouse position) hit". This is a more general solution that works in all cases (even if you transform the map, directly or by parent transform, or change camera to non-2D view).

To learn what the ray hits, it is easiest to use the MyViewport.MouseRayHit.Info. The MyViewport.MouseRayHit.Info returns true and fills a record (of type TRayCollisionNode) with information about the collision.

See the MyViewport.MouseRayHit and TRayCollision.Info methods documentation for details.

You want to just access the point on your map that was picked (if any). Assuming your map is declared as Map: TCastleTiledMap; and you have a MyViewport: TCastleViewport; somewhere, you can do it like this:

var
  TileUnderMouseValid: Boolean;
  TileUnderMouse: TVector2Integer;
  RayHitInfo: TRayCollisionNode;
begin
  TileUnderMouseValid := false;
  if MyViewport.MouseRayHit <> nil then
  begin
    if MyViewport.MouseRayHit.Info(RayHitInfo) and
       (RayHitInfo.Item = Map) then
    begin
      TileUnderMouseValid := Map.Data.PositionToTile(
        RayHitInfo.Point.XY, TileUnderMouse);
    end;
  end;
  // ... now use TileUnderMouseValid and TileUnderMouse as you see fit
end;

5.3.3. Query the map position under an arbitrary UI position (not necessarily where the mouse is)

You are not limited to using MouseRayHit to query the map position under the mouse. You can use a similar approach to query the map position under…​ anything. E.g. to query what is in the middle or corner of the screen, or under a UI element.

To do this, realize that MyViewport.MouseRayHit is equivalent to:

  1. Get the mouse position using Container.MousePosition.

  2. Determine the ray indicated by mouse using MyViewport.PositionToRay

  3. Use this ray to perform collision query using MyViewport.Items.WorldRay

So if you want, you can e.g. query any other ray this way. You can use MyViewport.PositionToRay with any argument, or even calculate the ray yourself. Then use MyViewport.Items.WorldRay to get TRayCollision instance, on which you have Info method discussed above.

Remember to free later the TRayCollision instance.

For an example, see the code snippet in the next section.

5.3.4. Can we query the map position such that the map is not obscured (for this query) by stuff in front of the map, e.g. NPCs on map?

Sure. Follow the above instructions to use MyViewport.PositionToRay + MyViewport.Items.WorldRay. Around the calls to these methods, you can temporarily hide the things that you don’t want to be returned by the query.

It is easiest when you have a parent TCastleTransform group that contains e.g. all NPCs. You can then hide them by setting MyNpcs.Exists := false; and then restore visibility by setting MyNpcs.Exists := true;.

Example code:

var
  RayOrigin, RayDirection: TVector3;
  TileUnderMouseValid: Boolean;
  TileUnderMouse: TVector2Integer;
  RayHit: TRayCollision;
  RayHitInfo: TRayCollisionNode;
begin
  TileUnderMouseValid := false;

  MyNpcs.Exists := false;

  MyViewport.PositionToRay(Container.MousePosition, true, RayOrigin, RayDirection);
  RayHit := MyViewport.Items.WorldRay(RayOrigin, RayDirection);
  if RayHit <> nil then
  try
    if RayHit.Info(RayHitInfo) and
       (RayHitInfo.Item = Map) then
    begin
      TileUnderMouseValid := Map.Data.PositionToTile(
        RayHitInfo.Point.XY, TileUnderMouse);
    end;
  finally FreeAndNil(RayHit) end;

  MyNpcs.Exists := true; // restore
end;
Note
Using the physics engine collision queries, you can utilize physics layers to limit what is hit by the ray-cast. TODO: Extend our API and docs to enable physics ray cast naturally.

To improve this documentation just edit this page and create a pull request to cge-www repository.