Difference between revisions of "Modding Tutorials/GameComponent"
m (→Requirements: link fix.) |
m (small typo: save -> safe) |
||
Line 5: | Line 5: | ||
'''Benefits''':<br/> | '''Benefits''':<br/> | ||
<nowiki>+</nowiki> Are very well supported by RimWorld.<br/> | <nowiki>+</nowiki> Are very well supported by RimWorld.<br/> | ||
− | <nowiki>+</nowiki> Generally | + | <nowiki>+</nowiki> Generally safe to add to existing saves.</br> |
<nowiki>+</nowiki> Work at a very global level.<br/> | <nowiki>+</nowiki> Work at a very global level.<br/> | ||
<nowiki>+</nowiki> Can save data.<br/> | <nowiki>+</nowiki> Can save data.<br/> |
Revision as of 18:45, 14 February 2019
< 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
- 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
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.