Personal Project, Unreal, C++, Only Developer |
|
---|---|
Descent Into Atlantis uses a variation of the press turn system popularised by Shin Megami Tensei Nocturne. (Hitting an enemy's weakness will give you more turns while hitting an enemy's strength will make you lose your turns.) (The combat moves in a much more fervent pace and was a great choice to create engaging combat.)
// This is called after we have already selected a skill
void UPressTurnManager::ActivateSkill(UCombatEntity* aAttacker, int aCursorPosition, USkillBase* aSkill)
{
TArray enemyInCombat = TArray(combatManager->GetEnemysInCombat());
TArray playersInCombat = TArray(combatManager->GetPlayersInCombat());
TArray entitySkillsAreUsedOn;
TArray turnReactions;
FSkillsData skillsData = aSkill->skillData;
if(aAttacker->characterType == ECharactertype::Ally)
{
entitySkillsAreUsedOn = skillsData.skillUsage == ESkillUsage::Opponents ? enemyInCombat : playersInCombat;
}
else if(aAttacker->characterType == ECharactertype::Enemy)
{
entitySkillsAreUsedOn = skillsData.skillUsage == ESkillUsage::Opponents ? playersInCombat : enemyInCombat;
}
if(skillsData.skillRange == ESkillRange::Single)
{
turnReactions.Add(aSkill->UseSkill(aAttacker,entitySkillsAreUsedOn[aCursorPosition]));
}
else if (skillsData.skillRange == ESkillRange::Multi)
{
for(int i = 0 ; i < entitySkillsAreUsedOn.Num();i++)
{
turnReactions.Add(aSkill->UseSkill(aAttacker,entitySkillsAreUsedOn[i]));
}
}
ProcessTurn(turnReactions);
}
-----------------------------------------------------------------------------------------
void UPressTurnManager::ProcessTurn(TArray aAllTurnReactions)
{
//Null Skills Consume all press turns completely
for (PressTurnReactions reaction : aAllTurnReactions)
{
if (reaction == PressTurnReactions::Null)
{
ConsumeTurn(activePressTurns.Num());
return;
}
}
//If Dodged Consume two press turns if empowered only take the empowered and the next token
for (PressTurnReactions reaction : aAllTurnReactions)
{
if (reaction == PressTurnReactions::Dodge ||
reaction == PressTurnReactions::Strong )
{
ConsumeTurn(2);
return;
}
}
//If weakness is hit correctly then the turn that was used will be empowered
for (PressTurnReactions reaction : aAllTurnReactions)
{
if (reaction == PressTurnReactions::Weak ||
reaction == PressTurnReactions::Pass)
{
EmpowerTurn();
return;
}
}
//Normal action Consume 1 empowered or normal pressturn
ConsumeTurn(1);
//Passing will turn a whole icon into a empowered one but will consume an empowered one if it is
}
---------------------------------------------------------------------------------------------
void UPressTurnManager::ConsumeTurn(int aAmountOfTurnsConsumed)
{
int TurnsRemaining = (activePressTurns.Num() - 1) - aAmountOfTurnsConsumed;
for (int i = activePressTurns.Num() - 1; i > TurnsRemaining; i--)
{
if(i >= 0)
{
inActivePressTurns.Add(activePressTurns[i]);
activePressTurns.RemoveAt(i);
}
}
turnCounter->SetTurnOrder(activePressTurns.Num(),characterType);
combatManager->TurnFinished();
}
-------------------------------------------------------------------------------------------
void UPressTurnManager::EmpowerTurn()
{
int ActivePositionTurn = activePressTurns.Num() - 1;
if (activePressTurns[ActivePositionTurn]->isEmpowered == false)
{
activePressTurns[ActivePositionTurn]->isEmpowered = true;
turnCounter->SetEmpowerTurnIcon(ActivePositionTurn);
combatManager->TurnFinished();
}
else
{
ConsumeTurn(1);
}
}
skillFactory = NewObject();
skillFactory->InitializeDatabase(dataTablesSkills);
if(dataTables.Contains(EDataTableTypes::Enemys) &&
dataTables.Contains(EDataTableTypes::EnemyGroups))
{
if(dataTables[EDataTableTypes::Enemys] != nullptr
&& dataTables[EDataTableTypes::EnemyGroups] != nullptr)
{
enemyFactory = NewObject();
enemyFactory->InitializeDatabase(dataTables[EDataTableTypes::Enemys],
dataTables[EDataTableTypes::EnemyGroups]);
}
}
combatManager = NewObject();
combatManager->Initialize(this,world);
if(dataTables.Contains(EDataTableTypes::Party) &&
!dataTablesClasses.IsEmpty())
{
if(dataTables[EDataTableTypes::Party] != nullptr)
{
partyManager = NewObject();
partyManager->InitializeDataTable(skillFactory,dataTables[EDataTableTypes::Party], dataTablesClasses,combatManager);
}
}
if(dataTables.Contains(EDataTableTypes::Tutorial))
{
if(dataTables[EDataTableTypes::Tutorial] != nullptr)
{
tutorialManager = NewObject();
tutorialManager->InitializeDatabase(dataTables[EDataTableTypes::Tutorial]);
}
}
if(dataTables.Contains(EDataTableTypes::Floor)
&& dataTables.Contains(EDataTableTypes::FloorEvent))
{
if(dataTables[EDataTableTypes::Floor] != nullptr
&&dataTables[EDataTableTypes::FloorEvent] != nullptr)
{
floorFactory = NewObject();
floorFactory->InitializeDatabase(dataTables[EDataTableTypes::Floor],dataTables[EDataTableTypes::FloorEvent]);
floorEventManager = NewObject();
floorEventManager->Initialize( this,floorFactory,combatManager);
}
}
if(dataTables.Contains(EDataTableTypes::Dialogue))
{
if(dataTables[EDataTableTypes::Dialogue] != nullptr)
{
dialogueFactory = NewObject();
dialogueFactory->InitializeDatabase(dataTables[EDataTableTypes::Dialogue]);
}
}
-----------------------------------------------------------------------------------------
void UFloorFactory::InitializeDatabase(UDataTable* aFloorDatabase,UDataTable* aFloorEnemyDatabase)
{
UDataTable* datatable = aFloorDatabase;
for(int i = 0 ; i < datatable->GetRowMap().Num(); i ++)
{
floorData.Add(*datatable->FindRow(FName(FString::FromInt(i)),FString("Searching for Floors"),true));
UFloorBase* floorBase = NewObject();
floorBase->floorData = floorData[i];
floorDictionary.Add(floorData[i].floorIdentifier,floorBase);
}
UDataTable* datatable2 = aFloorEnemyDatabase;
for(int i = 0 ; i < datatable2->GetRowMap().Num(); i ++)
{
floorEnemyData.Add(*datatable2->FindRow(FName(FString::FromInt(i)),FString("Searching for Floors Events"),true));
}
for(int i = 0 ; i < floorEnemyData.Num(); i++)
{
floorDictionary[floorEnemyData[i].floorIdentifier]->floorEventData.Add(floorEnemyData[i].positionInGrid,floorEnemyData[i]);
}
}
-----------------------------------------------------------------------------------------
void UPartyManager::InitializeDataTable (USkillFactory* aSkillFactory,UDataTable* aDataTable, TMap aClassDataTable,UCombatManager* aCombatManager)
{
skillFactory = aSkillFactory;
UDataTable* datatable = aDataTable;
for(int i = 0 ; i < datatable->GetRowMap().Num(); i ++)
{
playerEntityData.Add(*datatable->FindRow(FName(FString::FromInt(i)),FString("Searching for seres"),true));
}
for(int i = 0;i < playerEntityData.Num();i++)
{
//if we dont get back the correct information from the datatable
EDataTableClasses classTable = playerEntityData[i].DataTableClass;
UPlayerCombatEntity* PlayerCombatEntity = NewObject();
PlayerCombatEntity->SetPlayerEntity(playerEntityData[i]);
PlayerCombatEntity->SetTacticsEntity(aSkillFactory);
PlayerCombatEntity->SetPlayerClass(aClassDataTable[classTable]);
PlayerCombatEntity->SetTacticsEvents(aCombatManager);
playerCombatEntity.Add(PlayerCombatEntity);
playerCombatEntityInfo.Add(classTable,PlayerCombatEntity);
}
}
The game is designed to be fully data driven. The enemies, dialogue, floor, and tutorials are all data driven.
to see how the data is setup click here
Descent Into Atlantis is constructed using information set in a DataTable. (Each node of the floor tells us which cardinal direction is currently free. This information is used when generating the level to decide where walls should be placed)
void AFloorManager::Initialize(ADesentIntoAtlantisGameModeBase* aGameModeBase)
{
cardinalPositions.Add(ECardinalNodeDirections::Up, FVector2D(-1,0));
cardinalPositions.Add(ECardinalNodeDirections::Down, FVector2D(1,0));
cardinalPositions.Add(ECardinalNodeDirections::Left, FVector2D(0,-1));
cardinalPositions.Add(ECardinalNodeDirections::Right, FVector2D(0,1));
gameModeBase = aGameModeBase;
}
-----------------------------------------------------------------------------------------
//Floor base is created and set by Data in a DataTable
void AFloorManager::SpawnFloor(UFloorBase* aFloorBase)
{
if(aFloorBase == nullptr)
{
return;
}
aFloorBase->Initialize();
AFloorNode* Object = nullptr;
int AmountOfFloorNodes = aFloorBase->GridDimensionX * aFloorBase->GridDimensionY;
floorNodes.Init(Object,AmountOfFloorNodes);
currentFloor = aFloorBase;
CreateGrid(aFloorBase);
SetFloorNodeNeightbors(floorNodes);
}
-----------------------------------------------------------------------------------------
void AFloorManager::CreateGrid(UFloorBase* aFloor)
{
UFloorBase* tempfloor = aFloor;
for (int x = 0; x < tempfloor->GridDimensionX; x++)
{
for (int y = 0; y < tempfloor->GridDimensionY; y++)
{
int LevelIndex = aFloor->GetIndex(x, y);
//If there is no node then continue
if (tempfloor->floorData.floorBlueprint[LevelIndex] == (short)ECardinalNodeDirections::Empty)
{
continue;
}
SpawnFloorNode(x , y,LevelIndex );
floorNodes[LevelIndex]->SetWalkableDirections(aFloor->floorData.floorBlueprint[LevelIndex]);
FVector2D positionInGrid = FVector2D(x,y);
if(aFloor->floorEventData.Contains(positionInGrid))
{
floorNodes[LevelIndex]->hasFloorEvent = true;
floorNodes[LevelIndex]->floorEventHasBeenTriggeredEvent = gameModeBase->floorEventManager->EventHasBeenTriggered;
SpawnFloorEnemyPawn(positionInGrid);
}
}
}
}
---------------------------------------------------------------------------------------------
void AFloorManager::CreateFloor(EFloorIdentifier aFloorIdentifier)
{
floorDictionary = gameModeBase->floorFactory->floorDictionary;
if(floorDictionary[aFloorIdentifier] != nullptr)
{
SpawnFloor(floorDictionary[aFloorIdentifier]);
SetPlayerPosition(floorDictionary[aFloorIdentifier]->floorData.startPosition);
}
}
-------------------------------------------------------------------------------------------
void AFloorManager::SpawnFloorNode(int aRow, int aColumn, int aIndex)
{
//Setting new Positon
FVector PositionOffset;
PositionOffset.Set(200 * aRow, 200 * aColumn, 0);
FVector ActorFinalSpawnPoint = GetActorLocation() + PositionOffset ;
FVector2D PositionInGrid;
PositionInGrid.Set(aRow,aColumn);
//Rotation
FRotator rotator = GetActorRotation();
//Spawn
AFloorNode* floorNode;
floorNode = Cast(GetWorld()->SpawnActor(floorNodeReference, ActorFinalSpawnPoint, rotator));
floorNode->SetPositionInGrid(PositionInGrid);
floorNodes[aIndex] = floorNode;
}
-------------------------------------------------------------------------------------------
void AFloorManager::SetFloorNodeNeightbors(TArray aFloorNodes)
{
if(aFloorNodes.Num() == 0)
{
return;
}
for(int i = 0 ; i < aFloorNodes.Num();i++)
{
AFloorNode* mainNode = aFloorNodes[i];
// In situations where the node was removed at creation
if(mainNode != nullptr)
{
TArray walkableDirections = mainNode->walkableDirections;
for (ECardinalNodeDirections direction : walkableDirections)
{
AFloorNode* neightborNode = GetNodeInDirection(mainNode->positionInGrid, direction);
//In situations where the neightbor in that direction doesnt exist
if(neightborNode != nullptr)
{
mainNode->nodeNeighbors.Add(direction,neightborNode);
}
}
}
}
}