-
Notifications
You must be signed in to change notification settings - Fork 7
[Guide] Getting started in code modding from 0
Caution
This guide is WIP and not finished!
Additionally, it is outdated in some aspects.
This means that if you decide to still use this guide, expect things to be different today, than mentioned here.
This tutorial is designed to help people who are interested in making code mods, but either don't know C#, or want some examples of how to make a basic mod. It will often link outside sources for more detailed explanations, and will show you what resources you have available to figure out problems for yourself rather than just show you exactly how to do something.
The first step to start coding is to choose and set up an IDE. IDE stands for Integrated Development Environment, it can be thought of similar to choosing a word processor like Google Docs vs. Microsoft Word, or an image editor like Photoshop vs. Aseprite. While both Google Docs and Word take the same input, your text, what they can do with that, and how they achieve that varies, for example Word might have a font Google Docs doesn't, and Google Docs saves your work in your Google account, IDEs are the same concept. For this tutorial you will see how to set up Visual Studio, but most modern C# IDE's will work as well.
First go to the Visual Studio download page 🔗 and select Community Edition.
This will automatically start downloading the installer for Visual Studio. For Celeste modding you will need to install this 🔗 (note: .net 7 is the version which everest runs on, but any version higher than that, e.g .net 8, should work too), for a more detailed installation tutorial look here 🔗.
Once you have Visual Studio installed, You can follow this guide, or you can use this command dotnet new celestemod --Samples
rather than what the guide says (you can open the console on Windows by pressing the Windows key and typing "cmd").
Once that is all set up, if you are having difficulties with installing the template feel free to reach out on the Celeste Discord 🔗.
This section is all about explaining what is happening in the example entity.
To open your mod open the Example Mod.csproj
.
You should see this:
this side bar contains all the files that you have access to in your code, take note of dependencies, it contains the source code for celeste decompiled, so if you need an example of how celeste did something you can find it here:


As an example to find the code for the seeker entity, double click celeste in the dependencies, it will open an explorer in your main window with a search bar at the top, search for seeker and you will see this:

Now there are a lot of things here, Celeste.Seeker
is the entity, all things from Celeste will be named like this: Celeste.OOOO
Here's what it looks like inside the class:

Orange triangles are classes. Purple cubes are functions inside classes.

Anything starting with IL or ON is what is called a hook, basically a point you can use to add code when that thing happens like ON.Celeste.Seekerbarrier
is used when you want to change or add stuff that happens when the seeker barrier is used.

Now navigate to the entities folder, and double click on SampleEntity.cs to open that class it should look like this. Let's break down each part of this:


These are stating that you are using these libraries in your file, you shouldn't worry about these unless you have an error when you try to use a function
like this, you should hover over the code giving the error, click show potential fixes, and it will give you options like add using System
in this example, or change cos to Cos




('Math.Cos()' is used to find the cosine of an angle)
Next is the namespace, this is where this class exists inside the code, as an example this entity will exist inside Celeste inside Mod as this image shows


A good way to think about this is these two are the same, and this is used for making things easier to understand, and structure:


[CustomEntity("Example_Mod/SampleEntity")]
is telling Everest and Lönn what your entity is called, we will go over this later in the Lönn section

public class SampleEntity : Entity
this code is saying that this class named sample entity extends the class Entity, which means it inherits all variables, functions and anything else that this contains as an example the entity class contains these variables, so if you run Active = false;
it will have that variable already and all the logic associated with it



This is what is called an initializer, it runs one time, when you create one of these, its good to set variables in here that you want to be the default, or any code that you only want to run once.
The : base(data.Position + offset)
means that it extends the base function for its position and offset, best not to mess with this in most cases as it allows things like lonn to place your entity in the correct places.

As a note, if you want to see for yourself what anything does you can hover over the function or any part of it to see what that code contains.


Now inside the initializer you have the logic that runs once when this is created, so when you place one of these in lonn, then load the map, when you enter the room with this entity, this code runs, in this example, it gets the data from Sprites.xml
, then it sets the hitbox to be a Hitbox
16 pixels wide by 16 pixels tall that originates from the center of your sprite.


Note you can use CelesteTAS and mess around with this to see what changing this does, such as setting the -8 to 0 or 16 to 12, in general, it shoudld be structured like this though Hitbox(x, y, -x / 2, -y /2) as this will make it originate from the center of the sprite.
Now let's explain the spritebank, or Sprites.xml that 'Add(GFX.SpriteBank.Create("sampleEntity"));' is reading from. Go to your solution explorer, and go to Graphics:

double click on Sprites.xml and you will see this:

The top is some settings for reading this xml file.
Under that there's <Sprites>

This works similar to a folder or a class, where everything inside this is your sprites it ends on </Sprites>

Next is the declaration of this is what Add(GFX.SpriteBank.Create("sampleEntity"));
is using inside the create function, if we change it like this:


It will do the exact same thing, this is just the name you can use to find this data.
Next you have it setting the path to be "objects/Example_Mod/sampleEntity/" which you can see here image and it is setting the starting animation to be "idle". After this it sets the justification, which is where is 0,0 on the sprite, on this current one 0.5,0.5 it will be halfway up the sprite's height, and halfway through its width at 1,1 it justifies in the top left, and 0,0 bottom right image
next it is making a new animation, with the id "Idle" which is what the sprite defaults to using, for understanding how to make animations check here
Now that we know what everything does, let's start making our own!
So the first thing you have to think about when making a custom entity is, is asking yourself, does this already exist?, a quick way to check is the custom entity list 🔗
if it doesn't, or it doesn't work the way you need, either ask the creator if they can add the feature you want, or make your own!
For this example I want to make a goomba, I want it to move left until it hits a wall, then it moves right, you can jump on it to kill it, so first I check the custom entity list, press ctrl+f and search goomba, see that it's not there, and decide I'm gonna make it.


