Difference between revisions of "Modding Tutorials/Hello World"

From RimWorld Wiki
Jump to navigation Jump to search
(Created page with "{{BackToTutorials}} In this tutorial you will learn how to make a mod that runs on startup and writes "Hello World" to the dev console. =Requirements= * This tutorial requir...")
 
m (→‎But why can't I make my StaticConstructorOnStartup inherit from mod?: Resent research: two condoms not working is a bit of a myth?)
(8 intermediate revisions by 2 users not shown)
Line 5: Line 5:
 
=Requirements=
 
=Requirements=
 
* This tutorial requires you to have [[Modding Tutorials/Setting up a solution|set up a solution]].
 
* This tutorial requires you to have [[Modding Tutorials/Setting up a solution|set up a solution]].
* This tutorial requires a Mod folder (here called: MyMod) and the mod folder will need an About folder containing a valid About.xml and an Assemblies folder we will put our HelloWorld.dll file. For more info, see [[Modding_Tutorials/Mod_folder_structure]]
+
* This tutorial requires a Mod folder (here called: MyMod) and the mod folder will need an About folder containing a valid About.xml and an Assemblies folder where we will put our HelloWorld.dll file. For more info, see [[Modding_Tutorials/Mod_folder_structure | Mod folder structure]]
  
 
=Bootstrapping=
 
=Bootstrapping=
Line 11: Line 11:
  
 
=Hello World=
 
=Hello World=
    using RimWorld;
+
<source lang="csharp">
    using Verse;
+
using RimWorld;
    namespace TestMod
+
using Verse;
 +
 
 +
namespace TestMod
 +
{
 +
    [StaticConstructorOnStartup]
 +
    public static class MyMod
 
     {
 
     {
         [StaticConstructorOnStartup]
+
         static MyMod() //our constructor
        public static class MyMod
 
 
         {
 
         {
             static MyMod() //our constructor
+
             Log.Message("Hello World!"); //Outputs "Hello World!" to the dev console.
            {
 
                Log.Message("Hello World!"); //Outputs "Hello World!" to the dev console.
 
            }
 
 
         }
 
         }
 
     }
 
     }
 
+
}
 +
</source>
 
* The [StaticConstructorOnStartup] annotation does what it says on the tin. At startup, it runs the Static Constructor of any class with that annotation. For a thorough explanation of what a static constructor is, refer to [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors official C# documentation.] What you need to know: It's a method without a return type, has the same name as the class and doesn't take any arguments.
 
* The [StaticConstructorOnStartup] annotation does what it says on the tin. At startup, it runs the Static Constructor of any class with that annotation. For a thorough explanation of what a static constructor is, refer to [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors official C# documentation.] What you need to know: It's a method without a return type, has the same name as the class and doesn't take any arguments.
  
Line 35: Line 37:
  
 
=StaticConstructorOnStartup vs inheriting from Mod=
 
=StaticConstructorOnStartup vs inheriting from Mod=
Subclasses of the Mod class are loaded extremely early during. As such, it is less suitable for most purposes (most notably: using Defs or anything that uses Defs). Classes with the StaticConstructorOnStartup are initialised just before the Main menu is shown. Classes with this annotation are loaded in the main thread: a requirement for loading any Texture, Material, Shader, Graphic, GameObject or MaterialPropertyBlock.
+
Subclasses of the Mod class are loaded extremely early during launch. As such, it is less suitable for most purposes (most notably: using Defs or anything that uses Defs). Classes with the StaticConstructorOnStartup are initialised just before the Main Menu is shown. Classes with this annotation are loaded in the main thread: a requirement for loading any Texture, Material, Shader, Graphic, GameObject or MaterialPropertyBlock.
  
Inheriting from Mod is necessary if you want [[Modding Tutorials/Mod Settings]], or if you need to run really early in the loading process.
+
Inheriting from Mod is necessary if you want [[Modding Tutorials/ModSettings|Mod Settings]], or if you need to run really early in the loading process.
  
 
For the exact order of initialisation, look up Verse.PlayDataLoader.DoPlayLoad.
 
For the exact order of initialisation, look up Verse.PlayDataLoader.DoPlayLoad.
 +
 +
==But why can't I make my StaticConstructorOnStartup inherit from mod?==
 +
That's like wearing two condoms. You might think you're extra safe, in reality it puts you in a worse position.  Okay, two condoms might actually be safe enough, but you can't both inherit from the mod and expect the StaticConstructorOnStartup to work.
 +
 +
When C# tries to instantiate a type (like your Mod class) for the very first time, it first runs the static constructor and then an instance constructor. The static constructor in this case is the StaticConstructorOnStartup: meaning it'll try to load your textures outside the main thread and run your Harmony patches using Defs the game doesn't know exist yet. Subsequent new instances of your Mod class will not have their static constructor called again: they run once and only once. Likewise, if the StaticConstructorOnStartup then tries to call the static constructor again, nothing will happen as the static constructor already ran once.
 +
 +
Sometimes you need both a StaticConstructorOnStartup and a class that inherits from Mod. The correct way of doing that is by making two separate classes; one that inherits from Mod and the other with a StaticConstructorOnStartup attribute.
 +
 +
[[Category: Modding]]
 +
[[Category: Modding tutorials]]

Revision as of 05:33, 11 August 2019

Modding Tutorials

In this tutorial you will learn how to make a mod that runs on startup and writes "Hello World" to the dev console.

Requirements

  • This tutorial requires you to have set up a solution.
  • This tutorial requires a Mod folder (here called: MyMod) and the mod folder will need an About folder containing a valid About.xml and an Assemblies folder where we will put our HelloWorld.dll file. For more info, see Mod folder structure

Bootstrapping

There are two acceptable ways of making RimWorld load your mod. One is by using the StaticConstructorOnStartup annotation, the other is by inheriting from the Mod class. We will be using the StaticConstructorOnStartup annotation, for reasons explained below.

Hello World

using RimWorld;
using Verse;

namespace TestMod
{
    [StaticConstructorOnStartup]
    public static class MyMod
    {
        static MyMod() //our constructor
        {
            Log.Message("Hello World!"); //Outputs "Hello World!" to the dev console.
        }
    }
}
  • The [StaticConstructorOnStartup] annotation does what it says on the tin. At startup, it runs the Static Constructor of any class with that annotation. For a thorough explanation of what a static constructor is, refer to official C# documentation. What you need to know: It's a method without a return type, has the same name as the class and doesn't take any arguments.

Common pitfalls

  • Your constructor isn't static.
  • Your constructor isn't a constructor. Refer once more to the official C# documentation.
  • There are red squiggles under [StaticConstructorOnStartup] (or anywhere else): you either typo'd, are missing using statements or don't have a reference to Assembly-Csharp.dll. Read how to set up a solution again.
  • You did not compile, and if you did compile, your IDE placed the resulting assembly in some far away folder. Set the output path of your build to something more sane. Read how to set up a solution again.
  • There are lot of yellow errors along the line of "x already has short hash": You did not set "Copy local" to false. Read how to set up a solution again.

StaticConstructorOnStartup vs inheriting from Mod

Subclasses of the Mod class are loaded extremely early during launch. As such, it is less suitable for most purposes (most notably: using Defs or anything that uses Defs). Classes with the StaticConstructorOnStartup are initialised just before the Main Menu is shown. Classes with this annotation are loaded in the main thread: a requirement for loading any Texture, Material, Shader, Graphic, GameObject or MaterialPropertyBlock.

Inheriting from Mod is necessary if you want Mod Settings, or if you need to run really early in the loading process.

For the exact order of initialisation, look up Verse.PlayDataLoader.DoPlayLoad.

But why can't I make my StaticConstructorOnStartup inherit from mod?

That's like wearing two condoms. You might think you're extra safe, in reality it puts you in a worse position. Okay, two condoms might actually be safe enough, but you can't both inherit from the mod and expect the StaticConstructorOnStartup to work.

When C# tries to instantiate a type (like your Mod class) for the very first time, it first runs the static constructor and then an instance constructor. The static constructor in this case is the StaticConstructorOnStartup: meaning it'll try to load your textures outside the main thread and run your Harmony patches using Defs the game doesn't know exist yet. Subsequent new instances of your Mod class will not have their static constructor called again: they run once and only once. Likewise, if the StaticConstructorOnStartup then tries to call the static constructor again, nothing will happen as the static constructor already ran once.

Sometimes you need both a StaticConstructorOnStartup and a class that inherits from Mod. The correct way of doing that is by making two separate classes; one that inherits from Mod and the other with a StaticConstructorOnStartup attribute.