SPHERES - Devlog

SUMMARY

  1. Introduction
    1.1. First of all
    1.2. What I’m going to talk about
    1.3. DISCLAIMER
  2. The beginning
    2.1. Mindset & Constraints
    2.2. Technical Beginning (story based titles but it starts there)
  3. Major Issues
    3.1. Talking about computation
    3.2. Top down camera
    3.2.1. Distance Culling
    3.2.2. Actor Replication
    3.2.3. Player Instigated Camera
    3.2.4. Stuttering
  4. Additional features
    4.1. Pools
    4.2. Verse crash detector
    4.3. Custom switch_device persistency
    4.4. Skins
    4.5. Name picker
    4.6. Custom scoreboard
    4.7. Emoji Grade system
    4.8. Generic playtime tracking device
    4.9. Audio
    4.10. UEFN

Introduction

First of all


So I started this map back in August and I’ve been improving it when I thought I could.

I’m doing this (unique and final) devlog because I just released the map today and I’m trying not to care too much, I though that maybe talking about it would be a good way to not think about the stats it makes for a moment

Anyways, just so you know what I’m talking about, here’s what it looks like :

Basically a cheap version of agar.io (I say cheap because its lacking some important gameplay mechanics imo)

What I’m going to talk about


Sit down comfortably because I’m going to take you to the everyday life of someone who’s trying to achieve something that is not meant to be achieved (and he did it! and he’s very humble about it, don’t you dare think otherwise!)

But let’s get actually real there, I’m not going to tell you any story, it’s just a devlog. So… here’s what we’re going to do, I will try to talk about every unique and relevant aspects of the game, briefly explain how it works, explain why I got there and talk about some issues I have encountered along the way.

TIP: Look out for blockquotes like this one

DISCLAIMER

We’re on v27.00 as of the day of the release, and I started this project in v26.00, some constraints I talk about might not apply anymore.
I’ve hit many issues related to UE5, and I’m really not a pro at this, so maybe my issues could have been solved differently sometimes.
I might have done some poor game design choices too.
More generally, I don’t believe I know the best answers everytime, so I may have took bad choices along the way, but who knows, I was alone in this so it’s hard to tell :cry:

The beginning

How do one proceed to make an Agar.io game in Fortnite?!

SHORT ANSWER: He doesn’t know, he tries

I just liked the idea and went confident with it, and I actually hit 2 technical breakpoints where I wanted to give up, we’ll talk about those later

Ok so to resume the difficulties here, we need to :

  • Make a smooth Top Down camera game
  • Have player controls that can work in top down view
  • Have player controls that feel responsive
  • Find a smooth movement method for the spheres (we know that MoveTo still has issues)
  • Have an performant code (since all the game content is almost always shown in the same area)

And overall, build something that’s compatible with what UEFN / Verse / Fortnite can give us. I almost tackled all those things almost perfectly so stay tuned! ( I need the watchtime sorry :smiley: )

Ok let’s get at it!


So this is the first video I’ve recorded while making the game

So we can agree that it’s really bad, but what we can see here is my first approach at drawing the spheres, the circle around our player is actually a Static Mesh spawned through a Niagara System, and so are the particles floating around it. So this is not the first version, I was initially going for a world space positioned Niagara Particle System (with just the particles) so I could move them in Verse while seeing the stuttering (see video)

Calling MoveTo() to move a prop instead on every frame (in this case because we want the prop to be aligned with the player’s position as much as possible) would have make the movements a bit stuttery

I then added the Sphere Static Mesh to it because I saw that the particles would not behave the same way on every graphics settings

Niagara particle count would get lower on lower graphics settings
Also, the particle system would stop being rendered permanently if I looked away for some duration

That being observed, I could not use a Particle System anymore

Let’s use a good looking Static Mesh then

Do you guys know about those ?
image

They are called metaballs and they kinda look like the balls in agar.io, it’s great because when they get close to each other they look like they would eat the other ball. So I went ahead and made these :
image

Here’s the material for it

I took the base material from a tutorial and added threshold so it would not deform if the scale of the ball is too low, dynamic emissive amount so that it shines more as it grows and a DistanceFieldsRenderingSwitch node so that it won’t get executed on platforms that doesn’t support DistanceFields.

I’ve made this material straight away without checking that it was working inside Fortnite and I’m still using this material even though it doesn’t work in game at all :face_with_hand_over_mouth:

