Forum rules - please read before posting.

[Feature Request] Make objects' relations in the hierarchy available to ActionLists and Parameters

There is that great option to change an object's material via an ActionList action.

What I'd like to suggest though is an easy way to e.g. change a ParentGameObject A's 3rd ChildGameObject's material to a ParentGameObject B's 1st ChildGameObject's material.

I don't know if this is a feasible request at all because clearly that would probably require to take hierarchy orders in the unity editor window into consideration (and accidentally rearranging something in the editor would break the game logic, so game objects would need an option to get locked in place, too).

But it would be so cool!

Comments

  • I don't quite follow your example, but you can assign Materials in Unity Object parameters.

    By hooking into the OnBeginActionList custom event, you can update parameter values through script. One such method would be to have the two ParentGameObjects be GameObject parameters which have been previously set by e.g. the ActionList: Run Action, and then a custom script could set the Unity Object parameter accordingly:

    using UnityEngine;
    using AC;
    
    public class ActionListParamExample : MonoBehaviour
    {
    
        public ActionList actionList;
    
        private void OnEnable () { EventManager.OnBeginActionList += OnBeginActionList; }
        private void OnDisable () { EventManager.OnBeginActionList -= OnBeginActionList; }
    
        private void OnBeginActionList (ActionList actionList, ActionListAsset actionListAsset, int startingIndex, bool isSkipping)
        {
            if (this.actionList == actionList)
            {
                GameObject go = actionList.GetParameter (0); // ParentA is param 0
                Material mat = go.GetComponent <Renderer>().materials[0];
                actionList.GetParameter (1).SetValue (mat); // Material is param 1
            }
        }
    
    }
    
  • edited February 2020

    My point is that I do not want the material of the parent object as a parameter (as it does not have one, it does not even have a mesh renderer but is just an empty parent) … but the material of one of its children objects, depending on this objects position in the children‘s list.

    Maybe I should explain It from a different angle: Let’s assume I want to change Object ABC‘s material to that of one out of eight other objects, depending on which of them gets clicked.

    Right now I can change an object‘s material only by saying …

    „Object ABC: Change Material to (red leather.mat)“

    … while the material can of course be a unity object parameter, but I still have to name/select a specific (!) material in the interaction.

    What I love to have though is something like …

    „Object ABC: Change Material to the material of Object DEF“

    … or even …

    „Object ABC: Change Material to the material of the nth Child of Object DEF“.

    It‘s actually all about more flexibility in creating puzzles and at the same time avoid duplicate information. In the above example, I have to enter one and the same material information in two different places:
    1. The mesh renderer of Object DEF
    2. The target material of the „Object: Change material“ action.

    What I am looking for is eliminating step 2 by making the material reference relative instead of absolute (and thus eliminate a potential error source by accidentally entering incoherent material information, too, because from now on I need to specify a material only once) and pull the material information from a source object instead of having to specify it explicitly.

    Especially with the new variables component, which allows for creating multiple variants of one Single puzzle prefab So easily, that would be an additional massive time saver.

  • Thanks for the clarification.

    I think referencing an object by being the "nth child" of something creates more room for error, since the Hierarchy can change.

    However, it would be possible to write a custom Action to set a Material from this if that's what you wanted.

    I would still say that setting parameters via hook events is the way to go. Did you try the code above? It still sounds applicable based on your example, even if you have to replace "go.GetComponent" with "go.transform.GetChild (n).GetComponent" to get the exact Material you're after.

  • As regards the nth child thing in the hierarchy, I certainly agree.

    The more I look at it, the more I realise that I have been merging two basically unrelated requests into one:

    1. A relative "Object: Copy component value(s) from another object" action.
    2. A relative hierarchy based selection of objects through actions.

    Actually, I am only requesting No. 1.

    No, I have not tried the code, because it is far beyond my capabilities to understand or even implement it for my needs … ;-)
    I love your ActionList Editor, because I understand it and can handle it. But I am no coder and am completely lost with "hooks" and "privates" and "voids" and so on …

  • edited February 2020

    Writing a system to copy generic component values without direct references - in a way that works at runtime - involves use of reflection. This can often be messy, and things only get more complicated when you want to be able to specify a particular field. I'm afraid it's not something I'd be looking to incorporate officially.

    However, there do exist quite a few threads on the Unity forums about component copying which can be used in custom scripts. The most basic way to trigger a custom script function in AC is to create a new C# script, paste that function into it, attach it to a GameObject in your scene, and then use either AC's Object: Call event or Object: Send message Actions to run it. Both avoid you having to write custom Actions or event hooks.

    Writing an Action that copies specific values though, is very different. Here's an Action that copies the Material from one GameObject, and applies it to another:

    using UnityEngine;
    using System.Collections.Generic;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    namespace AC
    {
    
        [System.Serializable]
        public class ActionCopyMaterial : Action
        {
    
            public GameObject rendererFrom;
            public int rendererFromConstantID;
            public int rendererFromParameterID = -1;
            private GameObject runtimeRendererFrom;
    
            public GameObject rendererTo;
            public int rendererToConstantID;
            public int rendererToParameterID = -1;
            private GameObject runtimeRendererTo;
    
    
            public ActionCopyMaterial ()
            {
                this.isDisplayed = true;
                category = ActionCategory.Object;
                title = "Copy material";
            }
    
    
            public override void AssignValues(List<ActionParameter> parameters)
            {
                runtimeRendererFrom = AssignFile (parameters, rendererFromParameterID, rendererFromConstantID, rendererFrom);
                runtimeRendererTo = AssignFile (parameters, rendererToParameterID, rendererToConstantID, rendererTo);
            }
    
    
            public override float Run ()
            {
                if (runtimeRendererFrom != null && runtimeRendererTo != null)
                {
                    rendererTo.GetComponent <Renderer>().material = rendererFrom.GetComponent <Renderer>().material;
                }
                return 0f;
            }
    
    
            #if UNITY_EDITOR
    
            public override void ShowGUI (List<ActionParameter> parameters)
            {
                EditorGUILayout.LabelField ("From:");
    
                rendererFromParameterID = Action.ChooseParameterGUI ("Renderer:", parameters, rendererFromParameterID, ParameterType.GameObject);
                if (rendererFromParameterID >= 0)
                {
                    rendererFromConstantID = 0;
                    rendererFrom = null;
                }
                else
                {
                    rendererFrom = (GameObject) EditorGUILayout.ObjectField ("Renderer:", rendererFrom, typeof (GameObject), true);
    
                    rendererFromConstantID = FieldToID (rendererFrom, rendererFromConstantID);
                    rendererFrom = IDToField (rendererFrom, rendererFromConstantID, true, false);
                }
    
                EditorGUILayout.LabelField ("To:");
    
                rendererToParameterID = Action.ChooseParameterGUI ("Renderer:", parameters, rendererToParameterID, ParameterType.GameObject);
                if (rendererToParameterID >= 0)
                {
                    rendererToConstantID = 0;
                    rendererTo = null;
                }
                else
                {
                    rendererTo = (GameObject) EditorGUILayout.ObjectField ("Renderer:", rendererTo, typeof (GameObject), true);
    
                    rendererToConstantID = FieldToID (rendererTo, rendererToConstantID);
                    rendererTo = IDToField (rendererTo, rendererToConstantID, true, false);
                }
    
                AfterRunningOption ();
            }
    
            #endif
    
        }
    
    }
    

    Place that in a C# script named ActionCopyMaterial, and install it as a custom Action following the steps covered in this tutorial.

  • edited October 2020

    Hey Chris,

    I've never thanked you for this script, probably because a completely different project came up then and I never got to try it so far. So thank you now! ;-)

    There is one complaint from the console though:

    UnassignedReferenceException: The variable rendererTo of ActionCopyMaterial has not been assigned.
    You probably need to assign the rendererTo variable of the ActionCopyMaterial script in the inspector.

    The full log is too long to copy; it starts with (Sequence 1)…

    UnityEngine.GameObject.GetComponent[T] () (at /Users/builduser/buildslave/unity/build/Runtime/Export/Scripting/GameObject.bindings.cs:28)
    AC.ActionCopyMaterial.Run () (at Assets/Onyon/MyActions/ActionCopyMaterial.cs:44)
    AC.ActionList+d__34.MoveNext () (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:377)
    UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Scripting/Coroutines.cs:17)

    … next up is this (Sequence 2, which gets repeated several times) …

    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    AC.ActionList:ProcessAction(Int32) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:327)
    AC.ActionList:ProcessActionEnd(ActionEnd, Int32, Boolean) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:563)
    AC.ActionList:EndAction(Action) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:481)
    AC.d__34:MoveNext() (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:449)

    … followed by one instance of this (Sequence 3) …

    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    AC.ActionList:ProcessAction(Int32) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:327)
    AC.ActionList:BeginActionList(Int32, Boolean) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:294)
    AC.ActionList:Interact(Int32, Boolean) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:195)
    AC.ActionRunActionList:Run() (at Assets/Plugins/AdventureCreator/Scripts/Actions/ActionRunActionList.cs:159)
    AC.d__34:MoveNext() (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:377)

    … then several repetitions of Sequence 2 plus one instance of Sequence 3 follow for several times, before the message ends with this (Sequence 4):

    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    AC.ActionList:ProcessAction(Int32) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:327)
    AC.ActionList:BeginActionList(Int32, Boolean) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:294)
    AC.ActionList:Interact(Int32, Boolean) (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:195)
    AC.ActionList:Interact() (at Assets/Plugins/AdventureCreator/Scripts/ActionList/ActionList.cs:158)
    AC.d__34:MoveNext() (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:1442)
    UnityEngine.MonoBehaviour:StartCoroutine(String, Object) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Scripting/MonoBehaviour.bindings.cs:79)
    AC.PlayerInteraction:ClickButton(InteractionType, Int32, Int32, Hotspot) (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:1026)
    AC.PlayerInteraction:HandleInteraction() (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:718)
    AC.PlayerInteraction:ContextSensitiveClick() (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:605)
    AC.PlayerInteraction:HandleInteractionMenu() (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:193)
    AC.PlayerInteraction:UpdateInteraction() (at Assets/Plugins/AdventureCreator/Scripts/Controls/PlayerInteraction.cs:120)
    AC.StateHandler:Update() (at Assets/Plugins/AdventureCreator/Scripts/Game engine/StateHandler.cs:205)

    I am still on Unity 2019.3.0f6 and AC 1.71.5

  • My mistake - try this:

    using UnityEngine;
    using System.Collections.Generic;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    namespace AC
    {
    
        [System.Serializable]
        public class ActionCopyMaterial : Action
        {
    
            public GameObject rendererFrom;
            public int rendererFromConstantID;
            public int rendererFromParameterID = -1;
            private GameObject runtimeRendererFrom;
    
            public GameObject rendererTo;
            public int rendererToConstantID;
            public int rendererToParameterID = -1;
            private GameObject runtimeRendererTo;
    
    
            public ActionCopyMaterial ()
            {
                this.isDisplayed = true;
                category = ActionCategory.Object;
                title = "Copy material";
            }
    
    
            public override void AssignValues(List<ActionParameter> parameters)
            {
                runtimeRendererFrom = AssignFile (parameters, rendererFromParameterID, rendererFromConstantID, rendererFrom);
                runtimeRendererTo = AssignFile (parameters, rendererToParameterID, rendererToConstantID, rendererTo);
            }
    
    
            public override float Run ()
            {
                if (runtimeRendererFrom && runtimeRendererTo)
                {
                    runtimeRendererTo.GetComponent <Renderer>().material = runtimeRendererFrom.GetComponent <Renderer>().material;
                }
                return 0f;
            }
    
    
            #if UNITY_EDITOR
    
            public override void ShowGUI (List<ActionParameter> parameters)
            {
                EditorGUILayout.LabelField ("From:");
    
                rendererFromParameterID = Action.ChooseParameterGUI ("Renderer:", parameters, rendererFromParameterID, ParameterType.GameObject);
                if (rendererFromParameterID >= 0)
                {
                    rendererFromConstantID = 0;
                    rendererFrom = null;
                }
                else
                {
                    rendererFrom = (GameObject) EditorGUILayout.ObjectField ("Renderer:", rendererFrom, typeof (GameObject), true);
    
                    rendererFromConstantID = FieldToID (rendererFrom, rendererFromConstantID);
                    rendererFrom = IDToField (rendererFrom, rendererFromConstantID, true, false);
                }
    
                EditorGUILayout.LabelField ("To:");
    
                rendererToParameterID = Action.ChooseParameterGUI ("Renderer:", parameters, rendererToParameterID, ParameterType.GameObject);
                if (rendererToParameterID >= 0)
                {
                    rendererToConstantID = 0;
                    rendererTo = null;
                }
                else
                {
                    rendererTo = (GameObject) EditorGUILayout.ObjectField ("Renderer:", rendererTo, typeof (GameObject), true);
    
                    rendererToConstantID = FieldToID (rendererTo, rendererToConstantID);
                    rendererTo = IDToField (rendererTo, rendererToConstantID, true, false);
                }
    
                AfterRunningOption ();
            }
    
            #endif
    
        }
    
    }
    
  • Works beautifully – thank you!

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.