Skip to content

Instantly share code, notes, and snippets.

@DraconInteractive
Created August 21, 2022 00:35
Show Gist options
  • Save DraconInteractive/892ab734bf5a9a19632a4eb7630f26fc to your computer and use it in GitHub Desktop.
Save DraconInteractive/892ab734bf5a9a19632a4eb7630f26fc to your computer and use it in GitHub Desktop.
Basic logic flow for VR Bow mechanic
using System;
using System.Collections;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Devices;
using UltimateXR.Manipulation;
using UnityEngine;
using UnityEngine.Events;
public class BowModule : UxrGrabbableObjectComponent<BowModule>
{
[Header("Objects")]
public UxrGrabbableObject notch;
public LineRenderer bowString;
[Header("Settings")]
public float notchRecoverySpeed;
public float minimumForce;
[Header("Targets")]
public Transform top, bottom, rest;
[Header("Arrows")]
public GameObject[] arrowPrefabs;
public int arrowIndex;
public Arrow spawnedArrow;
[Header("Aim Assist")]
[Range(0, 1)]
public float velocityAssist = 0.1f;
public Vector2 aimAssist;
private UxrGrabber grabber;
private Coroutine grabR;
#region Helper Properties
Vector3 ArrowDir => (rest.position - notch.transform.position).normalized;
float LaunchForce => Mathf.Clamp01(Vector3.Distance(notch.transform.position, Vector3.Lerp(top.position, bottom.position, 0.5f)));
BowTarget BestTarget()
{
Vector3 notchPosition = notch.transform.position;
Vector3 arrowDirection = ArrowDir;
float bestDot = -1;
BowTarget bestTarget = null;
foreach (var t in BowTarget.All)
{
float dot = t.Dot(notchPosition, arrowDirection);
if (dot > bestDot && dot > 0.2f)
{
bestDot = dot;
bestTarget = t;
}
}
return bestTarget;
}
#endregion
protected override void Start()
{
base.Start();
// Subscribe to notch interaction events
notch.Grabbed += (sender, args) => NotchGrab();
notch.Released += (sender, args) => NotchRelease();
}
private void LateUpdate()
{
// If not holding notch, move it back towards rest point (located halfway between top and bottom points)
if (!notch.IsBeingGrabbed)
{
Vector3 targetPos = Vector3.Lerp(top.position, bottom.position, 0.5f);
notch.transform.position = Vector3.MoveTowards(notch.transform.position, targetPos, notchRecoverySpeed * Time.deltaTime);
}
// Notch looks at rest so that when/if an arrow spawns, it also looks at rest to begin
// This also influences the axis of constrained pull-back
// May not function with Uxr (I dont want to break things!)
notch.transform.LookAt(rest);
// Calculate and set string line renderer positions
// Simple 3 point string feels fine, havent found the need for more points
Vector3[] pos = new Vector3[3]
{
top.position,
notch.transform.position,
bottom.position
};
bowString.SetPositions(pos);
}
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
// Cache the grabbing object for later use
grabber = e.Grabber;
}
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
base.OnObjectReleased(e);
// Release the cached object
grabber = null;
}
private void UxrManager_AvatarsUpdated()
{
if (!IsBeingGrabbed) return;
// if being grabbed, check if the user clicks the 1st button on the controller. If so, move to the next arrow prefab
bool buttonPressDown = UxrAvatar.LocalAvatarInput.GetButtonsPressDown(grabber.Side, UxrInputButtons.Button1);
if (buttonPressDown)
{
arrowIndex++;
if (arrowIndex >= arrowPrefabs.Length)
{
arrowIndex = 0;
}
}
}
public void NotchGrab()
{
if (IsBeingGrabbed)
{
// Create arrow, reset its local rotation (incase of prefab shenanigans)
// Start the handler routine
spawnedArrow = Instantiate(arrowPrefabs[arrowIndex], notch.transform.position, notch.transform.rotation, notch.transform).GetComponent<Arrow>();
spawnedArrow.transform.localRotation = Quaternion.identity;
grabR = StartCoroutine(GrabRoutine());
}
}
public void NotchRelease()
{
// If arrow exists, launch it, stop the handler routine
if (spawnedArrow != null)
{
Launch();
StopCoroutine(grabR);
}
}
// Handler routine. Previously used for aim visualisation, still working on that
IEnumerator GrabRoutine()
{
while (true)
{
/*
if (spawnedArrow != null)
{
var bestTarget = BestTarget();
if (bestTarget != null)
{
Vector3 targetPoint = bestTarget.transform.position;
Quaternion targetRot = Quaternion.Lerp(spawnedArrow.transform.rotation, Quaternion.LookRotation((targetPoint - spawnedArrow.transform.position).normalized, Vector3.up), aimAssist.y);
Vector3 aimPos = spawnedArrow.tip.position;
aimObject.transform.rotation = targetRot;
aimPos = aimObject.transform.forward * Vector3.Distance(spawnedArrow.tip.position, targetPoint);
aimObject.transform.position = Vector3.Lerp(aimPos, targetPoint, aimAssist.y);
}
}
*/
yield return null;
}
}
// Launch arrow (called on notch release)
void Launch()
{
// Calculate force from base * distance from notch rest point
float force = LaunchForce;
// If that distance is too small, dont launch
if (force < minimumForce)
{
Destroy(spawnedArrow.gameObject);
return;
}
// If its a 'proper' release, find the best target, check how much aim assist to use,
// then send a launch instruction to the arrow with this info
var bestTarget = BestTarget();
float triggerPressAmount = UxrAvatar.LocalAvatarInput.GetInput1D(grabber.Side, UxrInput1D.Trigger);
float assist = (triggerPressAmount - aimAssist.x) / (aimAssist.y - aimAssist.x);
spawnedArrow.GetComponent<Arrow>().Launch(force, bestTarget, assist);
spawnedArrow = null;
// TODO: Add 'active' arrow list for arrows that have been launched and are still alive.
// Could be some fun control mechanics there.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment