///<summary> ///Create a GameObject for each Tile with a defined type and thus potentially a prefab. ///</summary> static void CreateTileObjects( Map map, Map.Layer layer, Map.Layer.Chunk chunk, GameObject layerObject ) { var chunkPosition = new Vector3Int(chunk.x, layer.height - chunk.height - chunk.y, 0); for (var k = 0; k < chunk.gids.Length; ++k) { var gid = chunk.gids[k]; var tile = map.tiles[gid & 0x1ffffff]; if (!tile?.prefab) { continue; } // The 3 most significant bits indicate in what way the tile should be flipped. var diagonal = (gid >> 29) & 1; var vertical = (gid >> 30) & 1; var horizontal = (gid >> 31) & 1; var position = chunkPosition + new Vector3Int(k % chunk.width, k / chunk.width, 0); // Since a tile's GameObject is instantiated automatically in Edit Mode and *again* // in Play Mode, we can't use the built in approach, and instead have to // handle instantiation ourselves to avoid duplicate GameObjects. var gameObject = PrefabUtility.InstantiatePrefab( tile.prefab, layerObject.transform ) as GameObject; if (gameObject) { gameObject.name += $" {position.x},{position.y}"; gameObject.transform.localPosition = position; if (vertical == 1) { gameObject.transform.localRotation *= Quaternion.Euler(180f, 0f, 0f); gameObject.transform.localPosition -= gameObject.transform.up; } if (horizontal == 1) { gameObject.transform.localRotation *= Quaternion.Euler(0f, 180f, 0f); gameObject.transform.localPosition -= gameObject.transform.right; } if (diagonal == 1) { gameObject.transform.localRotation *= Quaternion.AngleAxis(180f, new Vector3(-1f, 1f, 0f)); gameObject.transform.localPosition -= gameObject.transform.up; gameObject.transform.localPosition -= gameObject.transform.right; } foreach (var component in gameObject.GetComponentsInChildren <MonoBehaviour>()) { Property.Apply(tile.properties, component); } } } }
///<summary> ///Get an array of all tiles in a chunk. ///</summary> static Tile[] GetTiles(Map map, Map.Layer layer, Map.Layer.Chunk chunk) { var tiles = new Tile[chunk.gids.Length]; for (var k = 0; k < tiles.Length; ++k) { tiles[k] = map.tiles[chunk.gids[k] & 0x1ffffff]; } return(tiles); }
///<summary> ///Add and configure a Tilemap component for a layer. ///</summary> static void CreateTileLayer( GameObject layerObject, Map map, Map.Layer layer, int order ) { var tilemap = layerObject.AddComponent <UnityEngine.Tilemaps.Tilemap>(); var renderer = layerObject.AddComponent <TilemapRenderer>(); layerObject.AddComponent <TilemapCollider2D>().usedByComposite = true; tilemap.color = new Color(1f, 1f, 1f, layer.opacity); renderer.sortingOrder = order; // Hexagonal maps have to be rotated 180 degrees, flipped horizontally, and the tilemap // object has to be flipped vertically. Because of this, the sort order is also weird :/ if (map.orientation == "hexagonal") { tilemap.orientation = UnityEngine.Tilemaps.Tilemap.Orientation.Custom; tilemap.orientationMatrix = Matrix4x4.Rotate(Quaternion.Euler(0f, 180f, 180f)); tilemap.transform.localScale = new Vector3(1f, -1f, 1f); renderer.sortOrder = TilemapRenderer.SortOrder.BottomLeft; } else { renderer.sortOrder = TilemapRenderer.SortOrder.TopLeft; } // Tiled generally defines Y-positive as down, whereas Unity defines it as up. // This effectively means that all positions need to have their Y-component reversed. for (var j = 0; j < layer.chunks.Length; ++j) { var chunk = layer.chunks[j]; var position = new Vector3Int(chunk.x, layer.height - chunk.height - chunk.y, 0); var size = new Vector3Int(chunk.width, chunk.height, 1); var bounds = new BoundsInt(position, size); tilemap.SetTilesBlock(bounds, GetTiles(map, layer, chunk)); FlipTiles(tilemap, layer, chunk); CreateTileObjects(map, layer, chunk, layerObject); } }
///<summary> ///Flip all tiles in a chunk according to their 3 most significant flip bits, if any. ///</summary> static void FlipTiles(Tilemap tilemap, Map.Layer layer, Map.Layer.Chunk chunk) { var chunkPosition = new Vector3Int(chunk.x, layer.height - chunk.height - chunk.y, 0); for (var k = 0; k < chunk.gids.Length; ++k) { var gid = chunk.gids[k]; // The 3 most significant bits indicate in what way the tile should be flipped. if (gid >> 29 == 0) { continue; } var diagonal = (gid >> 29) & 1; var vertical = (gid >> 30) & 1; var horizontal = (gid >> 31) & 1; var position = chunkPosition + new Vector3Int(k % chunk.width, k / chunk.width, 0); var rotation = Quaternion.identity; // Flip tiles along X, Y, and diagonally based on the flip flags. if (vertical == 1) { rotation *= Quaternion.Euler(180f, 0f, 0f); } if (horizontal == 1) { rotation *= Quaternion.Euler(0f, 180f, 0f); } if (diagonal == 1) { rotation *= Quaternion.AngleAxis(180f, new Vector3(-1f, 1f, 0f)); } var transform = Matrix4x4.TRS(pos: Vector3.zero, rotation, s: Vector3.one); tilemap.SetTransformMatrix(position, transform); } }
///<summary> ///Create a GameObject for each Tiled object in layer. ///</summary> static void CreateObjectLayer( AssetImportContext context, GameObject layerObject, Map map, Map.Layer layer, int order, Dictionary <uint, AnimatorController> animators ) { foreach (var @object in layer.objects) { var tile = map.tiles[(int)(@object.gid & 0x1fffffff)]; var sprite = tile?.sprite; var gameObject = CreateObject(context, @object, tile); var size = new Vector2(@object.width / map.tilewidth, @object.height / map.tileheight); // Since Tiled's up is Y-negative while Unity's is Y-positive, the Y position is // effectively reversed. gameObject.transform.parent = layerObject.transform; gameObject.transform.localPosition = new Vector3( @object.x / map.tilewidth, [email protected] / map.tileheight + map.height ); // Tiled's rotation is clockwise, while Unity's is anticlockwise. gameObject.transform.localRotation *= Quaternion.Euler(0f, 0f, [email protected]); // Continue early if there's no tile or sprite associated with the object. if (!sprite) { gameObject.transform.localPosition += Vector3.down * size.y; continue; } // Position children of the GameObject at the center of the object. var childPosition = size / 2f; // Rotate and realign the object based on 3 most significant flip bits. //var diagonal = ((@object.gid >> 29) & 1) == 1; var vertical = ((@object.gid >> 30) & 1) == 1; var horizontal = ((@object.gid >> 31) & 1) == 1; if (vertical) { gameObject.transform.localRotation *= Quaternion.Euler(180f, 0f, 0f); gameObject.transform.localPosition -= gameObject.transform.up * size.y; } if (horizontal) { gameObject.transform.localRotation *= Quaternion.Euler(0f, 180f, 0f); gameObject.transform.localPosition -= gameObject.transform.right * size.x; } // Create a SpriteRenderer child object, and scale it according to object size. var renderer = new GameObject("Renderer").AddComponent <SpriteRenderer>(); renderer.transform.SetParent(gameObject.transform); renderer.transform.localPosition = childPosition; renderer.transform.localRotation = Quaternion.identity; renderer.sprite = sprite; renderer.sortingOrder = order; renderer.spriteSortPoint = SpriteSortPoint.Pivot; renderer.drawMode = SpriteDrawMode.Sliced; // HACK: Makes renderer.size work renderer.size = new Vector2(@object.width, @object.height) / map.tilewidth; renderer.color = new Color(1f, 1f, 1f, layer.opacity); renderer.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; // A new animator controller is created for each unique tile, if necessary. if (tile.frames.Length > 1) { if (!animators.TryGetValue(@object.gid & 0x1fffffff, out var controller)) { controller = CreateAnimatorController(context, $"{@object.gid}", tile); animators[@object.gid & 0x1fffffff] = controller; } var animator = renderer.gameObject.AddComponent <Animator>(); animator.runtimeAnimatorController = controller; // Apply animations to all components. foreach (var component in gameObject.GetComponentsInChildren <MonoBehaviour>()) { ApplyAnimation(context, controller, tile.frames, component); } } // A Collider child object is created for each collision shape defined in Tiled. if (tile.colliderType == Tile.ColliderType.Sprite) { var shapeCount = sprite.GetPhysicsShapeCount(); for (var j = 0; j < shapeCount; ++j) { var points = new List <Vector2>(); sprite.GetPhysicsShape(j, points); var collider = new GameObject($"Collider {j}"); collider.AddComponent <PolygonCollider2D>().points = points.ToArray(); collider.transform.SetParent(gameObject.transform); collider.transform.localPosition = childPosition; collider.transform.localRotation = Quaternion.identity; } } } }