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:

  1. Right-click on the game in your Library
  2. Select Properties…
  3. 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

  1. Copy the addon mod template and rename it (The folder name ends up being the logical name of your mod)
  2. Open up modmeta.json
  3. Change displayName to a name of your choosing
  4. Ensure isAddon: true
  5. 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

  1. Get your ship sprite sheet as a PNG file

    Size indeed does matter, bigger resolutions result in bigger sprites, and bigger hitboxes!

  2. Place it within the GFX folder of your mod.

    /GFX/Sprites/Player/mySkin.png for this example

    NOTE: If the path already exists in an addonTarget, you will replace it instead

  3. 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 complex
    • generateColliders: 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
        }
    ]
    
  4. 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

  1. 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

  2. Define replacement in Data/animations.json

    Look for the original definition of the sprite in your addonTargets and copy it into your own mod's Data/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:

  1. Open up Data/level_panels.json

  2. 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

  1. 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

  1. 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's Data/ folder. We name it mycoollevel_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

  2. 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

  3. Define our thumbnails in Data/animations.json

    Example Data/animations.json:

    [
        {
            "sprite": "Scenery/MyCoolLevel/thumbnail"
        },
        {
            "sprite": "Scenery/MyCoolLevel/thumbnail_locked"
        }
    ]
    
  4. 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
    
  5. 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 in Data/messages.csv: MyCoolLevel.Title
    • "subtitle" TextID of text shown in the level panel Data/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 copied Data/level1_data.plat, this will be the "name" parameters of all the checkpoint 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 the addonTargets
    • "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

  1. Copy the "Platypus Reclayed" reference and rename it (The folder name ends up being the logical name of your mod)
  2. Open up modmeta.json
  3. 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

  1. Copy the Conversion Mod Template and rename it (The folder name ends up being the logical name of your mod)
  2. Open up modmeta.json
  3. Change displayName to a name of your choosing
  4. 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