I’ve red that World Position Offset node should be working in game, so I don’t know what’s wrong with my material, tbh I didn’t care enough to debug it

I guess we’ll use MoveTo calls now…


Ok, so why is it bad to use this ? For sure, MoveTo is great when you call it once every 0.1/0.2s but if you want to align its position with the player’s position, you’ll want to call MoveTo as often as you can (on every frame, 30 times per second). Doing so will result in stuttery movements

So I’ve decided that… We’re not going to call it on every frame, at the cost of having the sphere lagging behind the actual player’s position, we’re going for smooth movements instead!

This means that the player’s position cannot be used for any computation anymore (since its not perfectly aligned with the sphere). We should forbid the use of Mutator Zone / Damage Volume / and other devices that use the player’s position

You can watch this video to understand better (the green trail is the Movement Modulator VFX being attached to player’s feet, the arrow represents the sphere position, sorry didn’t have a better video to show this :cold_sweat:)

Major issues

Talking about computation


Ok so this can be a lot for you already if you’re not familiar with this, but we’re just getting started, I’m not giving you any break, so stay focused! :smiling_imp:
(Eventhough this is the moment I took my first break on the project)

You remember the video I showed you earlier about my first version of the sphere using particle system ? Well if you watch closely you’ll see log prints that say Absorb when I get close to the red spheres (which are supposed to be bombs). The detection was done through a basic loop, I don’t have the script anymore but it must have looked like this :

# Absorbables being an array with all the absorbable_base present in the map
for(Absorbable : Absorbables, SphereRadius := GetSphereRadiusFromLevel(Absorbable.GetLevel())): 
    for(
        OtherAbsorbable : Absorbables,
        OtherAbsorbable <> Absorbable,
        Distance(Absorbable.GetPosition[], OtherAbsorbable.GetPosition[]) <= SphereRadius
    ):
        Absorbable.Absorb(OtherAbsorbable)

Starting some contextual explanation

Just so you know, here’s a quick overview of the absorbable files, everything that can absorb/be absorbed is a child class of absorbable_base. Basically, any child class must override the following functions :

CanAbsorb<override>()<transacts><decides>:void # Can the object be absorbed right now?
CanBeAbsorbed<override>()<transacts><decides>:void # Can the object absorb right now?
GetAttachableItem<override>()<transacts><decides>:attachable_item # Parent class for something that can be attached to any positional
OnAbsorbed<override>(?Absorber: ?absorbable_base)<suspends>:void # What happens when absorbed?
GetLevelGivenOnDeath<override>(:absorbable_base)<transacts>:int # How much levels do we give/take upon being absorbed

image

End of contextual explanation

Back to the computation method!
Why is it so bad ?
Why did I hit a breakpoint there ?

Well, I’m not really a math aficionado, but I know that the algorithm complexity can be improved there, we’re looping inside a loop, so let’s say we have 100 spheres on the map (including players / food / bombs), we’ll need to iterate 100 times and loop again 100 times every time. Meaning we’ll do 10000 distance checks on every frame :exploding_head: . While computers can be quite fast, I’m trying to go for something smooth here, so we don’t want any server lag happening.

(looking at my game stats doing 1 CCU right now :fearful: )

Ok, so we need another way to detect when a player/sphere is close to another player/sphere.
The method should :

  • Simulate a spherical distance check
  • Be compatible with players AND food/bomb spheres
  • Ignore the caller of the method
  • Give a way to find the instigator agent of the distance check (who is running the method)

I don’t remember everything that I tried but I know that I tried those

  • Attaching a cylinder mutator zone to each player’s sphere
  • Attaching an explosive device to the player’s sphere that would damage the other spheres (which would be damageable creative props)
  • Attaching fire volumes to player’s sphere and all the spheres would be made of wood
  • Attaching water volumes
  • Attaching prop movers that would move a ring around the player’s sphere so that we can change the Z positionning of our in-range enemies
  • Doing the same with Verse MoveTo() calls
  • Using a capture area to detect enemies entering our zone
  • Using an invisible bouncer (mushroom) to change Z value of enemies
  • Using cylinder damage volumes to damage nearby enemies
  • Something with triggers?

