Arconia - Open World, SCI-FI Rogue-Like

Arconia
In a nutshell: Geometry Wars meets Star Control 2.
[HR][/HR]
Intro Video
https://youtube.com/watch?v=Sk36Gu1m_lA

Latest Progress
I’m organizing this post so the latest 2 videos will always be at the top of this post.
https://youtube.com/watch?v=ybHq5eKJ5KY
https://youtube.com/watch?v=KPJf9z9V8Mo

Visuals
https://youtube.com/watch?v=VIFRroigdRc

[SIZE=2]Player Ship Model
[/SIZE][sketchfab]87389a3964aa4584914bfffa0faf0333[/sketchfab]

Enemy Ship
[sketchfab]2316fa1ab8aa44e09c7d0b3162be9efc[/sketchfab]

Player Character
[sketchfab]2594106253e145518314cb04c1cff372[/sketchfab]

Day 22.
Added energy mechanic, world boundaries and updated the scanning mechanic. Also made some more HUD refinements.

very Nice. too many games now days don’t seem to focus on if the game work at all, just that it looks pretty.

I agree! I’ve been on the other end too though, it’s easy and distracting and fun to get caught up in how things look, especially in UE4 where everything looks amazing! Once my game is fully playable I’ll allow myself to indulge that end of things. For now I’d rather have a 10-minute experience for people to play than a handful of nice screenshots.

Day 23

Minor fixes and code restructuring. Added cheat codes to speed up iteration times. Added passive items and created the first passive item type (Power System I). Refactored stats screen making it easier to programatically generate stat UI for each item type. Tuned weapons to make missiles a bit more fun. Also added some more variety to asteroid spawns.

Day 24

Today I mostly worked on enemy AI. I added a reaction and alertness system. Enemies are now spawned with a randomized chassis size, randomized loadout and with a random accuracy value. The accuracy ranges from [0-1] where 0 shoots where you are and 1 shoots at where you are going to be.

Looking through ways of predicting accuracy, I implemented this solution. My implementation looked like this, I use prediction in the case of firing a projectile weapon and just straight up location when firing a beam weapon. ShotAccuracy below ranges from [0-1]:


const FVector targetVelocity = target->PhysicsComponent->GetPhysicsLinearVelocity();
const FVector targetLocation = target->GetActorLocation();
const FVector gunLocation = ShipPawn->GetActorLocation();
const float projectileSpeed = 4500.f;

FVector finalProjectileAim = FVector::ZeroVector;
FVector finalLaserAim = FVector::ZeroVector;
    
if (ShipPawn->bAIHasProjectileWeapon)
{
    const float a = FMath::Square(targetVelocity.X) + FMath::Square(targetVelocity.Y) - FMath::Square(projectileSpeed);
    const float b = 2.f * (targetVelocity.X * (targetLocation.X - gunLocation.X) + targetVelocity.Y * (targetLocation.Y - gunLocation.Y));
    const float c = FMath::Square(targetLocation.X - gunLocation.X) + FMath::Square(targetLocation.Y - gunLocation.Y);
    const float discriminant = FMath::Square(b) - 4.f * a * c;
    float finalT = 0;

    if (discriminant < 0)
    {
        // We can't hit the target in time :(, just fire for show
        finalProjectileAim = (targetLocation - gunLocation).GetSafeNormal();
    }
    else
    {
        const float t1 = (-b + FMath::Sqrt(discriminant)) / (2.f * a);
        const float t2 = (-b - FMath::Sqrt(discriminant)) / (2.f * a);

        // Choose the smallest positive t value
        if (t1 > 0 && t2 > 0)
        {
            finalT = FMath::Min(t1, t2);
        }
        else
        {
            finalT = FMath::Max(t1, t2);
        }

        const FVector finalTarget = ShotAccuracy * finalT * targetVelocity + targetLocation;
        finalProjectileAim = (finalTarget - gunLocation).GetSafeNormal();
    }
}
    
if (ShipPawn->bAIHasLaserWeapon)
{
    finalLaserAim = (targetLocation - gunLocation).GetSafeNormal();
}

Day 25

I finally added the ability to jettison modules from your ship. I try to make this un-annoying by setting a bJettisoned state and then once the ship gets an EndOverlap for the module, I set a timer that switches the flag to allow for re-picking up the module.

