Forum rules - please read before posting.

A custom action to change a character's Speech Menu Placement Child position

edited October 2020 in Technical Q&A

Hello Adventure Creator community;

In my seemingly never ending quest to make nice looking speech bubbles as my game's subtitle menus, I've been trying to write a custom action to move the speech menu child placements around -

The game has a lot of characters of varying sizes (like in the film Labyrinth, you might be talking to a big monster or a little caterpillar) that result in lots of varying degrees of camera zoom to frame them and the Player up nice. The subtitles being in the same places each time sometime results in bubbles off-screen or over the other character in the conversation.

So!

I tried to knock up a custom action so in a talking interaction I can nudge the X and Y of the speech menu placement around, like I can change those values in the editor at runtime to see the new positions.

I'm new to everything when it comes to coding (designer for decades) and the script below is ... not working ... when the change happens, the subs menu appears miles away from the scene, and the x and y values are nothing like the ones in the action.

Any hints? What am I doing wrong?

`
using UnityEngine;
using System.Collections.Generic;

if UNITY_EDITOR

using UnityEditor;

endif

namespace AC
{

[System.Serializable]
public class ActionChangeSpeechChildPos : Action
{

    // Declare variables here
    public GameObject speakingCharacter;
    public float childX;
    public float childY;
    public int constantID;

    override public void AssignValues()
    {
        speakingCharacter = AssignFile(constantID, speakingCharacter);
    }

    public ActionChangeSpeechChildPos()
    {
        this.isDisplayed = true;
        category = ActionCategory.Character;
        title = "Change Speech Position";
        description = "Change the speech child object position for this character";
    }


    public override float Run()
    {
        /* 
         * This function is called when the action is performed.
         * 
         * The float to return is the time that the game
         * should wait before moving on to the next action.
         * Return 0f to make the action instantenous.
         * 
         * For actions that take longer than one frame,
         * you can return "defaultPauseTime" to make the game
         * re-run this function a short time later. You can
         * use the isRunning boolean to check if the action is
         * being run for the first time, eg: 
         */
        {
            if (speakingCharacter)
            {
                speakingCharacter.transform.position = new Vector3(childX, childY, speakingCharacter.transform.position.z);

            }
            return 0f;
        }
    }




    public override void Skip ()
    {
        /*
         * This function is called when the Action is skipped, as a
         * result of the player invoking the "EndCutscene" input.
         * 
         * It should perform the instructions of the Action instantly -
         * regardless of whether or not the Action itself has been run
         * normally yet.  If this method is left blank, then skipping
         * the Action will have no effect.  If this method is removed,
         * or if the Run() method call is left below, then skipping the
         * Action will cause it to run itself as normal.
         */

         Run ();
    }


    #if UNITY_EDITOR

    public override void ShowGUI ()
    {
        speakingCharacter = (GameObject)EditorGUILayout.ObjectField("Speech Child Object", speakingCharacter, typeof(GameObject), true);
        childX = EditorGUILayout.FloatField("New child X position:", childX);
        childY = EditorGUILayout.FloatField("New child Y position:", childY);

        constantID = FieldToID(speakingCharacter, constantID);
        speakingCharacter = IDToField(speakingCharacter, constantID, true);

        AfterRunningOption ();
    }


    public override string SetLabel()
    {
        // Return a string used to describe the specific action's job.
        if (speakingCharacter)
        {
            return ("Change + speakingCharacter.name ");
        }
        return "";
    }

endif

}

}
`

