Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Networked RPG Ability System - How ??

Discussion in 'NetCode for ECS' started by WAYNGames, Mar 8, 2023.

?

What would you choose ?

  1. FULL RPC

    1 vote(s)
    14.3%
  2. RPC + Ghost Field

    0 vote(s)
    0.0%
  3. IInputComponentData

    6 vote(s)
    85.7%
  4. Other ???

    0 vote(s)
    0.0%
  1. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    Hi, this thread is a gold mine. Thanks everyone for the explanation.

    I'm still facing some brain knots though.

    I working on a RPG type project. And for that I want to trigger an ability, for that I have 3 option so far.
    For that the assumption is that VFX, SFX and Animation are client side only (server doesn't even know there are these things to trigger). Other simulation data such as health are ghost fields simulated only on server.
    If a projectile is needed, a predicted ghost spawn would be used.


    A) FULL RPC:

    1. The client capture the player input, creates a server RPC with the information about the ability to trigger (an ID)
    2. The server receives the RPC and update a component for the combat systems to read and trigger the ability effects
    3. The Server broadcast back the RPC to all clients (including original sender)
    4. The Client receives the RPC and update a component for the combat systems to read and trigger the ability effects
    • Pros :
      • I'm certain to have a one time trigger reliably whatever the network conditions (600ms ping)
      • I can use the RPC broadcast from server to Client to trigger NPC abilities
      • I can use change filters on the server and client since the component is only written to when a RPC comes in
      • Very low bandwidth ?
    • Cons :
      • RPC management is ugly (receive RPC on connection entity -> find target entity from connection entity -> apply to target entity). Even worse for NPC that don't have a connection... (simplified by caching a map of ghost id to entity)
      • The ability is triggered on the client after the server sent it back
    • Potential improvements :
      • When sending client RPC, also apply to local entity. And ignore the RPC broadcast coming back if it's from myself (maybe there is even a way to broadcast to everyone but a given connection?)

    B) RPC + Ghost Field

    1. The client capture the player input, creates a server RPC with the information about the ability to trigger (an ID)
    2. The server receives the RPC and update a component for the combat systems to read and trigger the ability effects
    3. The component get synchronized through a ghost snapshot
    4. The Client reads the snapshot component and triggers the vfx,sfx,animation
    • Pros :
    • I'm certain to have a one time trigger on the server
    • I can update the component on server to trigger abilities of NPC on clients
    • I can use change filters on the server since the component is only written to when a RPC comes in
    • Cons :
    • RPC management is ugly (receive RPC on connection entity -> find target entity from connection entity -> apply to target entity)
    • The ability is triggered on the client after the server sent it back
    • I can NOT use change filters on the client since the component is written every frame by the snapshot (may be fixed in future version ??)
    • I can miss some triggers on client with high ping, missing an animation when a boss starts casting a killer attack would be bad)
    C) IInputComponentData
    1. Still working on that one...
    • Pros :
    • Prediction is handled ?? The ability is triggered on the client when it sends it
    • No ugly RPC management
    • Cons :
    • I can NOT use that for NPC ability triggers ?
    • I can NOT use change filters on either the client or the server since the component is written every frame


    My understanding is that RPC are a valid choice when it comes to triggering an ability (ref; BossRoom).
    That being said, it also sometime uses non RPC triggers from slow moving projectiles.
    There may also be some scaling issues if lots of RPC gets triggered.
    Or how to handle relevancy/area of interest of clients.
    A client on one side of the map does not care about the ability of another player or monster being sent for the other side of the map. The can always ignore it but it would be better not to sent them (safety and savings).


    Am I missing any consideration in regards to any of the 3 options ? Am I mistaken in my understanding of how I should handles such use case ?
     
    Opeth001 likes this.
  2. NikiWalker

    NikiWalker

    Unity Technologies

    Joined:
    May 18, 2021
    Posts:
    224
    Hello! I'd strongly recommend the IInputComponentData route, for the reasons you mentioned:
    • Prediction is handled for you.
    • As are: Stuns, interrupts, entity destruction (i.e. death mechanics), collision, lag compensation, prediction error correction, importance, relevancy etc.
    • As is: Anti-cheat!
    I haven't personally seen the BossRoom code, but BossRoom is client authoritative. "Netcode for Entities" is fully server authoritative, and our recommendation for almost all gameplay interactions is to use commands (i.e. IInputComponentData).

    Oh, you absolutely can. You just have the (server-owned and server-driven) NPC AI write directly to an input component.

    For the input component, you mean? You're right, but you likely can have change filtering for NPC input (if you don't expect it to change often), but I honestly cant imagine that being the most expensive part of your game (by a long shot). E.g. One thing you could do is early out the expensive character controller if the input component is "default".

    EDIT: I'll also add that, if you have a project use-case for IInputComponentData structs which need change filtering, please send us a bug report of the project. It's likely not too hard to add, but we'd really appreciate a use-case. Thanks mate.
     
    WAYNGames and tertle like this.
  3. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    As far as I know, the best way to do it is to use inputs to trigger abilities on the server, then replicate them to clients using ghosts. This way, the abilities are networked by default, and you can customize the ghosts using classification systems to add client/server only components (vfx...).
    You can sync the abilities and actors' states by using replicated components. The Unreal Gameplay Ability System helped me a lot in planning my approach, even though it's based on OOP.
     
    WAYNGames likes this.
  4. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    Thanks for the answer.
    I've been chatting with @Occuros on discord and I think he help me understand a lot of stuff.

    I'm trying to make a simple full feature ability (deal damage/play animation/vfx/sfx).
    The initial assumption was that the server doesn't need to know anything about the "presentation" parts of the ability (animation/vfx/sfx). if a projectile need to be created is would be a prespawned predicted ghost.

    The idea was to use a ghost component to know when an ability is triggered and read that on the client to play animation and other vidual stuff.

    That doesn't work because a client can miss a snapshot and not see the ability trigger.
    It also doesn't work for late joining client or client getting into the relevancy area after the ability was triggered.

    That means I need to drive the animation state from the server and sycn that state to the client.

    So the new plan would be to use the IInputComponentData to capture the triggered ability in the GhostInputSystemGroup.

    On the client, in PredictedSimulationSystemGroup, I set a component that is read by the combat system to predict the ability effects.
    On the server the same thing happens. That will change the state of the target reduce health and set the animator start (playing X animation state starting form serverTick Y). These would be ghost component replicated to the client.
    When the client receives the snapshot it can read the state and apply it to the proper entity.
    For immediate change (health decrease) it would be a simple override.
    For long running changes like animation or vfx/sfx, the client would need to figure out where the animation is at based on the current simulated serverTick and the serverStartTick for the animation sate.

    • I hope I'm clear and I understood correctly ??



    For the change filter the idea is that the combat system would need to check lots of things. Is the ability cooled down? Is the target in range? does the caster have enough resources? then it would have to compute and apply the effect, get the stats of the caster and target compute damage, apply the new health, register animation state, ...

    I don't expect all my player and NPC to be in combat every frame. So there would be no need to perform all this on entities that are not trying to cast an ability.

    As explained I can use an intermediate component to drive that. If that component is not a ghost component, It would be set only by the input system.
    It's used only by the predicted client and server. All other client would just react to the state change from the server.



    Other questions regarding ghost animation controller :
    • Is it usable ? or is it not finished?
    • Does/Will it handle the late join synchronisation I described ?
    • Are there any sample on how to actually use it ?
    • Where does it fit in regards to the hopefully upcoming DOTS animation (DOTS motion ?) package ?
     
  5. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    774
    For doing what you described forget using the Animator on the client side. It would be a mess.
    So focus on using a complete solution based on Playables, that let you control all things with a way more simpler logic (and controlled state).
    Also, because also the server run the animations, it means that everything should be triggered/driven by replicated components. That means, all the necessary data for blending, transition (maybe, not necessary all the time) and all these must be in that data.

    There should be a complete separation from the visualisation state and the game state to make things simple and understandable.

    Depends on how you serialise that. If the trigger is just a int that say at which tick the ability has been triggered, and/or at which tick the ability is terminating (if it has time) you don't miss anything.
    You send the interval, and the client determine the rest.
    And the client probably not even reduce the health either if it is an interpolated ghosts. For predicted one, yes they will do but they have all the information they need to know.

    yes, you can even pass further information to figure:
    - out blending state
    - interpolation factor used by the server at that time
    - the total elapsed time in ms of the animation track
    To make it more precise the start.

    But then, remember, the goal is not to make it exactly the same. But to look great on the client. For both interpolated and/or predicted is always an approximation.

    Yes, indeed you can avoid making many of these calculation if the NPC is not in combat. But at that point, I'm expecting that you have a state machine or use IEnableable component for that, so the code logic not even run in first place, without need to even check for a change, that may still occurs for other reason, in other states.

    Also, remember again, change filter works on a chunk level, not entity level. So if one entity get update, the whole chunk need to be updated. So the saving need to be measured.

    • It is usable, yes.
    • It does nothing for you. It will let you drive your animation (with animator and/or playable) in the right place and dealing with passing data and synchronise GO/Entity transforms. But the synchronisation logic of the tracks is all up to. Because it depend on the type of animation system you are making.
    • We do have sample for it. But unfortunately we can't release them yet.
    • DOTS Motion is not coming for 1.0. The release data is not confirmed either. In that respect it does not fit at all with that architecture. It is/was not mean for that.
    [/quote][/quote]
     
    WAYNGames likes this.
  6. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    After studying the netcode package documentation, I have gained a deeper understanding of DOTS and how to design systems in a stateless manner. One key insight I've gleaned is that the netcode package does not rely on triggers to instantiate Ghosts (such as Abilities in your case), but instead utilizes data to predict things. Adopting a stateful triggering approach would contradict the principles of netcode and negate its advantages.

    My recommendation is to view abilities as a composition of actions that can be recursive through events. Each synchronized action can be represented by a Ghost entity, which allows the netcode package to automatically fulfill synchronization requirements such as late joining clients and missed snapshots.

    For example, you can represent a fireball projectile as a Ghost entity, and upon impact, a new Ghost entity will be spawned to represent the explosion. If a player joins the game while the fireball ability is already triggered, they will only see the remaining part of the action.

    Overall, the key takeaway is to design your system in a stateless manner and utilize Ghost entities to ensure proper synchronization within the netcode package.
     
    NikiWalker likes this.
  7. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    You kinda lost me.
    I should design a stateless ability system but all the netcode does is synchronize state :/
     
  8. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,068
    I understand this is a bit complex at first glance, to be short and simple try to base your ability system on composition and think of each ability as separate actions and each replicated action as a ghost.
    Don't think about the netcode part it will be done by default.
     
  9. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    I managed to make something work in a way that satisfy me for now.

    I still have my IInputComponentData write to a IComponentData that contain the id of the ability to trigger and when the server tiggered it (NetworkTick). The IComponentData is not send back to owner so the predicted client as authority over itself. But that authority is limited to the presentation aspects of the ability. Effects on stats such as consuming mana or dealing damage can be predicted but are overridden by a snapshot. Projectiles will be predicted spawned ghost.

    Then in my clients I the information and compute what animation should be playing and from what point it should be playing.
    For that I'm using the current client simulated NetworkTick and compare it to the NetworkTick when the ability was triggered multiplied by the ClientServerTickRate.SimulationFixedTimeStep. That gives me the time the animation has been playing for on the server and I can adjust the client animation accordingly.
     
  10. Richay

    Richay

    Joined:
    Aug 5, 2013
    Posts:
    75
    I'm having trouble with the client-only parts of my ability system (e.g. vfx). The same as WAYNGames, my goal is for clients to predict ability effects but have authority over presentation aspects.

    My setup is currently like this:

    - Client uses IInputComponentData to show that ability button has been pressed.
    - Abililty ghost is created.
    - After x time, the ability performs an action (e.g. on the frame when a hammer swing hits the floor).
    - In the shared server/client ProcessAbilitySystem prediction system, an activation buffer element is added to the ability, with the current server tick as a field.
    - In the shared server/client ActivateAbilitySystem prediction system, all activation buffer elements are applied if we're on the activation's tick.

    My abilities are composed of actions, some of which are client-only - the server doesn't need to know anything about feedback vfx. For these actions, we simply return early when we're being processed by the server. Also, for client-only actions, we ensure that isFirstTimeFullyPredictingTick is true.

    This setup works perfectly for the local client when they predict ProcessAbilitySystem. However, when ProcessAbilitySystem is set to server only, the client no longer sees the correct ServerTick in ActivateAbilitySystem - it is always in advance.

    My understanding is that in ActivateAbilitySystem, the client's ServerTick should rollback to match the server's ServerTick, then execute repeatedly with an incremented tick until we're back at the 'live' tick. I see that the rollback occurs, but it is always to 1 tick in advance of the server (I assume 1 is because the server and client are on the same machine).

    e.g. (seen using breakpoints)
    - Client activates ability
    - X time passes until ability action occurs
    - Server sets ability activation Tick field to 10
    - Client processes ServerTime 12

    Because of this, clients do not see the presentation aspects of abilities from other clients, even if they are predicting ProcessAbilitySystem.

    Am I doing something completely bonkers? Any advice would be much appreciated.

    A related question: is there a way on the client to see if the current tick has been verified by the server? Some presentation effects may be required only if the frame's data is confirmed as good by the server.
     
    Opeth001 likes this.
  11. Richay

    Richay

    Joined:
    Aug 5, 2013
    Posts:
    75