Modding Tutorials/GameComponent

From RimWorld Wiki
Revision as of 18:45, 14 February 2019 by Ww9 (talk | contribs) (small typo: save -> safe)
Jump to navigation Jump to search

Modding Tutorials
Modding Tutorials/Custom Comp Classes
Although neither explicitly nor formally linked, Map-, World- and GameComponents share a very similar design (and because of that they also share one tutorial). Similar to ThingComp, you can add any custom code to your Map-, World- or GameComponents for you to call manually. It also has methods called at specific occurrences that you can override.

Benefits:
+ Are very well supported by RimWorld.
+ Generally safe to add to existing saves.
+ Work at a very global level.
+ Can save data.
+ Certain functionality gets automatically called by RimWorld.
+ Can be employed to achieve lots of different types of functionality.
+ Are always accessible.

Downsides:
- Removing a mod with these types of components from a save-game results in a (mostly harmless) one-time error.
- Does not expose any of its functionality to XML.

Requirements

What you'll learn

How to use Map-, World- or GameComponents, for the price of one.

Setting up

RimWorld will automatically create an instance of all classes that inherit from Map-, World- or GameComponent.

To implement a Map-, WorldComponent, we need to inherit from their respective Map- or WorldComponent class and implement their parametered constructor inheriting from base. See example. This constructor gives an object reference to the respective components. In other worlds, it tells your Map or WorldComponent what Map or World they're tied to. And vice versa.

Note that you'll need using RimWorld.Planet; for WorldComponents. A constructor is not enforced for GameComponent, as there's only ever one instance of Game.

Example Code

using RimWorld;
using Verse;
using RimWorld.Planet;

namespace MyExampleMod
{
    public class MyExampleWorldComponent : WorldComponent
    {
        public bool myExampleBool = true;

        public MyExampleWorldComponent(World world) : base(world)
        {
        }
    }
}

And there we have it. That creates an instance of a WorldComponent. We can then put things like a tracker for "How often did we trade with Faction X" in, or override one or more of the accessible methods. Since these types of components are accessible from pretty much anywhere, they lend themselves to a wide variety of uses.

Accessing your Map-, World- or GameComponent

These all follow the same design that ThingComp follows; GetComponent<TYPE>().

MapComponent

First off: Know that null is a perfectly valid value for a Map:

  • Players can abandon all their maps and just caravan around.
  • A pawn on a Caravan is in a null map.
  • A pawn inside a ThingHolder (Carried by another pawn, inside a cryptosleep casket or transport pod, inside a Corpse) is in a null map.

Trying to access your Component from something that is null leads to bad things happening. Null-check.

From a Thing on a map

thing.Map.GetComponent<MyExampleMapComponent>();

Note: If the thing in question is inside a ThingHolder, use thing.MapHeld instead. That iterates through the ThingHolders until it finds a Map (or null) to return.

From the currently visible map

Find.CurrentMap.GetComponent<MyExampleMapComponent>();

Note: Ensure that you want your MapComponent to do stuff on the currently visible map. thing.Map is often better.

From who knows where

If you want to play it safe, you can add something like the below to your MapComponent class to return it like so:

        public static MyExampleMapComponent GetMapComponentFor(Map map)
        {
            if (map == null)
            {
                Log.Error("Called GetMapComponent on a null map");
                return null;
            }

            if (map.GetComponent<MyExampleMapComponent>() == null)
            {
                map.components.Add(new MyExampleMapComponent(map));
            }
            return map.GetComponent<MyExampleMapComponent>();
        }

The safety this design offers is two-fold: It null checks the Map and gives a useful warning, and it handles the rare case where something went wrong with instantiating the MapComponent. This can happen when your MapComponent caused an exception during instantiation, or when a MapComponent that was loaded before yours threw an exception during itsinstantiation.

WorldComponent

Find.World.GetComponent<MyExampleWorldComponent>();

GameComponent

Current.Game.GetComponent<MyExampleGameComponent>();

Note the difference in Current vs Find for Game vs Map- and World Components.

Overridable methods of Map-, World- or GameComponent

NOTE: This is up to date for version 1.0.2150. For the most up-to-date info, grab a decompiler.
NOTE: Not every method listed here is available to all three types of Component.
NOTE: Mentally replace TYPE with the type associated with your Component, be it Map, World or Game.

It does not make sense to override every method listed here. It therefor goes without saying you should only override those methods need.

TYPEComponentUpdate

Runs per frame, even when the game is paused. Best used for things that need updating regardless of tickrate, but it's unwise to put game logic here.

TYPEComponentTick

Runs per Tick.

TYPEComponentOnGUI

Runs per frame, when the TYPE is currently visible. Not available to WorldComponent. Useful for adding GUI stuff.

ExposeData

Saves data.

FinalizeInit

Called when RimWorld is done instantiating the TYPE.

StartedNewGame

Unique to GameComponent. Almost, but not quite, entirely unlike FinalizeInit.

LoadedGame

Unique to GameComponent. Left as an exercise to the reader.

MapGenerated

Unique to MapComponent. Left as an exercise to the reader.

MapRemoved

Unique to MapComponent. Left as an exercise to the reader.