I’ll try to tell you what went wrong for some of those

  • Mutator Zone: Requires me to use creatures/wildlife for food spheres (so we’d be limited to 90), also after testing, the AgentEntersEvent won’t fire when creatures/wildlife enter the zone
  • Explosive Device: While using a damageable prop is a good method because it doesn’t limit ourselves to 90 spheres (which is the maximum number of AIs that can be spawned on the map), the creative prop wouldn’t be damaged by the Explosive Device for some reason
  • Fire volumes, Water volumes, Triggers: Can only be squares
  • Prop Mover: Really glitchy, wouldn’t work
  • MoveTo: Players who are grounded (feet touching the floor) cannot be ungrounded so easily, so the prop would just go through them without chaning their Z position
  • Capture Area: Some problems with teams? Beacon? Particles? I don’t remember
  • Bouncer: Cannot ignore a team/class so it would trigger on owning player too

Ok so if you’re a good detective, you should know what method I used now, which is… Damage Volumes :partying_face:


On this video, I have an invisible damage volume attached to me that is set to ignore damage for me

Unlike Mutator Zones, damage volumes work with creatures and wildlife (didn’t manage to make it work with creative_prop either) since it will kill them, they can be set to use a Cylinder shape, they can be set to Ignore a team/class, and they give a way to pass the instigator of the damage (through passing its team index in the Damage field).
Yep we’ll change player’s team everytime they’re assigned a sphere from now on. Could’ve use the classes too but 1. I feel like they often bug, 2. They’re limited to 16 and 3. I wanted to keep them available for switching HUD and movement variables between being in the Lobby and being In game

And we can set the Damage in code using SetDamage<public>(Damage:int):void so less human error possible, yayyy!

So now, when any fort_character (wildlife & players) receive damage we can easily retrieve the player instigator’s team by doing this :

TeamIndex  :=  Round[Result.Amount] -  400,  # Round in case there's float point errors

And since we assign a different team for each player’s damage volume, we can just retrieve the playing player that belongs to this team index and voilà!

SIDE NOTE
We’re using wildlife from now on, so we’re limited to 90 enemy spheres
Also, wildlife can move and attack us, this is why we’re using a Custom Damage to Player setting at 0.0 and a Custom Movement Multiplier of 0.01 (could also have used the mutator zone maybe here?)
Still with movement being really slow, some creatures won’t care, all the flying ones won’t care, and the boar will still charge you and bump you if you get close, so we’re going to use wolves

Top down camera


While it can look easy to just use a sequence device and move a camera on top of a player’s head, well, it’s not at all.

  • The player can sometimes be forced to look in the same direction as the camera (i.e. the ground), which will mess around the movements controls (WASD)
  • Any little stuttering in the camera movement is felt way more easily
  • Player Instigated sequences playing together on multiple players simply don’t work
  • Many UE5/Fortnite features will use the current camera to trigger different things (e.g. LODs, distance culling, distance based actor replication)
  • Player character’s direction would freeze permanently in some cases
  • We need the camera height to be dynamic for each player (so we can adjust the zoom like in the original game)

Ok so let me explain a little bit, we’re going to have to work around many things, and I wasn’t aware at the time, I’m not sure I would have tried it otherwise :joy:

I don’t know / remember how I fixed the first issue, it would just worked somehow. But I definitely hit another breakpoint with stuttering, distance culling, actor replication and camera freezing

Let’s do this chronologically, shall we ?

Distance Culling

So basically, when any actor is too far away from your camera, then the engine can say “don’t render this actor anymore”. I think I’ve hit this issue eventhough I’ve built custom LODs for my spheres, I guess it will rather not render the actor at all than render a far LOD.
Anyways, I don’t know why but I remember having to do many things when I worked around this. When now, I’m pretty sure you can just change the Desired Max Draw Distance of the Static Mesh to something really high and it will work. But maybe, they didn’t add the Propagate Draw Distance on Additional Component setting at the moment?
image

image

distance_culling1
distance_culling2

Actor Replication (networking)

When working on a multiplayer game, things need to be replicated over to the clients, things like a player position for example. So if Player A moves, Player B should see Player A moving right ?

Well it’s the same with everything that moves and is replicated so that everybody has the same information

Player Instigated sequence devices are not replicated for example (or are they just replicated to one client?)

The server has the authority and he’s the only one who knows the real position of elements, he’s the one who sends them to you so you can replicate those positions inside your version of the game.

Anyways, in order to not download all the map data all the times (imagine your computer trying to compute every data in a Battle Royale game, it would be resource intensive), there are checks being done that tells your game if an actor should be replicated or not, based on its distance to camera for example

