예제 #1
0
    /// <summary>
    /// Make sure to select the correct background image
    /// </summary>
    private void EnsureCurrentBackgroundImage()
    {
        // Instead of referencing the image component directly, and therefore breaking the code every time the location generator
        // recreates the game objects. We stored the articy game object that represents the background image, and now search for the game object
        // that was created representing the background image object.
        var            childRefs      = GetComponentsInChildren <ArticyReference>(true);
        SpriteRenderer spriteRenderer = null;

        foreach (var child in childRefs)
        {
            // same id, means the image object we stored, and the one stored on this game object is the same
            // means, this game object is our background image representation
            if (child.reference.id == imageElement.id)
            {
                // so we take its sprite renderer.
                spriteRenderer = child.gameObject.GetComponent <SpriteRenderer>();
                break;
            }
        }

        // if we don't find one, we might not have yet generated the game objects
        if (spriteRenderer == null)
        {
            Debug.LogError("Image game object not found!");
            return;
        }

        // the template contains a reference strip with a list of alternative backgrounds
        // we iterate over every one
        foreach (var background in currentLocation.Template.LocationSettings.Backgrounds)
        {
            var varBinding = background as BackgroundImage;
            if (varBinding != null)
            {
                // and call its script, checking if this texture should be used
                var shouldBeVisible = varBinding.Template.VariableBinding.VariableName.CallScript(null);
                if (shouldBeVisible)
                {
                    // now we just create the texture from the image, using the cliprect
                    var tex        = varBinding.LoadAsset <Texture2D>();
                    var clipRect   = imageElement.GetObject <LocationImage>().ClipRect;
                    var sourceRect = ArticyUtility.ConvertToSpriteSourceRect(tex, clipRect);
                    // and finally we create a sprite from the texture and rect
                    spriteRenderer.sprite = Sprite.Create(tex, sourceRect, new Vector2());
                    break;
                }
            }
        }
    }
    /// <summary>
    /// Starts the creation process.
    /// This will effectively create a new gameobject for every object in our articy location, keeping the
    /// </summary>
    public static void GenerateLocation(GameObject aTarget, ILocation aLocation, int aPixelsToUnits)
    {
        var mdl = aLocation as ArticyObject;
        var attachBehaviours = new Dictionary <string, Type>();

        // The first thing we do is, find all the user scripts inside the project
        MonoScript[] types = (MonoScript[])Resources.FindObjectsOfTypeAll(typeof(MonoScript));

        foreach (var script in types)
        {
            if (script == null)
            {
                continue;
            }

            var type = script.GetClass();

            if (type == null)
            {
                continue;
            }
            // now we check if this script contains our AttachBehaviorByTemplate attribute
            var attributes = type.GetCustomAttributes(typeof(AttachBehaviourByTemplateAttribute), true);
            if (attributes.Length > 0)
            {
                // if it does, we extract the template name that links both script and template
                var attach = attributes[0] as AttachBehaviourByTemplateAttribute;

                // now we only need to cache the type of the behavior to the template name for later use
                attachBehaviours[attach.TemplateName] = type;
            }
        }

        // here we calculate the bounds of the 2D elements we are going to create
        Rect overallBounds = new Rect();

        foreach (var child in mdl.Children)
        {
            // the only elements that could actually change the bounds are objects with vertices (Zones, images etc)
            var vertices = child as IObjectWithVertices;

            if (vertices != null)
            {
                var childBounds     = ArticyUtility.GetBounds(vertices.Vertices);
                var correctedPoints = new Vector2[vertices.Vertices.Count];
                int i = 0;
                // articy and unity have a different y axis, so we have to take care of that and also incorporating the pixels to units conversion
                foreach (var vec in vertices.Vertices)
                {
                    var v = new Vector2(vec.x / aPixelsToUnits, (childBounds.yMax - vec.y) / aPixelsToUnits);
                    correctedPoints[i++] = v;
                }
                childBounds   = ArticyUtility.GetBounds(correctedPoints);
                overallBounds = ArticyUtility.Union(overallBounds, childBounds);
            }
        }
        // and now we can create all the elements inside the location
        int zindex = 0;

        GenerateChildren(aTarget.transform, mdl, aPixelsToUnits, overallBounds, attachBehaviours, ref zindex);
    }
    /// <summary>
    /// this method is called for every articy object in the location, starting with the location itself
    /// </summary>
    private static void GenerateChildren(Transform aParent, ArticyObject aChild, int aPixelsToUnits, Rect aOverallBounds, Dictionary <string, Type> aAttachBehaviours, ref int aZindex)
    {
        // the children collection is usually unsorted, but in this case we have to worry about proper ordering regarding z sorting
        // luckly the plugin has a way to return the children sorted by a user defined method, in this case we sort our children by their zindex
        var sortedChildren = aChild.GetSortedChildren <IObjectWithZIndex>((x, y) => x.ZIndex.CompareTo(y.ZIndex) * -1);

        foreach (var sortedChild in sortedChildren)
        {
            var  child          = sortedChild as ArticyObject;
            var  gameObjectName = GetNameForGameObject(child);
            bool hasZoneScript  = false;

            #region Create new Gameobject for our Location child
            // we create a game object for the new object and parent it to our previous game object
            var childGameObject = new GameObject(gameObjectName);
            childGameObject.transform.SetParent(aParent, false);
            EditorUtility.SetDirty(childGameObject);
            Rect bounds    = new Rect();
            bool boundsSet = false;

            // then we add an articyReference to it, storing the articy object that this new game object represents with it
            var artRef = childGameObject.AddComponent <ArticyReference>();
            artRef.reference = new ArticyRef()
            {
                id = child.Id
            };
            #endregion

            #region Attach Behaviours By Template to the new game object
            // now we use the previously created cache of behaviours for specific templates.
            // So first we check via reflection, if the object has a template property
            var templateProp = child.GetType().GetProperty("Template");
            if (templateProp != null)
            {
                // and if it does, we get the value of its Template property
                var templateObj = templateProp.GetValue(child, null);
                if (templateObj != null)
                {
                    // now we can extract its class name, which usually is the Template Technical name with a Template suffix "Condition_ZoneTemplate" for example.
                    var  templateName = templateObj.GetType().Name;
                    Type behaviour;
                    // and here we ask the cache if it has a registered behaviour under the current template name
                    if (aAttachBehaviours.TryGetValue(templateName, out behaviour))
                    {
                        // if it does, we add a new script component using the stored type
                        childGameObject.AddComponent(behaviour);
                        // we also store if it was a clickable zone behaviour, because then we have to create the collider and enable it
                        // location images for example could also have an attached behaviour, but we don't want them to interfere with our raycasting
                        if (behaviour == typeof(ClickableZone))
                        {
                            hasZoneScript = true;
                        }
                    }
                }
            }
            #endregion

            #region Create a Collider if it has vertices
            // all objects with vertices will be converted to game objects with polygon collider.
            var vertices = child as IObjectWithVertices;
            if (vertices != null)
            {
                // first we add the component to our create game object
                var collider = childGameObject.AddComponent <PolygonCollider2D>();
                // then we calculate the overall bounds of this polygon
                bounds = ArticyUtility.GetBounds(vertices.Vertices);

                // and then we convert the articy vertices to unity vertices (flipped y axis and different pixel scaling)
                var colliderPoints = new Vector2[vertices.Vertices.Count];
                int i = 0;
                foreach (var vec in vertices.Vertices)
                {
                    var v = new Vector2(vec.x / aPixelsToUnits, (bounds.yMax - vec.y) / aPixelsToUnits);
                    colliderPoints[i++] = v;
                }
                // after we have converted the points, we can store them
                collider.SetPath(0, colliderPoints);

                // we need the bounds again, if this object does not only contain vertices, but also represents an image
                bounds = ArticyUtility.GetBounds(collider.points);

                // only if this is a clickable zone, we initially enable the collider.
                collider.enabled = hasZoneScript;

                boundsSet = true;
            }
            #endregion

            #region Create and setup Spriter renderer for location images

            // our location images, have a reference to an articy reference
            // we use that to fill a sprite renderer with the image
            var image = child as ILocationImage;
            if (image != null)
            {
                SpriteRenderer spriteRenderer = null;
                // use reflection to get the Image
                var asset = GetProperty <ArticyObject>(child, "ImageAsset");
                if (asset != null)
                {
                    // make sure it is an actual image asset
                    var imageAsset = asset as IAsset;
                    if (imageAsset != null && imageAsset.Category == AssetCategory.Image)
                    {
                        // add a new sprite renderer to our game object
                        spriteRenderer = childGameObject.AddComponent <SpriteRenderer>();

                        // load the underlying texture
                        var tex = imageAsset.LoadAsset <Texture2D>();
                        // we also need the cliprect, as the image might be cropped
                        var clipRect = GetProperty <Rect>(child, "ClipRect");
                        // convert the clip rect to a sprite source rect
                        var sourceRect = ArticyUtility.ConvertToSpriteSourceRect(tex, clipRect);
                        // the sorting order is reversed in regards to our zindex,
                        spriteRenderer.sortingOrder = 1000 - aZindex;
                        // and finally convert the texture to a sprite using our calculated source rect
                        spriteRenderer.sprite = Sprite.Create(tex, sourceRect, new Vector2());
                        if (!boundsSet)
                        {
                            bounds.width  = tex.width / (float)aPixelsToUnits;
                            bounds.height = tex.height / (float)aPixelsToUnits;
                        }
                    }
                }
            }
            #endregion

            #region Adjust the objects transformations

            var trans     = childGameObject.transform;
            var transform = child as IObjectWithTransformation;
            if (transform != null)
            {
                // change the position
                trans.localPosition = new Vector3(transform.Transform.Translation.x / aPixelsToUnits,
                                                  aOverallBounds.height - bounds.height - (transform.Transform.Translation.y / aPixelsToUnits));
                // Todo: scale and rotating
            }
            #endregion

            aZindex++;

            // some objects inside a location, can have children themselves, so we just recursively call GenerateChildren again
            GenerateChildren(childGameObject.transform, child, aPixelsToUnits, aOverallBounds, aAttachBehaviours, ref aZindex);
        }
    }