• Hello Guest!

    We have recently updated our Site Policies regarding the use of Non Commercial content within Paid Content posts. Please read the new policy here.

    An offical announcement about this new policy can be read on our Discord.

    ~The VaMHub Moderation Team
  • Hello Guest!

    We posted an announcment regarding upcoming changes to Paid Content submissions.

    Please see this thread for more information.

CUA Editor tutorial

Guides CUA Editor tutorial 1

A few person on discord and on the hub asked if it was possible to control a custom asset through script... so after fixing a bug I had with mine with the help of meshed, I promised I made a tutorial to share the knowledge. So here we go.


What do you need?

  • C# knowledge
  • A bit of Unity knowledge
  • A bit of "VAM code" knowledge (included in VAM as a VS solution)


What is the goal of the tutorial?

It is meant to show you how to create a script / plugin for VAM which will be able to control a standard Unity prefab in a CUA (Custom Unity Asset) atom.

This is not a tutorial about Unity and how to create prefabs. If you don't know how to create prefabs and bundle them, you better start with that. MacGruber did an excellent guide about that.

I'm not gonna gonna go step by with the code since this is pretty basic C#. I made an heavily commented example that you'll just have to read.

You can download the var example which contains the script and the demo CUA.


1 - Create your asset in Unity and package it

First, you need to create your asset. In this case, we're gonna create an empty object containing two shapes, a sphere and a cube. In Unity, the hierarchy of my object looks like this :

cua-editor-01.gif


vam_script_tutorial is just a transform ( just because I like things ordered and neat )
sphere and cube are basic gameobjects with a mesh filter, mesh renderer and a collider.

Save your object as a prefab, pack it and put it in your VAM directory.

WARNING : I had a few persons asking me why the script wasn't working... you are coding, case is really important. Pay attention to how you're naming your structure and what name you're trying to find in the structure. Sphere is different than sphere.


2 - Write your script

Create an empty scene, add a CUA and load your freshly packed prefab from Unity. Then start to write your script.

C#:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using SimpleJSON;
using System.Linq;
using System.IO;

// CUAEditor example
//
// Base editor for a CUA (Custom Unity Asset)
namespace CUAEditorPlugin
{
    public class CUAEditor : MVRScript
    {
        // My JSONStorable for the editor
        public JSONStorableColor SphereColor;
        public JSONStorableBool SphereVisibility;
        public JSONStorableColor CubeColor;
        public JSONStorableBool CubeVisibility;
    
        // My misc variables to find and edit the prefab made in Unity
        private string objectNameSearchString = "vam_script_tutorial";
        public Transform ObjectRoot;
        private Transform sphereTR;
        private Transform cubeTR;
        private Renderer sphereRenderer;
        private Renderer cubeRenderer;

        // Security and initialization
        private bool editorInitialized = false;
        private int initLoops = 0;
        private int maxInitLoops = 3600;

