We're happy to share this amazing tutorial by our Discord member, debugChicken. We appreciate it and hope it helps you build games for Steam!
My name is Danilo Ganzella, and I’m the developer of Wirewalk. In this tutorial, I’ll be covering how to export a Cocos Creator game to Electron in order to be able to sell them on Steam - Windows, Mac, and Linux, as I did with my game.
Did you know that Cocos Creator itself is made in Electron? Electron is a great tool that allows us to embed HTML5 Applications in Desktop Applications. They achieve it by running a Google Chrome webview which will handle the rendering of the HTML/canvas & javascript execution.
P.S. All command lines here will be running from Windows VSCode Terminal or Windows Powershell.
Getting to Know Node.js
As primarily a game dev, I always struggled with all kinds of web development/server development. Node.js is a powerful tool that enables the development of many types of applications and other tools by using javascript as a common language to deploy to many native Platforms. In order to install Electron, first, you need to install Node.js. (pick the latest stable version). There are many great tutorials online to help you install it on your computer.
Installing Electron
After installing Node.js, just follow these instructions. (pick the latest stable version)
Creating an Electron Project
Open VSCode, find any folder that you want for the project, then type:
npm init -y
npm i --save-dev electron
Afterward, create a file called index.js in the same folder, with the following content:
const { app, BrowserWindow } = require('electron')
app.on('window-all-closed', function() {
if (process.platform != 'darwin')
app.quit();
});
app.on('ready', function() {
mainWindow = new BrowserWindow({
width: 768,
height: 768,
show: true,
fullscreen: false,
resizable: false,
frame: true,
title: "MyGame"
});
mainWindow.removeMenu();
mainWindow.on('closed', function() {
mainWindow = null;
});
});
You can test it by executing
electron .
If it opens a blank application window with the title “MyGame”, you’re done. We now need to provide the cocos exported project to the BrowserWindow.
You can have more details about creating an electron app here.
Exporting the Project from Cocos creator to electron
Now let's open the Cocos Creator application and export the project as Web Desktop.
You will want to export it somewhere inside the Electron project. Create a folder called “cocosExport” child of the root folder of your Electron project.
Opening the Exported Project & Running the game
Now back to electron’s index.js, what you want to do is to add these lines anywhere after creating the mainWindow Object.
mainWindow.loadURL('file://' + __dirname + '/cocosExport/web-desktop/index.html').then(() =>{
});
Now simple execute
electron .
Hooray! The game should now be up and running!
You may need to make additional adjustments to the Exported HTML from Cocos and also to Electron’s BrowserWindow, so it looks smooth.
Ok, but how do we integrate the Steamworks API?
Installing Greenworks & Steamworks API
Because Steamworks does not support Electron natively (not even in JavaScript), you will need to download a separate tool called Greenworks. This tool is also a Node.js package that accesses the native compiled C++ Functions of the Steam API by exposing an interface in JavaScript.
Download Greenworks & Steamworks API
The first thing you want to do is download the master branch of the Greenworks repository and put it in a separate folder.
Now, download the Steamworks API from the Steam developer portal. You will need to put the Steamworks API in the deps folder that is in the root of the Greenworks, and rename the Steamworks folder to steamworks_sdk.
We will need to build Greenworks, so the binary runs properly on the version of Node.js you downloaded and already installed.
Building Steamworks
First, start by running Greenworks in the root folder of Greenworks, so it installs all dependencies properly.
npm install
Then you can now build Greenworks itself. For that, we will need a tool called node-gyp, which is a build tool for node.js. First, install node-gyp globally by running:
npm install node-gyp -g
Then build the Greenworks, by running this command in the root of the Greenworks project:
node-gyp rebuild --target=<electron_version> --arch=x64 --dist-url=https://atom.io/download/atom-shell
Change electron_version with the version of electron you installed. For example 12.0.7
The relevant file will be inside:
build\Release\greenworks-win64.node
You can have more details about building Greenworks here.
Copying Greenworks to your Electron project and loading it
Now, there are a few files you will need to copy to your Electron project. First, create a folder called Greenworks in the root folder of your Electron project. Inside it, copy both the greenworks.js file and the lib folder present in the root file of the Greenworks project.
Inside the lib folder, replace the file greenworks-win64.node with the one you build on the previous step.
Awesome! Now just add the following line on the top of the index.js file of your electron project:
const greenworks = require('./greenworks/greenworks'); |
And you’re done! You can now log the Greenworks object to see which methods it has.
Initiating Greenworks
Now, to initiate Greenworks, the first thing you need to do is to create a file called steam_appid.txt that contains your app id and nothing else. The app id is the one in the store. For example, for my game Wirewalk, the steam is 1636700.
https://store.steampowered.com/app/1636700/Wirewalk/
P.S. Remember not to copy this .txt file to the final build on the last step of the tutorial since it's only required for testing purposes.
Now you can finally initiate Greenworks!
let relaunch = greenworks.restartAppIfNecessary(1636700);
if(relaunch){
app.quit();
}
else{
if(greenworks.init()){
console.log('Steam inited successfully');
loadWindow();
}
else{
app.quit();
}
}
Initing Greenworks should be the first thing you do after receiving the ‘ready’ event.
The restartAppIfNecessary function prevents the game from being launched outside of steam and restarting it by opening it from steam.
But this is only for release -- because the presence of the file steam_appid.txt will make restartAppIfNecessary always return false.
Also, when testing, make sure that steam is open and running and that you own the game id.
Communicating Between Cocos & Electron - Achievements
Now, let’s make an example of using a Steamworks functionality by implementing steam Achievements. First, start by creating an achievement on the Steamworks website:
Keep in mind that the API Name is the one we will need to pass to greenworks. In this case, bad_cats.
First, on Cocos Creator, let’s make a class to handle communicating with Electron that you can call after your achievement logic determines its time to release an achievement. This code is in Typescript.
export default class Electron
{
static releaseAchievement(id: string): void
{
(window as any).electron.ipcRenderer.invoke('releaseAchievement', id);
}
}
Now in cocos, you will call
Electron.releaseAchivement('bad_cats');
And the method will look for an object called “electron” in the global scope. This “electron” object will be our door to communicating with the Electron process from the Cocos process. We will be communicating by calling the “invoke” method, with a string parameter to identify which callback to call followed by any number of parameters.
If you are playing the game on the editor, or a website, you may want to test against the existence of the global “electron” object because it will not be defined in every context.
We now need to define this object “electron”. I found the easier way to do is this by editing the resulting HTML that is generated by cocos when you build your project (index.html that is in the cocosExport folder), by adding the following to the output HTML, right before the <body> tag:
<script>
window.electron = require('electron');
</script>
We can't make this require(‘electron’) on the game code itself because cocos will try to find the “electron” js file when building and give an error because it won’t be able to find it.
Next, we need to tell Electron that the HTML of the browser window can communicate with the native node process. Essentially making the electron.js available. For that, we need to pass these extra parameters when creating the BrowserWindow.
webPreferences: { nodeIntegration: true, contextIsolation: false }
It's usually a security flaw to enable these, but it's only mostly if the websites are not loaded locally. Since our game loads only local HTML files, it’s ok, since no weird code will be dynamically loaded. Disabling contextIsolation makes it easier to expose global objects in the window object.
And last, add the receiver function on Electron. This code can be added anywhere after the mainWindow is created.
ipcMain.handle('releaseAchievement', (event, achievementString) => {
console.log('releasing achievement', achievementString);
greenworks.activateAchievement(achievementString);
});
The “handle” method is the one that matches with the “invoke”. In this example, after Electron receives the “releaseAchievement” message, it calls Greenworks “activateAchievement” to tell it to activate the achievement for the user.
Test your game once again by running
electron .
Exporting Project to Windows, Mac, or Linux
First, install electron-packager globally by running.
npm install electron-packager -g
And finally, run the following line to build your game! In this case, for windows.
electron-packager . MyGame --platform=win32 --arch=x64 --overwrite
But you can also build Mac and Linux by changing the --platform value. A list of available platforms can be found in the electron-packager documentation.