Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6ea572aaf7320f74ea8c4a8fba20559e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RotateShoulder.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.CyborgAvatar
{
/// <summary>
/// Component that rotates the Cyborg shoulder so that the opening points in the arm direction to leave it
/// more space.
/// </summary>
public class RotateShoulder : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _rotatingShoulder;
[SerializeField] private Vector3 _rotatingShoulderAxis;
[SerializeField] private Vector3 _rotatingShoulderOpeningAxis;
[SerializeField] private Transform _arm;
[SerializeField] private Vector3 _armLocalForward;
[SerializeField] private float _rotationDampingMin = 1.0f;
[SerializeField] private float _rotationDampingMax = 0.2f;
[SerializeField] private float _armAngleToRotateMin = 30.0f;
[SerializeField] private float _armAngleToRotateMax = 60.0f;
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Performs the shoulder rotation.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
Vector3 armForward = _arm.TransformDirection(_armLocalForward);
Vector3 rotatingShoulderAxis = _rotatingShoulder.TransformDirection(_rotatingShoulderAxis);
float armAngle = Vector3.Angle(armForward, rotatingShoulderAxis);
if (armAngle > _armAngleToRotateMin)
{
float t = Mathf.Clamp01((armAngle - _armAngleToRotateMin) / (_armAngleToRotateMax - _armAngleToRotateMin));
Vector3 openingCurrent = _rotatingShoulder.TransformDirection(_rotatingShoulderOpeningAxis);
Vector3 openingTarget = Vector3.ProjectOnPlane(armForward, rotatingShoulderAxis);
float currentAngle = Vector3.SignedAngle(openingCurrent, openingTarget, rotatingShoulderAxis);
float dampedAngle = Mathf.SmoothDampAngle(currentAngle, 0.0f, ref _currentAngleVelocity, Mathf.Lerp(_rotationDampingMin, _rotationDampingMax, t));
_rotatingShoulder.Rotate(_rotatingShoulderAxis, currentAngle - dampedAngle, Space.Self);
}
else
{
_currentAngleVelocity = 0.0f;
}
}
#endregion
#region Private Types & Data
private float _currentAngleVelocity;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 66858c08b0019284683fe834e616d2d2
timeCreated: 1548833695
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,75 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WristConnectionRays.RayProperties.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Mechanics.CyborgAvatar
{
public partial class WristConnectionRays
{
#region Private Types & Data
/// <summary>
/// Stores the properties of a connection ray.
/// </summary>
[Serializable]
private class RayProperties
{
#region Inspector Properties/Serialized Fields
[SerializeField] [ColorUsage(true, true)] private Color _color;
[SerializeField] private float _thickness;
[SerializeField] private float _offset;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the ray thickness.
/// </summary>
public float Thickness => _thickness;
/// <summary>
/// Gets the ray offset.
/// </summary>
public float Offset => _offset;
/// <summary>
/// Gets the ray color.
/// </summary>
public Color Color
{
get => _color;
set => _color = value;
}
/// <summary>
/// Gets or sets the GameObject created at runtime for the ray.
/// </summary>
public GameObject GameObject { get; set; }
/// <summary>
/// Gets or sets the line renderer component created at runtime for the ray.
/// </summary>
public LineRenderer LineRenderer { get; set; }
/// <summary>
/// Gets or sets the offset in the 2d section of the ray direction.
/// </summary>
public Vector2 OffsetXY { get; set; }
/// <summary>
/// Gets or sets the start color.
/// </summary>
public Color StartColor { get; set; }
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d398127f73748839a21126772e4539b
timeCreated: 1643732645

View File

@@ -0,0 +1,194 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WristConnectionRays.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.CyborgAvatar
{
/// <summary>
/// Component that drives the two devices that connect the Cyborg wrist to the arm.
/// </summary>
public partial class WristConnectionRays : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _gradientPosStart1 = 0.15f;
[SerializeField] private float _gradientPosStart2 = 0.2f;
[SerializeField] private float _gradientPosEnd1 = 0.8f;
[SerializeField] private float _gradientPosEnd2 = 0.85f;
[SerializeField] private Material _rayMaterial;
[SerializeField] private bool _useMaterialNoiseParameters;
[SerializeField] private Transform _src;
[SerializeField] private Transform _dst;
[SerializeField] private List<RayProperties> _rays;
#endregion
#region Unity
/// <summary>
/// Subscribes to avatar update event.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from avatar update events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
Create(_src.position, _dst.position);
}
#endregion
#region Event Handling Methods
/// <summary>
/// Updates the component.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
if (_src != null && _dst != null)
{
UpdateRays(_src.position, _dst.position);
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates the connections.
/// </summary>
/// <param name="src">Source position</param>
/// <param name="dst">Destination position</param>
private void Create(Vector3 src, Vector3 dst)
{
foreach (RayProperties ray in _rays)
{
ray.GameObject = new GameObject("Ray");
ray.GameObject.transform.SetParent(transform, true);
ray.GameObject.transform.localPosition = Vector3.zero;
ray.GameObject.transform.localRotation = Quaternion.identity;
ray.LineRenderer = ray.GameObject.AddComponent<LineRenderer>();
ray.LineRenderer.material = _rayMaterial;
if (_useMaterialNoiseParameters)
{
ray.LineRenderer.material.SetFloat(DistortTimeStartVarName, Random.value * 10000.0f);
}
ray.LineRenderer.textureMode = LineTextureMode.Stretch;
ray.OffsetXY = Random.insideUnitCircle;
}
UpdateRays(src, dst);
}
/// <summary>
/// Updates the connection rays.
/// </summary>
/// <param name="src">Source position</param>
/// <param name="dst">End position</param>
private void UpdateRays(Vector3 src, Vector3 dst)
{
foreach (RayProperties ray in _rays)
{
if (ray.GameObject == null)
{
continue;
}
ray.GameObject.transform.position = src;
ray.GameObject.transform.LookAt(dst);
float rayLength = Vector3.Distance(src, dst) / ray.LineRenderer.transform.lossyScale.z;
Vector3[] positions =
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, rayLength * _gradientPosStart1),
new Vector3(0.0f, 0.0f, rayLength * _gradientPosStart2),
new Vector3(0.0f, 0.0f, rayLength * _gradientPosEnd1),
new Vector3(0.0f, 0.0f, rayLength * _gradientPosEnd2),
new Vector3(0.0f, 0.0f, rayLength)
};
Vector3 offset = (ray.GameObject.transform.right * ray.OffsetXY.x + ray.GameObject.transform.up * ray.OffsetXY.y).normalized * ray.Offset;
for (int pos = 0; pos < positions.Length; ++pos)
{
positions[pos] = ray.LineRenderer.transform.InverseTransformPoint(ray.GameObject.transform.TransformPoint(positions[pos]) + offset);
}
ray.LineRenderer.useWorldSpace = false;
ray.LineRenderer.positionCount = 6;
ray.LineRenderer.SetPositions(positions);
ray.LineRenderer.startWidth = ray.Thickness;
ray.LineRenderer.endWidth = ray.Thickness;
ray.LineRenderer.material.color = ray.Color;
if (ray.LineRenderer.material.mainTexture != null)
{
ray.LineRenderer.material.mainTextureScale = new Vector2(rayLength / ray.Thickness / (ray.LineRenderer.material.mainTexture.width / (float)ray.LineRenderer.material.mainTexture.height), 1.0f);
}
Gradient colorGradient = new Gradient();
colorGradient.colorKeys = new[]
{
new GradientColorKey(Color.white, 0.0f),
new GradientColorKey(Color.white, _gradientPosStart1),
new GradientColorKey(Color.white, _gradientPosStart2),
new GradientColorKey(Color.white, _gradientPosEnd1),
new GradientColorKey(Color.white, _gradientPosEnd2),
new GradientColorKey(Color.white, 1.0f)
};
colorGradient.alphaKeys = new[]
{
new GradientAlphaKey(0.0f, 0.0f),
new GradientAlphaKey(0.0f, _gradientPosStart1),
new GradientAlphaKey(1.0f, _gradientPosStart2),
new GradientAlphaKey(1.0f, _gradientPosEnd1),
new GradientAlphaKey(0.0f, _gradientPosEnd2),
new GradientAlphaKey(0.0f, 1.0f)
};
ray.LineRenderer.colorGradient = colorGradient;
}
}
#endregion
#region Private Types & Data
private readonly string DistortTimeStartVarName = "_DistortTimeStart";
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 956417d4411d10142ac02fcd9618b664
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fc0a56be6f684a1db8ea8b54a0afd95d
timeCreated: 1643904891

View File

@@ -0,0 +1,226 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrActor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// An actor in the Weapons module is an entity that can inflict and/or take damage.
/// </summary>
public class UxrActor : UxrComponent<UxrActor>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _life;
[SerializeField] private Animator _animator;
[SerializeField] private string _takeDamageAnimationTriggerVarName;
[SerializeField] private string _dieAnimationTriggerVarName;
[SerializeField] private AudioClip _takeDamageAudioClip;
[SerializeField] private AudioClip _dieAudioClip;
[SerializeField] private float _destroyAfterDeadSeconds = -1.0f;
[SerializeField] private bool _automaticDamageHandling = true;
[SerializeField] private bool _automaticDeadHandling = true;
#endregion
#region Public Types & Data
/// <summary>
/// Event triggered right before the actor is about to receive damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> will allow not to take the damage.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceiving;
/// <summary>
/// Event triggered right after the actor received damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> is not supported, since the damage was already taken.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceived;
/// <summary>
/// Gets or sets whether damage should be handled automatically. Automatic damage handling will take care of computing
/// the new life value when receiving damage.
/// </summary>
public bool AutomaticDamageHandling
{
get => _automaticDamageHandling;
set => _automaticDamageHandling = value;
}
/// <summary>
/// Gets or sets whether to handle death automatically when the actor's life reaches zero.
/// </summary>
public bool AutomaticDeadHandling
{
get => _automaticDeadHandling;
set => _automaticDeadHandling = value;
}
/// <summary>
/// Gets or sets the actor's life value.
/// </summary>
public float Life
{
get => _life;
set => _life = value;
}
/// <summary>
/// Gets whether the actor is dead.
/// </summary>
public bool IsDead { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Makes the actor receive a damaging projectile impact.
/// </summary>
/// <param name="actorSource">Actor source of the projectile</param>
/// <param name="raycastHit">Raycast that hit the actor</param>
/// <param name="damage">Damage to be taken</param>
public void ReceiveImpact(UxrActor actorSource, RaycastHit raycastHit, float damage)
{
OnReceiveDamage(new UxrDamageEventArgs(actorSource, this, raycastHit, damage, damage >= Life));
}
/// <summary>
/// Makes the actor receive explosive damage.
/// </summary>
/// <param name="actorSource">Actor source of the projectile</param>
/// <param name="position">Explosion source</param>
/// <param name="damage">Damage to be taken</param>
public void ReceiveExplosion(UxrActor actorSource, Vector3 position, float damage)
{
OnReceiveDamage(new UxrDamageEventArgs(actorSource, this, position, damage, damage >= Life));
}
/// <summary>
/// Makes the actor receive generic damage.
/// </summary>
/// <param name="damage">Damage to be taken</param>
public void ReceiveDamage(float damage)
{
OnReceiveDamage(new UxrDamageEventArgs(damage, damage >= Life));
}
/// <summary>
/// Forces the actor to die after a certain amount of seconds.
/// </summary>
/// <param name="delaySeconds">Seconds to wait for the actor to die</param>
public void Die(float delaySeconds)
{
Invoke(nameof(DieInternal), delaySeconds);
}
#endregion
#region Unity
/// <summary>
/// Makes sure the <see cref="UxrWeaponManager" /> singleton instance is available so that actors are registered."/>
/// </summary>
protected override void Awake()
{
base.Awake();
UxrWeaponManager.Instance.Poke();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Handles receiving damage and calls the appropriate events.
/// </summary>
/// <param name="e">Damage event parameters</param>
private void OnReceiveDamage(UxrDamageEventArgs e)
{
if (IsDead)
{
return;
}
DamageReceiving?.Invoke(this, e);
if (!e.IsCanceled)
{
bool destroy = false;
if (_automaticDamageHandling)
{
_life -= e.Damage;
}
if (_life <= 0.0f)
{
// Deadly damage
if (_automaticDeadHandling)
{
destroy = true;
}
else
{
IsDead = true;
}
}
else
{
// Non-deadly damage
if (_animator != null && string.IsNullOrEmpty(_takeDamageAnimationTriggerVarName) == false)
{
_animator.SetTrigger(_takeDamageAnimationTriggerVarName);
}
if (_takeDamageAudioClip)
{
AudioSource.PlayClipAtPoint(_takeDamageAudioClip, transform.position);
}
}
DamageReceived?.Invoke(this, e);
if (destroy)
{
DieInternal();
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Makes the actor die.
/// </summary>
private void DieInternal()
{
Life = 0.0f;
IsDead = true;
if (_animator != null && string.IsNullOrEmpty(_dieAnimationTriggerVarName) == false)
{
_animator.SetTrigger(_dieAnimationTriggerVarName);
}
if (_dieAudioClip)
{
AudioSource.PlayClipAtPoint(_dieAudioClip, transform.position);
}
Destroy(gameObject, _destroyAfterDeadSeconds > 0.0f ? _destroyAfterDeadSeconds : 0.0f);
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 618f9ab40c56ca4438d3a53036e0d785
timeCreated: 1504854550
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,127 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDamageEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Damage event parameters.
/// </summary>
public class UxrDamageEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the type of damage.
/// </summary>
public UxrDamageType DamageType { get; }
/// <summary>
/// Gets the actor that inflicted the damage, or null if the damage didn't come from any specific actor.
/// </summary>
public UxrActor ActorSource { get; }
/// <summary>
/// Gets the actor that received the damage.
/// </summary>
public UxrActor ActorTarget { get; }
/// <summary>
/// Gets the raycast information for projectile hits. Only valid if <see cref="DamageType" /> is
/// <see cref="UxrDamageType.ProjectileHit" />.
/// </summary>
public RaycastHit RaycastHit { get; }
/// <summary>
/// Gets the source position for explosive damage. Only valid if <see cref="DamageType" /> is
/// <see cref="UxrDamageType.Explosive" />
/// </summary>
public Vector3 ExplosionPosition { get; }
/// <summary>
/// Gets the amount of damage taken/inflicted.
/// </summary>
public float Damage { get; }
/// <summary>
/// Gets whether the damage will result in the death of the receiving actor.
/// </summary>
public bool Dies { get; }
/// <summary>
/// Gets if the damage was canceled for damage pre-events. Damage post-events cannot be canceled since the damage was
/// already inflicted.
/// </summary>
public bool IsCanceled { get; private set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor for projectile damage.
/// </summary>
/// <param name="source">Source actor</param>
/// <param name="target">Target actor</param>
/// <param name="raycastHit">Raycast hit</param>
/// <param name="damage">Damage amount</param>
/// <param name="dies">Whether the damage results in death</param>
public UxrDamageEventArgs(UxrActor source, UxrActor target, RaycastHit raycastHit, float damage, bool dies)
{
DamageType = UxrDamageType.ProjectileHit;
ActorSource = source;
ActorTarget = target;
RaycastHit = raycastHit;
Damage = damage;
Dies = dies;
}
/// <summary>
/// Constructor for explosive damage.
/// </summary>
/// <param name="source">Source actor or null if the damage didn't come from another actor</param>
/// <param name="target">Target actor</param>
/// <param name="explosionPosition">Explosion world position</param>
/// <param name="damage">Damage amount</param>
/// <param name="dies">Whether the damage results in death</param>
public UxrDamageEventArgs(UxrActor source, UxrActor target, Vector3 explosionPosition, float damage, bool dies)
{
DamageType = UxrDamageType.Explosive;
ActorSource = source;
ActorTarget = target;
ExplosionPosition = explosionPosition;
Damage = damage;
Dies = dies;
}
/// <summary>
/// Constructor for generic damage.
/// </summary>
/// <param name="damage">Damage amount</param>
/// <param name="dies">Whether the damage results in death</param>
public UxrDamageEventArgs(float damage, bool dies)
{
DamageType = UxrDamageType.Other;
Damage = damage;
Dies = dies;
}
#endregion
#region Public Methods
/// <summary>
/// Allows pre-events to cancel the damage. post-events can not be cancelled since the damage was already taken.
/// </summary>
public void Cancel()
{
IsCanceled = true;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e8987630d8e0d4546858455b2e5ef7e5
timeCreated: 1535631921
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDamageType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Enumerates the different damage types that can be taken.
/// </summary>
public enum UxrDamageType
{
/// <summary>
/// Damage due to projectile hit.
/// </summary>
ProjectileHit,
/// <summary>
/// Damage due to explosion.
/// </summary>
Explosive,
/// <summary>
/// Other types of damage (falls, elements from scenario that generate damage...).
/// </summary>
Other
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02fe82ff5ef54cbd9015b60d1eb34664
timeCreated: 1642859942

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDeflectEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Projectile deflection event parameters.
/// </summary>
public class UxrDeflectEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the projectile source.
/// </summary>
public UxrProjectileSource ProjectileSource { get; }
/// <summary>
/// Gets the raycast that was used to detect the collision.
/// </summary>
public RaycastHit RaycastHit { get; }
/// <summary>
/// Gets the new projectile direction after being deflected.
/// </summary>
public Vector3 NewDirection { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="projectileSource">The projectile source</param>
/// <param name="raycastHit">The raycast used to detect projectile collision</param>
/// <param name="newDirection">The new, deflected, projectile direction</param>
public UxrDeflectEventArgs(UxrProjectileSource projectileSource, RaycastHit raycastHit, Vector3 newDirection)
{
ProjectileSource = projectileSource;
RaycastHit = raycastHit;
NewDirection = newDirection;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 12118d803c3748b4b907fa4778d35e9e
timeCreated: 1642860517

View File

@@ -0,0 +1,278 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrExplodeHierarchy.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Animation.GameObjects;
using UltimateXR.Audio;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that allows to explode a GameObject and all its rigidbody children.
/// If the component is attached to a GameObject that also has a <see cref="UxrActor" /> component the explosion will
/// be triggered when the actor dies.
/// The explosion can also be called explicitly using <see cref="Explode" /> and <see cref="ExplodeNow" />.
/// </summary>
public class UxrExplodeHierarchy : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrAudioSample[] _audioExplodePool;
[SerializeField] private float _minExplodeSpeed = 5.0f;
[SerializeField] private float _maxExplodeSpeed = 20.0f;
[SerializeField] private float _minExplodeAngularSpeed = 1.0f;
[SerializeField] private float _maxExplodeAngularSpeed = 8.0f;
[SerializeField] private float _secondsToExplode = -1.0f;
[SerializeField] private float _piecesLifeSeconds = 5.0f;
[SerializeField] private float _piecesFadeoutSeconds = 1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the explode timer. A negative value will either indicate that it is not using any timer (if
/// <see cref="HasExploded" /> is false) or that it already exploded (if <see cref="HasExploded" /> is true).
/// </summary>
public float ExplodeTimer { get; private set; } = -1.0f;
/// <summary>
/// Gets whether the object has exploded.
/// </summary>
public bool HasExploded { get; private set; }
/// <summary>
/// Gets or sets the minimum random speed that the chunks will have when the object explodes.
/// </summary>
public float MinExplodeSpeed
{
get => _minExplodeSpeed;
set => _minExplodeSpeed = value;
}
/// <summary>
/// Gets or sets the maximum random speed that the chunks will have when the object explodes.
/// </summary>
public float MaxExplodeSpeed
{
get => _maxExplodeSpeed;
set => _maxExplodeSpeed = value;
}
/// <summary>
/// Gets or sets the minimum random angular speed that the chunks will have when the object explodes.
/// </summary>
public float MinExplodeAngularSpeed
{
get => _minExplodeAngularSpeed;
set => _minExplodeAngularSpeed = value;
}
/// <summary>
/// Gets or sets the maximum random angular speed that the chunks will have when the object explodes.
/// </summary>
public float MaxExplodeAngularSpeed
{
get => _maxExplodeAngularSpeed;
set => _maxExplodeAngularSpeed = value;
}
/// <summary>
/// Gets or sets the seconds it will take for the object to explode after the component is enabled. A negative value
/// will disable the user of a timer and will only explode when the object was added to an <see cref="UxrActor" /> that
/// dies.
/// </summary>
public float SecondsToExplode
{
get => _secondsToExplode;
set
{
ExplodeTimer = value;
_secondsToExplode = value;
}
}
/// <summary>
/// Gets or sets the seconds it will take for the chunks to disappear after the object explodes.
/// </summary>
public float PiecesLifeSeconds
{
get => _piecesLifeSeconds;
set => _piecesLifeSeconds = value;
}
/// <summary>
/// Gets or sets the seconds it will take for the chunks to fade-out when the chunks disappear after
/// <see cref="PiecesLifeSeconds" />.
/// </summary>
public float PiecesFadeoutSeconds
{
get => _piecesFadeoutSeconds;
set => _piecesFadeoutSeconds = value;
}
#endregion
#region Public Methods
/// <summary>
/// Explodes an object.
/// </summary>
/// <param name="root">Root object to explode</param>
/// <param name="minExplodeVelocity">Minimum random velocity assigned to the chunks</param>
/// <param name="maxExplodeVelocity">Maximum random velocity assigned to the chunks</param>
/// <param name="secondsToExplode">Seconds to wait before exploding</param>
/// <param name="piecesLifeSeconds">Life in seconds to assign to the chunks, after which they will be destroyed</param>
public static void Explode(GameObject root, float minExplodeVelocity, float maxExplodeVelocity, float secondsToExplode, float piecesLifeSeconds)
{
UxrExplodeHierarchy explodeHierarchy = root.AddComponent<UxrExplodeHierarchy>();
explodeHierarchy.MinExplodeSpeed = minExplodeVelocity;
explodeHierarchy.MaxExplodeSpeed = maxExplodeVelocity;
explodeHierarchy.SecondsToExplode = secondsToExplode;
explodeHierarchy.PiecesLifeSeconds = piecesLifeSeconds;
}
/// <summary>
/// Explodes an object immediately using the current parameters.
/// </summary>
public void ExplodeNow()
{
if (HasExploded)
{
return;
}
foreach (Collider chunkCollider in _colliders)
{
chunkCollider.enabled = true;
chunkCollider.transform.SetParent(null);
if (!chunkCollider.gameObject.TryGetComponent<Rigidbody>(out var rigidBodyChunk))
{
rigidBodyChunk = chunkCollider.gameObject.AddComponent<Rigidbody>();
}
rigidBodyChunk.isKinematic = false;
rigidBodyChunk.velocity = Random.insideUnitSphere * Random.Range(MinExplodeSpeed, MaxExplodeSpeed);
rigidBodyChunk.angularVelocity = Random.insideUnitSphere * Random.Range(MinExplodeAngularSpeed, MaxExplodeAngularSpeed);
float startAlpha = 1.0f;
Renderer renderer = chunkCollider.gameObject.GetComponent<Renderer>();
if (renderer)
{
startAlpha = renderer.material.color.a;
}
UxrObjectFade.Fade(chunkCollider.gameObject, startAlpha, 0.0f, PiecesLifeSeconds - PiecesFadeoutSeconds, PiecesFadeoutSeconds);
Destroy(rigidBodyChunk.gameObject, PiecesLifeSeconds);
}
HasExploded = true;
ExplodeTimer = -1.0f;
if (_audioExplodePool != null)
{
int i = Random.Range(0, _audioExplodePool.Length);
_audioExplodePool[i].Play(transform.position);
}
Destroy(gameObject);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_colliders = gameObject.GetComponentsInChildren<Collider>(true);
HasExploded = false;
}
/// <summary>
/// Subscribes to events and initializes the explosion timer.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (!HasExploded)
{
ExplodeTimer = SecondsToExplode;
}
UxrActor actor = GetCachedComponent<UxrActor>();
if (actor)
{
actor.DamageReceived += Actor_Damaged;
}
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrActor actor = GetCachedComponent<UxrActor>();
if (actor)
{
actor.DamageReceived -= Actor_Damaged;
}
}
/// <summary>
/// Updates the explosion timer and checks if the object needs to explode.
/// </summary>
private void Update()
{
if (ExplodeTimer >= 0.0f && !HasExploded)
{
ExplodeTimer -= Time.deltaTime;
if (ExplodeTimer < 0.0f)
{
ExplodeNow();
}
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the component was added to an object with an <see cref="UxrActor" /> component and it took damage.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Actor_Damaged(object sender, UxrDamageEventArgs e)
{
if (e.Dies)
{
ExplodeNow();
}
}
#endregion
#region Private Types & Data
private Collider[] _colliders;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d47748b13f81fbe419af1d5c560c07a4
timeCreated: 1505383405
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,99 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmAmmoLabel.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that draws the ammo left in a firearm magazine.
/// </summary>
[RequireComponent(typeof(UxrFirearmWeapon))]
public class UxrFirearmAmmoLabel : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Text _textTarget;
[SerializeField] private int _triggerIndex;
[SerializeField] private bool _showCapacity = true;
[SerializeField] private int _digits = 2;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_firearm = GetComponent<UxrFirearmWeapon>();
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called after all avatars were updated. Updates the ammo information.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
if (!_textTarget || !_firearm)
{
return;
}
string ammoRemaining = _firearm.HasMagAttached(_triggerIndex) ? _firearm.GetAmmoLeft(_triggerIndex).ToString() : string.Empty;
string ammoCapacity = _firearm.HasMagAttached(_triggerIndex) ? _firearm.GetAmmoCapacity(_triggerIndex).ToString() : string.Empty;
if (_firearm.HasMagAttached(_triggerIndex))
{
ammoRemaining = ammoRemaining.PadLeft(_digits, '0');
ammoCapacity = ammoCapacity.PadLeft(_digits, '0');
}
else
{
ammoRemaining = ammoRemaining.PadLeft(_digits, '-');
ammoCapacity = ammoCapacity.PadLeft(_digits, '-');
}
_textTarget.text = _showCapacity ? $"{ammoRemaining}/{ammoCapacity}" : ammoRemaining;
}
#endregion
#region Private Types & Data
private UxrFirearmWeapon _firearm;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7bf7ee2c23f42ed4aafb6a70b591259b
timeCreated: 1533114909
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmMag.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrFirearmMag
{
#region Protected Overrides
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_rounds), ref _rounds);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e13b4d310cd347bbbbdc3ca7e92b7fd9
timeCreated: 1706634432

View File

@@ -0,0 +1,53 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmMag.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// A magazine that contains ammo for a <see cref="UxrFirearmWeapon" />. Magazines can be attached to firearms using
/// <see cref="UxrGrabbableObject" /> functionality.
/// </summary>
public partial class UxrFirearmMag : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _rounds;
[SerializeField] private int _capacity;
#endregion
#region Public Types & Data
/// <summary>
/// Total ammo capacity.
/// </summary>
public int Capacity => _capacity;
/// <summary>
/// Remaining ammo.
/// </summary>
public int Rounds
{
get => Mathf.Clamp(_rounds, 0, _capacity);
set
{
_rounds = Mathf.Clamp(value, 0, _capacity);
RoundsChanged?.Invoke();
}
}
/// <summary>
/// Event called whenever the number of rounds changed.
/// </summary>
public Action RoundsChanged;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 097a8eab230aadf40bdce734556f5482
timeCreated: 1533114909
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmTrigger.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Audio;
using UltimateXR.Core.Math;
using UltimateXR.Haptics;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Stores all the information related to a trigger in a <see cref="UxrFirearmWeapon" />. The projectile that will be
/// shot is described by a separate <see cref="UxrShotDescriptor" />. <see cref="ProjectileShotIndex" /> determines
/// which <see cref="UxrShotDescriptor" /> in a <see cref="UxrProjectileSource" /> will be shot. It usually is 0, but
/// can be higher if the projectile source supports multiple shot types.
/// </summary>
[Serializable]
internal class UxrFirearmTrigger
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _projectileShotIndex;
[SerializeField] private UxrShotCycle _cycleType;
[SerializeField] private int _maxShotFrequency;
[SerializeField] private UxrAudioSample _shotAudio;
[SerializeField] private UxrAudioSample _shotAudioNoAmmo;
[SerializeField] private UxrHapticClip _shotHapticClip = new UxrHapticClip(null, UxrHapticClipType.Shot);
[SerializeField] private UxrGrabbableObject _triggerGrabbable;
[SerializeField] private int _grabbableGrabPointIndex;
[SerializeField] private Transform _triggerTransform;
[SerializeField] private UxrAxis _triggerRotationAxis = UxrAxis.X;
[SerializeField] private float _triggerRotationDegrees = 40.0f;
[SerializeField] private UxrGrabbableObjectAnchor _ammunitionMagAnchor;
[SerializeField] private float _recoilAngleOneHand = 0.5f;
[SerializeField] private float _recoilAngleTwoHands = 2.0f;
[SerializeField] private Vector3 _recoilOffsetOneHand = -Vector3.forward * 0.03f;
[SerializeField] private Vector3 _recoilOffsetTwoHands = -Vector3.forward * 0.01f;
[SerializeField] private float _recoilDurationSeconds;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the index in the <see cref="UxrProjectileSource" /> component of the shot fired whenever the triggers is
/// pressed.
/// </summary>
public int ProjectileShotIndex => _projectileShotIndex;
/// <summary>
/// Gets the shot cycle type.
/// </summary>
public UxrShotCycle CycleType => _cycleType;
/// <summary>
/// Gets the maximum shooting frequency.
/// </summary>
public int MaxShotFrequency => _maxShotFrequency;
/// <summary>
/// Gets the audio played when the user pulls the trigger and the weapon shoots.
/// </summary>
public UxrAudioSample ShotAudio => _shotAudio;
/// <summary>
/// Gets the audio played when the user pulls the trigger and the weapon isn't loaded.
/// </summary>
public UxrAudioSample ShotAudioNoAmmo => _shotAudioNoAmmo;
/// <summary>
/// Gets the haptic feedback sent whenever the weapon shoots.
/// </summary>
public UxrHapticClip ShotHapticClip => _shotHapticClip;
/// <summary>
/// Gets the object that is required to grab in order to access the trigger.
/// </summary>
public UxrGrabbableObject TriggerGrabbable => _triggerGrabbable;
/// <summary>
/// Gets the index point for <see cref="TriggerGrabbable" />.
/// </summary>
public int GrabbableGrabPointIndex => _grabbableGrabPointIndex;
/// <summary>
/// Gets the transform that will rotate when the trigger is pressed.
/// </summary>
public Transform TriggerTransform => _triggerTransform;
/// <summary>
/// Gets the trigger rotation axis.
/// </summary>
public UxrAxis TriggerRotationAxis => _triggerRotationAxis;
/// <summary>
/// Gets the amount of degrees that the trigger will rotate when it is fully pressed.
/// </summary>
public float TriggerRotationDegrees => _triggerRotationDegrees;
/// <summary>
/// Gets the anchor where mags for ammo that will be shot using the trigger will be attached to.
/// </summary>
public UxrGrabbableObjectAnchor AmmunitionMagAnchor => _ammunitionMagAnchor;
/// <summary>
/// Recoil rotation in degrees when a single hand is grabbing the weapon.
/// </summary>
public float RecoilAngleOneHand => _recoilAngleOneHand;
/// <summary>
/// Recoil rotation in degrees when two hands are grabbing the weapon.
/// </summary>
public float RecoilAngleTwoHands => _recoilAngleTwoHands;
/// <summary>
/// Recoil offset when a single hand is grabbing the weapon.
/// </summary>
public Vector3 RecoilOffsetOneHand => _recoilOffsetOneHand;
/// <summary>
/// Recoil offset when two hands are grabbing the weapon.
/// </summary>
public Vector3 RecoilOffsetTwoHands => _recoilOffsetTwoHands;
/// <summary>
/// Recoil animation duration in seconds. The animation will be procedurally applied to the weapon.
/// </summary>
public float RecoilDurationSeconds => _recoilDurationSeconds;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b5e96be893ae465a870923f59e5e626a
timeCreated: 1643104910

View File

@@ -0,0 +1,195 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmWeapon.RuntimeTriggerInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core.Serialization;
using UltimateXR.Core.StateSave;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrFirearmWeapon
{
#region Private Types & Data
private class RuntimeTriggerInfo : IUxrSerializable, ICloneable
{
#region Public Types & Data
/// <summary>
/// Gets or sets whether the trigger is being pressed.
/// </summary>
public bool TriggerPressed
{
get => _triggerPressed;
set => _triggerPressed = value;
}
/// <summary>
/// Gets or sets whether the trigger just started being pressed.
/// </summary>
public bool TriggerPressStarted
{
get => _triggerPressStarted;
set => _triggerPressStarted = value;
}
/// <summary>
/// Gets or sets whether the trigger just finished being pressed.
/// </summary>
public bool TriggerPressEnded
{
get => _triggerPressEnded;
set => _triggerPressEnded = value;
}
/// <summary>
/// Gets or sets the decreasing timer in seconds that will reach zero when the firearm is ready to shoot again.
/// </summary>
public float LastShotTimer
{
get => _lastShotTimer;
set => _lastShotTimer = value;
}
/// <summary>
/// Gets or sets whether the weapon is currently loaded.
/// </summary>
public bool HasReloaded
{
get => _hasReloaded;
set => _hasReloaded = value;
}
/// <summary>
/// Gets or sets the trigger's initial local rotation.
/// </summary>
public Quaternion TriggerInitialLocalRotation
{
get => _triggerInitialLocalRotation;
set => _triggerInitialLocalRotation = value;
}
/// <summary>
/// Gets or sets the decreasing timer in seconds that will reach zero when the recoil animation finished.
/// </summary>
public float RecoilTimer
{
get => _recoilTimer;
set => _recoilTimer = value;
}
#endregion
#region Implicit ICloneable
/// <summary>
/// Clones the object. Helps <see cref="UxrStateSaveImplementer{T}" /> avoid using serialization to create a deep copy.
/// </summary>
/// <returns>Copy</returns>
public object Clone()
{
RuntimeTriggerInfo copy = new RuntimeTriggerInfo();
copy._triggerPressed = _triggerPressed;
copy._triggerPressStarted = _triggerPressStarted;
copy._triggerPressEnded = _triggerPressEnded;
copy._lastShotTimer = _lastShotTimer;
copy._hasReloaded = _hasReloaded;
copy._triggerInitialLocalRotation = _triggerInitialLocalRotation;
copy._recoilTimer = _recoilTimer;
return copy;
}
#endregion
#region Implicit IUxrSerializable
/// <inheritdoc />
public int SerializationVersion => 0;
/// <inheritdoc />
public void Serialize(IUxrSerializer serializer, int serializationVersion)
{
serializer.Serialize(ref _triggerPressed);
serializer.Serialize(ref _triggerPressStarted);
serializer.Serialize(ref _triggerPressEnded);
serializer.Serialize(ref _lastShotTimer);
serializer.Serialize(ref _hasReloaded);
serializer.Serialize(ref _triggerInitialLocalRotation);
serializer.Serialize(ref _recoilTimer);
}
#endregion
#region Public Overrides object
/// <summary>
/// Compares this object to another. Helps <see cref="UxrStateSaveImplementer{T}" /> compare to avoid unnecessary
/// serialization.
/// </summary>
/// <param name="obj">Object to compare it to</param>
/// <returns>Boolean telling whether the object is equal to <paramref name="obj" /></returns>
public override bool Equals(object obj)
{
return Equals(obj as RuntimeTriggerInfo);
}
/// <inheritdoc />
public override int GetHashCode()
{
// Use XOR (^) to combine hash codes for booleans that are used in Equals().
return _triggerPressed.GetHashCode() ^ _triggerPressStarted.GetHashCode() ^ _triggerPressEnded.GetHashCode() ^ _hasReloaded.GetHashCode();
}
#endregion
#region Public Methods
/// <summary>
/// Compares this object to another. Helps <see cref="UxrStateSaveImplementer{T}" /> compare to avoid unnecessary
/// serialization.
/// </summary>
/// <param name="other">Object to compare it to</param>
/// <returns>Boolean telling whether the object is equal to <paramref name="other" /></returns>
public bool Equals(RuntimeTriggerInfo other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
// TODO: Check if we need to add the remaining fields in the comparison. We avoid it for now to avoid detecting unnecessary changes all the time.
return _triggerPressed == other._triggerPressed &&
_triggerPressStarted == other._triggerPressStarted &&
_triggerPressEnded == other._triggerPressEnded &&
_hasReloaded == other._hasReloaded;
}
#endregion
#region Private Types & Data
private bool _triggerPressed;
private bool _triggerPressStarted;
private bool _triggerPressEnded;
private float _lastShotTimer;
private bool _hasReloaded;
private Quaternion _triggerInitialLocalRotation;
private float _recoilTimer;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 55cb3d8b82224584bb0674d4181f6048
timeCreated: 1706006024

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmWeapon.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrFirearmWeapon
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Logic is already handled through events, we don't serialize these parameters in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_runtimeTriggers), ref _runtimeTriggers);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 829cda3eb2b441ff87890a63f9e39e5a
timeCreated: 1706004543

View File

@@ -0,0 +1,700 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFirearmWeapon.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Devices;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Type of weapon that shoots projectiles. A firearm has one or more <see cref="UxrFirearmTrigger" /> entries. Each
/// trigger allows to shoot a different type of projectile, and determines properties such as the shot cycle, shot
/// frequency, ammunition, recoil and grabbing.
/// A <see cref="UxrFirearmWeapon" /> requires a <see cref="UxrProjectileSource" /> component that defines the
/// projectiles being shot. If a firearm has more than one trigger (for instance, a rifle that shoots bullets and has a
/// grenade launcher), the <see cref="UxrProjectileSource" /> will require the same amount of entries in
/// <see cref="UxrProjectileSource.ShotTypes" />.
/// </summary>
[RequireComponent(typeof(UxrProjectileSource))]
public partial class UxrFirearmWeapon : UxrWeapon
{
#region Inspector Properties/Serialized Fields
[SerializeField] protected Transform _recoilAxes;
[SerializeField] private List<UxrFirearmTrigger> _triggers;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right after the weapon shot a projectile using the given trigger index.
/// </summary>
public event Action<int> ProjectileShot;
#endregion
#region Public Methods
/// <summary>
/// Checks whether a trigger is in a loaded state, meaning it is ready to shoot if pressed and there is any ammo left.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <returns>Whether it is ready to shoot</returns>
public bool IsLoaded(int triggerIndex)
{
if (_runtimeTriggers.TryGetValue(triggerIndex, out RuntimeTriggerInfo runtimeTrigger))
{
return runtimeTrigger.HasReloaded;
}
return false;
}
/// <summary>
/// Sets the given weapon trigger loaded state so that it is ready to shoot if there is ammo left.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
public void Reload(int triggerIndex)
{
if (!_runtimeTriggers.TryGetValue(triggerIndex, out RuntimeTriggerInfo runtimeTrigger))
{
return;
}
BeginSync();
runtimeTrigger.HasReloaded = true;
EndSyncMethod(new object[] { triggerIndex });
}
/// <summary>
/// Checks whether there is a magazine attached that fires shots using the given trigger. It may or may not have ammo.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <returns>Whether there is a magazine attached</returns>
public bool HasMagAttached(int triggerIndex)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return false;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
return trigger.AmmunitionMagAnchor != null && trigger.AmmunitionMagAnchor.CurrentPlacedObject != null;
}
/// <summary>
/// Gets the attached magazine maximum capacity.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <returns>Maximum capacity of ammo in the attached magazine. If there isn't any magazine attached it returns 0</returns>
public int GetAmmoCapacity(int triggerIndex)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return 0;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
if (trigger.AmmunitionMagAnchor != null && trigger.AmmunitionMagAnchor.CurrentPlacedObject != null)
{
UxrFirearmMag mag = trigger.AmmunitionMagAnchor.CurrentPlacedObject.GetCachedComponent<UxrFirearmMag>();
return mag != null ? mag.Capacity : 0;
}
return 0;
}
/// <summary>
/// Gets the ammo left in the attached magazine.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <returns>Ammo left in the attached magazine. If there isn't any magazine attached it returns 0</returns>
public int GetAmmoLeft(int triggerIndex)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return 0;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
if (trigger.AmmunitionMagAnchor != null)
{
if (trigger.AmmunitionMagAnchor.CurrentPlacedObject != null)
{
UxrFirearmMag mag = trigger.AmmunitionMagAnchor.CurrentPlacedObject.GetCachedComponent<UxrFirearmMag>();
return mag != null ? mag.Rounds : 0;
}
}
else
{
return int.MaxValue;
}
return 0;
}
/// <summary>
/// Sets the ammo left in the attached magazine.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <param name="ammo">New ammo</param>
public void SetAmmoLeft(int triggerIndex, int ammo)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
if (trigger.AmmunitionMagAnchor != null && trigger.AmmunitionMagAnchor.CurrentPlacedObject != null)
{
UxrFirearmMag mag = trigger.AmmunitionMagAnchor.CurrentPlacedObject.GetCachedComponent<UxrFirearmMag>();
if (mag != null)
{
mag.Rounds = ammo;
}
}
}
/// <summary>
/// Sets the trigger pressed amount.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <param name="amount">Pressed amount between range [0.0, 1.0]</param>
public void SetTriggerPressedAmount(int triggerIndex, float amount)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return;
}
if (!_runtimeTriggers.TryGetValue(triggerIndex, out RuntimeTriggerInfo runtimeTrigger))
{
return;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
if (trigger.TriggerTransform)
{
trigger.TriggerTransform.localRotation = runtimeTrigger.TriggerInitialLocalRotation * Quaternion.AngleAxis(trigger.TriggerRotationDegrees * amount, trigger.TriggerRotationAxis);
}
}
/// <summary>
/// Tries to shoot a round using the given trigger.
/// </summary>
/// <param name="triggerIndex">Index in <see cref="_triggers" /></param>
/// <returns>
/// Whether a round was shot. If no round was shot it can mean that:
/// <list type="bullet">
/// <item>The trigger index references an entry that doesn't exist.</item>
/// <item>The firearm isn't loaded.</item>
/// <item>The firearm doesn't have any ammo left or there is no magazine attached.</item>
/// <item>The shoot frequency doesn't allow to shoot again so quickly.</item>
/// </list>
/// </returns>
public bool TryToShootRound(int triggerIndex)
{
if (triggerIndex < 0 || triggerIndex >= _triggers.Count)
{
return false;
}
if (!_runtimeTriggers.TryGetValue(triggerIndex, out RuntimeTriggerInfo runtimeTrigger))
{
return false;
}
UxrFirearmTrigger trigger = _triggers[triggerIndex];
if (GetAmmoLeft(triggerIndex) > 0 && runtimeTrigger.LastShotTimer <= 0.0f)
{
SetAmmoLeft(triggerIndex, GetAmmoLeft(triggerIndex) - 1);
runtimeTrigger.LastShotTimer = trigger.MaxShotFrequency > 0 ? 1.0f / trigger.MaxShotFrequency : -1.0f;
// TODO: here we probably should add some randomization depending on recoil using the additional optional parameters
_weaponSource.Shoot(trigger.ProjectileShotIndex);
runtimeTrigger.RecoilTimer = trigger.RecoilDurationSeconds;
// Audio
trigger.ShotAudio?.Play(_weaponSource.GetShotOrigin(trigger.ProjectileShotIndex));
// Raise events
OnProjectileShot(triggerIndex);
return true;
}
return false;
}
#endregion
#region Unity
/// <inheritdoc />
protected override void Awake()
{
base.Awake();
for (int i = 0; i < _triggers.Count; ++i)
{
if (!_runtimeTriggers.ContainsKey(i))
{
_runtimeTriggers.Add(i, new RuntimeTriggerInfo());
}
}
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (RootGrabbable)
{
RootGrabbable.ConstraintsApplied += RootGrabbable_ConstraintsApplied;
}
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
foreach (UxrFirearmTrigger trigger in _triggers)
{
if (trigger.TriggerGrabbable != null)
{
trigger.TriggerGrabbable.Released += Trigger_Released;
trigger.TriggerGrabbable.Placed += Trigger_Placed;
}
if (trigger.AmmunitionMagAnchor != null)
{
trigger.AmmunitionMagAnchor.Placed += MagTarget_Placed;
trigger.AmmunitionMagAnchor.Removed += MagTarget_Removed;
}
}
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (RootGrabbable)
{
RootGrabbable.ConstraintsApplied -= RootGrabbable_ConstraintsApplied;
}
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
foreach (UxrFirearmTrigger trigger in _triggers)
{
if (trigger.TriggerGrabbable != null)
{
trigger.TriggerGrabbable.Released -= Trigger_Released;
trigger.TriggerGrabbable.Placed -= Trigger_Placed;
}
if (trigger.AmmunitionMagAnchor != null)
{
trigger.AmmunitionMagAnchor.Placed -= MagTarget_Placed;
trigger.AmmunitionMagAnchor.Removed -= MagTarget_Removed;
}
}
}
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
_weaponSource = GetCachedComponent<UxrProjectileSource>();
for (int i = 0; i < _triggers.Count; i++)
{
UxrFirearmTrigger trigger = _triggers[i];
if (_runtimeTriggers.TryGetValue(i, out RuntimeTriggerInfo info))
{
info.LastShotTimer = -1.0f;
info.HasReloaded = true;
if (trigger.TriggerTransform)
{
info.TriggerInitialLocalRotation = trigger.TriggerTransform.localRotation;
}
}
if (trigger.AmmunitionMagAnchor != null)
{
if (trigger.AmmunitionMagAnchor.CurrentPlacedObject != null)
{
// Disable mag collider while it is attached
Collider magCollider = trigger.AmmunitionMagAnchor.CurrentPlacedObject.GetComponentInChildren<Collider>();
if (magCollider != null)
{
magCollider.enabled = false;
}
}
}
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the grip of a grabbable object for a given trigger was released.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event parameters</param>
private void Trigger_Released(object sender, UxrManipulationEventArgs e)
{
if (e.Grabber != null && e.Grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
SyncAmmoLeft(e.GrabbableObject);
}
}
/// <summary>
/// Called when the grip of a grabbable object for a given trigger was placed.
/// </summary>
/// <param name="sender">Sender</param>
/// <param name="e">Event parameters</param>
private void Trigger_Placed(object sender, UxrManipulationEventArgs e)
{
if (e.Grabber != null && e.Grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
SyncAmmoLeft(e.GrabbableObject);
}
}
/// <summary>
/// Called after the avatars have been updated. Updates the hand trigger blend value.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
for (int i = 0; i < _triggers.Count; i++)
{
UxrFirearmTrigger trigger = _triggers[i];
if (!_runtimeTriggers.TryGetValue(i, out RuntimeTriggerInfo runtimeTrigger))
{
continue;
}
// Check if we are grabbing the given grip using the local avatar
if (trigger.TriggerGrabbable && UxrGrabManager.Instance.GetGrabbingHand(trigger.TriggerGrabbable, trigger.GrabbableGrabPointIndex, out UxrGrabber grabber))
{
if (grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
// Get the trigger press amount and use it to send it to the animation var that controls the hand trigger. Use it to rotate the trigger as well.
float triggerPressAmount = UxrAvatar.LocalAvatarInput.GetInput1D(grabber.Side, UxrInput1D.Trigger);
trigger.TriggerGrabbable.GetGrabPoint(trigger.GrabbableGrabPointIndex).GetGripPoseInfo(grabber.Avatar).PoseBlendValue = triggerPressAmount;
SetTriggerPressedAmount(i, triggerPressAmount);
// Now depending on the weapon type check if we need to shoot
SyncTriggerPressStates(i,
UxrAvatar.LocalAvatarInput.GetButtonsPress(grabber.Side, UxrInputButtons.Trigger),
UxrAvatar.LocalAvatarInput.GetButtonsPressDown(grabber.Side, UxrInputButtons.Trigger),
UxrAvatar.LocalAvatarInput.GetButtonsPressUp(grabber.Side, UxrInputButtons.Trigger));
}
else
{
// Remote avatars will get the trigger pressed amount from the avatar pose blend amount, because poses are synchronized.
SetTriggerPressedAmount(i, grabber.Avatar.GetCurrentHandPoseBlendValue(grabber.Side));
}
bool shoot = false;
switch (trigger.CycleType)
{
case UxrShotCycle.ManualReload:
{
shoot = runtimeTrigger.TriggerPressStarted && runtimeTrigger.HasReloaded;
if (shoot)
{
runtimeTrigger.HasReloaded = false;
}
break;
}
case UxrShotCycle.SemiAutomatic:
shoot = runtimeTrigger.TriggerPressStarted;
break;
case UxrShotCycle.FullyAutomatic:
shoot = runtimeTrigger.TriggerPressed;
break;
}
if (runtimeTrigger.TriggerPressStarted && GetAmmoLeft(i) == 0)
{
trigger.ShotAudioNoAmmo?.Play(trigger.TriggerTransform != null ? trigger.TriggerTransform.position : trigger.TriggerGrabbable.GetGrabPointGrabProximityTransform(grabber, trigger.GrabbableGrabPointIndex).position);
}
if (shoot)
{
// Shoot!
if (TryToShootRound(i))
{
if (grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
// Send haptic to the hand grabbing the grip
UxrAvatar.LocalAvatarInput.SendHapticFeedback(grabber.Side, trigger.ShotHapticClip);
// Send haptic to the other hand if it is also grabbing the weapon
if (UxrGrabManager.Instance.IsHandGrabbing(grabber.Avatar, trigger.TriggerGrabbable, grabber.OppositeSide, true))
{
UxrAvatar.LocalAvatarInput.SendHapticFeedback(grabber.OppositeSide, trigger.ShotHapticClip);
}
}
}
}
if (grabber.Avatar.AvatarMode == UxrAvatarMode.Local && runtimeTrigger.TriggerPressEnded)
{
// Sync ammo after shooting a fully automatic weapon to make sure the ammo left is the same.
if (trigger.CycleType == UxrShotCycle.FullyAutomatic)
{
SyncAmmoLeft(i, GetAmmoLeft(i));
}
}
}
runtimeTrigger.TriggerPressStarted = false;
runtimeTrigger.TriggerPressEnded = false;
if (runtimeTrigger.LastShotTimer > 0.0f)
{
runtimeTrigger.LastShotTimer -= Time.deltaTime;
}
if (runtimeTrigger.RecoilTimer > 0.0f)
{
runtimeTrigger.RecoilTimer -= Time.deltaTime;
}
}
}
/// <summary>
/// Called when a mag is removed.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void MagTarget_Removed(object sender, UxrManipulationEventArgs e)
{
foreach (UxrFirearmTrigger trigger in _triggers)
{
if (e.GrabbableAnchor == trigger.AmmunitionMagAnchor)
{
Collider magCollider = e.GrabbableObject.GetComponentInChildren<Collider>();
if (magCollider != null)
{
magCollider.enabled = true;
}
}
}
}
/// <summary>
/// Called when a mag is attached.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void MagTarget_Placed(object sender, UxrManipulationEventArgs e)
{
foreach (UxrFirearmTrigger trigger in _triggers)
{
if (e.GrabbableAnchor == trigger.AmmunitionMagAnchor)
{
Collider magCollider = e.GrabbableObject.GetComponentInChildren<Collider>();
if (magCollider != null)
{
magCollider.enabled = false;
}
}
}
}
/// <summary>
/// Called right after applying constraints to the main grabbable object. It is used to apply the recoil after the
/// constraints to do it in the appropriate order.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void RootGrabbable_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs e)
{
// Get the grabbable object and apply recoil depending on the number of hands that are grabbing the gun
UxrGrabbableObject grabbableObject = sender as UxrGrabbableObject;
Transform grabbableTransform = grabbableObject.transform;
int grabbingHandCount = UxrGrabManager.Instance.GetGrabbingHandCount(grabbableObject);
for (int i = 0; i < _triggers.Count; i++)
{
UxrFirearmTrigger trigger = _triggers[i];
if (_runtimeTriggers.TryGetValue(i, out RuntimeTriggerInfo runtimeTrigger) && runtimeTrigger.RecoilTimer > 0.0f)
{
float recoilT = trigger.RecoilDurationSeconds > 0.0f ? (trigger.RecoilDurationSeconds - runtimeTrigger.RecoilTimer) / trigger.RecoilDurationSeconds : 0.0f;
Vector3 recoilRight = _recoilAxes != null ? _recoilAxes.right : grabbableTransform.right;
Vector3 recoilUp = _recoilAxes != null ? _recoilAxes.up : grabbableTransform.up;
Vector3 recoilForward = _recoilAxes != null ? _recoilAxes.forward : grabbableTransform.forward;
Vector3 recoilPosition = _recoilAxes != null ? _recoilAxes.position : grabbableTransform.position;
float amplitude = 1.0f - recoilT;
Vector3 recoilOffset = grabbingHandCount == 1 ? amplitude * trigger.RecoilOffsetOneHand : amplitude * trigger.RecoilOffsetTwoHands;
grabbableTransform.position += recoilRight * recoilOffset.x + recoilUp * recoilOffset.y + recoilForward * recoilOffset.z;
grabbableTransform.RotateAround(recoilPosition, -recoilRight, grabbingHandCount == 1 ? amplitude * trigger.RecoilAngleOneHand : amplitude * trigger.RecoilAngleTwoHands);
}
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="ProjectileShot" />.
/// </summary>
/// <param name="triggerIndex">The weapon trigger index</param>
protected virtual void OnProjectileShot(int triggerIndex)
{
ProjectileShot?.Invoke(triggerIndex);
}
#endregion
#region Private Methods
/// <summary>
/// Sets the trigger pressed state, to sync multiplayer.
/// </summary>
/// <param name="triggerIndex">The trigger index</param>
/// <param name="pressed">Whether the trigger is pressed</param>
/// <param name="pressDown">Whether the trigger just started being pressed down</param>
/// <param name="pressUp">Whether the trigger just started being released</param>
private void SyncTriggerPressStates(int triggerIndex, bool pressed, bool pressDown, bool pressUp)
{
if (_runtimeTriggers.TryGetValue(triggerIndex, out RuntimeTriggerInfo runtimeTrigger))
{
if (runtimeTrigger.TriggerPressed != pressed || runtimeTrigger.TriggerPressStarted != pressDown || runtimeTrigger.TriggerPressEnded != pressUp)
{
BeginSync();
runtimeTrigger.TriggerPressed = pressed;
runtimeTrigger.TriggerPressStarted = pressDown;
runtimeTrigger.TriggerPressEnded = pressUp;
EndSyncMethod(new object[] { triggerIndex, pressed, pressDown, pressUp });
}
}
}
/// <summary>
/// See <see cref="SyncAmmoLeft(int, int)" />.
/// </summary>
/// <param name="grabbableTrigger">The grabbable object for the trigger</param>
private void SyncAmmoLeft(UxrGrabbableObject grabbableTrigger)
{
UxrFirearmTrigger trigger = _triggers.FirstOrDefault(t => t.TriggerGrabbable == grabbableTrigger);
if (trigger != null && trigger.CycleType == UxrShotCycle.FullyAutomatic)
{
int index = _triggers.IndexOf(trigger);
if (index != -1)
{
SyncAmmoLeft(index, GetAmmoLeft(index));
}
}
}
/// <summary>
/// Sets the ammo left, to sync multiplayer after a fully automatic gun stopped firing.
/// </summary>
/// <param name="triggerIndex"> The trigger index</param>
/// <param name="ammo">The ammo left</param>
private void SyncAmmoLeft(int triggerIndex, int ammo)
{
BeginSync();
SetAmmoLeft(triggerIndex, ammo);
EndSyncMethod(new object[] { triggerIndex, ammo });
}
#endregion
#region Private Types & Data
/// <summary>
/// Gets the root grabbable object.
/// </summary>
private UxrGrabbableObject RootGrabbable
{
get
{
UxrGrabbableObject firstTriggerGrabbable = _triggers.Count > 0 && _triggers[0].TriggerGrabbable != null ? _triggers[0].TriggerGrabbable : null;
if (firstTriggerGrabbable)
{
// A normal setup will have just one grabbable point but for rifles and weapons with multiple parts we may have different grabbable objects.
// We will just get the root one so that we can subscribe to its ApplyConstraints event to apply recoil effects
Transform weaponRootGrabbableTransform = firstTriggerGrabbable.UsesGrabbableParentDependency ? firstTriggerGrabbable.GrabbableParent.transform : firstTriggerGrabbable.transform;
UxrGrabbableObject rootGrabbable = weaponRootGrabbableTransform.GetComponent<UxrGrabbableObject>();
return rootGrabbable;
}
return null;
}
}
private UxrProjectileSource _weaponSource;
private Dictionary<int, RuntimeTriggerInfo> _runtimeTriggers = new Dictionary<int, RuntimeTriggerInfo>();
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c1d9becc057b5a042859f71c7df57fdf
timeCreated: 1530189801
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrenadeActivationMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Enumerates the different modes to activate a grenade.
/// </summary>
public enum UxrGrenadeActivationMode
{
/// <summary>
/// Grenade has no activation mode.
/// </summary>
NoActivation,
/// <summary>
/// Grenade requires to remove a pin to activate a detonation timer.
/// </summary>
TriggerPin,
/// <summary>
/// A detonator timer is started after being launched.
/// </summary>
OnHandLaunch,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 428ff67b3dd04f44a26ef93f0e0b456e
timeCreated: 1643109873

View File

@@ -0,0 +1,313 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrenadeWeapon.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Audio;
using UltimateXR.Avatar;
using UltimateXR.Core.Caching;
using UltimateXR.Haptics;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Grenade weapon. A grenade inflicts explosive damage to <see cref="UxrActor" /> components.
/// </summary>
public class UxrGrenadeWeapon : UxrWeapon, IUxrPrecacheable
{
#region Inspector Properties/Serialized Fields
// General parameters
[SerializeField] private UxrGrenadeActivationMode _activationTrigger;
[SerializeField] private bool _explodeOnCollision;
// Timer
[SerializeField] private float _timerSeconds = 3.0f;
// Pin
[SerializeField] private UxrGrabbableObject _pin;
[SerializeField] private UxrAudioSample _audioRemovePin;
[SerializeField] private UxrHapticClip _hapticRemovePin = new UxrHapticClip(null, UxrHapticClipType.Click);
// Impact
[SerializeField] private LayerMask _impactExplosionCollisionMask = ~0;
// Explosion
[SerializeField] private GameObject[] _explosionPrefabPool;
[SerializeField] private float _explosionPrefabLife = 4.0f;
// Damage
[SerializeField] private float _damageRadius = 10.0f;
[SerializeField] private float _damageNear = 10.0f;
[SerializeField] private float _damageFar;
// Physics
[SerializeField] private bool _createPhysicsExplosion;
[SerializeField] private float _physicsExplosionForce = 10.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the grenade has been activated and the detonation timer is running.
/// </summary>
public bool IsActivated => _timer >= 0.0f;
/// <summary>
/// Gets the seconds left to explode. If the grenade hasn't been activated yet, it will return
/// <see cref="TimerDuration" />.
/// </summary>
public float Timer => _timer < 0.0f ? _timerSeconds : _timer;
/// <summary>
/// Gets the seconds it will take for the grenade to explode once it has been activated.
/// </summary>
public float TimerDuration => _timerSeconds;
/// <summary>
/// Gets distance from the explosion the grenade will start inflicting damage.
/// </summary>
public float DamageRadius => _damageRadius;
/// <summary>
/// Gets the maximum damage, applied at the very point of the explosion.
/// </summary>
public float DamageNear => _damageNear;
/// <summary>
/// Gets the minimum damage, applied at <see cref="DamageRadius" /> distance.
/// </summary>
public float DamageFar => _damageFar;
#endregion
#region Implicit IUxrPrecacheable
/// <inheritdoc />
public IEnumerable<GameObject> PrecachedInstances => _explosionPrefabPool;
#endregion
#region Public Methods
/// <summary>
/// Starts the detonation timer.
/// </summary>
public void ActivateTimer()
{
_timer = _timerSeconds;
}
/// <summary>
/// Freezes or resumes the detonation timer.
/// </summary>
/// <param name="freeze">Whether to freeze or resume the detonation timer</param>
public void FreezeTimer(bool freeze = true)
{
_timerFrozen = freeze;
}
/// <summary>
/// Restores the detonation timer to the initial time.
/// </summary>
public void RestoreTimer()
{
_timer = _timerSeconds;
}
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (_pin)
{
_pin.Grabbed += Pin_Grabbed;
}
if (_activationTrigger == UxrGrenadeActivationMode.OnHandLaunch)
{
if (GrabbableObject != null)
{
GrabbableObject.Released += Grenade_Released;
}
}
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (_pin)
{
_pin.Grabbed -= Pin_Grabbed;
}
if (_activationTrigger == UxrGrenadeActivationMode.OnHandLaunch)
{
if (GrabbableObject != null)
{
GrabbableObject.Released -= Grenade_Released;
}
}
}
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
if (_pin)
{
// Disable collider. We will enable it once the pin is grabbed.
Collider pinCollider = _pin.GetCachedComponent<Collider>();
if (pinCollider)
{
pinCollider.enabled = false;
}
}
}
/// <summary>
/// Updates the component.
/// </summary>
private void Update()
{
if (_timer > 0.0f && !_timerFrozen)
{
_timer -= Time.deltaTime;
if (_timer <= 0.0f)
{
Explode();
}
}
}
/// <summary>
/// Called by Unity when the physics-driven rigidbody collider hit something.
/// </summary>
/// <param name="collision">Collision object</param>
private void OnCollisionEnter(Collision collision)
{
if (_explodeOnCollision && (_impactExplosionCollisionMask.value & 1 << collision.collider.gameObject.layer) != 0)
{
Explode();
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the grenade was released.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Grenade_Released(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged && _activationTrigger == UxrGrenadeActivationMode.OnHandLaunch)
{
_timer = _timerSeconds;
}
}
/// <summary>
/// Called when the pin was grabbed. It will remove the pin from the grenade.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Pin_Grabbed(object sender, UxrManipulationEventArgs e)
{
if (e.IsGrabbedStateChanged && _activationTrigger == UxrGrenadeActivationMode.TriggerPin && !IsActivated)
{
_timer = _timerSeconds;
Collider pinCollider = _pin.GetCachedComponent<Collider>();
if (pinCollider)
{
pinCollider.enabled = true;
}
_audioRemovePin?.Play(transform.position);
// Send haptic to the hand that grabbed the pin
e.Grabber.Avatar.ControllerInput.SendGrabbableHapticFeedback(e.GrabbableObject, _hapticRemovePin);
}
}
#endregion
#region Private Methods
/// <summary>
/// Explodes the grenade, causing explosion and damage.
/// </summary>
private void Explode()
{
if (_exploded)
{
return;
}
_exploded = true;
if (_explosionPrefabPool.Length > 0)
{
GameObject newExplosion = Instantiate(_explosionPrefabPool[Random.Range(0, _explosionPrefabPool.Length)], transform.position, Quaternion.LookRotation(-UxrAvatar.LocalAvatar.CameraForward));
if (_explosionPrefabLife > 0.0f)
{
Destroy(newExplosion, _explosionPrefabLife);
}
}
if (_createPhysicsExplosion)
{
Collider[] colliders = Physics.OverlapSphere(transform.position, _damageRadius);
foreach (Collider targetCollider in colliders)
{
if (targetCollider.TryGetComponent<Rigidbody>(out var targetRigidbody))
{
targetRigidbody.AddExplosionForce(_physicsExplosionForce, transform.position, _damageRadius);
}
}
}
UxrWeaponManager.Instance.ApplyRadiusDamage(Owner, transform.position, _damageRadius, _damageNear, _damageFar);
Destroy(gameObject);
}
#endregion
#region Private Types & Data
private float _timer = -1.0f;
private bool _timerFrozen;
private bool _exploded;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2c2b9039ba07bb74e886b059d2d0ce7b
timeCreated: 1533207471
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,165 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrImpactDecal.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Audio;
using UltimateXR.Core;
using UltimateXR.Core.Caching;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that defines a decal generated as a result of the impact of a projectile.
/// </summary>
public class UxrImpactDecal : UxrComponent<UxrImpactDecal>, IUxrPrecacheable
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _ignoreDynamicObjects;
[SerializeField] private float _decalOffset = 0.005f;
[SerializeField] private Renderer[] _decalRenderers;
[SerializeField] private UxrAudioSample _audioImpact = new UxrAudioSample();
[SerializeField] private bool _audioOnDynamicObjects = true;
[SerializeField] private string _decalRendererColorName = UxrConstants.Shaders.StandardColorVarName;
#endregion
#region Implicit IUxrPrecacheable
/// <inheritdoc />
public IEnumerable<GameObject> PrecachedInstances
{
get { yield return gameObject; }
}
#endregion
#region Public Methods
/// <summary>
/// Checks if a given impact should generate a decal, and creates it if necessary.
/// </summary>
/// <param name="raycastHit">Impact raycast hit</param>
/// <param name="checkLayerMask">Layer mask that should generate a decal</param>
/// <param name="prefabDecal">The decal prefab to use when if the decal should be generated</param>
/// <param name="lifeTime">New decal life time, after which it will fade out and be destroyed</param>
/// <param name="fadeOutDurationSeconds">Decal fade out duration in seconds</param>
/// <param name="createDoubleSidedDecal">Whether to also generate a secondary decal for the other side of the impact</param>
/// <param name="doubleSidedDecalThickness">
/// Surface thickness to consider when generating the secondary decal for the other
/// side
/// </param>
/// <returns>New decal or null if no decal was generated</returns>
public static UxrImpactDecal CheckCreateDecal(RaycastHit raycastHit,
LayerMask checkLayerMask,
UxrImpactDecal prefabDecal,
float lifeTime,
float fadeOutDurationSeconds,
bool createDoubleSidedDecal = false,
float doubleSidedDecalThickness = 0.001f)
{
if (prefabDecal != null && raycastHit.collider != null && prefabDecal._ignoreDynamicObjects && raycastHit.collider.gameObject.IsDynamic())
{
// Dynamic objects have been set up to not generate decals. Play impact audio only.
if (prefabDecal._audioImpact != null && prefabDecal._audioOnDynamicObjects)
{
prefabDecal._audioImpact.Play(raycastHit.point);
}
return null;
}
if (prefabDecal != null && (checkLayerMask & 1 << raycastHit.collider.gameObject.layer) != 0)
{
UxrImpactDecal decal = Instantiate(prefabDecal, raycastHit.point + raycastHit.normal * prefabDecal._decalOffset, Quaternion.LookRotation(raycastHit.normal));
decal.transform.parent = raycastHit.collider.transform;
if (lifeTime > 0.0f)
{
Destroy(decal.gameObject, lifeTime);
}
decal._fadeOutDuration = fadeOutDurationSeconds;
decal._fadeOutTimer = lifeTime;
if (createDoubleSidedDecal)
{
UxrImpactDecal decalDoubleSided = Instantiate(prefabDecal, raycastHit.point - raycastHit.normal * (prefabDecal._decalOffset + doubleSidedDecalThickness), Quaternion.LookRotation(-raycastHit.normal));
decalDoubleSided.transform.parent = raycastHit.collider.transform;
if (lifeTime > 0.0f)
{
Destroy(decalDoubleSided.gameObject, lifeTime);
}
decalDoubleSided._fadeOutDuration = fadeOutDurationSeconds;
decalDoubleSided._fadeOutTimer = lifeTime;
}
if (prefabDecal._audioImpact != null)
{
prefabDecal._audioImpact.Play(raycastHit.point);
}
return decal;
}
return null;
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (_decalRenderers != null)
{
foreach (Renderer decalRenderer in _decalRenderers)
{
_startColors.Add(decalRenderer, decalRenderer.sharedMaterial.HasProperty(_decalRendererColorName) ? decalRenderer.sharedMaterial.GetColor(_decalRendererColorName) : Color.white);
}
}
}
/// <summary>
/// Updates the component.
/// </summary>
private void Update()
{
_fadeOutTimer -= Time.deltaTime;
if (_fadeOutTimer < _fadeOutDuration)
{
foreach (Renderer decalRenderer in _decalRenderers)
{
Material material = decalRenderer.material;
Color color = _startColors[decalRenderer];
color.a = _startColors[decalRenderer].a * (_fadeOutTimer / _fadeOutDuration);
material.SetColor(_decalRendererColorName, color);
}
}
}
#endregion
#region Private Types & Data
private readonly Dictionary<Renderer, Color> _startColors = new Dictionary<Renderer, Color>();
private float _fadeOutTimer;
private float _fadeOutDuration;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: d84dd744b1aa82b459278eda7f83ebf3
timeCreated: 1504853692
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,82 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMuzzleFlash.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Muzzle flash component for weapons that are firing shots.
/// </summary>
public class UxrMuzzleFlash : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Material _material;
[SerializeField] private int _textureColumns = 1;
[SerializeField] private int _textureRows = 1;
[SerializeField] private bool _randomizeAngle = true;
[SerializeField] private float _minRandomizeScale = 1.0f;
[SerializeField] private float _maxRandomizeScale = 1.0f;
[SerializeField] private string _scaleOffsetVarName = UxrConstants.Shaders.StandardMainTextureScaleOffsetVarName;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_meshRenderers = GetComponentsInChildren<MeshRenderer>();
if (_randomizeAngle)
{
transform.Rotate(Vector3.forward, Random.value * 360.0f, Space.Self);
}
foreach (MeshRenderer meshRenderer in _meshRenderers)
{
int randomColumn = Random.Range(0, _textureColumns);
int randomRow = Random.Range(0, _textureRows);
if (meshRenderer.sharedMaterial == _material)
{
Vector4 vecScaleOffset = meshRenderer.material.GetVector(_scaleOffsetVarName);
if (_textureColumns > 0)
{
vecScaleOffset.x = 1.0f / _textureColumns;
vecScaleOffset.z = randomColumn * vecScaleOffset.x;
}
if (_textureRows > 0)
{
vecScaleOffset.y = 1.0f / _textureRows;
vecScaleOffset.w = randomRow * vecScaleOffset.y;
}
meshRenderer.material.SetVector(_scaleOffsetVarName, vecScaleOffset);
}
}
float randomScale = Random.Range(_minRandomizeScale, _maxRandomizeScale);
transform.localScale *= randomScale;
}
#endregion
#region Private Types & Data
private MeshRenderer[] _meshRenderers;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: daa942b3dd922ad4ca6eada519005e7a
timeCreated: 1532593431
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrNonDamagingImpactEventArgs.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Event parameters for projectile impacts that do not cause any damage to actors, such as impacts on the
/// scenario or other elements.
/// </summary>
public class UxrNonDamagingImpactEventArgs : EventArgs
{
#region Public Types & Data
/// <summary>
/// Gets the actor that fired the shot.
/// </summary>
public UxrActor WeaponOwner { get; }
/// <summary>
/// The projectile source.
/// </summary>
public UxrProjectileSource ProjectileSource { get; }
/// <summary>
/// The raycast that detected the hit.
/// </summary>
public RaycastHit RaycastHit { get; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="weaponOwner">Owner of the weapon that fired the shot</param>
/// <param name="projectileSource">Projectile source</param>
/// <param name="raycastHit">Raycast that detected the hit</param>
public UxrNonDamagingImpactEventArgs(UxrActor weaponOwner, UxrProjectileSource projectileSource, RaycastHit raycastHit)
{
WeaponOwner = weaponOwner;
ProjectileSource = projectileSource;
RaycastHit = raycastHit;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 02fc4e983a7341fcb56deb4aeb0d3261
timeCreated: 1642855038

View File

@@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrOverrideImpactDecal.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that, added to a GameObject, will allows to override the decal generated by a projectile impact.
/// When a projectile impact coming from an <see cref="UxrProjectileSource" /> hits anything, an
/// <see cref="UxrOverrideImpactDecal" /> will be looked for traversing upwards in the hierarchy starting from the
/// collider.
/// If no <see cref="UxrOverrideImpactDecal" /> was found, the decal specified in the
/// <see cref="UxrProjectileSource" /> component will be used.
/// </summary>
public class UxrOverrideImpactDecal : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrImpactDecal _decalToUse;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the decal to override with when the object was hit.
/// </summary>
public UxrImpactDecal DecalToUse => _decalToUse;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5d02330c0eb2fbd428bed238a91d7099
timeCreated: 1505571355
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,161 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrProjectileDeflect.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Audio;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a collider, will allow to deflect shots coming from
/// <see cref="UxrProjectileSource" /> components.
/// </summary>
public class UxrProjectileDeflect : UxrGrabbableObjectComponent<UxrProjectileDeflect>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrAudioSample _audioDeflect;
[SerializeField] private UxrImpactDecal _decalOnReflect;
[SerializeField] private float _decalLife = 5.0f;
[SerializeField] private float _decalFadeoutDuration = 1.0f;
[SerializeField] private bool _twoSidedDecal;
[SerializeField] private float _twoSidedDecalThickness = 0.01f;
[SerializeField] private LayerMask _collideLayersAddOnReflect = 0;
#endregion
#region Public Types & Data
/// <summary>
/// Event called when a projectile got deflected after hitting the object.
/// </summary>
public event EventHandler<UxrDeflectEventArgs> ProjectileDeflected;
/// <summary>
/// Gets the sound to play when a projectile was deflected.
/// </summary>
public UxrAudioSample AudioDeflect => _audioDeflect;
/// <summary>
/// Gets the decal to instantiate when a projectile was deflected.
/// </summary>
public UxrImpactDecal DecalOnReflect => _decalOnReflect;
/// <summary>
/// Gets the decal life in seconds after which it will fade out and be destroyed.
/// </summary>
public float DecalLife => _decalLife;
/// <summary>
/// Gets the decal fadeout duration in seconds.
/// </summary>
public float DecalFadeoutDuration => _decalFadeoutDuration;
/// <summary>
/// Gets whether the decal requires to generate another copy on the other side.
/// </summary>
public bool TwoSidedDecal => _twoSidedDecal;
/// <summary>
/// Gets the object thickness in order to know how far the other side is to generate the copy on the backside of the
/// impact.
/// </summary>
public float TwoSidedDecalThickness => _twoSidedDecalThickness;
/// <summary>
/// Optional layer mask to add to the collider after a projectile was deflected.
/// </summary>
public LayerMask CollideLayersAddOnReflect => _collideLayersAddOnReflect;
/// <summary>
/// Gets the owner in case the deflection object is part of an avatar or can be grabbed.
/// </summary>
public UxrActor Owner { get; private set; }
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
Owner = GetComponentInParent<UxrActor>();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called when the object was grabbed. Will change the <see cref="Owner" />.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
if (e.IsGrabbedStateChanged && UxrGrabManager.Instance.GetGrabbingHand(e.GrabbableObject, e.GrabPointIndex, out UxrGrabber grabber))
{
Owner = grabber.Avatar.GetComponentInChildren<UxrActor>();
}
}
/// <summary>
/// Called when the object was released. Will reset the <see cref="Owner" />.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectReleased(UxrManipulationEventArgs e)
{
base.OnObjectReleased(e);
if (e.IsGrabbedStateChanged)
{
Owner = null;
}
}
/// <summary>
/// Called when the object was released. Will reset the <see cref="Owner" />.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectPlaced(UxrManipulationEventArgs e)
{
base.OnObjectPlaced(e);
if (e.IsGrabbedStateChanged && UxrGrabManager.Instance.GetGrabbingHand(e.GrabbableObject, e.GrabPointIndex, out UxrGrabber grabber))
{
Owner = null;
}
}
/// <summary>
/// Raises the <see cref="ProjectileDeflected" /> event.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseProjectileDeflected(UxrDeflectEventArgs e)
{
ProjectileDeflected?.Invoke(this, e);
}
#endregion
#region Protected Overrides UxrGrabbableObjectComponent<UxrProjectileDeflect>
/// <summary>
/// The grabbable object is not required. When it is present it will be used to assign the <see cref="Owner" /> so that
/// the damage will be attributed to the actor instead of the original source.
/// </summary>
protected override bool IsGrabbableObjectRequired => false;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e7805ce0b14da774880ea8149eec105e
timeCreated: 1505824554
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,206 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrProjectileSource.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core.Caching;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that has the ability to fire shots.
/// </summary>
public class UxrProjectileSource : UxrComponent<UxrProjectileSource>, IUxrPrecacheable
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Animator _weaponAnimator;
[SerializeField] private List<UxrShotDescriptor> _shotTypes;
#endregion
#region Public Types & Data
/// <summary>
/// The different shots that can be fired using the component.
/// </summary>
public IReadOnlyList<UxrShotDescriptor> ShotTypes => _shotTypes;
#endregion
#region Implicit IUxrPrecacheable
/// <inheritdoc />
public IEnumerable<GameObject> PrecachedInstances
{
get
{
foreach (UxrShotDescriptor shotType in _shotTypes)
{
if (shotType.PrefabInstantiateOnImpact)
{
yield return shotType.PrefabInstantiateOnImpact;
}
if (shotType.PrefabInstantiateOnTipWhenShot)
{
yield return shotType.PrefabInstantiateOnTipWhenShot;
}
if (shotType.PrefabScenarioImpactDecal && shotType.PrefabScenarioImpactDecal.gameObject)
{
yield return shotType.PrefabScenarioImpactDecal.gameObject;
}
if (shotType.ProjectilePrefab)
{
yield return shotType.ProjectilePrefab;
}
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Tries to get the <see cref="UxrActor" /> that holds the <see cref="UxrWeapon" /> that has the
/// <see cref="UxrProjectileSource" /> component.
/// </summary>
/// <returns>Actor component or null if it wasn't found</returns>
public UxrActor TryGetWeaponOwner()
{
UxrWeapon weapon = GetComponentInParent<UxrWeapon>();
if (weapon)
{
return weapon.Owner;
}
return GetComponentInParent<UxrActor>();
}
/// <summary>
/// Shoots a round.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to fire</param>
public void Shoot(int shotTypeIndex)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
Shoot(shotTypeIndex, _shotTypes[shotTypeIndex].ShotSource.position, _shotTypes[shotTypeIndex].ShotSource.rotation);
}
}
/// <summary>
/// Shoots a round, overriding the source position and orientation.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to fire</param>
/// <param name="projectileSource">Source shot position</param>
/// <param name="projectileOrientation">Shot source orientation. The shot will be fired in the z (forward) direction</param>
public void Shoot(int shotTypeIndex, Vector3 projectileSource, Quaternion projectileOrientation)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
if (_shotTypes[shotTypeIndex].PrefabInstantiateOnTipWhenShot)
{
GameObject newInstance = Instantiate(_shotTypes[shotTypeIndex].PrefabInstantiateOnTipWhenShot, _shotTypes[shotTypeIndex].Tip.position, _shotTypes[shotTypeIndex].Tip.rotation);
if (_shotTypes[shotTypeIndex].PrefabInstantiateOnTipParent)
{
newInstance.transform.parent = transform;
}
else
{
newInstance.transform.parent = null;
}
if (_shotTypes[shotTypeIndex].PrefabInstantiateOnTipLife >= 0.0f)
{
Destroy(newInstance, _shotTypes[shotTypeIndex].PrefabInstantiateOnTipLife);
}
}
UxrWeaponManager.Instance.RegisterNewProjectileShot(this, _shotTypes[shotTypeIndex], projectileSource, projectileOrientation);
if (_weaponAnimator != null && string.IsNullOrEmpty(_shotTypes[shotTypeIndex].ShotAnimationVarName) == false)
{
_weaponAnimator.SetTrigger(_shotTypes[shotTypeIndex].ShotAnimationVarName);
}
}
}
/// <summary>
/// Shoots a round pointing to the given target.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to fire</param>
/// <param name="target">Position where the shot will be going towards</param>
public void ShootTo(int shotTypeIndex, Vector3 target)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
Vector3 direction = (target - _shotTypes[shotTypeIndex].ShotSource.position).normalized;
Shoot(shotTypeIndex, _shotTypes[shotTypeIndex].ShotSource.position, Quaternion.LookRotation(direction));
}
}
/// <summary>
/// Gets the distance where a shot using the current position and orientation will impact.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to use</param>
/// <returns>Shot distance or a negative value telling the current target is out of range</returns>
public float ShotRaycastDistance(int shotTypeIndex)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
if (Physics.Raycast(_shotTypes[shotTypeIndex].ShotSource.position,
_shotTypes[shotTypeIndex].ShotSource.forward,
out RaycastHit raycastHit,
_shotTypes[shotTypeIndex].ProjectileMaxDistance,
_shotTypes[shotTypeIndex].CollisionLayerMask,
QueryTriggerInteraction.Ignore))
{
return raycastHit.distance;
}
}
return -1.0f;
}
/// <summary>
/// Gets the current world-space origin of projectiles fired using the given shot type.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to use</param>
/// <returns>Projectile world-space source</returns>
public Vector3 GetShotOrigin(int shotTypeIndex)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
return _shotTypes[shotTypeIndex].ShotSource.position;
}
return Vector3.zero;
}
/// <summary>
/// Gets the current world-space direction of projectiles fired using the given shot type.
/// </summary>
/// <param name="shotTypeIndex">Index in <see cref="ShotTypes" />, telling which shot type to use</param>
/// <returns>Projectile world-space direction</returns>
public Vector3 GetShotDirection(int shotTypeIndex)
{
if (shotTypeIndex >= 0 && shotTypeIndex < _shotTypes.Count)
{
return _shotTypes[shotTypeIndex].ShotSource.forward;
}
return Vector3.zero;
}
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1b09fc2a58519c04382be9afd085eb88
timeCreated: 1504852557
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrShotCycle.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Enumerates the supported firearm shot cycles.
/// </summary>
public enum UxrShotCycle
{
/// <summary>
/// Weapon requires a manual reload to fire the next round.
/// </summary>
ManualReload,
/// <summary>
/// Weapon fires a single round each time the trigger is pressed. The next round requires to release the trigger and
/// press it again.
/// </summary>
SemiAutomatic,
/// <summary>
/// Weapon keeps firing one round after another while the trigger is being pressed.
/// </summary>
FullyAutomatic
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e568b53c93d42c1ae79c5d81703d695
timeCreated: 1643104513

View File

@@ -0,0 +1,169 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrShotDescriptor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Class describing all the information of a type of projectile that a GameObject having a
/// <see cref="UxrProjectileSource" /> component can shoot.
/// Normally there will be a <see cref="UxrFirearmWeapon" /> with a <see cref="UxrProjectileSource" /> component
/// supporting one or more <see cref="UxrShotDescriptor" />.
/// For example, a rifle with a grenade launcher attachment will be able to fire two types of projectiles: bullets and
/// explosive grenades.
/// <see cref="UxrProjectileSource" /> components, however, do not require to be part of a
/// <see cref="UxrFirearmWeapon" /> and can be used on their own.
/// </summary>
[Serializable]
public class UxrShotDescriptor
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _shotSource;
[SerializeField] private Transform _tip;
[SerializeField] private bool _useAutomaticProjectileTrajectory = true;
[SerializeField] private string _shotAnimationVarName;
[SerializeField] private GameObject _prefabInstantiateOnTipWhenShot;
[SerializeField] private float _prefabInstantiateOnTipLife = 5.0f;
[SerializeField] private bool _prefabInstantiateOnTipParent = true;
[SerializeField] private GameObject _projectilePrefab;
[SerializeField] private float _projectileSpeed = 30.0f;
[SerializeField] private float _projectileMaxDistance = 300.0f;
[SerializeField] private float _projectileLength = 0.01f;
[SerializeField] private float _projectileDamageNear = 20.0f;
[SerializeField] private float _projectileDamageFar = 20.0f;
[SerializeField] private float _projectileImpactForceMultiplier = 1.0f;
[SerializeField] private LayerMask _collisionLayerMask = -1;
[SerializeField] private GameObject _prefabInstantiateOnImpact;
[SerializeField] private float _prefabInstantiateOnImpactLife = 5.0f;
[SerializeField] private UxrImpactDecal _prefabScenarioImpactDecal;
[SerializeField] private float _prefabScenarioImpactDecalLife = 10.0f;
[SerializeField] private float _decalFadeoutDuration = 7.0f;
[SerializeField] private LayerMask _createDecalLayerMask = -1;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the <see cref="Transform" /> that is used to fire projectiles from, using the forward vector as direction.
/// </summary>
public Transform ShotSource => _shotSource;
/// <summary>
/// Gets the <see cref="Transform" /> that is used to instantiate effects on the tip when a shot was fired, using
/// <see cref="PrefabInstantiateOnTipWhenShot" />.
/// </summary>
public Transform Tip => _tip;
/// <summary>
/// Gets whether the projectiles fired should be updated automatically to compute their trajectory or they will be
/// updated manually.
/// </summary>
public bool UseAutomaticProjectileTrajectory => _useAutomaticProjectileTrajectory;
/// <summary>
/// Optional <see cref="Animator" /> trigger variable name that will be triggered on the weapon each time a round is
/// fired.
/// </summary>
public string ShotAnimationVarName => _shotAnimationVarName;
/// <summary>
/// An optional prefab that will be instantiated on the <see cref="Tip" /> each time a round is fired.
/// </summary>
public GameObject PrefabInstantiateOnTipWhenShot => _prefabInstantiateOnTipWhenShot;
/// <summary>
/// Life in seconds of <see cref="PrefabInstantiateOnTipWhenShot" /> after which it will be destroyed.
/// </summary>
public float PrefabInstantiateOnTipLife => _prefabInstantiateOnTipLife;
/// <summary>
/// Whether <see cref="PrefabInstantiateOnTipWhenShot" /> will be parented to the <see cref="Tip" /> after being
/// instantiated or will remain unparented.
/// </summary>
public bool PrefabInstantiateOnTipParent => _prefabInstantiateOnTipParent;
/// <summary>
/// Prefab that will be instantiated as the projectile.
/// </summary>
public GameObject ProjectilePrefab => _projectilePrefab;
/// <summary>
/// Speed at which the projectile will move.
/// </summary>
public float ProjectileSpeed => _projectileSpeed;
/// <summary>
/// Maximum reach of the projectile, after which it will be destroyed.
/// </summary>
public float ProjectileMaxDistance => _projectileMaxDistance;
/// <summary>
/// The physical length of the projectile, used in ray-casting computations.
/// </summary>
public float ProjectileLength => _projectileLength;
/// <summary>
/// The damage a projectile will do if it were to hit at the closest distance. Damage will linearly decrease over
/// distance down to <see cref="ProjectileDamageFar" /> until the projectile reaches
/// <see cref="_projectileMaxDistance" />.
/// </summary>
public float ProjectileDamageNear => _projectileDamageNear;
/// <summary>
/// The damage a projectile will do if it were to hit at the farthest distance. Damage will linearly decrease over
/// distance from the start down to <see cref="ProjectileDamageFar" /> until the projectile reaches
/// <see cref="_projectileMaxDistance" />.
/// </summary>
public float ProjectileDamageFar => _projectileDamageFar;
/// <summary>
/// The force multiplier applied to a rigidbody that was hit by a projectile. The total force applied will be speed *
/// ForceMultiplier.
/// </summary>
public float ProjectileImpactForceMultiplier => _projectileImpactForceMultiplier;
/// <summary>
/// The layer mask used to determine which objects can be hit.
/// </summary>
public LayerMask CollisionLayerMask => _collisionLayerMask;
/// <summary>
/// An optional prefab to instantiate at the point of impact.
/// </summary>
public GameObject PrefabInstantiateOnImpact => _prefabInstantiateOnImpact;
/// <summary>
/// Life in seconds after which <see cref="PrefabInstantiateOnImpact" /> will be destroyed.
/// </summary>
public float PrefabInstantiateOnImpactLife => _prefabInstantiateOnImpactLife;
/// <summary>
/// Default decal that will be used when the projectile impacted with something. Can be overriden using the
/// <see cref="UxrOverrideImpactDecal" /> component.
/// </summary>
public UxrImpactDecal PrefabScenarioImpactDecal => _prefabScenarioImpactDecal;
/// <summary>
/// Life in seconds after which <see cref="PrefabScenarioImpactDecal" /> will fadeout and be destroyed.
/// </summary>
public float PrefabScenarioImpactDecalLife => _prefabScenarioImpactDecalLife;
/// <summary>
/// Duration of the fadeout effect before a <see cref="PrefabScenarioImpactDecal" /> is destroyed.
/// </summary>
public float DecalFadeoutDuration => _decalFadeoutDuration;
/// <summary>
/// The layer mask used to determine if an impact will generate a decal.
/// </summary>
public LayerMask CreateDecalLayerMask => _createDecalLayerMask;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 96ddb71598ce41cd8fe0e4380569ba02
timeCreated: 1643100819

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrShotgunPump.State.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrShotgunPump
{
#region Private Types & Data
/// <summary>
/// Enumerates the different states in a <see cref="UxrShotgunPump" /> component.
/// </summary>
private enum State
{
/// <summary>
/// Waiting for the pump action in the first direction.
/// </summary>
WaitPump,
/// <summary>
/// Waiting for the pump action back.
/// </summary>
WaitPumpBack
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2746817ac1b94b3d888360921776ad90
timeCreated: 1643728993

View File

@@ -0,0 +1,117 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrShotgunPump.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Audio;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Haptics;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Component that, added to a GameObject with a <see cref="UxrFirearmWeapon" /> component, allows to communicate
/// whenever the shotgun is reloaded using a pump action using a <see cref="UxrGrabbableObject" />. The shot cycle in
/// the firearm should be set to <see cref="UxrShotCycle.ManualReload" />.
/// </summary>
[RequireComponent(typeof(UxrFirearmWeapon))]
public partial class UxrShotgunPump : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _triggerIndex;
[SerializeField] private UxrGrabbableObject _pump;
[SerializeField] private Vector3 _localPumpDirection = Vector3.forward;
[SerializeField] private Vector3 _localPumpOffset = Vector3.forward * 0.2f;
[SerializeField] [Range(0, 1)] private float _slideThreshold = 0.7f;
[SerializeField] private UxrAudioSample _audioSlide = new UxrAudioSample();
[SerializeField] private UxrAudioSample _audioSlideBack = new UxrAudioSample();
[SerializeField] private UxrAudioSample _audioSlideAlreadyLoaded = new UxrAudioSample();
[SerializeField] private UxrAudioSample _audioSlideBackAlreadyLoaded = new UxrAudioSample();
[SerializeField] private UxrHapticClip _hapticClipSlide = new UxrHapticClip(null, UxrHapticClipType.Click);
[SerializeField] private UxrHapticClip _hapticClipSlideBack = new UxrHapticClip(null, UxrHapticClipType.Click);
[SerializeField] private UxrHapticClip _hapticClipSlideAlreadyLoaded = new UxrHapticClip(null, UxrHapticClipType.Slide);
[SerializeField] private UxrHapticClip _hapticClipSlideBackAlreadyLoaded = new UxrHapticClip(null, UxrHapticClipType.Slide);
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_state = State.WaitPump;
_localStart = _pump != null ? _pump.transform.localPosition : Vector3.zero;
_firearm = GetComponent<UxrFirearmWeapon>();
}
/// <summary>
/// Updates the component, looking for the pump action if necessary.
/// </summary>
private void Update()
{
if (_pump == null)
{
return;
}
float currentSlide = Vector3.Scale(_pump.transform.localPosition - _localStart, _localPumpDirection).magnitude / _localPumpOffset.magnitude;
if (_state == State.WaitPump && currentSlide > _slideThreshold)
{
_state = State.WaitPumpBack;
if (_firearm.IsLoaded(_triggerIndex))
{
_audioSlideAlreadyLoaded.Play(_pump.transform.position);
}
else
{
_audioSlide.Play(_pump.transform.position);
}
if (UxrGrabManager.Instance.GetGrabbingHand(_pump, 0, out UxrGrabber grabber) && grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
UxrAvatar.LocalAvatarInput.SendHapticFeedback(grabber.Side, _firearm.IsLoaded(_triggerIndex) ? _hapticClipSlideAlreadyLoaded : _hapticClipSlide);
}
}
else if (_state == State.WaitPumpBack && currentSlide < _slideThreshold * 0.9f)
{
if (_firearm.IsLoaded(_triggerIndex))
{
_audioSlideBackAlreadyLoaded.Play(_pump.transform.position);
}
else
{
_audioSlideBack.Play(_pump.transform.position);
}
if (UxrGrabManager.Instance.GetGrabbingHand(_pump, 0, out UxrGrabber grabber) && grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
{
UxrAvatar.LocalAvatarInput.SendHapticFeedback(grabber.Side, _firearm.IsLoaded(_triggerIndex) ? _hapticClipSlideBackAlreadyLoaded : _hapticClipSlideBack);
}
_firearm.Reload(_triggerIndex);
_state = State.WaitPump;
}
}
#endregion
#region Private Types & Data
private UxrFirearmWeapon _firearm;
private Vector3 _localStart;
private State _state;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 00cec3188f04edb4cb6dc7b4fa4c540a
timeCreated: 1533114909
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,29 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeapon.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.StateSave;
namespace UltimateXR.Mechanics.Weapons
{
public abstract partial class UxrWeapon
{
#region Protected Overrides UxrComponent
/// <inheritdoc />
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
// Logic is already handled through events, we don't serialize these parameters in incremental changes
if (level > UxrStateSaveLevel.ChangesSincePreviousSave)
{
SerializeStateValue(level, options, nameof(_owner), ref _owner);
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5a251ea42b89455fafdc31e73801fc38
timeCreated: 1706004719

View File

@@ -0,0 +1,102 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeapon.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components.Composite;
using UltimateXR.Manipulation;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Base class for weapons. Weapons are used by <see cref="UxrActor" /> components to inflict damage to other actor
/// components.
/// </summary>
public abstract partial class UxrWeapon : UxrGrabbableObjectComponent<UxrWeapon>
{
#region Public Types & Data
/// <summary>
/// Gets who is in possession of the weapon, to attribute the inflicted damage to.
/// </summary>
public UxrActor Owner
{
get => _owner;
protected set => _owner = value;
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrActor.GlobalUnregistering += UxrActor_GlobalUnregistering;
Owner = GetComponentInParent<UxrActor>();
}
/// <summary>
/// Called when it's going to be destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
UxrActor.GlobalUnregistering -= UxrActor_GlobalUnregistering;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever an actor is about to be destroyed.
/// </summary>
/// <param name="actor"></param>
private void UxrActor_GlobalUnregistering(UxrActor actor)
{
if (Owner == actor)
{
Owner = null;
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Called when the object was grabbed. It is used to set the weapon owner.
/// </summary>
/// <param name="e">Event parameters</param>
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
{
base.OnObjectGrabbed(e);
if (e.IsGrabbedStateChanged && UxrGrabManager.Instance.GetGrabbingHand(e.GrabbableObject, e.GrabPointIndex, out UxrGrabber grabber))
{
Owner = grabber.Avatar.GetComponentInChildren<UxrActor>();
}
}
#endregion
#region Protected Overrides UxrGrabbableObjectComponent<UxrWeapon>
/// <inheritdoc />
protected override bool IsGrabbableObjectRequired => false;
#endregion
#region Private Types & Data
private UxrActor _owner;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 102dfa92408c7034a9b3d0294424e47a
timeCreated: 1535641242
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeaponManager.DamageActorInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrWeaponManager
{
#region Private Types & Data
/// <summary>
/// Stores information about an actor in the
/// </summary>
private class ActorInfo
{
#region Public Types & Data
public Transform Transform { get; }
#endregion
#region Constructors & Finalizer
public ActorInfo(UxrActor target)
{
Transform = target.GetComponent<Transform>();
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ce80fc4320b94062a4e49dd779bfbc63
timeCreated: 1643733896

View File

@@ -0,0 +1,132 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeaponManager.ProjectileInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
public partial class UxrWeaponManager
{
#region Private Types & Data
/// <summary>
/// Stores information of a projectile currently travelling through the world.
/// </summary>
private class ProjectileInfo
{
#region Public Types & Data
/// <summary>
/// Gets the <see cref="UxrActor" /> that shot the projectile, or deflected it. It will be used know who to attribute
/// the damage to.
/// </summary>
public UxrActor WeaponOwner { get; }
/// <summary>
/// Gets the source that shot the projectile.
/// </summary>
public UxrProjectileSource ProjectileSource { get; }
/// <summary>
/// Gets the shot descriptor that was used to shoot the projectile.
/// </summary>
public UxrShotDescriptor ShotDescriptor { get; }
/// <summary>
/// Gets the layer mask, used to determine which objects the shot can collide with.
/// </summary>
public LayerMask ShotLayerMask { get; }
/// <summary>
/// Gets the projectile GameObject instance.
/// </summary>
public GameObject Projectile { get; }
/// <summary>
/// Gets the world-space point the projectile came from.
/// </summary>
public Vector3 ProjectileOrigin { get; }
/// <summary>
/// Gets the current projectile speed in units/second.
/// </summary>
public float ProjectileSpeed { get; }
/// <summary>
/// Gets or sets the projectile's position during the previous frame.
/// </summary>
public Vector3 ProjectileLastPosition { get; set; }
/// <summary>
/// Gets or sets the currently travelled distance.
/// </summary>
public float ProjectileDistanceTravelled { get; set; }
/// <summary>
/// Gets or sets the deflector that deflected the shot or null if there wasn't any.
/// </summary>
public UxrProjectileDeflect ProjectileDeflectSource { get; set; }
/// <summary>
/// Gets or sets whether the current state is the first frame in the shot.
/// </summary>
public bool FirstFrame { get; set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="weaponOwner">Weapon owner, that fired the shot</param>
/// <param name="projectileSource">Projectile source component</param>
/// <param name="shotDescriptor">Shot descriptor</param>
/// <param name="position">World-space position where the shot started</param>
/// <param name="orientation">
/// World space orientation where the shot started. The shot will travel in the z (forward)
/// position of these axes
/// </param>
public ProjectileInfo(UxrActor weaponOwner, UxrProjectileSource projectileSource, UxrShotDescriptor shotDescriptor, Vector3 position, Quaternion orientation)
{
WeaponOwner = weaponOwner;
ProjectileSource = projectileSource;
ShotDescriptor = shotDescriptor;
ShotLayerMask = shotDescriptor.CollisionLayerMask;
Projectile = Instantiate(shotDescriptor.ProjectilePrefab, position, orientation);
Projectile.transform.parent = null;
ProjectileOrigin = position;
ProjectileSpeed = shotDescriptor.ProjectileSpeed;
ProjectileLastPosition = ProjectileOrigin;
ProjectileDistanceTravelled = 0.0f;
ProjectileDeflectSource = null;
FirstFrame = true;
}
#endregion
#region Public Methods
/// <summary>
/// Adds a value to the layer mask that is used to determine the objects the projectile can collide with.
/// </summary>
/// <param name="value">Value to add to the mask</param>
public void AddShotLayerMask(int value)
{
_shotLayerMask |= value;
}
#endregion
#region Private Types & Data
private LayerMask _shotLayerMask;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1db86a63784643c7a06668bb5d11f7f6
timeCreated: 1643733924

View File

@@ -0,0 +1,357 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWeaponManager.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core;
using UltimateXR.Core.Components.Singleton;
using UltimateXR.Core.Settings;
using UnityEngine;
namespace UltimateXR.Mechanics.Weapons
{
/// <summary>
/// Singleton manager in charge of updating projectiles, computing hits against entities and damage done on
/// <see cref="UxrActor" /> components.
/// </summary>
public partial class UxrWeaponManager : UxrSingleton<UxrWeaponManager>
{
#region Public Types & Data
/// <summary>
/// Event triggered right before an <see cref="UxrActor" /> is about to receive damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> will allow not to take the damage.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceiving;
/// <summary>
/// Event triggered right after the actor received damage.
/// Setting <see cref="UxrDamageEventArgs.Cancel" /> is not supported, since the damage was already taken.
/// </summary>
public event EventHandler<UxrDamageEventArgs> DamageReceived;
/// <summary>
/// Event called whenever there was a projectile impact but no <see cref="UxrActor" /> was involved. Mostly hits
/// against the scenario that still generate decals, FX, etc.
/// </summary>
public event EventHandler<UxrNonDamagingImpactEventArgs> NonActorImpacted;
#endregion
#region Public Methods
/// <summary>
/// Updates the manager.
/// </summary>
public void UpdateManager()
{
UpdateProjectiles();
}
/// <summary>
/// Registers a new projectile shot so that it gets automatically update by the manager from that moment until it hits
/// something or gets destroyed.
/// </summary>
/// <param name="projectileSource">Projectile source</param>
/// <param name="shotDescriptor">Shot descriptor</param>
/// <param name="position">World position</param>
/// <param name="orientation">World orientation. The projectile will travel in the forward (z) direction</param>
public void RegisterNewProjectileShot(UxrProjectileSource projectileSource, UxrShotDescriptor shotDescriptor, Vector3 position, Quaternion orientation)
{
_projectiles.Add(new ProjectileInfo(projectileSource.TryGetWeaponOwner(), projectileSource, shotDescriptor, position, orientation));
}
/// <summary>
/// Applies radius damage to all elements around a source position.
/// </summary>
/// <param name="actorSource">The actor that was responsible for the damage or null if there wasn't any</param>
/// <param name="position">Explosion world position</param>
/// <param name="radius">Radius</param>
/// <param name="nearDamage">Damage at the very same point of the explosion</param>
/// <param name="farDamage">Damage at the distance set by <paramref name="radius" /></param>
public void ApplyRadiusDamage(UxrActor actorSource, Vector3 position, float radius, float nearDamage, float farDamage)
{
foreach (KeyValuePair<UxrActor, ActorInfo> damageActorPair in _damageActors)
{
float distance = Vector3.Distance(damageActorPair.Value.Transform.position, position);
if (distance < radius)
{
float damage = Mathf.Lerp(nearDamage, farDamage, distance / radius);
damageActorPair.Key.ReceiveExplosion(actorSource, position, damage);
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the manager. Subscribes to actor enable/disable events.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrActor.GlobalEnabled += Actor_Enabled;
UxrActor.GlobalDisabled += Actor_Disabled;
}
/// <summary>
/// Initializes the manager. Unsubscribes from actor enable/disable events.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
UxrActor.GlobalEnabled -= Actor_Enabled;
UxrActor.GlobalDisabled -= Actor_Disabled;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever an <see cref="UxrActor" /> was enabled. Create a new entry in the internal dictionary.
/// </summary>
/// <param name="actor">Actor that was enabled</param>
private void Actor_Enabled(UxrActor actor)
{
if (_damageActors.ContainsKey(actor) == false)
{
_damageActors.Add(actor, new ActorInfo(actor));
actor.DamageReceiving += Actor_DamageReceiving;
actor.DamageReceived += Actor_DamageReceived;
}
}
/// <summary>
/// Called whenever an <see cref="UxrActor" /> was disabled. Remove the entry from the internal dictionary.
/// </summary>
/// <param name="actor">Actor that was disabled</param>
private void Actor_Disabled(UxrActor actor)
{
if (_damageActors.ContainsKey(actor))
{
_damageActors.Remove(actor);
actor.DamageReceiving -= Actor_DamageReceiving;
actor.DamageReceived -= Actor_DamageReceived;
}
}
/// <summary>
/// Called whenever an actor is about to receive any damage. The damage can be canceled using
/// <see cref="UxrDamageEventArgs.Cancel" />.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Actor_DamageReceiving(object sender, UxrDamageEventArgs e)
{
OnDamageReceiving(e);
}
/// <summary>
/// Called right after an actor received any damage. Damage can not be canceled using
/// <see cref="UxrDamageEventArgs.Cancel" /> because it was already taken.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void Actor_DamageReceived(object sender, UxrDamageEventArgs e)
{
OnDamageReceived(e);
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="DamageReceiving" />.
/// </summary>
/// <param name="e">Event parameters</param>
private void OnDamageReceiving(UxrDamageEventArgs e)
{
DamageReceiving?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="DamageReceived" />.
/// </summary>
/// <param name="e">Event parameters</param>
private void OnDamageReceived(UxrDamageEventArgs e)
{
if (UxrGlobalSettings.Instance.LogLevelWeapons >= UxrLogLevel.Relevant)
{
string sourceInfo = e.ActorSource != null ? $" from actor {e.ActorSource.name}." : string.Empty;
if (UxrGlobalSettings.Instance.LogLevelWeapons >= UxrLogLevel.Relevant)
{
Debug.Log($"{UxrConstants.WeaponsModule}: Actor {e.ActorTarget.name} received {e.Damage} damage of type {e.DamageType}{sourceInfo}. Is dead? {e.Dies}.");
}
}
DamageReceived?.Invoke(this, e);
}
#endregion
#region Private Methods
/// <summary>
/// Updates all current projectiles.
/// </summary>
private void UpdateProjectiles()
{
for (int i = 0; i < _projectiles.Count; ++i)
{
if (_projectiles[i].Projectile == null)
{
// Dynamic instance removed somewhere
_projectiles.RemoveAt(i);
i--;
continue;
}
if (_projectiles[i].FirstFrame)
{
// Render first frame where the weapon is
_projectiles[i].FirstFrame = false;
continue;
}
Vector3 oldPos = _projectiles[i].ProjectileLastPosition;
Vector3 newPos = _projectiles[i].Projectile.transform.position;
Vector3 projectileForward = _projectiles[i].Projectile.transform.forward;
if (_projectiles[i].ShotDescriptor.UseAutomaticProjectileTrajectory)
{
newPos = oldPos + (_projectiles[i].ProjectileSpeed * Time.deltaTime * projectileForward);
_projectiles[i].Projectile.transform.position = newPos;
}
_projectiles[i].ProjectileLastPosition = newPos;
_projectiles[i].ProjectileDistanceTravelled += Vector3.Distance(oldPos, newPos);
if (_projectiles[i].ProjectileDistanceTravelled >= _projectiles[i].ShotDescriptor.ProjectileMaxDistance)
{
// Max distance travelled
Destroy(_projectiles[i].Projectile);
_projectiles.RemoveAt(i);
i--;
continue;
}
if (_projectiles[i].ShotDescriptor.UseAutomaticProjectileTrajectory)
{
float rayLength = Vector3.Distance(oldPos, newPos + projectileForward * _projectiles[i].ShotDescriptor.ProjectileLength);
if (Physics.Raycast(oldPos, projectileForward, out RaycastHit raycastHit, rayLength, _projectiles[i].ShotLayerMask, QueryTriggerInteraction.Ignore))
{
UxrProjectileDeflect projectileDeflect = raycastHit.collider.GetComponentInParent<UxrProjectileDeflect>();
// Has UxrProjectileDeflect component?
if (projectileDeflect != null)
{
Vector3 newForward = Vector3.Reflect(projectileForward, raycastHit.normal).normalized;
_projectiles[i].Projectile.transform.SetPositionAndRotation(raycastHit.point + newForward * 0.05f, Quaternion.LookRotation(newForward)); // + rayLength - raycastHit.distance));
_projectiles[i].ProjectileDeflectSource = projectileDeflect;
projectileDeflect.AudioDeflect?.Play(raycastHit.point);
if (projectileDeflect.DecalOnReflect != null)
{
// Generate decal?
UxrImpactDecal.CheckCreateDecal(raycastHit,
-1,
projectileDeflect.DecalOnReflect,
projectileDeflect.DecalLife,
projectileDeflect.DecalFadeoutDuration,
projectileDeflect.TwoSidedDecal,
projectileDeflect.TwoSidedDecalThickness);
}
_projectiles[i].AddShotLayerMask(projectileDeflect.CollideLayersAddOnReflect.value);
_projectiles[i].ProjectileLastPosition = _projectiles[i].Projectile.transform.position;
projectileDeflect.RaiseProjectileDeflected(new UxrDeflectEventArgs(_projectiles[i].ProjectileSource, raycastHit, newForward));
}
else
{
UxrActor targetActor = raycastHit.collider.GetComponentInParent<UxrActor>();
if (targetActor != null)
{
// Impact with an actor.
float normalizedDistance = _projectiles[i].ProjectileDistanceTravelled / _projectiles[i].ShotDescriptor.ProjectileMaxDistance;
float damage = Mathf.Lerp(_projectiles[i].ShotDescriptor.ProjectileDamageNear, _projectiles[i].ShotDescriptor.ProjectileDamageFar, normalizedDistance);
if (_projectiles[i].ProjectileDeflectSource != null)
{
// Came from a shot deflected by a UxrProjectileDeflect
targetActor.ReceiveImpact(_projectiles[i].ProjectileDeflectSource.Owner, raycastHit, damage);
}
else
{
// Direct hit from a projectile
targetActor.ReceiveImpact(_projectiles[i].ProjectileSource.TryGetWeaponOwner(), raycastHit, damage);
}
}
else
{
// Impact with something that isn't an actor, probably the scenario.
if (_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpact)
{
Vector3 instantiateForward = Vector3.Reflect(projectileForward, raycastHit.normal).normalized;
GameObject newInstance = Instantiate(_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpact, raycastHit.point, Quaternion.LookRotation(instantiateForward));
if (_projectiles[i].ShotDescriptor.PrefabInstantiateOnImpactLife >= 0.0f)
{
Destroy(newInstance, _projectiles[i].ShotDescriptor.PrefabInstantiateOnImpactLife);
}
}
Rigidbody rigidbody = raycastHit.collider.GetComponentInParent<Rigidbody>();
if (rigidbody != null)
{
rigidbody.AddForceAtPosition(_projectiles[i].ProjectileSpeed * _projectiles[i].ShotDescriptor.ProjectileImpactForceMultiplier * projectileForward, raycastHit.transform.position);
}
UxrOverrideImpactDecal overrideDecal = raycastHit.collider.GetComponentInParent<UxrOverrideImpactDecal>();
// Generate decal?
UxrImpactDecal.CheckCreateDecal(raycastHit,
_projectiles[i].ShotDescriptor.CreateDecalLayerMask,
overrideDecal != null ? overrideDecal.DecalToUse : _projectiles[i].ShotDescriptor.PrefabScenarioImpactDecal,
_projectiles[i].ShotDescriptor.PrefabScenarioImpactDecalLife,
_projectiles[i].ShotDescriptor.DecalFadeoutDuration);
NonActorImpacted?.Invoke(_projectiles[i].ProjectileSource, new UxrNonDamagingImpactEventArgs(_projectiles[i].ProjectileSource.TryGetWeaponOwner(), _projectiles[i].ProjectileSource, raycastHit));
}
Destroy(_projectiles[i].Projectile);
_projectiles.RemoveAt(i);
i--;
}
}
}
}
}
#endregion
#region Private Types & Data
private readonly Dictionary<UxrActor, ActorInfo> _damageActors = new Dictionary<UxrActor, ActorInfo>();
private readonly List<ProjectileInfo> _projectiles = new List<ProjectileInfo>();
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7bd5b3cc3b4e328428572f39944e1803
timeCreated: 1535629434
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: