Mod Setup
Mod Setup
The game was originally built with the idea that "Platypus Reclayed" is a mod of "Platypus Original", as such modders have the ability to use the same tooling for their own purposes
The game supports 3 larger types of mods
- Add-Ons: This is a mod that is loaded alongside of another mod, when resources like entities, art or audio is requested it will look for a fitting definition in the mod before loading it from the addon "addonTargets" - These show up in the "Add-on" menu
- Full Conversions: This is a mod that provides all data by itself, all sprites, all audio, all everything. "Platypus Reclayed" and "Platypus Original" are Full Conversion "mods" - These show up in the "Mods" menu
- Conversions: Similar to Full conversions, except that they are based on a Full Conversion mod, if a requested resource wasn't defined in any addon or the mod, it will search in its "baseMod" - These show up in the "Mods" menu
Bundles
The two in-built mods come with separate pregenerated asset bundles to make them load significantly faster. Unfortunately, for now we have no publicly available tools for modders to pregenerate these bundles themselves
Disclaimer
- Some structures are rather strict, if you provide an image resource without defining it in your
animations.json
file you will end up with undefined behaviour. - The json parser Platypus uses is a bit of a special one and extremely strict. No comments or trailing commas. Additionally: "[]" and "[ ]" are NOT the same thing to this parser, if you need an empty array use the former ("[]"). (If we make a sequel, that parser will be replaced first)
Development Mode
When developing mods it can be handy to see extra informtation about what the game is doing. It's also beneficial to utilise cheats to help you quickly check your changes. To access that functionality, start the game with "-cheats" (without the quotes) as a startup parameter. To set this on Steam:
- Right-click on the game in your Library
- Select Properties…
- Under the General section in the text box at the bottom, enter: -cheats.
When you start the game you should be able to see game state information at the top-left of the screen. This can be toggled on/off by pressing F1. To open the cheats panel you can:
- Press CTRL + F12 on keyboard, or
- On controller press Left Bumper/L1 and click the Right-Stick at the same time
Making an Add-On mod
- Copy the addon mod template and rename it (The folder name ends up being the logical name of your mod)
- Open up modmeta.json
- Change
displayName
to a name of your choosing - Ensure
isAddon: true
- Set
addonTargets
to the mod(s) that you want to create an addon for. ("Platypus Reclayed" and "Platypus Original" for the default mods). eg."addonTargets": [ "Platypus Reclayed" ],
While empty, it should now load without issues
Your mod is an addon mod, if "isAddon"
is set to true and if "addonTargets"
is provided. Addons can not have a "baseMod"
Adding a ship skin
Difficulty: Easy
Relevant files: Data/skins.json
, Data/animations.json
-
Get your ship sprite sheet as a PNG file
Size indeed does matter, bigger resolutions result in bigger sprites, and bigger hitboxes!
-
Place it within the GFX folder of your mod.
/GFX/Sprites/Player/mySkin.png
for this exampleNOTE: If the path already exists in an addonTarget, you will replace it instead
-
Open up
Data/animations.json
.Here we need to let the game know some information about our sprite sheet. To make our life easier we can copy a sprite sheet definition from Platypus Reclayed and change it to our needs
"sprite"
: point it towards our new sprite sheet"rows"
: how many sprites high is our sprite sheet?"columns"
: how many sprites wide is our sprite sheet?"collisionSprite"
: we can optionally define a different sprite to use as the collision sprite, we can leave it empty unless our sprite gets too complexgenerateColliders
: since this will be used as the player ship this needs to be true - this lets it be hit by bullets and enemies etc
Example content of
Data/animations.json
:[ { "sprite": "Sprites/Player/mySkin", "updateCounter": 3, "matDayCycleMultiplier": 0.2, "invertSheetOrder": true, "generateColliders": true, "startingIndex": 5, "rows": 5, "columns": 2 } ]
-
Open up
/Data/skins.json
Here we tell the game all about our skin. Again, we copy an existent definition and modify it to our needs
"name"
: the identifier of the skin, it shouldn't clash with any other skins, so best to prefix it with your name, if it clashes, then the definition of one or the other will be replaced"targetShip"
: what ship is this skin valid for. "Platypus Original" comes with "regular" while "Platypus Reclayed" comes with "beginner", "regular" and "advanced""shipSprite"
: the sprite we will see and control ingame"iconSprite"
: this is the little icon that shows on the scoreboard, we can do something special, but for simplicity let's replace it with just the main skin"previewSprite"
: this is the sprite we show in the ship selection, it's color is modified via the colors below, How those interact is a bit special so let's ignore it for now
Example content of
Data/skins.json
:[ { "name": "mySuperAwesomeSkin", "targetShip": "regular", "shipSprite": "Sprites/Player/mySkin", "iconSprite": "Sprites/Player/mySkin", "previewSprite": "Sprites/Player/mySkin", "iconColor": "#FFFFFF", "backgroundColor": "#FFFFFF", "uiPrimaryColorHigh": "#FF0000", "uiPrimaryColorLow": "#FF0000", "uiPrimaryColorMidpoint": 0.4, "uiPrimaryColorSmoothness": 1.0, "uiSecondaryColorHigh": "#00FF00", "uiSecondaryColorLow": "#00FF00", "uiSecondaryColorMidpoint": 0.4, "uiSecondaryColorSmoothness": 0.5, "uiTertiaryColorHigh": "#0000FF", "uiTertiaryColorLow": "#0000FF", "uiTertiaryColorMidpoint": 0.5, "uiTertiaryColorSmoothness": 0.5, "weaponOffsetX": 31, "weaponOffsetY": -6, "warningSprite": "Sprites/Player/alarmed", "warningOffsetX": 20, "warningOffsetY": 20, "jetSprite": "Sprites/Player/player_jet", "jetOffsetX": -233, "jetOffsetY": -3, "wakeSprite": "Effects/Water/water spray", "wakeSpawnHeightOffset": 12, "unlockKey": "none" } ]
Replace an existing sprite
Difficulty: Easy
Relevant files: Data/animations.json
-
Place your replacement sprite(s) at the same path as the one you want to overwrite
This is relative to your mod's folder. For example:
When overwriting
"/Platypus Reclayed/GFX/Sprites/Enemies/saucer.png"
, we would put our new file into"/MyMod/GFX/Sprites/Enemies/saucer.png"
For most sprites it is ok for resolution and size to change. The amount of sprites withing the sprite sheet image can also change, though you will need to keep this in mind at the next step
-
Define replacement in
Data/animations.json
Look for the original definition of the sprite in your
addonTargets
and copy it into your own mod'sData/animations.json
Example
Data/animations.json
:[ { "sprite": "Sprites/Enemies/saucer", "generateColliders": true, "matDayCycleMultiplier": 0.5, "startingIndex": 2, "rows": 1, "columns": 9 } ]
Remove all the levels except level 1 from the level menu
Difficulty: Easy
Relevant files: Data/level_panels.json
We can't "undefine" entries in the base mod (e.g. Platypus Reclayed), but we can overwrite it and set the enabled flag to false, to do so:
-
Open up
Data/level_panels.json
-
Define replacements for the levels you want to disable
The key to look out for is "mainLevel", if it is the same as an existing one in the addonTarget, you will overwrite it
"enabled"
is the bool to set to false to remove the level
Example
Data/level_panels.json
:[ { "title": "", "subtitle": "", "thumbnailSprite": "", "thumbnailSpriteLocked": "", "practiceCheckpoints": [], "practiceCheckpointNames": [], "enabled": false, "hideOnEasy": true, "mainLevel": "level2_data", "endlessLevel": "", "requireCompletedLevel": "" }, { "title": "", "subtitle": "", "thumbnailSprite": "", "thumbnailSpriteLocked": "", "practiceCheckpoints": [], "practiceCheckpointNames": [], "enabled": false, "hideOnEasy": true, "mainLevel": "level3_data", "endlessLevel": "", "requireCompletedLevel": "" }, { "title": "", "subtitle": "", "thumbnailSprite": "", "thumbnailSpriteLocked": "", "practiceCheckpoints": [], "practiceCheckpointNames": [], "enabled": false, "hideOnEasy": true, "mainLevel": "level4_data", "endlessLevel": "", "requireCompletedLevel": "" }, { "title": "", "subtitle": "", "thumbnailSprite": "", "thumbnailSpriteLocked": "", "practiceCheckpoints": [], "practiceCheckpointNames": [], "enabled": false, "hideOnEasy": true, "mainLevel": "level5_data", "endlessLevel": "", "requireCompletedLevel": "" } ]
NB We needn't provde any of the other parameters since the level will be disabled anyway
Make an existing enemy shoot really really fast, too fast in fact
Difficulty: Easy
Relevant files: Data/entities.json
-
Open up
Data/entities.json
Here we redefine an existing enemy to modify their firing behaviour, while different enemies usually have different code some things are rather similar. Let's modify the saucer which uses "firePattern" to fire
The current definition of the saucer in Platypus Reclayed is this:
{ "name": "saucer", "sprite": "Sprites/Enemies/saucer", "behaviour": "Saucer", "explosionEntity": "explosionSmall", "sortOrder": 10000, "maxHitPoints": 1, "speed": 4, "isEnemy": true, "deathBulletEntity": "deathBullet", "highlightPoints": false, "points": 10, "secondaryTargetValue": 1, "customBehaviourData": { "firePattern": { "veryEasy": [ 80 ], "easy": [ 80 ], "normal": [ 60 ], "hard": [ 40 ], "nasty": [ 50 ] } } }
We can copy it over and begin modifying it right away:
"firePattern"
is responsible for defining the time spent waiting after a bullet is fired on the saucer
Example
Data/entities.json
:[ { "name": "saucer", "sprite": "Sprites/Enemies/saucer", "behaviour": "Saucer", "explosionEntity": "explosionSmall", "sortOrder": 10000, "maxHitPoints": 1, "speed": 4, "isEnemy": true, "deathBulletEntity": "deathBullet", "highlightPoints": false, "points": 10, "secondaryTargetValue": 1, "customBehaviourData": { "firePattern": { "veryEasy": [ 6 ], "easy": [ 5 ], "normal": [ 4 ], "hard": [ 3 ], "nasty": [ 2 ] } } } ]
Add a new level
Difficulty: Hard
Relevant files: Data/level_panels.json
, Data/animation.json
, Data/messages.csv
-
Create the level file
For the sake of keeping it simple here, let's copy an existing level file:
Data/level1_data.plat
from the source and place it into our mod'sData/
folder. We name itmycoollevel_data.plat
.Important step is to open this file up and look for the
"name"
parameter in the metadata command, it should be at the top of the file. This name needs to match up with the filename, so in our example:"name": "mycoollevel_data",
.A more focused documentation of the level file format and the commands itself can be found as a sibling to this file: "levelFiles.md", for now, we just focus on getting the level loaded
-
Prepare thumbnail images for the level panel.
This should be two files:
- "thumbnail.png"
- "thumbnail_locked.png"
The game uses 2058x1158 files, but pretty much everything goes
-
Define our thumbnails in
Data/animations.json
Example
Data/animations.json
:[ { "sprite": "Scenery/MyCoolLevel/thumbnail" }, { "sprite": "Scenery/MyCoolLevel/thumbnail_locked" } ]
-
Open up
Data/messages.csv
and add your level text it. We will need entries for:- Level title
- Level subtitle
- One name for every checkpoint in your level
Example
Data/messages.csv
:Meta.Key,en_gb MyCoolLevel.Title,Cool Level MyCoolLevel.Subtitle,It is what I have created MyCoolLevel.Area1,Area 1: Never MyCoolLevel.Area2,Area 2: Gonna MyCoolLevel.Area3,Area 3: Give MyCoolLevel.Area4,Area 4: You MyCoolLevel.Area5,Area 5: Up
-
Open up
Data/level_panels.json
Let's copy an existing definition of a level and modify it to our needs:
"title"
this will be the key we defined for our text inData/messages.csv
:MyCoolLevel.Title
"subtitle"
TextID of text shown in the level panelData/messages.csv
:MyCoolLevel.Subtitle
"thumbnailSprite"
The sprite used to preview the level in level selection"thumbnailSpriteLocked"
If the level is locked, this sprite will be used instead"practiceCheckpoints"
Array of all the checkpoint names we have in the level, if you copiedData/level1_data.plat
, this will be the "name" parameters of all thecheckpoint
commands"practiceCheckpointNames"
The display name for said checkpoints, these MUST be the same amount of entries as"practiceCheckpoints"
"enabled"
This flag is only useful if we want to override existing levels to remove them from theaddonTargets
"mainLevel"
name of the level file to load when starting the game
Example
Data/level_panels.json
:[ { "title": "MyCoolLevel.Title", "subtitle": "MyCoolLevel.Subtitle", "thumbnailSprite": "Scenery/MyCoolLevel/thumbnail", "thumbnailSpriteLocked": "Scenery/MyCoolLevel/thumbnail_locked", "practiceCheckpoints": [ "area1", "area2", "area3", "area4", "area5" ], "practiceCheckpointNames": [ "MyCoolLevel.Area1", "MyCoolLevel.Area2", "MyCoolLevel.Area3", "MyCoolLevel.Area4", "MyCoolLevel.Area5" ], "enabled": true, "hideOnEasy": false, "mainLevel": "mycoollevel_data", "endlessLevel": "", "requireCompletedLevel": "" } ]
Making a Full Conversion mod
As starting a full conversion from scratch requires a lot of knowledge of the inner workings of the game, it is recommended that you start by copying the "Platypus Reclayed" reference completely and use it as a base instead As a full conversion mod, you are responsible to provide core assets that will break the game if missing
- Copy the "Platypus Reclayed" reference and rename it (The folder name ends up being the logical name of your mod)
- Open up modmeta.json
- Change
displayName
to a name of your choosing
Your mod is a full conversion mod, if "isAddon"
is set to false and "baseMod"
is empty
Unless addon mods are used, you now exclusive control over the assets loaded, directly inside your local mods folder
Making a Conversion mod
Similar to full conversions, except that you don't actually have to provide ALL the assets the game needs. The mod behaves a bit like an addon mod and you can now add and replace things as if it was an addon mod
The big downside is that the baseMod is fully loaded, no matter what files you actually "replace", but you do get the loading speed gain from having prebuilt assets if the baseMod supports them
- Copy the Conversion Mod Template and rename it (The folder name ends up being the logical name of your mod)
- Open up modmeta.json
- Change
displayName
to a name of your choosing - Change
baseMod
to the mod you want to base your conversion on
Your mod is a full conversion mod, if "isAddon"
is set to false and "baseMod"
is set