- Home /
Projectile not firing right - simple angle and velocity problem?
Hi guys
I am having some trouble getting a projectile to fire how I am after. What I have is a crosshair that remains stationary in the center of the screen where the projectile will fire through (firePosition). By using the mouse, they can drag to an x,y point in the screen with a z position about 3 behind the crosshair (handPosition). Then when they fire, the projectile will go from the mouse position and through the crosshair using the 2 different positions to work out the angle of the shot.
using UnityEngine;
using System.Collections;
public class ProjectileFire : MonoBehaviour {
public float shootForce;
public Transform target;
Vector3 firePosition;
Vector3 handPosition;
public GameObject projectilePrefab;
// Use this for initialization
void Start () {
firePosition = new Vector3(0,3,0);
}
// Update is called once per frame
void Update () {
transform.LookAt(target);
if (Input.GetMouseButtonDown(0))
{
}
if (Input.GetMouseButtonUp(0))
{
handPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x,Input.mousePosition.y,3.0f));
Instantiate (projectilePrefab,handPosition,Quaternion.identity);
projectilePrefab.rigidbody.AddForce(transform.forward * shootForce);
}
}
}
Any suggestions? Thanks
Answer by aldonaletto · Oct 24, 2011 at 02:21 PM
There are some errors in the logic. You must get firePosition using ViewportToWorldPoint with (0.5, 0.5, hand distance + 3), and handPosition with (mouse pos X, mouse pos Y, hand distance), where hand distance is the distance from camera to hand (variable distFromCam added to the code to define this distance).
While the mouse button is pressed, calculate both, handPosition and firePosition, and when the mouse is released create and shoot the projectile - you must calculate the rotation to create the projectile in the shooting direction. Get a reference to the projectile instantiated and use it to add the shooting force in its forward direction:
... public GameObject projectilePrefab; public float distFromCam = 2.0f; // distance from camera to hand
void Update () {
transform.LookAt(target);
if (Input.GetMouseButton(0)) // aim while the mouse button is down
{
firePosition = Camera.main.ViewportToWorldPoint (new Vector3(0.5f, 0.5f, distFromCam+3.0f)); // 3D point at screen center, 3 units ahead hand
handPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, distFromCam));
// draw a debug line just to see what's happening (click Gizmos to see the line)
Debug.DrawLine(handPosition, firePosition, Color.green);
}
if (Input.GetMouseButtonUp(0)) // shoot in last aimed direction when button released
{
Vector3 projDir = firePosition - handPosition; // calculate the projectile direction
// calculate rotation to turn projectile's forward direction to the shooting direction
Quaternion rot = Quaternion.FromToRotation(Vector3.forward, projDir);
// create the projectile in a variable to access its rigidbody...
GameObject projectile = Instantiate (projectilePrefab, handPosition, rot);
// then add force in the projectile's forward direction
projectile.rigidbody.AddForce(projectile.transform.forward * shootForce);
Destroy(projectile, 10); // destroy projectile after 10 seconds
}
}
EDITED: I think you can recycle the projectile forever: create the projectile only once, then bring it to the shooting position when firing again - but you must make the rigidbody forget about the past, or it will continue doing whatever it was doing before (rotating, moving to the old direction, etc.) I suppose it can be done by zeroing the rigidbody velocities.
This is the ecologically correct version of the script above - the projectile is created once and recycled each time you shoot:
... public GameObject projectilePrefab; public float distFromCam = 2.0f; // distance from camera to hand private GameObject projectile; // current projectile
void Update () {
transform.LookAt(target);
if (Input.GetMouseButton(0)) // aim while the mouse button is down
{
firePosition = Camera.main.ViewportToWorldPoint (new Vector3(0.5f, 0.5f, distFromCam+3.0f)); // 3D point at screen center, 3 units ahead hand
handPosition = Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, distFromCam));
// draw a debug line just to see what's happening (click Gizmos to see the line)
Debug.DrawLine(handPosition, firePosition, Color.green);
}
if (Input.GetMouseButtonUp(0)) // shoot in last aimed direction when button released
{
Vector3 projDir = firePosition - handPosition; // calculate the projectile direction
// calculate rotation to turn projectile's forward direction to the shooting direction
Quaternion rot = Quaternion.FromToRotation(Vector3.forward, projDir);
// if projectile doesn't still exist, create it
if (!projectile) projectile = Instantiate(projectilePrefab);
// make sure the projectile is active:
projectile.SetActiveRecursively(true);
// bring it to the handPosition and set its rotation
projectile.transform.position = handPosition;
projectile.transform.rotation = rot;
// make rigidbody forget the past by zeroing its velocities
projectile.rigidbody.velocity = Vector3.zero;
projectile.rigidbody.angularVelocity = Vector3.zero;
// then add force in the shooting direction
projectile.rigidbody.AddForce(projDir * shootForce);
}
} The projectile script usually have code to destroy the projectile when it hits something - for instance:
void OnCollisionEnter(Collision col){ // do the usual projectile stuff - check what was hit, apply damage etc. // and finally destroy the projectile: Destroy(gameObject); }But if you are recycling the projectile, you must change the code to deactivate it instead:
void OnCollisionEnter(Collision col){ // do the projectile stuff... // and deactivate the projectile: gameObject.SetActiveRecursively(false); }
Answer by 8r3nd4n · Oct 25, 2011 at 01:00 AM
Thanks for the quick response, I had a feeling my logic was off but didnt know howso. ha.
I tried your script but the projectile is still not firing at the angle that the debug line is showing. It comes out of the point of the line (hand position) but does not take into consideration the angle I think. I have tried moving a lot of things around but its still not working as it should. Just so Im clear on things, the script should be attached to an empty gameObject? or should it be on the camera? on those, my empty GO projectile is at 0, 2.5, 0 and camera at 0, 3.2, -8.5 if that helps.
You can add this script to the camera, or to an empty object.
The problem is an error in the last line - it uses transform.forward, but it should be projectile.transform.forward, like this:
projectile.rigidbody.AddForce(projectile.transform.forward * shootForce);
I fixed the answer.
NOTE: An alternative - maybe a better one - would be to use projDir ins$$anonymous$$d:
projectile.rigidbody.AddForce(projDir * shootForce);
Thanks, I managed to get it all working right with some more playing around and by using projectile.rigidbody.AddRelativeForce(transform.forward * shootForce); ins$$anonymous$$d.
I do have another question relating to the script. Is it possible to use the same object over and over again without having to instantiate a prefab? Basically, the more clones that are on screen are starting to impact on performance and I want the player to only have the one projectile at a time to play with. I though about using transform.translate on the object whenever the mouse is clicked but it does not let me add the rotation values. Any suggestions? Thanks
I edited my answer and included this projectile recycling idea. The usual way is to destroy the projectile after some time, or when it hits something. I included code to auto destroy the projectile in the Instantiate version too, and showed in the end how projectiles are destroyed when something is hit - in both versions.