210 lines
5.8 KiB
C#
210 lines
5.8 KiB
C#
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// vp_Shell.cs
|
|
// © Opsive. All Rights Reserved.
|
|
// https://twitter.com/Opsive
|
|
// http://www.opsive.com
|
|
//
|
|
// description: a shell casing with rigidbody physics adapted for more
|
|
// realistic behavior
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
[RequireComponent(typeof(Rigidbody))]
|
|
[RequireComponent(typeof(CapsuleCollider))]
|
|
[RequireComponent(typeof(AudioSource))]
|
|
|
|
public class vp_Shell : MonoBehaviour
|
|
{
|
|
|
|
Transform m_Transform = null;
|
|
Rigidbody m_Rigidbody = null;
|
|
AudioSource m_Audio = null;
|
|
Collider m_Collider = null;
|
|
|
|
public float LifeTime = 10; // time to live in seconds for this type of shell
|
|
protected float m_RemoveTime = 0.0f; // the exact time of removal for this particular shell (calculated in Start)
|
|
|
|
// physics
|
|
public float m_Persistence = 1.0f; // chance of shell _not_ being removed after settling on the ground
|
|
public delegate void RestAngleFunc(); // function pointer for the chosen forced rest state
|
|
protected RestAngleFunc m_RestAngleFunc;
|
|
protected float m_RestTime = 0.0f; // after this many seconds a rest state will be forced (calculated in Start)
|
|
|
|
// sound
|
|
public List<AudioClip> m_BounceSounds = new List<AudioClip>(); // list of sounds to be randomly played on each ground impact
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
void Awake()
|
|
{
|
|
|
|
m_Transform = transform;
|
|
m_Rigidbody = GetComponent<Rigidbody>();
|
|
m_Audio = GetComponent<AudioSource>();
|
|
m_Collider = GetComponent<Collider>();
|
|
m_Audio.playOnAwake = false;
|
|
m_Audio.dopplerLevel = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
void OnEnable()
|
|
{
|
|
|
|
m_RestAngleFunc = null;
|
|
|
|
m_RemoveTime = Time.time + LifeTime;
|
|
m_RestTime = Time.time + (LifeTime * 0.25f);
|
|
|
|
m_Rigidbody.maxAngularVelocity = 100; // allow shells to spin faster than rigidbody default
|
|
m_Rigidbody.velocity = Vector3.zero;
|
|
m_Rigidbody.angularVelocity = Vector3.zero;
|
|
m_Rigidbody.constraints = RigidbodyConstraints.None;
|
|
|
|
if(m_Collider != null)
|
|
m_Collider.enabled = true;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
void Update()
|
|
{
|
|
|
|
if (m_RestAngleFunc == null)
|
|
{
|
|
// after a while we decide how the shell should come to rest.
|
|
// see comments on 'DecideRestAngle' for details.
|
|
if (Time.time > m_RestTime)
|
|
DecideRestAngle();
|
|
}
|
|
else
|
|
m_RestAngleFunc();
|
|
|
|
if (Time.time > m_RemoveTime)
|
|
{
|
|
m_Transform.localScale = Vector3.Lerp(m_Transform.localScale, Vector3.zero, (Time.deltaTime * 60.0f) * 0.2f);
|
|
if (Time.time > m_RemoveTime + 0.5f)
|
|
vp_Utility.Destroy(gameObject);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// modifies the shell's behavior upon a hard impact, and
|
|
/// on soft impact determines whether to remove it early.
|
|
/// this is an optional optimization feature for weapons
|
|
/// that emit large amounts of shells.
|
|
/// </summary>
|
|
void OnCollisionEnter(Collision collision)
|
|
{
|
|
|
|
// if collision velocity is sufficient, make a 'hard bounce'
|
|
if (collision.relativeVelocity.magnitude > 2)
|
|
{
|
|
|
|
// on a hard bounce, we apply more random rotation velocity to make
|
|
// the shell behave a bit unpredictably, like real brass shells do
|
|
if (Random.value > 0.5f)
|
|
m_Rigidbody.AddRelativeTorque(-Random.rotation.eulerAngles * 0.15f);
|
|
else
|
|
m_Rigidbody.AddRelativeTorque(Random.rotation.eulerAngles * 0.15f);
|
|
|
|
// also, we play a random bounce sound
|
|
if (m_Audio != null && m_BounceSounds.Count > 0)
|
|
{
|
|
m_Audio.pitch = Time.timeScale;
|
|
m_Audio.PlayOneShot(m_BounceSounds[(int)Random.Range(0, (m_BounceSounds.Count))]);
|
|
}
|
|
|
|
}
|
|
// soft collision = time to determine if this shell lives or dies
|
|
else if (Random.value > m_Persistence)
|
|
{
|
|
// allow this shell to fall through geometry and
|
|
// remove it after half a second
|
|
if (m_Collider != null)
|
|
m_Collider.enabled = false;
|
|
m_RemoveTime = Time.time + 0.5f;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// by default a rigidbody will not come to rest in a
|
|
/// manner reminiscent of a brass shell, no matter what
|
|
/// physics material you have set on it. this method
|
|
/// determines if the shell should rest in its upright or
|
|
/// tipped over angle, for more realistic shell motion.
|
|
/// </summary>
|
|
protected void DecideRestAngle()
|
|
{
|
|
|
|
float up = Mathf.Abs(m_Transform.eulerAngles.x - 270); // see how close shell is to its upright position
|
|
|
|
// if shell is close to standing up with its (heavier) base
|
|
// toward the ground
|
|
if (up < 55)
|
|
{
|
|
|
|
// see if the ground is flat
|
|
Ray ray = new Ray(m_Transform.position, Vector3.down);
|
|
RaycastHit hit;
|
|
if (Physics.Raycast(ray, out hit, 1))
|
|
{
|
|
// if so, we will force the shell to its upright position
|
|
if (hit.normal == Vector3.up)
|
|
{
|
|
m_RestAngleFunc = UpRight;
|
|
m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// either the shell is fairly tilted or the ground is not flat,
|
|
// so we will force the shell to lie down
|
|
m_RestAngleFunc = TippedOver;
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// quickly rotates the shell to an upright position
|
|
/// </summary>
|
|
protected void UpRight()
|
|
{
|
|
m_Transform.rotation = Quaternion.Lerp(m_Transform.rotation,
|
|
Quaternion.Euler(-90, m_Transform.rotation.y, m_Transform.rotation.z),
|
|
Time.time * ((Time.deltaTime * 60.0f) * 0.05f));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// smoothly rotates the shell to a lying down position
|
|
/// </summary>
|
|
protected void TippedOver()
|
|
{
|
|
m_Transform.localRotation = Quaternion.Lerp(m_Transform.localRotation,
|
|
Quaternion.Euler(0, m_Transform.localEulerAngles.y, m_Transform.localEulerAngles.z),
|
|
Time.time * ((Time.deltaTime * 60.0f) * 0.005f));
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|