And I think I’ve hit this limit where the sphere would get too far from the camera and the server would say “Ok you’re too far from the actor so I won’t give you its position anymore”

NOTE: This is my interpretation of the bug, since I didn’t get any answer, but you can check by yourself

(LINK TO TOPIC: Prop MoveTo() not replicating after a certain distance from camera)

I thought I was hitting a real breakpoint here, I cannot do agar.io without being able to unzoom the camera so I stopped the project after posting about my problem to see if anyone would answer it.
After two weeks maybe, I released a tweet about the map (eventhough I wasn’t working on it anymore), the tweet did fine and it made a team also post about their agar.io version that they were working on. Gave me enough motivation to come back on the project and find an alternate solution to the problem.

I reduced the default sphere size and player zoom, retrieved the maximum camera height I can use without having replication issues, and interpolated between those two new height values, which I think are actually fine now.

Player Instigated Camera (sequence devices, freezes and dynamic height)

Ok, so we cannot use a single sequence device here since we need to assign a different camera to every player. So we’re going to use a camera pool, I’ve made my own parametric item_pool class that allows me to retrieve and lock an item from the pool untill I release it back into the pool.

So we now have 20 cameras bound in 20 sequences, assigned in 20 sequence devices (eventhough we have 16 max players, I don’t want any trouble)

You can find another implementation of this method on this snippet made by @Twin01

On paper, our camera system should work right away like this right ? We assign a sequence device to a player when he exits the Lobby zone and we Stop() and release the sequence device when the player dies (or leaves).

Well, it was not that simple, yet again, a New Challenger ™ appears.

So Player A gets assigned the Sequence Device A and Player B gets assigned the sequence device B. Well, what happened to me is that when Player A would die, I would call cinematic_sequence_device.Stop(:agent) on him and Player B would have its direction locked towards -X axis permanently.
Had to do random stuff to workaround this one, I tried :

  • Using Team Instigated sequence instead (each team has one player so it should work the same)
  • Change the Keep State / Restore State values of the sequence device
  • Change the Keep State / Restore State values of the sequence tracks
  • Play with the Loop Playback parameter
  • Same with the Actor Always Relevant parameter (and changing sequence device location)
  • Tried to have an very long duration sequence
  • Added different tracks / Removed different tracks
  • Played with random sequence parameters (e.g. evaluation settings)
  • Calling cinematic_sequence_device.Stop() without an agent
  • Calling cinematic_sequence_device.GoToEndAndStop(:agent)
  • Calling cinematic_sequence_device.GoToEndAndStop() without an agent
  • Calling cinematic_sequence_device.Pause(:agent)
  • Calling cinematic_sequence_device.Pause() without an agent
  • Calling nothing at all

I might have missed some other things I tried, but one of those method worked anyways, which IIRC was… Calling nothing at all.
Strange right ? Well I assumed that was because the player died so its camera would automatically get reset for some reason, I was okay with this. :+1:


But you can guess what happens next, as this is a very random solution, either I didn’t look close enough or it just broke in the next update. I went ahead and tried all those things again.

So here we go again! This time, the method that works is cinematic_sequence_device.Pause(:agent)
BUT! In order to resume the sequence we have to be sure that the same player keeps the same camera all along, if we don’t do this and call cinematic_sequence_device.Play(:agent) on another player, it will silently act as a cinematic_sequence_device.Stop(:agent) for the player’s whose sequence has been paused. (triggering the freezing again)
So I made it so that instead of being assigned a camera upon leaving the Lobby area, they’ll be assigned a camera as soon as they connect and they only release it when they disconnect


But you can guess what happens next, as this is a very random solution, it actually broke with the PlayRate update (v26.20 / v26.30?)

So here we go again! :slightly_smiling_face:

Ok so I found a new method this time, since they just added the PlayRate, PlaybackFrame, PlaybackTime functions, why not using them ? After experimenting a bit again, I found that splitting the sequence in 2 parts and using cinematic_sequence_device.SetPlaybackFrame(:int) to decide whether we should play the Camera Cuts or we should play nothing (assigning the default player camera back again) was a good enough solution. (I mean it worked)

When the player dies, we tell the sequence to be playing the frame 0 (no camera) and then we Pause() the sequence

But you can guess what happens next, version 27.00 releases, and guess what ? Well nothing, it worked like a charm, absolutely nothing broke for me this update :scream: :pray:

Stuttering

