Scorpio RPG Kit I: Overview | ebp
Post

Scorpio RPG Kit I: Overview

This series will cover the architecture and thought process behind Scorpio, a RPG kit for Unreal Engine 5.

Scorpio RPG Kit I: Overview

The content in this post is based on Unreal Engine 5.4.0

Introduction

Scorpio is a RPG kit for Unreal Engine 5. Major systems and architecture was developed by me solo, and for quest and dialogue system, it’s a custom adaptation for Narrative 3 (Scorpio is not dependent on Narrative 3 plugin, I bought the plugin and manually merged the source code into my architecture with changes). For this reason, Scorpio will not be an open-source project due to licensing, it’s a personal project that I want to create as a centralized game framework solution for my other game projects.

So what this series is all about if it’s not open-sourced? Well, during the development of Scorpio (And of course use it in game project), I have already benefited a lot from the architecture and approaches taken, It also saved a lot of time and effort for me during development, so I’d like to share the architecture and thought process behind, problems and solutions, as well as code snippets and examples, it will be great if I can help other indie developers take inspiration from it, or provide thoughts to help me improve.

Motivation

I’ve seen tons of tutorials and guides on how to create a specific gameplay system in a very basic or often hacky manner, these tutorials are great for beginners to understand the basic concept, or just rush a demo quickly, yet from my years of experience in the industry, I can barely remember any of these tutorials being used in actual production environment. (I’m talking about stuff like create a combat system in 20 mins - kind of tutorial)

What’s the biggest issue with them? Well, Architecture. So many times we seen a cool plugin in marketplace and just jam it into our project and turns out that it doesn’t work smoothly with our existing system. And even more times we pulled the whole plugin in just to use one of the hundred features in it. We effectively opened the portal to invite other modules freely invade our depot. At the beginning we thought it’s manageable, but as more and more incoherent systems, plugins, utilities, etc intrude into our project. It quickly becomes an unmaintainable mess, a lot projects didn’t even see the light of day because of this. So I want this series to cover more about the architecture, thought process behind a decision, and why behind a system, rather than just how to create it. It doesn’t necessarily mean that the architecture is anywhere near perfect, but at least I hope it can be an initiative for more discussion on this topic among fellow indie developers or beginners.

A common dilemma: Plugin A wrote it’s core in a GameInstance class, great, but oh no, we already wrote our GameInstance, now we have two approaches, we either copy paste logics in Plugin A’s GameInstance into our version, and then searching through all the blueprints and manually rerouting all the cast from Plugin A’s GameInstance to our version. Or vice versa, we merge our stuff into Plugin A’s GameInstance. Great, but oh no, there’s another cool Plugin B, and Plugin C, D, E… that all did the same thing… Arrrrgh!

Goal

The goal of this project is to create a universal RPG framework for Unreal Engine to solve the following pain points:

  • Reusability: A modular system that can be easily integrated into any project with plug-and-play support for RPG genres.
  • Scalability: A system that can be expanded upon without breaking existing functionalities
  • Centralized Solution: A system that different project can submit their game agnostic improvements back to the plugin stream, seamlessly benefiting all projects.
  • Complexity Reduction: A system that abstracts the complexity for some universal modules, including save load system, inventory, quest, dialogue, character, skill, etc.
  • Faster Iteration: A system that streamlines gameplay logics into modular logic blocks, allowing mix-and-match possibilities for new gameplay under rapid development.
  • Lower Cost: Together, lower the margin cost for indie games under certain genres, such as RPG, by providing a solid foundation to build upon.

Relationship with Unreal Engine

From functional perspective, we have Unreal Engine as the base that provide the core functionalities for creating a video game, but not really pushing towards any specific genre, as a general purpose engine designed for arbitrarily any game genre (Or even non games) this is very much expected.

Building upon this base, we have Scorpio, it’s more a RPG genre specific framework, although it shouldn’t stop the developer from expanding it to create other genre like racing game or FPS games (In fact, it’s also quite common that a RPG game has such mechanics, it’s just not the goal for Scorpio to provide support to, for example, CoD like gunplay, etc.)

A game project will then depend on Scorpio, and utilize its framework to build the game. Scorpio support expansion from it with little to no modification to the core code, and it’s designed to be easily integrated into any project.

Scorpio Kit Scorpio Kit Relationship Diagram

Source Control Structure

Observing the source control structure, essentially every game project will depend on Scorpio, for now it’s not a copy-paste relationship but a direct stream mapping. (for now as I don’t have too much game projects working in parallel, when the scale grows, it should absolutely having local Scorpio fork for each project)

Scorpio Kit Scorpio Kit Perforce Relationship Diagram

