Rewarding players for coming back and playing your game every day is nothing new, and is probably used in most successful games out there in one way or another. I don't want to miss an opportunity to keep my Ice Trap players for a little longer if possible, so I've implemented a simple daily rewards solution in Corona SDK to handle this.
My solution is the simplest possible really: Give the player a reward if he/she plays two days in a row. The reward will be the same every day regardless of the number of consecutive days played. I guess you could make this just as advanced as you like, with increasing rewards after a specific number of days, different rewards on different days and so on. But for the moment I don't see a need for that, and a flat reward will do just fine to start with.
Recently I implemented a concept in Ice Trap called level keys, allowing the player to unlock the next level if getting stuck. The player starts the game with a few keys, and can then earn new level keys while playing.
Level keys make a perfect daily reward in my opinion. If a player tries to unlock a new level and is out of level keys, just pop up a dialog saying something like "Check back tomorrow to get 2 new level keys". This should hopefully entice the player to do just that, check back, instead of quitting and never play the game again.
Following is the full source code for my Corona SDK implementation of a dailyrewards module together with a small example of how to use it. Note that dailyrewards also requires the following modules, so make sure to get them too if you want to be able to use the code:
My solution is the simplest possible really: Give the player a reward if he/she plays two days in a row. The reward will be the same every day regardless of the number of consecutive days played. I guess you could make this just as advanced as you like, with increasing rewards after a specific number of days, different rewards on different days and so on. But for the moment I don't see a need for that, and a flat reward will do just fine to start with.
Recently I implemented a concept in Ice Trap called level keys, allowing the player to unlock the next level if getting stuck. The player starts the game with a few keys, and can then earn new level keys while playing.
![]() |
Allow the player to unlock the next level from level selection... |
...and when getting tired of failing on some level. |
Level keys make a perfect daily reward in my opinion. If a player tries to unlock a new level and is out of level keys, just pop up a dialog saying something like "Check back tomorrow to get 2 new level keys". This should hopefully entice the player to do just that, check back, instead of quitting and never play the game again.
Following is the full source code for my Corona SDK implementation of a dailyrewards module together with a small example of how to use it. Note that dailyrewards also requires the following modules, so make sure to get them too if you want to be able to use the code:
- fileutil - A little helper class I've written myself to simplify working with local json files.
- Lua Date - A date/time module for Lua which I got from here: https://github.com/wscherphof/lua-date. It's enough to download and require the date.lua file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local fileutil = require("fileutil") | |
local date = require("date") | |
-- Configuration of the daily rewards, and an optional callback function. Will be injected in init() function | |
local _rewardsConfig | |
local _onReward | |
local _rewards = { | |
lastRewardDate = tostring(date()), -- Save an initial timestamp | |
pending = { | |
}, | |
redeemed = { | |
} | |
} | |
local _filename = "daily-rewards.json" | |
local function _saveDailyRewards() | |
fileutil.saveUserFile(_filename, _rewards) | |
end | |
-- This function is normally only called when app is launched | |
-- Returns true if rewards were loaded from file, false if not | |
local function _loadDailyRewards() | |
local fileContents = fileutil.readUserFile(_filename) | |
if ( fileContents ) then | |
_rewards = fileContents | |
return true | |
else | |
return false | |
end | |
end | |
local function _validateRewardId(rewardId) | |
assert(_rewardsConfig[rewardId], "Invalid rewardId for daily reward: " .. rewardId) | |
end | |
--[[ | |
Gets the value of a pending reward | |
--]] | |
local function getPendingReward(rewardId) | |
_validateRewardId(rewardId) | |
return _rewards.pending[rewardId] or 0 | |
end | |
--[[ | |
Checks if there's a pending reward. Convenience function to use instead of checking that getPendingReward(rewardId) > 0. | |
--]] | |
local function hasPendingReward(rewardId) | |
return (getPendingReward(rewardId) > 0) | |
end | |
--[[ | |
Resets the value for given rewardId | |
Returns the pre-reset value of the pending reward | |
--]] | |
local function redeemPendingReward(rewardId) | |
_validateRewardId(rewardId) | |
-- Get and reset the pending reward | |
local rewardValue = _rewards.pending[rewardId] or 0 | |
_rewards.pending[rewardId] = 0 | |
-- Save a little information about the redeemed reward. Just for statistics and history purposes, not used anywhere right now... | |
if (not _rewards.redeemed[rewardId]) then | |
_rewards.redeemed[rewardId] = {} | |
end | |
_rewards.redeemed[rewardId][#_rewards.redeemed[rewardId] + 1] = { | |
timestamp = tostring(date(true)), | |
value = rewardValue | |
} | |
_saveDailyRewards() | |
return rewardValue | |
end | |
local function _addPendingReward(rewardId, value) | |
_validateRewardId(rewardId) | |
_rewards.pending[rewardId] = (_rewards.pending[rewardId] or 0) + value | |
end | |
local function _setDailyRewardsTimestamp() | |
_rewards.lastRewardDate = tostring(date()) | |
end | |
--[[ | |
This function does all the work of checking if there are any new rewards that should be given to the player, and saves them as pending. | |
--]] | |
local function checkForRewards() | |
print("Checking for daily rewards...") | |
local function getDaysSinceLastReward() | |
local lastCheck = date(_rewards.lastRewardDate) | |
local now = date() | |
-- Return the number of days passed since rewards were last given | |
return (math.floor(now:spandays()) - math.floor(lastCheck:spandays())) | |
end | |
local daysPassed = getDaysSinceLastReward() | |
-- Daily rewards are only given if a player plays two days in a row. If more time has passed, the timestamp will be reset. | |
if (daysPassed == 1) then | |
print("Will reward player for being loyal and playing two days in a row! :-)") | |
-- Create new pending rewards that can later be redeemed | |
for rewardId, rewardConfig in pairs(_rewardsConfig) do | |
if ((rewardConfig.isEligible == nil) or rewardConfig.isEligible()) then | |
_addPendingReward(rewardId, rewardConfig.value) | |
-- Make callback if a function was specified in init() | |
if (_onReward) then | |
_onReward(rewardId, rewardConfig.value) | |
end | |
else | |
print("Player not eligible for daily reward " .. rewardId .. "... :-(") | |
end | |
end | |
-- Set a new timestamp, and save the changes | |
_setDailyRewardsTimestamp() | |
_saveDailyRewards() | |
elseif(daysPassed > 1) then | |
print("No daily rewards to give... More than one day has passed since player was given daily rewards. Will reset timestamp.") | |
-- We only save a new timestamp so that the player can collect a daily reward tomorrow instead | |
_setDailyRewardsTimestamp() | |
_saveDailyRewards() | |
else | |
print("No daily rewards to give... Player has already been rewarded for this day.") | |
end | |
end | |
--[[ | |
Initialize the module | |
rewardsConfig (required) - A table to configure the daily rewards. Note that a rewardId must exist as a key in this table to be able to call any of the {has/get/redeem}PendingReward() functions with that rewardId. Example config: | |
{ | |
bombs = { | |
value = 5, | |
}, | |
coins = { | |
value = 100, | |
-- Optional function to check additional conditions that must be fulfilled in order for player to be rewarded. Return true if reward should be given, false otherwise. | |
-- Not specifying isEligible is the same as an isEligible function that always returns true. | |
isEligible = function() | |
-- Player will not get 100 more coins if he has more than 1000. | |
return (player.coins <= 1000) | |
end, | |
} | |
}. | |
onReward (optional) - An optional callback function accepting two parameters rewardId and rewardValue that will be called when a new daily reward is created. | |
--]] | |
local function init(rewardsConfig, onReward) | |
assert((rewardsConfig ~= nil), "No rewardsConfig specified") | |
assert((next(rewardsConfig) ~= nil), "No rewards specified in rewardsConfig") | |
if ( not _loadDailyRewards() ) then | |
_saveDailyRewards() | |
end | |
_rewardsConfig = rewardsConfig | |
_onReward = onReward | |
end | |
return { | |
init = init, | |
checkForRewards = checkForRewards, | |
hasPendingReward = hasPendingReward, | |
getPendingReward = getPendingReward, | |
redeemPendingReward = redeemPendingReward, | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local json = require("json") | |
local function readFile( filename, directory) | |
local filePath = system.pathForFile( filename, directory ) | |
local theFile = io.open( filePath, "r" ) | |
if theFile then | |
local contents = theFile:read( "*a" ) | |
io.close( theFile ) | |
local asJson = json.decode(contents) | |
return asJson | |
else | |
return nil | |
end | |
end | |
local function readResourceFile( filename ) | |
return readFile( filename, system.ResourceDirectory) | |
end | |
local function readUserFile( filename ) | |
return readFile( filename, system.DocumentsDirectory) | |
end | |
local function saveUserFile( filename, tableData ) | |
local filePath = system.pathForFile( filename, system.DocumentsDirectory ) | |
local userFile = io.open( filePath, "w" ) | |
userFile:write( json.encode(tableData) ) | |
io.close( userFile ) | |
userFile = nil | |
end | |
local function fileExists(filename, baseDir) | |
assert(filename, "filename is missing") | |
assert(baseDir, "baseDir is missing") | |
local filePath = system.pathForFile( filename, baseDir ) | |
local exists = false | |
if (filePath) then | |
-- File MAY exist. won't know for sure until it's actually opened... | |
local fileHandle = io.open( filePath, "r" ) | |
if (fileHandle) then | |
exists = true | |
io.close(fileHandle) | |
end | |
end | |
return exists | |
end | |
local function resourceFileExists(filename) | |
return fileExists(filename, system.ResourceDirectory) | |
end | |
local function userFileExists(filename) | |
return fileExists(filename, system.DocumentsDirectory) | |
end | |
return { | |
resourceFileExists = resourceFileExists, | |
readResourceFile = readResourceFile, | |
userFileExists = userFileExists, | |
readUserFile = readUserFile, | |
saveUserFile = saveUserFile, | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Example of using the dailyrewards module to rewards loyal players for playing two days in a row | |
When running this example for the first time, no rewards will be given. | |
To test that the rewards work, just open the daily-rewards.json file located in the {Project Sandbox}/Documents, update the lastRewardTimestamp to the day before and then run the example again. | |
Markus Ranner 2016 | |
--]] | |
local dailyrewards = require("dailyrewards") | |
display.setStatusBar(display.HiddenStatusBar) | |
-- Setup a hard coded player object | |
local player = { | |
bombs = 10, | |
coins = 500, | |
} | |
-- The callback function when a reward is given | |
local onReward = function(rewardId, value) | |
print("There is a new pending '" .. rewardId .. "' reward waiting, value = " .. value) | |
-- If you want to reward the player immediately when a daily reward is created, do it here. | |
-- Otherwise, if you want to have more control over when to actually reward the player, make calls to dailyrewards.{has/get/redeem}PendingReward() instead. See the bottom of this example. | |
end | |
local rewardsConfig = { | |
bombs = { | |
value = 5, | |
}, | |
coins = { | |
value = 100, | |
isEligible = function() | |
-- Player will not get 100 more coins if he already has more than 1000. | |
return (player.coins <= 1000) | |
end, | |
} | |
} | |
-- Init the daily rewards module with game specific settings | |
dailyrewards.init(rewardsConfig, onReward) | |
-- Setup a system event listener to check for daily rewards every time the app is launched or resumed from the background | |
local function onSystemEvent( event ) | |
local eventType = event.type | |
if ( eventType == "applicationStart" or eventType == "applicationResume" ) then | |
dailyrewards.checkForRewards() | |
end | |
end | |
Runtime:addEventListener( "system", onSystemEvent ) | |
-- After a second has passed, we check for any pending rewards. | |
timer.performWithDelay(1000, function() | |
if (dailyrewards.hasPendingReward("bombs")) then | |
-- Here we start by getting the reward value, using it to update the player, and then redeem the reward to that it can't be rewarded again. | |
local bombsReward = dailyrewards.getPendingReward("bombs") | |
player.bombs = player.bombs + bombsReward | |
dailyrewards.redeemPendingReward("bombs") | |
print("player.bombs = " .. player.bombs) | |
end | |
if (dailyrewards.hasPendingReward("coins")) then | |
-- But we can just as well redeem the reward directly and update the player afterwards, like this: | |
local coinsReward = dailyrewards.redeemPendingReward("coins") | |
player.coins = player.coins + coinsReward | |
print("player.coins = " .. player.coins) | |
end | |
end) |
Comments
Post a Comment