박하의 나날

[15]Battery Collector_플레이 상태별 spawn volume작동, 플레이어 입력차단, 캐릭터 레그돌효과

프로그래밍/Unreal

 

19.Toggling the Spawn Volumes 

SpawnVolume.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "GameFramework/Actor.h"
#include "SpawnVolume.generated.h"
 
UCLASS()
class BATTERYCOLLECTOR_API ASpawnVolume : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    ASpawnVolume();
 
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
    
    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;
    /*Returns the WhereToSpawn subobject*/
    FORCEINLINE class UBoxComponent* GetWhereToSpawn() const { return WhereToSpawn; }
 
    /*Find a random point within the Boxcomponent*/
    UFUNCTION(BlueprintPure, Category = "Spawning")
    FVector GetRandomPointInVolume();
 
    /**This function toggles whether or not the spawn volume spawns pickups*/ 
    UFUNCTION(BlueprintCallable, Category = "Spawning")
    void SetSpawningActive(bool bShouldSpawn);
 
protected:
    /**The pickup to spawn*/
    UPROPERTY(EditAnywhere, Category = "spawn")
    TSubclassOf<class APickup> WhatToSpawn;
 
    FTimerHandle SpawnTimer;
    //Minimum spawn delay
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
    float SpawnDelayRangeLow;
    //Maximum spawn delay
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning")
    float SpawnDelayRangeHigh;
 
private:
    /*Box Component to specify where pickups should be spawned*/
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spawning", meta = (AllowPrivateAccess = "true"))
    class UBoxComponent* WhereToSpawn;
 
    //Handle spawning a new pickup
    void SpawnPickup();
 
    /*The current spawn delay*/
    float SpawnDelay;
};
 
cs

 

33)Blueprint의 기능을 이용하면 spawn volume으로 하여금 언제 spawn을 하고 말지 호출하도록 만들 수 있다.

34)volume spawn 의 pickupspawn 작동을 조정하는 함수
 파라미터로 Bool bShouldSpawn을 받아서 Spawn Volme의 활성화 여부를 결정하게 한다.

SpawnVolume.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Fill out your copyright notice in the Description page of Project Settings.
 
#include "BatteryCollector.h"
#include "SpawnVolume.h"
#include "Kismet/KismetMathLibrary.h"
#include "Pickup.h"
 
// Sets default values
ASpawnVolume::ASpawnVolume()
{
     // Set this actor to call Tick() every frame. 
    PrimaryActorTick.bCanEverTick = false;
    //Create the Box Component to represent the spawn volume
    WhereToSpawn = CreateDefaultSubobject<UBoxComponent>(TEXT("WhereToSpawn"));
    RootComponent = WhereToSpawn;
    //Set the spawn delay range
    SpawnDelayRangeLow = 1.0f;
    SpawnDelayRangeHigh = 4.5f;
}
 
// Called when the game starts or when spawned
void ASpawnVolume::BeginPlay()
{
    Super::BeginPlay();        
}
 
// Called every frame
void ASpawnVolume::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );
}
 
FVector ASpawnVolume::GetRandomPointInVolume(){
    FVector SpawnOrigin = WhereToSpawn->Bounds.Origin;
    FVector SpawnExtent = WhereToSpawn->Bounds.BoxExtent;
    
    return UKismetMathLibrary::RandomPointInBoundingBox(SpawnOrigin, SpawnExtent);
}
 
void ASpawnVolume::SetSpawningActive(bool bShouldSpawn){
    if (bShouldSpawn){        
        //Set the timer on Spawn Pickup
        SpawnDelay = FMath::FRandRange(SpawnDelayRangeLow, SpawnDelayRangeHigh);        
        GetWorldTimerManager().SetTimer(SpawnTimer, this&ASpawnVolume::SpawnPickup, SpawnDelay, false);
    }
    else{
        //clear the timer on spawn pickup        
        GetWorldTimerManager().ClearTimer(SpawnTimer);
    }
}
 
