Skip to content

Repository files navigation

ProjectCore

The icon

A tower defense game where the towers are units and the map keeps rearranging itself. Lanes spawn, run for a few waves, and retire somewhere else, so there is no build to settle into. The whole game is deciding which of your towers should be which type, and where.

It started as a MonoGameJam entry and has been rebuilt since. The jam version is the video at the bottom.

How it plays

Movement is the resource. A tower stops firing the moment you pick it up, and any tower that lands on a hex it was not already on gets Overcharge: a few seconds at double fire rate. Standing still is the weak state.

Change gets telegraphed. A new lane draws in as a dashed ghost for three seconds before it goes live. Anything still standing on it when it does gets shoved to the nearest free hex, which counts as landing, so the knock hands you Overcharge. Nothing ever silently stops working.

Read the wave, not a tooltip. Three minion types carry three different defenses, and each one has an answer:

Minion What it has The answer
Swarm nothing, and there are a lot of them Shotgun, whose cone clears a pack
Armored flat damage reduction Sniper, since shotgun pellets tick for 1
Shielded a shield that regrows and blocks direct damage Flamethrower, since burn goes through it

A wrong hit is a small grey tick, a right one is a big amber crunch. Retyping is free and costs about a second of downtime, so which tower is which type is a question every wave.

Four cores instead of a life counter. Lanes run to one of them. A leak damages the one that lane was headed for, a core that runs out dies, and its lanes redirect to whatever is left. The tower budget grows with the number of lanes and stays one ahead of it, so you cannot cover everything and you are picking which core takes the hit.

Mining. Charge nodes appear out where the lanes are not and pay only while a tower is standing on one, for about eleven seconds. Pulling a tower off the fight is the decision, and the trip back lands it in Overcharge. Charge buys one of three upgrades offered after each wave, or a lane or a pack dropped on an opponent. One number, two uses.

Controls

Drag a tower where you want it, or click it and click again. Both do the same thing, so it works on touch.

Input What it does
Left drag on empty ground Box select
Shift click Add to the selection
Double click a tower Select every tower of that type
Q W E Retype the selection to shotgun, sniper, flamethrower
Right click a tower Cycle its type
Ctrl + 1-6 Bind a control group, the number alone recalls it
Shift (hold) Slow time to about a fifth while you issue moves, spending Focus
Space Pause
R Restart
O Open the versus lobby
Esc Cancel a drag, then clear the selection, then quit
F12 Screenshot next to the executable

Hover a tower to see its range as a hex ring. The sniper's dead zone is drawn as a red hole, since it is a rule you otherwise only meet by losing a shot to it.

Versus

Two boards, one relay. Each player runs their own simulation, so nothing has to stay in sync and there is no lockstep and no rollback. Only discrete events cross the wire:

msg send <kind>                    a lane or a pack, headed to the other board
msg state <cores> <wave> <score>   about 2 Hz, for the opponent strip
msg over <score>                   this board is done

The relay in Server/ never reads a game message. It pairs clients into rooms and forwards lines, and it deploys to Cloudflare Workers from .github/workflows/relay.yml. Desktop reads the address out of Settings.json next to the executable, and the browser uses whatever is compiled into Game/Settings.cs, since it has nowhere to keep a file.

Platforms

Game/ holds all the code. Each folder under Platforms/ is a host that links it in.

Platform Framework Notes
DesktopGL MonoGame Windows, macOS, Linux. This is the one the release workflow ships.
WindowsDX MonoGame Windows only.
BlazorGL.KNI KNI Runs in the browser, and it's what goes to itch.io as an HTML5 build.

Drawing is one ShapeBatch from Apos.Shapes, with no SpriteBatch and no render target. Text is drawn from the font's own outlines in the same batch, so the content pipeline only handles audio.

The simulation in World.cs has no drawing in it and no MonoGame types beyond Vector2, which is what lets a headless harness run thousands of games for balance without a window.

Run

dotnet run --project Platforms/DesktopGL
dotnet run --project Platforms/WindowsDX

The browser build serves itself:

dotnet run --project Platforms/BlazorGL.KNI

Setting PROJECTCORE_SHOTS to a comma separated list of seconds writes a png at each of them and exits, which is how the game gets looked at without a person driving it. PROJECTCORE_SEED fixes the board so two runs are comparable:

PROJECTCORE_SEED=7 PROJECTCORE_SHOTS=4,30,90 dotnet run --project Platforms/DesktopGL

Publish

dotnet publish Platforms/DesktopGL -c Release -r win-x64 --self-contained --output artifacts/windows
dotnet publish Platforms/DesktopGL -c Release -r linux-x64 --self-contained --output artifacts/linux

macOS ships as an .app bundle instead, built by a script that has to run on a Mac:

./Platforms/DesktopGL/package-osx.sh 2.0.0 artifacts/osx

The bundle carries a full tree per architecture and a shell launcher that picks between them, because a self-contained .NET app ships framework assemblies compiled per architecture and lipo can't merge those. Every Mach-O in it gets ad-hoc signed, which is the part that can't happen anywhere but macOS: Apple Silicon kills any process whose images aren't signed, so an unsigned build is one that never starts. The publish also turns NetCoreBeauty off for osx-*, since it patches hostfxr and a patched Mach-O is invalid.

The web build lands in artifacts/web/wwwroot, with the index.html at its root. That folder is what goes to itch.io:

dotnet publish Platforms/BlazorGL.KNI -c Release --output artifacts/web

Pushing a v* tag builds all four and sends them to itch.io on the windows, osx, linux, and html5 channels. Running the workflow by hand from the Actions tab builds everything and publishes nothing, which is the way to check a change to it without minting a tag.

Icons

Draws the icon at every size it ships in, for both desktop platforms, the macOS bundle and the web favicon:

python Tools/make-icons.py

Nothing is upscaled. Three lanes converging on a core reads at 512 and turns to mush at 16, so the small sizes drop them and keep the hex alone.

Creators

This game was created by Lightning2x and Apos. Follow us on Twitter:

@Lightning2KHD

@JeanDavidMoisan

The jam version

Gameplay thumbnail

https://www.youtube.com/watch?v=QqVwQMF2OHc

About

Entry for the MonoGameJam on itch.io. https://itch.io/jam/monogamejam

Resources

Stars

7 stars

Watchers

2 watching

Forks

Used by

Contributors

Languages