Devlog 62: How I added XP and Level ups in our MMO-RPG style game with UE and Java

Character XP and Levels in stats widget
Character XP and Levels in stats widget

In this post, I extend our stats system to support XP and Level ups for our classes. This is a fairly high level overview of how it was achieved, so bear that in mind – this is not a tutorial as it will have too many variables for different implementations.

Stats system: handling XP and level ups

I previously introduced a base stats system in this post.

Now I will add several new stat types which will help us manage XP and level ups. In my particular case, I want to have a very dynamic system available for classes and levels. That’s to say that I want to enable and support multi-classing.

For example, I will want the ability for a player to splash levels in fighter, mage, ranger together, rather than always stick to 1 class.

Custom server implementation

I process these stats updates on a custom Java server that can be found here:

https://github.com/yazoo321/mmo_server_with_micronaut

Most of the relevant code can be found in java/server/attribute/stats/service/PlayerLevelStatsService.java

The main parameters that are useful to be aware of is having the Stats property associated to each player.

The stats contain fields for:

  • Base Stats:
    • STR
    • DEX
    • INT
    • STA
  • Available points (to spend on base stats)
  • Level (aggregated class levels)
  • Can Level ? (specifies whether player has enough XP to level up)
  • Class Types:
    • MAGE
    • FIGHTER
    • CLERIC
    • RANGER

There is no impact of class types at this stage yet, but it will be utilized later to unlock different skills and talents.

Back-end flow

I’ll go over the back-end flow very briefly, so that the UE implementation will make a bit more sense.

Gaining XP flow

I have a Combat Service which is responsible for dealing with combat related processes. Part of this, is when mobs are killed by players.

I added a function which, upon mob death, will give XP to player which hit the mob last (I will modify this behavior in future to be balanced).

In the function which adds XP to player, I check whether we have enough XP to level up.

If we have enough XP to level, then I set CAN_LEVEL property to true and this gets sent to the client.

Level up flow and add stat flow

Both levels and stats are part of the base statistics for player, so I was able to refactor it together.

public void handleAddBaseStat(String actorId, String statType) {
    if (isClassValid(statType)) {
        handleLevelUp(actorId, statType).subscribe();
        return;
    }

    if (isBaseStatValid(statType)) {
        handleAddStatPoint(actorId, statType).subscribe();
        return;
    }

    log.error("Erroneous stat type being added: {} on actor: {}", statType, actorId);
}

I validate the stat type that the player is trying to increase (which is either the class or base stat).

Once the stat type is confirmed, I can validate the request itself. For example, if you’re leveling up, I check that the player has enough XP. If the player is trying to add a base stat, I check that the player has available points to spend.

Unreal Engine Stats component

As usual, my integrations will occur through a Connected Component Base.

This was introduced in the post covering motion integration with custom server.

I also used the same approach when displaying damage.

Receiving server message with stats

Let’s check what we do when we process this message.

Process Stats Update function

You may want to copy the image and paste into Paint or similar, to see it more clearly (if zoom is not available).

Merge Base Stats

Merge base stats implementation

The received messages from the server contain the delta. This means it only sends updates when required. This component merges this delta with the snapshot, for other components that require it.

Merge derived stats

This function is very similar, but for derived stats rather than base.

These stats are technically out of scope for the Level and XP implementation.

Stats Visuals on Actor

I added this function which will be extended to add any visual effects on character, depending on stats updates.

In this particular case, it adds level up effect visuals.

Stats visuals on actor

As you can see, I process the newly updated stats, specifically the base stats and I search for CAN_LEVEL.

If it’s present, this means that I should show the new visual for Level up particles at the player. You can make this whatever you need.

Update Dependents

This function will update our players widgets components. I only want to execute this if the actor is the player itself.

Update dependent widgets

The Update Derived Stats function will update dependents such as HP bar.

As you can see, it’s connected to the Action Bar Widget and at the moment, we just need to update the HP. Later it will include MP and multiple other effects.

Update Stats function is connected to our main Stats widget, which displays all stats, base and derived.

Update Stats function in widget

Let’s see the first implementation for Populate Base Stats.

Populate base stats function

When the player logs in, there’s a call to fetch all stats. We process and add all base stats first then add the derived stats after.

Base Stat Entry widget

For reference, the Base Stat Entry widget looks like this:

Here are the corresponding blueprints in this widget.

Event graph for base stat
Update value for base stat
Show add button for stat
Show add level button
Show add stats button

Back to updating stats function

Ok so now we checked the Populate Base Stats and its dependency, Base Stat Entry. Here’s the function again, for reference.

Update Stats function

As I am adding the stats dynamically, I also added the separator dynamically too, which splits the base and derived components. There’s other ways of doing this, I could’ve had 2 set components for example.

Add a separator widget

Now let’s look into Populate Derived Stats.

Populate Derived Stats

This function is very similar to the base stats, however its a little simpler. This is because we cannot modify the derived stats ourselves, they are READ-ONLY based on our base stats + status effects & equipment.

Populate derived stats function
Populate derived stats widget

Stats Component referenced functionalities

In some of the widgets we covered above, we used the Stats Component to process certain functions, including:

  • HasPointsAvailable
  • CanLevel
  • HandleAddBaseStat

Let’s check these out.

CanLevel

Check if the base stats contain "CAN_LEVEL" field

This simply checks if we have the CAN_LEVEL stat and its not 0.

HasPointsAvailable

Checks whether player has AVAILABLE_PTS param > 0 on their stats

In a very similar fashion, we simply check whether the player has AVAILABLE_PTS on their stats.

Handle Add Base Stat

This one is interesting and at the same time simple for me in UE side.

I simply need to make a request for my Java server to process this request. UE client needs to simply form a request to ask server to process to add the stat for the player, where the stat can also be the level up.

Create a request to send to server to add stats

To help with this, I created a new generic structure: ServerCustomPayload

Server custom payload structure definition

It contains the update type, actor ID which it refers to and custom data. In this context, it will refer to stat type that I am adding, but I specifically make this generic so that I can re-use this in future for many other calls.

When I add the stat, the server will process the request, modify the base stats + derived stats and will automatically send updates, which will update the widgets as we saw above.

That generally completes the flow! When put together, we have our stats displayed and are able to level up our classes and base stat points.

Character XP and Levels at play

1 Comment

  1. fuat

    you do really great job, I appreciate your work and waiting to make it fully playable an online server 🙂

Comments are closed