Difference between revisions of "Modding Tutorials/GameComponent"
m (→GameComponent) |
|||
(2 intermediate revisions by 2 users not shown) | |||
Line 27: | Line 27: | ||
RimWorld will automatically create an instance of all classes that inherit from Map-, World- or GameComponent. | RimWorld will automatically create an instance of all classes that inherit from Map-, World- or GameComponent. | ||
− | To implement a Map-, | + | To implement a Map-, World-, or GameComponent, we need to inherit from the respective class and implement a parametered constructor inheriting from base. See [[Modding Tutorials/GameComponent#Example Code|example]]. This constructor tells your Map-, World-, or GameComponent what Map, World, or Game it belongs to. |
− | Note that you'll need ''using RimWorld.Planet;'' for WorldComponents | + | Note that you'll need ''using RimWorld.Planet;'' for WorldComponents. |
== Example Code == | == Example Code == | ||
Line 101: | Line 101: | ||
Current.Game.GetComponent<MyExampleGameComponent>(); | Current.Game.GetComponent<MyExampleGameComponent>(); | ||
</source> | </source> | ||
+ | '''Gotcha:''' The constructor should take a parameter of type ''Game'' (or you'll get errors saying "Constructor not found") | ||
+ | |||
Note the difference in Current vs Find for Game vs Map- and World Components. | Note the difference in Current vs Find for Game vs Map- and World Components. | ||
Line 126: | Line 128: | ||
Called when RimWorld is done instantiating the TYPE. This runs after your constructor. | Called when RimWorld is done instantiating the TYPE. This runs after your constructor. | ||
− | FinalizeInit runs after creating a new instance of TYPE and when loading. If you need instance data from your TYPE (like a WeatherManager, LordManager whatever else) this is the place. | + | FinalizeInit runs after creating a new instance of TYPE and when loading. If you need instance data from your TYPE (like a WeatherManager, LordManager whatever else) this is the place. This is also the place to initialise your TYPEComponent if your constructor is at risk of throwing: There is no try/catch logic in instantiating new components, but there is for FinalizeInit. Throwing an exception in your constructor '''will''' cause issues with generating the Game/World/Map, whereas throwing in FinalizeInit ''may'' cause issues. |
===StartedNewGame=== | ===StartedNewGame=== |
Latest revision as of 10:27, 28 August 2021
< 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[edit]
- You need to have set up your editor and environment
- You need to know how to write custom code
- A decompiler helps, because this tutorial isn't going to tell you the exact implementation details of the Map-, World- or GameComponents.
What you'll learn[edit]
How to use Map-, World- or GameComponents, for the price of one.
Setting up[edit]
RimWorld will automatically create an instance of all classes that inherit from Map-, World- or GameComponent.
To implement a Map-, World-, or GameComponent, we need to inherit from the respective class and implement a parametered constructor inheriting from base. See example. This constructor tells your Map-, World-, or GameComponent what Map, World, or Game it belongs to.
Note that you'll need using RimWorld.Planet; for WorldComponents.
Example Code[edit]
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[edit]
These all follow the same design that ThingComp follows; GetComponent<TYPE>().
MapComponent[edit]
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[edit]
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[edit]
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[edit]
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[edit]
Find.World.GetComponent<MyExampleWorldComponent>();
GameComponent[edit]
Current.Game.GetComponent<MyExampleGameComponent>();
Gotcha: The constructor should take a parameter of type Game (or you'll get errors saying "Constructor not found")
Note the difference in Current vs Find for Game vs Map- and World Components.
Overridable methods of Map-, World- or GameComponent[edit]
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[edit]
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[edit]
Runs per Tick.
TYPEComponentOnGUI[edit]
Runs per frame, when the TYPE is currently visible. Not available to WorldComponent. Useful for adding GUI stuff.
ExposeData[edit]
Saves data.
FinalizeInit[edit]
Called when RimWorld is done instantiating the TYPE. This runs after your constructor.
FinalizeInit runs after creating a new instance of TYPE and when loading. If you need instance data from your TYPE (like a WeatherManager, LordManager whatever else) this is the place. This is also the place to initialise your TYPEComponent if your constructor is at risk of throwing: There is no try/catch logic in instantiating new components, but there is for FinalizeInit. Throwing an exception in your constructor will cause issues with generating the Game/World/Map, whereas throwing in FinalizeInit may cause issues.
StartedNewGame[edit]
Unique to GameComponent. Almost, but not quite, entirely unlike FinalizeInit.
LoadedGame[edit]
Unique to GameComponent. Left as an exercise to the reader.
MapGenerated[edit]
Unique to MapComponent. Left as an exercise to the reader.
MapRemoved[edit]
Unique to MapComponent. Left as an exercise to the reader.