        // The usual init function for VAM
        public override void Init()
        {         
            try
            {
                // We only authorize the script to be used by a CUA
                if (containingAtom.type != "CustomUnityAsset")
                {
                    logError("CUAEditor - Please add this script on a Custom Unity Asset ( script disabled ).");
                    return;
                }
            
                // Default color for both shapes
                HSVColor hsvc = HSVColorPicker.RGBToHSV(1f, 1f, 1f);
            
                // Color selection for the sphere
                SphereColor = new JSONStorableColor("Sphere color", hsvc, SphereColorCallback);
                RegisterColor(SphereColor);
                CreateColorPicker(SphereColor);
            
                // Visibility state for the sphere
                SphereVisibility = new JSONStorableBool("Sphere is visible", true, SphereVisibilityCallback);
                SphereVisibility.storeType = JSONStorableParam.StoreType.Full;
                CreateToggle(SphereVisibility, false);
                RegisterBool(SphereVisibility);
            
                // Color selection for the cube
                CubeColor = new JSONStorableColor("Cube color", hsvc, CubeColorCallback);
                RegisterColor(CubeColor);
                CreateColorPicker(CubeColor);
            
                // Visibility state for the cube
                CubeVisibility = new JSONStorableBool("Cube is visible", true, CubeVisibilityCallback);
                CubeVisibility.storeType = JSONStorableParam.StoreType.Full;
                CreateToggle(CubeVisibility, false);
                RegisterBool(CubeVisibility);

                // A tiny bit of help to give some info to your fellow creator and/or player
                JSONStorableString helpText = new JSONStorableString("Help",
                    "<color=#000><size=35><b>Help</b></size></color>\n\n" +
                    "<color=#333>" +
                    "<b>CUAEditor</b> is a tiny plugin to show you how you can create an editor for a CUA ( Custom Unity Asset ).\n\n" +
                    "Change the parameters on the left and you will see the CUA updating in realtime.\n\n" +
                    "<b>IMPORTANT :</b> If you switch objects in the Asset tab of the CUA, don't forget to reload the script to enable customization.\n\n" +
                    "</color>"
                );
                UIDynamic helpTextfield = CreateTextField(helpText, true);
                helpTextfield.height = 1100.0f;
            }
            catch(Exception e)
            {
                SuperController.LogError("CUAEditor - Exception caught: " + e);
            }
        }
    
        // In this case we don't need that, but maybe you do ?
        void Start()
        {

        }
    
        // Used for the initialization of the editor.
        // When it is initialized, there's nothing done every frame
        void Update()
        {
            // If the editor is not initialized, we're gonna try to do it
            // I'm putting 3600 tries ( at 60fps it is 1min )
            if( initLoops < maxInitLoops && editorInitialized == false ) {
                initLoops++;
                InitEditor();
            }
        
            // Triggering an error if we have reached our limit of tries
            if( initLoops == maxInitLoops ) {
                logError("CUAEditor : This CUA could not be initialized properly after 3600 tries. Please select a compatible object and reload the script.");
                initLoops++; // To avoid spamming the log with the error
            }
        }
    
        // The function initializing everything we need to update the prefab with the editor
        // You can imagine whatever you want here, the only limitations are the things available in VAM's build
        // In this example case, we need the main transform, a transform for the sphere and the cube
        //
        // Initially this function is made to return a bool state to see if it is properly initialized.
        // But since the CUA loader is asynchronous when you reload a scene and I don't have a way to identify when unity's prefab is loaded
        // I have to "brute force" the initialization several times until the script finds the transform I need
        // It implies that your prefab is done properly and has the good name ( this is why the maxInitLoops exists )
        private bool InitEditor() {
            // When the editor is initialized, we don't need to do that anymore
            if( editorInitialized == true ) return true;
        
            // We need to find our main transform, getObjectRoot is gonna do that for us
            // the search is based on our variable objectNameSearchString
            // you need to create a prefab containing this name in Unity
            // This method works for unparented and parented objects
            // ( Thanks for the reParentObject tip meshed ! )
            ObjectRoot = getObjectRoot( containingAtom.reParentObject );
            if( ObjectRoot == null ) {
                return false;
            }
        
            // Finding my sphere and cube transforms
            sphereTR = ObjectRoot.Find("sphere");
            cubeTR = ObjectRoot.Find("cube");
                
            // I found my transforms, now the rest
            if( sphereTR && cubeTR ) {
                // Finding my sphere and cube renderers
                sphereRenderer = sphereTR.GetComponent<Renderer>();
                cubeRenderer = cubeTR.GetComponent<Renderer>();
            
                // I found my renderers components, let's finish this
                if( sphereRenderer && cubeRenderer ) {
                    // Everything is good, I can consider my editor initialized
                    editorInitialized = true;
                
                    // I now need initialize my different objects based on the informations in my JSONStorables
                    // You could go crazy and make unified functions to control the whole object instead
                    SphereColorCallback( SphereColor );
                    SphereVisibilityCallback( SphereVisibility.val );
                    CubeColorCallback( CubeColor );
                    CubeVisibilityCallback( CubeVisibility.val );
                            
                    logDebug("CUAEditor : editor initialized properly");
                    return true;
                } else {
                    logError("CUAEditor : your CUA has a transform with the good name but does not have a renderer.");
                    initLoops = maxInitLoops; // We don't have what we need, we consider the initialization failed
                    return false;
                }
            } else {
                logError("CUAEditor : your CUA is missing mandatory components ( transform name cube and sphere ).");
                initLoops = maxInitLoops; // We don't have what we need, we consider the initialization failed
                return false;
            }
        
            return false;
        }
    
