Script that adds a CollisionTrigger that calls the script on collide

mrmr32

Well-known member
Messages
123
Reactions
320
Points
63
Website
github.com
Patreon
mrmr32
I'm making an script that performs a morph animation when an Atom (called _collider) enters a CollisionTrigger (_collisionBox), but the lack of documentation it's really making it hard.
Right now I have all the morphs that should change and the difference for each animation (stored in _morphIncrement), the animation duration (_durationStorable) and a CollisionTrigger (added manually for the moment) that follows the containingAtom's head.
I don't think making the animation itself will be difficult, but I don't know how to:
  1. Create the CollisionTrigger by code
  2. Set the added CollisionTrigger to only react with the _collider atom
  3. Call a script function (OnCollision) when it triggers
Here's the code:
C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel; // TypeDescriptor
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using SimpleJSON; // JSONNode

namespace JustAnotherUser {
    public class TriggerIncrementer : MVRScript {
        private List<DAZMorph> _morphs;
        private Atom _collider; // the object that will colide with the collider region
        private MotionAnimationControl _head;
        private DAZCharacterSelector _characterSelector;

        private JSONStorableStringChooser _colliderStorable;
        private JSONStorableFloat _durationStorable;

        private IDictionary<DAZMorph, JSONStorableFloat> _morphIncrement; // all the morphs that it should modify and its increment

        private long _finalTime; // play the animation until finalTime
        private Atom _collisionBox; // area where to detect collider

        private static readonly string COLLISION_BOX_NAME = "TI_collision"; // TODO add {name} at the end

        public override void Init() {
            // plugin VaM GUI description
            pluginLabelJSON.val = "TriggerIncrementer v0.1";

            // get the objects
            if (containingAtom.type != "Person" || (this._characterSelector = containingAtom.GetComponentInChildren<DAZCharacterSelector>()) == null) {
                throw new InvalidOperationException("Missing DAZCharacterSelector");
            }

            if ((this._head = GetHead()) == null) throw new InvalidOperationException("Head not found in added object");

            this._morphs = new List<DAZMorph>();
            this._morphIncrement = new Dictionary<DAZMorph, JSONStorableFloat>();
        }


        // Runs once when plugin loads (after Init)
        protected void Start() {
            this._morphs.Clear();
            ScanBank(this._characterSelector.morphBank1, this._morphs); // @author https://github.com/ProjectCanyon/morph-merger/blob/master/MorphMerger.cs

            this._colliderStorable = new JSONStorableStringChooser("collider", SuperController.singleton.GetAtoms().Select(a => a.name).Distinct().ToList(), "", "Collider", (string colliderName) => {
                this._collider = SuperController.singleton.GetAtoms().FirstOrDefault(a => a.name == colliderName);
                SuperController.LogMessage("New collider");
            });
            RegisterStringChooser(this._colliderStorable);
            var linkPopup = CreateFilterablePopup(this._colliderStorable);
            linkPopup.popupPanelHeight = 600f;

            JSONStorableStringChooser morphChooseList = new JSONStorableStringChooser("morph", this._morphs.Select(m => m.morphName).Distinct().ToList(), "", "Add morph", (string morphName) => {
                if (this._morphIncrement.Keys.Select(e => e.morphName).Contains(morphName)) {
                    SuperController.LogMessage(morphName + " already added");
                    return;
                }

                SuperController.LogMessage("Added " + morphName);
                AddMorph(morphName);
            });
            //RegisterStringChooser(morphChooseList); // TODO no need to register it (?) [we don't care about this information]
            linkPopup = CreateFilterablePopup(morphChooseList, true); // create it on the right
            linkPopup.popupPanelHeight = 600f;

            this._durationStorable = new JSONStorableFloat("duration", 10.0f, 0.1f, 120.0f);
            RegisterFloat(this._durationStorable);
            CreateSlider(this._durationStorable);

            this._collisionBox = GetCollisionBox();
            // TODO change onCreate
            this._collisionBox.GetStorableByID("scale").GetFloatJSONParam("scale").val = 0.4f;
            this._collisionBox.GetStorableByID("trigger").GetFloatJSONParam("trigger").val = 0.4f;

            //SuperController.LogMessage(pluginLabelJSON.val + " Loaded");

            LoadJson(GetPluginJsonFromSave());
        }

        public void OnCollision() {
            // TODO change _finalTime variable
        }

        public void FixedUpdate() { }

        public void Update() {
            if (this._collisionBox == null) return; // temporal, while the code doesn't add the trigger
            this._collisionBox.mainController.transform.position = this._head.transform.position + new Vector3(0f, -0.05f, 0f);
            // TODO rotation

            // TODO animate with GetTimestamp
        }

        public void OnDestroy() {
            // TODO remove collision box
        }

        public void OnUnLoad() {
            // TODO remove collision box
        }

        public float GetDuration() {
            return this._durationStorable.val;
        }

        private Atom GetCollisionBox() {
            // does it already exists?
            Atom r = SuperController.singleton.GetAtoms().FirstOrDefault(a => a.name == COLLISION_BOX_NAME);
            if (r != null) return r;

            // no collision box; generate a new one
            // TODO
            return r;
        }

