A simple tutorial: Dynamic bullet spread

HakujinHakujin Join Date: 2003-05-09 Member: 16157Members, Constellation
edited April 2010 in Modding
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.

Comments

  • AezayAezay Join Date: 2003-04-19 Member: 15660Members
    I've not looked at the code myself, or how it actually works, but wouldn't you need to change the following to make it work?
    <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->self.velocity.x
    <into>
    abs(self.velocity.x)<!--c2--></div><!--ec2-->
  • HakujinHakujin Join Date: 2003-05-09 Member: 16157Members, Constellation
    Haha - makes sense to me. Velocity is based on a grid with a centerpoint. If the player were moving in a -x, -y direction they would have high accuracy when moving. That would be a tricky bug to find! Thanks.
  • tankefugltankefugl One Script To Rule Them All... Trondheim, Norway Join Date: 2002-11-14 Member: 8641Members, Retired Developer, NS1 Playtester, Constellation, NS2 Playtester, Squad Five Blue
    edited April 2010
    Alright, my first reply got eaten by the big nasty forum troll! I suspect the creation of a mapping subforum has something to do with this ;)

    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.
  • remiremi remedy [blu.knight] Join Date: 2003-11-18 Member: 23112Members, Super Administrators, Forum Admins, NS2 Developer, NS2 Playtester
    <!--quoteo(post=1767105:date=Apr 15 2010, 10:10 AM:name=tankefugl)--><div class='quotetop'>QUOTE (tankefugl @ Apr 15 2010, 10:10 AM) <a href="index.php?act=findpost&pid=1767105"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The numbers here are arbitary; find something that works and plays well. Note that we can also penalise movement in the z-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.<!--QuoteEnd--></div><!--QuoteEEnd-->
    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
  • tankefugltankefugl One Script To Rule Them All... Trondheim, Norway Join Date: 2002-11-14 Member: 8641Members, Retired Developer, NS1 Playtester, Constellation, NS2 Playtester, Squad Five Blue
    edited April 2010
    Ah, thank you. I'll correct that.

    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?
  • remiremi remedy [blu.knight] Join Date: 2003-11-18 Member: 23112Members, Super Administrators, Forum Admins, NS2 Developer, NS2 Playtester
    edited April 2010
    local result = Math.DotProduct( vector1, vector2)

    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.
  • RobBRobB TUBES OF THE INTERWEB Join Date: 2003-08-11 Member: 19423Members, Constellation, Reinforced - Shadow
    I can't fully agree on the z vector for spreading, at least not when falling down...
  • CXZmanCXZman Join Date: 2010-02-26 Member: 70730Members
    edited April 2010
    Just asking...

    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 ?
  • HakujinHakujin Join Date: 2003-05-09 Member: 16157Members, Constellation
    Actually, adding spread in general is not very sporting. A perfectly accurate rifle with compensatable recoil would be the fairest system. I was working on that but have become stuck - I need documentation to finish it.
  • CXZmanCXZman Join Date: 2010-02-26 Member: 70730Members
    Well, I wasn't even talking about realism. I guess I just would be pissed if my gun would fire anywhere but on target just because I'm crouching down on an elevator going up trying to wipe out that f*** Lerk !

    I'm actually fine with spread, but not with spread at the wrong time, or in the wrong place.
  • remiremi remedy [blu.knight] Join Date: 2003-11-18 Member: 23112Members, Super Administrators, Forum Admins, NS2 Developer, NS2 Playtester
    <!--quoteo(post=1767615:date=Apr 17 2010, 01:47 PM:name=CXZman)--><div class='quotetop'>QUOTE (CXZman @ Apr 17 2010, 01:47 PM) <a href="index.php?act=findpost&pid=1767615"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->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 ?<!--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.
  • CXZmanCXZman Join Date: 2010-02-26 Member: 70730Members
    Yeah, I guess so :) That threshold only needs balancing to get past float value approximations. It is not a gameplay value.
  • KalabalanaKalabalana Join Date: 2003-11-14 Member: 22859Members
    The implementation is not the issue, it's the processes. You need a function to discern between voluntary player movement, and environmentally driven movement (if a player is moving himself, or is falling, in an elevator, a car, riding an onos, etc). Also, should the rate of "spread" be proportional to the movement speed, and any positive or negative acceleration the player is experiencing? Or should we take a new approach rather then spread, and simply keep accuracy relative to the rifle just like in real life? If so, what forces should we be applying to the players crosshairs (bobbing, weaving, etc)?

    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.
  • remiremi remedy [blu.knight] Join Date: 2003-11-18 Member: 23112Members, Super Administrators, Forum Admins, NS2 Developer, NS2 Playtester
    <!--quoteo(post=1768361:date=Apr 22 2010, 07:27 PM:name=Kalabalana)--><div class='quotetop'>QUOTE (Kalabalana @ Apr 22 2010, 07:27 PM) <a href="index.php?act=findpost&pid=1768361"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The implementation is not the issue, it's the processes. You need a function to discern between voluntary player movement, and environmentally driven movement (if a player is moving himself, or is falling, in an elevator, a car, riding an onos, etc). Also, should the rate of "spread" be proportional to the movement speed, and any positive or negative acceleration the player is experiencing? Or should we take a new approach rather then spread, and simply keep accuracy relative to the rifle just like in real life? If so, what forces should we be applying to the players crosshairs (bobbing, weaving, etc)?

    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.
  • tankefugltankefugl One Script To Rule Them All... Trondheim, Norway Join Date: 2002-11-14 Member: 8641Members, Retired Developer, NS1 Playtester, Constellation, NS2 Playtester, Squad Five Blue
    <!--quoteo(post=1768365:date=Apr 23 2010, 07:05 AM:name=Psyke)--><div class='quotetop'>QUOTE (Psyke @ Apr 23 2010, 07:05 AM) <a href="index.php?act=findpost&pid=1768365"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->ideas and concept = design.
    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."
  • KalabalanaKalabalana Join Date: 2003-11-14 Member: 22859Members
    edited May 2010
    <!--quoteo(post=1768365:date=Apr 23 2010, 12:05 AM:name=Psyke)--><div class='quotetop'>QUOTE (Psyke @ Apr 23 2010, 12:05 AM) <a href="index.php?act=findpost&pid=1768365"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->ideas and concept = design.
    process or algorithm = implementation.<!--QuoteEnd--></div><!--QuoteEEnd-->
    umm gj.


    Tank, you are right, good thread, should help people get their feet wet
Sign In or Register to comment.