public static List <TmxLayerNode> ListFromXml(XElement xmlRoot, TmxLayerNode parent, TmxMap tmxMap) { List <TmxLayerNode> list = new List <TmxLayerNode>(); foreach (XElement item in xmlRoot.Elements()) { TmxLayerNode tmxLayerNode = null; if (item.Name == (XName)"layer" || item.Name == (XName)"imagelayer") { tmxLayerNode = TmxLayer.FromXml(item, parent, tmxMap); } else if (item.Name == (XName)"objectgroup") { tmxLayerNode = TmxObjectGroup.FromXml(item, parent, tmxMap); } else if (item.Name == (XName)"group") { tmxLayerNode = TmxGroupLayer.FromXml(item, parent, tmxMap); } if (tmxLayerNode != null && tmxLayerNode.Visible && tmxLayerNode.Ignore != IgnoreSettings.True) { list.Add(tmxLayerNode); } } return(list); }
public static List <TmxLayerNode> ListFromXml(XElement xmlRoot, TmxLayerNode parent, TmxMap tmxMap) { List <TmxLayerNode> nodes = new List <TmxLayerNode>(); foreach (var xmlNode in xmlRoot.Elements()) { TmxLayerNode layerNode = null; if (xmlNode.Name == "layer" || xmlNode.Name == "imagelayer") { layerNode = TmxLayer.FromXml(xmlNode, parent, tmxMap); } else if (xmlNode.Name == "objectgroup") { layerNode = TmxObjectGroup.FromXml(xmlNode, parent, tmxMap); } else if (xmlNode.Name == "group") { layerNode = TmxGroupLayer.FromXml(xmlNode, parent, tmxMap); } // If the layer is visible then add it to our list if (layerNode != null && layerNode.Visible) { nodes.Add(layerNode); } } return(nodes); }
private void BuildCollisionLayers_ByObjectType() { for (int i = 0; i < Data.Chunks.Count; i++) { TmxChunk tmxChunk = Data.Chunks[i]; for (int j = 0; j < tmxChunk.TileIds.Count; j++) { uint num = tmxChunk.TileIds[j]; if (num != 0) { uint tileIdWithoutFlags = TmxMath.GetTileIdWithoutFlags(num); foreach (TmxObject @object in base.ParentMap.Tiles[tileIdWithoutFlags].ObjectGroup.Objects) { if (@object is TmxHasPoints) { TmxLayer tmxLayer = CollisionLayers.Find((TmxLayer l) => string.Compare(l.Name, @object.Type, true) == 0); if (tmxLayer == null) { tmxLayer = new TmxLayer(null, base.ParentMap); CollisionLayers.Add(tmxLayer); tmxLayer.Name = @object.Type; tmxLayer.Offset = base.Offset; tmxLayer.Width = Width; tmxLayer.Height = Height; tmxLayer.Ignore = base.Ignore; tmxLayer.Properties = base.Properties; tmxLayer.Data = Data.MakeEmptyCopy(tmxLayer); } tmxLayer.Data.Chunks[i].TileIds[j] = num; } } } } } }
private void ParseAllLayers(XDocument doc) { Program.WriteLine("Parsing layer elements ..."); var layers = (from item in doc.Descendants("layer") select item).ToList(); foreach (var lay in layers) { TmxLayer tmxLayer = TmxLayer.FromXml(lay, this.Layers.Count); // Layers may be ignored if (tmxLayer.Properties.GetPropertyValueAsBoolean("unity:ignore", false) == true) { // We don't care about this layer Program.WriteLine("Ignoring layer due to unity:ignore property: {0}", tmxLayer.UniqueName); continue; } int maxVertices = 65535; int numVertices = (from tileId in tmxLayer.TileIds where tileId != 0 select tileId).Count() * 4; if (numVertices > maxVertices) { Program.WriteWarning("Layer '{0}' will have more than {1} vertices (vertex count = {2}) and will be split into {3} parts by Unity.", tmxLayer.UniqueName, maxVertices, numVertices, numVertices / maxVertices + 1); } this.Layers.Add(tmxLayer); } }
private List <XElement> CreateMeshElementsForLayer(TmxLayer layer) { List <XElement> xmlMeshes = new List <XElement>(); foreach (TmxMesh mesh in layer.Meshes) { XElement xmlMesh = new XElement("GameObject", new XAttribute("name", mesh.ObjectName), new XAttribute("copy", mesh.UniqueMeshName), new XAttribute("sortingLayerName", layer.SortingLayerName), new XAttribute("sortingOrder", layer.SortingOrder), new XAttribute("opacity", layer.Opacity)); xmlMeshes.Add(xmlMesh); if (mesh.FullAnimationDurationMs > 0) { XElement xmlAnimation = new XElement("TileAnimator", new XAttribute("startTimeMs", mesh.StartTimeMs), new XAttribute("durationMs", mesh.DurationMs), new XAttribute("fullTimeMs", mesh.FullAnimationDurationMs)); xmlMesh.Add(xmlAnimation); } } return(xmlMeshes); }
// Splits a layer into TmxMesh instances public static List <TmxMesh> ListFromTmxLayer(TmxLayer layer) { List <TmxMesh> meshes = new List <TmxMesh>(); for (int i = 0; i < layer.TileIds.Count(); ++i) { // Copy the tile unto the mesh that uses the same image // (In other words, we are grouping tiles by images into a mesh) uint tileId = layer.TileIds[i]; TmxTile tile = layer.TmxMap.GetTileFromTileId(tileId); if (tile == null) { continue; } int timeMs = 0; foreach (var frame in tile.Animation.Frames) { uint frameTileId = frame.GlobalTileId; // Have to put any rotations/flipping from the source tile into this one frameTileId |= (tileId & TmxMath.FLIPPED_HORIZONTALLY_FLAG); frameTileId |= (tileId & TmxMath.FLIPPED_VERTICALLY_FLAG); frameTileId |= (tileId & TmxMath.FLIPPED_DIAGONALLY_FLAG); // Find a mesh to stick this tile into (if it exists) TmxMesh mesh = meshes.Find(m => m.CanAddFrame(tile, timeMs, frame.DurationMs)); if (mesh == null) { // Create a new mesh and add it to our list mesh = new TmxMesh(); mesh.TileIds = new uint[layer.TileIds.Count()]; mesh.UniqueMeshName = String.Format("mesh_{0}", layer.TmxMap.GetUniqueId().ToString("D4")); mesh.TmxImage = tile.TmxImage; // Keep track of the timing for this mesh (non-animating meshes will have a start time and duration of 0) mesh.StartTimeMs = timeMs; mesh.DurationMs = frame.DurationMs; mesh.FullAnimationDurationMs = tile.Animation.TotalTimeMs; mesh.ObjectName = Path.GetFileNameWithoutExtension(tile.TmxImage.AbsolutePath); if (mesh.DurationMs != 0) { // Decorate the name a bit with some animation details for the frame mesh.ObjectName += string.Format("[{0}-{1}]", timeMs, timeMs + mesh.DurationMs); } meshes.Add(mesh); } // This mesh contains this tile mesh.AddTile(i, frameTileId); // Advance time timeMs += frame.DurationMs; } } return(meshes); }
public void VisitTileLayer(TmxLayer tileLayer) { // Tile layer does render something and therefore increases draw order index tileLayer.DrawOrderIndex = this.drawOrderIndex++; // Either inherit depth buffer index of parent or advance tileLayer.DepthBufferIndex = (tileLayer.ParentNode != null) ? tileLayer.ParentNode.DepthBufferIndex : this.depthBufferIndex++; }
public void VisitTileLayer(TmxLayer tileLayer) { // Tile layer does render something and therefore increases draw order index tileLayer.DrawOrderIndex = this.drawOrderIndex++; // Children don't have a depth buffer index. Their parent sets the depth. tileLayer.DepthBufferIndex = (tileLayer.ParentNode != null) ? 0 : this.depthBufferIndex++; }
// Splits a layer into TmxMesh instances public static List<TmxMesh> ListFromTmxLayer(TmxLayer layer) { List<TmxMesh> meshes = new List<TmxMesh>(); for (int i = 0; i < layer.TileIds.Count(); ++i) { // Copy the tile unto the mesh that uses the same image // (In other words, we are grouping tiles by images into a mesh) uint tileId = layer.TileIds[i]; TmxTile tile = layer.TmxMap.GetTileFromTileId(tileId); if (tile == null) continue; int timeMs = 0; foreach (var frame in tile.Animation.Frames) { uint frameTileId = frame.GlobalTileId; // Have to put any rotations/flipping from the source tile into this one frameTileId |= (tileId & TmxMath.FLIPPED_HORIZONTALLY_FLAG); frameTileId |= (tileId & TmxMath.FLIPPED_VERTICALLY_FLAG); frameTileId |= (tileId & TmxMath.FLIPPED_DIAGONALLY_FLAG); // Find a mesh to stick this tile into (if it exists) TmxMesh mesh = meshes.Find(m => m.CanAddFrame(tile, timeMs, frame.DurationMs)); if (mesh == null) { // Create a new mesh and add it to our list mesh = new TmxMesh(); mesh.TileIds = new uint[layer.TileIds.Count()]; mesh.UniqueMeshName = String.Format("mesh_{0}", layer.TmxMap.GetUniqueId().ToString("D4")); mesh.TmxImage = tile.TmxImage; // Keep track of the timing for this mesh (non-animating meshes will have a start time and duration of 0) mesh.StartTimeMs = timeMs; mesh.DurationMs = frame.DurationMs; mesh.FullAnimationDurationMs = tile.Animation.TotalTimeMs; mesh.ObjectName = Path.GetFileNameWithoutExtension(tile.TmxImage.AbsolutePath); if (mesh.DurationMs != 0) { // Decorate the name a bit with some animation details for the frame mesh.ObjectName += string.Format("[{0}-{1}]", timeMs, timeMs + mesh.DurationMs); } meshes.Add(mesh); } // This mesh contains this tile mesh.AddTile(i, frameTileId); // Advance time timeMs += frame.DurationMs; } } return meshes; }
public static TmxData FromDataXml(XElement xml, TmxLayer parentLayer) { TmxData tmxData = new TmxData(parentLayer); tmxData.Encoding = TmxHelper.GetAttributeAsEnum(xml, "encoding", DataEncoding.Xml); tmxData.Compression = TmxHelper.GetAttributeAsEnum(xml, "compression", DataCompression.None); tmxData.Chunks = TmxChunk.ListFromDataXml(xml, tmxData); return(tmxData); }
public static List <TmxMesh> ListFromTmxLayer(TmxLayer layer) { List <TmxMesh> list = new List <TmxMesh>(); foreach (TmxChunk chunk in layer.Data.Chunks) { list.AddRange(ListFromTmxChunk(chunk)); } return(list); }
public TmxData MakeEmptyCopy(TmxLayer parent) { TmxData data = new TmxData(parent); data.Encoding = Encoding; data.Compression = Compression; data.Chunks = (from c in Chunks select c.MakeEmptyCopy(data)).ToList(); return(data); }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space PointF pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); ClipperLib.IntPoint point = new ClipperLib.IntPoint(pointUnity3d.X, pointUnity3d.Y); return(point); }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Program.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); // Add our polygon and edge colliders List <XElement> polyColliderElements = new List <XElement>(); if (layer.IsExportingConvexPolygons()) { AddPolygonCollider2DElements_Convex(solution, polyColliderElements); } else { AddPolygonCollider2DElements_Complex(solution, polyColliderElements); } AddEdgeCollider2DElements(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return(null); } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); return(gameObjectCollision); }
private StringWriter BuildWavefrontStringForLayerMesh(TmxLayer layer, TmxMesh mesh, IEnumerable <int> horizontalRange, IEnumerable <int> verticalRange) { Logger.WriteVerbose("Building mesh obj file for '{0}'", mesh.UniqueMeshName); GenericListDatabase <Vertex3> vertexDatabase = new GenericListDatabase <Vertex3>(); HashIndexOf <PointF> uvDatabase = new HashIndexOf <PointF>(); StringBuilder faces = new StringBuilder(); foreach (int y in verticalRange) { foreach (int x in horizontalRange) { int tileIndex = layer.GetTileIndex(x, y); uint tileId = mesh.GetTileIdAt(tileIndex); // Skip blank tiles if (tileId == 0) { continue; } TmxTile tile = this.tmxMap.Tiles[TmxMath.GetTileIdWithoutFlags(tileId)]; // What are the vertex and texture coorindates of this face on the mesh? var position = this.tmxMap.GetMapPositionAt(x, y, tile); var vertices = CalculateFaceVertices(position, tile.TileSize); // If we're using depth shaders then we'll need to set a depth value of this face float depth_z = 0.0f; if (Tiled2Unity.Settings.DepthBufferEnabled) { depth_z = CalculateFaceDepth(position.Y + tmxMap.TileHeight, tmxMap.MapSizeInPixels.Height); } FaceVertices faceVertices = new FaceVertices { Vertices = vertices, Depth_z = depth_z }; // Is the tile being flipped or rotated (needed for texture cooridinates) bool flipDiagonal = TmxMath.IsTileFlippedDiagonally(tileId); bool flipHorizontal = TmxMath.IsTileFlippedHorizontally(tileId); bool flipVertical = TmxMath.IsTileFlippedVertically(tileId); var uvs = CalculateFaceTextureCoordinates(tile, flipDiagonal, flipHorizontal, flipVertical); // Adds vertices and uvs to the database as we build the face strings string v0 = String.Format("{0}/{1}/1", vertexDatabase.AddToDatabase(faceVertices.V0) + 1, uvDatabase.Add(uvs[0]) + 1); string v1 = String.Format("{0}/{1}/1", vertexDatabase.AddToDatabase(faceVertices.V1) + 1, uvDatabase.Add(uvs[1]) + 1); string v2 = String.Format("{0}/{1}/1", vertexDatabase.AddToDatabase(faceVertices.V2) + 1, uvDatabase.Add(uvs[2]) + 1); string v3 = String.Format("{0}/{1}/1", vertexDatabase.AddToDatabase(faceVertices.V3) + 1, uvDatabase.Add(uvs[3]) + 1); faces.AppendFormat("f {0} {1} {2} {3}\n", v0, v1, v2, v3); } } // We have all the data we need to build the wavefront file format return(CreateWavefrontWriter(mesh, vertexDatabase, uvDatabase, faces)); }
private void AddTileLayerToElement(TmxLayer tileLayer, XElement xmlRoot) { XElement xmlLayer = new XElement("GameObject"); xmlLayer.SetAttributeValue("name", tileLayer.Name); // Figure out the offset for this layer PointF offset = PointFToUnityVector(tileLayer.Offset); float depth_z = CalculateLayerDepth(tileLayer); xmlLayer.SetAttributeValue("x", offset.X); xmlLayer.SetAttributeValue("y", offset.Y); xmlLayer.SetAttributeValue("z", depth_z); // Add a TileLayer component { XElement layerComponent = new XElement("TileLayer", new XAttribute("opacity", tileLayer.Opacity), new XAttribute("offsetX", tileLayer.Offset.X), new XAttribute("offsetY", tileLayer.Offset.Y)); xmlLayer.Add(layerComponent); } // Add a Terrain component XElement terrainComponent = new XElement("Terrain", CreateTerrainDataForLayer(tileLayer)); xmlLayer.Add(terrainComponent); if (tileLayer.Ignore != TmxLayer.IgnoreSettings.Visual) { // Submeshes for the layer (layer+material) var meshElements = CreateMeshElementsForLayer(tileLayer); xmlLayer.Add(meshElements); } // Collision data for the layer if (tileLayer.Ignore != TmxLayer.IgnoreSettings.Collision) { foreach (var collisionLayer in tileLayer.CollisionLayers) { var collisionElements = CreateCollisionElementForLayer(collisionLayer); xmlLayer.Add(collisionElements); } } // Assign and special properties AssignUnityProperties(tileLayer, xmlLayer, PrefabContext.TiledLayer); AssignTiledProperties(tileLayer, xmlLayer); // Finally, add the layer to our root xmlRoot.Add(xmlLayer); }
private void DrawColliders(Graphics g) { for (int l = 0; l < this.tmxMap.Layers.Count; ++l) { TmxLayer layer = this.tmxMap.Layers[l]; if (layer.Visible == true && IsLayerEnabled(layer.DefaultName)) { Color lineColor = this.preferencesForm.GetLayerColor(layer.DefaultName); Color polyColor = Color.FromArgb(128, lineColor); DrawLayerColliders(g, layer, polyColor, lineColor); } } }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x, y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); float inverseScale = 1.0f / this.scale; if (inverseScale > 1) { inverseScale = 1; } using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 2.0f * inverseScale)) using (Brush brush = new HatchBrush(HatchStyle.Percent60, polyColor, Color.Transparent)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons // First, add them to the path // (But are we using convex polygons are complex polygons? var polygons = layer.IsExportingConvexPolygons() ? LayerClipper.SolutionPolygons_Simple(solution) : LayerClipper.SolutionPolygons_Complex(solution); foreach (var pointfArray in polygons) { path.AddPolygon(pointfArray); } // Then, fill and draw the path full of polygons if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private string CreateTerrainDataForLayer(TmxLayer layer) { StringBuilder result = new StringBuilder(); for (int y = 0; y < layer.Height; y += 1) { for (int x = 0; x < layer.Width; x += 1) { uint terrainId = layer.GetTileIdAt(x, y); result.Append(terrainId); result.Append(','); } } return(result.ToString()); }
private void BuildCollisionLayers_ByObjectType() { // Find all tiles with collisions on them and put them into a "Collision Layer" of the same type for (int t = 0; t < this.TileIds.Length; ++t) { uint rawTileId = this.TileIds[t]; if (rawTileId == 0) { continue; } uint tileId = TmxMath.GetTileIdWithoutFlags(rawTileId); TmxTile tmxTile = this.TmxMap.Tiles[tileId]; foreach (TmxObject colliderObject in tmxTile.ObjectGroup.Objects) { if ((colliderObject is TmxHasPoints) == false) { continue; } // We have a collider object on the tile // Add the tile to the Collision Layer of the matching type // Or, create a new Collision Layer of this type to add this tile to TmxLayer collisionLayer = this.CollisionLayers.Find(l => String.Compare(l.Name, colliderObject.Type, true) == 0); if (collisionLayer == null) { // Create a new Collision Layer collisionLayer = new TmxLayer(null, this.TmxMap); this.CollisionLayers.Add(collisionLayer); // The new Collision Layer has the name of the collider object and empty tiles (they will be filled with tiles that have matching collider objects) collisionLayer.Name = colliderObject.Type; collisionLayer.TileIds = new uint[this.TileIds.Length]; // Copy over some stuff from parent layer that we need for creating collisions collisionLayer.Offset = this.Offset; collisionLayer.Width = this.Width; collisionLayer.Height = this.Height; collisionLayer.Ignore = this.Ignore; collisionLayer.Properties = this.Properties; } // Add the tile to this collision layer collisionLayer.TileIds[t] = rawTileId; } } }
public static TmxLayer FromXml(XElement elem, TmxLayerNode parent, TmxMap tmxMap) { TmxLayer tmxLayer = new TmxLayer(parent, tmxMap); tmxLayer.FromXmlInternal(elem); if (elem.Name == (XName)"layer") { tmxLayer.ParseLayerXml(elem); } else if (elem.Name == (XName)"imagelayer") { tmxLayer.ParseImageLayerXml(elem); } tmxLayer.Meshes = TmxMesh.ListFromTmxLayer(tmxLayer); tmxLayer.BuildCollisionLayers(); return(tmxLayer); }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space Vector3D pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); IntPoint point = new IntPoint(pointUnity3d.X, pointUnity3d.Y); return point; }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Program.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); // Add our polygon and edge colliders List<XElement> polyColliderElements = new List<XElement>(); AddPolygonCollider2DElements(Clipper.ClosedPathsFromPolyTree(solution), polyColliderElements); AddEdgeCollider2DElements(Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return null; } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); return gameObjectCollision; }
public static TmxLayer FromXml(XElement elem, int layerIndex) { Program.WriteVerbose(elem.ToString()); TmxLayer tmxLayer = new TmxLayer(); // Have to decorate layer names in order to force them into being unique // Also, can't have whitespace in the name because Unity will add underscores tmxLayer.DefaultName = TmxHelper.GetAttributeAsString(elem, "name"); tmxLayer.UniqueName = String.Format("{0}_{1}", tmxLayer.DefaultName, layerIndex.ToString("D2")).Replace(" ", "_"); tmxLayer.Visible = TmxHelper.GetAttributeAsInt(elem, "visible", 1) == 1; tmxLayer.Width = TmxHelper.GetAttributeAsInt(elem, "width"); tmxLayer.Height = TmxHelper.GetAttributeAsInt(elem, "height"); tmxLayer.Properties = TmxProperties.FromXml(elem); tmxLayer.ParseData(elem.Element("data")); return tmxLayer; }
public static TmxLayer FromXml(XElement elem, int layerIndex) { Program.WriteVerbose(elem.ToString()); TmxLayer tmxLayer = new TmxLayer(); // Have to decorate layer names in order to force them into being unique // Also, can't have whitespace in the name because Unity will add underscores tmxLayer.DefaultName = TmxHelper.GetAttributeAsString(elem, "name"); tmxLayer.UniqueName = String.Format("{0}_{1}", tmxLayer.DefaultName, layerIndex.ToString("D2")).Replace(" ", "_"); tmxLayer.Visible = TmxHelper.GetAttributeAsInt(elem, "visible", 1) == 1; tmxLayer.Width = TmxHelper.GetAttributeAsInt(elem, "width"); tmxLayer.Height = TmxHelper.GetAttributeAsInt(elem, "height"); tmxLayer.Properties = TmxProperties.FromXml(elem); tmxLayer.ParseData(elem.Element("data")); return(tmxLayer); }
private void DrawColliders(Graphics g) { for (int l = 0; l < this.tmxMap.Layers.Count; ++l) { TmxLayer layer = this.tmxMap.Layers[l]; if (layer.Visible == true && layer.Ignore != TmxLayer.IgnoreSettings.Collision) { foreach (TmxLayer collisionLayer in layer.CollisionLayers) { TmxObjectType type = this.tmxMap.ObjectTypes.GetValueOrDefault(collisionLayer.Name); Color lineColor = type.Color; Color polyColor = Color.FromArgb(128, lineColor); DrawLayerColliders(g, collisionLayer, polyColor, lineColor); } } } }
private List <XElement> CreateMeshElementsForLayer(TmxLayer layer) { // Mesh elements look like this: // <GameObject copy="LayerName+TilesetName" /> // (The importer in Unity will look a submesh of that name and copy it to our prefab) // (This is complicated by potential tile animations now) var meshes = from rawTileId in layer.TileIds where rawTileId != 0 let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) let tile = this.tmxMap.Tiles[tileId] let name = TiledMapExpoterUtils.UnityFriendlyMeshName(tmxMap, layer.UniqueName, Path.GetFileNameWithoutExtension(tile.TmxImage.Path)) group tile.Animation by name into meshGroup select meshGroup; List <XElement> xmlMeshes = new List <XElement>(); foreach (var m in meshes) { XElement xmlMesh = new XElement("GameObject", new XAttribute("copy", m.Key)); // Do we have any animations? var animations = m.Distinct(); foreach (var anim in animations) { if (anim != null) { XElement xmlAnim = new XElement("TileAnimator"); foreach (var frame in anim.Frames) { xmlAnim.Add(new XElement("Frame", new XAttribute("vertex_z", frame.UniqueFrameId * Program.Vertex_ZScale), new XAttribute("duration", frame.DurationMs))); } xmlMesh.Add(xmlAnim); } } xmlMeshes.Add(xmlMesh); } return(xmlMeshes); }
public static TmxProperties GetPropertiesWithTypeDefaults(TmxHasProperties hasProperties, TmxObjectTypes objectTypes) { TmxProperties tmxProperties = new TmxProperties(); // Fill in all the default properties first // (Note: At the moment, only TmxObject has default properties it inherits from TmxObjectType) string objectTypeName = null; if (hasProperties is TmxObject) { TmxObject tmxObject = hasProperties as TmxObject; objectTypeName = tmxObject.Type; } else if (hasProperties is TmxLayer) { TmxLayer tmxLayer = hasProperties as TmxLayer; objectTypeName = tmxLayer.Name; } // If an object type has been found then copy over all the default values for properties TmxObjectType tmxObjectType = objectTypes.GetValueOrNull(objectTypeName); if (tmxObjectType != null) { foreach (TmxObjectTypeProperty tmxTypeProp in tmxObjectType.Properties.Values) { tmxProperties.PropertyMap[tmxTypeProp.Name] = new TmxProperty() { Name = tmxTypeProp.Name, Type = tmxTypeProp.Type, Value = tmxTypeProp.Default }; } } // Now add all the object properties (which may override some of the default properties) foreach (TmxProperty tmxProp in hasProperties.Properties.PropertyMap.Values) { tmxProperties.PropertyMap[tmxProp.Name] = tmxProp; } return(tmxProperties); }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x, y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 1.0f)) using (Brush brush = new HatchBrush(HatchStyle.ForwardDiagonal, lineColor, polyColor)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons foreach (var points in ClipperLib.Clipper.ClosedPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.AddPolygon(pointfs.ToArray()); } if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private void ParseAllLayers(XDocument doc) { Logger.WriteLine("Parsing layer elements ..."); // Parse "layer"s and "imagelayer"s var layers = (from item in doc.Descendants() where (item.Name == "layer" || item.Name == "imagelayer") select item).ToList(); foreach (var lay in layers) { TmxLayer tmxLayer = TmxLayer.FromXml(lay, this); // Layers may be ignored if (tmxLayer.Ignore == TmxLayer.IgnoreSettings.True) { // We don't care about this layer Logger.WriteLine("Ignoring layer due to unity:ignore = True property: {0}", tmxLayer.Name); continue; } this.Layers.Add(tmxLayer); } }
private void BuildBuildCollisionLayers_ByObjectType() { // Find all tiles with collisions on them and put them into a "Collision Layer" of the same type for (int t = 0; t < this.TileIds.Length; ++t) { uint rawTileId = this.TileIds[t]; if (rawTileId == 0) continue; uint tileId = TmxMath.GetTileIdWithoutFlags(rawTileId); TmxTile tmxTile = this.TmxMap.Tiles[tileId]; foreach (TmxObject colliderObject in tmxTile.ObjectGroup.Objects) { if ((colliderObject is TmxHasPoints) == false) continue; // We have a collider object on the tile // Add the tile to the Collision Layer of the matching type // Or, create a new Collision Layer of this type to add this tile to TmxLayer collisionLayer = this.CollisionLayers.Find(l => String.Compare(l.Name, colliderObject.Type, true) == 0); if (collisionLayer == null) { // Create a new Collision Layer collisionLayer = new TmxLayer(this.TmxMap); this.CollisionLayers.Add(collisionLayer); // The new Collision Layer has the name of the collider object and empty tiles (they will be filled with tiles that have matching collider objects) collisionLayer.Name = colliderObject.Type; collisionLayer.TileIds = new uint[this.TileIds.Length]; // Copy over some stuff from parent layer that we need for creating collisions collisionLayer.Offset = this.Offset; collisionLayer.Width = this.Width; collisionLayer.Height = this.Height; collisionLayer.Ignore = this.Ignore; collisionLayer.Properties = this.Properties; } // Add the tile to this collision layer collisionLayer.TileIds[t] = rawTileId; } } }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x,y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); float inverseScale = 1.0f / this.scale; if (inverseScale > 1) inverseScale = 1; using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 2.0f * inverseScale)) using (Brush brush = new HatchBrush(HatchStyle.Percent60, polyColor, Color.Transparent)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons // First, add them to the path // (But are we using convex polygons are complex polygons? var polygons = layer.IsExportingConvexPolygons() ? LayerClipper.SolutionPolygons_Simple(solution) : LayerClipper.SolutionPolygons_Complex(solution); foreach (var pointfArray in polygons) { path.AddPolygon(pointfArray); } // Then, fill and draw the path full of polygons if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private int GetMaxTilesWide(TmxLayer layer) { return Math.Min(layer.Width, MaxPreviewTilesWide); }
private int GetMaxTilesHigh(TmxLayer layer) { return Math.Min(layer.Height, MaxPreviewTilesHigh); }
private List<XElement> CreateMeshElementsForLayer(TmxLayer layer) { List<XElement> xmlMeshes = new List<XElement>(); foreach (TmxMesh mesh in layer.Meshes) { XElement xmlMesh = new XElement("GameObject", new XAttribute("name", mesh.ObjectName), new XAttribute("copy", mesh.UniqueMeshName), new XAttribute("sortingLayerName", layer.SortingLayerName), new XAttribute("sortingOrder", layer.SortingOrder), new XAttribute("opacity", layer.Opacity)); xmlMeshes.Add(xmlMesh); if (mesh.FullAnimationDurationMs > 0) { XElement xmlAnimation = new XElement("TileAnimator", new XAttribute("startTimeMs", mesh.StartTimeMs), new XAttribute("durationMs", mesh.DurationMs), new XAttribute("fullTimeMs", mesh.FullAnimationDurationMs)); xmlMesh.Add(xmlAnimation); } } return xmlMeshes; }
public static ClipperLib.PolyTree ExecuteClipper(TmxMap tmxMap, TmxLayer tmxLayer, TransformPointFunc xfFunc, ProgressFunc progFunc) { // The "fullClipper" combines the clipper results from the smaller pieces ClipperLib.Clipper fullClipper = new ClipperLib.Clipper(); // From the perspective of Clipper lines are polygons too // Closed paths == polygons // Open paths == lines var polygonGroups = from y in Enumerable.Range(0, tmxLayer.Height) from x in Enumerable.Range(0, tmxLayer.Width) let rawTileId = tmxLayer.GetRawTileIdAt(x, y) let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) where tileId != 0 let tile = tmxMap.Tiles[tileId] from polygon in tile.ObjectGroup.Objects where (polygon as TmxHasPoints) != null let groupX = x / LayerClipper.GroupBySize let groupY = y / LayerClipper.GroupBySize group new { PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile), HasPointsInterface = polygon as TmxHasPoints, TmxObjectInterface = polygon, IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId), IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId), IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId), TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f), } by Tuple.Create(groupX, groupY); int groupIndex = 0; int groupCount = polygonGroups.Count(); foreach (var polyGroup in polygonGroups) { if (groupIndex % 5 == 0) { progFunc(String.Format("Clipping '{0}' polygons: {1}%", tmxLayer.Name, (groupIndex / (float)groupCount) * 100)); } groupIndex++; // The "groupClipper" clips the polygons in a smaller part of the world ClipperLib.Clipper groupClipper = new ClipperLib.Clipper(); // Add all our polygons to the Clipper library so it can reduce all the polygons to a (hopefully small) number of paths foreach (var poly in polyGroup) { // Create a clipper library polygon out of each and add it to our collection ClipperPolygon clipperPolygon = new ClipperPolygon(); // Our points may be transformed due to tile flipping/rotation // Before we transform them we put all the points into local space relative to the tile SizeF offset = new SizeF(poly.TmxObjectInterface.Position); PointF[] transformedPoints = poly.HasPointsInterface.Points.Select(pt => PointF.Add(pt, offset)).ToArray(); // Now transform the points relative to the tile TmxMath.TransformPoints(transformedPoints, poly.TileCenter, poly.IsFlippedDiagnoally, poly.IsFlippedHorizontally, poly.IsFlippedVertically); foreach (var pt in transformedPoints) { float x = poly.PositionOnMap.X + pt.X; float y = poly.PositionOnMap.Y + pt.Y; ClipperLib.IntPoint point = xfFunc(x, y); clipperPolygon.Add(point); } // Because of Unity's cooridnate system, the winding order of the polygons must be reversed clipperPolygon.Reverse(); // Add the "subject" groupClipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } // Get a solution for this group ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); groupClipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); // Combine the solutions into the full clipper fullClipper.AddPaths(ClipperLib.Clipper.ClosedPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, true); fullClipper.AddPaths(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.Name)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return fullSolution; }
public static TmxLayer FromXml(XElement elem, TmxLayerNode parent, TmxMap tmxMap) { TmxLayer tmxLayer = new TmxLayer(parent, tmxMap); tmxLayer.FromXmlInternal(elem); // We can build a layer from a "tile layer" (default) or an "image layer" if (elem.Name == "layer") { tmxLayer.Width = TmxHelper.GetAttributeAsInt(elem, "width"); tmxLayer.Height = TmxHelper.GetAttributeAsInt(elem, "height"); tmxLayer.ParseData(elem.Element("data")); } else if (elem.Name == "imagelayer") { XElement xmlImage = elem.Element("image"); if (xmlImage == null) { Logger.WriteWarning("Image Layer '{0}' is being ignored since it has no image.", tmxLayer.Name); tmxLayer.Ignore = IgnoreSettings.True; return(tmxLayer); } // An image layer is sort of like an tile layer but with just one tile tmxLayer.Width = 1; tmxLayer.Height = 1; // Find the "tile" that matches our image string imagePath = TmxHelper.GetAttributeAsFullPath(elem.Element("image"), "source"); TmxTile tile = tmxMap.Tiles.First(t => t.Value.TmxImage.AbsolutePath == imagePath).Value; tmxLayer.TileIds = new uint[1] { tile.GlobalId }; // The image layer needs to be tranlated in an interesting way when expressed as a tile layer PointF translated = tmxLayer.Offset; // Make up for height of a regular tile in the map translated.Y -= (float)tmxMap.TileHeight; // Make up for the height of this image translated.Y += (float)tile.TmxImage.Size.Height; // Correct for any orientation effects on the map (like isometric) // (We essentially undo the translation via orientation here) PointF orientation = TmxMath.TileCornerInScreenCoordinates(tmxMap, 0, 0); translated.X -= orientation.X; translated.Y -= orientation.Y; // Translate by the x and y coordiantes translated.X += TmxHelper.GetAttributeAsFloat(elem, "x", 0); translated.Y += TmxHelper.GetAttributeAsFloat(elem, "y", 0); tmxLayer.Offset = translated; } // Sometimes TMX files have "dead" tiles in them (tiles that were removed but are still referenced) // Remove these tiles from the layer by replacing them with zero for (int t = 0; t < tmxLayer.TileIds.Length; ++t) { uint tileId = tmxLayer.TileIds[t]; tileId = TmxMath.GetTileIdWithoutFlags(tileId); if (!tmxMap.Tiles.ContainsKey(tileId)) { tmxLayer.TileIds[t] = 0; } } // Each layer will be broken down into "meshes" which are collections of tiles matching the same texture or animation tmxLayer.Meshes = TmxMesh.ListFromTmxLayer(tmxLayer); // Each layer may contain different collision types which are themselves put into "Collison Layers" to be processed later tmxLayer.BuildCollisionLayers(); return(tmxLayer); }
public static ClipperLib.PolyTree ExecuteClipper(TmxMap tmxMap, TmxLayer tmxLayer, TransformPointFunc xfFunc, ProgressFunc progFunc) { // The "fullClipper" combines the clipper results from the smaller pieces ClipperLib.Clipper fullClipper = new ClipperLib.Clipper(); // From the perspective of Clipper lines are polygons too // Closed paths == polygons // Open paths == lines var polygonGroups = from y in Enumerable.Range(0, tmxLayer.Height) from x in Enumerable.Range(0, tmxLayer.Width) let rawTileId = tmxLayer.GetRawTileIdAt(x, y) let tileId = TmxMath.GetTileIdWithoutFlags(rawTileId) where tileId != 0 let tile = tmxMap.Tiles[tileId] from polygon in tile.ObjectGroup.Objects where (polygon as TmxHasPoints) != null let groupX = x / LayerClipper.GroupBySize let groupY = y / LayerClipper.GroupBySize group new { PositionOnMap = tmxMap.GetMapPositionAt(x, y, tile), HasPointsInterface = polygon as TmxHasPoints, TmxObjectInterface = polygon, IsFlippedDiagnoally = TmxMath.IsTileFlippedDiagonally(rawTileId), IsFlippedHorizontally = TmxMath.IsTileFlippedHorizontally(rawTileId), IsFlippedVertically = TmxMath.IsTileFlippedVertically(rawTileId), TileCenter = new PointF(tile.TileSize.Width * 0.5f, tile.TileSize.Height * 0.5f), } by Tuple.Create(groupX, groupY); int groupIndex = 0; int groupCount = polygonGroups.Count(); foreach (var polyGroup in polygonGroups) { if (groupIndex % 5 == 0) { progFunc(String.Format("Clipping '{0}' polygons: {1}%", tmxLayer.Name, (groupIndex / (float)groupCount) * 100)); } groupIndex++; // The "groupClipper" clips the polygons in a smaller part of the world ClipperLib.Clipper groupClipper = new ClipperLib.Clipper(); // Add all our polygons to the Clipper library so it can reduce all the polygons to a (hopefully small) number of paths foreach (var poly in polyGroup) { // Create a clipper library polygon out of each and add it to our collection ClipperPolygon clipperPolygon = new ClipperPolygon(); // Our points may be transformed due to tile flipping/rotation // Before we transform them we put all the points into local space relative to the tile SizeF offset = new SizeF(poly.TmxObjectInterface.Position); PointF[] transformedPoints = poly.HasPointsInterface.Points.Select(pt => PointF.Add(pt, offset)).ToArray(); // Now transform the points relative to the tile TmxMath.TransformPoints(transformedPoints, poly.TileCenter, poly.IsFlippedDiagnoally, poly.IsFlippedHorizontally, poly.IsFlippedVertically); foreach (var pt in transformedPoints) { float x = poly.PositionOnMap.X + pt.X; float y = poly.PositionOnMap.Y + pt.Y; ClipperLib.IntPoint point = xfFunc(x, y); clipperPolygon.Add(point); } // Because of Unity's cooridnate system, the winding order of the polygons must be reversed clipperPolygon.Reverse(); // Add the "subject" groupClipper.AddPath(clipperPolygon, ClipperLib.PolyType.ptSubject, poly.HasPointsInterface.ArePointsClosed()); } // Get a solution for this group ClipperLib.PolyTree solution = new ClipperLib.PolyTree(); groupClipper.Execute(ClipperLib.ClipType.ctUnion, solution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); // Combine the solutions into the full clipper fullClipper.AddPaths(ClipperLib.Clipper.ClosedPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, true); fullClipper.AddPaths(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), ClipperLib.PolyType.ptSubject, false); } progFunc(String.Format("Clipping '{0}' polygons: 100%", tmxLayer.Name)); ClipperLib.PolyTree fullSolution = new ClipperLib.PolyTree(); fullClipper.Execute(ClipperLib.ClipType.ctUnion, fullSolution, LayerClipper.SubjectFillRule, LayerClipper.ClipFillRule); return(fullSolution); }
private int GetMaxTilesHigh(TmxLayer layer) { return(Math.Min(layer.Height, MaxPreviewTilesHigh)); }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space PointF pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); ClipperLib.IntPoint point = new ClipperLib.IntPoint(pointUnity3d.X, pointUnity3d.Y); return(point); }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Logger.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); var paths = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); if (paths.Count >= MaxNumberOfSafePaths) { StringBuilder warning = new StringBuilder(); warning.AppendFormat("Layer '{0}' has a large number of polygon paths ({1}).", layer.Name, paths.Count); warning.AppendLine(" Importing this layer may be slow in Unity. (Can take an hour or more for +1000 paths.)"); warning.AppendLine(" Check polygon/rectangle objects in Tile Collision Editor in Tiled and use 'Snap to Grid' or 'Snap to Fine Grid'."); warning.AppendLine(" You want colliders to be set up so they can be merged with colliders on neighboring tiles, reducing path count considerably."); warning.AppendLine(" In some cases the size of the map may need to be reduced."); Logger.WriteWarning(warning.ToString()); } // Add our polygon and edge colliders List <XElement> polyColliderElements = new List <XElement>(); if (layer.IsExportingConvexPolygons()) { AddPolygonCollider2DElements_Convex(solution, polyColliderElements); } else { AddPolygonCollider2DElements_Complex(solution, polyColliderElements); } AddEdgeCollider2DElements(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return(null); } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); // Collision layer may have a name and "unity physics layer" to go with it // (But not if we're using unity:layer override) if (String.IsNullOrEmpty(layer.UnityLayerOverrideName) && !String.IsNullOrEmpty(layer.Name)) { gameObjectCollision.SetAttributeValue("name", "Collision_" + layer.Name); gameObjectCollision.SetAttributeValue("layer", layer.Name); } return(gameObjectCollision); }
public static TmxLayer FromXml(XElement elem, TmxMap tmxMap) { Program.WriteVerbose(elem.ToString()); TmxLayer tmxLayer = new TmxLayer(tmxMap); // Order within Xml file is import for layer types tmxLayer.XmlElementIndex = elem.NodesBeforeSelf().Count(); // Have to decorate layer names in order to force them into being unique // Also, can't have whitespace in the name because Unity will add underscores tmxLayer.Name = TmxHelper.GetAttributeAsString(elem, "name"); tmxLayer.Visible = TmxHelper.GetAttributeAsInt(elem, "visible", 1) == 1; tmxLayer.Opacity = TmxHelper.GetAttributeAsFloat(elem, "opacity", 1); PointF offset = new PointF(0, 0); offset.X = TmxHelper.GetAttributeAsFloat(elem, "offsetx", 0); offset.Y = TmxHelper.GetAttributeAsFloat(elem, "offsety", 0); tmxLayer.Offset = offset; // Set our properties tmxLayer.Properties = TmxProperties.FromXml(elem); // Set the "ignore" setting on this layer tmxLayer.Ignore = tmxLayer.Properties.GetPropertyValueAsEnum<IgnoreSettings>("unity:ignore", IgnoreSettings.False); // We can build a layer from a "tile layer" (default) or an "image layer" if (elem.Name == "layer") { tmxLayer.Width = TmxHelper.GetAttributeAsInt(elem, "width"); tmxLayer.Height = TmxHelper.GetAttributeAsInt(elem, "height"); tmxLayer.ParseData(elem.Element("data")); } else if (elem.Name == "imagelayer") { XElement xmlImage = elem.Element("image"); if (xmlImage == null) { Program.WriteWarning("Image Layer '{0}' is being ignored since it has no image.", tmxLayer.Name); tmxLayer.Ignore = IgnoreSettings.True; return tmxLayer; } // An image layer is sort of like an tile layer but with just one tile tmxLayer.Width = 1; tmxLayer.Height = 1; // Find the "tile" that matches our image string imagePath = TmxHelper.GetAttributeAsFullPath(elem.Element("image"), "source"); TmxTile tile = tmxMap.Tiles.First(t => t.Value.TmxImage.AbsolutePath == imagePath).Value; tmxLayer.TileIds = new uint[1] { tile.GlobalId }; // The image layer needs to be tranlated in an interesting way when expressed as a tile layer PointF translated = tmxLayer.Offset; // Make up for height of a regular tile in the map translated.Y -= (float)tmxMap.TileHeight; // Make up for the height of this image translated.Y += (float)tile.TmxImage.Size.Height; // Correct for any orientation effects on the map (like isometric) // (We essentially undo the translation via orientation here) PointF orientation = TmxMath.TileCornerInScreenCoordinates(tmxMap, 0, 0); translated.X -= orientation.X; translated.Y -= orientation.Y; // Translate by the x and y coordiantes translated.X += TmxHelper.GetAttributeAsFloat(elem, "x", 0); translated.Y += TmxHelper.GetAttributeAsFloat(elem, "y", 0); tmxLayer.Offset = translated; } // Each layer will be broken down into "meshes" which are collections of tiles matching the same texture or animation tmxLayer.Meshes = TmxMesh.ListFromTmxLayer(tmxLayer); return tmxLayer; }
public static TmxLayer FromXml(XElement elem, TmxMap tmxMap) { Program.WriteVerbose(elem.ToString()); TmxLayer tmxLayer = new TmxLayer(tmxMap); // Order within Xml file is import for layer types tmxLayer.XmlElementIndex = elem.NodesBeforeSelf().Count(); // Have to decorate layer names in order to force them into being unique // Also, can't have whitespace in the name because Unity will add underscores tmxLayer.Name = TmxHelper.GetAttributeAsString(elem, "name"); tmxLayer.Visible = TmxHelper.GetAttributeAsInt(elem, "visible", 1) == 1; tmxLayer.Opacity = TmxHelper.GetAttributeAsFloat(elem, "opacity", 1); PointF offset = new PointF(0, 0); offset.X = TmxHelper.GetAttributeAsFloat(elem, "offsetx", 0); offset.Y = TmxHelper.GetAttributeAsFloat(elem, "offsety", 0); tmxLayer.Offset = offset; // Set our properties tmxLayer.Properties = TmxProperties.FromXml(elem); // Set the "ignore" setting on this layer tmxLayer.Ignore = tmxLayer.Properties.GetPropertyValueAsEnum <IgnoreSettings>("unity:ignore", IgnoreSettings.False); // We can build a layer from a "tile layer" (default) or an "image layer" if (elem.Name == "layer") { tmxLayer.Width = TmxHelper.GetAttributeAsInt(elem, "width"); tmxLayer.Height = TmxHelper.GetAttributeAsInt(elem, "height"); tmxLayer.ParseData(elem.Element("data")); } else if (elem.Name == "imagelayer") { XElement xmlImage = elem.Element("image"); if (xmlImage == null) { Program.WriteWarning("Image Layer '{0}' is being ignored since it has no image.", tmxLayer.Name); tmxLayer.Ignore = IgnoreSettings.True; return(tmxLayer); } // An image layer is sort of like an tile layer but with just one tile tmxLayer.Width = 1; tmxLayer.Height = 1; // Find the "tile" that matches our image string imagePath = TmxHelper.GetAttributeAsFullPath(elem.Element("image"), "source"); TmxTile tile = tmxMap.Tiles.First(t => t.Value.TmxImage.AbsolutePath == imagePath).Value; tmxLayer.TileIds = new uint[1] { tile.GlobalId }; // The image layer needs to be tranlated in an interesting way when expressed as a tile layer PointF translated = tmxLayer.Offset; // Make up for height of a regular tile in the map translated.Y -= (float)tmxMap.TileHeight; // Make up for the height of this image translated.Y += (float)tile.TmxImage.Size.Height; // Correct for any orientation effects on the map (like isometric) // (We essentially undo the translation via orientation here) PointF orientation = TmxMath.TileCornerInScreenCoordinates(tmxMap, 0, 0); translated.X -= orientation.X; translated.Y -= orientation.Y; // Translate by the x and y coordiantes translated.X += TmxHelper.GetAttributeAsFloat(elem, "x", 0); translated.Y += TmxHelper.GetAttributeAsFloat(elem, "y", 0); tmxLayer.Offset = translated; } // Each layer will be broken down into "meshes" which are collections of tiles matching the same texture or animation tmxLayer.Meshes = TmxMesh.ListFromTmxLayer(tmxLayer); return(tmxLayer); }
private void DrawLayerColliders(Graphics g, TmxLayer layer, Color polyColor, Color lineColor) { LayerClipper.TransformPointFunc xfFunc = (x,y) => new ClipperLib.IntPoint(x, y); LayerClipper.ProgressFunc progFunc = (prog) => { }; // do nothing ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); using (GraphicsPath path = new GraphicsPath()) using (Pen pen = new Pen(lineColor, 1.0f)) using (Brush brush = new HatchBrush(HatchStyle.ForwardDiagonal, lineColor, polyColor)) { pen.Alignment = PenAlignment.Inset; // Draw all closed polygons foreach (var points in ClipperLib.Clipper.ClosedPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.AddPolygon(pointfs.ToArray()); } if (path.PointCount > 0) { g.FillPath(brush, path); g.DrawPath(pen, path); } // Draw all lines (open polygons) path.Reset(); foreach (var points in ClipperLib.Clipper.OpenPathsFromPolyTree(solution)) { var pointfs = points.Select(pt => new PointF(pt.X, pt.Y)); path.StartFigure(); path.AddLines(pointfs.ToArray()); } if (path.PointCount > 0) { g.DrawPath(pen, path); } } }
private XElement CreateCollisionElementForLayer(TmxLayer layer) { // Collision elements look like this // (Can also have EdgeCollider2Ds) // <GameOject name="Collision"> // <PolygonCollider2D> // <Path>list of points</Path> // <Path>another list of points</Path> // </PolygonCollider2D> // </GameOject> LayerClipper.TransformPointFunc xfFunc = delegate(float x, float y) { // Transform point to Unity space PointF pointUnity3d = PointFToUnityVector_NoScale(new PointF(x, y)); ClipperLib.IntPoint point = new ClipperLib.IntPoint(pointUnity3d.X, pointUnity3d.Y); return point; }; LayerClipper.ProgressFunc progFunc = delegate(string prog) { Program.WriteLine(prog); }; ClipperLib.PolyTree solution = LayerClipper.ExecuteClipper(this.tmxMap, layer, xfFunc, progFunc); var paths = ClipperLib.Clipper.ClosedPathsFromPolyTree(solution); if (paths.Count >= MaxNumberOfSafePaths) { StringBuilder warning = new StringBuilder(); warning.AppendFormat("Layer '{0}' has a large number of polygon paths ({1}).", layer.Name, paths.Count); warning.AppendLine(" Importing this layer may be slow in Unity. (Can take an hour or more for +1000 paths.)"); warning.AppendLine(" Check polygon/rectangle objects in Tile Collision Editor in Tiled and use 'Snap to Grid' or 'Snap to Fine Grid'."); warning.AppendLine(" You want colliders to be set up so they can be merged with colliders on neighboring tiles, reducing path count considerably."); warning.AppendLine(" In some cases the size of the map may need to be reduced."); Program.WriteWarning(warning.ToString()); } // Add our polygon and edge colliders List<XElement> polyColliderElements = new List<XElement>(); if (layer.IsExportingConvexPolygons()) { AddPolygonCollider2DElements_Convex(solution, polyColliderElements); } else { AddPolygonCollider2DElements_Complex(solution, polyColliderElements); } AddEdgeCollider2DElements(ClipperLib.Clipper.OpenPathsFromPolyTree(solution), polyColliderElements); if (polyColliderElements.Count() == 0) { // No collisions on this layer return null; } XElement gameObjectCollision = new XElement("GameObject", new XAttribute("name", "Collision"), polyColliderElements); // Collision layer may have a name and "unity physics layer" to go with it // (But not if we're using unity:layer override) if (String.IsNullOrEmpty(layer.UnityLayerOverrideName) && !String.IsNullOrEmpty(layer.Name)) { gameObjectCollision.SetAttributeValue("name", "Collision_" + layer.Name); gameObjectCollision.SetAttributeValue("layer", layer.Name); } return gameObjectCollision; }