private void AddTileObjectElements(TmxObjectTile tmxObjectTile, XElement xmlTileObjectRoot) { // TileObjects can be scaled (this is separate from vertex scaling) SizeF scale = tmxObjectTile.GetTileObjectScale(); // Flipping is done through negative-scaling on the child object float flip_w = tmxObjectTile.FlippedHorizontal ? -1.0f : 1.0f; float flip_h = tmxObjectTile.FlippedVertical ? -1.0f : 1.0f; // Helper values for moving tile about local origin float full_w = tmxObjectTile.Tile.TileSize.Width; float full_h = tmxObjectTile.Tile.TileSize.Height; float half_w = full_w * 0.5f; float half_h = full_h * 0.5f; // Scale goes onto root node xmlTileObjectRoot.SetAttributeValue("scaleX", scale.Width); xmlTileObjectRoot.SetAttributeValue("scaleY", scale.Height); // We combine the properties of the tile that is referenced and add it to our own properties AssignTiledProperties(tmxObjectTile.Tile, xmlTileObjectRoot); // Add a TileObject component for scripting purposes { XElement xmlTileObjectComponent = new XElement("TileObjectComponent"); xmlTileObjectComponent.SetAttributeValue("width", tmxObjectTile.Tile.TileSize.Width * scale.Width * Tiled2Unity.Settings.Scale); xmlTileObjectComponent.SetAttributeValue("height", tmxObjectTile.Tile.TileSize.Height * scale.Height * Tiled2Unity.Settings.Scale); xmlTileObjectRoot.Add(xmlTileObjectComponent); } // Child node positions game object to match center of tile so can flip along x and y axes XElement xmlTileObject = new XElement("GameObject"); xmlTileObject.SetAttributeValue("name", "TileObject"); if (tmxMap.Orientation == TmxMap.MapOrientation.Isometric) { // In isometric mode the local origin of the tile is at the bottom middle xmlTileObject.SetAttributeValue("x", 0); xmlTileObject.SetAttributeValue("y", half_h); } else { // For non-isometric maps the local origin of the tile is the bottom left xmlTileObject.SetAttributeValue("x", half_w); xmlTileObject.SetAttributeValue("y", half_h); } xmlTileObject.SetAttributeValue("scaleX", flip_w); xmlTileObject.SetAttributeValue("scaleY", flip_h); // Add any colliders that might be on the tile // Note: Colliders on a tile object are always treated as if they are in Orthogonal space TmxMap.MapOrientation restoreOrientation = tmxMap.Orientation; this.tmxMap.Orientation = TmxMap.MapOrientation.Orthogonal; { foreach (TmxObject tmxObject in tmxObjectTile.Tile.ObjectGroup.Objects) { XElement objElement = null; if (tmxObject.GetType() == typeof(TmxObjectRectangle)) { // Note: Tile objects have orthographic rectangles even in isometric orientations so no need to transform rectangle points objElement = CreateBoxColliderElement(tmxObject as TmxObjectRectangle); } else if (tmxObject.GetType() == typeof(TmxObjectEllipse)) { objElement = CreateCircleColliderElement(tmxObject as TmxObjectEllipse, tmxObjectTile.Tile.ObjectGroup.Name); } else if (tmxObject.GetType() == typeof(TmxObjectPolygon)) { objElement = CreatePolygonColliderElement(tmxObject as TmxObjectPolygon); } else if (tmxObject.GetType() == typeof(TmxObjectPolyline)) { objElement = CreateEdgeColliderElement(tmxObject as TmxObjectPolyline); } if (objElement != null) { // This object is currently in the center of the Tile Object we are constructing // The collision geometry is wrt the top-left corner // The "Offset" of the collider translation to get to lop-left corner and the collider's position into account float offset_x = (-half_w + tmxObject.Position.X) * Tiled2Unity.Settings.Scale; float offset_y = (half_h - tmxObject.Position.Y) * Tiled2Unity.Settings.Scale; objElement.SetAttributeValue("offsetX", offset_x); objElement.SetAttributeValue("offsetY", offset_y); xmlTileObject.Add(objElement); } } } this.tmxMap.Orientation = restoreOrientation; // Add a child for each mesh // (The child node is needed due to animation) foreach (var mesh in tmxObjectTile.Tile.Meshes) { XElement xmlMeshObject = new XElement("GameObject"); xmlMeshObject.SetAttributeValue("name", mesh.ObjectName); xmlMeshObject.SetAttributeValue("copy", mesh.UniqueMeshName); xmlMeshObject.SetAttributeValue("sortingLayerName", tmxObjectTile.SortingLayerName ?? tmxObjectTile.ParentObjectGroup.SortingLayerName); xmlMeshObject.SetAttributeValue("sortingOrder", tmxObjectTile.SortingOrder ?? tmxObjectTile.ParentObjectGroup.SortingOrder); // Game object that contains mesh moves position to that local origin of Tile Object (from Tiled's point of view) matches the root position of the Tile game object // Put another way: This translation moves away from center to local origin xmlMeshObject.SetAttributeValue("x", -half_w); xmlMeshObject.SetAttributeValue("y", half_h); if (mesh.FullAnimationDurationMs > 0) { XElement xmlAnimation = new XElement("TileAnimator", new XAttribute("startTimeMs", mesh.StartTimeMs), new XAttribute("durationMs", mesh.DurationMs), new XAttribute("fullTimeMs", mesh.FullAnimationDurationMs)); xmlMeshObject.Add(xmlAnimation); } xmlTileObject.Add(xmlMeshObject); } xmlTileObjectRoot.Add(xmlTileObject); }
private void DrawObjectCollider(Graphics g, TmxObject tmxObject, Color color) { Color brushColor = Color.FromArgb(128, color); using (Brush brush = new HatchBrush(HatchStyle.BackwardDiagonal, color, brushColor)) using (Pen pen = new Pen(color)) { pen.Alignment = PenAlignment.Inset; GraphicsState state = g.Save(); PointF xfPosition = TmxMath.ObjectPointFToMapSpace(this.tmxMap, tmxObject.Position); g.TranslateTransform(xfPosition.X, xfPosition.Y); g.RotateTransform(tmxObject.Rotation); if (tmxObject.GetType() == typeof(TmxObjectPolygon)) { DrawPolygon(g, pen, brush, tmxObject as TmxObjectPolygon); } else if (tmxObject.GetType() == typeof(TmxObjectRectangle)) { if (this.tmxMap.Orientation == TmxMap.MapOrientation.Isometric) { TmxObjectPolygon tmxIsometricRectangle = TmxObjectPolygon.FromRectangle(this.tmxMap, tmxObject as TmxObjectRectangle); DrawPolygon(g, pen, brush, tmxIsometricRectangle); } else { // Rectangles are polygons DrawPolygon(g, pen, brush, tmxObject as TmxObjectPolygon); } } else if (tmxObject.GetType() == typeof(TmxObjectEllipse)) { DrawEllipse(g, pen, brush, tmxObject as TmxObjectEllipse); } else if (tmxObject.GetType() == typeof(TmxObjectPolyline)) { DrawPolyline(g, pen, tmxObject as TmxObjectPolyline); } else if (tmxObject.GetType() == typeof(TmxObjectTile)) { GraphicsState tileState = g.Save(); TmxObjectTile tmxObjectTile = tmxObject as TmxObjectTile; // Apply scale SizeF scale = tmxObjectTile.GetTileObjectScale(); g.ScaleTransform(scale.Width, scale.Height); // Apply horizontal flip if (tmxObjectTile.FlippedHorizontal) { g.TranslateTransform(tmxObjectTile.Tile.TileSize.Width, 0); g.ScaleTransform(-1, 1); } // Apply vertical flip if (tmxObjectTile.FlippedVertical) { g.TranslateTransform(0, -tmxObjectTile.Tile.TileSize.Height); g.ScaleTransform(1, -1); } // (Note: Now we can draw the tile and collisions as normal as the transforms have been set up.) // Draw the tile Rectangle destination = new Rectangle(0, -tmxObjectTile.Tile.TileSize.Height, tmxObjectTile.Tile.TileSize.Width, tmxObjectTile.Tile.TileSize.Height); Rectangle source = new Rectangle(tmxObjectTile.Tile.LocationOnSource, tmxObjectTile.Tile.TileSize); g.DrawImage(tmxObjectTile.Tile.TmxImage.ImageBitmap, destination, source, GraphicsUnit.Pixel); // Put a black border around the tile so it sticks out a bit as an object g.DrawRectangle(Pens.Black, destination); // Draw the collisions // Make up for the fact that the bottom-left corner is the origin g.TranslateTransform(0, -tmxObjectTile.Tile.TileSize.Height); foreach (var obj in tmxObjectTile.Tile.ObjectGroup.Objects) { DrawObjectCollider(g, obj, Color.Gray); } g.Restore(tileState); } else { g.Restore(state); RectangleF bounds = tmxObject.GetWorldBounds(); g.FillRectangle(Brushes.Red, bounds.X, bounds.Y, bounds.Width, bounds.Height); g.DrawRectangle(Pens.White, bounds.X, bounds.Y, bounds.Width, bounds.Height); string message = String.Format("Unhandled object: {0}", tmxObject.GetNonEmptyName()); DrawString(g, message, bounds.X, bounds.Y); } // Restore our state g.Restore(state); } }
private void AddTileObjectElements(TmxObjectTile tmxObjectTile, XElement xmlTileObjectRoot) { // We combine the properties of the tile that is referenced and add it to our own properties AssignTiledProperties(tmxObjectTile.Tile, xmlTileObjectRoot); // TileObjects can be scaled (this is separate from vertex scaling) SizeF scale = tmxObjectTile.GetTileObjectScale(); xmlTileObjectRoot.SetAttributeValue("scaleX", scale.Width); xmlTileObjectRoot.SetAttributeValue("scaleY", scale.Height); // Need another transform to help us with flipping of the tile (and their collisions) XElement xmlTileObject = new XElement("GameObject"); xmlTileObject.SetAttributeValue("name", "TileObject"); if (tmxObjectTile.FlippedHorizontal) { xmlTileObject.SetAttributeValue("x", tmxObjectTile.Tile.TileSize.Width * Tiled2Unity.Settings.Scale); xmlTileObject.SetAttributeValue("flipX", true); } if (tmxObjectTile.FlippedVertical) { xmlTileObject.SetAttributeValue("y", tmxObjectTile.Tile.TileSize.Height * Tiled2Unity.Settings.Scale); xmlTileObject.SetAttributeValue("flipY", true); } // Add any colliders that might be on the tile // Note: Colliders on a tile object are always treated as if they are in Orthogonal space TmxMap.MapOrientation restoreOrientation = tmxMap.Orientation; this.tmxMap.Orientation = TmxMap.MapOrientation.Orthogonal; { foreach (TmxObject tmxObject in tmxObjectTile.Tile.ObjectGroup.Objects) { XElement objElement = null; if (tmxObject.GetType() == typeof(TmxObjectRectangle)) { // Note: Tile objects have orthographic rectangles even in isometric orientations so no need to transform rectangle points objElement = CreateBoxColliderElement(tmxObject as TmxObjectRectangle); } else if (tmxObject.GetType() == typeof(TmxObjectEllipse)) { objElement = CreateCircleColliderElement(tmxObject as TmxObjectEllipse, tmxObjectTile.Tile.ObjectGroup.Name); } else if (tmxObject.GetType() == typeof(TmxObjectPolygon)) { objElement = CreatePolygonColliderElement(tmxObject as TmxObjectPolygon); } else if (tmxObject.GetType() == typeof(TmxObjectPolyline)) { objElement = CreateEdgeColliderElement(tmxObject as TmxObjectPolyline); } if (objElement != null) { // Objects can be offset (and we need to make up for the bottom-left corner being the origin in a TileObject) objElement.SetAttributeValue("offsetX", tmxObject.Position.X * Tiled2Unity.Settings.Scale); objElement.SetAttributeValue("offsetY", (tmxObjectTile.Size.Height - tmxObject.Position.Y) * Tiled2Unity.Settings.Scale); xmlTileObject.Add(objElement); } } } this.tmxMap.Orientation = restoreOrientation; // Add a child for each mesh (with animation if needed) foreach (var mesh in tmxObjectTile.Tile.Meshes) { XElement xmlMeshObject = new XElement("GameObject"); xmlMeshObject.SetAttributeValue("name", mesh.ObjectName); xmlMeshObject.SetAttributeValue("copy", mesh.UniqueMeshName); xmlMeshObject.SetAttributeValue("sortingLayerName", tmxObjectTile.SortingLayerName ?? tmxObjectTile.ParentObjectGroup.SortingLayerName); xmlMeshObject.SetAttributeValue("sortingOrder", tmxObjectTile.SortingOrder ?? tmxObjectTile.ParentObjectGroup.SortingOrder); // This object, that actually displays the tile, has to be bumped up to account for the bottom-left corner problem with Tile Objects in Tiled xmlMeshObject.SetAttributeValue("x", 0); xmlMeshObject.SetAttributeValue("y", tmxObjectTile.Tile.TileSize.Height * Tiled2Unity.Settings.Scale); if (mesh.FullAnimationDurationMs > 0) { XElement xmlAnimation = new XElement("TileAnimator", new XAttribute("startTimeMs", mesh.StartTimeMs), new XAttribute("durationMs", mesh.DurationMs), new XAttribute("fullTimeMs", mesh.FullAnimationDurationMs)); xmlMeshObject.Add(xmlAnimation); } xmlTileObject.Add(xmlMeshObject); } xmlTileObjectRoot.Add(xmlTileObject); }