GET STARTEDUPGRADING FROM 1.X: EVERY CHANGE
Upgrading from 1.x: every change
2.0 is a major release with breaking changes (plugin version 2, “2.0”). It makes networked interaction server-authoritative with prediction on the client, runs Actions and MoveHandlers through a new execution model, makes idle objects cheap to replicate, fixes the component references and fixes the bugs of 1.x. This guide lists every behaviour change: who is affected and what to do. Changelog lists every change with its reason.
Your 1.x assets are migrated when they load, and keep their 1.x behaviour where that was possible. The main exception is networking: the server now checks the uses of remote clients, and the server and the other clients now run a use themselves.
Contents:
- Upgrading from 1.x: what to check
- How old assets keep their behaviour
- Instancing: one shared object or a copy per use
- Where Actions and MoveHandlers run: NetExecutionPolicy
- Conditions on the server: ServerValidation
- Networked games: the server checks every use
- Networked games: every machine runs the use
- Replication cost
- A use from its start to its end
- Component references and other fixes
- C++ API
- The plugin’s and the demo’s content
- How 2.0 was checked against 1.x
Upgrading from 1.x: what to check
Section titled “Upgrading from 1.x: what to check”- Keep the 1.x versions of your packages (version control) if you may need to go back: 1.x cannot read the new settings. See Going back to 1.x.
- Open the project. Assets saved with 1.x are migrated when they load and keep their 1.x behaviour; nothing has to be re-saved, and a re-save keeps the behaviour. See Assets saved with 1.x.
- Make a full (non-iterative) cook. See Cooking.
- Read the Blueprint compiler log for renamed variables. See Blueprint variables with the new names.
- Blueprint Actions, MoveHandlers and Conditions that you create with 2.0 get the 2.0 behaviour by default: a fresh copy of the Action or MoveHandler for every use, and Conditions checked again on the server. C++ classes keep the 1.x behaviour until you choose otherwise. See Default: Blueprint classes and C++ classes.
- A copy per use (
InstancedPerExecution) does not keep its variables from one use to the next. See Instancing. - Networked games: the server now checks every use of a remote client and can reject it. Check the tolerances in Project Settings > Plugins > Character Interaction System, and look for rejections in the log. See When the server rejects a use.
- Networked games: content that changes the object state or the usable flag only on a client now logs a warning, and the server can reject the next use of such an object. See Object state and usable flag on a client.
- Networked games: the server and the clients that show a use now run its Action and MoveHandler themselves, led by their
NetExecutionPolicy; the 1.x Server and Multicast relays of the Action and the MoveHandler are no longer sent. An Action that finishes only on the server needsServerOnlyorLocalPredicted. See Networked games: every machine runs the use. - Networked games:
InteractionSettingsand most Actions and MoveHandlers are no longer replicated (each machine uses its own copy), and an idle object no longer sends anything. See Replication cost. - Networked games with many objects: consider
bManageOwnerDormancy, but read when not to turn it on. See Owner dormancy. - The demo now ships inside the plugin, in
/CharacterInteractionSystem/Demo, and its character uses Enhanced Input. See Where the demo is. If your project has a copy of the 1.x demo, see An old copy of the demo in your project. - The demo character is now the UE5 mannequin Manny. If you made Blueprints, animation Blueprints or animations from the 1.x demo character, see The demo character.
- Child Blueprints of the plugin’s or the demo’s Actions, MoveHandlers, Conditions and objects, levels that place the demo objects, and your own objects whose settings use these Actions, MoveHandlers or Conditions: they now take the 2.0 settings of those classes. See The plugin’s and the demo’s content.
- Networked games: a remote client sends the movement it held back right before the request of a use, so that a MoveHandler with root motion starts on the server with the same movement step as on the client. See Movement prediction.
- Characters and objects: review the bug fixes that change behaviour, for example the cleanup that no longer interrupts a use, the controller change that now initialises the character component, and the highlighted object that
TryUseObjectnow uses. See Component references and other fixes and Behaviour changes for every use. - Objects with two sides of the same angles: each side now uses its own Actions and MoveHandler (1.x aliased the later side to the earlier one). If content relied on that, merge the two sides into one. See Sides.
- Content that ends an Action that never finishes by itself by walking the character out of the trigger: walking out no longer interrupts a use. End the use yourself (
TryInterruptObjectUsing, orFinishAction). See Overlap, cleanup and tracked objects. - C++: an override of
GetUsableInteractObjectthat blocks a use at press time is no longer consulted while a usable highlight exists. Put such checks intoIsCanUseObjector into the object’s usable Conditions. See Overlap, cleanup and tracked objects. - C++:
SetOwnerOfActorandServerSetOwnerOfActorare deprecated and give a compiler warning; overrides ofEndPlay,Serialize,PostLoadandPostInitPropertiesmust callSuper; the 1.x name fields of the component references are nowSelected*ComponentName_DEPRECATED(useComponent). See C++ API. - C++, networked games:
ServerSetOwnerOfActorandSetOwnerOfActorcalled on a client for any actor other than the one of the object that the client’s character uses are refused now (a warning; nothing changes), and the user’s controller no longer stays the owner of the object’s actor after a use. To give an actor an owner, callAActor::SetOwneron the server. See Ownership of the object’s actor.
How old assets keep their behaviour
Section titled “How old assets keep their behaviour”The new settings
Section titled “The new settings”| Class | Setting | Values | Default |
|---|---|---|---|
Actions and MoveHandlers (UCISReplicatedObject) | InstancingPolicy | Default, SharedPerObject, InstancedPerExecution | Default |
Actions and MoveHandlers (UCISReplicatedObject) | NetExecutionPolicy | AllMachines, LocalPredicted, ServerOnly, LocalOnly | AllMachines |
Conditions (UCISInteractionObjectCondition) | ServerValidation | Default, Validate, DoNotValidate | Default |
FCISInteractionObjectSettings | bTransferOwnershipDuringUse | bool | true |
FCISInteractionObjectSettings | bManageOwnerDormancy | bool | false |
FCISInteractionObjectSettings | MaxUseDuration | seconds, 0 = no limit | 0 |
What they do: Instancing, NetExecutionPolicy, ServerValidation, Ownership of the object’s actor, Owner dormancy, Maximum use duration.
Default: Blueprint classes and C++ classes
Section titled “Default: Blueprint classes and C++ classes”Default is resolved by the class of the object. An explicit value always wins.
- A C++ class keeps the 1.x behaviour:
SharedPerObject, and its Conditions are not validated on the server. - A Blueprint class gets the 2.0 behaviour:
InstancedPerExecution, and its Conditions are validated on the server.
So Actions, MoveHandlers and Conditions of C++ classes (for example built with CreateDefaultSubobject or NewObject in C++ code) behave as in 1.x without any change, and new Blueprint content gets the safer 2.0 defaults.
A class with its own RPCs or replicated properties always runs as SharedPerObject. This applies to a Blueprint or C++ class below the plugin’s Action or MoveHandler classes; the plugin’s own fields do not count. When such a class asks for InstancedPerExecution, explicitly or through Default on a Blueprint class, data validation gives the warning “Uses its own RPCs or replicated properties; runs as SharedPerObject”. A Blueprint compile shows the same warning.
Who is affected: Blueprint Actions, MoveHandlers and Conditions that are created with 2.0, and old ones that you switch. To keep the 1.x behaviour for a new Blueprint class, set the settings explicitly: SharedPerObject and DoNotValidate.
Assets saved with 1.x
Section titled “Assets saved with 1.x”2.0 marks the packages it saves with a custom version, CISVersion; packages saved with 1.x do not have it. When the editor loads such a package, each of its Actions, MoveHandlers and Conditions gets:
- the value of its nearest Blueprint archetype that was saved with 2.0 (a Blueprint class default or template;
Defaultis copied asDefault); - otherwise the 1.x value:
InstancingPolicySharedPerObject,NetExecutionPolicyAllMachines,ServerValidationDoNotValidate.
The object’s own bWithReplication decides NetExecutionPolicy in both directions, as in 1.x. Off gives LocalOnly (in 1.x such an object ran only on the user’s machine). On gives the inherited value, except that an inherited LocalOnly becomes AllMachines.
A C++ archetype (a C++ class default or a C++ default subobject) passes nothing on. An archetype that was itself saved before 2.0 is looked through to its own archetype.
So a pre-2.0 Blueprint class, its instances, and new instances made from it keep the 1.x behaviour. This also holds for content saved with 2.0 that is built from a pre-2.0 archetype that was never re-saved: a new child Blueprint, a Blueprint instance, a level. The migration runs on every load of an old package, while its data is read (a class default at its PostLoad).
What a re-save writes:
- A re-save writes a migrated value only where it differs from the object’s archetype, as for every property.
- A re-saved Blueprint class default holds explicitly each value that differs from its C++ parent class (for example
InstancingPolicySharedPerObject). - An instance or a child that equals its archetype keeps inheriting from it, so a later change of the archetype reaches it.
Copies made in memory are not migrated, and keep the values they copied, including explicit values that a user set on an object of an old package. These copies are: PIE copies, duplicates (Ctrl+D, DuplicateObject), copy and paste, and undo.
Switching an old Blueprint class to the 2.0 behaviour
Section titled “Switching an old Blueprint class to the 2.0 behaviour”- Open its class defaults.
- Set
InstancingPolicyandServerValidationtoDefault(or toInstancedPerExecutionandValidate). - Save it.
Instances and child Blueprints that inherit these settings follow. An instance that shows its own value (the reset-to-default arrow, for example a NetExecutionPolicy of LocalOnly from its own bWithReplication) keeps it; reset it where the class value should apply. For a C++ class, set the values explicitly in its constructor: its Default is the 1.x behaviour. Before you switch an Action or MoveHandler, read When not to switch.
Blueprint variables with the new names
Section titled “Blueprint variables with the new names”UCISReplicatedObject now has properties named InstancingPolicy and NetExecutionPolicy. UCISInteractionObjectCondition has one named ServerValidation. A Blueprint below these classes may have its own variable with one of these names. Then:
- The Blueprint compiler renames that variable (to
InstancingPolicy_0or similar) and logs the warning “Found a member variable with a conflicting name (…) - changed to …”. The Blueprint’s nodes follow the rename. - Exception: a variable that is a property of the same kind is removed instead, and its nodes point at the plugin’s property. Only an enum variable of a C++
enum classtype is of the same kind. The compiler logs the note “replaced references with native parent class variable”. The nodes then fail to compile where the enum types differ.
A 1.x Blueprint cannot have held a variable of the new enum types.
What to do: rename such variables, and check the nodes that used them.
Cooking
Section titled “Cooking”After upgrading, make a full (non-iterative) cook.
- A cooked game does not migrate. It loads packages without the linker that the migration reads the version from.
- The cook migrates each package when it loads it in the editor, and writes the result.
- An iterative cook can keep packages that 1.x cooked. Those hold none of the new settings, so their Blueprint objects would resolve
Defaultto the 2.0 behaviour.
Going back to 1.x
Section titled “Going back to 1.x”Every Action, MoveHandler and Condition writes the custom version CISVersion when it is saved (GUID 7C0D5A1E-4B2F-4E7A-9C3D-2A6B8F1E5D40, version ExecutionPolicies = 1). 1.x does not know this version. When 1.x opens a package that 2.0 saved, it logs the warning “Package was saved with a custom version that is not present” and loads the package without the new settings. Go back with the 1.x versions of your packages, not with packages that 2.0 saved.
Live Coding and hot reload
Section titled “Live Coding and hot reload”The check for a class’s own RPCs or replicated properties (CIS::Migration::ClassHasCustomNetFields) is cached per class. The cache is cleared:
- when Blueprints are reinstanced in the editor;
- after every Live Coding patch or hot reload.
So a class that a patch gives its own RPCs or replicated properties runs as SharedPerObject from then on.
Instancing: one shared object or a copy per use
Section titled “Instancing: one shared object or a copy per use”InstancingPolicy decides which object a use runs:
SharedPerObject: the Action or MoveHandler set on the interaction object, as in 1.x. Its variables keep their values from one use to the next, and every use of the object shares them.InstancedPerExecution: a new copy for each use. The copy is made when the use starts, before the data object’sOnObjectUseStarted, from the object set on the interaction object, with that object’s current values (changes made to it at runtime included). Changes made to the object set on the interaction object insideOnObjectUseStartedor later do not reach the running copy: change the copy instead (theActionparameter ofOnObjectActionStartedor of the object’sOnActionStartedDelegate). Its outer is the interaction object component, as for the object set on it. The plugin releases the copy when the use ends, and the garbage collector destroys it. The copy’s variables never reach the next use or the object set on the interaction object.
Every machine that runs a use runs its own copy of an InstancedPerExecution object, the server included, so its variables do not persist between uses on any machine. A copy is never replicated.
Who is affected: Blueprint Actions and MoveHandlers created with 2.0, whose Default resolves to InstancedPerExecution, and old ones that you switch. Content saved with 1.x and C++ classes keep SharedPerObject.
When to switch to InstancedPerExecution
Section titled “When to switch to InstancedPerExecution”Switch an Action or MoveHandler when it keeps nothing that must outlive a use. Then overlapping uses (several characters, or a quick second use) can no longer change each other’s variables, and every use starts from the same values.
How: set InstancingPolicy to InstancedPerExecution (or Default on a Blueprint class) on the class defaults, then check the points of What changes for a copy.
When not to switch
Section titled “When not to switch”- The Blueprint keeps state between uses: a counter, “already opened”, a reference it caches for the next use. A copy starts from the object’s values at every use and loses what the previous use stored. Keep it
SharedPerObject, or move that state to the object’s actor or component. - The class has its own RPCs or replicated properties (for example a Blueprint “Run on Server” event or a replicated variable in the Action). Such a class always runs as
SharedPerObjectanyway, and the editor warns when you ask forInstancedPerExecution(see Default). - Other code reads the running Action through the object set on the interaction object (
GetObjectActionForSideIndex,GetObjectActionForSide, the settings): with a copy it sees an object that does not run (see below).
What changes for a copy
Section titled “What changes for a copy”- State that must outlive a use (a counter, “already opened”): keep it on the object’s actor or component, or set the class to
SharedPerObject. - Code that watches the running Action through the object set on the interaction object sees an object that does not run. Use the
Actionparameter ofOnObjectActionStartedor of the object’sOnActionStartedDelegate, orUCISUseExecution::GetActionandGetMoveHandler(C++:UCISCharacterInteractionComponent::GetActiveUseExecution). From a copy,UCISReplicatedObject::GetExecutionTemplatereturns the object set on the interaction object. - A copy that is still busy when the use ends (a timer, a latent node) may run until the garbage collector destroys it. Its late
FinishMovementdoes nothing, because the end invalidates a MoveHandler copy. Its lateFinishActionno longer reaches the use, but it still runs the Action’s own finish code (it setsNewStateAfterFinishAction), so do not leave a copy’s timer running past the end. - A MoveHandler copy ticks (
HandlerTick) while it moves, as the shared MoveHandler did in 1.x. The object set on the interaction object does not tick for it. - Interruptions: a
SharedPerObjectMoveHandler is still aborted by an interruption after its movement has finished (MovementAbortedafterMovementFinished), as in 1.x; all 1.x content runsSharedPerObject. A MoveHandler copy getsMovementAbortedonly while it moves. Who is affected: MoveHandler copies (InstancedPerExecution), which new Blueprint MoveHandlers are by default. What to do for them: clean up inMovementFinishedas well as inMovementAborted.
Where Actions and MoveHandlers run: NetExecutionPolicy
Section titled “Where Actions and MoveHandlers run: NetExecutionPolicy”A use has two steps: the movement (the side’s MoveHandler) and the action (the Action for the object’s state). In a networked game every machine that takes part in a use runs it: the user’s machine, the server, and the clients that have the object’s actor (they show the use). NetExecutionPolicy of the Action or MoveHandler decides where it runs and who leads its step:
NetExecutionPolicy | Runs on | Leads the step |
|---|---|---|
AllMachines (1.x content) | every machine | the user’s machine |
LocalPredicted | the user’s machine and the server | both: the one that completes it first |
ServerOnly | the server | the server |
LocalOnly (1.x content with bWithReplication off) | the user’s machine | the user’s machine |
| no object (no MoveHandler on the side, no Action for the state) | — | the user’s machine |
A use of a character on the server (a listen host’s own character, an AI) has one machine that leads every step: the server. A standalone game runs everything on its one machine, as before.
The leader goes on with the use on its own: when its movement finishes, the action starts; when its action finishes, the use ends. A machine that does not lead a step runs its own copy of it and waits for the leader:
- At the leader’s action phase, a movement that still runs there is finished (
MovementFinishedfires there; 1.x did the same throughMulticastFinishMovement), then the action starts there. - At the leader’s end, an action that still runs there finishes, not interrupted (as 1.x
MulticastFinishAction), and the use ends. An action that already finished by itself there is not finished again. - At the leader’s interruption, the use is interrupted there:
MovementAborted(aSharedPerObjectMoveHandler also after its movement finished, as in 1.x) and the action finishes as interrupted. - An Action that finishes by itself on a machine that does not lead it (for example an
AllMachinesAction whose timer also runs on the server) no longer ends the use there; the use ends with the leader’s end. It is finished there once per use: itsOnUseFinished,OnInteractionObjectActionFinishedDelegate,NewStateAfterFinishActionand, on a client,OnObjectStateChangedDelegaterun once there (1.x ran them a second time atMulticastFinishAction).
An Action that finishes only on the server
Section titled “An Action that finishes only on the server”With AllMachines (all 1.x content) the user’s machine leads. A FinishAction that only the server calls finishes only the server’s copy, and the use goes on until the user’s machine finishes its own copy, as in 1.x (where the user’s client never heard of it). If your Action finishes only on the server (for example after a server-side check or a server timer), choose ServerOnly or LocalPredicted: then the server’s finish ends the step for every machine (the user’s client gets it through the server’s phases).
Choosing a policy
Section titled “Choosing a policy”AllMachines: cosmetic or 1.x-style Actions and MoveHandlers that every machine plays and the user’s machine finishes (animations, sounds, a move to the object).LocalPredicted: movement or an Action that should start at once on the user’s machine and also run on the server, which can finish it first (for example root motion that the server must apply too). Other clients show nothing of it.ServerOnly: gameplay that only the server may decide (giving items, opening a door for everybody). The user’s machine waits for the server’s phases.LocalOnly: something only the user’s machine needs (a local camera move, UI). Such an Action’sNewStateAfterFinishActionis applied on the user’s machine only and never reaches the server: leave it off, and set the object’s state on the server instead.
Conditions on the server: ServerValidation
Section titled “Conditions on the server: ServerValidation”ServerValidation of a Condition decides whether the server evaluates it again when a remote client asks to use an object (Validate), or trusts the client (DoNotValidate). Default resolves to Validate on a Blueprint class and to DoNotValidate on a C++ class. C++ reads the resolved value with ShouldValidateOnServer().
A Condition that the server validates runs on the server without a data object: CheckCondition gets the server’s character, its interaction component, the object and the requested side. A Condition that reads the data object, or state that only the user’s machine has (UI, input, owner-only variables, whether the object is rendered on the client’s screen), must use DoNotValidate. Conditions saved with 1.x load with DoNotValidate, and C++ Conditions default to it.
Who is affected: Conditions created as Blueprints with 2.0, and old ones that you switch. What to do: set DoNotValidate on Conditions that depend on client-only state; keep Validate on Conditions that the server can check (distance, line of sight, inventory on the server).
Networked games: the server checks every use
Section titled “Networked games: the server checks every use”A remote client no longer runs its uses on its own word: the server checks every use request of a remote client and confirms or rejects it. The client still starts the use at once (prediction); a rejected use ends as interrupted. A standalone game, a listen host’s own character and characters that the server runs (AI) are never checked: their uses behave as before.
How a remote client’s use runs
Section titled “How a remote client’s use runs”TryUseObjectstarts the hold. When the object’s use time is above 0, the client tells the server that the hold started (ServerNotifyHoldStarted).- When the hold completes, the client sends the use request
ServerRequestUse(the object, the side index, the action index, and the use’sExecutionIdas the prediction key) and starts the use at once.GetActiveUsePhaseisPendingonly while the request is sent, thenMoving. - The server checks the request (see Checks) and answers:
- Confirmed (
ClientConfirmUse): the server records the use: the object’s use state (GetUseState),IsObjectUsing, the character’sGetCurrentUsingObject, and, withbTransferOwnershipDuringUse, the client’s controller as the owner of the object’s actor. Then it runs the use itself (see Networked games: every machine runs the use). The client’s use goes on. - Rejected (
ClientRejectUsewith anECISUseRejectReason): the client’s use ends as interrupted. The MoveHandler is aborted, the Action finishes withbWasInterruptedtrue,WasRejectedByServer()is true on both, then comeOnObjectUseFinishedwithbInterruptedtrue andOnEndUseObjectDelegate, and the client gives the object’s actor its previous owner back. The data object’sInterruptUsingis not called for a rejection. The server keeps nothing of a rejected use.
- Confirmed (
- When the use ends on the client, the client tells the server (
ServerUsePhasewithEnding). The server ends its run of the use, clears the use state,IsObjectUsingandCurrentUsingObjectand gives the object’s actor its previous owner back.
Every rejection carries the object’s state and usable flag on the server, and the client’s copy of the object takes them, also when the use has already ended on the client. A late rejection (for example of a use whose Action finished at once and set its NewStateAfterFinishAction before the answer arrived) ends nothing more, but what the rejected use changed on the client’s copy of the object goes back to the server’s values. A late confirmation is ignored.
The server sets the user’s GetCurrentUsingObject when the request arrives, which is after the client has broadcast OnStartUseObjectDelegate. 1.x sent ServerSetCurrentUseObject with the object before that broadcast. A Server RPC that a listener of OnStartUseObjectDelegate sends leaves before the request, so inside it GetCurrentUsingObject on the server is still null. Who is affected: content that reads GetCurrentUsingObject on the server inside such an RPC. What to do: pass the object as a parameter of your RPC.
Who is affected: networked games. What to do: nothing for content that a client could use in 1.x from where its character stood on the server. Check the tolerances below when your game lets characters use objects from farther away, at wider angles or faster than the server sees it (for example right after a teleport).
Checks
Section titled “Checks”The server checks with what it has when the request arrives: positions, the object’s state and usable flag, its use state, and the hold it was told about. In this order; the first failure is the reason:
| Reason | The request fails when |
|---|---|
ObjectInvalid | The object, its actor or the character is missing, the object’s actor is being destroyed, or they are in different worlds. |
NotUsable | IsObjectUsable() is false on the server. |
Occupied | Another character uses the object: its use state names another character, or IsObjectUsing() is true. |
NoActionForState | The object’s state on the server chooses another action of the side than the client’s, or the side on the server has no action at the requested index. |
BadSide | The character is outside the requested side, with AngleToleranceDegrees, or the object on the server has no side at the requested index. |
OutOfRange | The character is farther than InteractionDistance * (1 + DistanceToleranceScale) + DistanceToleranceAbsolute. |
BadAngle | The character faces away from the object’s angle detection point by more than half its interaction angle plus AngleToleranceDegrees. |
ConditionFailed | A visible or usable Condition that is validated on the server fails (ServerValidation: Validate, or Default on a Blueprint class). |
HoldTooShort | The use time is above 0 and the hold, measured on the server’s clock from the hold notice to the request, is shorter than the use time minus HoldTimeTolerance. |
The server checks the requested side and action indices first, right after the object, its actor and the character are found (before the other checks of the table), and also with bServerValidation off: a side index that the object on the server does not have is BadSide, an action index that its side does not have is NoActionForState. The settings are not replicated, so a client’s copy of the object can have sides or actions that the server’s does not (see InteractionSettings is not replicated).
Project Settings
Section titled “Project Settings”The tolerances and the switch are project settings: Project Settings > Plugins > Character Interaction System (UCISNetworkSettings, saved in DefaultGame.ini under [/Script/CharacterInteractionSystem.CISNetworkSettings]). They are the same for every object; there are no per-asset tolerances.
| Setting | Default | Meaning |
|---|---|---|
bServerValidation | true | Off: the server confirms every request, also for an object that another character uses (as 1.x), with three exceptions: it still rejects ObjectInvalid (no object, no object actor or no character), a side or action index that the object on the server does not have (BadSide, NoActionForState), and Occupied while the server itself runs a use of the same character. When two characters then use one object at the same time, each use holds the owner of its actor: the owner is the controller of the latest use that still runs, and the owner before the uses comes back after both. |
DistanceToleranceAbsolute | 100 | Centimetres beyond the object’s InteractionDistance. |
DistanceToleranceScale | 0.5 | Fraction of the object’s InteractionDistance added to it. |
AngleToleranceDegrees | 30 | Degrees added to the half angle of the side and to the half facing angle. |
HoldTimeTolerance | 0.3 | Seconds a hold may be shorter on the server’s clock (about one frame plus jitter). |
When the server rejects a use
Section titled “When the server rejects a use”A rejected use ends on the client as interrupted, with WasRejectedByServer() true on its Action and MoveHandler. Both machines log it in the category CISLog (verbosity Log):
- the server: “ServerRequestUse from <character>: the use of <object’s actor> is rejected (ECISUseRejectReason::<reason>), key <key>.”;
- the server, for a key that a use of this character on the server has taken (
Occupied, see Keys): “ServerRequestUse from <character>: key <key> was taken by a use of this character on the server (last <last key>). Rejected.”; - the client: “ClientRejectUse on <character>: the server rejected use <key> (ECISUseRejectReason::<reason>).”;
- the client, for a use whose key the server had taken already when the client was given the pawn (see Keys): “ClientSyncExecutionKeys on <character>: the server had taken key <key> already (last <last key>); the use ends as rejected.”
What to do, by reason:
NoActionForState: the client and the server disagree about the object’s state. The usual cause is content that changes the object’s state only on a client:SetObjectStateon a client changes only that client’s copy (and logs the warning of Object state and usable flag on a client), so the server does not find the client’s Action for its own state. Such content already went out of sync in 1.x multiplayer. Change the state on the server.NotUsable: the same for the usable flag: callSetUsableon the server.OutOfRange,BadAngle,BadSide: the server sees the character elsewhere or facing elsewhere than the client did. RaiseDistanceToleranceAbsolute,DistanceToleranceScaleorAngleToleranceDegreeswhen your game uses objects from farther away or right after a teleport. The server also checks with its own values of the character’s and the object’s settings, so values changed only on a client are rejected (see Known limitations).HoldTooShort: the hold that the server measured is shorter than the use time minusHoldTimeTolerance. RaiseHoldTimeTolerancefor long frames or lossy links.ConditionFailed: a Condition that the server validates fails there. SetDoNotValidateon Conditions that read client-only state (see Conditions on the server).Occupied: another character uses the object (or the server runs a use of this character). Expected when two players press at the same time: the first request wins.ObjectInvalid: the object or the character is gone, or is being destroyed.
To find out whether validation is the cause, turn bServerValidation off for a test: the server then confirms every request, as 1.x did. Do not ship with it off: without it a client can use any object from anywhere.
Characters on the server
Section titled “Characters on the server”A standalone game, a listen host’s own character and characters that the server runs (AI) send no request and are never checked. Their uses set the object’s use state too, so the server rejects a remote client’s request for an object they use (Occupied). Their ExecutionIds continue after every id that the server has seen for the same component (the request keys of its client and its own uses’ ids), and a request whose key such a use took is rejected (Occupied).
Malformed requests
Section titled “Malformed requests”Malformed requests disconnect the client: a key below 1 or above CIS::MaxExecutionId (MAX_int32 / 2), or an object of another world. Requests that only fail a check (state, position, timing) are rejected, not disconnected. So is a request for a side or an action index that the object on the server does not have (BadSide, NoActionForState; the settings are not replicated, so a client’s copy can differ); a hold notice for such a side is recorded as any hold. The 1.x RPCs keep accepting every call (their validation still returns true).
Networked games: every machine runs the use
Section titled “Networked games: every machine runs the use”The server runs a confirmed use itself and sends its phases; the clients that have the object’s actor show the use; the 1.x Server and Multicast relays of the Action and the MoveHandler are no longer sent. Who runs and leads each step: NetExecutionPolicy. A standalone game behaves as before.
How the phases travel
Section titled “How the phases travel”- The user’s client sends
ServerUsePhasewhen its action starts and when its use ends. The server applies them to its run of the use. It ignores the action phase while it leads the movement itself (aServerOnlyMoveHandler), and it applies an end that is not interrupted as interrupted while only the server leads the current step (aServerOnlyobject; the client ended the use early). - The server multicasts every phase of its run through the used object (
UCISInteractionObjectComponent::MulticastUsePhasewith anFCISUsePhaseEvent: the character, theExecutionId, the side and action indices, the phase, interrupted). To the user’s client it also sends the phases that its run reached by itself (ClientUsePhase), for example the end of aServerOnlyAction, or an end that the server forced. - A client that shows a use starts it at the multicast of its first phase: an execution with
IsSimulatedExecution()true and no data object. It runs the MoveHandler and the Action set toAllMachinesand follows the later phases. It does not run the data object’s events (they stay on the user’s machine). - A client that gets the object while a use of it runs (the object was not relevant to the client when the use started, or the client joined late) shows nothing of that use: it sees the object’s state (
IsObjectUsing,GetUseState), but the Action and the MoveHandler do not run there. A phase whose character a client does not know (the character is not relevant to it) is skipped, with aVerboseline inCISLog, without a crash. - The replicated use state (
GetUseState) starts nothing on a client. - On the server and on the clients that show the use,
GetActiveUsePhaseof the user’s component is the phase of their own run of it; the object’sOnUseStartedDelegate,OnActionStartedDelegateandOnUseFinishedDelegatefire there too. On the server the use is released beforeOnUseFinishedDelegatefires; on a client that shows the use,IsObjectUsingandGetUseStateare the replicated values there and can still name the user (see Object delegates).
Who is affected: networked games, content that counts on the timing of the relays, and C++ that overrides the relays. What to do:
- Nothing for most 1.x content: on the server and on the other clients the MoveHandler and the Action get the same events in the same order as with the relays, about half a round trip later than on the user’s machine, as before, except that an Action that finishes by itself there (for example a Blueprint Action that calls
FinishActionon every machine, or a timer that also runs there) is finished once per use instead of twice (see NetExecutionPolicy). ServerOnlyandLocalPredictedobjects of a remote client’s use run on the server.InstancedPerExecutionobjects run a fresh copy on every machine that runs the use.- C++ overrides of the relays’
_Implementationfunctions (MulticastStartObjectAction,MulticastFinishAction,MulticastStartMovement,MulticastFinishMovement,MulticastMovementAborted,MulticastInvalidateHandlerand theirServerversions) are no longer called by the plugin’s uses. Move such code to the Action’s or MoveHandler’s own events, which now run on every machine that runs the use. The relays stay, deprecated, for code that calls them itself;MulticastStartObjectAction,MulticastStartMovementandMulticastMovementAborteddo nothing on a client that gets a null character (one to which the character is not relevant). - A use without an execution (a C++
UseObjectoverride withoutSuper, the 1.x way of running a use) runs on the user’s machine only, and sends no request: the server never records it.
Ends that the server forces
Section titled “Ends that the server forces”The server’s run of a remote client’s use ends as interrupted when:
- the client’s pawn is destroyed (also when the client disconnects);
- the pawn’s controller changes (the client possesses another pawn, or the pawn is unpossessed), one tick after the change;
- the object’s actor is destroyed;
- the object’s
MaxUseDurationelapses (the server limits remote uses on its own clock); - a new request of the same client arrives while the run still runs.
The server then clears the use state, IsObjectUsing and CurrentUsingObject and gives the object’s actor its previous owner back, also when only the object component was destroyed during the use. The user’s client gets the end (ClientUsePhase) while it still controls the pawn; after a pawn change that client ends its use at the server’s multicast, and the pawn’s new owner gets no ClientUsePhase of that use. On the user’s machine such an end interrupts the use’s execution (UCISUseExecution::Interrupt), as a rejection does: the data object’s events fire, but its InterruptUsing is not called, so a C++ override of it does not see the end. A client that shows a use ends it with the user’s pawn, with the object’s actor, one tick after its copy of the object component is gone, or when it is given the user’s pawn (at the latest one tick after the pawn change); then the new owner can use objects with that pawn at once.
Ownership of the object’s actor
Section titled “Ownership of the object’s actor”- With
bTransferOwnershipDuringUse(default true) the user’s controller owns the object’s actor during the use: the user’s client sets it at once when it predicts the use, and the server sets it when it confirms the use. Server RPCs that your Action or MoveHandler sends on the object’s actor itself (a Blueprint “Run on Server” event) need this: keep it true for such content. The plugin’s own use needs no ownership (the server runs it and sends its phases through the object). With it false the use leaves the owner alone. - The owner is given back at the end of a use, on every machine: the use remembers the previous owner of the object’s actor and restores it, but only while the actor’s owner is still the controller that the use set. An owner that game code gives the actor during the use is kept. In 1.x the user’s controller stayed the owner on the server (and in a standalone game) after every use: the plugin cleared the owner locally and then called
ServerSetOwnerOfActorwithoutbClear, which set the controller again. - The owner of the object’s actor is held per use. When two uses of one object overlap (possible with
bServerValidationoff), the owner is the controller of the latest use that still runs, and the owner before the uses comes back after both. ServerSetOwnerOfActorcalled by a client changes only the owner of the actor of the object that the server has confirmed for that character, while that use runs. Any other actor logs the warning “ServerSetOwnerOfActor from <character>: <actor> is not the actor of the object this character uses; a client may only change the owner of that actor. Ignored.” and nothing changes. 1.x let any client take over any actor. Called on the server outside a received client RPC (a standalone game, a listen host, server code that no client RPC runs) it is not limited. The check isUE::Net::FNetContext::IsInsideNetRPC(): server code that runs inside any received client RPC is limited too, for example your ownServer…_Implementation.ServerSetCurrentUseObjectcalled by a client accepts only null or the object that the server has confirmed for that character (a call on the server outside a received client RPC is not limited, as above); anything else logs “ServerSetCurrentUseObject from <character>: <object> is not the object this character uses. Ignored.”. Null from a client no longer ends the server’s run of that client’s use (its end isServerUsePhase).- The plugin no longer calls
ServerSetOwnerOfActorfor its uses, norServerSetCurrentUseObject: the server sets both when it confirms a use.SetOwnerOfActor,ServerSetOwnerOfActorandServerSetCurrentUseObjectare deprecated and kept for C++ code (see C++ API). C++SetOwnerOfActor(Actor, true)now passesbClearto the server. The plugin’s only call left is the 1.x way of running a use: a use without an execution (a C++UseObjectoverride withoutSuper) still ends through the data object’sFinishUseObject, which callsSetOwnerOfActor(Actor, true)and soServerSetOwnerOfActor(refused from a remote client).
Who is affected (C++): code that calls ServerSetOwnerOfActor or SetOwnerOfActor on a client for another actor (now refused); code that relied on the controller staying the owner after a use; and C++ overrides of UseObject that run the use without Super on a remote client: such a use sends no request, so the server never records it, refuses its ServerSetOwnerOfActor (warning), and the use runs on the user’s machine only. What to do: call Super in UseObject (the plugin’s use, with a request), or start such uses on the server; to give an actor an owner, call AActor::SetOwner on the server.
Object state and usable flag on a client
Section titled “Object state and usable flag on a client”SetObjectState and SetUsable called on a client’s copy of an object (a simulated proxy: an interactable stays one on a client, also while that client’s controller owns its actor during a use) change only that copy, as in 1.x (SetObjectState also broadcasts OnObjectStateChangedDelegate there), and now log once per object component the warning “<function> on <actor> called on a client; the change is local only. Call it on the server.” in CISLog. The plugin’s own calls (the replicated state arriving, an Action’s NewStateAfterFinishAction) do not log.
Who is affected: content that sets the state or the usable flag on a client, for example from a widget; its next use of the object can be rejected (NoActionForState, NotUsable). What to do: call them on the server (through a Server RPC of your own), or ignore the warning where a local-only change is intended.
Use state and IsObjectUsing
Section titled “Use state and IsObjectUsing”UCISInteractionObjectComponent::GetUseState (Blueprint-pure) returns FCISObjectUseState: User (the character, null when nobody uses the object), ExecutionId, SideIndex and Phase of the use the server knows. It is replicated. C++ also gets IsObjectOccupied and SetUseState_Authority.
- A character’s data object treats an object as in use, on every machine, also while the object’s use state names another character (1.x: only while
IsObjectUsing()is true). The object is then neither visible nor usable for that character. The server sets and clears the use state in the same frame asIsObjectUsing, so on the server and on the observing clients the timing is that of 1.x, and a user’s own use state never blocks the user. It matters on a client whose own use of the object was just rejected (Occupied): the use state keeps the object unavailable to it while the other use runs. - The plugin writes
IsObjectUsingon each machine itself: the user’s client no longer sendsServerSetObjectIsUsingfor its use (the server sets its own value when it confirms the use), and the end of a use does not write false while the use state names another character. - The user’s machine keeps its use’s
IsObjectUsingand the owner that its use set while the server’s late state of its previous use arrives (a quick second use with network lag).
Who is affected: content that reads IsObjectUsing on a client to decide that an object is free. What to do: read GetUseState too.
A character component has one ExecutionId space on the server and on its client; ExecutionId increases with every use. A use that the server runs itself for a remote player’s character takes an id after every key the server has seen for that component, and a request with a key that such a use took is rejected (Occupied). The client’s next key comes after every id that the server’s phases showed it, and after the last key that the server had seen for the character when the client was given it: when a remote player’s controller takes a character, the server sends that client the character’s last key (ClientSyncExecutionKeys, one tick after the change). A use of that client that still waits for the server’s answer and has a key up to that one ends as rejected (the server has taken that key). CIS::MaxExecutionId (MAX_int32 / 2) is the largest key: ServerRequestUse and ServerUsePhase with a larger key are malformed and disconnect the client.
Movement prediction
Section titled “Movement prediction”A remote client now sends the server the movement that its UCharacterMovementComponent held back to combine with the next one (FlushServerMoves) right before it sends the request of a use, as the Gameplay Ability System does before a LocalPredicted ability. The server handles that movement first and the request after it, so a MoveHandler that runs on the server (AllMachines, LocalPredicted or ServerOnly) and applies root motion (for example through UCISRootMotionMovementTask) starts on the server with the same movement step as on the client. Before, when the client had held a movement back at the start of the use, the server applied the root motion to that movement as well, which the client had made without it, and then corrected the client’s movement.
Measured in single runs on one machine, with 100 ms of packet lag each way and a 1 s root-motion movement at 200 cm/s: with the client’s movement send interval raised to 0.1 s, so that a movement of about 0.06 to 0.09 s is held back at the press, 3 corrections per use without the flush (the same code with the flush removed, one run) and 0 with it (two runs). With the engine’s default send interval (about 16.6 ms; a held movement is then at most about one frame) no run held a movement at the press, and all had 0 corrections with and without the flush, so they say nothing about the flush.
Who is affected: networked games in which remote clients use objects. What to do: nothing. A held movement update now leaves before the request instead of after it, so the client may send one more small movement RPC per use; the server sends nothing more.
Known limitations
Section titled “Known limitations”- The hold is measured on the server’s clock. With frames longer than
HoldTimeTolerance, a hold of exactly the use time can be rejected asHoldTooShort. - A hold notice that is sent again after packet loss reaches the server about one round trip late, so the hold that the server measures is shorter by that much. On a link whose round trip under loss is longer than
HoldTimeTolerance, real holds can be rejected asHoldTooShort. - The server checks with its own values: the character’s
TimeToUseObjectandInteractionAngle, and the object’s settings and overrides (InteractionDistance, the sides,OverrideObjectUseTime,OverrideCharacterInteractionAngle). A game that changes them on the client only gets its requests rejected (HoldTooShort,OutOfRange,BadAngle,BadSide,NoActionForState). - A remote client that uses the same object again within about one round trip after its previous use of it ended gets the server’s late state of the first use (no owner, not in use) during the new use. The client puts back
IsObjectUsingand the owner that its use set in the frame that state arrives, before its timers run, so the Server RPCs that its Action or MoveHandler sends on the object’s actor leave with the owner. What is left: during its own use, the client’s copy of the object ignores an owner that the server gives the object’s actor, and after the use the copy keeps the owner from before the use until the server changes the owner again (the server’s value stays). - A use that the server runs itself for a remote player’s character is not shown on that player’s machine (the user’s machine runs only its own uses).
- A request that the client sends before the server’s phases of such a use reach it can be rejected (
Occupied) once. - When only the object component is destroyed during a use, no end reaches the clients through it; a client that shows the use ends it one tick after its copy of the component is gone.
- With
bServerValidationoff and overlapping uses, the use state andIsObjectUsingfollow the latest use. - A client’s end of use reaches the server only while the client still controls the pawn; after a pawn change the server ends its run itself.
- Under packet loss, a client that shows uses can get the start of a character’s next use (of another object) before the end of its previous one: it ends the shown previous use as interrupted (
MovementAborted, or the Action finished as interrupted there) and ignores the late end. Only that client’s display is affected. - A client that is given a pawn gets the pawn’s last key from the server one tick after the change (see Keys). A use that the client starts with that pawn before the key arrives can send a key that the server has seen already; the server ignores that request (or rejects it as
Occupied, which restores the object’s state). If the use still runs when the key arrives, it ends as rejected. If the server ignored the request and the use has already ended (an Action that finishes at once), what its Action changed on the client’s copy of the object (itsNewStateAfterFinishAction) stays there until the server changes the object again. - Every rejection carries the object’s state and usable flag on the server, and the client’s copy of the object takes them (see How a remote client’s use runs). When the server changes the object right after the rejection and, under packet loss, that change reaches the client before the rejection, the client’s copy takes the older values of the rejection and keeps them until the server changes them again.
Replication cost
Section titled “Replication cost”An interaction object that nobody uses now sends almost nothing to the clients. In 1.x every object wrote to its actor’s channel at every net update of the actor, whether or not anything had changed. The changes below concern networked games only; a standalone game behaves as before.
Which Actions and MoveHandlers are replicated
Section titled “Which Actions and MoveHandlers are replicated”An Action or MoveHandler set on an interaction object (a template in InteractionSettings) is replicated to the clients only when all of these hold:
- it runs
SharedPerObject(see Default); - its
bWithReplicationis on; - its class has RPCs or replicated properties of its own: a Blueprint or C++ class below the plugin’s Action or MoveHandler classes. The plugin’s own fields (the deprecated relays,
bReplicatedBool) do not count.
Every other template is no longer replicated: each machine uses its own copy, built from the same class defaults, Blueprint or level data, as InstancedPerExecution objects do. The object component still replicates its own state.
- The deprecated relays (
ServerFinishAction,MulticastFinishActionand the others) that your code calls itself still reach a template that is not replicated: a template of a class default, a Blueprint or a placed actor has the same name on every machine. A template that game code creates at runtime has no such name; call the relays only on templates that come from the object’s settings. bReplicatedBoolof a template that is not replicated no longer reaches the clients. A class that needs a replicated value declares a replicated property of its own (it is then replicated as before).- A template with
bWithReplicationoff is never replicated (1.x replicated it, and a server could stop at the check that the object is supported for networking). - When the object’s actor uses the registered subobject list (
bReplicateUsingRegisteredSubObjectListon the actor), the object component joins the list inBeginPlayon the server and registers the templates that replicate;EndPlayremoves them. That list is fixed atBeginPlay: a Blueprint recompiled during a play session, or a template that C++ code puts intoInteractionSettingsafterBeginPlay, is not registered until the object begins play again (without the list, the component asks again at every replication). Do not setbReplicateUsingRegisteredSubObjectListon the object component yourself while its actor does not use the list: the actor’s channel then ignores the component’s templates.
Who is affected: content that reads replicated variables of a plain Action or MoveHandler on a client, and C++ that overrides ReplicateSubobjects of the object component. What to do: give the class a replicated property or an RPC of its own (it then always runs SharedPerObject and replicates); a C++ override calls Super and keeps its own |=.
Push model
Section titled “Push model”ObjectState, bObjectIsUsing (IsObjectUsing) and UseState (GetUseState) replicate through the push model when the project turns it on (Net.IsPushModelEnabled, off by default in UE 5.6; off, nothing changes). The plugin marks them dirty at every write. bUsable does not use the push model: Blueprints write it directly.
Who is affected: C++ subclasses that write these protected fields directly. What to do: call the setters (SetObjectState, SetObjectIsUsing, SetUseState_Authority), or call MARK_PROPERTY_DIRTY_FROM_NAME after the write.
InteractionSettings is not replicated
Section titled “InteractionSettings is not replicated”Every machine reads its own copy of InteractionSettings, built from the same class defaults, Blueprint or level data as the server’s. InteractionSettings is edited in the class defaults and read-only for Blueprints (GetSettings), so only C++ code can change it at runtime.
What each machine reads from its own copy:
- a client, for its own character: the sides, angles,
InteractionDistance,AngleDetectionComponent, the use-time and angle overrides,ObjectPriorityand the data object class (the data object decides from them whether the object is visible and usable and how long the hold is), the visible and usable Conditions, the Actions and the MoveHandler of the use,ObjectInterruptionType, andbTransferOwnershipDuringUse(the client sets the owner when it predicts a use); - the server only:
MaxUseDuration(only the authority applies it: the server, a listen host, a standalone game),bManageOwnerDormancy, and the server check of a request, which uses the server’s copy of the sides, distances, overrides and Conditions (the tolerances come from Project Settings, which also only the server uses).
What changes:
- A change of
InteractionSettingsthat the server makes at runtime no longer reaches the clients. Make such a change on every machine. A change on the server only of the values the server check uses (for example the sides orInteractionDistance) makes the client’s own decision and the server’s check disagree: the client sees the object as usable and the server rejects the request (BadSide,OutOfRange,BadAngle,HoldTooShort). A change on the server only ofMaxUseDurationapplies; the clients never read it. - The Conditions of an object that the server spawns at runtime are no longer null on the clients. In 1.x the settings arrived from the server with their Conditions as null references, so a client never evaluated them; now each client evaluates its own copy’s Conditions for its own character, as for a placed object. A Condition that must not run on a client checks where it runs (for example
HasAuthorityof the object’s actor).
Owner dormancy (bManageOwnerDormancy)
Section titled “Owner dormancy (bManageOwnerDormancy)”bManageOwnerDormancy of FCISInteractionObjectSettings (off by default) lets the object’s actor rest dormant while nobody uses it, so it costs nothing on the network. It takes effect on the server:
- At
BeginPlaythe object’s actor goes dormant (DORM_DormantAll) when it is awake. A dormancy that the actor has from its author is kept:DORM_Neveralways;DORM_InitialandDORM_DormantPartialuntil the first use, after which the actor restsDORM_DormantAll. - A use wakes the actor (
DORM_Awake) before anything of the use replicates. 1 s after the end of the use the actor goes dormant again, unless another use or a hold of the actor runs then (it waits another second). A use within that second keeps it awake. - A change of the object state, of the usable flag or of
IsObjectUsingthrough the plugin’s setters while the actor is dormant reaches every client at once (FlushNetDormancy), and the actor stays dormant. A flush makes aDORM_InitialactorDORM_DormantAll(engine behaviour). - A client that joins later, or for which the actor becomes relevant later, gets the actor with its current state once.
When to turn it on: objects whose actor replicates (bReplicates; without it, or in a standalone game, the setting does nothing) and whose only replicated data is the plugin’s (the object state, the usable flag, the use), such as doors, switches and pickups, in levels with many of them.
When not to turn it on:
- the object’s Blueprint or C++ (the actor, its components, or its Actions and MoveHandlers) replicates variables or sends multicasts of its own: they do not leave a dormant actor;
- its Blueprints write
bUsabledirectly instead of callingSetUsable(no flush); - other code sets the dormancy of the object’s actor;
- its Action or MoveHandler sends a Server RPC on the object’s actor right at the start of a use: a client drops it while it has no channel for the dormant actor, that is, until about one round trip after its request.
Smaller changes
Section titled “Smaller changes”- A MoveHandler ticks only while its movement runs (from its start to the end of its use), and never as a class default or an archetype (
IsTickableandGetTickableTickTypeofUCISInteractionObjectMoveHandler). ItsHandlerTickalready ran only during a movement; only the idle ticks of templates are gone. Who is affected: C++ subclasses that overrideTickto act beforeStartMovementor afterInvalidateHandler(which ends the movement): that code no longer runs; and subclasses that overrideIsTickableorGetTickableTickType, which should keep class defaults and archetypes from ticking (IsTemplate()). UCISReplicatedObject::CallRemoteFunctionfinds the actor among the object’s outers and returns false when there is none (1.x: a fatal error for an object without an actor two outers up).
Measured cost
Section titled “Measured cost”All numbers are from single runs on one machine (a development PC, editor build, about 45 to 60 server frames per second) and vary with the frame rate and the machine. Where two runs exist, both are given. The plugin’s own tests check thresholds, not these values: at most 0.2 times the 1.x bunches and 0.5 times the 1.x bytes, per second and per server frame (with dormancy 0.1 times the bunches).
- 300 idle objects, a dedicated server and two clients: 34.4 to 37.1 bunches/s and 3904 to 5053 bytes/s out of the server, 0.62 to 0.76 bunches and 84 to 86 bytes per server frame (1.x: 22540.3 bunches/s, 94751 bytes/s, 375.7 bunches and 1579 bytes per frame). The same scene without objects measured the same within 0.2 bunches/s, so the idle objects themselves send about nothing. With
bManageOwnerDormancy: 34.8 to 37.1 bunches/s, 3908 to 4899 bytes/s. - One remote use with two clients: reliable RPCs out of the server 7 (1.x 10) and into it 3 (1.x 5); out bunches per use 7.53 to 13.25 (1.x 8.74) at 229 to 294 bytes per use (1.x 196), in two runs on the same server code. Per remote use the client sends
ServerRequestUseand twoServerUsePhase, and the server sendsClientConfirmUseto the user and threeMulticastUsePhaseto each client that has the object. - The use state: three updates per client and use (the start with user, key and side; the action phase; the clear), about 31 bytes (249 bits) of property data per client and use. This is an estimate from the observed updates and the serialized sizes of the changed fields, without bunch and packet headers.
A use from its start to its end
Section titled “A use from its start to its end”Executions
Section titled “Executions”A use now runs through an execution, UCISUseExecution (Execution/CISUseExecution.h). The character’s UCISCharacterInteractionComponent::StartUseExecution creates it when a hold completes. It runs the MoveHandler and the Action of the use side in the order of the side’s ActionExecutionType, calls the data object’s events in the 1.x order (OnObjectUseStarted, OnMovementStarted, OnMovementEnded, OnObjectActionStarted, OnObjectUseFinished), and ends the use. With 1.x content (SharedPerObject) a use calls the same events in the same order as in 1.x; the exceptions are listed under Behaviour changes for every use. In a networked game the server and the clients that show the use run their own execution (see Networked games: every machine runs the use).
Entry points for C++
Section titled “Entry points for C++”The virtual functions of UCISInteractionDataObject stay the entry points, and C++ overrides of them keep working:
UseObject(called when the hold completes) starts the execution throughUCISCharacterInteractionComponent::StartUseExecution, when the character can use the object.InterruptUsing(called byTryInterruptObjectUsingand when the data is removed) interrupts the execution (UCISUseExecution::Interrupt).FinishUseObjectends the execution (UCISUseExecution::End).- The execution calls
OnMovementToObjectFinished,OnObjectActionUseFinishedandFinishUseObjectat the points where 1.x called them, so overrides of these run as before.StartObjectActionstarts the execution’s action. - The execution never calls
UseObjectorInterruptUsing.
What changed for C++:
- The 1.x body of
UseObjectis gone: the execution runs it. An override that callsSuper::UseObjectand then readsCurrentActionorCurrentMoveHandlergets the objects that run (copies forInstancedPerExecution). They are null when the use has none, or when it ended insideUseObject. - An override of
UseObjectthat does not callSuperand runs a use itself, with the 1.x code, still works on its machine:InterruptUsing,StartObjectAction,OnMovementToObjectFinishedandFinishUseObjectkeep their 1.x code for a use without an execution. Such a use has no execution: its phase isNone,MaxUseDurationdoes not apply to it, and in a networked game it runs on the user’s machine only (see Ownership of the object’s actor). - Ends that the server forces, and rejections, interrupt the execution without calling
InterruptUsing(see Ends that the server forces).
Execution context
Section titled “Execution context”Actions and MoveHandlers (UCISReplicatedObject) have Blueprint-pure getters in the category “CIS|Execution”:
| Getter | Returns |
|---|---|
GetExecutionContext | FCISExecutionContext: Character, CharacterComponent, ObjectComponent, SideIndex, ActionIndex, ExecutionId, bIsServer, bIsOwner, bIsSimulated, bRejectedByServer |
IsExecutingOnServer | bIsServer: the server, or a standalone game |
IsLocallyPredicted | bIsOwner and not bIsServer: a client that runs its own use ahead of the server |
IsSimulatedExecution | bIsSimulated: a machine that only shows another player’s use |
WasRejectedByServer | bRejectedByServer |
The context is set when the use starts, before OnObjectUseStarted, and stays until the use ends, on every machine that runs the use: on the server bIsServer (with bIsOwner false for a remote client’s use), on a client that shows another player’s use bIsSimulated. Then a SharedPerObject object gets a default context again (ExecutionId 0, no character). A copy keeps its context. ExecutionId increases with every use of the character’s interaction component. The 1.x event parameters and Cached* fields stay and are filled as before.
Object delegates
Section titled “Object delegates”UCISInteractionObjectComponent has three new Blueprint-assignable delegates in the category “CIS|Delegates”. They use the delegate types that 1.x declared but never used:
| Delegate | Fires |
|---|---|
OnUseStartedDelegate (character, character component, object component) | after the data object’s OnObjectUseStarted, before the MoveHandler starts |
OnActionStartedDelegate (action, character, character component, object component) | after the data object’s OnObjectActionStarted, before the Action’s StartInteractionObjectAction. The action is the object that runs (a copy for InstancedPerExecution). |
OnUseFinishedDelegate (character, character component, object component, interrupted) | after the data object’s OnObjectUseFinished and the release of the object, before the character’s OnEndUseObjectDelegate. Not when the object component itself is gone. On the server, in a standalone game and on a listen host IsObjectUsing is false and GetUseState no longer names the use then; on the user’s client IsObjectUsing is false; on a client that shows the use both are the replicated values and can still name the user. |
They fire on every machine that runs the use: the user’s machine, the server and the clients that show the use.
OnUseFinishedDelegate fires only for a use whose OnUseStartedDelegate fired. A use that a listener of the data object’s OnObjectUseStarted ends (TryInterruptObjectUsing, destroying the object) fires no object delegate at all. One that a listener of OnObjectActionStarted ends fires OnUseFinishedDelegate (interrupted) but not OnActionStartedDelegate.
Use phase
Section titled “Use phase”UCISCharacterInteractionComponent::GetActiveUsePhase (Blueprint-pure, category “CIS|CharacterInteractionComponent”) returns the phase of the character’s use (ECISUsePhase):
| Phase | When |
|---|---|
None | No hold and no use. Also on a component whose owner is not a character. |
Holding | From TryUseObject until the hold completes and the use starts, or until the hold is cleared (OnClearDelayedObjectData). Inside OnClearDelayedObjectData the phase is already None, while IsHasDelayedObjectData() is still true there, as in 1.x. |
Pending | On a remote client, only while it sends its use request (see How a remote client’s use runs). |
Moving | The use has started; its MoveHandler runs, or the movement step is due. |
Acting | The Action runs. With StartParallelWithMoveHandler this begins in the same frame as Moving, while the MoveHandler still runs. |
Ending | Only inside the end of the use, for example in the data object’s OnObjectUseFinished. |
The phase is the use’s own state. The 1.x getters keep their 1.x timing, so inside the use’s events they can disagree with it:
IsCharacterUseAnyObjectis already true insideOnStartUseObjectDelegate, while the phase is stillHolding. InsideOnEndUseObjectDelegateboth say that no use runs (false,None), unless a new use has already started inside the end of that use (see Starting a new use from the end of the previous one): then they describe the new use.IsHasDelayedObjectDatais still true inside the use’s start events (OnObjectUseStarted, and the start of the MoveHandler and of the Action when they run in the frame of the start), while the phase is alreadyMovingorActing.GetDelayedObjectProgressis unchanged.
The phase exists on every machine that runs the use: on the server and on the clients that show a remote player’s use it is the phase of their own run of it (Moving, Acting, then None).
Who is affected: nobody; the getter is new.
Maximum use duration
Section titled “Maximum use duration”MaxUseDuration of FCISInteractionObjectSettings limits how long a use may run. A use that still runs MaxUseDuration seconds after it started ends as interrupted. 0, the default, is no limit, as in 1.x.
- The time counts from the start of the use, after the hold.
- The interruption is the one of
TryInterruptObjectUsing: the data object’sInterruptUsing(a remote client’s use ends on the client through the server’s end instead, withoutInterruptUsing: see Ends that the server forces). ASharedPerObjectMoveHandler getsMovementAborted(also after its movement finished), the Action finishes as interrupted (OnUseFinishedwithbWasInterruptedtrue), then comeOnObjectUseFinishedwithbInterruptedtrue, the object’sOnUseFinishedDelegateand the character’sOnEndUseObjectDelegate. It applies whatever the object’sObjectInterruptionTypeis,Noneincluded. - Only the authority applies the limit: a standalone game, the server, a listen host for its own uses, a character that the server runs (an AI). The server also limits a remote client’s use (its run of the use, on the server’s clock), and the client’s use then ends as interrupted too.
- The value is read when the use starts. A change during the use applies to the next use.
- Every end of the use stops the limit: a normal end, an interruption, the removal of the object’s data, the destruction of the character or of the object, a use that ends after the component shut down, and the end of play of the character’s component.
- If the character component ends play without its actor being destroyed (for example
DestroyComponent),EndPlaystops theMaxUseDurationtimer and the use keeps running without a limit.
Who is affected: objects whose MaxUseDuration you set above 0. Content saved with 1.x has 0.
A use that does not start ends at once
Section titled “A use that does not start ends at once”When the hold completes, the character broadcasts OnStartUseObjectDelegate and then calls the data object’s UseObject. When UseObject starts no use, the announced use now ends at once: OnClearDelayedObjectData, then one OnEndUseObjectDelegate with the object, and IsCharacterUseAnyObject is false again. This happens when:
- the data object is no longer usable when
UseObjectruns, for example because a listener ofOnStartUseObjectDelegatemade it unusable; - a C++ override of
UseObjectstarts no use (it neither callsSupernor runs a use itself).
In 1.x the character stayed “using” with no end: IsCharacterUseAnyObject stayed true, the character could not use any object again, and OnEndUseObjectDelegate never came.
When a listener of OnStartUseObjectDelegate clears the hold (TryInterruptObjectUsing) or removes the object (destroys its actor), the use does not start either, and OnEndUseObjectDelegate follows at once (with a null object component when the actor was destroyed); 1.x crashed there.
Who is affected: content that makes the object unusable in OnStartUseObjectDelegate, and C++ overrides of UseObject that refuse a use. What to do: nothing. To refuse a use, prefer the object’s usable Conditions (UsableConditions): they stop the hold before it starts.
Behaviour changes for every use
Section titled “Behaviour changes for every use”These apply to every use, 1.x content included.
- The action is chosen when the use starts.
- The context holds the action’s index, read from the object state right before
OnObjectUseStarted. 1.x looked the action up right afterOnObjectUseStarted, so a state change made insideOnObjectUseStarted(or by a listener ofOnObjectStateChangedDelegatecalled from it) chose the action of the new state. - Who is affected: data objects that change the object state in
OnObjectUseStarted. - What to do: change the state before the use starts, for example in the character’s
OnStartUseObjectDelegate, which still fires beforeUseObject.
- The context holds the action’s index, read from the object state right before
- The use side is fixed for the whole use.
- The execution uses the side the use started at (
SideIndex) for the movement, its abort, the action, and the side’sActionExecutionType. 1.x read the data object’s current side at each step. - So in 1.x, when the character entered another side during the use (for example moved there by its MoveHandler), the action started with the new side, and that side’s
ActionExecutionTypedecided whether the action started after the movement. - When the character left every side, 1.x used a default side (
StartAfterFinishMoveHandler): aStartParallelWithMoveHandleruse then started its action a second time when the movement finished. - Who is affected: uses whose character crosses into another side, or out of every side, during the use.
- What to do: nothing, unless content relied on the side change.
- The execution uses the side the use started at (
- A use that ends inside one of its own events stops there.
- A use that a listener ends or interrupts inside
OnObjectUseStarted,OnMovementStarted,OnObjectActionStartedor the new object delegates does not go on. 1.x went on to start the movement and the action of the ended use, and could then fail the check inFinishUseObject. - An interruption or an end requested while the use ends (for example from
OnObjectUseFinished) is ignored. 1.x could callOnObjectUseFinisheda second time.
- A use that a listener ends or interrupts inside
- A use whose character is gone when its action is due ends as interrupted, before
OnObjectActionStartedandOnActionStartedDelegate(neither fires). 1.x dereferenced the null character. A use without a character also skips its MoveHandler. - The 1.x Server and Multicast relays of an Action or MoveHandler are no longer sent, for any object. For a 1.x object with
bWithReplicationoff (nowLocalOnly) they ran locally in 1.x, so there only C++ overrides of their_Implementationfunctions notice. For networked objects see Networked games: every machine runs the use.
Starting a new use from the end of the previous one
Section titled “Starting a new use from the end of the previous one”A listener of the object’s OnUseFinishedDelegate (or of the data object’s end) may start a new use of the same character, for example an AI that uses the object again. The first use now completes before the new one starts: its MaxUseDuration limit stops (before, it could end the new use as interrupted), and a remote client sends the first use’s end to the server before the new request (before, the server ended its run of the first use as interrupted). A shared Action or MoveHandler keeps the new use’s execution context.
While the new use runs, IsCharacterUseAnyObject reads true and GetCurrentUsingObject names its object, so no hold can start, and the character’s OnEndUseObjectDelegate fires at the end of each use. These 1.x flags are set for every use that the character component starts, also for one that C++ starts without the hold (StartUseExecution, or the data object’s UseObject): such a use reads as in use while it runs and broadcasts the character’s OnEndUseObjectDelegate at its end, without an OnStartUseObjectDelegate before it (that stays an event of the hold).
Who is affected: C++ that starts uses without the hold, and 1.x content that calls the data object’s UseObject directly. Such a use also reacts to TryInterruptObjectUsing now: when the object’s ObjectInterruptionType matches, the call interrupts the use (1.x did nothing); for a use whose data object the character does not track (a data object that C++ made itself), the call creates a new tracked data object instead and the use goes on. TryUseObject starts no hold while such a use runs. What to do: code that listens to the character’s OnEndUseObjectDelegate: expect an end there without a start before it; code that calls TryInterruptObjectUsing or TryUseObject during such a use: expect the behaviour above.
Component references and other fixes
Section titled “Component references and other fixes”The first part of 2.0 changed the component references and fixed many 1.x bugs that do not depend on networking. Changelog has the details of each; here is who is affected and what to do.
Component references
Section titled “Component references”FCISPrimitiveComponentsOnActorandFCISSceneComponentsOnActorkeep their names and now store the component inComponent(FComponentReference, Blueprint read/write) next toSocketName. A 1.x name moves intoComponent.ComponentPropertywhen an asset is loaded; saving writes only the new field.- The lookup (
GetPrimitiveComponentWithName,GetSceneComponentWithName, the trigger components, the angle detection component) tries the property name first, then the object name as 1.x did. So C++ components whose object name differs from their property name are now found (for exampleCapsuleComponentof a character, whose object isCollisionCylinder; 1.x found nothing for them). A name that is an object-reference variable of component type now gives the component that variable holds (when it belongs to the same actor), and when property X holds another component than the one whose object name is X, the property’s component wins. A name that matches nothing still gives nothing, and logs one warning per actor class and name. Every reference in the demo content resolves to the same component as in 1.x. - The editor picker lists the components of the actor class by property or variable name (C++ components, Blueprint components, inherited ones; for an overridden Blueprint component, the override), adds a list of the selected component’s sockets, shows a saved name that it does not offer as “<name> (not found)”, and no longer lists editor-only components (they are stripped from a cooked game) or component properties that are empty on the class defaults. A pick replaces the whole reference in one undoable step.
- A 1.x instance that overrode its archetype’s name with an empty one keeps the archetype’s migrated reference; clear
Componenton that instance to restore the empty reference. - When
AngleDetectionComponentis empty or names no component, the facing angle is measured to the object’s root component (1.x: to the world origin). The angle detection component is looked up once per character and object instead of every frame.
Who is affected: C++ code that read SelectedPrimitiveComponentName or SelectedSceneComponentName (now Selected*ComponentName_DEPRECATED, only read on load: use Component); references that 1.x silently did not resolve (they work now, check that this is what you want); content that relied on the world origin as the facing point.
- A side is identified by its index in
InteractionSides, no longer by the equality of its two angles. Two sides with the same angles now each use their own Actions and MoveHandler (1.x aliased the later one to the earlier one, or never selected it). If content relied on that aliasing, merge the two sides into one.UCISInteractionDataObject::CurrentSideis deprecated: it is kept in sync, but writes to it are ignored. - A side whose two angles are half a turn apart (for example −90..90) now covers the half circle around its middle angle, or the other half with
bAngleSide. 1.x ignored where the character stood for such a side (valid everywhere or nowhere, depending on float rounding). For a side that is valid everywhere, use 0/0 withbAngleSide.
Overlap, cleanup and tracked objects
Section titled “Overlap, cleanup and tracked objects”- An object stays overlapped while any of the character’s triggers still overlaps any primitive of the object’s actor (1.x ended the whole object’s overlap at the first end overlap of one pair).
InitInteractionTriggersskips null entries of its array (1.x crashed).- The periodic cleanup no longer removes the data of an object the character is using, so walking out of the trigger no longer interrupts a use. What to do: content that relied on that to end an Action that never finishes by itself must end the use itself (
TryInterruptObjectUsing, orFinishAction). - The cleanup also removes the data of an object whose interaction component was destroyed while its actor stayed (up to
TimeToClearInvalidObjectsData, 90 s by default, later); a use of it then ends as interrupted. Some ends after such a destruction reachOnEndUseObjectDelegateand the data object’sOnVisibleChanged/OnUsableChangedwith a null object component: a Blueprint data object should checkIsValidthere. - User code that removes a tracked object while the character ticks its data objects no longer raises an ensure; a removed object is not ticked any more, an added one from the next frame.
GetCountOfUsableObjectsreturns the number of tracked objects the character can use now; it is never negative (1.x drifted and went below zero).GetDelayedObjectProgressstays in [0,1] (1.x gave NaN or Inf for a use time of 0, the default). A negative use time (C++ only) now starts the use in the next tick, as 0 does; 1.x never started it.TryUseObjectuses the highlighted object (BestObjectToUse) while it is still usable, and only otherwise selects afresh, so the character uses the object the UI shows. A C++ override ofGetUsableInteractObjectthat blocks a use at press time is no longer consulted while a usable highlight exists: put such checks intoIsCanUseObjector into the object’s usable Conditions.
The character component and its controller
Section titled “The character component and its controller”- The character component follows its character’s controller, one tick after each change: it initialises when the character becomes locally controlled by a player controller, and shuts down when it is no longer locally controlled (unpossess, possession of another pawn,
DetachFromControllerPendingDestroy). So a pawn possessed after it spawned, or a client pawn whose controller arrives late, now interacts. A running use is not interrupted by a shutdown. An AI keeps the 1.x rule (initialised atBeginPlayonly when possessed before). - Blueprint state stored on the data objects of other tracked objects is lost at a shutdown (they are new after the next initialisation): keep such state on the object or on the character.
InitCharacterInteractionComponentdoes nothing while the component is initialised or shutting down; a project that called it after a possession can drop the call, and one that called it to rebuild the triggers callsInitInteractionTriggersinstead. A C++ override of it runs again after each such controller change: testbInteractionInitializedbeforeSuperfor code that must run once per init. Code that reads the component’s state right afterPossessorUnPossessmust wait one tick. An AI possessed afterBeginPlaythat should interact still needsInitCharacterInteractionComponentcalled after the possession, as in 1.x.- A trigger set that user code passed to
InitInteractionTriggersstays in force across shutdowns and re-inits (“the last call wins”, an empty array included). To go back to the settings’ triggers, pass them toInitInteractionTriggers. - A
UCISCharacterInteractionComponenton an actor that is not anACharacterlogs an error once and does nothing (1.x crashed).
Animation notify and root motion
Section titled “Animation notify and root motion”UCISAnimNotifyStateAttachMeshkeeps the attached mesh per skeletal mesh, so two meshes in the notify window no longer destroy each other’s attachment, and a repeatedNotifyBeginno longer leaks a mesh.SpawnedMeshstays, deprecated and unused.RootMotionMoveTowith a speed of 0 gets a finite duration (Max(TimeOut, 0.05)s) instead of an infinite one; observably the character is held and the task ends byTimeOut, as before.MoveToDistancestill has no effect; its tooltip says so.
Replication of bUsable
Section titled “Replication of bUsable”bUsable is registered for replication. With the engine’s default (Net.AutoRegisterReplicatedProperties 1) nothing changes. In a project that sets that variable to 0, clients now get the server’s SetUsable. What to do: call SetUsable on the server; for usability that differs per player, use UsableConditions. A C++ subclass that registered bUsable itself must remove its own registration (the engine stops at a duplicate).
Build dependencies
Section titled “Build dependencies”The editor module no longer lists UMG, UMGEditor and Blutility in its public dependencies: a project module that used their headers through it adds them to its own Build.cs. The editor module is limited to Win64, Mac and Linux.
C++ API
Section titled “C++ API”Deprecated
Section titled “Deprecated”UCISCharacterInteractionComponent::SetOwnerOfActorandServerSetOwnerOfActorare markedUE_DEPRECATED: C++ code that calls them gets a compiler warning (C4996 with MSVC,-Wdeprecated-declarationswith Clang). They still work as described in Ownership of the object’s actor. Where a call must stay for now, put it betweenPRAGMA_DISABLE_DEPRECATION_WARNINGSandPRAGMA_ENABLE_DEPRECATION_WARNINGS. OverridingServerSetOwnerOfActor_Implementationgives no warning. To give an actor an owner, callAActor::SetOwneron the server.- Deprecated in their doc comments, kept and working for code that calls them itself (the plugin calls none of them): the relays of
UCISInteractionObjectAction(ServerStartObjectAction,MulticastStartObjectAction,ServerFinishAction,MulticastFinishAction) and ofUCISInteractionObjectMoveHandler(ServerStartMovement,MulticastStartMovement,ServerMovementAborted,MulticastMovementAborted,ServerFinishMovement,MulticastFinishMovement,ServerInvalidateHandler,MulticastInvalidateHandler),UCISReplicatedObject::GetLegacyRelayObject, and the protectedServerSetCurrentUseObjectofUCISCharacterInteractionComponent. - Also deprecated:
UCISInteractionDataObject::CurrentSide(kept in sync, writes ignored),UCISAnimNotifyStateAttachMesh::SpawnedMesh(unused), and the fieldsSelected*ComponentName_DEPRECATEDof the component references (read on load only).
Overrides that must call Super
Section titled “Overrides that must call Super”Serialize,PostLoadandPostInitPropertiesof Actions, MoveHandlers (UCISReplicatedObject) and Conditions: the migration of 1.x assets runs there.EndPlayofUCISCharacterInteractionComponentand ofUCISInteractionObjectComponent(both new overrides).ReplicateSubobjectsofUCISInteractionObjectComponent: callSuperand keep your own|=.- The
_Implementationof the new RPCs (ServerNotifyHoldStarted,ServerRequestUse,ClientConfirmUse,ClientRejectUse,ServerUsePhase,ClientUsePhase,MulticastUsePhase,ClientSyncExecutionKeys) when a subclass overrides them. - A C++ subclass that wants the 2.0 behaviour sets
InstancingPolicyorServerValidationin its constructor, because itsDefaultis the 1.x behaviour. - A subclass of
UCISInteractionObjectMoveHandlerthat overridesIsTickableorGetTickableTickTypekeeps class defaults and archetypes from ticking (IsTemplate()).
New API
Section titled “New API”- Settings and migration:
ECISInstancingPolicy,ECISNetExecutionPolicy,ECISConditionServerValidation;UCISReplicatedObject::GetEffectiveInstancingPolicy(the policy a use applies, neverDefault),IsWithReplicationand the protectedGetResolvedInstancingPolicy(Defaultresolved by the class, before the check for the class’s own RPCs or replicated properties);UCISInteractionObjectCondition::ShouldValidateOnServer;FCISCustomVersion(Common/CISCustomVersion.h); inCommon/CISMigration.h:CIS::Migration::IsFromPackageBefore,NeedsLegacyMigration,GetNonNativeArchetype,ApplyLegacyDefaults,IsLoadingLegacyData,InheritPendingLegacyArchetype,ClassHasCustomNetFields. - Executions:
UCISUseExecution(Initialize,Start,AdvanceToActing,End,Interrupt,GetPhase,GetContext,GetAction,GetMoveHandler,HasStarted,HasEnded,IsRunning,ShouldRunOnRole,LeadsStep,LeadsOwnStep,OwnerLeadsStep,ApplyLeaderPhase,IsEndInterrupted,OnLocalStepCompleted,OnPhaseChanged,OnReleasing(fires inEndwhere the use releases its object, before the object’sOnUseFinishedDelegate),OnEnded);FCISExecutionContext;UCISReplicatedObject::SetExecutionContext,GetExecutionTemplate,IsPerExecutionInstance,InitPerExecutionInstance;UCISInteractionDataObject::GetCurrentExecution. - The character component:
StartUseExecution,GetActiveUseExecution,GetActiveUsePhase,HandleSimulatedUsePhase(public); the protectedActiveUse(FCISActiveUse: the object, its data object, the side and action indices, theExecutionId, the phase, the execution, andbRequestSent,bAwaitingServerAnswer,bRemoteUseOnServer,TransferredObjectOwner,UserController,ObjectActor,bSimulatedUse,bEndAppliedFromRemote,bReleasedOnAuthority),NextPredictionKeyandEndActiveUse(bool bInterrupted)(ends a running use through the data object’s entry points, stops theMaxUseDurationlimit and clears the active use; a second call does nothing); the protected RPCsServerNotifyHoldStarted,ServerRequestUse,ServerUsePhase(Server,WithValidation),ClientConfirmUse,ClientRejectUse,ClientUsePhaseandClientSyncExecutionKeys(Client). From the first part of 2.0:HandleCISCharacterControllerChanged,ShutdownCharacterInteractionComponent, the protectedbInteractionInitializedandIsActorOverlappingAnyTrigger,GetTrackedObjectDataCount. - The object component:
GetUseState(FCISObjectUseState),IsObjectOccupied,SetUseState_Authority, the delegatesOnUseStartedDelegate,OnActionStartedDelegate,OnUseFinishedDelegate; the protectedMulticastUsePhase(NetMulticast),SetObjectIsUsingForUse,TakeOwnershipForUseandReleaseOwnershipForUse. From the first part of 2.0:GetSideByIndex,FindSideIndex,GetActionIndexForSide,GetObjectActionForSideIndex. - The data object (first part of 2.0):
GetCurrentSideIndex,SetCurrentUseSideIndex. - Networking:
UCISNetworkSettings;FCISUseRequestandCIS::Validation::ValidateUseRequest;CIS::Geometry::ResolveTargetComponent,GetTargetPointFromandGetTargetPoint(the point a character faces, used by the data object and the server check);FCISUsePhaseEventandCIS::MaxExecutionId(Common/CISTypes.h); the enumsECISUsePhaseandECISUseRejectReason. - Component references:
UCISFunctionLibrary::ResolveComponentReference,FCIS*ComponentsOnActor::IsSet; editor:Customization/CISComponentPickerUtils.h(CIS::Editor::GatherComponentCandidates,GatherCommonComponentCandidates,GatherSocketNames,FindOwnerActorClass,PickComponent).
Other changes
Section titled “Other changes”UCISInteractionObjectComponent:InteractionSettingsis no longerReplicated;GetLifetimeReplicatedPropsregistersObjectState,bObjectIsUsingandUseStatepush-based, andbUsablewithout the push model;ReplicateSubobjectsstarts fromSuper(1.x: from true) and replicates only the templates of Which Actions and MoveHandlers are replicated.UCISInteractionObjectMoveHandleroverridesIsTickableandGetTickableTickType.UCISReplicatedObject::CallRemoteFunctionusesGetTypedOuter<AActor>()and returns false without an actor.UCISCharacterInteractionComponent::StartUseExecutioncallsFlushServerMovesof the user’sUCharacterMovementComponentright beforeServerRequestUseon a remote client.UCISInteractionObjectMoveHandler::FinishMovementdoes nothing when the movement is not running (1.x crashed), and logs aVerboseline inCISLogthen.- The plugin module depends on
DeveloperSettings(public) andNetCore(private); it no longer depends onBlutilityandUMGEditor.
The plugin’s and the demo’s content
Section titled “The plugin’s and the demo’s content”The assets of the plugin (/CharacterInteractionSystem) and of the demo (/CharacterInteractionSystem/Demo; in 1.x the demo was a separate project, with its content in /Game/CharacterInteraction) are saved with 2.0 and hold the 2.0 settings below. The Actions, MoveHandlers and Conditions, and the objects and the level that hold them, also carry CISVersion. 1.x cannot read these settings (see Going back to 1.x).
Where the demo is
Section titled “Where the demo is”The demo ships inside the plugin, in /CharacterInteractionSystem/Demo (on disk Plugins/CharacterInteractionSystem/Content/Demo), with the folders and asset names that 1.x had in /Game/CharacterInteraction. The plugin’s own content stays in /CharacterInteractionSystem/Content.
To open the demo, open the level /CharacterInteractionSystem/Demo/Levels/CISExampleMap and press Play. To see it in the Content Browser, turn on Settings > Show Plugin Content, and also Show Engine Content when the plugin is installed in the engine, as Fab installs it. The level sets its own game mode (BP_CISGameMode, with BP_CISCharacter and BP_CISHUD), so the default game mode of your project does not matter.
The demo needs nothing in the configuration of your project, except Enhanced Input as its default input (see the requirement at the end of The demo’s input):
- Gameplay tags: the plugin declares the demo’s
CIS.*tags itself, as native tags of its runtime module. If your project declares some of the same tags in its configuration, nothing breaks: the engine merges each of them into one tag. - Input: the demo character uses Enhanced Input with its own mapping context. See The demo’s input.
- Exposure and motion blur: the level has an unbound Post Process Volume with a fixed exposure and no motion blur, so the demo looks the same whatever the defaults of your project are.
- Light: the sun of the level (
Light Source, a directional light) is movable and casts dynamic shadows. The other lights are stationary, and the level ships its built lighting (CISExampleMap_BuiltData) for them. In a project that does not allow static lighting (Project Settings > Engine > Rendering > Allow Static Lighting, off in most UE 5 templates, Blank included), the engine treats the stationary lights as movable too and lights the level dynamically, so the level can look a little different there.
Packaging: a default cook of your project does not package the demo. It is packaged when content of yours uses its assets or your map list names its level. If your project cooks everything (Project Settings > Project > Packaging > Cook everything in the project content directory), the demo can be packaged too; to keep it out, list your own maps instead.
The demo character
Section titled “The demo character”The demo character BP_CISCharacter is the UE5 mannequin Manny (1.x: the UE4 mannequin).
- Mesh:
SKM_Manny_Simpleon the UE5 skeletonSK_Mannequin, with the physics assetPA_Mannequinand its materials and textures, in/CharacterInteractionSystem/Demo/Characters/Mannequins. The UE4 mannequin of the 1.x demo (in 1.x/Game/CharacterInteraction/Meshes/Mannequin: the mesh SK_Mannequin, the skeleton UE4_Mannequin_Skeleton, the physics asset SK_Mannequin_PhysicsAsset, its materials and textures) is not in the 2.0 demo. - Interaction animations: the montages and sequences in
/CharacterInteractionSystem/Demo/Animations/Characterkeep their paths and names and use the UE5 skeleton. Their notifies (BP_AnimNotify_AttachUseObject,BP_AnimNotify_SetTextOnCodeLockand the attach notify ofMan_KeyDoorOpen), slots and sections are unchanged, so the demo objects play them as before. - Movement:
ABP_CIStargets the UE5 skeleton and keeps its variables (Speed,Direction,IsInAir,CISCharacter,IsRun) and its states (idle and run, jump start, jump loop, jump end, and theDefaultSlotthat the montages play in). Idle, walk and run play the 1D blend spaceBS_CISLocomotion(/CharacterInteractionSystem/Demo/Animations/Character/Movement) withMM_Idle,MF_Unarmed_Walk_FwdandMF_Unarmed_Jog_Fwdof the UE5 Third Person template; the jump playsMM_Jump,MM_Fall_LoopandMM_Land. Walk and run speeds are unchanged (200 and 600 cm/s).Directionis still computed but no longer used, so a character that moves sideways or backward relative to where it faces plays the forward walk or run. The 1.x blend spaces BS_CISMovement and BS_CISMovementRun, the UE4 movement animations and FirstPerson_Idle were removed. KeySocket, where picked-up items and the key attach, is onhand_rof the UE5 skeleton.
If you made content from the demo character:
- A child Blueprint of
BP_CISCharactergets the new mesh and animation Blueprint from it. A mesh, animation class or component offset that your child sets itself stays as you set it. A child that sets a mesh on the UE4 skeleton should also set its own animation class, because the inheritedABP_CIStargets the UE5 skeleton. - An animation Blueprint, montage or sequence of yours that uses the UE4 skeleton of the 1.x demo does not find that skeleton in the 2.0 demo. Keep your own copy of the UE4 mannequin, or retarget your animations to the UE5 skeleton with an IK Retargeter.
- To keep the UE4 mannequin of a 1.x demo copy in your project when you follow An old copy of the demo in your project: in step 1, move the folder
Meshes/Mannequinout of/Game/CharacterInteractiontogether with your own assets. The redirect of step 3 sends every path under/Game/CharacterInteractionto the plugin, and the 2.0 demo has no UE4 mannequin. - A montage of yours that a demo object plays must use the UE5 skeleton and the
DefaultSlotslot, like the demo montages.
The demo’s input
Section titled “The demo’s input”BP_CISCharacter uses Enhanced Input. The input assets are in /CharacterInteractionSystem/Demo/Input:
| Action | Value | Keys in IMC_CISDemo |
|---|---|---|
IA_CISDemo_Move | Axis2D | W, S, A, D, the Up and Down arrow keys, the gamepad’s left stick |
IA_CISDemo_Look | Axis2D | the mouse, the gamepad’s right stick, the Left and Right arrow keys |
IA_CISDemo_Jump | Boolean | Space Bar |
- The character adds
IMC_CISDemoto its local player when it gets a controller and removes it when it loses the controller (Event On Controller Changed). This works in a standalone game, on a listen server and on clients. - The demo no longer uses the legacy axis and action mappings of the project (
MoveForward,MoveRight,Turn,LookUp,TurnRate,LookUpRate,Jump). Your project can keep or remove them. - The look speed is the one of 1.x. The mouse is scaled by the camera’s field of view as the legacy input did; mouse smoothing is not reproduced. The gamepad’s right stick and the Left and Right arrow keys turn at the 1.x rate too: the
Scalarmodifiers of these mappings inIMC_CISDemostand for the 1.xBaseTurnRateandBaseLookUpRate.BaseTurnRateandBaseLookUpRateofBP_CISCharacterare no longer used. - The direct key events of the demo are unchanged: E, V, 1, Left Shift and the gamepad buttons on the character, and the keys of the code lock. Jump has no gamepad button, as in 1.x: the gamepad’s bottom face button uses objects.
- Requirement: the default input classes of the project must be the Enhanced Input ones,
EnhancedPlayerInputandEnhancedInputComponent(Project Settings > Engine > Input > Default Classes). They are the default since UE 5.1. The plugin depends on the Enhanced Input plugin and enables it. With the legacy classes, the demo character does not move.
An old copy of the demo in your project
Section titled “An old copy of the demo in your project”This applies only if your project has a copy of the 1.x demo in /Game/CharacterInteraction.
-
Your copy keeps working as before: 2.0 migrates its data when it loads. It still uses the input mappings in the configuration of your project. Its gameplay tags are the demo’s tags that the plugin declares, so your project can keep or remove its own declarations of them.
-
To use the demo in the plugin instead: the changes you made in your copy are lost, and your content uses the 2.0 demo assets of the plugin from then on. If that is what you want:
-
Back up your project or commit it to version control. Move every asset of your own out of
/Game/CharacterInteractionin the Content Browser, fix up the redirectors that the move leaves there, then close the editor. -
With the editor closed, delete the folder
Content/CharacterInteractionof your project on disk or in version control. Do not delete it in the Content Browser: for assets that your levels and Blueprints use, it offers Force Delete, which clears those references. -
Add a redirect to
Config/DefaultEngine.iniof your project, so that your levels and Blueprints that use demo assets load them from the plugin:[CoreRedirects]+PackageRedirects=(OldName="/Game/CharacterInteraction/...",NewName="/CharacterInteractionSystem/Demo/",MatchWildcard=true) -
Open the editor. Keep the redirect until every asset of yours that uses demo assets has been saved again.
From then on, Who is affected by the content changes applies to your content. Through the redirect, a level of yours that places demo objects can report “Failed import” errors for components that the 2.0 demo classes no longer have, for example
ForwardPositionandBackwardPositionofBP_InteractionDoor_Locked. The level still loads. -
Demo Actions and MoveHandlers
Section titled “Demo Actions and MoveHandlers”| Class | InstancingPolicy | NetExecutionPolicy | Why |
|---|---|---|---|
BP_InteractionObjectActionPlayMontage, BP_PlaySoundAction, BP_CISNullObjectAction | InstancedPerExecution | AllMachines | Animations and sounds that every machine plays. They keep nothing from one use to the next. |
BP_InteractionObjectActionPickUp | InstancedPerExecution | AllMachines | It only plays the pick-up animation. The object handles the item: the BP_AnimNotify_AttachUseObject notify attaches it, the Action’s NewStateAfterFinishAction sets CIS.PickObject.Picked, and BP_BasePickItem destroys itself on the server in that state. |
BP_CodeLockAction | InstancedPerExecution | LocalOnly | It switches the user’s camera and input and shows the code lock to the user. The camera mode replicates through the character, and the code lock sends the result to the server itself. Its NewStateAfterFinishAction is off and must stay off: a LocalOnly Action applies its new state on the user’s machine only. |
BP_BaseMoveHandler, BP_MoveToComponentMoveHandler, BP_MoveToDistanceMoveHandler | InstancedPerExecution | AllMachines | The move to the object, as in 1.x. Every use starts from the same values. |
Demo Conditions
Section titled “Demo Conditions”| Class | ServerValidation | Why |
|---|---|---|
BP_InteractionObjectConditionDIstanceCheck, BP_InteractionObjectConditionTrace, and the plugin’s BP_InteractionObjectConditionDIstanceCheckExample and BP_InteractionObjectConditionTraceExample | Validate | The server has the positions and the level, so it checks distance and line of sight itself. |
BP_InteractionObjectCondition_IsObjectRendered and the plugin’s BP_InteractionObjectCondition_IsObjectRenderedExample | DoNotValidate | Only the client knows whether it renders the object. |
The server checks a validated Condition with its own positions and without the tolerances of Project Settings (those apply to the plugin’s own distance and angle checks). The demo objects use the distance Condition with a Distance of 180 to be usable and 400 to be visible. The server sees an approaching character about one network trip behind the client, so a use pressed right when the object becomes usable can be rejected (ConditionFailed) at any speed; the player then sees the use start and stop. A hold (TimeToUseObject above 0) avoids most of this, because the server checks the request at the end of the hold. To widen the margin, raise Distance on the Condition in the object’s UsableConditions, or set that Condition to DoNotValidate.
Demo objects and owner dormancy
Section titled “Demo objects and owner dormancy”bManageOwnerDormancy is on for BP_BaseInteractObject and all its children except BP_CodeLock (BP_BasePickItem, BP_Key, BP_Button, BP_CodeLockWithAnimation, BP_Crate, BP_Gate, BP_HandScanner, BP_InteractionDoor, BP_InteractionDoor_Locked, BP_InteractionDoorWithoutAnim, BP_Lever, BP_SimpleSwitcher, BP_Valve), and for BP_PickUpInteractionObject. Their only replicated data is the plugin’s.
It stays off for:
BP_CodeLock: it has Server and Multicast events of its own, and it sends a Server RPC at the start of a use;BP_Test: its actor does not replicate;- the plugin’s
BP_CISBaseInteractableActor: its actor does not replicate, so the setting would do nothing there, and a child that you make replicate may replicate state of its own. Turn it on in such a child when Owner dormancy allows it.
NetUpdateFrequency is unchanged: 20 (minimum 2) for BP_BaseInteractObject and its children, 100 for BP_PickUpInteractionObject and BP_CISBaseInteractableActor.
Object state set on the server
Section titled “Object state set on the server”BP_CodeLocknow sets its stateCIS.CodeLock.Openedon the server, in itsServerPassMatchedevent, which is now reliable (as isMulticastPassMatched). 1.x set it only on the user’s client, so the server and the other clients keptCIS.CodeLock.Closed.- The level Blueprint of
CISExampleMapcloses the locked door (CIS.Object.Door.Closed) when the lever is pulled, only on the server (IsServer). 1.x ran it on every client too, which in 2.0 logs the warning of Object state and usable flag on a client.
Unchanged in the content
Section titled “Unchanged in the content”The other variables and defaults of these Blueprints, bWithReplication, the component references (each one resolves to the same component as in 1.x), the animation notifies and the widgets.
Who is affected by the content changes
Section titled “Who is affected by the content changes”- Content of yours saved with 1.x that uses these classes: your child Blueprints of them, your levels that place the demo objects, and your own objects whose settings use these Actions, MoveHandlers or Conditions. Their Actions, MoveHandlers and Conditions hold no value of their own for the new settings, so they take the values above when they load (see Assets saved with 1.x): a copy per use, and server validation of the distance and trace Conditions. One exception: such an Action with
bWithReplicationon takesAllMachinesinstead ofLocalOnly, so in your 1.x level the code lock’s Action still runs on every machine, as in 1.x. What to do: to keep the 1.x behaviour, set the values explicitly on the Action, MoveHandler and Condition templates in the Blueprint:InstancingPolicySharedPerObject,NetExecutionPolicyAllMachines,ServerValidationDoNotValidate. The object’s settings are not editable on an object placed in a level; a placed object takes them from its Blueprint. - Your child Blueprints of
BP_BaseInteractObjectand of its children now getbManageOwnerDormancyon from their parent. The setting is on the interaction object component (Networking) and can be changed in a Blueprint only, not on an object placed in a level. - A child of a demo Action or MoveHandler that keeps state between uses. What to do: set its
InstancingPolicytoSharedPerObject(see When not to switch). - A child of a demo object that replicates variables or sends multicasts of its own, writes
bUsabledirectly, sets its actor’s dormancy, or whose Action or MoveHandler sends a Server RPC at the start of a use. What to do: turn offbManageOwnerDormancyon its interaction object component. - Code that calls
SetObjectStateon the code lock or on the doors from a client. What to do: call it on the server.
How 2.0 was checked against 1.x
Section titled “How 2.0 was checked against 1.x”Before the work started, the 1.x plugin recorded the order of the events of Actions, MoveHandlers, data objects, Conditions and the components’ delegates in 25 scenarios (standalone, a listen server with a client, a dedicated server with two clients), plus the state of the objects and characters after each step, the settings of the plugin’s and the demo’s assets, the Blueprint API and the replication cost. 2.0 is compared with these recordings on every test run. The differences, all intended and described above:
- The state after a use: the owner of the object’s actor now comes back at the end of each use (see Ownership of the object’s actor).
- Three dedicated-server scenarios whose Action finishes by itself on every machine: the server and the observing client now finish it once per use instead of twice, so the observing client broadcasts
OnObjectStateChangedDelegateonce per use instead of twice; the object’s state is the same as in 1.x on every machine (see NetExecutionPolicy). - The scenario with an object spawned at runtime: the clients now evaluate its Conditions (see InteractionSettings is not replicated).
- The replication cost (see Measured cost), and the network API list, which no longer has
InteractionSettings.
All other recorded event orders are equal to 1.x, and all plugin and demo Blueprints compile.