During my internship, I worked on the PC port for Horizon Forbidden West: The Complete Edition. Horizon Forbidden West is a game by the studio Guerilla Games, which is also under Sony Interactive Entertainment. The game was released two years ago on the PS5. However, this does not mean that there is no creativity involved in the porting process. Different platforms bring new features, challenges, and interactions. For instance, entire menus sometimes need to be adjusted or newly created. Also, think about the interaction of a mouse versus a joystick. Within this project, I mainly focused on the following tasks:
Achievement Support
My first task was implementing achievements for Steam and Epic Games. The game also has so-called trophies on the PlayStation. My goal for this task was to capture the trophies sent to the PS5 backend and forward them to the API of the store used by the user. It was a perfect starting task because, in essence, it is not very complicated but is if you are not yet familiar with the codebase and need to get used to your new internship. The most important thing I learned here is the use of preprocessor defines. Preprocessor defines are a kind of special code rules. These rules ensure that certain code is not used in different game builds. This way, I could ensure that certain code was only used for Steam or Epic Games.
Photomode
After implementing the achievements, I continued supporting the photomode. For the photomode, I started by setting up a design document. This document included the photomode controls of the PlayStation version of Horizon Forbidden West and the PC versions of Horizon Zero Dawn and Spider-Man. Based on this, I thought about the controls I wanted to implement for Horizon Forbidden West on PC. In this document, I described what needed to be done to support PC interaction and estimated how long I thought it would take. I then received feedback on this document from my colleagues and adjusted the document based on that feedback. The further implementation then went according to plan, and I completed it within the estimated time.
Boardgame (“Machine Strike”)
Afterward, it was time for a more challenging task: adding controls to the boardgame. The boardgame initially had no support for mouse and keyboard, so the plan was to implement this in two steps. I also created a design document for this.
Step 1 was adding keyboard support. Since a PlayStation controller only has buttons and joysticks, this was quite easy to translate to a keyboard. Most could be remapped one-to-one.
Step 2, on the other hand, was much more complicated. A PlayStation does not use a mouse, so it is difficult to ensure you can click on something, which is very natural on a PC. To make this work, it was necessary to add a raycast. A raycast is an imaginary line in the 3D world that can hit objects. By using such a line from the camera, I thought I could click on things. However, I also discovered that because you normally can’t click on the board game, there were no so-called colliders. This meant that the raycast could not hit the objects and therefore did not click.
To make this work, I had to add the colliders in the game engine. I eventually managed this with the help of a tech artist. However, it was still not perfect; if you still wanted to use the keyboard but your desk wobbled a bit, the game would pick it up as if you wanted to use your mouse. Therefore, a possible deadzone also had to be taken into account. After this, it worked as you would expect on a PC.
PSN Account Linking
The game also has the functionality to link your Steam account to your PlayStation Network Account for an extra reward. This task consisted of two major parts: the UI and the backend. Personally, I enjoyed the backend part the most and learned a lot from it. For the implementation, I had to use a library and communicate back and forth with it. This works through callbacks. A callback is when you provide an instruction in your code for what should happen after something is completed. For example, if you want to do something over the internet, you don’t know how fast it will go because you sometimes have to wait for the internet connection. What you do then is provide a function that is executed when the internet request is completed. Giving such a function can be quite complicated; if you want such a function to be executed on a specific object, you also have to provide a reference to it. This can be done, for example, with a void pointer (void*). A void pointer is a variable that points to a piece of memory without specifying what the memory is. Using this taught me a lot about asynchronous programming and memory management.
Furthermore, the UI part was a big challenge. It needs to look good but also function well. It is important to communicate clearly to the user and meet certain expectations. I learned to consider this by, for example, not showing something that is not possible or providing visible feedback when something is loading or has changed.
Leaderboard Support
Finally, I worked on the code for the leaderboards. There are certain challenges, and if you complete them faster, you get a higher score. I did this for both Steam and Epic Games.
To achieve this, I again made extensive use of callback functions. This was necessary to capture the results from the store backend. To efficiently pass all this data to different classes, I also used typedefines, templates, and lambdas. A combination of these three principles allowed me to make the code abstract enough to be easily scalable for potential future projects with leaderboards.
- Typedefine: define a new type of variable from other code.
- Template: delay the definition of variables until the moment you create them.
- Lambda: a function that you can use and pass around as a variable.
I also learned more about memory management by letting the calls run on their own piece of memory, so multiple calls can run simultaneously.