Start of game mode
This commit is contained in:
parent
8ac4271602
commit
1fc53e9e62
@ -1,3 +1,8 @@
|
||||
[/Script/EngineSettings.GeneralProjectSettings]
|
||||
ProjectID=EAE41D29471FBD447E5454B1D01C9ECF
|
||||
ProjectName=Third Person Game Template
|
||||
ProjectName=Gravity Stomp
|
||||
Description=Multiplayer competitive platformer where you manipulate gravity
|
||||
CopyrightNotice=Gravity Stomp Copyright Kevin Poretti
|
||||
ProjectVersion=0.0.1
|
||||
ProjectDisplayedTitle=NSLOCTEXT("[/Script/EngineSettings]", "79D56CC04C9E98AB5B2981808A14FB31", "Gravity Stomp")
|
||||
|
||||
|
BIN
GravityStomp/Content/Core/BP_GSDefaultGameMode.uasset
(Stored with Git LFS)
BIN
GravityStomp/Content/Core/BP_GSDefaultGameMode.uasset
(Stored with Git LFS)
Binary file not shown.
BIN
GravityStomp/Content/Maps/Debug/Test.umap
(Stored with Git LFS)
BIN
GravityStomp/Content/Maps/Debug/Test.umap
(Stored with Git LFS)
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "GSCharacter.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
@ -57,6 +57,11 @@ AGSCharacter::AGSCharacter(const FObjectInitializer& ObjectInitializer)
|
||||
}
|
||||
|
||||
|
||||
void AGSCharacter::ChangeTeamColor(bool bIsPlayerFriendly)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void AGSCharacter::BeginPlay()
|
||||
{
|
||||
// Call the base class
|
||||
@ -98,6 +103,7 @@ void AGSCharacter::Move(const FInputActionValue& Value)
|
||||
AddMovementInput(FVector::RightVector, MovementVector.X);
|
||||
}
|
||||
|
||||
|
||||
void AGSCharacter::ChangeGravityDirection(const FInputActionValue& Value)
|
||||
{
|
||||
FVector2D GravityDirection = Value.Get<FVector2D>();
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -35,6 +35,8 @@ class AGSCharacter : public ACharacter
|
||||
public:
|
||||
AGSCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
void ChangeTeamColor(bool bIsPlayerFriendly);
|
||||
|
||||
protected:
|
||||
|
||||
/** Called for movement input */
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "Character/GSCharacterMovementComponent.h"
|
||||
|
||||
@ -856,15 +856,9 @@ void UGSCharacterMovementComponent::PhysicsRotation(float DeltaTime)
|
||||
|
||||
if (ShouldRemainVertical())
|
||||
{
|
||||
FString DebugDesiredRotation = FString::Printf(TEXT("Desired Rotation: %s"), *DesiredRotation.ToString());
|
||||
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Cyan, DebugDesiredRotation);
|
||||
|
||||
DesiredRotation.Pitch = IsCharacterUpAlignedToWorldUp() ? 0.f : FRotator::NormalizeAxis(DesiredRotation.Pitch);
|
||||
DesiredRotation.Yaw = IsCharacterUpAlignedToWorldUp() ? FRotator::NormalizeAxis(DesiredRotation.Yaw) : 0.f;
|
||||
DesiredRotation.Roll = GetRollFromCharacterUpDir(CharacterUpDirection);
|
||||
|
||||
DebugDesiredRotation = FString::Printf(TEXT("Desired Rotation after keeping it vertical: %s"), *DesiredRotation.ToString());
|
||||
GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Cyan, DebugDesiredRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -1,7 +1,309 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "GSGameModeBase.h"
|
||||
|
||||
// GS includes
|
||||
#include "EngineUtils.h"
|
||||
#include "GSGameState.h"
|
||||
#include "Player/GSPlayerController.h"
|
||||
#include "Player/GSPlayerState.h"
|
||||
|
||||
AGSGameModeBase::AGSGameModeBase()
|
||||
{
|
||||
ScoreLimit = 25;
|
||||
MaxNumTeams = 2;
|
||||
TimeBetweenRounds = 20.0f;
|
||||
RoundTime = 600.0f;
|
||||
WarmupTime = 15.0f;
|
||||
bWarmupEnabled = true;
|
||||
bDelayedStart = bWarmupEnabled;
|
||||
|
||||
PlayerControllerClass = AGSPlayerController::StaticClass();
|
||||
PlayerStateClass = AGSPlayerState::StaticClass();
|
||||
GameStateClass = AGSGameState::StaticClass();
|
||||
|
||||
MinRespawnDelay = 5.0f;
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::PreInitializeComponents()
|
||||
{
|
||||
Super::PreInitializeComponents();
|
||||
|
||||
// Setup timer for match tick
|
||||
GetWorldTimerManager().SetTimer(TimerHandle_DefaultTimer, this, &AGSGameModeBase::DefaultTimer, GetWorldSettings()->GetEffectiveTimeDilation(), true);
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage)
|
||||
{
|
||||
Super::InitGame(MapName, Options, ErrorMessage);
|
||||
|
||||
bUseSeamlessTravel = !GetWorld()->IsPlayInEditor();
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::InitGameState()
|
||||
{
|
||||
Super::InitGameState();
|
||||
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
check(GS)
|
||||
GS->SetNumTeams(MaxNumTeams);
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId,
|
||||
FString& ErrorMessage)
|
||||
{
|
||||
// reject player is match has ended
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
bool bMatchEnded = GS && GS->HasMatchEnded();
|
||||
if(bMatchEnded)
|
||||
{
|
||||
ErrorMessage = TEXT("Can't join a match that has ended.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Super::PreLogin(Options, Address, UniqueId, ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::PostLogin(APlayerController* NewPlayer)
|
||||
{
|
||||
Super::PostLogin(NewPlayer);
|
||||
|
||||
PickTeam(NewPlayer->GetPlayerState<AGSPlayerState>());
|
||||
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(NewPlayer);
|
||||
|
||||
if(PC && IsMatchInProgress())
|
||||
{
|
||||
PC->Client_GameStarted();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::RestartPlayer(AController* NewPlayer)
|
||||
{
|
||||
Super::RestartPlayer(NewPlayer);
|
||||
|
||||
// inform player controller that game has started using client RPC
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(NewPlayer);
|
||||
if(PC)
|
||||
{
|
||||
PC->Client_GameStarted();
|
||||
}
|
||||
}
|
||||
|
||||
bool AGSGameModeBase::ShouldSpawnAtStartSpot(AController* Player)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AGSGameModeBase::CanDamagePlayer(AGSPlayerController* Damager, AGSPlayerController* Victim)
|
||||
{
|
||||
AGSPlayerState* DamagerPS = Damager ? Damager->GetPlayerState<AGSPlayerState>() : nullptr;
|
||||
AGSPlayerState* VictimPS = Victim ? Victim->GetPlayerState<AGSPlayerState>() : nullptr;
|
||||
|
||||
// can damage if both instigator and victim are valid and are on different teams
|
||||
return DamagerPS && VictimPS && (DamagerPS->GetTeamNum() != VictimPS->GetTeamNum());
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::PickTeam(AGSPlayerState* PlayerState)
|
||||
{
|
||||
// place player on the team with the fewest players, or on the first team if all teams have the same number of players
|
||||
TArray<int32> NumTeamPlayers;
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
NumTeamPlayers.AddZeroed(GS->GetNumTeams());
|
||||
|
||||
check(NumTeamPlayers.Num() > 0);
|
||||
|
||||
for(int32 PlayerIdx = 0; PlayerIdx < GS->PlayerArray.Num(); ++PlayerIdx)
|
||||
{
|
||||
AGSPlayerState* CurrPS = Cast<AGSPlayerState>(GS->PlayerArray[PlayerIdx]);
|
||||
if(CurrPS && CurrPS->GetTeamNum() >= 0 && CurrPS->GetTeamNum() < NumTeamPlayers.Num())
|
||||
{
|
||||
NumTeamPlayers[CurrPS->GetTeamNum()]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Player %s is on team number %d but only %d teams exist"), *CurrPS->GetPlayerName(), CurrPS->GetTeamNum(), NumTeamPlayers.Num())
|
||||
}
|
||||
}
|
||||
|
||||
int32 LowestTeamIdx = 0;
|
||||
for(int32 TeamIdx = 0; TeamIdx < NumTeamPlayers.Num(); TeamIdx++)
|
||||
{
|
||||
if(NumTeamPlayers[TeamIdx] < NumTeamPlayers[LowestTeamIdx])
|
||||
{
|
||||
LowestTeamIdx = TeamIdx;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerState->SetTeamNum(LowestTeamIdx);
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::OnPlayerKilled(AGSPlayerController* Killer, AGSPlayerController* Victim)
|
||||
{
|
||||
// Only keep track of kills in an active round
|
||||
if(MatchState != MatchState::InProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AGSPlayerState* KillerPS = Killer ? Killer->GetPlayerState<AGSPlayerState>() : nullptr;
|
||||
AGSPlayerState* VictimPS = Victim ? Victim->GetPlayerState<AGSPlayerState>() : nullptr;
|
||||
|
||||
if(KillerPS && VictimPS)
|
||||
{
|
||||
// keep track of player's K/D
|
||||
KillerPS->IncrementNumKills();
|
||||
Killer->Client_OnKill(KillerPS, VictimPS);
|
||||
|
||||
VictimPS->IncrementNumDeaths();
|
||||
|
||||
// inform all players of the death
|
||||
for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
|
||||
{
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
|
||||
if(PC)
|
||||
{
|
||||
PC->Client_OnDeath(KillerPS, VictimPS);
|
||||
}
|
||||
}
|
||||
|
||||
// give killer's team a point and check if score limit is reached
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
check(GS);
|
||||
int32 NewScore = GS->GiveTeamPoint(KillerPS->GetTeamNum());
|
||||
if(NewScore >= ScoreLimit)
|
||||
{
|
||||
FinishMatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::DefaultTimer()
|
||||
{
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
if(GS && GS->GetRemainingTime() > 0)
|
||||
{
|
||||
GS->SetRemainingTime(GS->GetRemainingTime() - 1);
|
||||
if(GS->GetRemainingTime() <= 0)
|
||||
{
|
||||
if(GetMatchState() == MatchState::WaitingPostMatch)
|
||||
{
|
||||
RestartGame();
|
||||
}
|
||||
else if (GetMatchState() == MatchState::InProgress)
|
||||
{
|
||||
FinishMatch();
|
||||
}
|
||||
else if (GetMatchState() == MatchState::WaitingToStart)
|
||||
{
|
||||
StartMatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::FinishMatch()
|
||||
{
|
||||
if(IsMatchInProgress())
|
||||
{
|
||||
EndMatch();
|
||||
|
||||
// determine match winner
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
check(GS);
|
||||
int32 WinningTeamIdx = GS->GetWinningTeam();
|
||||
|
||||
// notify players that game has ended and notify winning players that they won
|
||||
for(FConstPlayerControllerIterator PCIt = GetWorld()->GetPlayerControllerIterator(); PCIt; ++PCIt)
|
||||
{
|
||||
// notify player if they won
|
||||
APlayerController* PC = PCIt->Get();
|
||||
PC->GameHasEnded(nullptr, PC->GetPlayerState<AGSPlayerState>()->GetTeamNum() == WinningTeamIdx);
|
||||
}
|
||||
|
||||
// turn off all pawns
|
||||
for (APawn* Pawn : TActorRange<APawn>(GetWorld()))
|
||||
{
|
||||
Pawn->TurnOff();
|
||||
}
|
||||
|
||||
// set game state remaining time to time between matches
|
||||
GS->SetRemainingTime(TimeBetweenRounds);
|
||||
GS->ResetTeamScores();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::HandleMatchIsWaitingToStart()
|
||||
{
|
||||
Super::HandleMatchIsWaitingToStart();
|
||||
|
||||
// setup warmup
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
if(GS && GS->GetRemainingTime() <= 0)
|
||||
{
|
||||
if(bWarmupEnabled)
|
||||
{
|
||||
GS->SetRemainingTime(WarmupTime);
|
||||
}
|
||||
}
|
||||
|
||||
// notify players warmup period has started
|
||||
for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
|
||||
{
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
|
||||
if(PC)
|
||||
{
|
||||
PC->Client_WarmupStarted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSGameModeBase::HandleMatchHasStarted()
|
||||
{
|
||||
Super::HandleMatchHasStarted();
|
||||
|
||||
// setup in progress match
|
||||
AGSGameState* GS = GetGameState<AGSGameState>();
|
||||
if(GS && GS->GetRemainingTime() <= 0)
|
||||
{
|
||||
GS->SetRemainingTime(RoundTime);
|
||||
}
|
||||
|
||||
// notify players match has started
|
||||
for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
|
||||
{
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
|
||||
if(PC)
|
||||
{
|
||||
PC->Client_GameStarted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AGSGameModeBase::RestartGame()
|
||||
{
|
||||
// notify players game has restarted
|
||||
for(FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
|
||||
{
|
||||
AGSPlayerController* PC = Cast<AGSPlayerController>(*It);
|
||||
if(PC)
|
||||
{
|
||||
PC->Client_GameRestarted();
|
||||
}
|
||||
}
|
||||
|
||||
Super::RestartGame();
|
||||
}
|
@ -1,18 +1,129 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
// GS includes
|
||||
#include "Player/GSPlayerController.h"
|
||||
#include "Player/GSPlayerState.h"
|
||||
|
||||
// UE includes
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "GameFramework/GameMode.h"
|
||||
|
||||
#include "GSGameModeBase.generated.h"
|
||||
|
||||
/**
|
||||
* Game mode for a team deathmatch/free for all game mode
|
||||
*/
|
||||
UCLASS(minimalapi)
|
||||
class AGSGameModeBase : public AGameModeBase
|
||||
class AGSGameModeBase : public AGameMode
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AGSGameModeBase();
|
||||
|
||||
/**
|
||||
* Sets up default timer
|
||||
*/
|
||||
virtual void PreInitializeComponents() override;
|
||||
|
||||
/**
|
||||
* Initialize the game. This is called before actors' PreInitializeComponents.
|
||||
*
|
||||
*/
|
||||
virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override;
|
||||
|
||||
/**
|
||||
* Initialize the game state. Sets the number of teams based on this game mode's settings.
|
||||
*/
|
||||
virtual void InitGameState() override;
|
||||
|
||||
/**
|
||||
* Accept or reject a player attempting to join the server. Fails login if you set the ErrorMessage to a non-empty string.
|
||||
*/
|
||||
virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;
|
||||
|
||||
/**
|
||||
* Picks a player team and informs them to start if the match is in progress
|
||||
*/
|
||||
virtual void PostLogin(APlayerController* NewPlayer) override;
|
||||
|
||||
/**
|
||||
* Tries to spawn the player's pawn
|
||||
*/
|
||||
virtual void RestartPlayer(AController* NewPlayer) override;
|
||||
|
||||
/**
|
||||
* Choose a random spawn
|
||||
*/
|
||||
virtual bool ShouldSpawnAtStartSpot(AController* Player) override;
|
||||
|
||||
/**
|
||||
* Checks if one player can damage another. For example, players on the same team should not be able to damage one another.
|
||||
*/
|
||||
virtual bool CanDamagePlayer(AGSPlayerController* Damager, AGSPlayerController* Victim);
|
||||
|
||||
/**
|
||||
* Decides on a team for a player based on current player distribution
|
||||
*/
|
||||
virtual void PickTeam(AGSPlayerState* PlayerState);
|
||||
|
||||
/**
|
||||
* Handles updating score and informing appropriate clients when one player kills another
|
||||
*/
|
||||
virtual void OnPlayerKilled(AGSPlayerController* Killer, AGSPlayerController* Victim);
|
||||
|
||||
/**
|
||||
* Decides which team wins and then informs players the match has ended and if they won or not
|
||||
*/
|
||||
void FinishMatch();
|
||||
|
||||
/**
|
||||
* Sets up match warmup timer
|
||||
*/
|
||||
virtual void HandleMatchIsWaitingToStart() override;
|
||||
|
||||
/**
|
||||
* Sets up match round timer
|
||||
*/
|
||||
virtual void HandleMatchHasStarted() override;
|
||||
|
||||
/**
|
||||
* Notifies players
|
||||
*/
|
||||
virtual void RestartGame() override;
|
||||
|
||||
/**
|
||||
* Function that acts as the game mode "tick" and does match state transition logic
|
||||
*/
|
||||
virtual void DefaultTimer();
|
||||
|
||||
protected:
|
||||
// Maximum number of teams allowed in this gamemode
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta = (ClampMin = 0))
|
||||
int32 MaxNumTeams;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta = (ClampMin = 0))
|
||||
int32 ScoreLimit;
|
||||
|
||||
// time in between rounds, in seconds
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta = (ClampMin = 0))
|
||||
float TimeBetweenRounds;
|
||||
|
||||
// duration of a round, in seconds
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta = (ClampMin = 0))
|
||||
float RoundTime;
|
||||
|
||||
// duration of the pre-match/warmup period, in seconds
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings, meta = (ClampMin = 0))
|
||||
float WarmupTime;;
|
||||
|
||||
// whether or not there is a warmup or the round should immediately start
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category=Settings)
|
||||
bool bWarmupEnabled;
|
||||
|
||||
FTimerHandle TimerHandle_DefaultTimer;
|
||||
};
|
||||
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "GameModes/GSGameState.h"
|
||||
|
||||
// UE includes
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
void AGSGameState::SetNumTeams(int32 InNumTeams)
|
||||
{
|
||||
NumTeams = InNumTeams;
|
||||
|
||||
// Grow team score array if num teams is greater than current capacity
|
||||
if(TeamScores.Num() < NumTeams)
|
||||
{
|
||||
TeamScores.AddZeroed(NumTeams - TeamScores.Num());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int32 AGSGameState::GiveTeamPoint(int32 InTeamIdx)
|
||||
{
|
||||
if(InTeamIdx < 0 || InTeamIdx >= TeamScores.Num())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Tried to give team %d a point but the team index is out of range"), InTeamIdx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
TeamScores[InTeamIdx]++;
|
||||
OnTeamScoresUpdated.Broadcast(TeamScores);
|
||||
return TeamScores[InTeamIdx];
|
||||
}
|
||||
|
||||
|
||||
void AGSGameState::ResetTeamScores()
|
||||
{
|
||||
for(int32 TeamIdx = 0; TeamIdx < TeamScores.Num(); TeamIdx++)\
|
||||
{
|
||||
TeamScores[TeamIdx] = 0;
|
||||
}
|
||||
|
||||
OnTeamScoresUpdated.Broadcast(TeamScores);
|
||||
}
|
||||
|
||||
|
||||
int32 AGSGameState::GetWinningTeam()
|
||||
{
|
||||
int32 WinningTeamIdx = 0;
|
||||
int32 WinningTeamScore = 0;
|
||||
for(int32 TeamIdx = 0; TeamIdx < TeamScores.Num(); TeamIdx++)\
|
||||
{
|
||||
if(TeamScores[TeamIdx] > WinningTeamScore)
|
||||
{
|
||||
WinningTeamIdx = TeamIdx;
|
||||
WinningTeamScore = TeamScores[TeamIdx];
|
||||
}
|
||||
}
|
||||
|
||||
return WinningTeamIdx;
|
||||
}
|
||||
|
||||
|
||||
void AGSGameState::SetRemainingTime(float InRemainingTime)
|
||||
{
|
||||
RemainingTime = InRemainingTime;
|
||||
|
||||
OnRemainingTimeUpdated.Broadcast(RemainingTime);
|
||||
}
|
||||
|
||||
void AGSGameState::OnRep_MatchState()
|
||||
{
|
||||
Super::OnRep_MatchState();
|
||||
|
||||
OnMatchStateUpdated.Broadcast(GetMatchState());
|
||||
}
|
||||
|
||||
|
||||
void AGSGameState::OnRep_TeamScores()
|
||||
{
|
||||
OnTeamScoresUpdated.Broadcast(TeamScores);
|
||||
}
|
||||
|
||||
|
||||
void AGSGameState::OnRep_RemainingTime()
|
||||
{
|
||||
OnRemainingTimeUpdated.Broadcast(RemainingTime);
|
||||
}
|
||||
|
||||
|
||||
void AGSGameState::GetLifetimeReplicatedProps(TArray< FLifetimeProperty > & OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(AGSGameState, NumTeams);
|
||||
DOREPLIFETIME(AGSGameState, TeamScores);
|
||||
DOREPLIFETIME(AGSGameState, RemainingTime);
|
||||
}
|
73
GravityStomp/Source/GravityStompGame/GameModes/GSGameState.h
Normal file
73
GravityStomp/Source/GravityStompGame/GameModes/GSGameState.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
// UE includes
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/GameState.h"
|
||||
|
||||
#include "GSGameState.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTeamScoresUpdatedSignature, const TArray<int32>&, TeamScores);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRemainingTimeUpdatedDelegate, float, RemainingTime);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMatchStateUpdatedSignature, FName, MatchState);
|
||||
|
||||
/**
|
||||
* Game state for a team deathmatch/free for all game mode
|
||||
*/
|
||||
UCLASS()
|
||||
class GRAVITYSTOMPGAME_API AGSGameState : public AGameState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
void SetNumTeams(int32 InNumTeams);
|
||||
|
||||
FORCEINLINE int32 GetNumTeams() { return NumTeams; }
|
||||
|
||||
/**
|
||||
* Gives team of the supplied team index a point. Returns the new score for that team.
|
||||
*/
|
||||
int32 GiveTeamPoint(int32 InTeamIdx);
|
||||
|
||||
void ResetTeamScores();
|
||||
|
||||
int32 GetWinningTeam();
|
||||
|
||||
FORCEINLINE float GetRemainingTime() { return RemainingTime; }
|
||||
|
||||
void SetRemainingTime(float InRemainingTime);
|
||||
|
||||
// Begin AGameState interface
|
||||
virtual void OnRep_MatchState() override;
|
||||
// End AGameState interface
|
||||
|
||||
protected:
|
||||
// number of teams currently in the game
|
||||
UPROPERTY(Replicated, BlueprintReadOnly)
|
||||
int32 NumTeams;
|
||||
|
||||
// array of team scores, index by the team number
|
||||
UPROPERTY(ReplicatedUsing=OnRep_TeamScores, BlueprintReadOnly)
|
||||
TArray<int32> TeamScores;
|
||||
|
||||
// time remaining for the current match state (i.e. warmup, the round itself, post round)
|
||||
UPROPERTY(ReplicatedUsing=OnRep_RemainingTime, BlueprintReadOnly)
|
||||
float RemainingTime;
|
||||
|
||||
/** Triggered when this weapon is fired. */
|
||||
UPROPERTY(BlueprintAssignable, Category="Events")
|
||||
FTeamScoresUpdatedSignature OnTeamScoresUpdated;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="Events")
|
||||
FRemainingTimeUpdatedDelegate OnRemainingTimeUpdated;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="Events")
|
||||
FMatchStateUpdatedSignature OnMatchStateUpdated;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_TeamScores();
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_RemainingTime();
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -0,0 +1,68 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "Player/GSPlayerController.h"
|
||||
|
||||
void AGSPlayerController::Client_GameStarted_Implementation()
|
||||
{
|
||||
SetIgnoreMoveInput(false);
|
||||
|
||||
// enable all actions on pawn
|
||||
OnGameStarted();
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerController::Client_OnKill_Implementation(AGSPlayerState* KillerPlayerState,
|
||||
AGSPlayerState* VictimPlayerState)
|
||||
{
|
||||
OnKill(KillerPlayerState, VictimPlayerState);
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerController::Client_OnDeath_Implementation(AGSPlayerState* KillerPlayerState,
|
||||
AGSPlayerState* VictimPlayerState)
|
||||
{
|
||||
OnDeath(KillerPlayerState, VictimPlayerState);
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerController::Client_WarmupStarted_Implementation()
|
||||
{
|
||||
OnWarmupStarted();
|
||||
}
|
||||
|
||||
void AGSPlayerController::Client_GameRestarted_Implementation()
|
||||
{
|
||||
OnGameRestarted();
|
||||
}
|
||||
|
||||
void AGSPlayerController::ClientGameEnded_Implementation(AActor* EndGameFocus, bool bIsWinner)
|
||||
{
|
||||
Super::ClientGameEnded_Implementation(EndGameFocus, bIsWinner);
|
||||
|
||||
SetIgnoreMoveInput(true);
|
||||
|
||||
APawn* MyPawn = GetPawn();
|
||||
if(MyPawn) // pawn may be null if we died and then the round ended
|
||||
{
|
||||
// disable all actions on character
|
||||
MyPawn->TurnOff();
|
||||
}
|
||||
|
||||
OnGameEnded(bIsWinner);
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerController::UnFreeze()
|
||||
{
|
||||
Super::UnFreeze();
|
||||
|
||||
ServerRestartPlayer();
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerController::AcknowledgePossession(APawn* P)
|
||||
{
|
||||
OnAcknowledgePossession();
|
||||
|
||||
Super::AcknowledgePossession(P);
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
// UE includes
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
|
||||
#include "GSPlayerController.generated.h"
|
||||
|
||||
/**
|
||||
* Player controller for a team deathmatch/free for all game mode
|
||||
*/
|
||||
UCLASS()
|
||||
class GRAVITYSTOMPGAME_API AGSPlayerController : public APlayerController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Notify player that a game has started
|
||||
*/
|
||||
UFUNCTION(Reliable, Client)
|
||||
void Client_GameStarted();
|
||||
|
||||
/**
|
||||
* Notify player when the warmup period has started
|
||||
*/
|
||||
UFUNCTION(Reliable, Client)
|
||||
void Client_WarmupStarted();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Events")
|
||||
void OnWarmupStarted();
|
||||
|
||||
/**
|
||||
* Notify a player that a game has started
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Events")
|
||||
void OnGameStarted();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnAcknowledgePossession();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnPawnInitialized();
|
||||
|
||||
/**
|
||||
* RPC when this player kills someone
|
||||
*/
|
||||
UFUNCTION(Reliable, Client)
|
||||
void Client_OnKill(AGSPlayerState* KillerPlayerState, AGSPlayerState* VictimPlayerState);
|
||||
|
||||
/**
|
||||
* Blueprint event when the OnKill RPC is called
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Events")
|
||||
void OnKill(AGSPlayerState* KillerPlayerState, AGSPlayerState* VictimPlayerState);
|
||||
|
||||
/**
|
||||
* RPC when a death occurs so the player can update their UI
|
||||
*/
|
||||
UFUNCTION(Reliable, Client)
|
||||
void Client_OnDeath(AGSPlayerState* KillerPlayerState, AGSPlayerState* VictimPlayerState);
|
||||
|
||||
/**
|
||||
* Blueprint event when the OnDeath RPC is called
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Events")
|
||||
void OnDeath(AGSPlayerState* KillerPlayerState, AGSPlayerState* VictimPlayerState);
|
||||
|
||||
/**
|
||||
* Blueprint event when the game ended RPC is called
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="Events")
|
||||
void OnGameEnded(bool bIsWinner);
|
||||
|
||||
UFUNCTION(Reliable, Client)
|
||||
void Client_GameRestarted();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnGameRestarted();
|
||||
|
||||
// APlayerController interface
|
||||
/**
|
||||
* Does character cleanup when the game ends
|
||||
*/
|
||||
virtual void ClientGameEnded_Implementation(AActor* EndGameFocus, bool bIsWinner) override;
|
||||
|
||||
/**
|
||||
* Request respawn from server when we are allowed
|
||||
*/
|
||||
virtual void UnFreeze() override;
|
||||
|
||||
/**
|
||||
* Calls OnAcknowledgePossession so the UI can bind itself to events like health updates
|
||||
*/
|
||||
virtual void AcknowledgePossession(APawn* P) override;
|
||||
// End APlayerController interface
|
||||
};
|
@ -0,0 +1,94 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#include "Player/GSPlayerState.h"
|
||||
|
||||
// GS includes
|
||||
#include "Character/GSCharacter.h"
|
||||
|
||||
// UE includes
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
|
||||
AGSPlayerState::AGSPlayerState()
|
||||
{
|
||||
TeamNum = 0;
|
||||
NumKills = 0;
|
||||
NumDeaths = 0;
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::SetTeamNum(int32 InTeamNum)
|
||||
{
|
||||
TeamNum = InTeamNum;
|
||||
UpdateTeamColor();
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::UpdateTeamColor()
|
||||
{
|
||||
AGSPlayerState* LocalPS = GetWorld()->GetFirstPlayerController()->GetPlayerState<AGSPlayerState>();
|
||||
// if this player state belongs to us then we need to update all other characters
|
||||
// rendering of team will essentially flip (all enemies are now rendered as friendly and vice versa)
|
||||
if((GetPlayerController() && (GetNetMode() == ENetMode::NM_Client)) || ((GetPlayerController() == GetWorld()->GetFirstPlayerController()) && (GetNetMode() == ENetMode::NM_ListenServer)))
|
||||
{
|
||||
for(int32 PSIdx = 0; PSIdx < UGameplayStatics::GetNumPlayerStates(GetWorld()); ++PSIdx)
|
||||
{
|
||||
AGSPlayerState* CurrPS = Cast<AGSPlayerState>(UGameplayStatics::GetPlayerState(GetWorld(), PSIdx));
|
||||
if(CurrPS != LocalPS) // only update team colors for remote characters
|
||||
{
|
||||
AGSCharacter* CurrChar = CurrPS->GetPawn<AGSCharacter>();
|
||||
if(CurrChar && CurrPS && LocalPS)
|
||||
{
|
||||
CurrChar->ChangeTeamColor(IsPlayerFriendly(CurrPS));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // this PS represents a remote player so we just need to switch how we are rendered to the local player
|
||||
{
|
||||
AGSCharacter* Char = GetPawn<AGSCharacter>();
|
||||
if(Char && LocalPS)
|
||||
{
|
||||
Char->ChangeTeamColor(IsPlayerFriendly(LocalPS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::OnRep_TeamNum()
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("%s is on team %d"), *GetPlayerName(), TeamNum);
|
||||
|
||||
UpdateTeamColor();
|
||||
}
|
||||
|
||||
|
||||
bool AGSPlayerState::IsPlayerFriendly(const AGSPlayerState* PS) const
|
||||
{
|
||||
return TeamNum == PS->GetTeamNum();
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::Reset()
|
||||
{
|
||||
NumKills = 0;
|
||||
NumDeaths = 0;
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::ClientInitialize(AController* InController)
|
||||
{
|
||||
Super::ClientInitialize(InController);
|
||||
|
||||
UpdateTeamColor();
|
||||
}
|
||||
|
||||
|
||||
void AGSPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(AGSPlayerState, NumKills);
|
||||
DOREPLIFETIME(AGSPlayerState, NumDeaths);
|
||||
DOREPLIFETIME(AGSPlayerState, TeamNum);
|
||||
}
|
73
GravityStomp/Source/GravityStompGame/Player/GSPlayerState.h
Normal file
73
GravityStomp/Source/GravityStompGame/Player/GSPlayerState.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Gravity Stomp Copyright Kevin Poretti
|
||||
|
||||
#pragma once
|
||||
|
||||
// UE includes
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
|
||||
#include "GSPlayerState.generated.h"
|
||||
|
||||
/**
|
||||
* Player state for a team deathmatch/free for all game mode
|
||||
*/
|
||||
UCLASS()
|
||||
class GRAVITYSTOMPGAME_API AGSPlayerState : public APlayerState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AGSPlayerState();
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
FORCEINLINE int32 GetTeamNum() const { return TeamNum; }
|
||||
|
||||
void SetTeamNum(int32 InTeamNum);
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
FORCEINLINE int32 GetNumKills() const { return NumKills; }
|
||||
|
||||
FORCEINLINE void SetNumKills(int32 InNumKills) { NumKills = InNumKills; }
|
||||
|
||||
FORCEINLINE void IncrementNumKills() { NumKills++; }
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
FORCEINLINE int32 GetNumDeaths() const { return NumDeaths; }
|
||||
|
||||
FORCEINLINE void SetNumDeaths(int32 InNumDeaths) { NumDeaths = InNumDeaths; }
|
||||
|
||||
FORCEINLINE void IncrementNumDeaths() { NumDeaths++; }
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
bool IsPlayerFriendly(const AGSPlayerState* PS) const;
|
||||
|
||||
// Begin APlayerState interface
|
||||
/**
|
||||
* Clear kills and deaths
|
||||
*/
|
||||
virtual void Reset() override;
|
||||
|
||||
/**
|
||||
* Updates team color on init
|
||||
*/
|
||||
virtual void ClientInitialize(class AController* InController) override;
|
||||
// End APlayerState interface
|
||||
|
||||
protected:
|
||||
UPROPERTY(Replicated)
|
||||
int32 NumKills;
|
||||
|
||||
UPROPERTY(Replicated)
|
||||
int32 NumDeaths;
|
||||
|
||||
UPROPERTY(ReplicatedUsing=OnRep_TeamNum)
|
||||
int32 TeamNum;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_TeamNum();
|
||||
|
||||
/**
|
||||
* Changes how this player is rendered to a client
|
||||
*/
|
||||
void UpdateTeamColor();
|
||||
};
|
Loading…
Reference in New Issue
Block a user