Sie kennen diese mühsamen Aufgaben, die Sie bei der Arbeit erledigen müssen: Aktualisieren von Konfigurationsdateien, Kopieren und Einfügen von Dateien, Aktualisieren von Jira-Tickets.
Mit der Zeit summiert sich das. Das war sehr oft der Fall, als ich 2016 für ein Online-Spieleunternehmen arbeitete. Die Arbeit konnte manchmal sehr lohnend sein, wenn ich konfigurierbare Vorlagen für Spiele erstellen musste, aber etwa 70% meiner Zeit verbrachte ich damit, Kopien dieser Vorlagen zu erstellen und neu gestylte Implementierungen bereitzustellen.
Was ist ein Reskin?
Die Definition eines Reskins in diesem Unternehmen war die Verwendung derselben Spielmechanik, Bildschirme und Elementpositionierung, jedoch mit Änderung der visuellen Ästhetik wie Farbe und Assets. Im Kontext eines einfachen Spiels wie „Stein, Papier, Schere“ würden wir eine Vorlage mit grundlegenden Assets wie unten erstellen.

Aber wenn wir einen Reskin davon erstellen, würden wir andere Assets verwenden und das Spiel würde immer noch funktionieren. Wenn Sie sich Spiele wie Candy Crush oder Angry Birds ansehen, werden Sie feststellen, dass sie viele Varianten desselben Spiels haben. Normalerweise Halloween-, Weihnachts- oder Oster-Releases. Aus geschäftlicher Sicht ergibt das durchaus Sinn.
Nun... zurück zu unserer Implementierung. Jedes unserer Spiele würde dieselbe gebündelte JavaScript-Datei verwenden und eine JSON-Datei laden, die unterschiedliche Inhalte und Pfade zu Assets enthielt. Das Ergebnis?

Das Gute daran, konfigurierbare Werte in eine JSON-Datei zu extrahieren, ist, dass Sie die Eigenschaften ändern können, ohne das Spiel erneut kompilieren/erstellen zu müssen. Mit Node.js und dem ursprünglichen Breakout-Spiel von Mozilla erstellen wir ein sehr einfaches Beispiel dafür, wie Sie eine konfigurierbare Vorlage erstellen und daraus Releases erstellen können, indem Sie die Kommandozeile verwenden.
Unser Spiel
Dies ist das Spiel, das wir erstellen werden. Reskins des MDN Breakout, basierend auf dem vorhandenen Quellcode.

