PedrelliLuca / ScalarField

A TopDown RPG Game, where magic works by interacting with the environment.
4 stars 0 forks source link

Make the character's health be affected by temperature #27

Closed PedrelliLuca closed 2 years ago

PedrelliLuca commented 2 years ago

What?

Make a health component that keeps track of the owner's max and current health. Implement some system that causes any character to take damage over time when their temperature is outside a given temperature interval [Tmin, Tmax] called "comfort" interval.

Why?

Even though at the moment they're just coloring stuff, temperature-based skills are supposed to deal damage. The way they do so is by driving the target's temperature outside the "comfort" interval. This issue will bring this project closer to an actual video game.

PedrelliLuca commented 2 years ago

Here is a first draft of the system that will cause health to be affected by temperature. I was mainly inspired by Module 8 of CG Spectrum's Game Programming Course.

image

@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam sequenceParticipant underline
skinparam linetype ortho

package ScalarField {
    class AScalarFieldCharacter {
        + <i>void TakeDamage(...) override
        --
        - <i>void _onTemperatureChanged(double temperature)
    }

}

package GameplayAttributes {
    class UHealthComponent #palegreen {
        + void TickComponent(...) override
        + void TakeDamage(double damage)
        + bool IsDead() const
        + double GetCurrentHealth() const
        + void SetCurrentHealth(double health)
        --
        - double _maxHealth
        - double _healthRegenPerSecond
        - double _currentHealth
    }

    class UManaComponent
}

AScalarFieldCharacter --> UHealthComponent
AScalarFieldCharacter --> UManaComponent

package DamageHandlers {
    class UTemperatureDamageHandlerComponent #palegreen {
        + void HandleTemperatureDamage(TWeakObjectPtr<UThermodynamicComponent> thermoC)
        + void TickComponent(...) override
        + void SetMinComfortTemperature(double minComfortT)
        + void SetMaxComfortTemperature(double maxComfortT)
        + double GetMinComfortTemperature() const
        + double GetMaxComfortTemperature() const
        --
        - double <color:darkviolet> _minComfortTemperature
        - double <color:darkviolet> _maxComfortTemperature
        - double <color:darkviolet> _damageInterval
        - double _currentInteralTime
    }
}

AScalarFieldCharacter --> UTemperatureDamageHandlerComponent

@enduml
PedrelliLuca commented 1 year ago

The final class diagram, with the UI. Orange attributes are BlueprintReadOnly, while blue functions are BlueprintImplementableEvents

image

@startuml
skinparam sequenceArrowThickness 2
skinparam roundcorner 20
skinparam sequenceParticipant underline
skinparam linetype ortho

package ScalarField {
    class AScalarFieldCharacter {
        + <i>void Tick(float deltaTime) override
        + <i>void TakeDamage(...) override
        --
        - <i>void _healthChanged(double newHealth) const
        - <i>void _maxHealthChanged(double newMaxHealth) const
        - <i>void _healthRegenChanged(double newHealthRegen) const
        - <i>void _manaChanged(double newHealth) const
        - <i>void _maxManaChanged(double newMaxHealth) const
        - <i>void _manaRegenChanged(double newHealthRegen) const
        - <i>void _temperatureChanged(double newTemperature)
        - TObjectPtr<UManaComponent> _manaC
        - <i>TObjectPtr<UHealthComponent> _healthC
        - <i>TObjectPtr<UTemperatureDamageHandlerComponent> _temperatureDmgHandlerC
    }

    class AScalarFieldPlayerController {
        + TWeakObjectPtr<UHUDWidget> GetGameplayHUD()
        --
        - void _createHUD()
        - TObjectPtr<UHUDWidget> _hudWidget
    }

    AScalarFieldPlayerController <..> AScalarFieldCharacter
}

