I recently released a game written entirely in JavaScript/TypeScript on Steam.
I thought I'd be helpful to share the process that goes into doing that.
Goal
- Build a web game that's playable on web portals like Itch.io or Newgrounds.
- Package that game into a desktop executable
- Submit the game into Steam
Setting up a local server
First part is to setup a local server that serves the game. It's useful for local testing and see how the game plays like in the web browser.
To stick with just one language, we can use NodeJS. This is written in typescript:
import express, { Application, Request, Response } from 'express';
import serve from "express-static";
import fs from "fs";
import { extname } from 'path';
const app: Application = express();
const PORT = process.env.PORT || 8000;
app.get("/", async (req: Request, res: Response): Promise<void> => {
res.writeHead(200, { "Content-Type": "text/html" });
const html = await fs.promises.readFile(`./public/index.html`);
res.write(html);
res.end();
});
//@ts-ignore
app.use(serve("./public", null));
app.listen(PORT, (): void => {
console.log(`Server Running here 👉 https://localhost:${PORT}`);
});
Files will be located in a public folder, with the entry point being index.html.
The game is then available on https://localhost:8000
Note that we want the NodeJS to serve static files, no dynamic content for the game. If your game relies on a NodeJS server, it's unable to play on its own in Web portals.
Use NWJS to package the game
To package the web game into an executable, there are options like Electron, but I prefer NWJS because it creates less bulky executables.
Install dependencies.
Install nw-builder as node dependencies.
npm i nw-builder
Setup package.json
Setup a new package.json file in the "public" folder where the game is located. This is needed by NWJS.
{
"name": "game-id",
"version": "1.0.0",
"main": "index.html",
"author": "Jack Le Hamster <jacklehamster@gmail.com> (https://jacklehamster.github.io/)",
"company": "Dobuki Studio",
"country": "U.S",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/jacklehamster/my-git-repo.git"
},
"bugs": {
"url": "https://github.com/jacklehamster/my-git-repo/issues"
},
"homepage": "https://github.com/jacklehamster/my-git-repo#readme",
"window": {
"title": "My Game Title",
"width": 1000,
"height": 800,
"fullscreen": false,
"kiosk": false,
"resizable": true,
"always_on_top": false,
"show_in_taskbar": true,
"frame": true,
"transparent": false,
"position": "center",
"icon": "icon.png"
},
"nodejs": false
}
Trigger the packaging
To execute the packaging, I setup my NodeJS server to build when I open the game with the option ?release. So the execution happens in the NodeJS file:
...
import NwBuilder from "nw-builder";
...
const nw = new NwBuilder({
files: "./public/**/**",
platforms: ["win", "osx64", "linux"],
flavor: "normal",
macIcns: "./public/app.icns",
});
fs.rmdirSync('build', { recursive: true });
await nw.build();
This results in a Win64, Win32, Mac, Linux64, Linux32 binaries being generated in the build folder.
Zip the result
For both web portal release and submitting binaries to Steam, you need to zip the artifacts. For that, you can use "archiver" in NodeJS:
npm i archiver
...
import archiver from "archiver";
...
const gameName = "my-awesome-game";
function zipPublic(source: string, out: string) {
const archive = archiver.create("zip", { zlib: { level: 9 } });
const stream = fs.createWriteStream(out);
return new Promise((resolve, reject) => {
archive
.directory(source, "", {
name: gameName,
})
.on("error", (err: unknown) => reject(err))
.pipe(stream);
stream.on("close", () => {
resolve(archive);
});
archive.finalize();
});
}
await zipPublic("public", `${gameName}.zip`);
// Generate archive for web portals
await zipPublic("public", `${gameName}-web.zip`);
// Generate archive for Steam artifacts
await zipPublic(`build/${gameName}/win32`, `${gameName}-win32.zip`);
await zipPublic(`build/${gameName}/win64`, `${gameName}-win64.zip`);
await zipPublic(`build/${gameName}/osx64`, `${gameName}-mac.zip`);
await zipPublic(`build/${gameName}/linux32`, `${gameName}-linux32.zip`);
await zipPublic(`build/${gameName}/linux64`, `${gameName}-linux64.zip`);
Publishing the game into Steam
Note that this process takes a few weeks, so it's best you start long before you plan to release the game.
Join Steamworks
Go to https://partner.steamgames.com/ and Join steamworks. You can reuse your Steam gamer account or create a new one.
The steps are straightforward on the site. Note that you will need to:
- Provide some bank/tax information in order to get paid.
- Pay a $100 fee for each game release.
It takes a few days to get approved for joining the Steam program after you paid the fee. Then you can start submitting your game.
Provide assets for the store
There's a checklist you can follow. You have to provide several images in different format for showing in the store (logo, hero images, backgrounds, icons). So have your image editor handy for that.
Here are some example assets submitted for my Steam release:
It's also good to have a game trailer. For that, I use Quicktime to record the screen while playing some levels of my game, and used iMovie to edit.
You can checkout my game trailer here.
Submit binaries for the game.
Submit the zip files you previously generated into the Build section. I won't go into the details for it. If it's a bit complicated, you can contact Steam support and they generally respond within a day or two.
Both store and builds require a review from the Steam staff, which takes 2-3 days.
Once the build is approved, you can still update it without needing to go through the review process again. (just don't take advantage of that to sneak back in an element that the Steam task asked you to remove during review!)
Once your checklist is completed, you'll have your game ready for review. There might be some back and forth with the Steam staff if you need to make some adjustments to your game. Then a couple weeks later, your game can be released.
Game promotion
You might know better than me how to use Social media to promote your game. One of the things you can do is write dev blog posts (which is what I'm doing right now!).
But one notable feature of Steam is to get curators to test and review your game. Some curators have contacted me directly for that. You use this feature called Curator Connect, which lets you hand some keys for free to a few popular curators and they can play and recommend your game.
I'm a bit new to this, so I haven't been able to see how effective that is so far. You might be reluctant to give away game keys for free, but it's not really like you're losing money from it. And it might help spread the word about your game.
So how is it going so far?
Well, the game just got released a couple days ago so I'm still trying to spread the word and get some traction.
It's available here to purchase on Steam:
https://store.steampowered.com/app/2258040/World_of_Turtle
But I think the main thing is that knowing the process, you can repeat the process again for other games. And making the game is really the easy part, isn't that right?!
Well, I hope this post was helpful to you. If you enjoy it, please spread the word and feel free to try out my game!
Top comments (2)
Congrats my dude!
Does anyone know if there are promo codes on Steam for getting different game bonuses? I’m particularly interested in Rust bonuses. I feel like that would be the safest option. I already know a few sites and resources like bandit.camp codes, which I’ve used a few times to get some awesome bonuses. But if you know any other recommendations, I’d love to hear them.