Modding Tutorials/Plant Rendering

From RimWorld Wiki
Jump to navigation Jump to search

Modding Tutorials

?
Under Review
This tutorial or guide is currently undergoing review and should not be considered ready to use.

This page covers the technical aspects of how plants (whether they be trees, bushes, grass, or some new vegetative monstrosity cooked up in a mod) are rendered to the screen in-game.

How Plants Are Rendered[edit]

When RimWorld draws a plant, it does the following:

  • Calculates plant size and center position based on current growth percent out of 100%.
  • Determines how many textures (copies of the plant graphic) to draw on a single mesh, per cell on the map.
  • Randomly places those textures on the mesh with slight randomized offsets along both the x and z axes.
  • Applies mesh offsets to simulate wind movement.
  • Adds a shadow if defined, based on the base position calculated above for the default texture.

All of these behaviors can be controlled using the plant's XML ThingDef.

Mesh Count & Placement[edit]

The number of texture copies drawn per plant instance is controlled by the maxMeshCount field on each plant ThingDef.

<Defs>
  <ThingDef>
    <plant>
      <maxMeshCount>9</maxMeshCount>
    </plant>
  </ThingDef>
<Defs>
  • By the time the plant reaches full maturity (100% growth) all copies will be drawn, though until that time, fewer copies will be drawn.
    • Meaning if the maxMeshCount is 9, then when fully grown, 9 textures will be rendered for the plant.
  • Each copy of the texture on the mesh has a chance to be flipped on the x-axis as well, to add some visual variance.
  • If the plant uses random graphics, Graphic_Random, then each copy of the texture will be picked at random too.

WikiPlantRendering maxMeshCount RandomGraphics Example .png

▲ Example of randomized plant graphics with a maxMeshCount value of 4.


The example code block above tells the game to draw up to 9 overlapping textures per plant instance. These textures appear spread across a grid (roughly 3x3 in the case of 9) within the occupying cell. Only perfect square numbers less than or equal to 25 are allowed as well. Any value not equaling a perfect square or being > 25 will result in an error on game launch. All the various possible maxMeshCount values below, aside from 1, are also drawn with each texture getting small random offsets applied to the x and z-axes to add randomness to their visual spread.

  • 1 — single-centered plant (no grid)
  • 4 — 2x2 layout
  • 9 — 3x3 layout
  • 16 — 4x4 layout
  • 25 — 5x5 layout

WikiPlantRendering maxMeshCount Example.png

▲ Here's an example of how different maxMeshCount values create semi-random grid-like layouts per each plant instance mesh. Remember, in each of these examples, there is only one actual plant; we are just rendering multiple copies of its default texture to adjust the visual density of the plant on the map and in the cell.

Visual Size Scaling[edit]

Plants change size as they grow. This is controlled by the combination of the def's drawSize and visualSizeRange fields as follows. Where drawSize, min, and max are defined as:

  • drawSize — 1.0 as the default for all plants (at 1.0, the actual texture will be scaled to fill a single cell on the map)
  • min — scale at 0% growth
  • max — scale at 100% growth (fully grown)

It's important to remember that the visualSizeRange values correspond to the actual size (area) of a single cell on the map. So a value of 1.0 would be the same as the total area of a single cell on the map so long as the drawSize is also 1.0. A value of 3.0, as seen in the below example for the max field, means the texture will be scaled up to cover a 3x3 area of cells with a drawSize of 1.0. If the maxMeshCount is 1.0, as is the case with trees and many other larger plants, then the texture will always be positioned relative to the bottom of the cell it occupies. If the maxMeshCount is greater than 1, like with grass or other small plants, then the entire mesh and each copy of the texture on the mesh will be scaled and positioned relative to the center of the cell it occupies. This is applied to shadows as well, if defined.

<Defs>
  <ThingDef>
    <graphicData>
      <drawSize>1.0</drawSize>
    </graphicData>
    <plant>
      <visualSizeRange>
        <min>1.0</min>
        <max>3.0</max>
      </visualSizeRange>
    </plant>
  </ThingDef>
<Defs>

WikiPlantRendering visualSizeRange Example.png

▲ In this example, we see the visual scaling progression of an oak tree at various stages of growth (from 15% to 100%). For this example, we've also adjusted the visualSizeRange field for the oak tree to use the above range of 1.0 to 3.0 for clarity. The tree texture is also using the default drawSize value of 1.0. At 100% growth, we can see the tree texture takes up the space of a 3x3 area of cells on the map. In this example, if the drawSize was 2.0, instead of 1.0, then at 100% growth the tree texture would take up a 6x6 area of cells.

Remember, the visualSizeRange * drawSize and current growth % act as a ratio. So in the example above we see the following:

Max Growth % : Max visualSizeRange Current Growth % : Cells Covered (X x Z)
100% : 3.0 15% : 0.45 x 0.45
100% : 3.0 38% : 1.14 x 1.14
100% : 3.0 41% : 1.23 x 1.23
100% : 3.0 100% : 3.00 x 3.00