The benefit of this structure is that, any improvement or bug fix in Scorpio can be easily submitted back to the plugin stream, and all projects can benefit from it instantly. It also allows a centralized solution for all projects, and a single point of entry for all game agnostic improvements. In the future, Scorpio might be exposed as multiple modules so that a project can choose to only use a subset of Scorpio. This setup in p4 takes advantage of stream mapping, meaning during the development of Game Project A, if we decided to fix a bug or add a feature in Scorpio, we can just modify it right away, p4 will automatically submit different files from the same changelist to different streams. Allowing multiple projects battle-test the same framework.

It’s important to realize that the benefit will soon become a problem if there are more developers working under the system, as different project might unintentionally receive changes submitted from other projects and introduce regressions, but for me as a solo developer, this is not a concern. If this becomes a problem, we would add another layer of stream mapping to resolve it, so like each project won’t directly submit back to the actual shared Scorpio stream, nor pulling the head of the shared stream Instead, any change should go to the project specific Scorpio stream, and then integrate back to shared Scorpio stream, with their own approval and merge process. Each project can also have a chance to cherry pick features from shared Scorpio to their local Scorpio stream.

Scope

Here’s a list of features that Scorpio will cover. So far, Scorpio provide the following features (Ordered by importance):

Core

Theses a core features that are essential for other systems in Scorpio to work properly:

  • Auto Save & Load for World State
    • When player opens a door, enter a room, eat an apple, they are interacting with the game world, these data needs to be saved and loaded otherwise the game experience will be a one-off experience, Scorpio tries to take care of this as much as possible
  • Message Bus
    • When player enters a volume, often there’re quite a few systems that need to know about it, however, we either let the blueprint to handle it which introduces a lot of coupling, or, we let each system to handle it by listening to the event, either way, we couple-ed the system with an actual Volume in the world, which in the end we only care about the information of entering or exiting a volume. That’s why we can have a message bus to deliver message around, so that systems can communicate without direct reference to each other.
  • Runtime Constant Data
    • Constant data that can be accessed at runtime, always loaded for quick access, so systems can operate on them without causing runtime hitch
  • Macro based auto code generation
    • Generate boilerplate code for managers and serializable data, for faster iteration and less human error
  • Static utility functions
    • A collection of utility functions that can be used across the project, such as wrappers of math, etc.

Asset Management

These are the data assets that represent the game world and configurations:

  • Character
    • Player & NPC has their own character data, allowing expansion by underlying project
  • Character Schedule
    • NPC can have their own schedule to follow
  • Interaction Settings
    • Define whether and how player can interact with an object
  • Area
    • Indicate when player enter or exit an area, an area can contain multiple locations, usually all locations in an area will be streamed in/out together
  • Location
    • Indicate when player enter or exit a location
  • Persistent Flag
    • A simple flag asset that will be persisted, can be used for arbitrary anything that needs to persistent, like player has seen a cutscene, etc. Note that for interactions, dialogues, quests they will have their own persistence system, this is used for flexible data that’s more game specific.
  • Reward & Reward Group
    • A reward can contain items, or attributes, a reward group can contain multiple rewards for reusability
  • Vendor
    • Any NPC can be a vendor, which contains a list of items that can be sold to player
  • Quest
    • A FSM based Quest system
  • Dialogue
    • A FSM based Dialogue system

Gameplay

