Wednesday, June 6, 2018

web - Implementing the "earn resource every N seconds" mechanism common to F2P games


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?



Answer



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.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...