Random Position Offsets[edit]

To avoid plants looking too uniform, RimWorld offsets mesh positions slightly using internal randomness.

  • For maxMeshCount = 1, a small random horizontal offset is applied.
  • For higher counts, mesh copies are placed in a grid and randomly nudged to avoid sharp alignment.

We cannot directly control this randomness in XML, but it still enhances visual variety.

Texture Selection[edit]

A plant’s default texture is defined in its def via the graphicData field like so.

<Defs>
  <ThingDef>
    <graphicData>
      <texPath>Things/Plant/BerryPlant</texPath>
      <graphicClass>Graphic_Random</graphicClass>
    </graphicData>
  </ThingDef>
<Defs>

Most base game and modded plants use random graphics for the plant, as seen in the example of the base game berry bush above. To ensure the plant uses random graphics, use the Graphic_Random class and ensure the texPath ends with the name of the folder containing the textures, not the name of the actual texture file(s).

  • To provide multiple texture variants, append a suffix for each (e.g., Dandelion.png, DandelionA.png, DandelionB.png, etc.).


Plants can also have immature, leafless, or polluted (if Biotech is loaded) textures for those states. All of these paths will use the same graphicClass the default texture uses, along with the same shaderType, drawSize, color, and colorTwo fields. These textures are all defined in the plant field like so. In the case of immature, leafless, and polluted texture variants (if using random graphics), name them the same as you would the default texture, like so.

default immature leafless polluted
BerryBushA.png BerryBushImmatureA.png BerryBushLeaflessA.png BerryBushPollutedA.png
  • The same number of immature, leafless, or polluted textures as the default texture is not required. One of each is fine, or 5 default variations and 1 immature, leafless, and polluted would work too.
<Defs>
  <ThingDef>
    <plant>
      <leaflessGraphicPath>Things/Plant/BerryPlant_Leafless</leaflessGraphicPath>
      <immatureGraphicPath>Things/Plant/BerryPlant_Immature</immatureGraphicPath>
      <pollutedGraphicPath>Things/Plant/BerryPlant_Polluted</pollutedGraphicPath> <!-- doesn't actually exist in the base game for the berry plant, it could though -->
    </plant>
  </ThingDef>
<Defs>


Plants will automatically change textures depending on their current state as mentioned above, such as immature, leafless, polluted, or mature. A plant is considered "mature" by the game if its current growth % is greater than the harvestAfterGrowth value defined in the plants' def.

A plant's various textures are used accordingly:

  • Default - When the current growth % is > the harvestAfterGrowth value defined in the plants' def.
  • Immature - When the current growth % is <= the harvestAfterGrowth value defined in the plants' def.
  • Leafless - When the plant cannot be sown or harvested and when it should be made leafless.
  • Polluted - When the cell the plant is in becomes polluted (Biotech must be loaded).
  • harvestAfterGrowth is out of 1.0, being 100% or fully grown. So in the example below, if the plant has an immatureGraphicPath defined then the immature texture will be used until the plant reaches 31% growth, at which point the plant's default texture will be used. This is generally the case for plants that give resources such as bushes and trees.
<Defs>
  <ThingDef>
    <plant>
      <harvestAfterGrowth>0.30</harvestAfterGrowth>
    </plant>
  </ThingDef>
<Defs>

Wind Movement[edit]

To simulate wind effects, RimWorld applies a subtle animation to each plant mesh in code.

  • The plant's topWindExposure field determines how much the plant sways in the wind. This is done by offsetting the vertices of the plant mesh.
  • This is not a simple back-and-forth animation. The sway produces an exaggerated stretching of the mesh along the x and z axes. Look at the example GIFs below carefully for examples.
  • The default value is 0.25.
  • A value of 1.0 (the maximum) means the plant will sway back and forth in the wind as much as possible.
  • A value of 0.0 (the minimum) means the plant will have no visible sway in the wind.
<Defs>
  <ThingDef>
    <plant>
      <topWindExposure>0.25</topWindExposure>
    </plant>
  </ThingDef>
<Defs>
Wiki PlantRendering WindMovement 0.gif
Wiki PlantRendering WindMovement point25.gif
Wiki PlantRendering WindMovement point5.gif
Wiki PlantRendering WindMovement 1.gif

▲ Here is a comparison of different topWindExposure values for the base game oak tree.

  • From left to right the examples use values of 0, 0.25, 0.5, and 1 at a game speed of 1 (normal play speed).

Shadow Rendering[edit]

For a plant to cast a shadow (like a tree or tall bush might), define it like so: Where the volume dictates the scale of the shadow graphic for the plant, and the offset determines the shadow's offset relative to the center of the plant graphic. If shadowData is omitted, no shadow is drawn.

<Defs>
  <ThingDef>
    <graphicData>
      <shadowData>
        <volume>(0.5, 0.2, 0.5)</volume>
        <offset>(0.1, 0, 0.1)</offset>
      </shadowData>
    </graphicData>
  </ThingDef>
<Defs>