I made a rail shooter for Kenney Jam 2025 | Maximum Voltage

In 48 hours, I made a Starfox-inspired rail shooter

Practice. Practice. Practice.

Since Scream Jam 2024, I’ve been creating tons of small prototype projects in Godot to better learn the engine’s features. Since I wanted to focus on the coding, I’ve been using Kenney assets exclusively for these projects so I was really excited when I learned that Kenney has a yearly game jam!

Preparation

After not doing so well in my last 7 day jam, I was extremely nervous to try a 48 hour jam but decided to make the best of my time by reviewing Adam Younis’ jam devlogs for tips before the jam. I took away two points from his devlogs: make a schedule and prioritize tasks. To that end, I put together my own general purpose schedule for a 48 hour jam based on my best guess at how long each task would take. The exact time spent on each task isn’t that important but the order I work on them was carefully chosen to get the game playable as early as possible.

Expand schedule
DayTimeGoalActions
Friday11:00 AMIdeationBrainstorming - Concept Design
Friday12:00 PMSystemsMechanics - Core Loop
Friday01:00 PM"
Friday02:00 PM"
Friday03:00 PM"
Friday04:00 PMGame Playable
Friday05:00 PMWorld Design
Friday06:00 PMCore Animations
Friday07:00 PMGame Loop BookendsScore - Win Condition - Loose Condition
Friday08:00 PMLevel Design
Friday09:00 PM"
Friday10:00 PM"
Friday11:00 PM"
Saturday12:00 AM"
Saturday01:00 AMUI Design
Saturday02:00 AM"
Saturday03:00 AMGame Complete-able
Saturday04:00 AMJuice
Saturday05:00 AM"
Saturday06:00 AM"
Saturday07:00 AMStory
Saturday08:00 AMSFX
Saturday09:00 AM"
Saturday10:00 AMMusic
Saturday11:00 AM"
Saturday12:00 PMBeta Test
Saturday01:00 PMCollectables/Extras
Saturday02:00 PM"
Saturday03:00 PM"
Saturday04:00 PMTitle Screen
Saturday05:00 PMSettings Menu
Saturday06:00 PMCredits
Saturday07:00 PMCutscenes
Saturday08:00 PM"
Saturday09:00 PM"
Saturday10:00 PMCreate itch Page
Saturday11:00 PM"
Sunday12:00 AMTestingTest Game in itch
Sunday01:00 AMCreate thumbnail
Sunday02:00 AMScreenshots
Sunday03:00 AMSubmit
Sunday04:00 AM
Sunday05:00 AM
Sunday06:00 AM
Sunday07:00 AM
Sunday08:00 AM
Sunday09:00 AM
Sunday10:00 AM
Sunday11:00 AMDEADLINE

After building the schedule, I finished my prep for the jam by downloading every Kenney and Kay asset pack and importing them into a file manager called Eagle. Eagle is a program I learned about last year from a YouTube video on tag-based filesystems and I’ve been using it ever since to help organize my game assets (not sponsored, I just really like it). The nice thing about Eagle is that it lets you assign tags to your assets and search for them much easier than through Windows Explorer. I figured having the files indexed would help me find what I need during the jam quicker.

Searching for Kenney and Kay assets in Eagle

Ideation

Before the jam started, I had been on a rail-shooter kick playing games like Starfox 64 and Galaga: Destination Earth. Having seen the ships in the Kenney Space pack before, I really wanted to try making a game like these and was thankful when power was announced to be the theme. Immediately, I decided that I would build a 2-3 minute rail shooter where the player fires electrical bolts and can charge their ship to either unleash a more powerful attack or dodge. I figured this idea was perfect for a short jam since player and enemy movement could be done entirely with Path3D nodes.

Being inspired by retro rail shooters, I also decided the game would run in an extremely low resolution (256x224) and adhere to the simple geometry and colors of a SNES SuperFX game.

Development

Component architecture

I’ve been working to embrace component architecture more in my recent games so I started by defining a whole set of reusable components for every game function I could need: health, hitboxes, hurtboxes, scoring, item pickups, damage/hit effects, etc. These components are so generalized that almost every object in my game was built from the same components.

Charge ring components

Enemy components

Player components

Projectile components

Most components are also written in such a way that they can take references to other components to chain behavior without needing any code. This is done by having the components self-subscribe to the linked component’s signals on ready. For example, the pickup component will kill a linked health component when obtained and the death component will destroy an object when its linked health component is killed.

Example death component

Example flash component

Example hit-box component

Example hurt-box component

Example score component

Player controllers

Both the player and enemy inherit from a custom PathFollow3D node called RailFollower which adds extra logic to follow a path at a set speed.

Rail follower

Enemy code

The player ship doesn’t directly follow the path but rather is constrained to a 3D plane following the path; unfortunately, it gets a bit more complicated still. I learned that to look correct, the ship actually needs to pitch and yaw away from the camera as it reaches the extents of the screen. That caused problems for the aiming since I implemented the crosshairs as literal 3D objects attached to the front of the ship. Having the ship pitch/yaw as it moves caused the crosshairs to go off screen and required a lot of manual tuning to fix.

Player scene showing the crosshairs in front

Player and enemy paths

Rendering

Several people in the jam comments assumed I was using a shader to pixelate my game but the reality is much simpler. I just set the project’s window resolution to 256x224, enabled viewport scaling, and switched it to integer upscaling. The game is natively rendering in 256x224 and the pixel size just gets multiplied for larger windows.

