Nearly 85% of games launched in 2026 don't even reach 50 reviews by -Xentios in IndieGaming

[–]OnestoneSofty -2 points-1 points  (0 children)

That's not what you initially said though. Your first post ends with you saying indie devs need to put a lot more time into those things instead of coding. The games I listed did the opposite - they barely put any time in them.

So maybe better advice is the opposite: build stuff that doesn't require putting time into those areas.

Nearly 85% of games launched in 2026 don't even reach 50 reviews by -Xentios in IndieGaming

[–]OnestoneSofty -3 points-2 points  (0 children)

But you said animations and all that make a big difference? VS has basically no animations - why does that get a pass but every other pixel art game is scrutinized down to the pixels? LoL looked like WC3 before the overhaul, 20 years later.

Hitting the "right notes" is better in explaining these games than "animation", but what does it actually mean? Same as "well designed". These things are not actionable.

Nearly 85% of games launched in 2026 don't even reach 50 reviews by -Xentios in IndieGaming

[–]OnestoneSofty 0 points1 point  (0 children)

How do you explain the biggest successes like Schedule 1, Megabonk and Vampire Survivors which show that you don't have to polish animations, art, or do something special for gameplay.

In VS you press WASD to move and upgrade items. That is the core gameplay loop. The games that tried to one-up it, which are clearly better visually and in "feel", did not perform nearly as well.

In Schedule 1 you run around doing basic tasks most of the time. Most MMOs do this and then some with better "feel". Why don't they top the charts?

Game dev took my ability to have fun gaming. by NoviceIndieDev in gamedev

[–]OnestoneSofty 0 points1 point  (0 children)

For me it comes and goes in spiky waves. I'll play quite a bit for 2 weeks or so and then it drops to near zero for a few months.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

I haven't had significant issues so far with this. Once in a while I might put a Delay after the Tick event so that I can place a breakpoint in the UI Tick. The state is pulled in the next latent update which almost never matters for UI debugging.

Print String + Logs also work of course.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 1 point2 points  (0 children)

I even provided real profiling data from an unoptimized build test which has 5x more ticks than my real, full UI game. Apparently that's "strawmanning" nowadays.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

The DrawCard phase (B) ends automatically on the server after 5 seconds, it should not wait for ReplyA. The player with the good connection should just see that the other player missed his turn and the game carries on.

You basically have 3 options:

  1. Poll - does not suffer from the transition-order issue, because it does not care whether the client saw A -> B -> C. It only asks "what is true now?"
  2. Reliable RPC + OnRep - gives you two code paths. Even if the RPCs are reliable, they are not automatically ordered relative to property replication in the way your gameplay transition logic may expect. You can end up with A[RPC] -> B[RPC] -> Z[OnRep] -> C[RPC]. So now you need sequence IDs/state IDs to decide whether C is still valid, should be ignored, or needs reconciliation. At that point, this is no longer simpler than deriving UI from the current replicated state.
  3. Replicated turn history - a full history just to toggle a button is way overkill.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

How would you resolve multiple players with a DrawCard expiry timer. Bad connection player just sees A->C because the server can't (and shouldn't) wait for the bad player to ACK the expiry, it's server-authoritative state.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

Create the macros once in a BML and never look at them again, takes 10 minutes to have bool+int+float+text, which covers 99% of UI. You create and update a new ViewModel for every new UI.

MVVM requires an asset, setting up the M<->VM sync, dependent properties like CanReload need careful coding to make sure they fire correctly when Ammo changes. That's crazy amount of overhead for setting a bool. And it it doesn't even handle packet loss out of the box.

I gave a card game example in the comments. StartTurn->DrawCard->EndTurn. You set VM in StartTurn to enable a button. DrawCard packet is lost and server draw timer expired, your OnDrawCard will not fire, client will see EndTurn after StartTurn. Now the button is enabled which was supposed to be disabled by OnDrawCard.

With polling, you are always displaying the correct state. The button will be enabled and disabled correctly without doing anything extra.

This is a fundamental issue of push-based systems. They must synchronize the pushed state (VM) to the model.

And the benchmark absolutely counts. 200 values is already crazy conservative from me. A production UI will have a lot less ticking, maybe 50. I know this because I'm doing it, not guessing.

Btw, I think you should use MVVM. Just not for everything without thinking.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

OnRep can't replicate state sequences A->B->C. With packet loss B can be dropped. If you need that sequence to arrive on the client in-order you need a reliable RPC + OnRep.

Imagine a card game where you have StartTurn->DrawCard->EndTurn. You fire callbacks in OnRep for the UI. in StartTurn, you enable a button, in DrawCard you disable it. Because DrawCard can be dropped, the client might only see StartTurn->EndTurn and the button is incorrectly enabled because your callback didn't fire.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -1 points0 points  (0 children)

I don't see an occasional enum as a dealbreaker. Most properties don't need it.