        private void AddMorph(string morphName, float value = 0.0f) {
            DAZMorph morph = FindMorphByName(this._morphs, morphName);
            if (morph == null) return;

            JSONStorableFloat jsonFloat = new JSONStorableFloat(morphName, value, -1.0f, 1.0f);
            RegisterFloat(jsonFloat);
            CreateSlider(jsonFloat);

            // get jsonFloat.val
            // change morphValue

            this._morphIncrement.Add(morph, jsonFloat);
        }

        private void LoadJson(JSONNode node) {
            if (node == null) return;

            foreach (string entry in node.AsObject.Keys) {
                switch (entry) {
                    case "collider":
                        this._colliderStorable.val = node[entry].ToString();
                        break;

                    case "duration":
                        this._durationStorable.val = node[entry].AsFloat;
                        break;

                    case "id":
                    case "pluginLabel":
                        break; // ignore

                    default:
                        // tracked morphs
                        try {
                            AddMorph(entry, node[entry].AsFloat);
                        } catch (Exception ex) { }
                        break;
                }
            }
        }

        // @author https://raw.githubusercontent.com/ChrisTopherTa54321/VamScripts/master/FloatMultiParamRandomizer.cs
        public JSONNode GetPluginJsonFromSave() {
            foreach (JSONNode atoms in SuperController.singleton.loadJson["atoms"].AsArray) {
                if (!atoms["id"].Value.Equals(containingAtom.name)) continue;

                foreach (JSONNode storable in atoms["storables"].AsArray) {
                    if (storable["id"].Value == this.storeId) {
                        return storable;
                    }
                }
            }

            return null;
        }

        private MotionAnimationControl GetHead() {
            foreach (MotionAnimationControl mac in containingAtom.motionAnimationControls) { // TODO get head inside linkableRigidbodies?
                if (!mac.name.Equals("headControl")) continue;
                return mac;
            }

            return null; // not found
        }

        private void ScanBank(DAZMorphBank bank, List<DAZMorph> morphs) { // TODO only morph (not morph & pose)
            if (bank == null) return;

            foreach (DAZMorph morph in bank.morphs) {
                if (!morph.visible) continue;

                morphs.Add(morph);
                //SuperController.LogMessage(morph.morphName);
            }
        }

        private DAZMorph FindMorphByName(List<DAZMorph> morphs, string name) {
            foreach (DAZMorph morph in morphs) {
                if (!morph.morphName.Equals(name)) continue;

                return morph;
            }

            return null; // not found
        }
    }
}
 
OK, with some try and error and using the 'Plugin Builder' tool included in the VaM release (as recommended on Hello World - plugin basics 1) I've found a way to do point 2 and 3 (still no way to create a CollisionTrigger Atom).

To Call a script function when it triggers I've created a GUI toggle which the collider has access:
C#:
this._activeStorable = new JSONStorableBool("run", false, (val) => {
  if (!val) return;

  OnCollision();
  this._activeStorable.val = false;
});
RegisterBool(this._activeStorable); // we don't care about this information, but we need to register it to set it on the trigger
CreateToggle(this._activeStorable, true); // create it on the right

Now, to trigger it (the rest of point 3) and set to react only to one atom (point 2) I've changed the JSON of the CollisionTrigger:
C#:
        public void OnColliderChange(string colliderName) {
            this._collider = SuperController.singleton.GetAtoms().FirstOrDefault(a => a.name == colliderName);

            JSONStorable trigger = this._collisionBox.GetStorableByID("Trigger");
            JSONClass triggerJSON = trigger.GetJSON();

            if (!triggerJSON.HasKey("atomFilter")) triggerJSON.Add("atomFilter", "");
            triggerJSON["atomFilter"].Value = this._collider.name;

            if (triggerJSON["trigger"]["startActions"].AsArray.Count == 0) {
                triggerJSON["trigger"]["startActions"][0].Add("receiverAtom", "");
                triggerJSON["trigger"]["startActions"][0].Add("receiver", "");
                triggerJSON["trigger"]["startActions"][0].Add("receiverTargetName", "");
                triggerJSON["trigger"]["startActions"][0].Add("boolValue", "");
            }
            triggerJSON["trigger"]["startActions"][0]["receiverAtom"].Value = containingAtom.name;
            triggerJSON["trigger"]["startActions"][0]["receiver"].Value = this.storeId; // plugin ID
            triggerJSON["trigger"]["startActions"][0]["receiverTargetName"].Value = "run";
            triggerJSON["trigger"]["startActions"][0]["boolValue"].Value = "true";
            trigger.LateRestoreFromJSON(triggerJSON);

            SuperController.LogMessage("New collider");
        }
 
Creating the atom was easy, but the method was hard to find: SuperController.singleton.AddAtomByType("CollisionTrigger", name).
Keep in mind that it returns an IEnumerator, that is, you need to call it with StartCoroutine() in order to not block the code.
 
2 hours of debugging just to find out I need to add entry.Key.SyncJSON(); after I set the morph value on the animation... VAM really REALLY needs a good API tutorial to make our lives easier...
 
Back
Top Bottom