A simple tutorial: Dynamic bullet spread
For this baby-mod, I wanted to add a small element of strategy into using the rifle by adding a small amount of spread when still and a larger amount of spread when moving. Adding universal spread is easy, but there is a typo in the source which prevents it from working. Let's fix that here in Rifle.lua:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->//add a base spread value as a class variable
Rifle.spread = 0.1
//fix the typos by changing 'spread' to 'self.spread'
// in function Rifle:FireBullets(player)
local xSpread = ((NetworkRandom() * 2 * self.spread) - self.spread) + ((NetworkRandom() * 2 * self.spread) - self.spread)
local ySpread = ((NetworkRandom() * 2 * self.spread) - self.spread) + ((NetworkRandom() * 2 * self.spread) - self.spread)<!--c2--></div><!--ec2-->
Now we have working spread. I recommend values between 0.1 - 0.2. But now we want to make it spread even more only when the player is moving. There are many ways to accomplish this, but we need to know two more things for sure:
1) how much more spread when moving?
2) is the player moving?
Different guns may have different movement spread penalties, so I chose to track the moving spread in a Rifle class variable in Rifle.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Rifle.moveSpread = 0.03<!--c2--></div><!--ec2-->
Now we need to know when the player is moving so we can adjust the spread formula. In the Player class, there is a networkvar 'velocity' that stores an X, Y, and Z value. We can check that when shooting to determine if the player is moving. I could do all kinds of checks in the Rifle:FireBullet method, but lets turn all that into a simple function in the Player class by adding:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function Player:IsMoving()
if self.velocity.x > 0.1 or self.velocity.y > 0.1 or self.velocity.z > 0.1 then
return true
else
return false
end
end<!--c2--></div><!--ec2-->
So this function returns true if the player is moving and false if the player is not. Note that I am not checking against zero. After much bug hunting, I learned that testing floating point values against zero only works before they are set above zero. After they they fall to an infinitesimally small number (like .002^10-e) but never back to zero. So I added a little tolerance to account for this fact.
Now we can edit the bullet spread parts of Rifle:FireBullets(player):
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> if (self.spread > 0) then //this if-statement can be removed since we know we have spread
//create a new value to hold the spread, start with base spread
local tempSpread = self.spread
// ask the Player class it is is moving, and if so, add the movement penalty
if player:IsMoving() then
tempSpread = (self.spread + self.moveSpread)
end
//updated this with the new temp variable
local xSpread = ((NetworkRandom() * 2 * tempSpread) - tempSpread) + ((NetworkRandom() * 2 * tempSpread) - tempSpread)
local ySpread = ((NetworkRandom() * 2 * tempSpread) - tempSpread) + ((NetworkRandom() * 2 * tempSpread) - tempSpread)
spreadDirection = viewCoords.zAxis + viewCoords.xAxis * xSpread + viewCoords.yAxis * ySpread
end //this if-statement can be removed since we have spread<!--c2--></div><!--ec2-->
You now have a rifle that has a base spread of 0.1, but when moving has a spread of 0.4.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->//add a base spread value as a class variable
Rifle.spread = 0.1
//fix the typos by changing 'spread' to 'self.spread'
// in function Rifle:FireBullets(player)
local xSpread = ((NetworkRandom() * 2 * self.spread) - self.spread) + ((NetworkRandom() * 2 * self.spread) - self.spread)
local ySpread = ((NetworkRandom() * 2 * self.spread) - self.spread) + ((NetworkRandom() * 2 * self.spread) - self.spread)<!--c2--></div><!--ec2-->
Now we have working spread. I recommend values between 0.1 - 0.2. But now we want to make it spread even more only when the player is moving. There are many ways to accomplish this, but we need to know two more things for sure:
1) how much more spread when moving?
2) is the player moving?
Different guns may have different movement spread penalties, so I chose to track the moving spread in a Rifle class variable in Rifle.lua
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Rifle.moveSpread = 0.03<!--c2--></div><!--ec2-->
Now we need to know when the player is moving so we can adjust the spread formula. In the Player class, there is a networkvar 'velocity' that stores an X, Y, and Z value. We can check that when shooting to determine if the player is moving. I could do all kinds of checks in the Rifle:FireBullet method, but lets turn all that into a simple function in the Player class by adding:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function Player:IsMoving()
if self.velocity.x > 0.1 or self.velocity.y > 0.1 or self.velocity.z > 0.1 then
return true
else
return false
end
end<!--c2--></div><!--ec2-->
So this function returns true if the player is moving and false if the player is not. Note that I am not checking against zero. After much bug hunting, I learned that testing floating point values against zero only works before they are set above zero. After they they fall to an infinitesimally small number (like .002^10-e) but never back to zero. So I added a little tolerance to account for this fact.
Now we can edit the bullet spread parts of Rifle:FireBullets(player):
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> if (self.spread > 0) then //this if-statement can be removed since we know we have spread
//create a new value to hold the spread, start with base spread
local tempSpread = self.spread
// ask the Player class it is is moving, and if so, add the movement penalty
if player:IsMoving() then
tempSpread = (self.spread + self.moveSpread)
end
//updated this with the new temp variable
local xSpread = ((NetworkRandom() * 2 * tempSpread) - tempSpread) + ((NetworkRandom() * 2 * tempSpread) - tempSpread)
local ySpread = ((NetworkRandom() * 2 * tempSpread) - tempSpread) + ((NetworkRandom() * 2 * tempSpread) - tempSpread)
spreadDirection = viewCoords.zAxis + viewCoords.xAxis * xSpread + viewCoords.yAxis * ySpread
end //this if-statement can be removed since we have spread<!--c2--></div><!--ec2-->
You now have a rifle that has a base spread of 0.1, but when moving has a spread of 0.4.
Comments
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->self.velocity.x
<into>
abs(self.velocity.x)<!--c2--></div><!--ec2-->
Anyway, just going to toss a few ideas out for the lot of you modding people:
For the spread, why not add a gaussian distribution to it? It means that most of the bullets will fall close to the center, yet still in a random pattern. It is fairly easy to implement, just take the difference between two random numbers (I assume the random number generator generates between 0 and 1) and scale it up with the spread factor:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->xSpread = (NetworkRandom() - NetworkRandom()) * self.spread
ySpread = (NetworkRandom() - NetworkRandom()) * self.spread<!--c2--></div><!--ec2-->
For checking a player's velocity, a good way to do so is to use the sum of squares (always an efficient an directionally independent way of checking the length of any vector) and compare it with the square of the threshold, as
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->threshold = 0.1
if (self.velocity.x^2 + self.velocity.y^2 + self.velocity.z^2 > threshold^2) then
return true
end<!--c2--></div><!--ec2-->
And while we're at it, why not add dynamic spread based on how fast you move?
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->self.spread = 0.1 + 0.1 * (self.velocity.x^2 + 4 * self.velocity.y^2 + self.velocity.z^2)<!--c2--></div><!--ec2-->
The numbers here are arbitary; find something that works and plays well. Note that we can also penalise movement in the y-direction by adding that factor 4 in front of it (or of course any other factor to either make it a larger or smaller penalty), to simulate lesser accuracy when falling and climbing than when walking/running.
A great post with some very insightful solutions. The only issue is that with your last one, Spark uses the y direction as up so you would actually be penalizing walking in one direction more than the other. :D
To fix, one would just multiply velocity.y by 4 rather than velocity.z
I haven't looked at the LUA code yet, just assumed z would be the direction upward as that's what I currently use in my writings. By the way, does anyone know if there is a built-in function for the cross and dot product of vectors?
Not sure about CrossProduct, but i bet it's Math.CrossProduct(v1,v2)
I also wouldn't be surprised if there is a lengthSquared() function on vectors. Most game engines have them.
Does it realy make sense to increase spread when the player velocity is high ? Let's think about a player trying to aim when crouching on an elevator. As far as I know, those scripts would detect him as "moving". Yet he does not move by himself.
You only get inacurate while firing a gun if you move by yourself (because of unbalance) OR if something makes you accelerate or shake too much. On an elevator, on a slow monorail, or any other easy and slow moving platform, you may figure out that you can aim without suffering any spread. But that script would increase spread anyway.
Ok, if you're standind on a moving thing, and even if it's slow, you have to aim better since you have to compensate you're being moving. But that's the player's job to do so. Spread isn't involved at all.
What COULD do the trick would be to test velocity against a higher walue than a "close to zero" one. That walue would be higher than "being moved by a common elevator" and lower than running velocity. Doing so, duck-walking, proning, or even standing on a slowmoving platform would be understood as NOT moving. And spread would be ok.
What about it ?
I'm actually fine with spread, but not with spread at the wrong time, or in the wrong place.
What about it ?<!--QuoteEnd--></div><!--QuoteEEnd-->
That's really a separate issue. If you tried to solve it the way you are you would get strange behavior in game. It might be perfectly accepted by fans, but such a method is not necessary.
Normally there is a way to get a player's personal velocity and their base velocity. In the Source engine being on an elevator set a player's BaseVelocity. This was a different value that was added in during the move calculations, but was not part of the player's Velocity if you were to access it like in this example.
You have a valid point that this is something you would need to deal with, but your solution is more of just a hack.
The Lua scripting will come, it's the idea's, concepts, and processes that are the tricky parts! I'm hoping to see some fresh new ideas in these forums. Should be fun.
The Lua scripting will come, it's the idea's, concepts, and processes that are the tricky parts! I'm hoping to see some fresh new ideas in these forums. Should be fun.<!--QuoteEnd--></div><!--QuoteEEnd-->
ideas and concept = design.
process or algorithm = implementation.
process or algorithm = implementation.<!--QuoteEnd--></div><!--QuoteEEnd-->
The distinction is rather arbitary and differs from context to context. As an example, I am working on a system where the algorithm and process is the core of the design as well as implementation =)
Anyway; I was not aiming for realism or considered any gameplay in my suggestions, I was just hoping to toss some ideas in for those that wants to mod this game. Even if some might not fancy the vertical direction being influental on the accuracy, all I wanted to do is to show: "It's possible, it's easy, and here's one way of going about for it using very simple vector algebra."
process or algorithm = implementation.<!--QuoteEnd--></div><!--QuoteEEnd-->
umm gj.
Tank, you are right, good thread, should help people get their feet wet