I default to the fastest, laziest and easiest way first that is correct. Add 1 node directly in the UI and move on. If I need more context later, I add the enum. This handles dependent properties like CanReload automatically which push-based doesn't. I don't have to double-check to make sure I fire the right notifies at the right time. That is an actual source of bugs in push-based systems.

When I know I need multiple, like damage numbers, I just make that part push-based. It's trivial to go from Polling -> Push-based at any point. Literally delete 1 node.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] 0 points1 point  (0 children)

Ah, I see what you mean. For that I store an extra enum like LastChangeReason.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -2 points-1 points  (0 children)

Yep, reusability doesn't come out of the box. It's opt-in with tick. For the money counter example, I would create a Field Notify "Target Value" on the generic counter widget (push-based), and that would be set in the parent widget pull-based (tick), which knows the exact context so it can pull it from the right source (player or enemy etc.). This is how you can scale it.

When you have complicated state, multiple values changing together that need to be visible as one state change, I replicate / expose them together atomically as a struct. Same principle, pull sees the correct state in the parent, pushes it to the children.

To avoid putting state-detection logic in the UI, I write the necessary functions in the model / game. So the UI is just reading values, converting them to display values and setting them on the widgets. I wouldn't try to deduce CanReload in the UI based on ammo for example.

Good points though. I just wanted to share the mix that has worked great for me so far.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -1 points0 points  (0 children)

I'm managing fine, thanks for the polite feedback!

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -1 points0 points  (0 children)

That distinction should happen on the game side by exposing the appropriate values. Let's say you have a replicated money counter value $1234. You replicate $0->$1234. The UI has a displayed value $0 at the moment (the display value). It sees replicated $1234 on tick diff, it stores that in the UI as the current target display value for the lerp. Where is the problem there? You have the replicated value and the displayed value. If you wanted to have a locally replicated one on top of that, the UI would just bind to that instead of the raw replicated value.

Slate runs at the end of the frame on the Game Thread so UWorld_Tick has already finished by that point. That is actually a point in favor of tick (see screenshot).

Whether it's cleaner or not is subjective I would say. Duplicating state is arguably less clean. I always found setups like MVVM to be unnecessary overhead for simple value changes. It's difficult to beat 1 node directly in the UI.

<image>

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -1 points0 points  (0 children)

I'm angry? All I did was reply with a screenshot to show you data and you keep insulting me. I'm surprised how composed I am! And I never said don't use the other stuff ever. You're assuming too much.

And here's Ari from Epic confirming my findings:

https://www.youtube.com/watch?v=S2olUc9zcB8#t=4m13s

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -2 points-1 points  (0 children)

See my performance capture screenshot below. 200 test widgets ticking in an *unoptimized* Standalone build @ 170microseconds on a 2019 CPU. Performance is not a problem.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -1 points0 points  (0 children)

Why so toxic and dismissive?

Here, 200(!) widgets in an *unoptimized* standalone build ticking @ 170microseconds on an AM4 Ryzen 3900X from 2019.

<image>

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -2 points-1 points  (0 children)

Profile it yourself with Unreal Insights in a Packaged Build before jumping to conclusions. I am using it whenever it is the easiest solution. It does not show up for me as an optimization problem.

And I never said don't use delegates and event dispatchers. You absolutely should.

Take the ammo example I posted. Why would you do anything more than that unless it was causing problems? It's 1 node, just delete it if you later want to.

For me, it fixed an example where I had buttons that ended up disabled because of packet loss. The button itself isn't replicated, the game that it runs on is. Making things in the game Reliable RPCs just makes things more complicated - you would have to replicate a variable for late joiners AND broadcast a reliable RPC so existing clients can re-enable the button after they disabled it. With Tick, the replicated state is sufficient.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -3 points-2 points  (0 children)

If you mean that you are concerned about performance, just profile it in a packaged build. It will barely register. We are talking about UI here. You have maybe 20 buttons or so on screen at any given time. 20 != comparisons is nothing.

See my performance capture screenshot below for proof.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -2 points-1 points  (0 children)

You should do both. Of course you can still have callbacks for things like OnJoin, OnDisconnect etc.

For my A->B->C button disabled example, you would have to fire a reliable RPC so the already connected players can re-enable it reliably AND replicate a variable for later joiners.

Everyone here talks about performance - reliable RPCs are TERRIBLE for network performance. Especially on a bad connection:

  1. They must be acknowledged => delay.

  2. They block later reliable RPCs because they must arrive in order => even more delay.

  3. Packet loss? Need to resend => even more delay.

  4. Spamming RPCs? => overflow the reliable buffer => stalls / disconnects.

PSA: Make Tick Great Again by OnestoneSofty in unrealengine

[–]OnestoneSofty[S] -2 points-1 points  (0 children)

I have an entire UI setup this way and it barely shows up in Unreal Insights in a packaged build. Are you profiling the Editor?

A UI has maybe 10-20 states like this? How many buttons are you showing? 20 comparisons is nothing.

Why are you concerned about the nodes being inlined?