-
Notifications
You must be signed in to change notification settings - Fork 7
Crop Tiles
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.
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
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.
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.
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.
Below are sequence diagrams depicting how interaction with the CropTileComponent
may occur.
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).
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.
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.
We leave the ultimate decision up to the tool and map generation teams.
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.
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.