Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Highscores System


_2.png


What's This?

You now have the ability to embed itch.io browser games on our site, as announced in a previous update.

In addition to this is the ability to create a highscores leaderboard for your game. This is a friendly way of sharing your score with your friends and is not intended as any kind of commercially sound system. It's all for fun.

How do I make a compatible game?

We recommend talking to Ellie on Discord who will be happy to help. Here are some tips on how to make a compatible game however:

Have one or more numerical scores

Your game needs to have a numerical high score, in which the higher the score, the better the performance of the player.

There can be any number of these scores to track. Examples are points, gold, level or number of deaths.

Generate the required values

You will need a username input screen. How I have implemented this myself is by using the NAME INPUT prompt within RPG Maker.

You will want to generate values for username, score, and highscore name.

For example: Ellie, 542, gold.

Choose a sort order

The order of your displayed highscores depends on the number you give it in highscore name. This goes at the start of the highscore name.

So for example:

1Points
2Gold
3Lives Remaining

Number 1 will display on your game page. The rest will display on your game page's Highscores tab.

Get some code from Ellie

You will need to edit your index.html file. We recommend just sending this file to Ellie and allowing her to insert her magical code.

You will then be given a script call which can be called from anywhere in your game at the moment you want to send your score(s).

Decide when to enter your name

In Blocks Blocks Revolution I chose to let the user enter their nickname before the game begins.

In Snakes and Dragons this is instead done at the time of high score submission.

Publish your game

Publish your game on itch.io preferably as a browser game. Send this link to Ellie, and she will set up your game page for you. You can then edit this to your heart's content.

Update your game page whenever your file changes

You will need to update your game page every time you change your file, due to how itch embedding works (by file not by game).

Any other thoughts

Just discuss it with Ellie and we'll work it all out.
 
Here is a plugin to integrate the high score system with your rpg maker MZ / MV game.
Make sure to save it as "HBHS.js" in your game's plugin folder and configure with the plugin manager.
You will need your game's ID (number on the end of the game thread), and secret (configured on the game thread edit page)

Using plugin commands, you can:
  • post a high score to HB games
  • show the top scores in a window (easy mode)
  • read the top scores into actor names and variables to display yourself (hard mode)
Example of the first two can be seen in the "12 days of christmas" game.

You may freely edit this plugin to fit your needs or use it as an example for creating integrations with other engines.
(It has an ISC license for this purpose)

HBHS.js:
/*:
 * @author Coelocanth & Ellie
 * @plugindesc HB Games high score integration
 * @target MZ
 * @url https://www.arpgmaker.com
 * 
 * @help This plugin lets you post high scores to your game page on HB Games
 * 
 * Use the plugin command "postHighScore" to send a score.
 * The first character of a score type is invisible and just used for sorting,
 * so use "1Score" and not "Score" or it'll appear as "core" on the site.
 * 
 * Use the plugin command "readHighScore" to pull the high score table into variables
 * to display yourself.
 * 
 * Use the plugin command "showHighScore" to just show a window with the scores in.
 * 
 * RPG Maker MV:
 * postHighScore 1 3 1Score
 * 1st argument is the actor ID who has the player name as their name
 * 2nd argument is the variable ID containing the score
 * 3rd argument is the type of the score (if omitted, "1Score")
 * 
 * readHighScore 1 2 3
 * 1st argument is the actor ID whose name is set to the high score's name
 * 2nd argument is the variable ID set to the high score's value
 * 3rd argument is the index of the high score table (0 = highest)
 * 
 * showHighScore 0
 * 1st argument is window type (0 = normal, 1 = dim, 2 = transparent)
 * 
 * RPG Maker MZ:
 * Use the plugin commands from this plugin
 * 
 * Script calls (use with conditional branch, control variables, etc.):
 * CC.HBHS.lastSuccess() -> returns whether the last high score post worked.
 * CC.HBHS.highscoreCount() -> returns the number of high score entries
 * CC.HBHS.highscoreName(index) -> returns the name of high score entry at index (0 is highest score)
 * CC.HBHS.highscoreValue(index) -> returns the score of high score entry at index (0 is highest score)
 * 
 * ISC License
 * 
 * Copyright (c) 2021 Coelocanth
 * 
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 * 
 * @param gameId
 * @text Game ID
 * @desc Your Game ID from HB Games (number at end of URL)
 * 
 * @param highscoresToken
 * @text Highscores Token
 * @desc Your Game's high scores token from HB Games (on game settings)
 * 
 * @param highscoreRows
 * @text Highscore Rows
 * @desc max number of top scores to show in the high score window
 * @type number
 * @default 10
 * @min 1
 * 
 * @param spacer
 * @text --- ignore below here (MV compatibility hack)
 * 
 * @param nothing
 * @text This does nothing (MV compatibility hack)
 * 
 * @command postHighScore
 * @arg name
 * @text Actor ID who has the player name as their name
 * @type actor
 * @default 1
 * @arg score
 * @text Variable ID containing the high score
 * @type variable
 * @default 1
 * @arg type
 * @text type of score (1st char is invisible for)
 * @type string
 * @default 1Score
 * 
 * @command readHighScore
 * @arg name
 * @text Actor ID to store the name in
 * @type actor
 * @default 2
 * @arg score
 * @text Variable ID to store the high score in
 * @type variable
 * @default 1
 * @arg index
 * @text Variable ID containing the index to read (0 is highest score)
 * @type variables
 * @default 2
 * 
 * @command showHighScore
 * @arg bgType
 * @text background type
 * @type select
 * @option normal
 * @value 0
 * @option dim
 * @value 1
 * @option transparent
 * @value 2
 */
