- Home /
How to add slight angle/rotation variation to transform.forward?
Right now, when shooting my gun, I'm using if (Physics.Raycast(shotOrigin.position, shotOrigin.forward, out hit, 1000, layerMask))
but I want to add a slight random variation so the bullet isn't always shooting perfectly forward in exactly the same place each time (I want little bit of up/down and left/right variety), and I just can't figure out the correct way to write any code to do this.
Any suggestions?
Answer by eses · Sep 20, 2018 at 05:32 PM
Hi @impurekind
You could first create your spread effect in world space.
Create a vector forward in world space, which has some offset in it's x and y:
Vector3 dir = new Vector3(Random.Range(-offset,offset),Random.Range(-offset,offset),1f);
When you have this, this should be transformed from the "drawing board" to something useful, i.e. to your objects local space (= weapon's tip or whatever).
@Bunny83 has listed plenty of alternatives for this kind of scenarios here:
https://answers.unity.com/questions/411764/converting-between-local-and-global-direction-1.html
Then you can do something like this, where transform is your weapon tip, pointing towards target or whatever it is:
Vector3 sprayDir = transform.TransformVector(dir);
This technique can be used in many similar scenarios. And this way, the spray works like a flashlight, it will spread to a direction you point it at...
Well, I have this right now:
Quaternion shotAngle = Quaternion.identity;
for (int i = 0; i<numberOfBullets; i++)
{
shotAngle.eulerAngles = new Vector3(Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f), 0);
if (Physics.Raycast(shotOrigin.position, shotAngle * shotOrigin.forward, out hit, 1000, layer$$anonymous$$ask))
{
//Do stuff
}
Which is mostly easy for me to grasp, and it mostly works, but it's not quite perfect because depending on the direction the player is facing, the "random" can be more of a straight line of bullets spread in either the x or y direction than properly random in both x and y (regardless of where I'm looking) for some reason I'm not clear on. $$anonymous$$ight be something related to .forward, but I don't really know how to get around that.
I'm getting quite confused with all the various ways everyone's suggesting, because I don't understand a lot of the code involved.
How would you go about fixing what I have already so it works fully (if you could properly write it out as the actual working code then that would be ideal)?
@impurekind
Try to re-read what i said. I'm not going to convince you more if it works or not... The point is to first create a vector in default orientation (with scatter) then rotate it so that former world z-axis will point towards weapons forward. This way you are not going to run into issue you describe.
I've put good deal of hours to learning vectors, rotations and such even though I'm not a math guy at all. I didn't get such hints as you have gotten now from several people... so...I'm not going to do the coding for you, I gave you already all the operations and code you need.
To me it sounds like you didn't put hours (or days) into understanding the issue you have. Seems like you have to do a lot more learning with vectors and transformations, reading doesn't help. Start with simple test cases. Create different vectors. Find examples. Read some math pages. Spawn cubes on your created Vector positions, and so on... put hours to testing and learning and that way you'll eventually figure out how Vectors work, and you don't have to just guess. Also, read all the links you got here, they are very helpful.
I read it all (all the responses, all the links, everything), but I just don't understand the correct sequence of events--lines of code--that lead to where it's trying to go in your example and how that's inserted into my existing code so it will work properly. And I've already spent over a week on this, trying to make some bullets fire in a direction with a bit of a spread, and still haven't figured it out, which I think is way too much wasted time when I know it's literally something that would probably take someone in here $$anonymous$$utes to figure out and do.
And all I'm really looking for in this help forum is that: For someone to see what I'm stuck on and provide the solution that I can use now (and stop me completely wasting more time needlessly), learn from the example through actual use, and then in the future be able to understand and use it whenever a similar problem arises again.
Some people learn differently to others. I genuinely don't learn by trying stuff myself without understanding it and just failing over and over. I learn by watching someone who knows how to do it right show me the correct way, and I use that, study that until I understand why it's correct, and then I go from there.
Answer by Shallow3 · Sep 20, 2018 at 01:19 PM
Use the Unity Ray system. Most of the time you do not want to use a transform's position as the ray.
References:https://docs.unity3d.com/ScriptReference/Ray.html
I don't get what you mean? I'm using the shotOrigin.position and shotOrigin.forward just to get the ray to start from the end of the barrel of the gun and shoot out forward from there relative to the object's forward direction. Why is that wrong?
But what I really need here is a way to give the direction that ray shoots out in a little bit of random variance in both the x and y axis, such that my shotgun will shoot a nice little spread regardless of where I'm pointing the gun.
Any suggestions?
Right now I'm doing the following based on some other suggestions and my own searching around:
Quaternion shotAngle = Quaternion.identity;
for (int i = 0; i<numberOfBullets; i++)
{
shotAngle.eulerAngles = new Vector3(Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f), 0);
if (Physics.Raycast(shotOrigin.position, shotAngle * shotOrigin.forward, out hit, 1000, layer$$anonymous$$ask))
{
//Do stuff
}
But it's not quite perfect because depending on the direction the player is facing, the "random" can be more of a straight line of bullets spread in either the x or y direction than properly random in both x and y (regardless of where I'm looking) for some reason I'm not clear on. $$anonymous$$ight be something related to .forward, but I don't really know how to get around that.
Answer by LCStark · Sep 20, 2018 at 03:32 PM
Check this out: Random.insideUnitCircle.
Interesting. Will this work for a 3D fps game? And will that actually set the angle of the shot or just the position it starts at? Because it needs to be the angle or else the bullets will all still just fire in a straight line from/towards that end random point, which means if the random difference is small near the gun it's going to be just as small far away in distance, and that's not quite how shotgun pellets work. The shots need to move further and further apart at they travel forwards, so when you shoot someone with the shotgun who's further away there will be a smaller chance that all the pellets with actually hit them, which is why it's the random angle I'm trying to figure out.
I don't see why not. You'd only need to rotate the vector to match your current direction. isideUnitCircle
returns a Vector2
, so it fits perfectly when you're looking in the Vector3.back
direction. Something like Quaternion.FromToRotation(Vector3.back, currentDirection) * (Vector3)Random.insideUnitCircle
should work.
Regarding the edited part: that depends on your implementation. You wrote that you use Physics.Raycast
. That uses a direction as the second parameter. You add that Random.insideUnitCircle
part to the direction, not to any position, so the angle should be O$$anonymous$$.
Answer by Bunny83 · Sep 20, 2018 at 04:40 PM
I think you're looking for my GetPointOnUnitSphereCap method. Note the second edit.
edit
Just copy those two methods to your class:
public static Vector3 GetPointOnUnitSphereCap(Quaternion targetDirection, float angle)
{
var angleInRad = Random.Range(0.0f,angle) * Mathf.Deg2Rad;
var PointOnCircle = (Random.insideUnitCircle.normalized)*Mathf.Sin(angleInRad);
var V = Vector3(PointOnCircle.x,PointOnCircle.y,Mathf.Cos(angleInRad));
return targetDirection*V;
}
public static Vector3 GetPointOnUnitSphereCap(Vector3 targetDirection, float angle)
{
return GetPointOnUnitSphereCap(Quaternion.LookRotation(targetDirection), angle);
}
Instead of shotOrigin.forward
you can use
GetPointOnUnitSphereCap(shotOrigin.forward, 5)
which returns a vector in the same direction as "shotOrigin.forward" but with a random variance of 5° Note that a value of 180° would mean you get a completely random direction. So keep in mind that the cone angle is twice the angle- So if you pass 45 as angle the cone would have a 90° angle at the tip.
I expect it might indeed work, but it's a bit too complicated for me to grasp what I need to use/do from your example at my beginner level right now. :-o
Unless you want to show me how I'd actually put it into my script stuff above specifically to get it to work?
Just tried it, putting the main stuff at the top of my script (where I normally put any variables I'm setting up) and the last part inside the raycast itself. With the main stuff I'm getting an error on the var V = Vector3(PointOnCircle.x, PointOnCircle.y, $$anonymous$$athf.Cos(angleInRad));
that says "Non-invocable member 'Vector3' cannot be used like a method".
I'll need to fix that before I can test it. Do you know what the issue is and how to fix it?
Also, is this written in Java (I usually don't see the use of stuff like "var" for declaring variables in C# scripts), because I need it to be C# obviously.
Answer by Aske_Johansen · Sep 21, 2018 at 12:38 AM
Here is a slightly inelegant but easy way to implement some bullet spread.
//This variable should probably be public, so that you easily can change it. It should probably also be pretty small
float maxSpread = 0.2f;
//We calculate the shots divergence from the original trajectory and put it into a vector.
float xSpread = Random.RandomRange(-maxSpread, maxSpread);
float ySpread = Random.RandomRange(-maxSpread, maxSpread);
float zSpread = Random.RandomRange(-maxSpread, maxSpread);
Vector3 spreadVector = new Vector3(xSpread, ySpread, zSpread);
//Here we combine the spread with the shooting direction and normalize it, so that it has the length/magnitude 1.
Vector3 shootingDirection = (spreadVector + shotOrigin.forward).normalized;
//Then you just call your original raycast like this
Physics.Raycast(shotOrigin.position, shootingDirection, out hit, 1000, layerMask)
Cool. This seems easy and clear enough for me to understand and implement. I'll give that a try tomorrow and see how it works out. Cheers.
O$$anonymous$$, your solution technically works, but it's suffering from the same issue the previous solution I came up with was/is suffering from too: Depending on the direction the player is facing, the spread ends up being more or less a straight line rather than a full spread. I think it's to do with the closer to the actual x or y origin you are facing/ai$$anonymous$$g, probably in world space, the less effect it has (I'm not using the z axis here). It seems it works great so long as I'm not pointing close to 0,0 in one or more of the axis, but the line effect happens and is more pronounced the closer I approach facing/pointing towards 0,0 in any of the axis.
Any idea how to get around that?
Here's the version I had myself for reference:
Quaternion shotAngle = Quaternion.identity;
for (int i = 0; i<numberOfBullets; i++)
{
shotAngle.eulerAngles = new Vector3(Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f), 0);
if (Physics.Raycast(shotOrigin.position, shotAngle * shotOrigin.forward, out hit, 1000, layer$$anonymous$$ask))
{
//Do stuff
}
And here's how I did it with your code:
float maxSpread = 0.2f;
float xSpread = Random.Range(-maxSpread, maxSpread);
float ySpread = Random.Range(-maxSpread, maxSpread);
float zSpread = Random.Range(0.0f, 0.0f);
Vector3 spreadVector = new Vector3(xSpread, ySpread, zSpread);
Vector3 shootingDirection = (spreadVector + shotOrigin.forward);
for (int i = 0; i<numberOfBullets; i++)
{
if (Physics.Raycast(shotOrigin.position, shootingDirection, out hit, 1000, layer$$anonymous$$ask))
{
//Do stuff
}
O$$anonymous$$, thanks for your help. I've now managed to find a really simple method that's both easy for me to understand and does exactly what I need:
Vector3 dir;
for (int i = 0; i < numberOfBullets; i++)
{
Vector3 dir = new Vector3(Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f), Random.Range(-5.0f, 5.0f)); // Offset created in world space
Vector3 sprayDir = transform.TransformVector(dir); // Offset converted to local space
if (Physics.Raycast(shotOrigin.position, shotOrigin.forward + sprayDir, out hit, 1000, layer$$anonymous$$ask))
{
DoShotStuff();
}
}
But your feedback and suggestion was much appreciated. Thanks. :)