Alright, I’ve tested this out myself and it works just fine. Fun note: The 65 force threshold I gave earlier was incorrect it’s more like 78 – 95 that misses on collision. I haven’t used this for projectiles since Unity 3, so it’s clear UT have made some very nice improvements here.
Let’s start with the setup. (Remember, let me know if you want the project file so you can test it out yourself.)
Scripts:
BowHandler.cs (This will be attached to the bow you are shooting the arrow from)
ArrowHandler.cs (This will be attached to the arrow that is being shot)
In your Scene:
BowWeapon [GameObject] [BowHandler]
Pt_arrow [GameObject]
Arrow [GameObject] [ArrowHandler] (make this use the same position/rotation as pt_arrow)
Prefabs:
Arrow.prefab (this is the prefab of the arrow that’s in the scene. It will get instantiated where the old one was after firing)
Bow Handler
using UnityEngine;
using System.Collections;
public class BowHandler : MonoBehaviour {
public GameObject fabArrow; //Reference to the arrow prefab
public GameObject gArrow; //Reference to the initial arrow. Note: You need to have an arrow on the bow already at start.
public Transform pt_arrow; //Point on the bow to create the new arrow after firing.
public float ArrowForce; //Force to be applied to the arrow when firing
// Update is called once per frame
void Update () {
//Simple input code to get a keypress. Change this to whatever key you like
if(Input.GetKeyDown(KeyCode.LeftControl))
{
FireArrow();
}
}
public void FireArrow()
{
//Anytime you use a public variable, or any variable really, you should
//always make sure the variable has been set before attempting to use it.
//This will avoid infuriating errors. Alternatively, you may want to send
//some information to the output log if it is null so that you can see what
//happened later.
if(fabArrow != null && gArrow != null)
{
//Get the rigidbody component of the arrow.
Rigidbody rArrow = gArrow.GetComponent<Rigidbody>();
if (rArrow != null)
{
//Setting isKinematic to false will allow physics to be applied to
//the arrow.
rArrow.isKinematic = false;
//Grab a reference to the arrow handler.
ArrowHandler aHand = gArrow.GetComponent<ArrowHandler>();
if(aHand != null)
{
//Set the arrow handler to active since we are now firing
aHand.IsActive = true;
}
//This command will add velocity to a rigidbody using a vector
//For our situation, we want the arrow to fly forward, so we multiply
//the arrow's forward facing vector times the force amount.
rArrow.AddForce(pt_arrow.forward * ArrowForce, ForceMode.VelocityChange);
//ForceMode.Impulse is just one of several force modes. You might want to play around with those
//to see if you can get better results with different options.
//Now we recreate a new arrow on the old arrow spot. This should be placed in your reload function
// if you have one.
gArrow = (GameObject)Instantiate(fabArrow, pt_arrow.position, pt_arrow.rotation);
}
else
{
Debug.Log("Bow Error! There is no rigidbody attached to the arrow object! Sent from: " + gArrow.name);
}
}
else
{
//If it is null, then we need to have a little chat with ourselves.
Debug.Log("Bow Error! You are trying to instantiate an object without a reference! Sent from: " + gameObject.name);
//I usually like to toss in the name of the object that throws the error just so I have a more focused search area
// when trying to identify a problem.
}
}
}
Arrow Handler
using UnityEngine;
using System.Collections;
public class ArrowHandler : MonoBehaviour {
public float Lifetime; //The float amount of seconds this arrow should remain in play before being destroyed
public float DestroyDelay; //The delay after this object hits something before destroying it
private TimerVar _lifeTimer; //If the arrow doesn't hit anything we need to destroy it after a time to save performance.
private TimerVar _deathTimer; //Timer for when to destroy if the arrow hits another object while in flight.
private TimerVar _colTimer; //We need this small timer to turn on the box collider for this object after firing.
private bool _hitSomething; //Has this arrow hit an object? Used to destroy the arrow after a time.
public bool IsActive; //Has this arrow been fired? Used to start lifetime calculations.
// Use this for initialization
void Start () {
//Set up our timers.
_lifeTimer = new TimerVar(Lifetime, false);
_deathTimer = new TimerVar(DestroyDelay, false);
}
// Update is called once per frame
void Update () {
//Don't do any calculations until after the arrow has been fired.
if (!IsActive)
return;
if(_hitSomething)
{
//Has this arrow hit something?
//If yes, start the death routine
if(_deathTimer.TimerDone)
{
//Check to see if the destroy delay is done.
//If yes, destroy the object.
Destroy(gameObject);
}
else
{
//If not, keep counting down.
_deathTimer.TimerUpdate();
}
}
else
{
//If not, keep checking the lifetime info
if(_lifeTimer.TimerDone)
{
//Has this object exceeded it's allotted life time?
//If yes, we destroy it.
Destroy(gameObject);
}
else
{
//If no, we keep counting down.
_lifeTimer.TimerUpdate();
}
}
}
void OnCollisionEnter(Collision col)
{
//This function must be on the gameObject that has the box collider attached to it.
_hitSomething = true;
}
}
I’ve tested this with force range from 10 – 70 and it works just fine. You can adjust the arrow speed from the bow and you can adjust the lifetime/deathtime of the arrow from the arrow in the inspector.
One last thing. You’ll notice I have a variable in the ArrowHandler called TimerVar.cs. This is the timer I talked about in my first post. My version is probably a little overkill for this scenario, but it does give you a lot of control for use later if you need it. (Better coders, feel free to let me know if this is poorly optimized. I did write it several years ago)
using UnityEngine;
using System.Collections;
//*********************************************************************************//
// Class: TimerVar
// Created By: Chuck (McMayhem) McMackin
//*********************************************************************************//
// The Timer Var class is used as a simple timer for anyting that requires timed
// activation or intervals. This is used to replace the general means of timers which
// can cause convoluted code and poor management. The timer var must be updated in the
// update function of any script it is attached to. For this reason, it can only be
// placed in MonoBehavior-based scripts.
//*********************************************************************************//
[System.Serializable]
public class TimerVar
{
public float TimerSet;
public bool TimerDone;
public bool Looping;
public float TimerSpeed;
public float CurTime;
public float Percent;
private float _timer;
public TimerVar(float tSet, bool loop)
{
TimerSet = tSet;
Looping = loop;
TimerDone = false;
_timer = TimerSet;
}
public TimerVar(float tSet, float tSpeed, bool loop)
{
TimerSet = tSet;
TimerSpeed = tSpeed;
Looping = loop;
TimerDone = false;
_timer = TimerSet;
}
public void TimerUpdate()
{
if (_timer > 0)
{
if (TimerSpeed > 0)
{
_timer -= Time.deltaTime * TimerSpeed;
}
else
{
_timer -= Time.deltaTime;
}
}
if (_timer < 0)
{
_timer = 0;
}
CurTime = _timer;
Percent = (CurTime / TimerSet) * 100;
if (_timer == 0)
{
TimerDone = true;
if (Looping)
{
Reset();
}
}
}
public void Reset()
{
Percent = 0;
TimerDone = false;
_timer = TimerSet;
}
public void Reset(float num)
{
TimerSet = num;
TimerDone = false;
_timer = TimerSet;
}
public void Reset(float num, float speed)
{
TimerDone = false;
_timer = num;
TimerSpeed = speed;
}
}
Hope this helps. Let me know if you need more clarification.