First let's make a class inside the entities file named Goomba.cs (cs stands for csharp)


Next I copy paste the sample entity to get a base for making my goomba


Then I change the class, initializer and '[CustomEntity("Example_Mod/Goomba")]' so they all are my entity's name "Goomba"

Now I think about if there is anything in Celeste similar to a goomba so I can see how Celeste implemented it, so I look up the snowball because it moves left to right, and you can jump off it, and see it has these functions in it.




Now because I am using this for reference, I pin the tab so I can check back quickly by clicking on it from the top.

Then I start making my own update function for my goomba, the update function is an override, so this will run instead of whatever is in the default entity's update function, so we add base.Update(); so that it runs the default update function, then our code:

(Note that if you see a gray outline of text you can press tab to auto complete your code to that)

The update function runs once every frame and I want my goomba to move to the left to start and I see that in the snowball code has this:

this code can also be written as base.X = base.X - (200 * Engine.DeltaTime)
Let's work through what this does on a couple frames to understand this, based on the reset position function here:

We know when it resets it starts at ten pixels to the right of the camera, we will pretend this is 410, in reality it is based on where the player is in the level but for thinking through this, it is 410.
So now our equation is 410 = 410 - (200 * Engine.DeltaTime)
now what about Engine.DeltaTime? Engine.DeltaTime is a number between 0 and 1 based on how much time has passed in the game Typically it is 1/60 or 0.01666~ as the game runs at 60 frames per second but it is good to use this to avoid lag affecting things, or if the game speed is slowed, this does too

so our equation is now
410 = 410 - (200 * 0.1666~)
Now we can see where our snowball will be on the next frame with some simple math! pemdas it out, parentheses
410 = 410 - 3.333~
then subtraction
410 = 407.666~
So the next frame it will be at 407.666~
Now let's repeat for the next frame
Since delta time, and 200 haven't changed our equation looks like this
407.666~ = 407.666~ - 3.333~
407.666~ = 404.333~
So we now know, this is moving left at a rate of 3 1/3 pixels per frame!
Now I think I can make my goomba do the same because it is also an entity, so it has an X position. So I write this in my update function:

To test if this works, set up some sprites, and a Lönn plugin so it can be placed in the map:




(There will be more about Lönn later) So now, I have my mod all set up, but when I run Lönn it isn't in there - first you need to build your solution. Celeste can't read cs files, so building makes it into a .dll file Celeste can read, so either press F6 or go to build in Visual Studio and press build, then, make a little testing map for your mod.

Now, these images weren't loading in Lönn, so I looked at it for a while and realized, my folder had a space in the name, it needs to use _ instead of that, so after fixing that:


As you can see my goomba works, they move at the very least, but they move really fast right now, so I am gonna change the update to 50f rather than 200f. Now I am gonna start working on making the goomba kill you if you touch it, unless you bounce on it:

I see that it has 2 functions for onbounce and onplayer, so I scroll up, and see those are connected to 2 hitboxes:

So I make my goomba have a similar setup:

The logic for what happens when it collides with each one goes inside here so, I make the onplayer kill the player, and the onplayerbounce
so I copy paste in the code the snowball uses, see it requires a destroy function, but it doesn't remove the entity in it, so I make mine use Scene.Remove()
:



And voila!, this should work just like a snowball now, so let's test it out:

While messing with this I notice it's hard to jump on, so I look at the hitboxes, and see they are really close, so moving them apart a bit will probably make it less precise:

Now it's working how I want, except it doesn't interact with walls, and I can't find anything that does, a good resource to use when you can't figure something out is to ask on the Celeste Modding Discord, so I poste in #code_modding , and they tell me about a couple functions called OnGround()
and MoveH()

These check if the vector you input into them will be on the ground, or a valid move that doesn't go into walls, and if it is, it moves the entity there
however, when I try and use it, it doesn't seem defined, so I ask again on the Discord and they tell me it only exists in Actor
s, so I find the Actor
s class and see it extends Entity
, so I can just replace public class Goomba : Entity
with public class Goomba : Actor
and now everything works!




So with a little bit of logic for determining whether it should be moving right or left, we have a fully functioning Goomba!

Now we just have to give it a sprite:


Make that sprite animate:
And it's all done go here to see the github of this entity:
This section is a reference explaining all the C# concepts you will use in this tutorial.
Home
Contributing
FAQ
Useful Links
Your First Custom Map
Your First Texture Pack
Mod Setup
Custom Maps
Texture Packs
Uploading Mods
Generated Dialog Keys
Reference Code Mod🔗
Vanilla Audio IDs
Vanilla Decal Registry Reference
Character Portraits
Mod Structure
Debug Mode
Debug Map
Command Line Arguments
Environment Variables
Install Issues
Common Crashes
Latency Guide
everest.yaml Setup
Mapping FAQ
Map Metadata
Vanilla Metadata Reference
Adding Custom Dialogue
Overworld Customisation
Entity & Trigger Documentation
Custom Entity List🔗
Camera
Useful Mapping Tools
Custom Tilesets
Tileset Format Reference
Stylegrounds
Reskinning Entities
Skinmods
Decal Registry
Chapter Complete Screen
Custom Portraits
Adding Custom Audio
Advanced Custom Audio
Cassette Music
Code Mod Setup
Making Code Mods
Settings, SaveData and Session
Everest Events
Understanding Input
Logging
Cross-Mod Functionality
Recommended Practices
Mod Updating Guide
Custom Entities, Triggers and Stylegrounds
Lönn Integration🔗
Custom Events
Adding Sprites
Adding Preexisting Audio