I couldn't get rotations to work. The sliders wouldn't drag in the UI. I never figured out why. I asked Gemini, who wrote this code in response, and it works. I can rotate now. I'm not a programmer, so I don't know what changed, but here's the code if it's useful.
// MoveControl.cs
using System;
using UnityEngine;
using System.Collections.Generic;
using SimpleJSON;
using UnityEngine.UI;
// This is a refactored version of the MoveControl script.
namespace MVRPlugin
{
public class MoveControl : MVRScript
{
// --- Cached References ---
private Transform _controlTransform;
private FreeControllerV3 _freeControllerV3;
// --- UI Storables ---
private JSONStorableFloat _xPositionSlider, _yPositionSlider, _zPositionSlider;
private JSONStorableFloat _xRotationSlider, _yRotationSlider, _zRotationSlider;
private JSONStorableFloat _xPositionIncrement, _yPositionIncrement, _zPositionIncrement;
private JSONStorableFloat _xRotationIncrement, _yRotationIncrement, _zRotationIncrement;
private JSONStorableFloat _xPosMin, _xPosMax, _yPosMin, _yPosMax, _zPosMin, _zPosMax;
private JSONStorableFloat _xLoopMin, _xLoopMax, _yLoopMin, _yLoopMax, _zLoopMin, _zLoopMax;
public override void Init()
{
try
{
var controlStorable = containingAtom.GetStorableByID("control");
if (controlStorable != null)
{
_controlTransform = controlStorable.transform;
_freeControllerV3 = controlStorable as FreeControllerV3;
}
else
{
SuperController.LogError("Could not find a storable named 'control'. This plugin will not function.");
return;
}
CreatePositionControlUI();
CreateRotationControlUI();
CreateHardLimitUI();
CreateLoopingBoundsUI();
}
catch (Exception e)
{
SuperController.LogError("Exception during Init: " + e);
}
}
void Update()
{
if (_controlTransform == null) return;
try
{
bool isSynced = CheckIfSlidersMatchTransform();
ApplyIncrementalMovement();
EnforceHardLimits();
ApplyLoopingBounds();
if (isSynced)
{
UpdateSlidersFromTransform();
}
}
catch (Exception e)
{
SuperController.LogError("Exception during Update: " + e);
}
}
#region Initialization and UI Creation
private void CreatePositionControlUI()
{
_xPositionSlider = new JSONStorableFloat("X Position", _controlTransform.position.x, SetPositionX, -5f, 5f, false);
_yPositionSlider = new JSONStorableFloat("Y Position", _controlTransform.position.y, SetPositionY, -5f, 5f, false);
_zPositionSlider = new JSONStorableFloat("Z Position", _controlTransform.position.z, SetPositionZ, -5f, 5f, false);
CreateSliderWithColor(_xPositionSlider, false, Color.black, Color.cyan);
CreateSliderWithColor(_yPositionSlider, false, Color.black, Color.cyan);
CreateSliderWithColor(_zPositionSlider, false, Color.black, Color.cyan);
_xPositionIncrement = new JSONStorableFloat("X Position Increment", 0f, -1f, 1f, false);
_yPositionIncrement = new JSONStorableFloat("Y Position Increment", 0f, -1f, 1f, false);
_zPositionIncrement = new JSONStorableFloat("Z Position Increment", 0f, -1f, 1f, false);
CreateSliderWithColor(_xPositionIncrement, true, Color.black, Color.green);
CreateSliderWithColor(_yPositionIncrement, true, Color.black, Color.green);
CreateSliderWithColor(_zPositionIncrement, true, Color.black, Color.green);
}
private void CreateRotationControlUI()
{
_xRotationSlider = new JSONStorableFloat("X Rotation", _controlTransform.eulerAngles.x, SetRotationX, 0f, 360f, true);
_yRotationSlider = new JSONStorableFloat("Y Rotation", _controlTransform.eulerAngles.y, SetRotationY, 0f, 360f, true);
_zRotationSlider = new JSONStorableFloat("Z Rotation", _controlTransform.eulerAngles.z, SetRotationZ, 0f, 360f, true);
CreateSliderWithColor(_xRotationSlider, false, Color.black, Color.cyan);
CreateSliderWithColor(_yRotationSlider, false, Color.black, Color.cyan);
CreateSliderWithColor(_zRotationSlider, false, Color.black, Color.cyan);
_xRotationIncrement = new JSONStorableFloat("X Rotation Increment", 0f, -10f, 10f, false);
_yRotationIncrement = new JSONStorableFloat("Y Rotation Increment", 0f, -10f, 10f, false);
_zRotationIncrement = new JSONStorableFloat("Z Rotation Increment", 0f, -10f, 10f, false);
CreateSliderWithColor(_xRotationIncrement, true, Color.black, Color.yellow);
CreateSliderWithColor(_yRotationIncrement, true, Color.black, Color.yellow);
CreateSliderWithColor(_zRotationIncrement, true, Color.black, Color.yellow);
}
private void CreateHardLimitUI()
{
_xPosMin = new JSONStorableFloat("X Min Limit", -100f, -100f, 100f, false);
_xPosMax = new JSONStorableFloat("X Max Limit", 100f, -100f, 100f, false);
CreateSliderWithColor(_xPosMin, false, Color.white, Color.grey);
CreateSliderWithColor(_xPosMax, true, Color.white, Color.grey);
// Completed Y and Z limits
_yPosMin = new JSONStorableFloat("Y Min Limit", -100f, -100f, 100f, false);
_yPosMax = new JSONStorableFloat("Y Max Limit", 100f, -100f, 100f, false);
CreateSliderWithColor(_yPosMin, false, Color.white, Color.grey);
CreateSliderWithColor(_yPosMax, true, Color.white, Color.grey);
_zPosMin = new JSONStorableFloat("Z Min Limit", -100f, -100f, 100f, false);
_zPosMax = new JSONStorableFloat("Z Max Limit", 100f, -100f, 100f, false);
CreateSliderWithColor(_zPosMin, false, Color.white, Color.grey);
CreateSliderWithColor(_zPosMax, true, Color.white, Color.grey);
}
private void CreateLoopingBoundsUI()
{
_xLoopMin = new JSONStorableFloat("X Minimum (loop)", -100f, -100f, 100f, false);
_xLoopMax = new JSONStorableFloat("X Maximum (loop)", 100f, -100f, 100f, false);
CreateSliderWithColor(_xLoopMin, false);
CreateSliderWithColor(_xLoopMax, true);
// Completed Y and Z looping bounds
_yLoopMin = new JSONStorableFloat("Y Minimum (loop)", -100f, -100f, 100f, false);
_yLoopMax = new JSONStorableFloat("Y Maximum (loop)", 100f, -100f, 100f, false);
CreateSliderWithColor(_yLoopMin, false);
CreateSliderWithColor(_yLoopMax, true);
_zLoopMin = new JSONStorableFloat("Z Minimum (loop)", -100f, -100f, 100f, false);
_zLoopMax = new JSONStorableFloat("Z Maximum (loop)", 100f, -100f, 100f, false);
CreateSliderWithColor(_zLoopMin, false);
CreateSliderWithColor(_zLoopMax, true);
}
#endregion
#region Core Movement Logic (Called from Update)
private bool CheckIfSlidersMatchTransform()
{
const float posTolerance = 0.01f;
const float rotTolerance = 0.1f;
if (Mathf.Abs(_xPositionSlider.val - _controlTransform.position.x) > posTolerance) return false;
if (Mathf.Abs(_yPositionSlider.val - _controlTransform.position.y) > posTolerance) return false;
if (Mathf.Abs(_zPositionSlider.val - _controlTransform.position.z) > posTolerance) return false;
if (Mathf.Abs(Mathf.DeltaAngle(_xRotationSlider.val, _controlTransform.eulerAngles.x)) > rotTolerance) return false;
if (Mathf.Abs(Mathf.DeltaAngle(_yRotationSlider.val, _controlTransform.eulerAngles.y)) > rotTolerance) return false;
if (Mathf.Abs(Mathf.DeltaAngle(_zRotationSlider.val, _controlTransform.eulerAngles.z)) > rotTolerance) return false;
return true;
}
private void ApplyIncrementalMovement()
{
float delta = Time.deltaTime;
_controlTransform.position += new Vector3(_xPositionIncrement.val * delta, _yPositionIncrement.val * delta, _zPositionIncrement.val * delta);
_controlTransform.Rotate(new Vector3(_xRotationIncrement.val * delta, _yRotationIncrement.val * delta, _zRotationIncrement.val * delta));
}
private void EnforceHardLimits()
{
Vector3 pos = _controlTransform.position;
pos.x = Mathf.Clamp(pos.x, _xPosMin.val, _xPosMax.val);
// Completed Y and Z logic
pos.y = Mathf.Clamp(pos.y, _yPosMin.val, _yPosMax.val);
pos.z = Mathf.Clamp(pos.z, _zPosMin.val, _zPosMax.val);
_controlTransform.position = pos;
}
private void ApplyLoopingBounds()
{
Vector3 pos = _controlTransform.position;
if (_xLoopMax.val > _xLoopMin.val)
{
if (pos.x < _xLoopMin.val) pos.x = _xLoopMax.val;
else if (pos.x > _xLoopMax.val) pos.x = _xLoopMin.val;
}
// Completed Y and Z logic
if (_yLoopMax.val > _yLoopMin.val)
{
if (pos.y < _yLoopMin.val) pos.y = _yLoopMax.val;
else if (pos.y > _yLoopMax.val) pos.y = _yLoopMin.val;
}
if (_zLoopMax.val > _zLoopMin.val)
{
if (pos.z < _zLoopMin.val) pos.z = _zLoopMax.val;
else if (pos.z > _zLoopMax.val) pos.z = _zLoopMin.val;
}
_controlTransform.position = pos;
}
private void UpdateSlidersFromTransform()
{
_xPositionSlider.val = _controlTransform.position.x;
_yPositionSlider.val = _controlTransform.position.y;
_zPositionSlider.val = _controlTransform.position.z;
_xRotationSlider.val = _controlTransform.eulerAngles.x;
_yRotationSlider.val = _controlTransform.eulerAngles.y;
_zRotationSlider.val = _controlTransform.eulerAngles.z;
}
#endregion
#region UI Callbacks
private void SetPositionX(float val) { if (_controlTransform != null) _controlTransform.position = new Vector3(val, _controlTransform.position.y, _controlTransform.position.z); }
private void SetPositionY(float val) { if (_controlTransform != null) _controlTransform.position = new Vector3(_controlTransform.position.x, val, _controlTransform.position.z); }
private void SetPositionZ(float val) { if (_controlTransform != null) _controlTransform.position = new Vector3(_controlTransform.position.x, _controlTransform.position.y, val); }
private void SetRotationX(float val) { if (_controlTransform != null) _controlTransform.eulerAngles = new Vector3(val, _controlTransform.eulerAngles.y, _controlTransform.eulerAngles.z); }
private void SetRotationY(float val) { if (_controlTransform != null) _controlTransform.eulerAngles = new Vector3(_controlTransform.eulerAngles.x, val, _controlTransform.eulerAngles.z); }
private void SetRotationZ(float val) { if (_controlTransform != null) _controlTransform.eulerAngles = new Vector3(_controlTransform.eulerAngles.x, _controlTransform.eulerAngles.y, val); }
#endregion
#region Helper Functions
private void CreateSliderWithColor(JSONStorableFloat storable, bool rightSide = false, Color? textColor = null, Color? bgColor = null)
{
RegisterFloat(storable);
UIDynamicSlider slider = CreateSlider(storable, rightSide);
if (textColor.HasValue && bgColor.HasValue)
{
var sliderBGImage = slider.GetComponentInChildren<Image>();
if (sliderBGImage != null) sliderBGImage.color = bgColor.Value;
if (slider.labelText != null) slider.labelText.color = textColor.Value;
}
}
#endregion
}
}