Project Elementals

Is a tactical turn based strategy game where the goal is to claim a territory from an opposing player using a powerful elemental and its minions. The game is developed in the Unreal Engine.

Content

This where the game starts. You will be given three options at start, host, join or quit. If you choose to host a game you be designated as the first player and will have first pick from the three elementals. Once you have chosen an elemental the background shifts to represent you choice. Then you can launch a match. Spawning you into a level of your choice (under development). You will then have to wait for a second player to choose join and be given the option of picking their elemental among the remaining owns. Once satisfied it will launch istelf into the game and the match will begin with the first player.

Playfield

Tile Manager manages the tiles that compose the play field. A tile has three states it can be in, player one, player two or neutral. The tiles take on the element of its owner. The plan is to let the players choose which element they will control. Right now there are only three elements, Crystal, forest and water.

Once the elements are decided the play field changes into the corresponding elements and the game will start. In the editor you can place tiles which have different colors to make it easy to see which tile will belong to what. The plan is to give the neutral faction a random number of hives, sentries, traps and barriersl to allow a buffer zone between the players at the start of the game.

HUD

The hud consists of a player area and a score area at this stage. The player hud showcases the player controlled elementals health (green), action points(orange), shield(middle sphere) and abilities.

The score area is under development and just shows the three factions fighting. Player one, Player Two and the neutral. The score is how many tiles that faction is in control over. It will be going through a desgin face, showing the turn order as well.

Player Elemental

Each player will get to control a powerful elemental influenced by your chosen element. Currently only the crystal one is available. So right now there are three versions of it with different glow. TThe class used is the ElementalCharacter. The specific variables for it are categories under Elemental.

UCLASS(Blueprintable, BlueprintType)
class ELEMETALS_API AElementalCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AElementalCharacter();

ElementalCharacter

The elemental character controlls the camera attached to it, holds the diffrent abilities it can perform. It has a statscomponent attached which moniters the health, action points and shield status of the player. The class has a couple of delegates which let’s other classes know when it changes it’s set ability, which tile it hovers over, when it is done spawning and when it’s active state changes.

The class listens for input from the right mouse button right now. The TileManager handles the broadcast of tiles getting pressed. Each player then checks which ability they set, if they are active and then if it should do anything at this time.

