diff --git a/Content/Animations/MyAnimBlueprint.uasset b/Content/Animations/MyAnimBlueprint.uasset index 3ad346c..b964de5 100644 Binary files a/Content/Animations/MyAnimBlueprint.uasset and b/Content/Animations/MyAnimBlueprint.uasset differ diff --git a/Content/Animations/MyBlendSpace1D.uasset b/Content/Animations/MyBlendSpace1D.uasset deleted file mode 100644 index b6fd33a..0000000 Binary files a/Content/Animations/MyBlendSpace1D.uasset and /dev/null differ diff --git a/Content/BattleField/BattleFieldMap.umap b/Content/BattleField/BattleFieldMap.umap index 84efa98..75f7d56 100644 Binary files a/Content/BattleField/BattleFieldMap.umap and b/Content/BattleField/BattleFieldMap.umap differ diff --git a/Content/MainMenu/MainMenuLevel.umap b/Content/MainMenu/MainMenuLevel.umap index 5b5490d..4c50f68 100644 Binary files a/Content/MainMenu/MainMenuLevel.umap and b/Content/MainMenu/MainMenuLevel.umap differ diff --git a/Content/Troopers/BP_Trooper.uasset b/Content/Troopers/BP_Trooper.uasset index 21b32ff..6e72344 100644 Binary files a/Content/Troopers/BP_Trooper.uasset and b/Content/Troopers/BP_Trooper.uasset differ diff --git a/Content/Troopers/GreenSelectionMaterial.uasset b/Content/Troopers/GreenSelectionMaterial.uasset deleted file mode 100644 index 0249cb3..0000000 Binary files a/Content/Troopers/GreenSelectionMaterial.uasset and /dev/null differ diff --git a/Content/Troopers/MyMyProjectile.uasset b/Content/Troopers/MyMyProjectile.uasset new file mode 100644 index 0000000..7a95d3a Binary files /dev/null and b/Content/Troopers/MyMyProjectile.uasset differ diff --git a/Content/Troopers/RedSelectionMaterial.uasset b/Content/Troopers/RedSelectionMaterial.uasset deleted file mode 100644 index 2c7e4ac..0000000 Binary files a/Content/Troopers/RedSelectionMaterial.uasset and /dev/null differ diff --git a/Content/Troopers/RingMaterial.uasset b/Content/Troopers/RingMaterial.uasset index a274a5b..1d53f42 100644 Binary files a/Content/Troopers/RingMaterial.uasset and b/Content/Troopers/RingMaterial.uasset differ diff --git a/Content/Troopers/TrooperSkeletonMelee.uasset b/Content/Troopers/TrooperSkeletonMelee.uasset index f810a34..5f291b7 100644 Binary files a/Content/Troopers/TrooperSkeletonMelee.uasset and b/Content/Troopers/TrooperSkeletonMelee.uasset differ diff --git a/Content/Troopers/TrooperWizard.uasset b/Content/Troopers/TrooperWizard.uasset index a29da5c..a3959ec 100644 Binary files a/Content/Troopers/TrooperWizard.uasset and b/Content/Troopers/TrooperWizard.uasset differ diff --git a/Source/TurnBasedTutorial/Ability.h b/Source/TurnBasedTutorial/Ability.h index 282921e..315c4b8 100644 --- a/Source/TurnBasedTutorial/Ability.h +++ b/Source/TurnBasedTutorial/Ability.h @@ -32,6 +32,9 @@ public: UPROPERTY(EditAnywhere) float LinearWidth = 50.0f; + + UPROPERTY(EditAnywhere) + float Speed = 1500.0f; // Called every frame // virtual void TickComponent(float DeltaTime, diff --git a/Source/TurnBasedTutorial/MyGameState.cpp b/Source/TurnBasedTutorial/MyGameState.cpp index d9635de..35f4a6c 100644 --- a/Source/TurnBasedTutorial/MyGameState.cpp +++ b/Source/TurnBasedTutorial/MyGameState.cpp @@ -62,6 +62,10 @@ AMyPlayerState *AMyGameState::PlayerNotInTurn() const { return GetMyPlayerState(!CurrentPlayerTurn); } +TArray AMyGameState::GetTroopers() const { + return Troopers; +} + void AMyGameState::GetLifetimeReplicatedProps( TArray &OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); diff --git a/Source/TurnBasedTutorial/MyGameState.h b/Source/TurnBasedTutorial/MyGameState.h index 6fb0eac..4038845 100644 --- a/Source/TurnBasedTutorial/MyGameState.h +++ b/Source/TurnBasedTutorial/MyGameState.h @@ -31,8 +31,9 @@ public: UFUNCTION(BlueprintPure) AMyPlayerState *PlayerNotInTurn() const; - - auto GetMyPlayerState(uint8 PlayerIndex) const; + + UFUNCTION() + TArray GetTroopers() const; private: UPROPERTY(Replicated) @@ -41,4 +42,5 @@ private: UPROPERTY(Replicated) uint8 CurrentPlayerTurn{0}; + auto GetMyPlayerState(uint8 PlayerIndex) const; }; diff --git a/Source/TurnBasedTutorial/MyPlayerState.cpp b/Source/TurnBasedTutorial/MyPlayerState.cpp index c9f4fc6..e7fe995 100644 --- a/Source/TurnBasedTutorial/MyPlayerState.cpp +++ b/Source/TurnBasedTutorial/MyPlayerState.cpp @@ -15,6 +15,10 @@ void AMyPlayerState::BeginPlay() { Super::BeginPlay(); } +auto AMyPlayerState::GetMyGameState() const { + return Cast(GetWorld()->GetGameState()); +} + void AMyPlayerState::SetPlayerIndex(uint8 NewPlayerIndex) { PlayerIndex = NewPlayerIndex; } @@ -40,11 +44,30 @@ void AMyPlayerState::MoveTrooper_Implementation(ATrooper *Trooper, } } +// void AMyPlayerState::Attack_Implementation(ATrooper *Attacker, +// FVector Location, +// int ActionIndex) { +// if (Attacker->CheckAttackCorrectness(Location, ActionIndex)) { +// Attacker->Attack(ActionIndex); +// } else { +// GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, +// FString::Printf( +// TEXT( +// "Out of radius or not enough Action Points!"))); +// } +// } + void AMyPlayerState::Attack_Implementation(ATrooper *Attacker, FVector Location, - int ActionIndex) { + int ActionIndex, + const TArray &Troopers) { if (Attacker->CheckAttackCorrectness(Location, ActionIndex)) { - Attacker->Attack(ActionIndex); + Attacker->Attack(ActionIndex, Location); + // for (const auto Trooper : Troopers) { + // if (Attacker->GetPlayerIndex() != Trooper->GetPlayerIndex()) { + // Trooper->TakeDamage(Attacker->GetAbility(ActionIndex)->Damage); + // } + // } } else { GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::Printf( @@ -55,7 +78,7 @@ void AMyPlayerState::Attack_Implementation(ATrooper *Attacker, void AMyPlayerState::CycleTurns() const { if (bIsMyTurn) { - Cast(GetWorld()->GetGameState())->CycleTurns(); + GetMyGameState()->CycleTurns(); } } @@ -73,6 +96,7 @@ void AMyPlayerState::SetMyTurn(bool bMyTurn) { } } + void AMyPlayerState::StartTurn_Implementation() { SetMyTurn(true); UE_LOG(LogTemp, Warning, TEXT("Your turn, %d"), PlayerIndex); @@ -122,7 +146,7 @@ void AMyPlayerState::OnPlayerAction(const FHitResult &HitResult) { // ATTACK! ATTACK! UE_LOG(LogTemp, Warning, TEXT("Do attack")); Attack(SelectedTrooper, NewlySelectedLocation, - CurrentAction); + CurrentAction, GetMyGameState()->GetTroopers()); SelectedTrooper->SetSelection(false, CurrentAction); SelectedTrooper = nullptr; break; @@ -145,7 +169,7 @@ void AMyPlayerState::SetCurrentAction_Implementation(int Action) { CurrentAction = Action; if (SelectedTrooper) { SelectedTrooper->UpdateSelectionRadius(Action); - } + } UE_LOG(LogTemp, Warning, TEXT("SetCurrentAction: %d on Player Controller " "with index %d"), CurrentAction, PlayerIndex); } diff --git a/Source/TurnBasedTutorial/MyPlayerState.h b/Source/TurnBasedTutorial/MyPlayerState.h index a8eb3d8..5387265 100644 --- a/Source/TurnBasedTutorial/MyPlayerState.h +++ b/Source/TurnBasedTutorial/MyPlayerState.h @@ -12,6 +12,7 @@ UCLASS() class TURNBASEDTUTORIAL_API AMyPlayerState : public APlayerState { GENERATED_BODY() + public: AMyPlayerState(); @@ -19,7 +20,7 @@ public: UFUNCTION(Client, Reliable) void StartTurn(); - + UFUNCTION(Client, Reliable, BlueprintCallable) void EndTurn(); @@ -27,7 +28,10 @@ public: void MoveTrooper(ATrooper *Trooper, FVector Location); UFUNCTION(Server, Reliable) - void Attack(ATrooper *Attacker, FVector Location, int ActionIndex); + void Attack(ATrooper *Attacker, + FVector Location, + int ActionIndex, + const TArray &Troopers); UFUNCTION() void CycleTurns() const; @@ -40,7 +44,7 @@ public: UFUNCTION(BlueprintCallable, Client, Reliable) void SetCurrentAction(int Action); - + UFUNCTION(BlueprintCallable) uint8 GetPlayerIndex(); @@ -53,16 +57,18 @@ public: private: UPROPERTY(Replicated) uint8 PlayerIndex; - + UFUNCTION() void SetMyTurn(bool bMyTurn); - + UPROPERTY(Replicated) bool bIsMyTurn; UPROPERTY(Replicated) int CurrentAction = 0; - + UPROPERTY(Replicated) ATrooper *SelectedTrooper; + + auto GetMyGameState() const; }; diff --git a/Source/TurnBasedTutorial/MyProjectile.cpp b/Source/TurnBasedTutorial/MyProjectile.cpp new file mode 100644 index 0000000..2f05f04 --- /dev/null +++ b/Source/TurnBasedTutorial/MyProjectile.cpp @@ -0,0 +1,64 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "MyProjectile.h" + +#include "Net/UnrealNetwork.h" +#include "VisualLogger/VisualLoggerCustomVersion.h" + +AMyProjectile::AMyProjectile() { + // if (!CollisionComponent) { + // CollisionComponent = CreateDefaultSubobject( + // TEXT("SphereComponent")); + // } + // RootComponent = CollisionComponent; + if (!ProjectileMeshComponent) { + ProjectileMeshComponent = CreateDefaultSubobject( + TEXT("ProjectileMeshComponent")); + static ConstructorHelpers::FObjectFinder Mesh( + TEXT( + "StaticMesh'/Game/StarterContent/Shapes/Shape_Sphere.Shape_Sphere'")); + if (Mesh.Succeeded()) { + ProjectileMeshComponent->SetStaticMesh(Mesh.Object); + RootComponent = ProjectileMeshComponent; + } + } + if (!ProjectileMovementComponent) { + ProjectileMovementComponent = CreateDefaultSubobject< + UProjectileMovementComponent>(TEXT("ProjectileMovementComponent")); + ProjectileMovementComponent->SetUpdatedComponent( + ProjectileMeshComponent); + ProjectileMovementComponent->InitialSpeed = 1000.0f; + ProjectileMovementComponent->MaxSpeed = 1000.0f; + ProjectileMovementComponent->ProjectileGravityScale = 0.0f; + } + InitialLifeSpan = 2.0f; +} + +void AMyProjectile::Initialize(const UAbility *Ability, + uint8 playerIndex, + float PathLength) { + ProjectileMovementComponent->InitialSpeed = + ProjectileMovementComponent->MaxSpeed = Ability->Speed; + Damage = Ability->Damage; + float Scale = Ability->LinearWidth / 100; + ProjectileMeshComponent->SetWorldScale3D({Scale, Scale, Scale}); + PlayerIndex = playerIndex; + SetLifeSpan(PathLength / Ability->Speed); +} + +void AMyProjectile::Shoot(FVector From, FVector To) const { + ProjectileMovementComponent->Velocity = + (To - From).GetSafeNormal() * ProjectileMovementComponent->InitialSpeed; +} + + +void AMyProjectile::BeginPlay() { + Super::BeginPlay(); +} + +void AMyProjectile::GetLifetimeReplicatedProps( + TArray &OutLifetimeProps) const { + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME(AMyProjectile, Damage); +} diff --git a/Source/TurnBasedTutorial/MyProjectile.h b/Source/TurnBasedTutorial/MyProjectile.h new file mode 100644 index 0000000..372a6ec --- /dev/null +++ b/Source/TurnBasedTutorial/MyProjectile.h @@ -0,0 +1,40 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Ability.h" +#include "Components/SphereComponent.h" +#include "GameFramework/Actor.h" +#include "GameFramework/ProjectileMovementComponent.h" +#include "MyProjectile.generated.h" + +UCLASS() +class TURNBASEDTUTORIAL_API AMyProjectile : public AActor { + GENERATED_BODY() + +public: + AMyProjectile(); + + void Initialize(const UAbility *Ability, uint8 playerIndex, float PathLength); + + void Shoot(FVector From, FVector To) const; + +protected: + UPROPERTY(Replicated) + float Damage; + + UPROPERTY(Replicated) + uint8 PlayerIndex = -1; + + // UPROPERTY(EditAnywhere) + // USphereComponent *CollisionComponent; + + UPROPERTY(EditAnywhere) + UStaticMeshComponent *ProjectileMeshComponent; + + UPROPERTY(VisibleAnywhere) + UProjectileMovementComponent *ProjectileMovementComponent; + + virtual void BeginPlay() override; +}; diff --git a/Source/TurnBasedTutorial/Trooper.cpp b/Source/TurnBasedTutorial/Trooper.cpp index c7c3662..e5bfe1b 100644 --- a/Source/TurnBasedTutorial/Trooper.cpp +++ b/Source/TurnBasedTutorial/Trooper.cpp @@ -2,7 +2,7 @@ #include #include "HealthBar.h" -#include "MyPlayerState.h" +#include "MyProjectile.h" #include "Components/WidgetComponent.h" #include "Net/UnrealNetwork.h" @@ -32,7 +32,6 @@ ATrooper::ATrooper() if (MeshToUse.Object) { SelectionStaticMesh->SetStaticMesh(MeshToUse.Object); - SelectionStaticMesh->SetStaticMesh(MeshToUse.Object); } // SelectionStaticMesh->SetRelativeTransform(FTransform({1000,1000,100}, {0, 0, 0}), false, // nullptr, ETeleportType::TeleportPhysics); @@ -71,6 +70,7 @@ void ATrooper::Initialize(uint8 const NewPlayerIndex, PlayerIndex = NewPlayerIndex; bIsMoving = false; AttackPlayedTime = 0.0f; + TakingDamagePlayedTime = 0.0f; CurrentLocation = SpawnLocation; Id = NewId; } @@ -78,11 +78,24 @@ void ATrooper::Initialize(uint8 const NewPlayerIndex, void ATrooper::Tick(float const DeltaTime) { if (bIsAttacking) { AttackPlayedTime += DeltaTime; + if (bIsWaitingForFire && AttackPlayedTime >= FireAfterTime) { + FireProjectile(); + CurrentAbilityIndex = -1; + CurrentAbilityDestination = {}; + bIsWaitingForFire = false; + } if (AttackPlayedTime >= AttackDuration) { AttackPlayedTime = 0.0f; bIsAttacking = false; } } + if (bIsTakingDamage) { + TakingDamagePlayedTime += DeltaTime; + if (TakingDamagePlayedTime >= TakingDamageDuration) { + TakingDamagePlayedTime = 0.0f; + bIsTakingDamage = false; + } + } if (bIsMoving) { FVector PositionVector = (TargetLocation - CurrentLocation); PositionVector.Normalize(); @@ -133,6 +146,11 @@ void ATrooper::GetLifetimeReplicatedProps( DOREPLIFETIME(ATrooper, ActionPoints); DOREPLIFETIME(ATrooper, HealthWidgetComponent); DOREPLIFETIME(ATrooper, AttackPlayedTime); + DOREPLIFETIME(ATrooper, TakingDamagePlayedTime); + DOREPLIFETIME(ATrooper, bIsTakingDamage); + DOREPLIFETIME(ATrooper, bIsDead); + DOREPLIFETIME(ATrooper, CurrentAbilityIndex); + DOREPLIFETIME(ATrooper, CurrentAbilityDestination); } uint8 ATrooper::GetPlayerIndex() const { @@ -221,19 +239,89 @@ UAbility *ATrooper::GetAbility(int AbilityIndex) const { } } -float ATrooper::GetAnimationValue() { +bool ATrooper::TakeDamage(float Damage) { + HitPoints = FMath::Max(0, HitPoints - Damage); + if (HitPoints == 0) { + bIsDead = true; + return true; + } + bIsTakingDamage = true; + return false; +} + +TSubclassOf ATrooper::GetProjectileClass( + uint8 AbilityIndex) const { + switch (AbilityIndex) { + case 1: + return AttackProjectileClass; + case 2: + return SpecialProjectileClass; + default: + return AMyProjectile::StaticClass(); + } +} + +void ATrooper::FireProjectile_Implementation() { + if (!this || !this->IsValidLowLevel()) + return; + FTransform SpawnTransform( + (CurrentAbilityDestination - CurrentLocation).Rotation()); + const FVector TransformedVector = SpawnTransform.TransformVector( + {GetAbility(CurrentAbilityIndex)->LinearWidth / 2 + 50.0f, 0.0f, + 88.0f}); + const FVector SpawnLocation = CurrentLocation + TransformedVector; + SpawnTransform.SetLocation(SpawnLocation); + FActorSpawnParameters SpawnParameters; + SpawnParameters.Owner = this; + SpawnParameters.Instigator = GetInstigator(); + SpawnParameters.SpawnCollisionHandlingOverride = + ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + AMyProjectile *Projectile = GetWorld()->SpawnActor( + GetProjectileClass(CurrentAbilityIndex), SpawnTransform, + SpawnParameters); + Projectile->Initialize(GetAbility(CurrentAbilityIndex), CurrentAbilityIndex, + (CurrentAbilityDestination - SpawnLocation). + Size()); + Projectile->Shoot(SpawnLocation, CurrentAbilityDestination); +} + +int ATrooper::GetAnimationValue() { + if (bIsDead) { + return 4; + } + if (bIsTakingDamage) { + return 3; + } if (bIsAttacking) { - return -100.0f; + return 2; } if (bIsMoving) { - return 100.0f; + return 1; } - return 0.0f; + return 0; } -void ATrooper::Attack(int abilityIndex) { +void ATrooper::Attack(int AbilityIndex, FVector ToLocation) { bIsAttacking = true; - ActionPoints -= GetAbility(abilityIndex)->ActionCost; + bIsWaitingForFire = true; + ActionPoints -= GetAbility(AbilityIndex)->ActionCost; + CurrentAbilityIndex = AbilityIndex; + ToLocation.Z = 88.0f; + CurrentAbilityDestination = ToLocation; + // FTransform SpawnTransform((ToLocation - CurrentLocation).Rotation()); + // SpawnTransform.SetLocation( + // CurrentLocation + SpawnTransform.TransformVector( + // {GetAbility(AbilityIndex)->LinearWidth/2 + 50.0f, 0.0f, 0.0f}) + // ); + // FActorSpawnParameters SpawnParameters; + // SpawnParameters.Owner = this; + // SpawnParameters.Instigator = GetInstigator(); + // SpawnParameters.SpawnCollisionHandlingOverride = + // ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + // AMyProjectile *Projectile = GetWorld()->SpawnActor( + // GetProjectileClass(AbilityIndex), SpawnTransform, SpawnParameters); + // Projectile->Initialize(GetAbility(AbilityIndex)); + // Projectile->Shoot(CurrentLocation, ToLocation); } bool ATrooper::CheckMoveCorrectness(const FVector newPos) const { diff --git a/Source/TurnBasedTutorial/Trooper.h b/Source/TurnBasedTutorial/Trooper.h index 411d004..dde6005 100644 --- a/Source/TurnBasedTutorial/Trooper.h +++ b/Source/TurnBasedTutorial/Trooper.h @@ -36,10 +36,10 @@ public: FVector GetLocation() const; UFUNCTION(BlueprintCallable) - float GetAnimationValue(); + int GetAnimationValue(); UFUNCTION() - void Attack(int abilityIndex); + void Attack(int AbilityIndex, FVector ToLocation); UFUNCTION() float GetActionRadius(int action) const; @@ -65,6 +65,9 @@ public: UFUNCTION() UAbility *GetAbility(int AbilityIndex) const; + UFUNCTION() + bool TakeDamage(float Damage); + protected: constexpr static float PIXELS_IN_RADIUS = 50; @@ -79,6 +82,24 @@ protected: UPROPERTY(EditAnywhere) UAbility *SpecialAbility; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TSubclassOf AttackProjectileClass; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TSubclassOf SpecialProjectileClass; + + UFUNCTION() + TSubclassOf GetProjectileClass(uint8 AbilityIndex) const; + + UFUNCTION(Server, Reliable) + void FireProjectile(); + + UPROPERTY(Replicated) + uint8 CurrentAbilityIndex = -1; + + UPROPERTY(Replicated) + FVector CurrentAbilityDestination = {}; UPROPERTY(EditAnywhere) float Speed = 300.0f; @@ -97,7 +118,7 @@ protected: UPROPERTY(Replicated) float ActionPoints; - + UPROPERTY(Replicated) bool bIsAttacking = false; @@ -106,6 +127,22 @@ protected: const float AttackDuration = 1.16667f; + const float FireAfterTime = 0.6f; + + UPROPERTY(Replicated) + bool bIsWaitingForFire = false; + + UPROPERTY(Replicated) + bool bIsTakingDamage = false; + + UPROPERTY(Replicated) + float TakingDamagePlayedTime; + + const float TakingDamageDuration = 1.46667f; + + UPROPERTY(Replicated) + bool bIsDead = false; + virtual void BeginPlay() override; virtual void Tick(float const DeltaTime) override;