Closed PedrelliLuca closed 2 years ago
Grid system architecture - first draft
@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam sequenceParticipant underline
skinparam linetype ortho
class UWorldSubsystem
struct FCellCoordinates {
+ bool operator==(const FCellCoordinates& other)
}
class UEnvironmentGridWorldSubsystem {
+ void PostInitialize() override
+ void OnCellEndOverlap(...)
--
- TMap<FCellCoordinates, TSet<FCellCoordinates>> _adjacencyList
- TMap<FCellCoordinates, TObjectPtr<AEnvironmentCell>> _cells
}
class AEnvironmentCell {
+ bool UnfreezeInTime()
+ bool FreezeInTime()
+ TWeakObjectPtr<UBoxComponent> GetBoxComponent()
+ const FCellCoordinates& GetCoordinates() const
+ void SetCoordinates(FCellCoordinates coordinates)
--
- FCellCoordinates _coordinates
- TObjectPtr<UBoxComponent> _boxC
}
class FEnvironmentCellCache
UWorldSubsystem <|-- UEnvironmentGridWorldSubsystem
UEnvironmentGridWorldSubsystem o-- AEnvironmentCell
AEnvironmentCell --> FCellCoordinates
UEnvironmentGridWorldSubsystem o-- FCellCoordinates
@enduml
After working on the environment system for a bit and better understanding the complexity of the problem, I came up with the following class diagram:
@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam sequenceParticipant underline
skinparam linetype ortho
class UWorldSubsystem
struct FCellCoordinates {
+ bool operator==(const FCellCoordinates& other) const
+ bool operator!=(const FCellCoordinates& other) const
+ operator FVector2D() const
+ FString ToString() const
+ int32 X
+ int32 Y
}
class UEnvironmentGridWorldSubsystem {
+ void SpawnGrid()
+ void ActivateOverlappedCells(const TSet<AEnvironmentCell*>& cellsToActivate)
--
- void _spawnCellAtCoordinates(FCellCoordinates coordinates, double cellSide)
- void _onCellEntered(FCellCoordinates cellEntered)
- void _onCellLeft(FCellCoordinates cellLeft)
- TMap<FCellCoordinates, TSet<FCellCoordinates>> _adjacencyList
- TMap<FCellCoordinates, TObjectPtr<AEnvironmentCell>> _cells
- TSet<FCellCoordinates> _overlappedCells
}
class AEnvironmentCell {
+ void FreezeTime()
+ void UnfreezeTime()
+ bool IsFrozen() const
+ double GetSide() const
+ TWeakObjectPtr<UBoxComponent> GetBox()
+ void SetCoordinates(FCellCoordinates coordinates)
+ const FCellCoordinates& GetCoordinates() const
+ FOnCellBeginningOverlap OnCellBeginningOverlap
+ FOnCellEndingOverlap OnCellEndingOverlap
--
- void _onCellBeginningOverlap(...)
- void _onCellEndingOverlap(...)
- double _side
- bool _isFrozen
- FCellCoordinates _coordinates
- FDateTime _freezingTime
- TObjectPtr<UBoxComponent> _boxC
- TObjectPtr<UStaticMeshComponent> _staticMeshC
- TObjectPtr<UMaterialInstanceDynamic> _materialInstance
}
class AScalarFieldCharacter {
# void BeginPlay() override
}
class UEnvironmentGridSettings
class UEnvironmentCellSettings
class FOnCellBeginningOverlap << (D, orchid) >>
class FOnCellEndingOverlap << (D, orchid) >>
UWorldSubsystem <|-- UEnvironmentGridWorldSubsystem
UEnvironmentGridWorldSubsystem o-- AEnvironmentCell
AEnvironmentCell --> FCellCoordinates
UEnvironmentGridWorldSubsystem --> FCellCoordinates
FOnCellBeginningOverlap <-- AEnvironmentCell
FOnCellEndingOverlap <-- AEnvironmentCell
UEnvironmentGridWorldSubsystem ..> FOnCellBeginningOverlap
UEnvironmentGridWorldSubsystem ..> FOnCellEndingOverlap
AScalarFieldCharacter ..> UEnvironmentGridWorldSubsystem
AScalarFieldCharacter <.. AEnvironmentCell
UEnvironmentGridSettings <.. UEnvironmentGridWorldSubsystem
UEnvironmentCellSettings <.. AEnvironmentCell
UEnvironmentCellSettings <.. UEnvironmentGridWorldSubsystem
@enduml
With this commit I managed to make AEnvironmentCell
independent from AScalarFieldCharacter
. This allowed me to create a grid-related classes module, EnvironmentGrid
that knows nothing about the ScalarField
module, improving the architecture.
@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam sequenceParticipant underline
skinparam linetype ortho
package ScalarField {
class AScalarFieldCharacter {
# void BeginPlay() override
}
}
package EnvironmentGrid {
struct FCellCoordinates {
+ bool operator==(const FCellCoordinates& other) const
+ bool operator!=(const FCellCoordinates& other) const
+ operator FVector2D() const
+ FString ToString() const
+ int32 X
+ int32 Y
}
class UEnvironmentGridWorldSubsystem {
+ void SpawnGrid()
+ void ActivateOverlappedCells(const TSet<AEnvironmentCell*>& cellsToActivate)
--
- void _spawnCellAtCoordinates(FCellCoordinates coordinates, double cellSide)
- void _onCellEntered(FCellCoordinates cellEntered)
- void _onCellLeft(FCellCoordinates cellLeft)
- TMap<FCellCoordinates, TSet<FCellCoordinates>> _adjacencyList
- TMap<FCellCoordinates, TObjectPtr<AEnvironmentCell>> _cells
- TSet<FCellCoordinates> _overlappedCells
}
class AEnvironmentCell {
+ void FreezeTime()
+ void UnfreezeTime()
+ bool IsFrozen() const
+ double GetSide() const
+ TWeakObjectPtr<UBoxComponent> GetBox()
+ void SetCoordinates(FCellCoordinates coordinates)
+ const FCellCoordinates& GetCoordinates() const
+ FOnCellBeginningOverlap OnCellBeginningOverlap
+ FOnCellEndingOverlap OnCellEndingOverlap
--
- void _onCellBeginningOverlap(...)
- void _onCellEndingOverlap(...)
- double _side
- bool _isFrozen
- FCellCoordinates _coordinates
- FDateTime _freezingTime
- TObjectPtr<UBoxComponent> _boxC
- TObjectPtr<UStaticMeshComponent> _staticMeshC
- TObjectPtr<UMaterialInstanceDynamic> _materialInstance
}
class UEnvironmentGridSettings
class UEnvironmentCellSettings
class FOnCellBeginningOverlap << (D, orchid) >>
class FOnCellEndingOverlap << (D, orchid) >>
}
UEnvironmentGridWorldSubsystem o-- AEnvironmentCell
AEnvironmentCell --> FCellCoordinates
UEnvironmentGridWorldSubsystem --> FCellCoordinates
FOnCellBeginningOverlap <-- AEnvironmentCell
FOnCellEndingOverlap <-- AEnvironmentCell
UEnvironmentGridWorldSubsystem ..> FOnCellBeginningOverlap
UEnvironmentGridWorldSubsystem ..> FOnCellEndingOverlap
UEnvironmentGridWorldSubsystem <.. AScalarFieldCharacter
UEnvironmentGridSettings <.. UEnvironmentGridWorldSubsystem
UEnvironmentCellSettings <.. AEnvironmentCell
UEnvironmentCellSettings <.. UEnvironmentGridWorldSubsystem
@enduml
What?
Add a system such that, based on the player character's location, the game:
Think of the map as a 2D grid. If the player character is standing in a given cell, then that cell and the 8 surrounding it are also active. The ones beyond can either be non-existent (they haven't been spawned yet) or cached (they have been spawned, but the player moved away from them and now they're not among the surrounding 8 anymore).
Cells' management. Pink: character. Orange: active cell. Blue: non-existing cell. Green: cached/inactive cell.
Why?
This system should help performance-wise. In my first iteration of the ScalarField prototype, a couple of years ago, I didn't develop this system: I had a single enormous air cell with a huge amount of "air molecule" actors. On a big map, that meant having hundreds of thousands of elements ticking in the scene to exchange heat, which caused the FPS to be extremely low.
By activating only the molecules closest to the player, the number of actors ticking simultaneously should become acceptable. By caching temperature-related data, I'll just need to reload such data and reactivate the cell if the player decides to revisit it.