Hi guys!
I’m still new to Unity and trying to use the XR Toolkit in combination with bow and arrow gameplay.
I have created an Arrow prefab that consists of a derived XRGrabable script that handles stuff like collision detection, its flight and physics, etc. It also contains variables like speed or damage.
The rest of the prefab contains meshes for the arrow itself, the tip, a collider for picking it up and a transform to attach it to sockets like the hand or bow.
Now I want to have an easy way to introduce new arrows to the game, that also have some effects like fire and particle effects and/or different meshes.
My first idea was to introduce a scriptable object that contains the values, meshes and effects. Then I would create different arrow-scripts derived from the scriptable object and assign the parameters during the instantiation of a new arrow from the quiver. To save me some headaches, I would like to hear how you guys would implement different arrow / projectile types with different behaviors and meshes, are there best practices to do it?
public class Arrow : XRGrabInteractable
{
public float speed = 2000.0f;
public float damage = 20.0f;
public Transform tip = null; // For Linecast
public BoxCollider boxCollider = null;
private bool inAir = false;
private Vector3 lastPosition = Vector3.zero;
private Rigidbody rb = null;
protected override void Awake()
{
base.Awake();
rb = GetComponent<Rigidbody>();
movementType = MovementType.VelocityTracking;
}
private void FixedUpdate()
{
if (inAir)
{
CheckForCollision();
lastPosition = tip.transform.position;
}
}
private void CheckForCollision()
{
RaycastHit hitInfo;
if (Physics.Linecast(lastPosition, tip.position, out hitInfo))
{
Debug.Log("Arrow hit:" + hitInfo.collider.gameObject.name);
// TODO: Hit of enemies and objects, deal damage and effects
if (hitInfo.transform.TryGetComponent(out Rigidbody body))
{
rb.interpolation = RigidbodyInterpolation.None;
transform.parent = hitInfo.transform;
body.AddForce(rb.velocity, ForceMode.Impulse);
}
Stop();
}
}
private void Stop()
{
inAir = false;
SetPhysics(false);
}
public void Release(float pullValue)
{
inAir = true;
SetPhysics(true);
MaskAndFire(pullValue);
StartCoroutine(RotateWithVelocity());
lastPosition = tip.position;
}
private void SetPhysics(bool usePhysics)
{
rb.isKinematic = !usePhysics;
rb.useGravity = usePhysics;
}
private void MaskAndFire(float power)
{
colliders[0].enabled = false;
interactionLayerMask = 1 << LayerMask.NameToLayer("Ignore");
Vector3 force = transform.forward * (power * speed);
rb.AddForce(force);
}
private IEnumerator RotateWithVelocity()
{
yield return new WaitForFixedUpdate();
// Rotate tip towards moving direction
while (inAir)
{
Quaternion newRotation = Quaternion.LookRotation(rb.velocity, transform.up);
transform.rotation = newRotation;
yield return null;
}
}
private void SetColliders(bool status)
{
foreach (var c in gameObject.GetComponentsInChildren<Collider>())
{
Debug.Log("Collider status:" + status.ToString());
if (!c.isTrigger) c.enabled = status;
}
}
public new void OnSelectEntering(XRBaseInteractor interactor)
{
base.OnSelectEntering(interactor);
}
public new void OnSelectEntered(XRBaseInteractor interactor)
{
base.OnSelectEntered(interactor);
}
public new void OnSelectExited(XRBaseInteractor interactor)
{
base.OnSelectExited(interactor);
}
public new void OnSelectExiting(XRBaseInteractor interactor)
{
base.OnSelectExiting(interactor);
}
}