Ok so this is more of a QoL feature, but I thought it was still important, because you see, using MoveTo to move the camera make it either feel laggy (w/ high MoveTo duration) or stuttery (with low MoveTo duration).
Well either way, it wasn’t smooth! (And it’s easy to notice when it’s your actual main fullscreen camera!)

This is where I hit another breakpoint in my project, everything was basically done, but I wasn’t satisfied with the camera movement.

So I waited…

Many weeks after playtesting what I thought being the v1 of the project (we playtested September 22th), I came accross this video about control rigs from @insaneUEFN, someone from a very special Discord sent it to me

I’m really grateful for those UE5 users who’ll share tricks like this, I’d never have discovered this on my own, but I still had to figure it out because the video isn’t really a tutorial, and I understand, you gotta deserve it right?

Well, I finally managed to implement it on my map, you can see it in action

Beautiful, isn’t it?

Maybe a little explanation is needed, the orange cubes inside the pink cubes are the pink cubes’ targets, it means that the pink cubes will interpolate smoothly towards their position. (In the video I set all the orange cube locations to all be the same, so the pink ones follow, you follow?) :crazy_face:

Ok perfect, well so now we just need to attach our orange cubes to our player’s sphere and our cameras to our pink cubes (with a Z offset) and we have… … …Smooth Camera Movement! :partying_face: :part

Here’s a comparison of the two methods :

BEFORE (MoveTo)

AFTER (MoveTo + ControlRigs)

We’re done talking about cameras now, thanks for reading untill there, I hope you’ve red useful things!

BONUS
I originally made a Dark Mode switch that would allow each player to change the color of the grid, using a sequence animating a Material Parameter Collection parameter. But it wouldn’t work anymore, I believe it freezes the camera again. Will definitely find a way to add it back if the map does well.

Sphere Movements


The term Sphere Movements is important, because it’s different from the Player Movements, they can be seen as 2 separate things since the sphere follows the invisible player. You’ll understand a bit better soon.

The 2 major issues we have here are :

  • Defining our movement method (reaction to inputs)
  • Making it smooth and responsive

Movement Methods

So I saw 3 possible solutions here, we’re using a Top Down camera for sure, but we still have choice, do we :

  • Use WASD keys to move the sphere and the mouse to rotate it : the default mode
  • Use WASD keys only : would require to lock the player’s orientation somehow, the problem is that when the player goes backward or strafes to the sides, he’s slower than when he goes forward. And we need constant speed in this game, as the speed is an important element (since higher level = less speed). Which brings me to the 3rd solution
  • Use a proxy movement method : we don’t attach the sphere to the player, instead we teleport the player somewhere hidden (some sort of box) and we analyze its velocity so that we can generate constant inputs from it. That way, we can take those constant inputs and remotely apply them to the sphere.

Using the 2nd and 3rd method would only give (roughly) 8 possible input directions since we don’t have the full precision of the mouse. BUT! Those solution might be better for responsiveness, because having delay with mouse movements is way more perceptible than having it on a key press.

Anyways, I was too lazy to implement that so I went with the default method and tweaked values instead :man_shrugging:

Smoothness & Responsiveness

So we actually chose to keep moving our player on the map (but he’s invisible), and the sphere is following him with a bit of delay, what we want to achieve is having the slowest delay possible (between the sphere transform and the player transform) while keeping a smooth sphere movement.

DISCLAIMER: One can tell me if I’m wrong here, I’m not confident enough about this :cold_sweat:

The thing we have to understand here, is that simulating responsive input in UEFN is not an easy task, and there are many reasons for that :

  • Script Authority : Everything we create in Verse is server sided, because the script is executed by the server. Which means that the server needs to replicate over the network any operation we tell him to do to the clients (read more about the Basics of Multiplayer Networking).
    I’ve told you earlier that everything needs to be replicated to the clients, well this is not the case for own player movements, they’re supposed to be called by the owning client first, THEN the server watches if the movement is correct and teleports you to a valid location if it thinks it’s not. Because if you wait for the server to validate your input (WASD/mouse), then the game would feel really really really laggy (you’d move the mouse wait for server to answer, and then the camera would rotate).
    Let’s say our Verse script wants to use the current rotation of our player to adjust the rotation of a prop, what will happen is that You'll move your mouse to turn around > Your new rotation is sent to the server > Server reads it (on next tick) > It adjust the prop rotation and send it back to you
    So the delay between your mouse rotation and the moment you see the prop rotating is :
    Ping (sending) + Random duration between 0ms <> 33ms (since servers run at 30fps) + Ping (receiving)
  • MoveTo starts at previous location : We’re not using TeleportTo since we want something smooth, you can add this delay to the formula too