Comments

  • The code looks all right, but it's a case of what exactly you want it to do.

    Right now, you're controlling the position in world-space, so it'll always be placed in the same position no matter where the character is standing. It may be that you instead want to affect the local position instead.

    Here's a modified script that'll do that:

    using UnityEngine;
    using System.Collections.Generic;
    
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    namespace AC
    {
    
        [System.Serializable]
        public class ActionChangeSpeechChildPos : Action
        {
    
            public GameObject speakingCharacter;
            public float childX;
            public float childY;
            public int constantID;
    
            override public void AssignValues()
            {
                speakingCharacter = AssignFile(constantID, speakingCharacter);
            }
    
            public ActionChangeSpeechChildPos()
            {
                this.isDisplayed = true;
                category = ActionCategory.Character;
                title = "Change Speech Position";
                description = "Change the speech child object position for this character";
            }
    
            public override float Run()
            {
                if (speakingCharacter)
                {
                    speakingCharacter.transform.localPosition = new Vector3(childX, childY, speakingCharacter.transform.localPosition.z);
    
                }
                return 0f;
            }
    
            #if UNITY_EDITOR
    
            public override void ShowGUI ()
            {
                speakingCharacter = (GameObject)EditorGUILayout.ObjectField("Speech Child Object", speakingCharacter, typeof(GameObject), true);
                childX = EditorGUILayout.FloatField("New child X position:", childX);
                childY = EditorGUILayout.FloatField("New child Y position:", childY);
    
                constantID = FieldToID(speakingCharacter, constantID);
                speakingCharacter = IDToField(speakingCharacter, constantID, true);
    
                AfterRunningOption ();
            }
    
            #endif
    
        }
    
    }
    
  • That's great! The local transform was what I was missing, thanks!

    There's one more thing though ... The script was put together following this tutorial https://adventurecreator.org/tutorials/writing-custom-action-2 and afaict, should work with my Player prefab, so long as their speech placement child has a ConstantID assigned to it. However, dragging that gameobject into the action, whether from the prefab or an in-scene instance of the Player, doesn't cause the ConstantID to register in the action, and the ID itself doesn't appear in the GUI.

    Can you spot anything in the ConstantID part that might be causing this, or do I need to jury rig an Is Player? tick-box from one of the other actions?

  • The Constant ID component has "Retain in prefab?" checked? Try dragging it into the Action from Prefab Mode.

    Though, you can always add an "Is Player?" checkbox to the Action, to automatically assign it:

    using UnityEngine;
    using System.Collections.Generic;
    
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    namespace AC
    {
    
        [System.Serializable]
        public class ActionChangeSpeechChildPos : Action
        {
    
            public GameObject speakingCharacter;
            public float childX;
            public float childY;
            public int constantID;
            public bool isPlayer;
    
            override public void AssignValues()
            {
                if (isPlayer)
                {
                    speakingCharacter = KickStarter.player.speechMenuPlacement.gameObject;
                }
                else
                {
                    speakingCharacter = AssignFile(constantID, speakingCharacter);
                }
            }
    
            public ActionChangeSpeechChildPos()
            {
                this.isDisplayed = true;
                category = ActionCategory.Character;
                title = "Change Speech Position";
                description = "Change the speech child object position for this character";
            }
    
            public override float Run()
            {
                if (speakingCharacter)
                {
                    speakingCharacter.transform.localPosition = new Vector3(childX, childY, speakingCharacter.transform.localPosition.z);
                }
    
                return 0f;
            }
    
            #if UNITY_EDITOR
    
            public override void ShowGUI ()
            {
                isPlayer = EditorGUILayout.Toggle ("Is Player?", isPlayer);
                if (!isPlayer)
                {
                    speakingCharacter = (GameObject)EditorGUILayout.ObjectField("Speech Child Object", speakingCharacter, typeof(GameObject), true);
                    constantID = FieldToID(speakingCharacter, constantID);
                    speakingCharacter = IDToField(speakingCharacter, constantID, true);
                }
    
                childX = EditorGUILayout.FloatField("New child X position:", childX);
                childY = EditorGUILayout.FloatField("New child Y position:", childY);
    
                AfterRunningOption ();
            }
    
            #endif
    
        }
    
    }
    
  • Thanks Chris!

    For anyone playing along at home, dragging the speech child (with ConstantID componet) onto the action from prefab mode doesn't work for me - but, the isPlayer tickbox in the code above works a treat!

    It's gonna look so good, we're very grateful!

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.