        // Recursive function searching for a transform containing the string set in objectNameSearchString
        // It is used the first time for the initialization of the script
        private Transform getObjectRoot( Transform parent ) {
            foreach( Transform child in parent )
            {
                if( child.name.Contains(objectNameSearchString) ) {
                    return child;
                } else {
                    Transform childSearch = getObjectRoot(child);
                    if (childSearch != null) return childSearch;
                }
            }
        
            return null;
        }
    
        // Callback triggered when you change the color for the sphere (or you can call it manually)
        protected void SphereColorCallback(JSONStorableColor color) {
            if( sphereRenderer ) {
                Color newColor = new Color(color.colorPicker.currentColor.r, color.colorPicker.currentColor.g, color.colorPicker.currentColor.b, 1.0f);
                sphereRenderer.material.SetColor("_Color",newColor);
            }
        }
    
        // Callback triggered when you change the visibility for the sphere (or you can call it manually)
        protected void SphereVisibilityCallback(bool visibility) {
            if( sphereTR ) {
                sphereTR.gameObject.SetActive(visibility);
            }
        }
    
        // Callback triggered when you change the color for the cube (or you can call it manually)
        protected void CubeColorCallback(JSONStorableColor color) {
            if( cubeRenderer ) {
                Color newColor = new Color(color.colorPicker.currentColor.r, color.colorPicker.currentColor.g, color.colorPicker.currentColor.b, 1.0f);
                cubeRenderer.material.SetColor("_Color",newColor);
            }
        }
    
        // Callback triggered when you change the visibility for the sphere (or you can call it manually)
        protected void CubeVisibilityCallback(bool visibility) {
            if( cubeTR ) {
                cubeTR.gameObject.SetActive(visibility);
            }
        }
    
        // **************************
        // Local Tools... just because I'm lazy
        // **************************
        private void logDebug( string debugText ) {
            SuperController.LogMessage( debugText );
        }
    
        private void logError( string debugText ) {
            SuperController.LogError( debugText );
        }
    }
}


3 - Make your own thing, be creative

This script shows one thing : when you have declared VAM's UI / JSONStorables, we are in a well know territory... Unity.
You then have virtually almost no limit to what you can do.

In this example, I've changed a parameter of the material of the objects and their Active state. But you could play with transform positions, sizes. You could attach components (as long as they are components embedded in VAM's build), you can interact with all the type of object Unity offers : 3D objects, 2D objects, cameras, sounds, video player...


4 - Conclusion

That's all I guess... if some things are not clear don't hesitate to ask for clarification.

I'll just add something about performances : I've seen a long time ago someone who did implement something similar for an asset. I don't remember what it was but the script was making modifications on the asset in the Update function.

Be really careful with that since update is called every frame. Unless your prefab is meant to do something every frame... modifying basic properties of an object that should not change can be done with callbacks.
Author
hazmhox
Downloads
2,745
Views
10,028
First release
Last update
Rating
5.00 star(s) 1 ratings

More resources from hazmhox

Latest reviews

Very easy to follow and works as described :)
Upvote 0
Back
Top Bottom