361 lines
14 KiB
C#
361 lines
14 KiB
C#
using System.Linq;
|
|
using System.Xml.Linq;
|
|
using SuperTiled2Unity.Editor.Geometry;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace SuperTiled2Unity.Editor
|
|
{
|
|
public class SuperObjectLayerLoader : SuperLayerLoader
|
|
{
|
|
private SuperObjectLayer m_ObjectLayer;
|
|
private float m_AnimationFramerate = 1.0f;
|
|
|
|
public SuperObjectLayerLoader(XElement xml, TiledAssetImporter importer)
|
|
: base(xml, importer)
|
|
{
|
|
}
|
|
|
|
public ColliderFactory ColliderFactory { get; set; }
|
|
public GlobalTileDatabase GlobalTileDatabase { get; set; }
|
|
public SuperMap SuperMap { get; set; }
|
|
|
|
public float AnimationFramerate
|
|
{
|
|
get { return m_AnimationFramerate; }
|
|
set { m_AnimationFramerate = value; }
|
|
}
|
|
|
|
public void CreateObjects()
|
|
{
|
|
Assert.IsNotNull(Xml, "Xml exlement is null");
|
|
Assert.IsNotNull(m_ObjectLayer, "Object layer is null");
|
|
Assert.IsNotNull(Importer, "Importer is null");
|
|
Assert.IsNotNull(ColliderFactory, "ColliderFactory is null");
|
|
|
|
var xObjects = Xml.Elements("object");
|
|
var drawOrder = Xml.GetAttributeAs<DrawOrder>("draworder", DrawOrder.TopDown);
|
|
|
|
if (drawOrder == DrawOrder.TopDown)
|
|
{
|
|
// xObjects need to be ordered by y-value
|
|
xObjects = xObjects.OrderBy(x => x.GetAttributeAs<float>("y", 0.0f));
|
|
}
|
|
|
|
foreach (var xObj in xObjects)
|
|
{
|
|
// Ignore invisible objects
|
|
if (!xObj.GetAttributeAs<bool>("visible", true))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CreateObject(xObj);
|
|
}
|
|
}
|
|
|
|
protected override SuperLayer CreateLayerComponent(GameObject go)
|
|
{
|
|
m_ObjectLayer = go.AddComponent<SuperObjectLayer>();
|
|
return m_ObjectLayer;
|
|
}
|
|
|
|
protected override void InternalLoadFromXml(GameObject go)
|
|
{
|
|
m_ObjectLayer.m_Color = Xml.GetAttributeAsColor("color", Color.grey);
|
|
}
|
|
|
|
private void CreateObject(XElement xObject)
|
|
{
|
|
// Templates may add extra data
|
|
Importer.ApplyTemplateToObject(xObject);
|
|
|
|
// Create the super object and fill it out
|
|
var superObject = CreateSuperObject(xObject);
|
|
FillSuperObject(superObject, xObject);
|
|
|
|
// Take care of properties
|
|
Importer.AddSuperCustomProperties(superObject.gameObject, xObject.Element("properties"), superObject.m_SuperTile, superObject.m_Type);
|
|
|
|
// Post processing after custom properties have been set
|
|
PostProcessObject(superObject.gameObject);
|
|
}
|
|
|
|
private SuperObject CreateSuperObject(XElement xObject)
|
|
{
|
|
// Create the object
|
|
GameObject goObject = new GameObject();
|
|
var comp = goObject.AddComponent<SuperObject>();
|
|
|
|
// Fill out the attributes
|
|
comp.m_Id = xObject.GetAttributeAs("id", 0);
|
|
comp.m_TiledName = xObject.GetAttributeAs("name", string.Format("Object_{0}", comp.m_Id));
|
|
|
|
comp.m_Type = xObject.GetAttributeAs("class", "");
|
|
|
|
//As of Tiled 1.9 types have been merged with classes
|
|
//As a simple way to support both version we can fall back like this to the old < 1.9 way
|
|
if (string.IsNullOrWhiteSpace(comp.m_Type))
|
|
{
|
|
comp.m_Type = xObject.GetAttributeAs("type", "");
|
|
}
|
|
|
|
comp.m_X = xObject.GetAttributeAs("x", 0.0f);
|
|
comp.m_Y = xObject.GetAttributeAs("y", 0.0f);
|
|
comp.m_Rotation = xObject.GetAttributeAs("rotation", 0.0f);
|
|
comp.m_Width = xObject.GetAttributeAs("width", 0.0f);
|
|
comp.m_Height = xObject.GetAttributeAs("height", 0.0f);
|
|
comp.m_TileId = xObject.GetAttributeAs<uint>("gid", 0);
|
|
comp.m_Visible = xObject.GetAttributeAs("visible", true);
|
|
comp.m_Template = xObject.GetAttributeAs("template", "");
|
|
|
|
// Assign the name of our game object
|
|
if (comp.m_TileId != 0)
|
|
{
|
|
// The tile object name is decorated. A descendent will have the "real" object name.
|
|
goObject.name = string.Format("{0} (TRS)", comp.m_TiledName);
|
|
}
|
|
else
|
|
{
|
|
goObject.name = comp.m_TiledName;
|
|
}
|
|
|
|
// Position the game object
|
|
var localPosition = new Vector2(comp.m_X, comp.m_Y);
|
|
localPosition = ColliderFactory.TransformPoint(localPosition);
|
|
localPosition = Importer.SuperImportContext.MakePoint(localPosition);
|
|
|
|
goObject.transform.localPosition = localPosition;
|
|
goObject.transform.localRotation = Quaternion.Euler(0, 0, Importer.SuperImportContext.MakeRotation(comp.m_Rotation));
|
|
|
|
// Add our object to the parent layer
|
|
m_ObjectLayer.gameObject.AddChildWithUniqueName(goObject);
|
|
|
|
return comp;
|
|
}
|
|
|
|
private void FillSuperObject(SuperObject superObject, XElement xObject)
|
|
{
|
|
// Determine which type of object we are
|
|
var xPolygon = xObject.Element("polygon");
|
|
var xPolyline = xObject.Element("polyline");
|
|
var xEllipse = xObject.Element("ellipse");
|
|
var xPoint = xObject.Element("point");
|
|
var xText = xObject.Element("text");
|
|
|
|
bool collisions = Importer.SuperImportContext.LayerIgnoreMode != LayerIgnoreMode.Collision;
|
|
|
|
if (superObject.m_TileId != 0)
|
|
{
|
|
ProcessTileObject(superObject, xObject);
|
|
}
|
|
else if (xPolygon != null && collisions)
|
|
{
|
|
ProcessPolygonElement(superObject.gameObject, xPolygon);
|
|
}
|
|
else if (xPolyline != null && collisions)
|
|
{
|
|
ProcessPolylineElement(superObject.gameObject, xPolyline);
|
|
}
|
|
else if (xEllipse != null && collisions)
|
|
{
|
|
ProcessEllipseElement(superObject.gameObject, xObject);
|
|
}
|
|
else if (xText != null)
|
|
{
|
|
// Text objects are not yet supported
|
|
}
|
|
else if (xPoint != null)
|
|
{
|
|
// A point is simply an empty game object out in space.
|
|
// We don't need to add anything else
|
|
}
|
|
else if (collisions)
|
|
{
|
|
// Default object is a rectangle
|
|
ProcessObjectRectangle(superObject.gameObject, xObject);
|
|
}
|
|
}
|
|
|
|
private void ProcessTileObject(SuperObject superObject, XElement xObject)
|
|
{
|
|
Assert.IsNull(superObject.m_SuperTile);
|
|
Assert.IsNotNull(GlobalTileDatabase, "Cannot process tile objects without a tileset database");
|
|
|
|
SuperTile tile = null;
|
|
var tileId = new TileIdMath(superObject.m_TileId);
|
|
int justTileId = tileId.JustTileId;
|
|
|
|
// Are we getting the tile from a template?
|
|
var template = xObject.GetAttributeAs("template", "");
|
|
if (!string.IsNullOrEmpty(template))
|
|
{
|
|
var asset = Importer.RequestDependencyAssetAtPath<ObjectTemplate>(template);
|
|
if (asset == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tile = asset.m_Tile;
|
|
if (tile == null)
|
|
{
|
|
Importer.ReportGenericError($"Missing tile '{justTileId}' from template '{template}' on tile object '{superObject.name}'");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Are we getting the tile from our tile database?
|
|
if (tile == null)
|
|
{
|
|
GlobalTileDatabase.TryGetTile(justTileId, out tile);
|
|
|
|
if (tile == null)
|
|
{
|
|
Importer.ReportGenericError($"Missing tile '{justTileId}' on tile object '{superObject.name}'");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Our type may come from the tile as well (this is 'Typed Tiles' in Tiled)
|
|
if (string.IsNullOrEmpty(superObject.m_Type))
|
|
{
|
|
superObject.m_Type = tile.m_Type;
|
|
}
|
|
|
|
// Construct the game objects for displaying a single tile
|
|
var inversePPU = Importer.InversePPU;
|
|
bool flip_h = tileId.HasHorizontalFlip;
|
|
bool flip_v = tileId.HasVerticalFlip;
|
|
|
|
var scale = Vector3.one;
|
|
scale.x = xObject.GetAttributeAs("width", 1.0f);
|
|
scale.y = xObject.GetAttributeAs("height", 1.0f);
|
|
|
|
scale.x /= tile.m_Width;
|
|
scale.y /= tile.m_Height;
|
|
|
|
var tileOffset = new Vector3(tile.m_TileOffsetX * inversePPU, -tile.m_TileOffsetY * inversePPU);
|
|
var pivotOffset = ObjectAlignmentToPivot.ToVector3(tile.m_Width, tile.m_Height, inversePPU, SuperMap.m_Orientation, tile.m_ObjectAlignment);
|
|
|
|
var translateCenter = new Vector3(tile.m_Width * 0.5f * inversePPU, tile.m_Height * 0.5f * inversePPU);
|
|
|
|
// Our root object will contain the translation, rotation, and scale of the tile object
|
|
var goTRS = superObject.gameObject;
|
|
goTRS.transform.localScale = scale;
|
|
|
|
// Our pivot object will contain the tileset orientation and offset
|
|
var goPivot = new GameObject();
|
|
goPivot.name = string.Format("{0} (Pivot)", superObject.m_TiledName);
|
|
goPivot.transform.localPosition = tileOffset + pivotOffset;
|
|
goTRS.AddChildWithUniqueName(goPivot);
|
|
|
|
// Add another object to handle tile flipping
|
|
// This object will center us into the tile and perform the flips through scaling
|
|
// This object also contains the tile offset in her transform
|
|
var goCF = new GameObject();
|
|
goCF.name = string.Format("{0} (CF)", superObject.m_TiledName);
|
|
goPivot.AddChildWithUniqueName(goCF);
|
|
|
|
goCF.transform.localPosition = translateCenter;
|
|
goCF.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
|
goCF.transform.localScale = new Vector3(flip_h ? -1 : 1, flip_v ? -1 : 1, 1);
|
|
|
|
var fromCenter = -translateCenter;
|
|
|
|
// Add another child, putting our coordinates back into the proper place
|
|
var goTile = new GameObject(superObject.m_TiledName);
|
|
goCF.AddChildWithUniqueName(goTile);
|
|
goTile.transform.localPosition = fromCenter;
|
|
goTile.transform.localRotation = Quaternion.Euler(0, 0, 0);
|
|
goTile.transform.localScale = Vector3.one;
|
|
|
|
if (Importer.SuperImportContext.LayerIgnoreMode != LayerIgnoreMode.Visual)
|
|
{
|
|
// Add the renderer
|
|
var renderer = goTile.AddComponent<SpriteRenderer>();
|
|
renderer.sprite = tile.m_Sprite;
|
|
renderer.color = superObject.CalculateColor();
|
|
Importer.AssignMaterial(renderer, m_ObjectLayer.m_TiledName);
|
|
Importer.AssignSpriteSorting(renderer);
|
|
|
|
// Add the animator if needed
|
|
if (!tile.m_AnimationSprites.IsEmpty())
|
|
{
|
|
var tileAnimator = goTile.AddComponent<TileObjectAnimator>();
|
|
tileAnimator.m_AnimationFramerate = AnimationFramerate;
|
|
tileAnimator.m_AnimationSprites = tile.m_AnimationSprites;
|
|
}
|
|
}
|
|
|
|
if (Importer.SuperImportContext.LayerIgnoreMode != LayerIgnoreMode.Collision)
|
|
{
|
|
// Add any colliders that were set up on the tile in the collision editor
|
|
tile.AddCollidersForTileObject(goTile, Importer.SuperImportContext);
|
|
}
|
|
|
|
// Store a reference to our tile object
|
|
superObject.m_SuperTile = tile;
|
|
}
|
|
|
|
private void ProcessPolygonElement(GameObject goObject, XElement xPolygon)
|
|
{
|
|
// Get the points of the polygon so we can decompose into a collection of convex polygons
|
|
var points = xPolygon.GetAttributeAsVector2Array("points");
|
|
points = points.Select(p => ColliderFactory.TransformPoint(p)).ToArray();
|
|
points = Importer.SuperImportContext.MakePoints(points);
|
|
|
|
// Triangulate the polygon points
|
|
var triangulator = new Triangulator();
|
|
var triangles = triangulator.TriangulatePolygon(points);
|
|
|
|
// Gather triangles into a collection of convex polygons
|
|
var composition = new ComposeConvexPolygons();
|
|
var convexPolygons = composition.Compose(triangles);
|
|
|
|
PolygonUtils.AddCompositePolygonCollider(goObject, convexPolygons, Importer.SuperImportContext);
|
|
}
|
|
|
|
private void ProcessPolylineElement(GameObject goObject, XElement xPolyline)
|
|
{
|
|
var points = xPolyline.GetAttributeAsVector2Array("points");
|
|
ColliderFactory.MakePolyline(goObject, points);
|
|
goObject.AddComponent<SuperColliderComponent>();
|
|
}
|
|
|
|
private void ProcessEllipseElement(GameObject goObject, XElement xObject)
|
|
{
|
|
var width = xObject.GetAttributeAs("width", 0f);
|
|
var height = xObject.GetAttributeAs("height", 0f);
|
|
ColliderFactory.MakeEllipse(goObject, width, height);
|
|
goObject.AddComponent<SuperColliderComponent>();
|
|
}
|
|
|
|
private void ProcessObjectRectangle(GameObject goObject, XElement xObject)
|
|
{
|
|
var width = xObject.GetAttributeAs("width", 0f);
|
|
var height = xObject.GetAttributeAs("height", 0f);
|
|
ColliderFactory.MakeBox(goObject, width, height);
|
|
goObject.AddComponent<SuperColliderComponent>();
|
|
}
|
|
|
|
private void PostProcessObject(GameObject go)
|
|
{
|
|
var properties = go.GetComponent<SuperCustomProperties>();
|
|
var collider = go.GetComponent<Collider2D>();
|
|
|
|
if (collider != null)
|
|
{
|
|
CustomProperty isTrigger;
|
|
if (properties.TryGetCustomProperty(StringConstants.Unity_IsTrigger, out isTrigger))
|
|
{
|
|
collider.isTrigger = Importer.SuperImportContext.GetIsTriggerOverridable(isTrigger.GetValueAsBool());
|
|
}
|
|
}
|
|
|
|
// Make sure all children have the same physics layer
|
|
// This is needed for Tile objects in particular that have their colliders in child objects
|
|
go.AssignChildLayers();
|
|
}
|
|
}
|
|
}
|