I changed the way that asteroids drop loot while being attacked. They now incrementally drop loot as they get hit


int32 NumOfLootToDrop = (1-(health/maxhealth)) * NumLootRemaining;

.

I swapped out the generic drops with the actual elemental types defined in our game design document.

I also refined delivery quests so that they can be 1:X meaning 1 pickup with multiple dropoffs. This required some reworking of the mini map to display multiple quest destination markers as well as multiple active quest selector markers. It wouldn’t be too hard to extend this to allow for X:1 missions, I may do that in the future. I’m also thinking that I want to add the ability to tie in a quest item with these. So maybe you pick up X items that you have to drop off to each location or vice-versa.

I continued my work on the mission interface in UMG. I now print out the currently selected mission, time remaining for the mission and the status of the mission goals. This alone has really started to make things feel like a game! My task list for the mission system has 2 remaining items that I’ll clean up on monday before getting to the really exciting stuff!

&stc=1

I love that “this is actually starting to feel like a game” feeling. The game looks pretty cool, keep us updated!

I like that the game is focused heavily on its core mechanics, seems it will be very fun to play :slight_smile:

Thanks! Yes, I love the feeling of crossing certain landmark thresholds in the development process. There will be many more :slight_smile:

Thanks for checking it out, I think it will be fun too :wink:

I’m really excited to share the first set of models for this project!
[TABLE=“width: 500”]

[sketchfab]87389a3964aa4584914bfffa0faf0333[/sketchfab]
[sketchfab]2316fa1ab8aa44e09c7d0b3162be9efc[/sketchfab]

Player Ship
Enemy Ship

I spent the last 2 days basically refactoring my gamepad navigable hud system. One thing about being proficient in C++ in UE4 is knowing your base classes well. This was a case where I didn’t know the HUD classes that well. I made the mistake of implementing my gamepad navigable hud using AHUD as the base class rather than UUserWidget. The end result was that I wanted to be able to spawn any number of these huds (Inventory, Mission Selection, Main Menu) and I couldn’t.

Now that this is all fixed, I can literally move on with my life. HUD is always a bit of a pain in game engines. The basic solution for my gamepad hud is a solution I’ve used in the past for previous engines. You store a map of game widgets that map to navigation structures.


TMap<UWidget*, UBaseNavigableMenuStructure*> NavigationMap;

Where UBaseNavigableMenuStructure is a class with the 4 members:


// Item above you
UPROPERTY()
UWidget* Up;

// Item below you
UPROPERTY()
UWidget* Down;

// Item to the left of you
UPROPERTY()
UWidget* Left;

// Item to the right of you
UPROPERTY()
UWidget* Right;


Then when constructing the HUD, stuff all of your selectable widgets into the map, setting the widget that is right/left/above/below each widget. Then you track the selected item, change brush materials to indicate selected/unselected items and poof you’re done :o It is a very low tech solution that has worked well in the past. If you need to store other pointers in the map for a specific menu you just override UBaseNavigableMenuStructure and make a type for the specific menu.

There could be a problem with a game called… Aracania. Maybe double check it :3

We’ll be careful to distinguish ourselves going forward :wink:

Today I worked on an exciting feature that allows the player to dock with a station or other object. When docked, the player hops out of the ship and runs around to perform various sundry tasks like buying, selling, upgrading the ship and exploring.

Here’s what our little Arconian pilot will look like when he’s out of the ship:
[sketchfab]8aa0c376e281470eb85d02502ea8f92e[/sketchfab]

At the core, this is nothing more than unpossessing one pawn and possessing the other. The player controller manages all of this and is where the actual functions to dock/undock exist. Here is a look at one of my station blueprints. You can see that each station has 3 unique things:

  1. The parking spot for the player ship
  2. The starting location of the player pawn
  3. A camera

&stc=1

When the player requests to dock, I do the following:

  1. Store the ship and camera location for undocking
  2. Unpossess the ship pawn
  3. Move the ship pawn to his parking spot
  4. Spawn/possess the player pawn
  5. Blend camera to the station blueprint’s camera

Undocking is basically the reverse

  1. UnPossess the player pawn and destroy
  2. Move the ship to the entry location stored in #1 of docking
  3. Possess ship
  4. Blend camera to ship pawn

