Combu: leaderboards and more for Unity, on your server
Until a few months ago, I used to rely on an online platform for the leaderboards of my games. It was easy to use and free within a limited amount of users, so I was happy. But there was a con, which I realized only later: being an online platform, I had to rely on someone else’s servers for my leaderboards to work. So when that online platform started hiccuping there was nothing I could do. Players started complaining with me (obviously) that the highscores weren’t loading nor uploading, while the online platform support seemed unable to fix the situation for a long time (I don’t even know if they fixed it by now). So I finally decided I had to move on a system that would be totally under my control and would reside on my own server. Too bad I’m not a database expert, and I know PHP only averagely. So. Enter Combu, which saved my games.
What is Combu?
Well, first and foremost, Combu is a Unity plugin (you can get it on the Asset Store or directly from the author’s website) created by Francesco Crocetti, which allows to manage online leaderboards and a lot more. It’s divided in two main parts…
The Unity files
Part of Combu is imported directly into your Unity project, giving you a load of APIs to interact with online players data, like leaderboards, accounts, mail notifications, inventories, news. It’s a very complete package and extremely simple to use, thanks to a very straightforward API which makes a smart use of events. This is a list of the current features (as of v1.4.3):
- Users: create, login, update, load, delete, change password, exists, load random
- Leaderboards: load leaderboard, highscores (overall, monthly, weekly, daily), post score
- Contacts: load, add, remove contacts
- Inventory: load, add/update item, remove item
- Achievements: load achievements, progress
- News: load
- Mails: load, send, read, remove
- Plus other miscellaneous stuff
The server files
The rest of Combu needs to be uploaded to your server, so it can connect your database with your Unity project, while also offering an online admin panel to check all your data and eventually add/delete/change things. Even if you don’t know shit about databases or php, Combu’s instructions make everything easy.
Observations
Combu can encrypt all the data you send to/from your Unity project, which is all you can ask security-wise. It will be up to you to protect your encryption key inside Unity, so that cheaters can’t access it (and for that I recommend Dmitriy Yukhanov’s Anti-Cheat Toolkit).
For what concerns highscores, Combu lets you choose between two options for each leaderboard. You can either store all user scores as they are sent, or keep a single score for every user, which increases each time you send a new one (useful for example in case of an online RPGs, to keep track of experience points). Personally, I prefer another option, but I’ll talk about it in the last paragraph.
Support-wise, Combu is fantastic. I was kind of a pain in the ass to Francesco while implementing it, but he always replied very quickly and with great efficiency.
So, Goscurry is now running on Combu and the highscores load very quickly, while everything resides on my own server so I don’t have to be worried of external platforms. All in all, I highly recommend Combu: it’s a very well made piece of software. Even money-wise it’s a great deal. With a one time fee you get a system you can re-use with all your games, while paying for a personal server is way cheaper than payed online services. And beware: I’m not payed to market Combu. I just really liked it, found myself very at ease with its implementation, and was extremely happy with the support.
A mod
As I mentioned, Combu leaderboards can either store every single score submitted, or a single sum of them all. Personally, I preferred another approach, which is to only keep the best overall/monthly/weekly/daily highscore for each user, in order to avoid overcrowding my database (when Goscurry will have tons of players — crossing my fingers very hard here). So I made a modification to a little part of the PHP code, and Francesco allowed me to post it here, in case you’re interested. But beware: I’m not a php/database expert, so you might find a better way to implement the same logic. If you do, let me know.
So. Get Combu and set it up. Then open the CB_Leaderboard.php file you find in the lib directory of the server contents. Get inside the last method there, PostScore, and replace this last line:
1 |
return $newRecord->Save(); |
with this:
1 2 3 4 5 |
$success = $newRecord->Save(); if (!$success) return FALSE; // Clean scores self::_cleanScores($account, $field); return TRUE; |
Then, in the same file, add this method and you’re done:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
private function _cleanScores($account, $field) { // Find top overall, monthly, weekly and daily records $topRecords = array(); $where_time = ""; // Overall for ($i = 0; $i < 4; $i++) { switch ($i) { case 1: // This Month $where_time = sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')", date("Y-m-d", mktime(0, 0, 0, date("m"), 1, date("Y"))), date("Y-m-t", mktime(0, 0, 0, date("m"), 1, date("Y")))); break; case 2: // This Week $where_time = sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')", date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 7, date("Y"))), date("Y-m-d")); break; case 3: // Today $where_time = sprintf(" AND (LastUpdated BETWEEN '%s 00:00:00' AND '%s 23:59:59')", date("Y-m-d"), date("Y-m-d")); break; } $where = sprintf("(IdLeaderboard = %d AND IdAccount = %d)", $this->Id, $account->Id).$where_time; $records = self::_load(CB_LeaderBoard_User::TABLE_NAME, "CB_LeaderBoard_User", $where); if (count($records) == 0) break; // No more records will exist $newTopRecord = null; foreach ($records as $record) { if ($newTopRecord == null || $record->$field > $newTopRecord->$field) $newTopRecord = $record; } // Add newTopRecord to topRecords only if it was not already stored $wasStored = false; if (count($topRecords > 0)) { foreach ($topRecords as $topRecord) { if ($newTopRecord->LastUpdated == $topRecord->LastUpdated) { $wasStored = true; break; } } } if (!$wasStored) $topRecords[] = $newTopRecord; } // Delete all scores except the top ones $deleteFilter = sprintf("(IdLeaderboard = %d AND IdAccount = %d)", $this->Id, $account->Id); foreach ($topRecords as $topRecord) { $deleteFilter .= sprintf(" AND LastUpdated != '%s'", $topRecord->LastUpdated); } $this->_Delete(CB_LeaderBoard_User::TABLE_NAME, $deleteFilter); } |
1 Comment
Lior Rosenspitz
February 11, 2018Thanks.
I didn’t know this one.