void ASpawnVolume::SpawnPickup(){
 
    //1.If we have set something to spawn:
    if (WhatToSpawn != NULL){
        //2.Check for a valid world:
        UWorld* const World = GetWorld();
        if (World){
            //3.Set the spawn parameters
            FActorSpawnParameters SpawnParams;
            SpawnParams.Owner = this;
            SpawnParams.Instigator = Instigator;
            /**Get a random location to spawn at*/
            FVector SpawnLocation = GetRandomPointInVolume();
            //Get a random rotation for the spawned item
            FRotator SpawnRotation;
            SpawnRotation.Yaw = FMath::FRand()*360.0f;
            SpawnRotation.Pitch = FMath::FRand()*360.0f;
            SpawnRotation.Roll = FMath::FRand()*360.0f;
            //4.spawn the pickup
            APickup* const SpawnedPickup = World->SpawnActor<APickup>(WhatToSpawn, SpawnLocation, SpawnRotation, SpawnParams);
 
            SpawnDelay = FMath::FRandRange(SpawnDelayRangeLow, SpawnDelayRangeHigh);            
            GetWorldTimerManager().SetTimer(SpawnTimer, this&ASpawnVolume::SpawnPickup, SpawnDelay, false);
        }
        
    }
}
cs

41)활성화 여부는 bShouldSpawn의 값을 따른다.

43,44)Beginplay에 타이머두고 기준으로 spawnpickup함수를 호출할지 말지 결정했었음.
  spawn volume은 setspawningActive함수가 호출되고 나서 spawning을 시작해야 하므로 BeginPlay에 있던 타이머를 여기로 옮겨옴.

44)게임속 실행중인 모든 타이머를 추적한다.

48)spawn pickup함수가 tick에서 더이상 호출되지 않는다.

 

BatteryCollectorGameMode.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/GameMode.h"
#include "BatteryCollectorGameMode.generated.h"
 
 
//enum to store the current state of gameplay
UENUM(BlueprintType)
enum class EBatteryPlayState{
    EPlaying,
    EGameOver,
    EWon,
    EUnknown
};
UCLASS(minimalapi)
class ABatteryCollectorGameMode : public AGameMode
{
    GENERATED_BODY()
 
public:
    ABatteryCollectorGameMode();
 
    virtual void Tick(float DeltaTime) override;
    
    //Returns power needed to win - needed for the HUD
    UFUNCTION(BlueprintPure, Category = "Power")
    float GetPowerToWin() const;
 
    virtual void BeginPlay() override;
 
    /**Returns the current playing state*/
    UFUNCTION(Blueprintpure, Category = "Power")
    EBatteryPlayState GetCurrentState() const;
    //Sets a new playing state
    void SetCurrentState(EBatteryPlayState NewState);
 
protected:
    /**The rate at which the character loses power */
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))
    float DecayRate;
 
    /**The power needed to win the game*/
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))
    float PowerToWin;
 
    /**The widget class to use for our HUD screen*/
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Power", Meta = (BlueprintProtected = "true"))
    TSubclassOf<class UUserWidget> HUDWidgetClass;
 
    //The instance of the HUD
    /***/
    UPROPERTY()
    class UUserWidget* CurrentWidget;
 
private:
    /** Keeps track of the current playing state*/
    EBatteryPlayState CurrentState;
    TArray<class ASpawnVolume*> SpawnVolumeActors;
 
    /**    Handle any function calls that rely upon changing the playing state of our game*/
    void HandleNewState(EBatteryPlayState NewState);
};
cs

57)모든 spawn volume을 배열에 저장할 것.

61)게임 플레이 상태에 영향을 주는 모든 함수의 호출을 담당

 

BatteryCollectorGameMode.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
 
#include "BatteryCollector.h"
#include "BatteryCollectorGameMode.h"
#include "BatteryCollectorCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/UserWidget.h"
#include "SpawnVolume.h"
 
ABatteryCollectorGameMode::ABatteryCollectorGameMode()
{
    // set default pawn class to our Blueprinted character
    static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));
    if (PlayerPawnBPClass.Class != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Class;
    }
 
    //base decay rate
    DecayRate = 0.01f;
}
 
