
Here’s my claim, and the rest of this post is the evidence: the engine is the least important decision in a small game. Art decides whether people play it, the server decides whether it’s fair, and the store decides whether anyone finds it. Splat, our third game template after Skirmish and Dash, is a first person team paintball shooter, four against four, in 3D, online, written in Java with Codename One. It’s the kind of game people assume needs a commercial engine. Watch it first and judge for yourself.






It’s paintball, and it never stops making fun of itself. Every match opens with a mission briefing read in the gravest voice available, and every hit stamps a big red PAINTED across your goggles.
The lesson: the engine isn’t the star
I’ll start with what surprised me, because it’s the most useful thing in this post.
The first playable Splat drew its fields and players from shapes generated in code. The networking worked, the shots were lag compensated, the referee caught cheaters. It looked like a tech demo, and nobody wanted to play it for more than a minute.
Then we replaced the shapes with real art: CC0 models from Quaternius and Kenney, photo scanned props and ground textures from Poly Haven, players built with MakeHuman and type set in Rajdhani. Nothing else changed, and it was suddenly a game.
Only after that did we add the engine features. Fog with height falloff, PBR materials, directional shadows and post processing all went into the framework because the art deserved them, not the other way around. If you’re building a game, budget for the artist first. A better renderer makes good art look great. It does nothing for boxes.
CC0 means none of those artists asked for credit. We credit them anyway, in scripts/splat/ART.md, along with the steps to re-skin every model.
What the framework gained
Building Splat added these to com.codename1.gpu, on every GPU backend: OpenGL in the simulator and on Linux, Android, WebGL in the browser, Metal on iOS and Direct3D 11 on Windows.
Environment: exponential fog with height falloff, hemisphere ambient light, exposure and tone mapping, and a procedural sky throughEnvironment.setSky.Material.Type.PBR: metallic and roughness, emissive and vertex color, filled from glTF.- Shadows: directional shadow maps through
Light.setShadows, softened with percentage closer filtering. PostProcesson aGameView: bloom, color grading, vignette and grain.GpuCapabilities: on a device without shadow or offscreen support the scene draws directly instead of failing.



The server is the referee
In a shooter, whoever decides a hit decides the game. If the phone decides, a modified client wins every fight. Splat’s backend runs the match and the phone predicts:
predicts movement and shots] -->|inputs over WebSocket| S[Backend referee
30 Hz fixed-point rules] S -->|authoritative state| P S --> R[Rewinds to the shooter's view
to check the hit] S --> A[Anti-cheat flags, XP, rating] S --> RP[Replays and leaderboards]
The rules module is fixed point and deterministic, and the same code runs on the phone and on the server. A golden corpus of recorded matches replays in CI, so a change that alters the outcome of a past match fails the build. Shots are lag compensated: the server rewinds to what the shooter saw, within a cap, so a hit that looked right on a slow connection counts.
Around the match there’s matchmaking by skill, parties with lobby codes and WebRTC voice chat, instant replays, a shop with store receipts checked against Apple and Google, and an admin console with moderation. The sound effects, music and ambience are all synthesized in code, so no audio files ship at all.
Build your own field
The mission builder lets players design a field on the phone or the desktop, play test it against bots and publish it to friends. You build one half and the other half mirrors it, so every field is fair.
Steam: the console you’re allowed to ship on
Last week I wrote that shipping on consoles needs SDKs behind NDAs. Steam is the exception. Valve’s Steam Machine and the Steam Deck run Linux, Steamworks is open to any developer who pays the app fee, and the same binary runs on Linux desktops. It’s the one major open console.
Codename One publishes to Steam with the native Linux port: one native executable, no JVM, packaged as a Steam depot and uploaded with SteamPipe. Set the app id:
codename1.arg.steam.appId=480
codename1.arg.steam.branch=betaThen build locally, or let the cloud do it:
mvn cn1:buildSteamLocal # depot in linux/target/steam
mvn cn1:steamUpload -Dsteam.user=builder # upload with SteamCMD
mvn cn1:buildSteam # or build in the cloudIn the build cloud console you save your Steam builder account once, then press “Submit to Steam” or turn on submitting every build. Valve only lets an upload go live on a beta branch, so promotion to the default branch and Valve’s review happen on the Steamworks site, as they do for everyone.
Controllers and Steam features from portable code
Games on Steam are expected to work with a controller, and players on the Deck and in Big Picture mode often have nothing else. Every gamepad Steam knows about arrives through Gamepads, and forms can be navigated with the D-pad with no code at all:
Gamepad pad = Gamepads.getGamepad(0);
if (pad != null && pad.isConnected()) {
float x = pad.getAxis(Gamepad.AXIS_LEFT_X); // -1..1, dead zone applied
boolean jump = pad.isButtonDown(Gamepad.BUTTON_A);
}Achievements, stats, cloud saves, rich presence, DLC ownership and the overlay go through GameServices. Off Steam the calls do nothing, so the same game builds for iOS and Android unchanged:
if (GameServices.isAvailable()) { // true on Steam
GameServices.unlockAchievement("FIRST_WIN");
GameServices.setStat("wins", GameServices.getStatInt("wins", 0) + 1);
GameServices.commit();
GameServices.writeSave("slot1", saveBytes);
}The app starts full screen, text fields open Steam’s on-screen keyboard in Gaming Mode, and isControllerFirst() tells you when to show controller prompts instead of mouse hints.
A few things the Steam runtime doesn’t provide: there’s no embedded browser, no GStreamer media playback and no secure storage. Games rarely need them, but check the Steam chapter before you plan around one.
Learn more
Generate Splat from the Initializr, read the implementor’s guide in the developer guide, and swap the art for your own. Then point it at Steam. If you ship a game this way, send it to me. I’d love to play it.