Search Unity

Continually drain an int?

Discussion in 'Scripting' started by Epictickle, Dec 18, 2014.

  1. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    So, I'm making an RTS almost exactly like Command and Conquer, but I seem to be having a problem with the algorithm for draining currency while building a unit/building.

    In command and conquer, if you are building units or buildings, it continually drains your money while they are building. My original thought was to create a moneyPerSecond variable that would be: moneyPerSecond = building.CurrencyCost / (int)building.buildTime.

    this WOULD give me the money per second, but C&C drains faster. They drain like every 0.1 seconds, or maybe even once per frame. I'm not sure... How would I go about doing this? lol
     
  2. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Time.deltaTime is a great way to track the time of things. If you add it to a float every frame and check for that float being greater than or equal to 0.1, that'll be true every 0.1 seconds and you can reset it and execute your currency drain code.

    As for the drain itself, how about having a static variable that is the amount to drain or gain from your currency every 0.1 seconds. When a building begins construction, it can add its drain cost to the variable. When it's finished, it can remove it's drain cost to the variable. The effect would be that if you make say 5 buildings that cost 10 each, the variable will be set to 50 until they're done. This system will be simple yet dynamic enough to also handle adding currency when you do things that give resources over time or whatever like mining and running refineries (wrong game?).
     
  3. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    The players currency is currently of type Int as well, so I'm having to deal with casting... This is the formula that I'm currently using, but it isn't working:

    Code (CSharp):
    1. int moneyPerIteration = Mathf.CeilToInt((buildTime * Time.deltaTime) / CurrencyCost);
    This only makes an int of 1 no matter what... This code relies in Update, by the way.. Here, I'll just give you the entire snippet:

    Code (CSharp):
    1. if(doBuildTimer)
    2.            {
    3.                if (!controller)
    4.                    controller = Camera.main.GetComponent<PlayerController>();
    5.  
    6.                int moneyPerIteration = Mathf.CeilToInt((buildTime * Time.deltaTime) / CurrencyCost);
    7.  
    8.                if (controller.currency - moneyPerIteration < 0)
    9.                {
    10.                    buildingButton.SetInactive(false);
    11.                    return;
    12.                }
    13.                else
    14.                {
    15.                    if(buildingButton.isInactive())
    16.                    {
    17.                        buildingButton.SetInactive(true);
    18.                    }
    19.                }
    20.  
    21.                Debug.Log("timing.");
    22.                currentBuildtime += Time.deltaTime;
    23.  
    24.              
    25.  
    26.                if(totalCurrencyTaken < CurrencyCost)
    27.                {
    28.                    totalCurrencyTaken += moneyPerIteration;
    29.                    controller.TakeCurrency(moneyPerIteration);
    30.                    //Debug.Log("MPI: " + moneyPerIteration.ToString());
    31.                }
    32.              
    33.                if(currentBuildtime >= buildTime)
    34.                {
    35.                    FinishBuilding();
    36.                }
    37.            }

    I'd like to be able to figure out how much money should be taken every, say, 0.1 seconds. It would probably be smart to implement a coroutine to do this.
     
  4. Sbizz

    Sbizz

    Joined:
    Oct 2, 2014
    Posts:
    250
    You can't do this if you're working with an int because the more you wanna give per seconds, the hardest to cast the variable is.

    Let's say you wanna drain 2 golds / second. Okay, no worries if your variable is an int. Now, how much gold do we have to drain for every 0.5 second ? 1 gold. Still OK. Now, for 0.1 second ? 0.2 gold. When you'll cast your int, you'll have 0.

    It's up to you! You should change your variable type to float and when you print your currency, you print it as an integer.

    Edit: OR, you can have temporary float variable and apply your drain on it and every time you change this variable, you change the "real" variable. I don't know If I'm clear ? :p
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,706
    Can you make currency an int property with a float variable behind it?
    Code (csharp):
    1. private float m_currency;
    2. public int currency {
    3.     get { return Mathf.FloorToInt(m_currency); } }
    4.     set { m_currency = value + (m_currency - Mathf.Floor(m_currency)); } }
    5. }
     
    Epictickle likes this.
  6. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    using a float as the getter and setter sounds like a good idea. I think I'm going to try both and see which one I like the most. They should both work. I was worried when I made it an int that I'd have to change it later... -_-
     
  7. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Well of course you got 1, you rounded up a float to an int. Anything greater than 0 and less than 1 is going to give you 1. I see you're already beyond that now, though :D

    I also don't recommend mixing ints and floats for your over time operations, because then you're never going to get exact numbers. Either have all of your costs ints or all of your costs floats. Otherwise, things are gonna get unpredictably wonky every time you have an odd number total for your ints and a float value, for example 0.5 and 2 will work... 0.5 and 4 will work... but 0.5 and 3 will give you 1.5, which will round up to 2 and create clear exploits for your game. The player will always opt in for the highest they can afford because it will get rounded off anyway - while opting for the lowest income greater than x.0 so it can be rounded up to the next value.
     
  8. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    I actually ended up changing everything to a float. It's working almost perfectly, except when I cast to an int for the GUI, it's always one dollar less than what I should have lol.

    Here's the code:

    Code (CSharp):
    1. if(doBuildTimer)
    2.            {
    3.                if (!controller)
    4.                    controller = Camera.main.GetComponent<PlayerController>();
    5.  
    6.                float moneyPerIteration = CurrencyCost / (buildTime / Time.deltaTime);
    7.  
    8.                Debug.Log(moneyPerIteration);
    9.  
    10.                if (controller.currency - moneyPerIteration < 0)
    11.                {
    12.                    buildingButton.SetInactive(false);
    13.                    return;
    14.                }
    15.                else
    16.                {
    17.                    if(buildingButton.isInactive())
    18.                    {
    19.                        buildingButton.SetInactive(true);
    20.                    }
    21.                }
    22.  
    23.                Debug.Log("timing.");
    24.                currentBuildtime += Time.deltaTime;
    25.  
    26.            
    27.  
    28.                if(totalCurrencyTaken < CurrencyCost)
    29.                {
    30.                    float temp = moneyPerIteration;
    31.                    if(totalCurrencyTaken + moneyPerIteration > CurrencyCost)
    32.                    {
    33.                        temp = Mathf.Floor(CurrencyCost - totalCurrencyTaken);
    34.                    }
    35.  
    36.                    totalCurrencyTaken += temp;
    37.                    controller.TakeCurrency(temp);
    38.                    //Debug.Log("MPI: " + moneyPerIteration.ToString());
    39.                }
    40.              
    41.                if(currentBuildtime >= buildTime)
    42.                {
    43.                    FinishBuilding();
    44.                }
    45.            }
    Pretty much, every time TakeCurrency is called, it calls a function in my GUIController class to update the players currency label. In the GUIController, I cast the players money to an int for the users viewing pleasure... xP
     
  9. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Such nice visuals, but at what cost?! Oh, the cost of the player's spare change ;)

    You can do something ugly, if you'd like. It'll stop the truncation issue.

    multiply your number by 100, cast the result to an int, cast the result to a float, divide by 100. That will turn something like $10.2501941928419 $10.25. Maybe every 1000-10,000 casts, you will lose 1 cent because floats usually turn 2.0 into 1.99999999999999, but I don't think the user will notice.
     
  10. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    No, my currency label actually only shows whole numbers. My problem is that it just isn't Flooring the float the right way.. Or maybe I need to use Mathf.Ceil?

    Also, if you have a float, and if you ever want to just display the first two numbers after the decimal point as a string you could always use: myFloat.ToString("F2");

    my problem with the one-dollar-off bug relies in this chunk of code:

    Code (CSharp):
    1. if(totalCurrencyTaken < CurrencyCost)
    2.                {
    3.                    float temp = moneyPerIteration;
    4.                    if(totalCurrencyTaken + moneyPerIteration > CurrencyCost)
    5.                    {
    6.                        temp = Mathf.Floor(CurrencyCost - totalCurrencyTaken); //ERROR
    7.                    }
    8.                    totalCurrencyTaken += temp;
    9.                    controller.TakeCurrency(temp);
    10.                    //Debug.Log("MPI: " + moneyPerIteration.ToString());
    11.                }
    specifically, line commented with ERROR

    Say I start off with $2,000. I start building a power plant that costs $500. After the build is finished my players currency label will say "1499". It only does this SOME of the time, though. I can look at my players current currency and see that he has like 1499.999997 dollars.. How could I catch a situation like this and make that number an even 1500?
     
    Last edited: Dec 18, 2014
  11. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Sure, you could do it that way, but strings are a last resort :D In large programs I've written, enabling and disabling logging (string concatenation to detail the program execution) was the difference between a runtime of 3 seconds and a runtime of 34 minutes.

    If you use floor, you're going to round down. If you use ceil, you're going to round up. If you don't round at all and just cast to an int in your display, you won't alter the user's currency.
     
  12. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    Sorry, I heavily edited the post above. Give it a look see and tell me if you can spot the error. Maybe I should wrap the error line in another if statement to see if I should either use Floor or Ceil. The problem with casting to an int on the GUI is that if I cast 1499.9999997 to an int it'll just convert the float to 1499. I need it to be a solid 1500. I don't really mind if I end up with 1500.001 or whatever.. As long as I don't end up with 1499 or 1501 lol. I want the user to visually see that the correct amount has been taken, even if it has only taken 0.000003 more than it was supposed to.

    PS: I really appreciate all the help guys!
     
    Last edited: Dec 18, 2014
  13. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    Mathf.Round

    Although I'd recommend just using ints to avoid all this. Use InvokeRepeating or some other timing mechanism to reduce an int by 1 per second or whatever.

    --Eric
     
  14. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,148
    Here's your solution :D

    int floats_are_stupid = int(float_value + 0.001f);

    In most cases, that trail of 9s is going to be quite a few places back, so 0.001 should safely never put you over the amount the user actually has.

    --edit

    Say your user has 350.50 and the float representation is 350.499999f, the solution will work here to preserve the 350.50 without changing it :) If you never have decimal point values and you're strictly worried about a whole number becoming that number minus 0.00000000000001 because float math, then this solution has that covered as well.
     
  15. Epictickle

    Epictickle

    Joined:
    Aug 12, 2012
    Posts:
    431
    @Eric5h5 almost had it with Mathf.Round. I also liked your solution, too though Tomnnn. Anyways, what I ended up doing was not really much caring if my users currency lands at 1499.999. Instead, I let the actual variable stay that way since it's a very miniscule margin of error (although I'm not fond of leaving a margin of error), and I just use Mathf.RoundToInt(currency) in my GUIController. This way the user thinks it has only taken $500, but it has really taken $500.001 lol