This is easy to talk about, but in reality it’s more about the feeling, so I went ahead and tested it. I’ve actually talked about movements earlier and said that I decided to go with long duration MoveTo() calls in order to keep it smooth (I know it feels off, wait a bit). While it is great for the sphere movement, it feels completely off for the arrow (which indicates the player’s rotation)

The controls would feel so delayed that my playtesters would actually stop using their mouse at some point, they would only use WASD keys to move). Which made them move slower.

So i’ve decided to split the two components, the directional arrow has a higher refresh rate than the sphere. You can see on the video that the sphere takes a bit more time to actually reach the arrow. Making it a lot more responsive (even though the sphere is lagging behind).

NOTE: Using control rigs to animate the sphere would’ve been roughly the same as using long duration MoveTo() calls but without the small stutterings, I could’ve implemented it but I didn’t take the time to.

ROLLBACK_NOTE: Now that the sphere lags a bit behind the player, we cannot use the damage zone device to detect player location (between them) anymore, since a moving player can be outside of its own sphere. We’re going to make them invincible and use the old distance check method just between the players (we have a maximum of 16 players so the Max Iteration count is now 16*16=256, which is way less than the previous 10k)

Additional features

We’re close to being finished here so don’t worry, but maybe you can find some more insight that could help, I’ll talk about issues I had with side features and also talk about those features just because I want to flex.

Pools


I’ve talked about this system earlier, and I’m really glad I made a generic class for this, because I use it in every maps. They are child classes of the item_pool class which implements these methods :

FetchAvailableItem()<suspends>:?tuple(item_type,  int) # Fetch and lock a random available item
TryFetchItemAtIndex(Index: int)<decides>:item_type # Try to fetch and lock the item at provided index if available
FetchAvailableItems(Count: int)<suspends>:?([]tuple(item_type,  int)) # Fetch multiple available items
ReleaseItem(AtIndex:int):void # Release the item into the pool (make it available again)
ReleaseAll() # Release the whole pool

The script could be improved, could get rid of ReleaseItem(:int) function for example, but I’m happy with it, considering it’s a parametric class and that those are pretty limited right now.


(me and my pools! :sunglasses:)

image
(you can pass them any data)

Verse crash detector


Does your server crash sometimes ? Well certainly not mine, thanks to someone who told me how to detect a crash and restart the round! (but it still crashes then :thinking:)

Custom switch_device persistency


I wanted to have a way to unlock things permanently and I thought that using the switch_devices to make a generic system would be actually a good solution
So I’ve made this generic device that adds persistency to any switch_device, you just need to check the switch’s state once the save data is loaded
https://twitter.com/i/status/1714608541556728133

I use it for my Prestige and Skin system, so that what you unlock stays unlocked, it basically persists logic values, which can be useful


(don’t mind the tiny devices)

You juste create a persistent_switch_system and add switches to it, it’s configurable so you can have multiples.
image

Skins


Whenever you buy a skin you unlock it permanently, and then you can equip it for free. (Using conditional button as a display mean only)

I then added a Skin Sphere that’s a bit bigger than the Player Spheres (inside the player sphere pool) and which is masked.

And whenever you get assigned your player sphere, the material of the Skin Sphere is swapped for your selected skin

skins

I had issues with this because the skin would never load if you’re not close enough to the origin transform of the prop (which is outside the map). This is why you get teleported where the spheres are for 2s when you start a new game.

c.f. SetMaterial() not working unless you get close enough to prop's origin transform

Name picker


This is just a simple device that allows you to pick a name composed of two words, it’s in a separate file so you can use it from anywhere.

The name (if defined) is then displayed on the sphere, and on the scoreboard UI

Custom scoreboard


I use a Verse UI that refreshes every 5 seconds for everybody, it will retrieve the top 3 playing players (who are actually controlling a sphere) and display their in-game name or localized player name if none is defined.

Had to make this function to retrieve the player name (since we need to display an emoji before and the player score after)

BuildPlayerMessage<localizes>(Agent:agent,  Prefix:string,  Suffix: string):message  =  "{Prefix}{Agent}{Suffix}"

I’m also using a custom “widget” called shadowed_text_block which allows for another text_block acting as a shadow to be rendered along with a text_block since text_block shadow settings don’t seem to work for me?