Die Primärfarbe färbt den Text, das Paddel, den Ball und die Blöcke, und die Sekundärfarbe färbt den Hintergrund. Wir werden mit einem Beispiel fortfahren: ein dunkelblauer Hintergrund und ein helles Himmelblau für die Vordergrundobjekte.
Voraussetzungen
Sie müssen sicherstellen, dass Folgendes vorhanden ist
- Sie haben Git installiert – https://git-scm.de/downloads
- Sie haben Node installiert – https://nodejs.org/en/download
- Sie haben ein GitHub-Konto – https://github.com
- Sie haben das Repository lokal geklont – https://github.com/smks/nobot-examples.git
- Sie haben im Stammverzeichnis des
nobot-examples-Projektsnpm installausgeführt. - Schließlich starten Sie den lokalen Server, indem Sie im Stammverzeichnis des Projekts über ein Terminal
npm run gameServeausführen.
Wir haben die ursprüngliche Firefox-Implementierung angepasst, sodass wir zuerst die JSON-Datei lesen und dann das Spiel mit HTML Canvas erstellen. Das Spiel liest eine Primärfarbe und eine Sekundärfarbe aus unserer game.json-Datei.
{
"primaryColor": "#fff",
"secondaryColor": "#000"
}
Wir verwenden Beispiel 20 aus dem Buch Automating with Node.js. Der Quellcode ist hier zu finden.
Öffnen Sie eine neue Kommandozeile (CMD für Windows, Terminal für Unix-ähnliche Betriebssysteme) und wechseln Sie in das folgende Verzeichnis, nachdem Sie das Repository lokal geklont haben.
$ cd nobot-examples/examples/020
Denken Sie daran, dass der Spielserver in einem separaten Terminal laufen sollte.
Unsere JSON-Datei befindet sich neben einer index.html-Datei in einem Verzeichnis namens template. Dies ist das Verzeichnis, aus dem wir jedes Mal kopieren, wenn wir eine neue Veröffentlichung/Kopie erstellen möchten.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Paddle Game</title>
<style>
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<canvas id="game" width="480" height="320"></canvas>
<script type="text/javascript" src="../../core/game-1.0.0.js"></script>
</body>
</html>
Wie oben gezeigt, verweist jedes von uns veröffentlichte Spiel auf dieselbe Kern-JavaScript-Datei. Werfen wir einen Blick auf unsere JavaScript-Implementierung unter dem Verzeichnis core.
Konzentrieren Sie sich nicht zu sehr auf die Mechanik, wie das Spiel funktioniert, sondern eher darauf, wie wir Werte in das Spiel einfügen, um es konfigurierbar zu machen.
(function boot(document) {
function runGame(config) {
const canvas = document.getElementById('game');
canvas.style.backgroundColor = config.secondaryColor;
// rest of game source code gets executed... hidden for brevity
// source can be found here: https://git.io/vh1Te
}
function loadConfig() {
fetch('game.json')
.then(response => response.json())
.then(runGame);
}
document.addEventListener('DOMContentLoaded', () => {
loadConfig();
});
}(document));
Der Quellcode verwendet ES6-Funktionen und funktioniert möglicherweise nicht in älteren Browsern. Führen Sie es mit Babel durch, falls dies ein Problem für Sie darstellt.
Sie können sehen, dass wir darauf warten, dass der DOM-Inhalt geladen wird, und dann rufen wir eine Methode namens loadConfig auf. Dies wird eine AJAX-Anfrage an game.json stellen, unsere JSON-Werte abrufen und sobald diese abgerufen wurden, wird das Spiel initialisiert und die Stile im Quellcode zugewiesen.
Hier ist ein Beispiel für die Konfiguration, die die Hintergrundfarbe festlegt.
const canvas = document.getElementById('game');
canvas.style.backgroundColor = config.secondaryColor; // overriding color here
Nachdem wir nun eine Vorlage haben, die konfigurierbar ist, können wir mit der Erstellung eines Node.js-Skripts fortfahren, das es dem Benutzer ermöglicht, entweder den Namen des Spiels und die Farben als Optionen an unser neues Skript zu übergeben, oder den Benutzer auffordert: den Namen des Spiels, die Primärfarbe und dann die Sekundärfarbe. Unser Skript wird eine Validierung durchführen, um sicherzustellen, dass beide Farben im Hex-Code-Format vorliegen (z. B. #101b6b).
Wenn wir einen neuen Game-Reskin erstellen wollen, sollten wir diesen Befehl ausführen können, um ihn zu generieren
$ node new-reskin.js --gameName='blue-reskin' --gamePrimaryColor='#76cad8' --gameSecondaryColor='#10496b'
Der obige Befehl wird das Spiel sofort erstellen, da er die drei benötigten Werte für die Veröffentlichung des Reskins enthält.
Wir erstellen dieses Skript new-reskin.js, und diese Datei führt die folgenden Schritte aus
- Es liest die Optionen, die in der Kommandozeile übergeben wurden, und speichert sie als Variablen. Optionen können durch Betrachten des process-Objekts (
process.argv) gelesen werden. - Es validiert die Werte und stellt sicher, dass der Spielname und die Farben nicht undefiniert sind.
- Wenn es Validierungsprobleme gibt, fordert es den Benutzer auf, diese korrekt einzugeben, bevor es fortfährt.
- Da es nun die Werte hat, erstellt es eine Kopie des Vorlagenverzeichnisses und platziert diese Kopie im releases-Verzeichnis und benennt das neue Verzeichnis mit dem Namen des Spiels, das wir ihm gegeben haben.
- Es liest dann die gerade erstellte JSON-Datei im releases-Verzeichnis und überschreibt die Werte mit den Werten, die wir übergeben haben (die Farben).
- Am Ende fragt es den Benutzer, ob er das Spiel im Browser öffnen möchte. Dies bietet etwas Komfort, anstatt dass wir uns die URL merken müssen.
Hier ist das vollständige Skript. Wir werden es danach durchgehen.
require('colors');
const argv = require('minimist')(process.argv.slice(2));
const path = require('path');
const readLineSync = require('readline-sync');
const fse = require('fs-extra');
const open = require('opn');
const GAME_JSON_FILENAME = 'game.json';
let { gameName, gamePrimaryColor, gameSecondaryColor } = argv;
if (gameName === undefined) {
gameName = readLineSync.question('What is the name of the new reskin? ', {
limit: input => input.trim().length > 0,
limitMessage: 'The project has to have a name, try again'
});
}
const confirmColorInput = (color, colorType = 'primary') => {
const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
if (hexColorRegex.test(color)) {
return color;
}
return readLineSync.question(`Enter a Hex Code for the game ${colorType} color `, {
limit: hexColorRegex,
limitMessage: 'Enter a valid hex code: #efefef'
});
};
gamePrimaryColor = confirmColorInput(gamePrimaryColor, 'primary');
gameSecondaryColor = confirmColorInput(gameSecondaryColor, 'secondary');
console.log(`Creating a new reskin '${gameName}' with skin color: Primary: '${gamePrimaryColor}' Secondary: '${gameSecondaryColor}'`);
const src = path.join(__dirname, 'template');
const destination = path.join(__dirname, 'releases', gameName);
const configurationFilePath = path.join(destination, GAME_JSON_FILENAME);
const projectToOpen = path.join('https://:8080', 'releases', gameName, 'index.html');
fse.copy(src, destination)
.then(() => {
console.log(`Successfully created ${destination}`.green);
return fse.readJson(configurationFilePath);
})
.then((config) => {
const newConfig = config;
newConfig.primaryColor = gamePrimaryColor;
newConfig.secondaryColor = gameSecondaryColor;
return fse.writeJson(configurationFilePath, newConfig);
})
.then(() => {
console.log(`Updated configuration file ${configurationFilePath}`green);
openGameIfAgreed(projectToOpen);
})
.catch(console.error);
const openGameIfAgreed = (fileToOpen) => {
const isOpeningGame = readLineSync.keyInYN('Would you like to open the game? ');
if (isOpeningGame) {
open(fileToOpen);
}
};
Oben im Skript fordern wir die Pakete an, die zur Durchführung des Vorgangs benötigt werden.
colors, um Erfolg oder Misserfolg durch grüne oder rote Textanzeige zu kennzeichnen.minimist, um das Übergeben von Argumenten an unser Skript zu erleichtern und diese optional zu parsen. Eingaben übergeben, ohne zur Eingabe aufgefordert zu werden.path, um Pfade zur Vorlage und zum Ziel des neuen Spiels zu konstruieren.readline-sync, um den Benutzer nach fehlenden Informationen zu fragen.fs-extra, damit wir unsere Spielvorlage kopieren und einfügen können. Eine Erweiterung des nativenfs-Moduls.opnist eine plattformübergreifende Bibliothek, die unser Spiel nach Abschluss im Browser öffnet.
Die Mehrheit der oben genannten Module wäre beim Ausführen von npm install im Stammverzeichnis des nobot-examples-Repositorys heruntergeladen/installiert worden. Die übrigen sind nativ für Node.
Wir prüfen, ob der Spielname als Option über die Kommandozeile übergeben wurde, und wenn nicht, fragen wir den Benutzer danach.
// name of our JSON file. We store it as a constant
const GAME_JSON_FILENAME = 'game.json';
// Retrieved from the command line --gameName='my-game' etc.
let { gameName, gamePrimaryColor, gameSecondaryColor } = argv;
// was the gameName passed?
if (gameName === undefined) {
gameName = readLineSync.question('What is the name of the new reskin? ', {
limit: input => input.trim().length > 0,
limitMessage: 'The project has to have a name, try again'
});
}
Da zwei unserer Werte Hex-Codes sein müssen, erstellen wir eine Funktion, die die Überprüfung für beide Farben durchführen kann: die Primär- und die Sekundärfarbe. Wenn die vom Benutzer bereitgestellte Farbe unsere Validierung nicht besteht, fragen wir so lange nach der Farbe, bis sie es tut.
// Does the color passed in meet our validation requirements?
const confirmColorInput = (color, colorType = 'primary') => {
const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
if (hexColorRegex.test(color)) {
return color;
}
return readLineSync.question(`Enter a Hex Code for the game ${colorType} color `, {
limit: hexColorRegex,
limitMessage: 'Enter a valid hex code: #efefef'
});
};
Wir verwenden die obige Funktion, um sowohl die Primär- als auch die Sekundärfarbe zu erhalten.
gamePrimaryColor = confirmColorInput(gamePrimaryColor, 'primary');
gameSecondaryColor = confirmColorInput(gameSecondaryColor, 'secondary');
Im nächsten Codeblock geben wir auf der Standardausgabe (console.log) aus, um die Werte zu bestätigen, die beim Erstellen des Spiels verwendet werden. Die folgenden Anweisungen bereiten die Pfade zu den relevanten Dateien und Verzeichnissen vor.
src zeigt auf das Vorlagenverzeichnis. destination zeigt auf ein neues Verzeichnis unter releases. Die Konfigurationsdatei, deren Werte aktualisiert werden, befindet sich unter diesem neuen Spielverzeichnis, das wir erstellen. Und schließlich konstruieren wir zur Vorschau unseres neuen Spiels die URL unter Verwendung des Pfads zum lokalen Server, den wir zuvor gestartet haben.
console.log(`Creating a new reskin '${gameName}' with skin color: Primary: '${gamePrimaryColor}' Secondary: '${gameSecondaryColor}'`);
const src = path.join(__dirname, 'template');
const destination = path.join(__dirname, 'releases', gameName);
const configurationFilePath = path.join(destination, GAME_JSON_FILENAME);
const projectToOpen = path.join('https://:8080', 'releases', gameName, 'index.html');
Im nachfolgenden Code
- Kopieren wir die Vorlagendateien in das releases-Verzeichnis.
- Nachdem dies erstellt wurde, lesen wir die JSON-Datei mit den ursprünglichen Vorlagenwerten.
- Mit dem neuen Konfigurationsobjekt überschreiben wir die vorhandenen Primär- und Sekundärfarben, die durch die Eingabe des Benutzers bereitgestellt wurden.
- Wir schreiben die JSON-Datei neu, damit sie die neuen Werte enthält.
- Sobald die JSON-Datei aktualisiert wurde, fragen wir den Benutzer, ob er das neue Spiel im Browser öffnen möchte.
- Wenn etwas schief gelaufen ist, fangen wir den Fehler ab und protokollieren ihn.
fse.copy(src, destination)
.then(() => {
console.log(`Successfully created ${destination}`green);
return fse.readJson(configurationFilePath);
})
.then((config) => {
const newConfig = config;
newConfig.primaryColor = gamePrimaryColor;
newConfig.secondaryColor = gameSecondaryColor;
return fse.writeJson(configurationFilePath, newConfig);
})
.then(() => {
console.log(`Updated configuration file ${configurationFilePath}`green);
openGameIfAgreed(projectToOpen);
})
.catch(console.error);
Unten sehen Sie die Funktion, die aufgerufen wird, wenn das Kopieren abgeschlossen ist. Sie fragt dann den Benutzer, ob er das Spiel im Browser öffnen möchte. Der Benutzer antwortet mit y oder n
const openGameIfAgreed = (fileToOpen) => {
const isOpeningGame = readLineSync.keyInYN('Would you like to open the game? ');
if (isOpeningGame) {
open(fileToOpen);
}
};
Schauen wir uns das in Aktion an, wenn wir keine Argumente übergeben. Sie sehen, dass es nicht fehlschlägt, sondern stattdessen den Benutzer nach den benötigten Werten fragt.
$ node new-reskin.js
What is the name of the new reskin? blue-reskin
Enter a Hex Code for the game primary color #76cad8
Enter a Hex Code for the game secondary color #10496b
Creating a new reskin 'blue-reskin' with skin color: Primary: '#76cad8' Secondary: '#10496b'
Successfully created nobot-examples\examples\020\releases\blue-reskin
Updated configuration file nobot-examples\examples\020\releases\blue-reskin\game.json
Would you like to open the game? [y/n]: y
(opens game in browser)
Mein Spiel öffnet sich automatisch auf meinem lokalen Server und das Spiel beginnt mit den neuen Farben. Super!

Oh... ich habe bereits ein Leben verloren. Wenn Sie nun zum releases-Verzeichnis navigieren, sehen Sie ein neues Verzeichnis namens blue-reskin. Dieses enthält die Werte in der JSON-Datei, die wir während der Skriptausführung eingegeben haben.
Unten sind einige weitere Releases, die ich durch Ausführen desselben Befehls erstellt habe. Stellen Sie sich vor, wenn Sie Spiele veröffentlichen würden, die unterschiedliche Bilder, Sounds, Beschriftungen, Inhalte und Schriftarten konfigurieren könnten, hätten Sie eine reichhaltige Bibliothek von Spielen, die auf derselben Mechanik basieren.

Noch besser: Wenn die Stakeholder und Designer all diese Informationen in einem Jira-Ticket hätten, könnten Sie die Jira-API in das Node-Skript integrieren, um diese Werte einzufügen, ohne dass der Benutzer Eingaben machen muss. Gewonnen!

Dies ist eines von vielen Beispielen, die in Automating with Node.js zu finden sind. In diesem Buch werden wir uns ein fortgeschritteneres Beispiel ansehen, das "Stein, Papier, Schere" als Grundlage für ein von Grund auf neu erstelltes Build-Tool verwendet.
- Farbdruck: http://amzn.eu/aA0cSnu
- Kindle: https://amzn.to/2JPTk7q
- Kobo: https://www.kobo.com/gb/en/ebook/automating-with-node-js
- Leanpub: https://leanpub.com/automatingwithnodejs