Quest Events
UE5 Quest System Version: 2.0What are Quest Events?
Quest events allow you to inject custom logic into your game at specific stages of a quest. You create these events using blueprints, then associate them with quests in your quest data table.
There are three key points within the quest system where quest events can be integrated:
Quest Event Structure
When you define a quest event each definition includes the following components:
Create a new Quest Event Blueprint
To create a new quest event, follow these steps:
- Start by making a child of the BP_QuestEvent located in the Blueprints/QuestEvents/ folder.
- In your new child blueprint, you'll need to override the OnQuestEvent event. This is the key event that gets triggered when your event is launched.
Review the variables below for an outline of what is included in this blueprint class.
Variables included in Quest Event Blueprints
When you're setting up your quest event blueprint, you'll have access to a set of predefined variables inherited through the BP_QuestEvent base class. These variables are designed to help you tailor the event's behavior and interactions within your quest system. Utilize these variables to store and manipulate data relevant to the specific event you're creating, ensuring that your quest events are both dynamic and integrated seamlessly within your game's quests.
Quest Event - Spawn Actor From Class
BP_QuestEvent_SpawnActorFromClass - Use this quest event to spawn an actor.
EventData Variables:
When using this quest event to dynamically load and spawn classes based on strings stored in a data table, it's vital to manually include the directories containing these classes in the "Additional Asset Directories to Cook" field within your project settings. This step is critical because when you package your game, the packaging system doesn't automatically detect and include classes referenced solely as strings in a data table. Without this manual inclusion, these classes won't be available in the packaged game, leading to issues where certain elements or actors don't spawn as intended.
To ensure your actor classes used by Spawn Actor From Class are included in the final build when you package your project:
- Go to your project settings.
- Find the "Packaging" section.
- Locate the "Additional Asset Directories to Cook" field.
- Add the directories that contain the classes you reference in your data table.
Quest Event - Destroy Actor with Tag
BP_QuestEvent_DestroyActorWithTag - Use this quest event to destroy an actor with a tag.
EventData Variables:
Quest Event - Play Level Sequence
BP_QuestEvent_PlayLevelSequenceWithTag - Use this quest event to play a level sequence actor in your level.
EventData Variables:
Quest Event - Give Item To Player
BP_QuestEvent_GiveItemToPlayer - Use this quest event to give an item to the player. This uses the giveQuestRewardToPlayer Connected Systems BPI to keep it simple as if you are using this you are most likely using quest rewards and have this function setup to already give items to the player.
EventData Variables:
Quest Event - Show Quest Window
BP_QuestEvent_ShowQuestWindow - Use this quest event to show a quest window to the player.
EventData Variables:
Quest Event - Simple Alert
BP_QuestEvent_ShowSimpleAlert - Use this quest event to show a simple alert at the top of the player's viewport.
EventData Variables:
Quest Event - Quest Timers
BP_QuestEvent_QuestTimer - Quest Timers allow you to set a specific time limit for players to complete a quest. When the timer is in use, players must finish the quest within the allotted time; otherwise, the quest's state will change to another state that you also specify. This change is often to mark the quest as failed or completed, depending on the nature of the quest and the timer's role within it.
By integrating timers with quest events, you can introduce time-based challenges that add urgency and excitement to your quests, encouraging players to engage actively with the content and make strategic decisions under pressure.
EventData Variables:
- start to start the quest timer. Make sure you also use Run on Reload? whenever you start a timer.
- stop to stop the quest timer. If you are using this stop action from a Objective Complete Event and you are using Run on Reload? with your start action when you start the quest timer, you need to make sure to enable Run on Reload? for this event as well.
- change to change the seconds to this value
- add to add seconds to the current time remaining
- remove to remove seconds from the current time remaining
- update to perform multiple updates at once in one call.
* - The "update" action is designed to handle updating multiple things about a timer at once, such as adding seconds and changing the state or UI message. If your only action is to add, remove, or change seconds to the current event you should use the associated action for that instead.
Create a Quest Timer for your Quest
Follow these steps to create a Quest Timer that starts via a Quest Event ...
- Navigate to the Quest State Events section for your quest in the DT_Quests Data Table.
- At this point you need to decide when you want the timer to start, this typically occurs in the "In Progress" quest state, which will start it as soon as the quest is accepted. If the "In Progress" key does not exist add it by clicking the Add Map Element next to Quest State Events.
- Expand the state section you added, for me that is "In Progress" since I want the timer to start as soon as the quest is accepted.
- Add an Array Element to the state's Events.
- Select the BP_QuestEvent_QuestTimer blueprint for the EventHandler.
Expand the EventData section, then add the following variables (from above):
- action - with a value of start
- state - with a value of failed
- seconds - with a value of 35
- ui - with a value of My Example Timer
- If you want your quest timer to restart if the game restarts, enable the Run on Reload? boolean.
- You can also have your quest timer save, so instead of restarting it continues where it left off shortly before stopping. You can achieve this by enabling Run on Reload? and also by adding the save variable to the EventData. The value for this variable is the number of seconds between saves of the data, if you leave it blank it will save every 5 seconds.
Now when your quest is accepted by the player you will see "My Example Timer" appear at the top of the Quest Tracking widget with a starting seconds of 35. When the timer reaches zero the state will be set to "Failed" for the quest.
You can also initiate your quest timer when an objective is completed, however keep in mind that even on the objective level if the timer reaches zero, the state you define will be set for the quest.
Make sure you have the Run on Reload? boolean for the quest event starting the quest timer enabled in the Data Table. When true, this boolean lets our system know we want to restart this quest event if our quest is still in this state. Remember Timmy, our Quest Timers are built on top of the Quest Event system.
You need to make sure you have this enabled on any Quest Event used to start the Quest Timer. However, it is not required on Quest Events used to perform any of the other Quest Timer actions (stop (except when you are using it as an objective complete event, then you should use run on reload), change, add, remove, update).
You need to use the save event data key if you would like the timer to save the seconds remaining.
Without this save key, and with Run on Reload? enabled, your quest timer will restart on each reload.
By default it will save every 5 seconds, you can change this interval by setting a value to the key equal to the seconds you wish to use.
You need to use both the Run on Reload? & save features to maintain this persistent state between restarts.
Stopping Quest Timers
By default quest timers will stop whenever the quest state changes to a state other than "In Progress". This is handled in AC_QuestSystem_PlayerState's internalQuestStateEventsHandler function.
You can also stop a timer from an objective complete event by using the Quest Timer handler with the Event Data key action with a value of stop.
How to stop a Quest Timer from another Blueprint
The AC_QuestSystem_PlayerState component includes the QuestTimerSop function, which can be used to force a specific quest's timer to stop. This function expects the QuestRowName and ObjectiveIndex as inputs. If your quest timer is for the entire quest and not an objective you should leave the ObjectiveIndex at -1.
Alternatively you can use the QuestTimerStopAll function, which will stop all timers. This function does not require a quest row name or objective index.
How to Add/Remove Seconds for a Quest Timer from another Blueprint
The AC_QuestSystem_PlayerState component includes a useful function named QuestTimerChangeSeconds, designed to modify the duration of an active quest timer. This function allows you to either add or subtract seconds from a timer and requires three inputs: QuestRowName, ObjectiveIndex, and Seconds. If your quest timer is for the entire quest and not an objective you should leave the ObjectiveIndex at -1.
Alternatively there is also QuestTimerChangeSecondsAll, which will change the seconds on any active timer, and does not require the quest or objective.
Under the surface this function is just dynamically creating a quest event to add seconds. When removing seconds you would just use a negative value.
For instance, in our demo world, there's a repeatable quest where destroying a cupcake adds 5 seconds to the remaining time. This adjustment is made by invoking the QuestTimerChangeSecondsAll function, demonstrating a practical application of dynamically altering quest timers based on in-game actions.
Starting Quest Timers from another Blueprint
Since quest timers are a feature unique to quests, there is no included way to externally start them, and they should only be started through the provided Quest Events points. The provided example functions mentioned above and the requirements for creating a timer should give you enough examples to create this functionality if you really need it in your game, but if you can't figure it out and need help feel free to hop in my discord and state your need and use case.
Multiplayer Notes
When designing quest events in a multiplayer game, it's crucial to ensure that the events don't inadvertently impact other players, particularly when events involve spawning actors or modifying level elements. Here are some key considerations and strategies:
Multiplayer Considerations
Remember that in a multiplayer setting, multiple players might trigger the same events simultaneously or sequentially. It's essential to design your events to handle such scenarios gracefully to avoid unwanted duplications or interactions that could affect gameplay balance or player experience.
Singleton Actors
In the case of objects that should only exist once in the world, like the button in the demo world example, you need mechanisms to check for existing instances and prevent multiple spawns. The button checks for other instances and self-destructs if others are found, ensuring that only one instance is present for all players.
Qualification Checks
For interactive elements like the button mentioned previously, it's vital to incorporate checks to ensure that only qualified players can activate or use them. This prevents unqualified players from triggering events or actions they shouldn't access.
Server and Client Logic
The onQuestEvent event runs on the server, which is suitable for most game logic in a multiplayer environment. However, if you need to update the client-side UI or perform other client-specific actions, you'll need to trigger client-executed events or functions from the server-side event.
Replication and Authority
Ensure that your quest events respect the network authority principles. Generally, the server should handle critical game logic, including the spawning and destruction of shared game elements, to maintain consistency across clients.
By keeping these points in mind and implementing appropriate checks and balances, you can ensure that your quest events enhance the multiplayer experience, maintaining both gameplay integrity and individual player progression.