(there’s no one on my map but I swear it works :smiling_face_with_tear: )

Emoji Grade system


So this is a system I’ve made that basically converts any integer (up to a certain amount) to a unique set of emojis, each emoji being dynamically assigned a value (based off of some device parameters)

I found that log from September showing what results it returns

Show GetEmojiGrade Function Results

[2023.09.17-17.21.14:964][922]LogVerse: : Emoji(20)=🎈
[2023.09.17-17.21.14:965][922]LogVerse: : Emoji(40)=:balloon::balloon:
[2023.09.17-17.21.14:966][922]LogVerse: : Emoji(60)=🧸
[2023.09.17-17.21.14:967][922]LogVerse: : Emoji(80)=:teddy_bear::balloon:
[2023.09.17-17.21.14:968][922]LogVerse: : Emoji(100)=:teddy_bear::teddy_bear:
[2023.09.17-17.21.14:968][922]LogVerse: : Emoji(120)=🔩
[2023.09.17-17.21.14:969][922]LogVerse: : Emoji(140)=:nut_and_bolt::teddy_bear::balloon:
[2023.09.17-17.21.14:970][922]LogVerse: : Emoji(160)=:nut_and_bolt::nut_and_bolt::balloon:
[2023.09.17-17.21.14:970][922]LogVerse: : Emoji(180)=💣
[2023.09.17-17.21.14:971][922]LogVerse: : Emoji(200)=:bomb::nut_and_bolt::teddy_bear:
[2023.09.17-17.21.14:971][922]LogVerse: : Emoji(220)=:bomb::bomb::teddy_bear::teddy_bear:
[2023.09.17-17.21.14:973][922]LogVerse: : Emoji(240)=🎮
[2023.09.17-17.21.14:974][922]LogVerse: : Emoji(260)=:video_game::bomb::nut_and_bolt::nut_and_bolt:
[2023.09.17-17.21.14:974][922]LogVerse: : Emoji(280)=:video_game::video_game::nut_and_bolt::nut_and_bolt:
[2023.09.17-17.21.14:975][922]LogVerse: : Emoji(300)=:fire::bomb:
[2023.09.17-17.21.14:976][922]LogVerse: : Emoji(320)=:fire::video_game::bomb::bomb:
[2023.09.17-17.21.14:976][922]LogVerse: : Emoji(340)=:fire::fire::video_game:
[2023.09.17-17.21.14:977][922]LogVerse: : Emoji(360)=:jack_o_lantern::video_game:
[2023.09.17-17.21.14:978][922]LogVerse: : Emoji(380)=:jack_o_lantern::fire::video_game::video_game:
[2023.09.17-17.21.14:978][922]LogVerse: : Emoji(400)=:jack_o_lantern::jack_o_lantern::fire:
[2023.09.17-17.21.14:979][922]LogVerse: : Emoji(420)=:8ball::fire:
[2023.09.17-17.21.14:980][922]LogVerse: : Emoji(440)=:8ball::jack_o_lantern::jack_o_lantern:
[2023.09.17-17.21.14:981][922]LogVerse: : Emoji(460)=:8ball::8ball::jack_o_lantern::fire:
[2023.09.17-17.21.14:981][922]LogVerse: : Emoji(480)=:tada::jack_o_lantern:
[2023.09.17-17.21.14:982][922]LogVerse: : Emoji(500)=:tada::8ball::8ball:
[2023.09.17-17.21.14:983][922]LogVerse: : Emoji(520)=:tada::tada::8ball::jack_o_lantern:
[2023.09.17-17.21.14:983][922]LogVerse: : Emoji(540)=:fireworks::8ball:
[2023.09.17-17.21.14:984][922]LogVerse: : Emoji(560)=:fireworks::tada::tada:
[2023.09.17-17.21.14:985][922]LogVerse: : Emoji(580)=:fireworks::fireworks::tada::8ball::8ball:
[2023.09.17-17.21.14:986][922]LogVerse: : Emoji(600)=:game_die::tada::tada:
[2023.09.17-17.21.14:986][922]LogVerse: : Emoji(620)=:game_die::fireworks::fireworks::tada:
[2023.09.17-17.21.14:987][922]LogVerse: : Emoji(640)=:game_die::game_die::fireworks::tada::tada:
[2023.09.17-17.21.14:988][922]LogVerse: : Emoji(660)=:sun_with_face: :fireworks::fireworks:
[2023.09.17-17.21.14:988][922]LogVerse: : Emoji(680)=:sun_with_face: :game_die::game_die::fireworks:
[2023.09.17-17.21.14:989][922]LogVerse: : Emoji(700)=:sun_with_face: :sun_with_face: :game_die::game_die:
[2023.09.17-17.21.14:990][922]LogVerse: : Emoji(720)=:rainbow::game_die::game_die:
[2023.09.17-17.21.14:991][922]LogVerse: : Emoji(740)=:rainbow::sun_with_face: :sun_with_face: :game_die::game_die:
[2023.09.17-17.21.14:991][922]LogVerse: : Emoji(760)=:rainbow::rainbow::sun_with_face: :sun_with_face:
[2023.09.17-17.21.14:992][922]LogVerse: : Emoji(780)=:snowflake::sun_with_face: :sun_with_face:
[2023.09.17-17.21.14:993][922]LogVerse: : Emoji(800)=:snowflake::rainbow::rainbow::sun_with_face: :sun_with_face:
[2023.09.17-17.21.14:994][922]LogVerse: : Emoji(820)=:snowflake::snowflake::rainbow::rainbow::sun_with_face:
[2023.09.17-17.21.14:994][922]LogVerse: : Emoji(840)=:100::snowflake:
[2023.09.17-17.21.14:995][922]LogVerse: : Emoji(860)=:100::snowflake::snowflake::rainbow::rainbow:
[2023.09.17-17.21.14:996][922]LogVerse: : Emoji(880)=:100::100::snowflake::snowflake::rainbow:
[2023.09.17-17.21.14:997][922]LogVerse: : Emoji(900)=:droplet::100:
[2023.09.17-17.21.14:998][922]LogVerse: : Emoji(920)=:droplet::droplet:
[2023.09.17-17.21.14:998][922]LogVerse: : Emoji(940)=:droplet::droplet::100::100::snowflake::snowflake:
[2023.09.17-17.21.14:999][922]LogVerse: : Emoji(960)=:hot_face::droplet:
[2023.09.17-17.21.15:000][922]LogVerse: : Emoji(980)=:hot_face::hot_face:
[2023.09.17-17.21.15:000][922]LogVerse: : Emoji(1000)=🌌

