Skip to content

[Guide] Getting started in code modding from 0

psyGamer edited this page Nov 22, 2024 · 18 revisions

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.

Section 1 What is an IDE

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.

Section 2 Installing Visual Studio

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

Section 3 Set up the mod template

Once you have Visual Studio installed, You can follow this guide, or you can use this command dotnet new celestemod --Samplesrather 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 🔗.

Section 4 What am I looking at?

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: Screenshot 2023-09-19 004250

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:

Screenshot 2023-09-19 004728 Screenshot 2023-09-19 005054

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:

Screenshot 2023-09-19 005353

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:

Screenshot 2023-09-19 005400

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

Screenshot 2023-09-19 051702

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.

Screenshot 2023-09-19 010304

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:

Screenshot 2023-09-19 010900 Screenshot 2023-09-19 011359

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

Screenshot 2023-09-19 011543 Screenshot 2023-09-19 012020 Screenshot 2023-09-19 012100 Screenshot 2023-09-19 012319

('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

Screenshot 2023-09-19 012549 Screenshot 2023-09-19 013632

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:

Screenshot 2023-09-19 013912 Screenshot 2023-09-19 014047

[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

Screenshot 2023-09-19 012613

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

Screenshot 2023-09-19 012549 Screenshot 2023-09-19 015033 Screenshot 2023-09-19 015131

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.

Screenshot 2023-09-19 015319

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.

Screenshot 2023-09-19 020006 Screenshot 2023-09-19 020024

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.

Screenshot 2023-09-19 020336 Screenshot 2023-09-19 020819

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:

Screenshot 2023-09-19 021540

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

thing2

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

Screenshot 2023-09-19 021714

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

thing

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:

Screenshot 2023-09-19 022518 Screenshot 2023-09-19 022532

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!

Section 5 Making a Custom Goomba

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.

Screenshot 2023-09-19 024332 Screenshot 2023-09-19 024358

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

Screenshot 2023-09-19 024720 Screenshot 2023-09-19 024732

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

Screenshot 2023-09-19 024753 Screenshot 2023-09-19 024823

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

Screenshot 2023-09-19 024919

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.

Screenshot 2023-09-19 025406 Screenshot 2023-09-19 025416 Screenshot 2023-09-19 025440 Screenshot 2023-09-19 025449

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.

Screenshot 2023-09-19 025611

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:

Screenshot 2023-09-19 025449

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

Screenshot 2023-09-19 025919

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:

Screenshot 2023-09-19 030425

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:

Screenshot 2023-09-19 030837

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

Screenshot 2023-09-19 031511

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:

Screenshot 2023-09-19 032819

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

Screenshot 2023-09-19 033236 Screenshot 2023-09-19 035130 Screenshot 2023-09-19 035122 Screenshot 2023-09-19 035116

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

Screenshot 2023-09-19 033810

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:

Screenshot 2023-09-19 040621 Screenshot 2023-09-19 041028

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:

Screenshot 2023-09-19 041307

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

Screenshot 2023-09-19 041716

So I make my goomba have a similar setup:

Screenshot 2023-09-19 042240

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():

Screenshot 2023-09-19 042442 Screenshot 2023-09-19 042511 Screenshot 2023-09-19 062627

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

Screenshot 2023-09-19 043005

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:

Screenshot 2023-09-19 043331

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()

Screenshot 2023-09-19 062924

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 Actors, so I find the Actors class and see it extends Entity, so I can just replace public class Goomba : Entity with public class Goomba : Actor and now everything works!

Screenshot 2023-09-19 045012 Screenshot 2023-09-19 045017 Screenshot 2023-09-19 045046 Screenshot 2023-09-19 063155

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

Screenshot 2023-09-19 064501

Now we just have to give it a sprite:

Screenshot 2023-09-19 063952 Screenshot 2023-09-19 064002

Make that sprite animate:

image

And it's all done go here to see the github of this entity:

Gomba

Section 6 Understanding some C# concepts that will help you

This section is a reference explaining all the C# concepts you will use in this tutorial.

Clone this wiki locally