Posted April 01, 2022 by Spelmakare Jens Nilsson AB
When Hive P v. S was released on Steam, I used Greenworks to add support for the Steamworks SDK. I had not intended to make any use of it, except to know when the Steam overlay was opened so that I could pause the game.
While fixing some issues that have shown up as the game has been played on Steam, I decided to implement Steam Achievements. A look a the documentation showed that it was a simple process with few steps.
Achievement for main objective of the game. Image Jens Nilsson
As I use Electron to package my game as a native application, I had some additional issues to solve. The process that renders the game is separate from the process that runs the application, this to make the application safer, as in essence it is a browser with unlimited access to the computer. While player progress happens in the render, Greenworks runs in the application process, therefor I had to add means for the two of them to communicate. For a little while I thought I had to add electron as a dependency to the render code, which would have complicated the otherwise clean code that makes it possible to have same code for web, mobile devices and computers. Thankfully, after some searching I found an example of how to implement it in a way that added no new dependencies to the render code.
The original solution: https://stackoverflow.com/a/68581487
How I used it to send Steam achievement notices from Electron render to Electron main.
//preloads.ts file import { ipcRenderer, contextBridge } from "electron"; // Setup sending notices about achievements. contextBridge.exposeInMainWorld("electron", { notificationApi: { sendNotification(achievement: string) { ipcRenderer.send('SteamAchievement', achievement); } } }); //index.ts file (main electron) ipcMain.on('SteamAchievement', (event, achievement) => { // Greenworks code to check if achievement should be acheived or already is. }); //app.ts file (render process) globalThis.electron.notificationApi.sendNotification(achievement);
For sending the notices I created a simple helper method, that I could call in the appropriate places to trigger an achievement. To keep it nice and tidy I made an enum with the 40 achievements and used it with the helper method.
// Enum export enum Achievement { HighScore = "HIGHSCORE", Tutorial = "TUTORIAL" // ... etc } // Helper method steamAchievementTrigger = (achievement: Achievement) => { // ... Some additional code to only do when in electron, and only once. // Send notification to electron. globalThis.electron.notificationApi.sendNotification(achievement); } // ...In the high score check, trigger achievement for not good enough for high score table. if (noHighScore) { // Achievement this.props.app.steamAchievementTrigger(Achievement.NoHighScore); } // Greenworks in the main process // ... Some additional greenworks code to check if achievement not already achieved. // Local set it achieved. greenworks.activateAchievement(achievement, () => console.log(`Achieved: ${achievement} ok!`), (error: any) => console.log(`Failed: ${achievement}, error: ${error}`) ); // Send it to Steam for permanent storage. greenworks.storeStats( () => console.log(`Stored: ${achievement} ok!`), (error: any) => console.log(`Failed storing: ${achievement}, error: ${error}`) );
That sums it all up for adding the achievements, only 39 more this.props.app.steamAchievementTrigger(Achievement.?); was added to the code... Or rather 9 more, as 20 are for levels and 10 are for power ups, which could be triggered in two places.
Power up achievement not yet achieved. Image Jens Nilsson
The achievements are not yet live on Steam, currently testing them in the beta before pushing them live.
Thanks for reading, next week it is back to TMoS!