var Imported = Imported || {};
Imported.HBHS = true;
var CC = CC || {};
class HBHighScores {
    constructor() {
        const params = PluginManager.parameters("HBHS");
        this.gameId = params["gameId"];
        this.token = params["highscoresToken"];
        this.rows = parseInt(params["highscoreRows"] || "10");
        this._lastSuccess = false;
        this._highscores = [];
    }
    postHighScore(interpreter, args) {
        const user = $gameActors.actor(JSON.parse(args.name)).name();
        const score = $gameVariables.value(JSON.parse(args.score));
        this.sendHighScore(interpreter, user, score, args.type);
    }
    readHighScore(interpreter, args) {
        const index = JSON.parse(args.index);
        $gameActors.actor(JSON.parse(args.name)).setName(this.highscoreName(index));
        $gameVariables.setValue(JSON.parse(args.score), this.highscoreValue(index));
    }
    sendHighScore(interpreter, user, score, type) {
        // block interpreter
        interpreter.setWaitMode("CC.HBHS.fetch");
        let qs = new URLSearchParams({
            "h": this.token + "@" + score + "@" + user + "@" + this.gameId + "@" + type
        })
        let url = new URL("https://www.arpgmaker.com/highscores.php?" + qs.toString());
        fetch(url, {"method": "GET", "mode": "cors"})
            .then(response => {
                this._lastSuccess = response.ok;
                return response.text()
            })
            .then(text => {
                this.processHighScores(text);
                // unblock interpreter
                interpreter.setWaitMode("");
            })
            .catch(error => {
                console.error(error);
                this._lastSuccess = false;
                // unblock interpreter
                interpreter.setWaitMode("");
            });
    }
    processHighScores(text) {
        try {
            let data = JSON.parse(text);
            let scores = [];
            if(data["highscores"]) {
                for (const [key, value] of Object.entries(data.highscores)) {
                    scores.push({"u": key, "s": value});
                }
                // sort descending by score
                scores.sort((a,b) => b.s - a.s);
                this._highscores = scores;
            }
        } catch(error) {
            console.warn(error);
            this._lastSuccess = false;
        }
    }
    lastSuccess() { return this._lastSuccess; }
    highscoreCount() { return this._highscores.length; }
    highscoreName(index) { return this._highscores[index].u || ""; }
    highscoreValue(index) { return this._highscores[index].s || 0; }
}
class Window_HBHighScores extends Window_Base {
    constructor() {
        if(Utils.RPGMAKER_NAME === "MV") {
            super(0,0,0,0);
        } else {
            // MZ (+?)
            super(new Rectangle(0,0,0,0));
        }
        this.hide();
    }
    refresh() {
        let c = Math.min(CC.HBHS.rows, CC.HBHS.highscoreCount());
        let w = Math.max(this.textWidth("High Scores"), Graphics.boxWidth / 2);
        let h = this.fittingHeight(CC.HBHS.rows + 2);
        for(let i=0; i<c; i++) {
            w = Math.max(w, this.textWidth(CC.HBHS.highscoreName(i) + " " + CC.HBHS.highscoreValue(i)));
        }
        if(Utils.RPGMAKER_NAME === "MV") {
            w += this.standardPadding() * 2;
        } else {
            // MZ (+?)
            w += $gameSystem.windowPadding() * 2;
        }
        this.move(Math.floor((Graphics.boxWidth - w) / 2),
                  Math.floor((Graphics.boxHeight - h) / 2),
                  w, h);
        this.createContents();
        this.resetFontSettings();
        this.drawText("High Scores", 0, 0, this.contentsWidth(), "center");
        for(let i=0; i<c; i++) {
            this.drawText(CC.HBHS.highscoreName(i), 0, this.lineHeight() * (2 + i), this.contentsWidth());
            this.drawText(CC.HBHS.highscoreValue(i), 0, this.lineHeight() * (2 + i), this.contentsWidth(), "right");
        }
    }
    update() {
        if(!this.active) return;
        if(Input.isTriggered("ok")) {
            this.hide();
            this.deactivate();
        }
    }
}
CC.HBHS = new HBHighScores();
CC.HBHS.Game_Interpreter_updateWaitMode = Game_Interpreter.prototype.updateWaitMode;
Game_Interpreter.prototype.updateWaitMode = function() {
    if(this._waitMode === "CC.HBHS.fetch") {
        return true; // blocked on http request    
    }
    if(this._waitMode === "CC.HBHS.show") {
        const w = SceneManager._scene._highscoreWindow;
        return w && w.active; // block until window dismissed
    }
    
    return CC.HBHS.Game_Interpreter_updateWaitMode.call(this);
}
CC.HBHS.Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
Scene_Map.prototype.createAllWindows = function() {
    CC.HBHS.Scene_Map_createAllWindows.call(this);
    this._highscoreWindow = new Window_HBHighScores();
    this.addWindow(this._highscoreWindow);
}
Scene_Map.prototype.showHighScore = function(bgType) {
    this._highscoreWindow.refresh();
    this._highscoreWindow.setBackgroundType(bgType);
    this._highscoreWindow.show();
    this._highscoreWindow.activate();
}
if(Utils.RPGMAKER_NAME === "MV") {
    // MV - bind plugin commands via Game_Interpreter
    CC.HBHS.Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        // MV only
        if(command === "postHighScore") {
            return CC.HBHS.postHighScore(this, {"name": args[0], "score": args[1], "type": args[2] || "1Score"});
        }
        if(command === "readHighScore") {
            return CC.HBHS.readHighScore(this, {"name": args[0], "score": args[1], "index": args[2]});
        }
        if(command === "showHighScore") {
            SceneManager._scene.showHighScore(parseInt(args[0]));
            this._waitMode = "CC.HBHS.show";
        }
        return CC.HBHS.Game_Interpreter_pluginCommand.call(this, command, args);
    };
} else {
    // MZ (+?) - bind plugin commands via plugin manager
    PluginManager.registerCommand("HBHS", "postHighScore", function(args) {
        CC.HBHS.postHighScore(this, args);
    });
    PluginManager.registerCommand("HBHS", "readHighScore", function(args) {
        CC.HBHS.readHighScore(this, args);
    });
    PluginManager.registerCommand("HBHS", "showHighScore", function(args) {
        SceneManager._scene.showHighScore(parseInt(args.bgType));
        this._waitMode = "CC.HBHS.show";
    });
}
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top