Skip to content

Latest commit

 

History

History
192 lines (131 loc) · 9.36 KB

tiled_maps.adoc

File metadata and controls

192 lines (131 loc) · 9.36 KB

Tiled maps

Introduction

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

cgeimg::block[ tiled_editor_snow.png|Tiled snow map (with animations), tiled_editor_beach.png|Tiled beach map (with animations) ]

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 cgeref:TCastleTiledMap[] component and set cgeref:TCastleTiledMap.Url[] to indicate your map (.tmx) file.

Example

Tiled features supported

cgeimg::float[ tiled_map_isometric.png|Tiled isometric map, tiled_hex.png|Tiled hexagonal map, tiled_3d.png|Tiled map in 3D perspective, tiled_choose_layers.png|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).

Usage

Using map inside a viewport

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

You can also place other things in the viewport (like cgeref:TCastleScene[], cgeref: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 specifc map tile, the cgeref: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 (cgeref:TCastleOrthographic.Height[]) to zoom in/out. Add the cgeref:TCastle2DNavigation[] component as a child of your viewport to allow user to move/zoom on the map too.

Important properties and methods

To load the map, set cgeref:TCastleTiledMap.Url[].

You can control texture filtering by cgeref:TCastleTiledMap.SmoothScaling[].

  • false (default) results in a pixel-art look.

  • true means to use smooth (bilinear) filtering for the tile images.

    If you see weird seams between tiles when using cgeref:TCastleTiledMap.SmoothScaling[] = true, this is likely a lack of spacing and alpha bleeding between tiles. You have to process your tileset image by adding some spacing between tiles and perform alpha bleeding in your image-editing software (you can use our castle-view-image). Alternatively, you can activate cgeref:TCastleTiledMap.SmoothScalingSafeBorder[] or cgeref:TCastleTiledMap.ForceTilesetSpacing[] to workaround it.

To show/hide specific layers use cgeref: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.

  • cgeref: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 cgeref:TCastleTiledMap[] with the same URL (same map loaded) but showing disjoint layers.

The cgeref: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.

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

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).

Simple case (2D camera, no map transformation)

To determine the map tile under mouse coordinates, use cgeref: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;

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 cgeref:TRayCollisionNode[]) with information about the collision.

See the cgeref:TCastleViewport.MouseRayHit[MyViewport.MouseRayHit] and cgeref:TRayCollision.Info[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;

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 cgeref:TCastleViewport.MouseRayHit[MyViewport.MouseRayHit] is equivalent to:

  1. Get the mouse position using cgeref:TCastleContainer.MousePosition[Container.MousePosition].

  2. Determine the ray indicated by mouse using cgeref:TCastleViewport.PositionToRay[MyViewport.PositionToRay]

  3. Use this ray to perform collision query using cgeref:TCastleAbstractRootTransform.WorldRay[MyViewport.Items.WorldRay]

So if you want, you can e.g. query any other ray this way. You can use cgeref:TCastleViewport.PositionToRay[MyViewport.PositionToRay] with any argument, or even calculate the ray yourself. Then use cgeref:TCastleAbstractRootTransform.WorldRay[MyViewport.Items.WorldRay] to get cgeref: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.

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 cgeref:TCastleViewport.PositionToRay[MyViewport.PositionToRay] + cgeref:TCastleAbstractRootTransform.WorldRay[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.