These are the gameplay systems that are built upon the core systems, they often operate the data assets above:

  • Custom Time
    • A custom time data, represents a time of YYYY-MM-DD, as well as different time zone (Morning, Noon, Afternoon, etc), this is indeed a bit project specific, but other projects that doesn’t need it can simply wrap it up and convert it to another form of time data structure
    • With Time, Scorpio also has the concept of Age, subtracting two time data will give the age, which can be used for stuff like “How long has the player been talked to this NPC”, etc
    • E.g A game that want to simulate HH-MM-SS, can simply wrap it up and then mapping different hour to the time zone, and it should work as well
    • However, the current framework doesn’t support a year that’s not 12 month, and a month that’s not matching with real world rule (The current implementation knows Jan. is 31 days, Apr. is 30 days, and handles Feb. correctly).
  • Interaction System
    • Involves whether a player can interact with an object, allowing complex pre-requisition for an interaction (Player must hold a key and talked to an NPC and went to beach at night 3 days ago, etc), and post action after an interaction (A chest can only be opened once per day, etc) They will all be persistent and automatically handled by save load system
    • Involves different interaction states, for example: a chest is not interactable at all (No UI feedback), a chest is locked showing a notification of requirement, a chest has been interacted today, etc
  • Camera Manager
    • A camera manager that can act as a central hub to retrieve camera instance, useful to blend player control camera and event camera
  • Skill Manager
    • A skill tree that allows the player to unlock skills, they will also work with GAS via FGameplayTag for unlocking and applying effects
  • Inventory Manager
    • A inventory system that allows player to collect items, for each items, they can have one or more of the following traits:
      • Used
        • A gameplay effect will be applied when used
      • Gifted
        • A gameplay effect can be applied when gifted to an NPC
      • Sold
      • Dropped
      • Equipped
        • A persistent gameplay effect will be applied when equipped, until unequipped
  • Char Stats Manager
    • All characters in the world will have a proxy actor represent their stats, so that the actual character actor don’t have to be exist in the world yet we can still operate on their states, these proxies will be managed by the manager
  • Game Flow Manager
    • Game flow manager monitors the game state, like whether the player is at the Frontend, in an event, playing mini-game, talking with someone, or just roaming around the world, it also take in charges of the actual game time, and can be used to move time forward, etc. It’s also possible that we store 2 sets of time, one is the actual game time, and the other is a time for event, so we can achieve stuff like “Throw back/memory events”
  • Map Manager
    • Take in charge of map streaming, layering, etc
  • Narrative Manager
    • Tracks and maintains quests and dialogues, and the state of them. Any request to start or complete a quest/dialogue will be calling API from this manager
  • Notification Manager
    • Any request to send a notification will be calling API from this manager, then these request will be queued to HUD, and the HUD can play animations, and decide the visual of them (Stacking messages and show them one by one, etc)
  • Persistent Manager
    • Interaction history, etc, any state that related to an actual instance in the world can be registered to this manager, it will automatically handle the save load for them
    • Any other project specific data that needs to be persistent can be defined and registered to this manager, it will automatically handle the save load for them

Modular Gameplay

These are the modular gameplay systems that can be mixed and matched to create new gameplay without re-inventing the wheel:

  • Gameplay Event
    • For stuff like “Request A UI”, “Start A Quest”, etc, they are a one off event that can be triggered by any system
  • Gameplay Condition
    • For stuff like “Is Player In Area”, “Is Player Has Item”, etc, they are a condition that can be checked by any system
  • Gameplay Task
    • For stuff like “Reaching A Location”, “Collecting An Item”, etc, they are a task that will be persistent until being completed or failed by Quest system

World Building Tool

These are utility tools that can quickly populate the world with content:

  • Spline Actor
    • For quickly populate spline based actors, like sideroad curbs, river, etc
  • Procedural Floor Actor
    • For quickly populate a grid of actors, it could also be road, but mainly for places like a city square, etc

UI & Feedback

These are the on HUD UI and feedback systems, including messages, notifications and the protocol to work with other systems:

  • Notification Widget
    • Notification widget will be used to handle notifications from Notification Manager, it’s more a demo widget that can be easily extended by game project.
  • Modular UI & Stack
    • A modular UI system that wraps basic UI elements from rich text, button, window, to more complex widget, in a more practical example, any system can call an API to request a Confirm UI (A window with text, and buttons like Yes and No, with corresponding callback system), and ensure consistent visual and behavior across the game.

Audio System

Provided audio manager as a central hub to duck down and manage audios from different buses:

  • Sound Manager
    • A Sound Manager that can act as a central hub to request audio instances, with their corresponding bus, and will be mixed and ducked down by the manager

Render

  • Utility Shaders for UI
  • Utility Shaders for Pose Process

Limitation

And here’re a few limitations:

Networking

Support to network is not planned at this moment due to workload, but the architecture didn’t prevent it from being added in the future. (We will need to implement a server side network hub to serialize and transmit net events around, and client side network hub to handle each of them. It needs to be manually implemented because the major drive is UObject in this framework, which doesn’t natively support replication, the underlying project would also need to implement network ready logics).

Animations

The Scorpio will not provide any animation assets or animation blueprints, it’s very project specific and it’s hard to create a universal solution for it. Plus Unreal Engine already has a very powerful animation system built-in.

Final Visual

The Scorpio will only provide utility shaders for UI and pose process, it won’t provide any production ready visual assets or materials, same reason above, it’s very project specific and it’s hard to create a universal solution for it. Plus Unreal Engine already has a very powerful rendering system built-in.

Input and Control

Same reason as above, it’s very project specific and it’s hard to create a universal solution for it. Plus Unreal Engine already has a very powerful input and control system built-in.

Let the journey begin

Starting from the next post, we will dive into the more detailed implementation of Scorpio. Stay tuned!

This post is licensed under CC BY 4.0 by the author.