void ABatteryCollectorGameMode::BeginPlay()
{
    Super::BeginPlay();
    
    TArray<AActor*> FoundActors; 
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundActors);
 
    for (auto Actor : FoundActors)
    {
        ASpawnVolume* SpawnVolumeActor = Cast<ASpawnVolume>(Actor);
        if (SpawnVolumeActor)
        {
            SpawnVolumeActors.AddUnique(SpawnVolumeActor);
        }
    }
 
    SetCurrentState(EBatteryPlayState::EPlaying);
 
    // set the score to beat
    ABatteryCollectorCharacter* MyCharacter = Cast<ABatteryCollectorCharacter(UGameplayStatics::GetPlayerPawn(this0));
    if (MyCharacter)
    {
        PowerToWin = (MyCharacter->GetInitialPower())*1.25f;
    }
 
    if (HUDWidgetClass != nullptr)
    {
        CurrentWidget = CreateWidget<UUserWidget>(GetWorld(), HUDWidgetClass);
        if (CurrentWidget != nullptr)
        {
            CurrentWidget->AddToViewport();
        }
    }    
}
cs

 

23~37)게임이 시작하면 GetAllActorsOfClass를 사용해서 모든 spawnvolume 액터를 검색함->
 찾아낸 Actor를 저장하기 위해 배열 내 엑터를 형변환 하고->  spawn volume엑터로 확인되면 spawnvolumeActor배열에 저장한다.

27)레벨에서 모든 엑터 찾기 ->spawn Volume찾으면 spawnvolumeActor배열에 저장.

28)GetAllActorsOfClass : 레벨전체를 검색하기때문에 레벨의 크기가 아주크면 퍼포먼스에 신경써야함.

27,8)또 다른 Gameplay Statics함수와 또 다른 배열을 이용해 초기화된 엑터를 담을것.
 엑터의 베이스 클래스로 배열을 설정한 이유는 GetAllActorsOfClass가 이 클래스 형으로  반환하기 때문이다.

 

 

20.Handling New Play States 

BatteryCollectorGameMode.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "GameFramework/GameMode.h"
#include "BatteryCollectorGameMode.generated.h"
 
 
//enum to store the current state of gameplay
UENUM(BlueprintType)
enum class EBatteryPlayState{
    EPlaying,
    EGameOver,
    EWon,
    EUnknown
};
UCLASS(minimalapi)
class ABatteryCollectorGameMode : public AGameMode
{
    private:
    /** Keeps track of the current playing state*/
    EBatteryPlayState CurrentState;
    TArray<class ASpawnVolume*> SpawnVolumeActors;
 
    /**    Handle any function calls that rely upon changing the playing state of our game    */
    void HandleNewState(EBatteryPlayState NewState);
};
cs

24) 게임 플레이 상태에 영향을 주는 모든 함수의 호출을 담당 .

handler를 gamemode내에서만 호출할 수 있게 하기 위해서 private.

 

BatteryCollectorGameMode.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
 
#include "BatteryCollector.h"
#include "BatteryCollectorGameMode.h"
#include "BatteryCollectorCharacter.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/UserWidget.h"
#include "SpawnVolume.h"
 
ABatteryCollectorGameMode::ABatteryCollectorGameMode()
{
    // set default pawn class to our Blueprinted character
    static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter"));
    if (PlayerPawnBPClass.Class != NULL)
    {
        DefaultPawnClass = PlayerPawnBPClass.Class;
    }
 
    //base decay rate
    DecayRate = 0.01f;
}
 
void ABatteryCollectorGameMode::BeginPlay()
{
    Super::BeginPlay();
    // find all spawn volume Actors    
    TArray<AActor*> FoundActors; 
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundActors);
 
    for (auto Actor : FoundActors)
    {
        ASpawnVolume* SpawnVolumeActor = Cast<ASpawnVolume>(Actor);
        if (SpawnVolumeActor)
        {
            SpawnVolumeActors.AddUnique(SpawnVolumeActor);
        }
    }
 
    SetCurrentState(EBatteryPlayState::EPlaying);
 
    // set the score to beat
    ABatteryCollectorCharacter* MyCharacter = Cast<ABatteryCollectorCharacter>(UGameplayStatics::GetPlayerPawn(this0));
    if (MyCharacter)
    {
        PowerToWin = (MyCharacter->GetInitialPower())*1.25f;
    }
 
    if (HUDWidgetClass != nullptr)
    {
        CurrentWidget = CreateWidget<UUserWidget>(GetWorld(), HUDWidgetClass);
        if (CurrentWidget != nullptr)
        {
            CurrentWidget->AddToViewport();
        }
    }    
}
 