This has the downside of causing the game to be very small by default; however, I used Itch.io’s canvas size option in the game settings to force the canvas to a larger initial size.

Time allocation

To help with my planning for future jams, I recorded my time throughout the jam:

Expand devlog
  • Friday
    • 11:00: Project setup
    • 11:15: Ideation
    • 12:15: Rendering
    • 12:22: Rail follower / camera
    • 13:20: Player movement
    • 13:38: Targeting reticule
    • 14:32: Bug fix
    • 16:00: Hit box / health components
    • 17:00: Laser components / spawn manager
    • 18:40: Break
    • 19:40: Basic enemy
    • 20:20: Gameplay tweaks
    • 23:20: Break
  • Saturday
    • 00:03: Scoring system
    • 01:19: Power system
    • 02:44: Break
    • 03:16: Add turbo laser
    • 04:30: Add boost
    • 04:45: Enemy spawner
    • 05:00: Trigger volumes
    • 05:30: Fixing shader stutter
    • 05:51: Game balance
    • 06:00: Bookends
    • 07:00: Break
    • 07:40: Jam page
    • 08:05: Break
    • 12:38: Bookends
    • 16:23: Beta testing
    • 17:53: Transitions
    • 18:35: Bug fixes
    • 18:39: Game over screen
    • 21:55: Level design
  • Sunday
    • 00:55: Break
    • 01:39: SFX
    • 02:55: Visual design
    • 03:56: Break
    • 04:20: HUD
    • 05:49: Splash screens
    • 06:43: Credits
    • 08:17: Store page
    • 10:00: Submission
    • 10:14: Fix sound bug
    • 10:17: Done

Expand time summary
TaskDetailsMinutes
PlanningIdeation60
Planning Total60
Project SetupGodot setup15
Project Setup Total15
Core CodeRendering code7
Core CodeShader preloader21
Core Code Total28
MechanicsMovement code18
MechanicsCamera58
MechanicsCrosshair54
MechanicsProjectile system100
MechanicsHit box / health components60
MechanicsDrone enemy40
MechanicsPower system components85
MechanicsDodge15
MechanicsTurbo laser74
MechanicsScoring system79
MechanicsEnemy spawner15
MechanicsTrigger volume component30
Mechanics Total628
Game Loop BookendsLevel reset code60
Game Loop BookendsMenu code225
Game Loop BookendsGame over code196
Game Loop Bookends Total481
Level DesignLevel design180
Level Design Total180
Beta TestingBeta testing93
Beta Testing Total93
Bug FixesBug fixes95
Bug Fixes Total95
BalanceGameplay tweaks180
BalanceScore balancing9
Balance Total189
AudioSFX76
Audio Total76
PolishScene transitions42
PolishVisual polish61
PolishHUD redesign89
PolishSplash screens54
PolishCredits screen94
Polish Total340
Jam SubmissionItch page128
Jam SubmissionSubmission14
Jam Submission Total142
BreakBreak514
Break Total514
Total2841
Dev Total2327 (81% of allotted time)

I’ve heard from multiple devs that a short highly polished game will do better in jams than a longer less polished game so I spent most of my time working on polish rather than the game’s level. This unfortunately meant the final level was only 30 seconds long but I think it worked to prove out the concept.

Post-mortem

Results

Of the 717 final entries, I received 22 comments and won best itch.io page.

Given the jam isn’t ranked, I wasn’t expecting to win anything with over 700 other entrants so I was blown away that I won best itch.io page.

The most common feedback I received was that the visuals, polish, enjoyment, and genre choice were great but that the game was too short, hard to determine depth, and the ctrl keybinding was conflicting with browser shortcuts.

You can play Maximum Voltage on itch.io or check out one of my favorite submission to the jam:

Pros and cons

Pros

  • Components made it easy to build levels and add new behaviors
  • Kenney assets look great in low-resolution
  • Focusing on core gameplay first was the right move
  • High-score focus makes the game more enjoyable to replay
  • My strength is cinematic events so a rail shooter really highlighted that
  • Including hit% in the score screen made it more fun to replay
  • Rapid fire is so satisfying
  • Early web testing helped identify several critical performance, audio, and sprite issues

Cons

  • Spent too much time working on the menus and didn’t have enough left to finish the first level
  • Difficult to tell in editor what the bounds of the player’s movement are which made level design more trial and error
  • There’s no reason in the final game to use the dodge so it just feels like a waste of power and by extension the single power pickups
  • Developed the game with my speakers on low so I didn’t notice until after submission the master volume was way too high
  • Should have made my game manager a general purpose scene transitioner as that would have reduced code in the main menu scene
  • Ctrl was a bad key mapping - it’s hard to press and I learned from a reviewer that pressing Ctrl+W closes the entire browser
  • No volume slider
  • Several people had issues with depth perception
  • Name didn’t quite match the game
  • Should have used pictures instead of headers for the jam page, shouldn’t have repeated the title in the page text after the banner
  • The aim-centering is bugged and re-centers the aim on the ship not the screen making it so your ship blocks the view when shooting anything straight ahead

What’s next

Due to popular demand, I started working on several updates to the game during the judging period. While I’d like to make a full rail shooter at some point, I don’t think Maximum Voltage has enough of an identity of its own for that but I am working to complete the first level of the game as I envisioned. This means adding enemies that shoot back and more cinematic encounters.

I have a lot of jams scheduled back-to-back this year so I won’t make any promises for the release date but know that it is in the works.