Behind the Scenes: Optimizing Phoenix Point's Geoscape

As many of you already know, Phoenix Point is coming to Steam with a definitive Year One Edition (wishlist here!) as well as consoles (more on timing at a later date!) and we cannot wait for you to experience it there. One of the studio’s main goals for the past months was to continually optimize the game, fix some of our current tech issues and continue to improve the game with every update. 

Hi, I’m Ned - gameplay/UI programmer here at Snapshot and I’m back with another batch of interesting optimization issues we found during our optimization sprints that I’m eager to share with you!

If you missed my last optimization post - check it out! It was written back in May when I discussed some of the issues that we had during the development of the Backer releases.

With that said, let’s continue… to more recent times!

Our goal during the optimization sprints was “simple”: Play the game while the Unity Profiler is running and investigate every spike or hiccup, looking for signs that something is taking more resources or time than necessary to be shown on the screen. While these issues weren’t showstoppers, they could ruin your experience. Also, on weaker PCs, those issues had more impact compared to beefier ones.

As a part of the Geoscape team, let me show you some of the interesting issues that we were surprised to find… and fix right after!

While I was playing the game, on several occasions one event was taking a big chunk of the frame to be resolved:

Going down the profiler list of functions revealed the culprit!

Going down the profiler list of functions revealed the culprit!

Let’s dig deeper, what is EventSystem.Update() and EventSystem.RaycastAll()?

As with most of the things in game development, some of the major systems in the game must be updated every frame so the game could react and be responsive to user input or other action that is happening on the screen. EventSystem.Update() updates every frame and is responsible for updating Unity’s Event System that handles input processing, dispatching events like mouse-in, mouse-out, and raycasting for most of the objects in the scene.

So, with that said, what does EventSystem.RaycastAll() do? Every time when you click with your mouse, Unity’s Event system “tests” or raycasts, trying to determine where on the screen you have clicked. Most of the UI objects in Unity like Text and Image objects have a flag that marks it as a raycastable target. So, the more raycastable targets you have on the screen, the more work for Unity.

With that explanation, the problem is clear. Somewhere around the geoscape on different screens, we had way more raycastable targets. Here's a general Unity UI optimization tip: reduce your raycastable targets and leave only the targets that need interacting. 

Armed with a tool that one of our colleagues wrote, which would highlight all the available raycastable UI targets on the screen, I went on a search to find as much as I could.

Let me show you some of my findings. 

The diplomacy screen as we know it:

There’s more to this than meets the eye!

There’s more to this than meets the eye!

All the available raycastable targets from the tool:

Unity thinks that everything on the screen is interactable!

Unity thinks that everything on the screen is interactable!

Way too many targets. When Unity tries to determine where you’ve clicked, it will test everywhere! After disabling the unnecessary targets, here are the results:

Only the necessary objects will be tested now.

Only the necessary objects will be tested now.

The geoscape encounters screen:

“The expedition has begun preparations for inspecting the screen!”

“The expedition has begun preparations for inspecting the screen!”

pic_6.png

On most of the geoscape screens, we have a special raycast target that prevents some inputs; for example, it stops you from rotating the globe when the fullscreen UI’s are open. That’s the big red rectangle that is over the whole screen. But even if we exclude the input blocker, you can notice that some of the texts are raycastable as is the other graphic that is not interactable.

After disabling the unnecessary raycastables, only the buttons and the input blocker will receive clicks:

“The expedition has finished its operation!”

Also, it turned out that the PX base tooltips on the geoscape would always receive your clicks. Even if you can’t interact with them.

 
“I’ve tried to click facilities way too many times! ”

“I’ve tried to click facilities way too many times! ”

pic_8b.png
 

And there are a lot more unnecessary raycastable targets that were all over the place. But why does this happen? When you create UI objects, Unity marks it as always as a Raycastable target and it needs to be managed very carefully. The moral of the story - manage your raycasts!

To the next issue!

The team noticed that on different occasions the profiler would show a spike when the player zooms-in or zooms-out while on the main geoscape screen. What was the cause of this?

UpdateForFov() - Update for Field of View

UpdateForFov() - Update for Field of View

This time the problematic call (function) had nothing to do with Unity. As we investigated deeper, it turns out that this function is responsible for the site info icons (trade resources, available recruits) that are showing when you zoom-in and are hiding out when you are far away from the globe.

 
Especially on weaker PCs the hiccup can be quite noticeable.

Especially on weaker PCs the hiccup can be quite noticeable.

 

Why UpdateForFov() was causing problems?

  • It turned out that the basic validations for that call would be called every frame even if you are not on the main geospace screen.

  • As it has some validations to not enable and disable visuals every frame, every time the validation conditions were met - UpdateForFov() would go through all geoscape sites (around 439) on the globe and try to enable/disable their visuals. 

What did we do? Simple, we added more validations!

  • Now UpdateForFov() would be called when the player is only on the main geoscape screen.

  • The sites would enable their information visuals only if they are functioning, visible, and inspected by the player.

As the issue is common and its solutions are not so groundbreaking, it can happen very often and have a big impact on the performance. So it’s always good to:

  • Execute specific logic that impacts a lot of objects only when it’s needed and specific conditions are met. It’s not possible to follow that rule every time, but at least try to limit those executions as much as possible.

  • Update the states/visuals of the objects to be done in batches or at small portions at a time. Some examples:

    • Splitting initialization/updating between different game frames.

    • Show/update only what’s visible to the player.

    • Show only a small batch of data at a time (scrolling virtualization).

As we constantly implement new features, we need to be diligent in correcting any issues that may arise, which include:

  • We had to add more canvases to the UI where it was necessary to.

  • We had to clear and reduce some of the hierarchies of the UI elements that we were using.

  • We found some heavy operations that were happening often - we cached a lot of those and their results so we can reuse them.

  • We had to split several initializations and calculations in different frames so we can avoid spikes and hiccups.

And my work isn’t done yet! As we head into our Year One Edition release, we have some other game improvements alongside features and new content to come to ensure you have a great time when you experience the game on your platform of choice!

 

Until next time!

Ned_signature.png