TiledWrapper
El componente TiledWrapper envuelve un tilemap exportado desde el editor de mapas Tiled y selecciona qué capa renderizar. Funciona junto con un TilemapRenderer en la misma entidad, que dibuja los tiles usando los tilesets del mapa. También puede crear entidades a partir de los objetos ubicados en el tilemap.
Limitaciones
- Solo se admiten mapas ortogonales. Las demás orientaciones no se traducen, por lo que los tiles de un mapa isométrico o hexagonal se ubican como si el mapa fuera ortogonal.
- Los tiles volteados o rotados en Tiled todavía no están soportados. Sus ids llevan los flags de volteo, por lo que no se renderizan como se espera. El soporte está planificado.
- Las formas de colisión dibujadas sobre los tiles de un tileset no están soportadas. El
objectgroupque un tile lleva desde el Tile Collision Editor no se lee. Los colliders de un tilemap provienen delTilemapCollider, que los genera a partir de los tiles que no están vacíos.
Opciones
| Opción | Tipo | Descripción |
|---|---|---|
tilemapPath | string | La URL del tilemap JSON exportado desde Tiled. Un wrapper que no la tiene se ignora. |
layerToRender | string | El nombre de la capa de Tiled a renderizar. |
objects | Map<string, TiledObjectBlueprint> | Las entidades a crear a partir de los objetos del tilemap, indexadas por la clase del objeto de Tiled. |
Ejemplo
import { Transform, TiledWrapper, TilemapRenderer } from "angry-pixel";
this.entityManager.createEntity([
new Transform(),
new TiledWrapper({ tilemapPath: "tilemap/map.json", layerToRender: "Ground" }),
// los tilesets se crean a partir de los que están embebidos en el mapa
new TilemapRenderer({ layer: "Foreground" }),
]);
El tilemap se referencia con la URL de su exportación JSON. No hace falta cargarlo de antemano: cuando no está entre los recursos cargados, el motor lo carga y lo lee apenas está disponible, por lo que el mapa se renderiza unos cuadros después de crearse la entidad. De todos modos, se recomienda cargarlo en el método loadAssets de la escena con el Asset Manager, porque la escena espera a sus recursos antes de crear las entidades y el mapa está desde el primer cuadro.
loadAssets(): void {
this.assetManager.loadJson("tilemap/map.json");
this.assetManager.loadImage("image/tileset.png");
}
La URL es también contra lo que se resuelven las rutas de las imágenes de los tilesets, por lo que conviene referenciar el tilemap por su URL y no por un nombre de recurso.
Tilesets
Los tilesets del TilemapRenderer se crean a partir de los que están embebidos en el mapa la primera vez que se procesa el componente, con su imagen, tamaño de tile, margen, espaciado, id del primer tile y cantidad de tiles. Un TilemapRenderer usado con un TiledWrapper no necesita declararlos.
Los tilesets deben estar embebidos en la exportación JSON. Tiled puede guardar un tileset en su propio archivo .tsx y referenciarlo desde el mapa; el motor no lee esos tilesets, porque el mapa solo lleva su firstgid y la ruta del .tsx. Hay que embeberlos en el mapa con la acción Embed Tileset del panel de Tilesets, o declarar a mano los tilesets del TilemapRenderer. De lo contrario el componente lanza un error al intentar crearlos —tanto para un mapa que no declara ningún tileset como para uno cuyos tilesets son externos— y los tiles animados en Tiled tampoco se mapean.
Tiled guarda la ruta de la imagen de un tileset de forma relativa al archivo del mapa, por lo que se resuelve contra la URL del mapa: un mapa cargado como tilemap/map.json que usa ../image/tileset.png necesita su imagen cargada como image/tileset.png.
Los tilesets pueden declararse igualmente a mano, lo que es necesario cuando las imágenes se cargan con un nombre que no coincide con su ruta, o para agregar animaciones de tiles propias. Los tilesets declarados se corresponden por posición con los del mapa, por lo que deben listarse en el mismo orden, y el firstgid de cada uno se toma del mapa salvo que se defina a mano.
Capas
La capa indicada en layerToRender se busca en todo el tilemap, incluso dentro de las capas de grupo. Estas propiedades de la capa se aplican al TilemapRenderer:
| Propiedad de la capa | Se aplica como |
|---|---|
offsetx / offsety | El offset del renderer. Se le suma el offset de los grupos que contienen a la capa. También se aplica al TilemapCollider, para que los colliders acompañen a los tiles. |
opacity | La opacity del renderer, multiplicada por la opacidad de los grupos que contienen a la capa. |
tintcolor | El tintColor del renderer. El canal alfa se descarta. |
visible | Una capa que no es visible, o que pertenece a un grupo que no es visible, no renderiza nada ni genera colliders. |
startx / starty | El origen de un tilemap infinito, cuyos chunks se ubican desde la esquina superior izquierda de la capa en lugar del origen del mapa, y pueden tener coordenadas negativas. |
El tamaño del tilemap y el tamaño de sus tiles se toman del tilemap, y de los límites de la capa en el caso de los tilemaps infinitos.
Tiles animados
Los tiles animados en Tiled se mapean a las animations del tileset del TilemapRenderer al que pertenecen la primera vez que se procesa el componente, por lo que se reproducen sin ninguna configuración adicional.
Un tile se indexa con el firstgid de su tileset más el id que tiene dentro de él. Las animaciones ya definidas en el tileset tienen precedencia sobre las declaradas en Tiled.
Tiled permite una duración distinta para cada frame, mientras que el motor renderiza todos los frames de una animación al mismo ritmo. Se usa la duración promedio, lo que conserva la duración total de la animación y es exacto siempre que todos los frames duren lo mismo.
Actualizar el tilemap en tiempo de ejecución
El tilemap se lee una sola vez, y los datos de los tiles se procesan una sola vez. Para aplicar un cambio hecho en tiempo de ejecución, como renderizar otra capa, hay que llamar a refresh en los componentes involucrados:
const tiledWrapper = this.entityManager.getComponent(entity, TiledWrapper);
tiledWrapper.layerToRender = "Background";
tiledWrapper.refresh();
this.entityManager.getComponent(entity, TilemapRenderer).refresh();
this.entityManager.getComponent(entity, TilemapCollider).refresh();
refresh vuelve a leer el tilemap, vuelve a procesar los datos de los tiles y vuelve a generar las formas de los colliders. Es una operación costosa, no debe llamarse en cada frame. Las entidades creadas a partir de los objetos del tilemap no se crean de nuevo.
Crear entidades a partir de objetos de Tiled
El mapa objects asocia una clase de objeto de Tiled con un blueprint. Se crea una entidad por cada objeto de esa clase, y los objetos cuya clase no está en el mapa se ignoran. Las entidades se crean una sola vez, la primera vez que se procesa el componente.
Un blueprint puede ser:
- Un archetype.
- Una colección de componentes (instancias o clases).
- Una función factory que recibe las propiedades del objeto y devuelve cualquiera de los dos.
import { TiledWrapper, TiledObjectBlueprint, Transform, SpriteRenderer } from "angry-pixel";
import { playerArchetype } from "../entity/Player";
import { Door } from "../component/Door";
const objects = new Map<string, TiledObjectBlueprint>([
// un archetype
["Player", playerArchetype],
// una colección de componentes
["Coin", [new Transform(), new SpriteRenderer({ image: "coin.png" })]],
// una función factory
["Door", (properties) => [new Door({ locked: properties.get("locked") as boolean })]],
]);
new TiledWrapper({ tilemapPath: "tilemap/map.json", layerToRender: "Ground", objects });
Propiedades de los objetos
La función factory recibe las propiedades del objeto de Tiled como un Map indexado por el nombre de la propiedad, y el objeto de Tiled en sí como segundo argumento (útil para leer su name, width, height, polygon, etc.).
El valor de una propiedad puede ser un number (int y float), un boolean (bool), un string (string, color y file), una Entity (object), o un conjunto anidado de valores (class), por lo que debe castearse al tipo esperado.
Las propiedades de tipo object referencian a otro objeto de Tiled por su id. La factory las recibe como la entidad creada para el objeto referenciado, de modo que las entidades pueden vincularse entre sí. Todas las entidades se crean antes de llamar a cualquier factory, por lo que el orden de los objetos en el tilemap no importa. Una propiedad que referencia a un objeto para el cual no se creó ninguna entidad se recibe como undefined.
Posición
La posición del Transform de la entidad se calcula a partir de las propiedades x e y del objeto, relativa al centro del tilemap, más la posición de la entidad que contiene el TiledWrapper. Si el blueprint no incluye un Transform, se le agrega uno.
La posición se calcula desde el centro del objeto, en el espacio en el que se renderiza el tilemap: el tamaño y el tamaño de tile del TilemapRenderer, que no siempre coinciden con los declarados por Tiled. Un tilemap infinito se renderiza con el tamaño de sus chunks. Cuando la entidad no tiene un TilemapRenderer, se usan los valores declarados en el tilemap.
Los tile objects (los objetos con gid) tienen su origen en la esquina inferior izquierda; el resto lo tiene en la esquina superior izquierda.
La rotación del objeto también se aplica al Transform, y el centro del objeto rota alrededor de su origen, tal como sucede en Tiled. Una rotación de cero no se aplica, para que un blueprint pueda definir su propia rotación.
Reglas
- Los objetos se buscan por clase en todas las capas de objetos del tilemap, sin importar a qué capa pertenecen, incluidas las capas anidadas en grupos.
- Los objetos con la visibilidad desactivada se ignoran, al igual que todos los objetos de una capa o un grupo con la visibilidad desactivada.
- El offset de la capa de objetos, y el de los grupos que la contienen, se aplica a la posición del objeto.
- La relación padre-hijo entre objetos de Tiled no está soportada.
- Tiled 1.9 exporta la clase de un objeto como
class, y el resto de las versiones comotype. Ambas están soportadas.