Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

In many free-to-play games, play will be rate limited in some manner by a resource that you naturally earn on a timed interval, but can opt to increase artificially through buy-ins.

A current example of this is the popular game Candy Crush, which refills 1 life (retry) on some set interval.

Key requirements:

  • The client should be able to accurately predict the time to the next resource, such that it might display a countdown, etc.
  • Resource gain is not regular. It can be offset by, at least, resource buy-ins, or perhaps varying intervals between users.
  • Resource gain is capped, and thus not continual. E.g. maximum "lives" might be 5.
  • The server is the resource authority, obviously. The client might predict resource generation without actual notification as to such, but the resource count is determined server side.

Naively, one might simply implement this as a database update across the collection on the interval (if the interval is constant), but this would surely not scale. It would become more and more out of sync with the client timers as the dataset scales and the operation takes more time. Not to mention, it would be wasteful, as it's likely that only the percentage of your membership currently online needs to see realtime updates.

It seems like it would be possible to calculate the resource as a function of time since creation, time spent uncapped, resources earned, and resources spent. Would this be a viable option?

The question is, what is the best way to implement this mechanism to scale?

share|improve this question

2 Answers 2

up vote 4 down vote accepted

It's simple arithmetic and requires no loops or periodic DB updates.

The player has a rate of resource gain. This is fixed until some external stimulus happens like the player buying an item to change speed. You need only know the current speed and resource counts for this to work.

Take the current time. Take the last time the resource counts were updated in the DB. Find the difference. Divide by speed. Add to current resources. Cap the value. Done.

If forced to calculate for any reason besides simple display, update the resource count, set last update time to now minus the remainder from the speed division, and you're good. Recalculate the current count and last time and store back in the DB if the speed is changed for any reason.

The only time you'll need to invoke this operation is when a timed-event occurs that changes resource acquisition rate or the player performs an action. Very efficient and scalable.

You don't even need to do the reculation for some timed events. If the player gains 30 resources/second for 10 seconds and then gains 10 resources/second after, you can easily account for this. Take the elapsed time and cap at 10 seconds. Calc new resources. Take the elapsed time minus that capped time value from the first recalculation, calc additional resources gained at the slower rate. You can extend this to be more generic and flexible. Essentially you can move all need to ever actually calculate a value to the client in most cases (for display) and only update the server when the server actually needs the updated values when the user tries to do something. No constantly running server code, no frequent ticks from the DB, no need to constantly write back into storage for inactive players.

The client generally does not need to be exact. Network latencies and the like make it impossible anyway. So long as you can pass the current resource count and last update time (in UTC or UNIX time or something), the client can estimate the current resource values within a few seconds of accuracy. This will generally be enough. The client will at worst be a few seconds behind. If that really matters, the client should periodically ask the server for its calculation and lerp over to that, accounting for measured request latency/RTT. You really don't need that for the average social game, though.

share|improve this answer
    
After commenting above this is actually exactly what I came up with. I was working on coding up an example to post as an answer, but you've saved me the trouble. A rolling snapshot of resource count at a given time with the remaining interval factored in. Thanks for the clear write up :-) I've already got some fairly precise timing code in place which works well, estimating server time by averaging latency. I ping the server periodically for presence anyway. –  numbers1311407 May 23 '13 at 21:17

I've done things like this before using delta timing.

Here is an example:

int millisecondsThatPast = 0;
int numberOfPowerUpsToGive = 0;
int timeForEachPowerUp = 0;

public void setTimeForEachPowerUp(int timeInMilliseconds)
{
    timeforeachPowerUp = timeInMilliseconds;
}

public void onGameUpdate(int delta)
{
    millisecondsThatPast += delta;
    int n = 0;
    if (millisecondsThatPast >= timeForEachPowerUp)
    {
        for (int i = millisecondsThatPast; i >= timeForEachPowerUp; i -=
            timeForEachPowerUp)
             n++;
    } 
    numberOfPowerUpsToGive = i * timeForEachPowerUp;
}

public int getNumberOfPowerUpsToGive()
{
    return numberOfPowerUpsToGive;
}

Here is an example on how to use it:

setTimeForEachPowerUp(5000); //5 Seconds

while (game_loop_in_progress)
{
    onGameUpdate(delta);
    int powerups = getNumberOfPowerUpsToGive();
    //Use the number of power ups to give this update
}
share|improve this answer
    
I'm leaning toward something like this, although it would also have to incorporate time spent capped, during which resources would not increase. Essentially the resource would be a function of several attributes of the user: probably creation time (or an updateable timestamp and resource count at that time), time spent capped, and total resources purchased (not earned). I think something like that could work. I had hoped to be able to vary the gain interval on the fly (through buffs, whatever), but that would make a technique like this much more complicated. –  numbers1311407 May 23 '13 at 20:13
    
@numbers1311407 This would be easy. You just say if(X > Max) X = Max somewhere to cap it off. –  DampeS8N May 23 '13 at 20:52

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.