package GameplayAttributes {
    class FOnHealthChanged << (D,Violet) >> #palegreen
    class FOnMaxHealthChanged << (D,Violet) >> #palegreen
    class FOnHealthRegenChanged << (D,Violet) >> #palegreen

    class UHealthComponent #palegreen {
        + void TickComponent(...) override
        + void PostEditChangeProperty(...) override
        + double GetCurrentHealth() const 
        + double GetMaxHealth() const 
        + double GetHealthRegen() const 
        + void SetCurrentHealth(double health)
        + void SetMaxHealth(double maxHealth, bool bUpdateHealth = true)
        + void SetHealthRegen(double healthRegenPerSecond)
        + void TakeDamage(double damage)
        + bool IsDead() const
        + FOnHealthChanged& OnHealthChanged() 
        + FOnMaxHealthChanged& OnMaxHealthChanged() 
        + FOnHealthRegenChanged& OnHealthRegenChanged() 
        --
        # void BeginPlay() override
        --
        - double _maxHealth
        - double _healthRegenPerSecond
        - double _currentHealth
        - FOnHealthChanged _onHealthChanged
        - FOnMaxHealthChanged _onMaxHealthChanged
        - FOnHealthRegenChanged _onHealthRegenChanged
    }

    FOnHealthChanged <-- UHealthComponent
    FOnMaxHealthChanged <-- UHealthComponent
    FOnHealthRegenChanged <-- UHealthComponent

    class FOnManaChanged << (D,Violet) >> #palegreen
    class FOnMaxManaChanged << (D,Violet) >> #palegreen
    class FOnManaRegenChanged << (D,Violet) >> #palegreen

    class UManaComponent {
        + UManaComponent()
        + void TickComponent(...) override
        + void PostEditChangeProperty(...) override
        + double GetCurrentMana() const 
        + double GetMaxMana() const 
        + double GetManaRegen() const 
        + void SetCurrentMana(double mana)
        + void SetMaxMana(double maxMana, bool bUpdateMana = true)
        + void SetManaRegen(double manaRegenPerSecond)
        + FOnManaChanged& OnManaChanged() 
        + FOnMaxManaChanged& OnMaxManaChanged() 
        + FOnManaRegenChanged& OnManaRegenChanged()
        --
        # void BeginPlay() override
        --
        - double _maxMana
        - double _manaRegenPerSecond
        - double _currentMana 
        - FOnManaChanged _onManaChanged
        - FOnMaxManaChanged _onMaxManaChanged
        - FOnManaRegenChanged _onManaRegenChanged
    }

    FOnManaChanged <-- UManaComponent
    FOnMaxManaChanged <-- UManaComponent
    FOnManaRegenChanged <-- UManaComponent
}

AScalarFieldCharacter --> UHealthComponent
AScalarFieldCharacter --> UManaComponent

AScalarFieldCharacter ..> FOnManaChanged
AScalarFieldCharacter ..> FOnMaxManaChanged
AScalarFieldCharacter ..> FOnManaRegenChanged

AScalarFieldCharacter ..> FOnHealthChanged
AScalarFieldCharacter ..> FOnMaxHealthChanged
AScalarFieldCharacter ..> FOnHealthRegenChanged

package DamageHandlers {
    class UTemperatureDamageType

    class UTemperatureDamageHandlerComponent #palegreen {
        + void TickComponent(...) override
        + void PostEditChangeProperty(...) override
        + void HandleDamage(double temperature)
        + void SetMinComfortTemperature(double minComfortTemperature)
        + void SetMaxComfortTemperature(double maxComfortTemperature)
        + double GetMinComfortTemperature() const
        + double GetMaxComfortTemperature() const
        + bool IsTemperatureComfortable(double temperature)
        --
        - double _computeDamageFromTemperature(double temperature)
        - double <color:darkviolet> _minComfortTemperature
        - double <color:darkviolet> _maxComfortTemperature
        - double <color:darkviolet> _damageInterval
        - double _currentInteralTime
    }

    UTemperatureDamageHandlerComponent ..> UTemperatureDamageType
}

AScalarFieldCharacter --> UTemperatureDamageHandlerComponent

package GameplayUI {
    class UHUDWidget #palegreen {
        + void SetMaxHealth(double newMaxHealth)
        + void SetCurrentHealth(double newCurrentHealth)
        + <color: blue>void SetHealth()
        + <color: blue>void SetHealthRegen(double newHealthRegen)
        + void SetMaxMana(double newMaxMana)
        + void SetCurrentMana(double newCurrentMana)
        + <color: blue>void SetMana()
        + <color: blue>void SetManaRegen(double newManaRegen)
        + <color: blue>void SetTemperature(double temperature, FLinearColor temperatureColor)
        + <color: blue>void SetPauseStatus(bool bIsGamePaused)
        --
        # <color: orange>double _currentHealth
        # <color: orange>double _maxHealth
        # <color: orange>double _currentMana
        # <color: orange>double _maxMana
    }    
}

UHUDWidget <-- AScalarFieldPlayerController
UHUDWidget <.. AScalarFieldCharacter

@enduml
PedrelliLuca commented 1 year ago

Sequence diagram explaining the damage by temperature's flow:

image

@startuml
skinparam sequenceMessageAlign center

?-> AScalarFieldCharacter : Tick()
AScalarFieldCharacter -> UTemperatureDamageHandlerComponent : HandleDamage(temperature)
note over UTemperatureDamageHandlerComponent #FFAAAA: Temperature outside\ncomfort interval!
UTemperatureDamageHandlerComponent -> AScalarFieldCharacter: AActor::TakeDamage()
UTemperatureDamageHandlerComponent -> UTemperatureDamageHandlerComponent: Starts ticking\nto cooldown
note over AScalarFieldCharacter: In the future the character\nwill be able to change the\ndamage taken via some\nother component (armor, ...)
AScalarFieldCharacter -> UHealthComponent: TakeDamage()
UHealthComponent -> UHealthComponent: SetHealth()
UHealthComponent -> AScalarFieldCharacter: FOnHealthChanged Broadcast(health)
AScalarFieldCharacter -> UHUDWidget: GetController()->GetGameplayHUD()->SetCurrentHealth(health)
@enduml