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) ]
-
Simple demo, along with many sample maps, is in examples/tiled/map_viewer.
-
Complete game, a simple turn-based strategy, is in examples/tiled/strategy_game_demo.
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).
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.
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 theLayers
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.
It’s a common task to determine what map tile is under the mouse (or, more generally, any coordinate on the screen).
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;
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;
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:
-
Get the mouse position using cgeref:TCastleContainer.MousePosition[Container.MousePosition].
-
Determine the ray indicated by mouse using cgeref:TCastleViewport.PositionToRay[MyViewport.PositionToRay]
-
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. |