Skip to content

Crop Tiles

charliebkw edited this page Sep 2, 2023 · 10 revisions

Introduction

Crop Tiles are a type of tile which store information about tilled soil on which plants and crops can be planted. Crop tiles are entities containing a CropTileComponent, a TextureRendererComponent, and a PhysicsComponent and ColliderComponent (set to be a sensor). Crop tile entities are created through a TerrainCropTileFactory class, containing a createTerrainEntity() method to instantiate a crop tile at the specified position (or specified x and y coordinates). In future sprints, an additional parameter will be provided to allow different types of Crop Tile to be instantiated depending on the TerrainCategory (see Map System) of the underlying map tile on which the Crop Tile was placed (this could affect the parameter soilQuality or waterDecreaseRate of the CropTileComponent), although this is not yet implemented as of Sprint 1.

CropTileComponent

Most of the entity’s primary functionality is provided through the usage of an attached CropTileComponent. This component stores important information about the state of the tile, including soil quality, water content, whether the soil has been fertilised, and a reference to the plant entity which is currently occupying the tile (null if tile is unoccupied). These properties are largely inaccessible to other classes, but can be used to calculate a growth rate, which can be accessed via a public method getGrowthRate(). On update, the tile’s water content decreases at a constant rate. In future sprints, the decrease rate of water content could depend on whether a plant is on the tile, the quality of the soil, or the TerrainCategory on which the crop tile was spawned.

On create(), the CropTileComponent will tell the entity it is attached to to begin listening to various player interaction events. These include:

  • “water” - this event increases the water content in the tile by a float parameter provided as an argument
  • “fertilise” - this event notifies the Crop Tile that it is now fertilised
  • “plant” - this event, if the tile is unoccupied, will create a plant entity using a factory method provided (of type Function<CropTileComponent, Entity>) as an argument, and register it to the entity service (nothing will happen if the tile is occupied by a plant)
  • “destroy” - this event will destroy the Crop Tile entity (and all attached components) if it is unoccupied, otherwise, it will destroy the plant entity currently occupying the tile, and notify the tile that it is no longer occupied

Architecture & Interface of CropTileComponents with other systems

With Tool System

Interaction between the player and the Crop Tiles/Plants is facilitated through the tool system. Different tools will interact with Crop Tiles, Plants and the Map to achieve various outcomes.

With Map System

To create a Crop Tile, a player will interact with the Map class using some tool. The createTerrainEntity() method in the TerrainCropTileFactory will then be called to create a Crop Tile at the provided location in the game world.

With Plant System

Plants and Crop Tiles have various interactions which affect the states of both entities. It was decided that plants would have a reference to the CropTileComponent of the Crop Tile on which it is planted. This way, they can access the growth rate of the CropTileComponent through getGrowthRate(), and setUnoccupied() (to notify the CropTileComponent that the plant is no longer planted on the crop tile). The CropTileComponent has a reference to a plant entity, and only uses it to determine whether it is currently occupied, and to destroy it on the case the CropTileComponent hears a “destroy” event while occupied.

Sequence Diagrams

Below are sequence diagrams depicting how interaction with the CropTileComponent may occur.

Destroy Interaction

Fertilise Interaction

Harvest Interaction

Plant Interaction

Water Interaction

Internal Functionality Details

Growth Rate Calculations

The CropTileComponent provides a getGrowthRate() method which can be accessed by plants to affect the rate at which they grow. This method returns a double value, where higher values represent quicker growth and a value of 0 indicates no growth is occurring. If a negative growth rate is returned, this indicates the conditions are inhospitable for the plant (the precise value of the growth rate is meaningless if it is negative - -1 will always be returned if the growth rate is to be negative).

The growth rate provided by the Crop Tiles is dependent on a number of factors, most notably the water content of the tile. waterContent is a float value between 0 and 2 inclusive. 0 indicates that the tile is bone-dry, while 2 indicates that the tile is flooded with water. Both of these extremes are considered unideal for plant growth.

The getGrowthRate() method accepts a single parameter, a float representing the water content which should produce the maximum possible growth rate. If no parameter is specified, a getGrowthRate() method override is provided, with an ideal water content value of 1.0f (this is considered the “default” ideal water content). The growth rate will be less than the maximum growth rate if and only if the current water content of the tile is greater than or less than this ideal amount.

If the soil is fertilised, then the growth rate is doubled. The growth rate is also multiplied by the soil quality, which will range from 0 upwards (higher soil quality then means plants will grow quicker). There are additional private parameters that statically affect the shape of the growth rate curve with respect to the water content. These are summarised below:

  • idealWaterFalloffSharpness - affects how quickly the growth rate decreases as water content differs from ideal water content
  • idealWaterFalloffTolerance - affects how tolerant the soil is to unideal water content (i.e., how much unideal water content reduces the growth rate of the plant)
  • waterDamageThreshold - affects how close the water content needs to be from the extremes (0 and 2) before it returns a negative growth rate

A Desmos file has been created for other members of the studio to play around with the growth rate function (see the Desmos file).

Dynamic Texture Updates

So that users can view the status of a CropTileComponent, when the update() method of the CropTileComponent is called the texture of the relevant entity is updated to reflect this status. The Crop Tile becomes darker when overwatered or lighter when un-watered. Additionally, when a CropTileComponent is fertilised the texture changes to include yellow specks representing the fertiliser.
Plain Crop Tile Watered Crop Tile Over-Watered Crop Tile
Fertilised Plain Crop Tile Fertilised Watered Crop Tile Fertilised Over-Watered Crop Tile

Creating Crop Tiles

Crop tiles can be created using the TerrainCropTileFactory.createTerrainEntity() method. A single Vector2 parameter should be provided specifying the position of the created entity. Alternatively, two float values representing the x and y coordinates can be provided.

Crop tiles can be created from the map or the tool. The two versions proposed by the crop tile team are summarised in the below sequence diagrams.

Till Interaction v1

Till Interaction v2

We leave the ultimate decision up to the tool and map generation teams.

Past Designs & Design Process

The old version of the CropTileComponent did not store a reference to the occupying plant entity, but rather a simple boolean value representing whether it was currently occupied by a plant. Additionally, the component provided a listener for the “harvest” event. The original plan was for plants to have a reference to the component and access getGrowthRate() when needed. However, after discussions with the plant team, design improvement was needed to allow for more complex behaviours (e.g., when a plant is harvested, some plant entities will be destroyed, while others will remain). Two possible solutions were proposed: one where the CropTileComponent stored a reference to the plant component, and managed the interactions with the player (i.e., the CropTileComponent updates the state of the plant), and one which was essentially the original plant. The “plant” interaction behaviour for these two design ideas are shown in the below sequence diagrams.

Plant Interaction Old v1

Plant Interaction Old v2

Ultimately, a hybrid approach was chosen. Plants are now created on the “plant” interaction from within the CropTileComponent. However, instead of managing the plant’s state, the CropTileComponent only stores a reference to the plant entity, to determine whether the crop tile is occupied, and to trigger certain events only when necessary (e.g., on the “destroy” event). This decision was made since v1 was thought to have too high coupling between components (requiring plant behaviours be managed by an external class), while v2 was too inflexible.

Clone this wiki locally