It’s also dynamic so I can change the thresholds and the emojis

image

Generic playtime tracking device


Most maps implement those playtime trackers so I thought I’d do a generic system. You can feed the device with a set of main playtime trackers that needs to be completed linearly.

Once all the main trackers have been completed, if you defined secondary trackers, it will pick a random secondary tracker and activate it for the player untill the player leaves. (or completes it)

I made rarity based trackers, so that after finishing the main trackers, the player can randomly get assigned a Common playtime tracker that rewards a bit, or a Legendary playtime tracker, if the player is lucky.

The rewards for each gamemode can be set by inheriting the playtime_tracker_reward base class, and then downcasting to the new child class inside a callback that would be fired by a custom_subscribable

Audio


Something that I did is that I had this recurring sound of wolves dying each time you’d eat a sphere, so I had to find a solution.

The audio mixer device was a good solution, but it’s lacking possibilities, so I had to mute everything if I wanted to mute animal sounds. This means that I had to redo the explosive_device SFX again. So I took the opportunity to assign different sounds to each bombs

I also added sounds for when you eat a sphere, these one are random so that it doesn’t sound too boring

NOTE: Something interesting I found out is that the volume attenuation of world placed audio devices actually take the distance to camera for reference, so that I have floating in the air since my default camera (for lvl1 sphere) is 3000 units high already

UEFN


Here are a list of additional things I learned making this game



This sky is made with the Day Sequence Device


https://twitter.com/lama_creator/status/1718267525958406610
This material I made for my lobby floor, the floor itself is a single Instanced Static Mesh, first time I use those, they’re supposed to be optimized when you want to render the same mesh many many times at the same place.

The material looks like this, (the only comment you can see on the left is for retrieving the Instanced Static Mesh world position, then it basically automatically switches between 3 display modes)



Putting a sequence of images onto a material :ok_hand:

// smth about easter egg?


:desert_island: 6806-7394-2329

10 Likes

Wow, that is an incredible accomplishment. !!!

1 Like

just wow!!!

1 Like

*slow clap that transitions into standing ovation*

1 Like

*laughing gradually too*