void ABatteryCollectorGameMode::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    // Check that we are using the battery collector character
    ABatteryCollectorCharacter* MyCharacter = Cast<ABatteryCollectorCharacter>(UGameplayStatics::GetPlayerPawn(this0));
    if (MyCharacter)
    {
        // If our power is greater than needed to win, set the game's state to Won
        if (MyCharacter->GetCurrentPower() > PowerToWin)
        {
            SetCurrentState(EBatteryPlayState::EWon);
        }
        // if the character's power is positive
        else if (MyCharacter->GetCurrentPower() > 0)
        {
            // decrease the character's power using the decay rate
            MyCharacter->UpdatePower(-DeltaTime*DecayRate*(MyCharacter->GetInitialPower()));
        }
        else
        {
            SetCurrentState(EBatteryPlayState::EGameOver);
        }
    }
}
 
float ABatteryCollectorGameMode::GetPowerToWin() const
{
    return PowerToWin;
}
 
EBatteryPlayState ABatteryCollectorGameMode::GetCurrentState() const
{
    return CurrentState;
}
 
void ABatteryCollectorGameMode::SetCurrentState(EBatteryPlayState NewState)
{
    //set current state
    CurrentState = NewState;
    // handle the new current state
    HandleNewState(CurrentState);
}
 
void ABatteryCollectorGameMode::HandleNewState(EBatteryPlayState NewState)
{
    switch (NewState)
    {
        // If the game is playing
        case EBatteryPlayState::EPlaying:
        {
            // spawn volumes active
            for (ASpawnVolume* Volume : SpawnVolumeActors)
            {
                Volume->SetSpawningActive(true);
            }
        }
        break;
        // If we've won the game
        case EBatteryPlayState::EWon:
        {
            // spawn volumes inactive
            for (ASpawnVolume* Volume : SpawnVolumeActors)
            {
                Volume->SetSpawningActive(false);
            }
        }
        break;
        // If we've lost the game
        case EBatteryPlayState::EGameOver:
        {
            // spawn volumes inactive
            for (ASpawnVolume* Volume : SpawnVolumeActors)
            {
                Volume->SetSpawningActive(false);
            }
            // block player input : cinematic mode 
            APlayerController* PlayerController = UGameplayStatics::GetPlayerController(this0);
            if (PlayerController){
                PlayerController->SetCinematicMode(truefalsefalsetruetrue);
            }
            // ragdoll the character
            ACharacter* MyCharacter = UGameplayStatics::GetPlayerCharacter(this0);
            if (MyCharacter){
                MyCharacter->GetMesh()->SetSimulatePhysics(true);
                MyCharacter->GetMovementComponent()->MovementState.bCanJump = false;                    
            }            
        }
        break;
        // Unknown/default state        
        default:
        case EBatteryPlayState::EUnknown:
        {
            // do nothing
        }
        break;
    }
 
}
 
cs

148,149)HandleCurrentState의 호출을 잊어서 EPlaying, EWon, EGameOVer등의 상태가 나타나지
  않을때 도움이 될것. 이 switch문에 떨어질때를 대비해 디버그스트링을 삽입할 수도 있다

110~)for문: 배열 내 모든엑터들을 돌면서 SetSpawningActive을 false 또는 true로.

134)cinematic mode:플레이어 입력 차단

139~144)mesh를 가져와서 게임에서 패배하면 캐릭터가 ragdoll을 일으켜 쓰러지도록함.
현재 include되어있는 기본캐릭터로 작업중이기 때문에 커스텀한 캐릭터로 형변환 하지 않는다.  
movement등이 기본으로 들어가있는 클래스.
폰이 아니라 반드시 캐릭터를 사용해야한다. 캐릭터 클래스에는 추가적인 이동셋과 스켈레탈 메시까지 붙어있기 때문이다.
   
140)뒤에 GetPlayerCharacter를 써서 형변환이 더이상 필요없도록.

*ragdoll은 skeletal mesh에서 create - physics asset추가. 그리고 캐릭터 블루프린트 collision변경.

 

 

게임오바되면 ragdoll적용되서 와르르 무너지고 입력안된당.