One issue I ran into while blending cameras using SetViewTargetWithBlend is that it couldn’t handle blending to a Camera Component :frowning: My work around was to spawn a helper camera actor in my player controller in BeginPlay():


TransitionCamera = GetWorld()->SpawnActor<ACameraActor>(ACameraActor::StaticClass());

Then, again in my player controller, I made an override for SetViewTargetWithBlend that detects if we’re blending to a space station actor and if so sets up the transition camera based on the camera contained in the blueprint.


void AShipPlayerController::SetViewTargetWithBlend(class AActor* NewViewTarget, float BlendTime, enum EViewTargetBlendFunction BlendFunc, float BlendExp, bool bLockOutgoing)
{
    ASpaceStationActor* Space = Cast<ASpaceStationActor>(NewViewTarget);
    if (Space)
    {
        // It's a space station! Setup parameters on the transition camera to match the space station camera and then transition to it
        TransitionCamera->GetCameraComponent()->FieldOfView = Space->Camera->FieldOfView;
        TransitionCamera->SetActorLocation(Space->Camera->GetComponentLocation());
        TransitionCamera->SetActorRotation(Space->Camera->GetComponentRotation());


        Super::SetViewTargetWithBlend(TransitionCamera, BlendTime, BlendFunc, BlendExp, bLockOutgoing);
    }
    else
    {
        Super::SetViewTargetWithBlend(NewViewTarget, BlendTime, BlendFunc, BlendExp, bLockOutgoing);
    }
}


Today I added the ability to travel between different systems. The tricky part was actually that I wanted to fully preserve my pawn as it traveled between levels.

The approach that worked out for me in the end was:

  1. In Game Mode, override HandleMatchHasStarted and prevent it from calling RestartPlayer if the pawn already exists.
  2. In player controller, override GetSeamlessTravelActorList and add pawn to actor list
  3. In player controller, override NotifyWorldLoaded and fix migrated actor tick properties because some actors get their tick re-enabled as part of the rename function.

I also moved some things from my GameState to my GameInstance since the latter doesn’t get destroyed during level transition.

When I first load into a system, I let the world generator do its thing. The next step is when exiting a system to save out the state of the actors in the system and load them the next time the system is entered. It was a long day, most of it was spent single-stepping through the Game Mode and Player Controller classes.

Well, I never played in Geometry wars or Star control, but as a roguelike player I find this project pretty interesting!
And yeah - with your focus on core gameplay it’s really cool to see how fast development goes :slight_smile:

Thanks for the comment :slight_smile: Maybe this can be a gateway game to introduce roguelike fans to these other genres.

I’ve implemented a system that allows preserving the state of all actors in a system when traveling between systems. This uses the FObjectAndNameAsStringProxyArchive and a system described by the Fortnite devs where you do a double pass. The first pass, I loop through all my arrays, recreating each actor with the correct class, name and transform. The second pass, I serialize all of the saved actors on top of the spawned actors.

This system works quite well, I mean my jaw literally dropped when I saw all of my pointers hook up correctly, it’s really fantastic! The only place where I had to do some fidgeting is saving/restoring timers (FTimerHandle can’t be a UPROPERTY). Restoring parent/child relations for actors that are attached to other actors. And finally, restructuring some actors so that BeginPlay knows whether the actor was restored via a save so that it can skip the 1-time initialization.

Overall I think this was one of the most difficult tasks for me to complete conceptually. It took me a long time to wrap my brain around the whole 2 pass system. Now it seems as clear as a bell, but for a day there it was not clicking at all. This save system could have easily turned into a huge beast if I had given up on the proxy archive solution. I’m quite excited to have this working and out of the way!

This was an asset integration week! I wrote a procedural planet generator with some cool options for spawning orbitals around the planets. I got the main player asset delivered and was able to integrate its movement system as well as the mounting system that allows you to attach your weapons and utilities to hard mount points on the ship. For the module mounting system, I finally got around to using CSV based data to define things and am excited to extend this into the other systems of the game.

I also messed with a new enemy type and got an automated build system setup. It was quite a shock to have my game fall apart completely when packaging :0 Initialization of actors happens in a completely different order in editor vs out of editor. So some refactoring to make all that work out… that’s why it’s always a good idea to get your builds going ASAP to work those minor issues out.

Overall a very busy week, it’s honestly hard to remember what all was done!