void AElementalCharacter::ActivateAbility_Implementation(ABaseTile* Tile)
{
	if( !bActive ) return;
	if( SelectedAbility == EActionType::None ) return;
	if( !IsAmongComboTiles(Tile) && Tile != HoveringTile ) return;
	if( StatsComponent->GetStat(EElementalStatType::Action) == 0 ) return;
void AElementalCharacter::BeginPlay()
{
	Super::BeginPlay();

	ActivateAbilityDelegate.BindUFunction(this, FName("ActivateAbility"));
	UTileManager::Get()->TileTriggered.AddUnique(ActivateAbilityDelegate);

Public part of Elemental Character

Abilities

The elemental has a set of abilities it can perform. How much you can do each turn is based on how many action points you have and which synergy bonuses you exploit. The abilities gets called from this function.

void AElementalCharacter::ActivateAbility_Implementation(ABaseTile* Tile)
{
	if( !bActive ) return;
	if( SelectedAbility == EActionType::None ) return;
	if( !IsAmongComboTiles(Tile) && Tile != HoveringTile ) return;
	if( StatsComponent->GetStat(EElementalStatType::Action) == 0 ) return;

	switch( SelectedAbility )
	{
	case EActionType::Move:
		MoveCharacter(Tile);
		break;
	case EActionType::Influence:
		Influence();
		SetAnimState(ECharacterAnimState::Influence);
		break;
	case EActionType::Attack_Normal:
		AttackTiles();
		SetAnimState(ECharacterAnimState::AttackNormal);
		break;
	case EActionType::Attack_Range:
		for( ABaseTile* TargetTile : AbilityTileCombo )
		{
			ProjectileTargets.Add(TargetTile);
		}
		SetAnimState(ECharacterAnimState::AttackRange);
		break;
	case EActionType::Attack_Area:
		AttackTiles();
		AnimState = ECharacterAnimState::AttackArea;
		break;
	case EActionType::LayTrap:
		BuildStructure(ETileStructureType::Trap, Tile);
		break;
	case EActionType::BuildBarrier:
		BuildStructure(ETileStructureType::Barrier, Tile);
		break;
	case EActionType::PlaceSentry:
		BuildStructure(ETileStructureType::Sentry, Tile);
		break;
	case EActionType::BuildHive:
		SpawnHive(Tile);
		break;
	case EActionType::Recover:
		SetAnimState(ECharacterAnimState::Influence);
		break;
	default:
		break;
	}

	PayAbilityCosts();
	ActivateSynergyBonus(Tile);
	CollectComboTiles();

	switch( SelectedAbility )
	{
	case EActionType::Influence:
	case EActionType::LayTrap:
		SetActionType(EActionType::Move);
		break;
	default:
		break;
	}
}

Move

You move the elemental to an adjacent tile, if its player controlled you will receive a synergy bonus allowing you to regain your spent action point. This will allow you to move freely around your own territory as long as you have a point to spend.

The data asset for the move action.

Move character function

void AElementalCharacter::MoveCharacter_Implementation(ABaseTile* Tile)
{
	if( HoveringTile == Tile ) return;
	if( bMoving )
	{
		UElementalCharacterManager::Get()->BroadcastMovementChange(this);
	}
	HoveringTile->RemoveOccupant(this);
	HoveringTile = Tile;
	HoveringTile->AddOccupant(this);
	bMoving = true;

	CollectComboTiles();

	OnTileChange.Broadcast(HoveringTile->GetInfluenceOwner() == 
        StatsComponent->GetInfluenceOwner());
}

Update Movement

void AElementalCharacter::UpdateMovement_Implementation(float DeltaTime)
{
	if( bMoving )
	{
		const FVector CurrentLocation = GetActorLocation();
		MoveTargetLocation = HoveringTile->GetActorLocation();
		MoveTargetLocation.Z = CurrentLocation.Z;
		MoveDirection = (MoveTargetLocation - CurrentLocation);
		MoveDirection.Normalize();

		const float Modifier = MoveSpeed * (GetWorld()->GetDeltaSeconds() * CustomTimeDilation);
		const FVector MoveLocation = CurrentLocation + MoveDirection * Modifier;

		SetActorLocation(MoveLocation);

		if( FVector::Distance(MoveTargetLocation, MoveLocation) < MoveRange )
		{
			bMoving = false;
			UElementalCharacterManager::Get()->BroadcastMovementChange(this);
		}
	}
}

Influence

You can influence tiles you hover over if you don’t currently control them.  You spend one action point to take control of the tile and gain a shield buff, giving you an extra health point. You can only have on shield active at a time.

Influence function

void AElementalCharacter::Influence_Implementation()
{
	if( HoveringTile->GetInfluenceOwner() == StatsComponent->GetInfluenceOwner() ) return;

	HoveringTile->Influence(StatsComponent->GetInfluenceOwner());
	const TArray<EInlfuenceComboType>& ComboTypes = UTileManager::Get()->GetInfluencCombos(HoveringTile, StatsComponent->GetInfluenceOwner());
	if( ComboTypes.Num() )
	{
		for( const EInlfuenceComboType InfluencComboType : ComboTypes )
		{
			switch( InfluencComboType )
			{
			case EInlfuenceComboType::Line:
			{
				StatsComponent->IncreaseCurrentMaxActionPoints();
				break;
			}
			case EInlfuenceComboType::Triangle:
			{
				StatsComponent->ModifyStat(EElementalStatType::Hive, 1);
				break;
			}
			case EInlfuenceComboType::Hexagon:
			{
				//Not Implemented yet
				break;
			}
			default:
				break;
			}
		}
	}
}

Attack

There are three attacks at the moment.

The normal will attack an adjacent tile and hit the enemy structures or AI there and deal low damage. This attack can destroy hostile barriers to clear the way onto hostile tiles.

The ranged attack will attack a couple of tiles a few tiles away from the hovered tile. This will deal low damage, and cost both action points and health points to perform. If shield is active it will take the shield first before a health point.

The area attack will attack a couple of tiles around the hovered tile with moderate damage and costs both action and health points. If shield is active it will take the shield first before a health point. The area attack visuals is under development. The goal is to have particle effects bursting out from the ground at the attack tiles, thus dealing moderate.

Attack tiles function

void AElementalCharacter::AttackTiles()
{
	UElementalAbilityAsset*const* AbilityAsset = Abilities.Find(SelectedAbility);
	if( AbilityAsset == nullptr ) return;

	const UElementalAbilityAsset* Action = *AbilityAsset;
	int32 Damage = Action->Action.Damage;

	for( ABaseTile* TargetTile : AbilityTileCombo )
	{
		UTileManager::Get()->BroadcastTileAttacked(TargetTile, Damage, StatsComponent->GetInfluenceOwner(), HoveringTile);
	}
}

Launch projectiles

void AElementalCharacter::LaunchProjectile(float DeltaTime)
{
	if( ProjectileTargets.Num() == 0 ) return;
	if( ProjectileClass.Get() == nullptr ) return;
 
	LaunchCountdown -= DeltaTime;
	if( LaunchCountdown < 0.0f )
	{
		FTransform Location = GetActorTransform();
		Location.AddToTranslation(ProjectileOffset);

		AActor* TargetTile = ProjectileTargets[ProjectileTargets.Num() - 1];

		AProjectile* Projectile = GetWorld()->SpawnActor<AProjectile>(ProjectileClass, Location);
		Projectile->SetTarget(TargetTile, StatsComponent->GetInfluenceOwner());

		ProjectileTargets.RemoveAt(ProjectileTargets.Num() - 1);
		LaunchCountdown = LaunchInterval;
	}
}

Projectiles

In the game the player and certain AI can launch projectiles and deal damage from a distance.

CLASS(Blueprintable, BlueprintType)
class ELEMETALS_API AProjectile : public AActor
{
	GENERATED_BODY()
public:
	AProjectile();

	virtual void BeginPlay() override;

	virtual void BeginDestroy() override;

	virtual void Tick(float DeltaTime) override;

	UFUNCTION(BlueprintCallable, Category = "Elemental | Projectiles")
	void SetTarget(AActor* InitialTarget, ETileInfluenceOwner Attacker);

private:
	UFUNCTION()
	void OnCollision(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	UFUNCTION(NetMultiCast, Reliable, WithValidation)
	void Explode();
	virtual void Explode_Implementation();
	virtual bool Explode_Validate();

Build

There are four build actions. The tiles in the game can support one structure and up to six barriers. The number of barriers is equal to the number of neighbours a tile has.

Build structure function

void AElementalCharacter::BuildStructure_Implementation(ETileStructureType Structure, ABaseTile* Tile)
{
	const TSubclassOf<ATileStructure>* TileStructurePointer = Structures.Find(Structure);
	if( TileStructurePointer )
	{
		FTransform Transform = FTransform();
		FVector Up = GetActorRotation().Quaternion().GetAxisZ();
		switch( Structure )
		{
		case ETileStructureType::Sentry:
		{
			Transform = Tile->GetTransform();
			ATileStructure* SpawnedStructure = GetWorld()->SpawnActor<ATileStructure>(*TileStructurePointer, Transform);
			if( SpawnedStructure )
			{
				Tile->AddStructure(SpawnedStructure);
				SpawnedStructure->Setup(Tile);
			}
		}
		break;
		case ETileStructureType::Barrier:
		{
			Transform = HoveringTile->GetTransform();
			ETileNeighbour Direction = HoveringTile->GetNeighbourDirection(Tile);
			ETileNeighbour NeighbourDirection = Tile->GetNeighbourDirection(HoveringTile);

			if( !HoveringTile->HasBarrier(Direction) )
			{
				Transform.SetLocation(HoveringTile->GetBarrierLocation(Direction));
				Transform.SetRotation(HoveringTile->GetBarrierRotation(Direction, Up));

				ATileStructure* SpawnedStructure = GetWorld()->SpawnActor<ATileStructure>(*TileStructurePointer, Transform);
				if( SpawnedStructure )
				{
					HoveringTile->AddBarrier(Direction, SpawnedStructure);
					SpawnedStructure->Setup(HoveringTile);
				}
			}
		}
		break;
		case ETileStructureType::Trap:
		{
			Transform = Tile->GetTransform();
			ATileStructure* SpawnedStructure = GetWorld()->SpawnActor<ATileStructure>(*TileStructurePointer, Transform);
			if( SpawnedStructure )
			{
				Tile->AddStructure(SpawnedStructure);
				SpawnedStructure->Setup(Tile);
			}
		}
		break;
		default:
			break;
		}


	}
}

Trap

Doesn’t cost anything to place, but it needs to be a controlled tile to place it. It will place a structure on the hovering tile which will attack enemies when they enter the tile for moderate damage. It will have a cooldown so you can only place one each turn.

The specific variables for the trap structure
The Animation blueprint for the trap structure.

Trap class

UCLASS()
class ELEMETALS_API ATrapStructure : public ATileStructure
{
	GENERATED_BODY()
public:
	ATrapStructure();

protected:

	virtual void BeginPlay() override;

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

public:
	virtual void Tick(float DeltaTime) override;

	virtual void Setup(ABaseTile* StructureOwner) override;

	virtual void OnDeath_Implementation() override;

	virtual void OnSpawn_Implementation() override;

	UFUNCTION(BlueprintCallable, Category = "Elemental | Trap")
	bool IsActive()const;

	UFUNCTION(BlueprintCallable, Category = "Elemental | Trap")
	void AfterActivating();

	UFUNCTION()
	void OnMovementChange(AElementalCharacter* Character);

private:
	UPROPERTY(EditDefaultsOnly, Category = "Elemental | Trap")
	int32 Damage;

	UPROPERTY(Replicated, VisibleAnywhere, Category = "Elemental | Trap")
	bool bActive;

};

Barrier

The barrier action will raises a barrier in a given direction from the hovering tile if available. The barrier costs an action point and occupies the direction slot for barriers for that tile. The barrier will protect friendly structures, elemental and AI that is occupying the tile. It will also block movement from hostile elementals.

The specific variables for the barrier structure

Barrier class

UCLASS(Blueprintable, BlueprintType)
class ELEMETALS_API ABarrierStructure : public ATileStructure
{
	GENERATED_BODY()
public:
	ABarrierStructure();

protected:

	virtual void BeginPlay() override;

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

public:
	virtual void Tick(float DeltaTime) override;

	virtual void Setup(ABaseTile* StructureOwner) override;

	virtual void OnDeath_Implementation() override;

	virtual void OnSpawn_Implementation() override;

	virtual void OnAttacked_Implementation(ABaseTile* Tile, int32 Damage, ETileInfluenceOwner Attacker, ABaseTile* AttackOrigin) override;

	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	UFUNCTION(BlueprintCallable, Category = "Elemental | Stats")
	void AfterBreaking();

	UFUNCTION(BlueprintCallable, Category = "Elemental | Stats")
	bool IsProtecting() const;

	UFUNCTION()
	void ProjectileChanges(ETileInfluenceOwner Attacker, AActor* Target, bool bSpawned);

private:
	FScriptDelegate DefendDelegate;

	ETileNeighbour Direction;

	UPROPERTY(Replicated)
	bool bProtect;

};

Sentry

You can place a sentry on an adjacent tile you control that doesn’t already have a structure. The sentry is a watchtower which will monitor the surrounding tiles for enemies. When one enters it will launch a projectile and deal low damage. It has low health but can be protected by placing barriers on the tile where you want the sentry before spawning it.

Sentry asset ability

AI

There are three AI in the game at this stage. The hive, explorer and defender. Might make a AI to play against as well at some point. The hive is the only one the player can spawn, the rest is an extension of the hive AI.

Hive

It is an unlockable structure that has artificial intelligence. To spawn it you need to complete a triangle combo bonus which triggers when you take over a tile and have the north, southeast and southwest or south, northeast and northwest already under your control. Placing the hive removes the buff. The hive is a structure with three health points and two action points. It can raise barriers around itself to protect it, spawn the explorer and defender minion and recover health points. It has most of its functionality except to spawn its AI minions.

Hive class

UCLASS(Blueprintable, BlueprintType)
class ELEMETALS_API AHive : public AElementalAI
{
	GENERATED_BODY()

public:
	AHive();

	virtual void BeginPlay() override;

	virtual void Tick(float DeltaTime) override;

	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	void OnDeath_Implementation() override;

	void OnSpawn_Implementation() override;

	void OnAttacked_Implementation(ABaseTile* Tile, int32 Damage, ETileInfluenceOwner Attacker, ABaseTile* AttackOrigin) override;

	void Setup(ABaseTile* StructureOwner) override;

	void Activate() override;

	void OnExplorerDeath();

	EAIAction DetermineBestAction() override;

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	bool IsSummoning() const;

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	void SummoningDone();

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	void LaunchSpawnProjectile();

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	bool SpawnMinion();

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	bool RaiseBarrier();

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	bool RecoverHealth();

	UFUNCTION(BlueprintCallable, Category = "Elemental | AI | Hive")
	void AfterBreak();

Explorer

It is an AI that tries to take over as many tiles as possible and attacking enemy elementals.  It has one health point, two action points and can do move, normal attack and influence actions.  

Explorer specific variables

Defender

It is an AI that tries to protect the hive. It has two health points, two action points and the abilities to move, normal attack, block projectile and repair the hive at the cost of its own life. It is in an early stage, only model.

Synergy

When you do an action you can activate synergy bonuses that will give you a health point, a shield or an action point. It only happens on tiles you control.

Combo bonus

If you take over a tile and manage to create a line with two of its neighbors, you will unlock an extra action point. If you take over a tile and have a triangle around it you will unlock the hive structure. If you control all the tiles around the one you take over and it’s a full circle you will unlock the behemoth mode, which is a power boost (currently not implemented).

Powered up state

Gameflow

You take turns with Player one -> Player one AI -> Player Two -> Player Two AI -> Neutral AI.

The match will continue until one player manages to take all the territory from the other player and destorying their elemental thus leaving them with no option to respawn their elemental on their controlled tiles after death.