-
Notifications
You must be signed in to change notification settings - Fork 575
Feature/animation anticipation #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
f975b84
fixing mage base attack. fixing chasing on attack action. making imps…
dwoodruffsf 74dca7a
initial changes for action anticipation
dwoodruffsf 40d141e
more temp work on anticipation
dwoodruffsf 5b5bdf6
clearing bogus change to dungeontest
dwoodruffsf 0392f82
initial pass at anticipative animations and other responsiveness tweaks
dwoodruffsf ace9a4f
taking develop merge
dwoodruffsf 7d8219c
fixing compilation isue
dwoodruffsf ee13f39
iterating on controller and restoring click feedback
dwoodruffsf bfad632
more iteration on animations
dwoodruffsf ad2d31d
more iteration on aticipative actions
dwoodruffsf 39adfaa
heavy comment about anticipative actions, and cleaned up issue with h…
dwoodruffsf 151eb1c
Merge remote-tracking branch 'origin/develop' into feature/animation_…
dwoodruffsf 2680287
removing some debug logs
dwoodruffsf 99a1a6d
updating back to 2020.3
dwoodruffsf 323cd8d
updating click feedback
dwoodruffsf 9531441
taking develop merge
dwoodruffsf 6f6967a
Merge remote-tracking branch 'origin/develop' into feature/animation_…
dwoodruffsf 6d2b2a2
fixing up change so that you can't anticipatively animate when fainted.
dwoodruffsf a12343a
taking develop merge
dwoodruffsf bf6e230
Update ClientInputSender.cs
dwoodruffsf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Git LFS file not shown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,14 +9,19 @@ namespace BossRoom.Visual | |
| /// </summary> | ||
| public class ActionVisualization | ||
| { | ||
| private List<ActionFX> m_PlayingActions; | ||
| private List<ActionFX> m_PlayingActions = new List<ActionFX>(); | ||
|
|
||
| /// <summary> | ||
| /// Don't let anticipated actionFXs persist longer than this. This is a safeguard against scenarios | ||
| /// where we never get a confirmed action for an action we anticipated. | ||
| /// </summary> | ||
| private const float k_AnticipationTimeoutSeconds = 1; | ||
|
|
||
| public ClientCharacterVisualization Parent { get; private set; } | ||
|
|
||
| public ActionVisualization(ClientCharacterVisualization parent) | ||
| { | ||
| Parent = parent; | ||
| m_PlayingActions = new List<ActionFX>(); | ||
| } | ||
|
|
||
| public void Update() | ||
|
|
@@ -27,15 +32,24 @@ public void Update() | |
| var action = m_PlayingActions[i]; | ||
| bool keepGoing = action.Update(); | ||
| bool expirable = action.Description.DurationSeconds > 0f; //non-positive value is a sentinel indicating the duration is indefinite. | ||
| bool timeExpired = expirable && (Time.time - action.TimeStarted) >= action.Description.DurationSeconds; | ||
| if (!keepGoing || timeExpired) | ||
| bool timeExpired = expirable && action.TimeRunning >= action.Description.DurationSeconds; | ||
| bool timedOut = action.Anticipated && action.TimeRunning >= k_AnticipationTimeoutSeconds; | ||
| if (!keepGoing || timeExpired || timedOut) | ||
| { | ||
| action.End(); | ||
| if (timedOut) { action.Cancel(); } //an anticipated action that timed out shouldn't get its End called. It is canceled instead. | ||
| else { action.End(); } | ||
|
|
||
| m_PlayingActions.RemoveAt(i); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| //helper wrapper for a FindIndex call on m_PlayingActions. | ||
| private int FindAction(ActionType action, bool anticipatedOnly ) | ||
| { | ||
| return m_PlayingActions.FindIndex(a => a.Description.ActionTypeEnum == action && (!anticipatedOnly || a.Anticipated)); | ||
| } | ||
|
|
||
| public void OnAnimEvent(string id) | ||
| { | ||
| foreach (var actionFX in m_PlayingActions) | ||
|
|
@@ -52,22 +66,55 @@ public void OnStoppedChargingUp() | |
| } | ||
| } | ||
|
|
||
| public void PlayAction(ref ActionRequestData data) | ||
| /// <summary> | ||
| /// Called on the client that owns the Character when the player triggers an action. This allows actions to immediately start playing feedback. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// | ||
| /// What is Action Anticipation and what problem does it solve? In short, it lets Actions run logic the moment the input event that triggers them | ||
| /// is detected on the local client. The purpose of this is to help mask latency. Because this demo is server authoritative, the default behavior is | ||
| /// to only see feedback for your input after a server-client roundtrip. Somewhere over 200ms of round-trip latency, this starts to feel oppressively sluggish. | ||
| /// To combat this, you can play visual effects immediately. For example, MeleeActionFX plays both its weapon swing and applies a hit react to the target, | ||
| /// without waiting to hear from the server. This can lead to discrepancies when the server doesn't think the target was hit, but on the net, will feel | ||
| /// more responsive. | ||
| /// | ||
| /// An important concept of Action Anticipation is that it is opportunistic--it doesn't make any strong guarantees. You don't get an anticipated | ||
| /// action animation if you are already animating in some way, as one example. Another complexity is that you don't know if the server will actually | ||
| /// let you play all the actions that you've requested--some may get thrown away, e.g. because you have too many actions in your queue. What this means | ||
| /// is that Anticipated Actions (actions that have been constructed but not started) won't match up perfectly with actual approved delivered actions from | ||
| /// the server. For that reason, it must always be fine to receive PlayAction and not have an anticipated action already started (this is true for playback | ||
| /// Characters belonging to the server and other characters anyway). It also means we need to handle the case where we created an Anticipated Action, but | ||
| /// never got a confirmation--actions like that need to eventually get discarded. | ||
| /// | ||
| /// Another important aspect of Anticipated Actions is that they are an "opt-in" system. You must call base.Start in your Start implementation, but other than | ||
| /// that, if you don't have a good way to implement an Anticipation for your action, you don't have to do anything. In this case, that action will play | ||
| /// "normally" (with visual feedback starting when the server's action broadcast reaches the client). Every action type will have its own particular set of | ||
| /// problems to solve to sell the anticipation effect. For example, in this demo, the mage base attack (FXProjectileTargetedActionFX) just plays the attack animation | ||
| /// anticipatively, but it could be revised to create and drive the mage bolt effect as well--leaving only damage to arrive in true server time. | ||
| /// | ||
| /// How to implement your own Anticipation logic: | ||
| /// 1. Isolate the visual feedback you want play anticipatively in a private helper method on your ActionFX, like "PlayAttackAnim". | ||
| /// 2. Override ActionFX.AnticipateAction. Be sure to call base.AnticipateAction, as well as play your visual logic (like PlayAttackAnim). | ||
| /// 3. In your Start method, be sure to call base.Start (note that this will reset the "Anticipated" field to false). | ||
| /// 4. In Start, check if the action was Anticipated. If NOT, then play call your PlayAttackAnim method. | ||
| /// | ||
| /// </remarks> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love this comment! @SamuelBellomo - seems like a good thing to reference in our docs on latency masking techniques.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks :-) |
||
| /// <param name="data">The Action that is being requested.</param> | ||
| public void AnticipateAction(ref ActionRequestData data) | ||
| { | ||
| ActionDescription actionDesc = GameDataSource.Instance.ActionDataByType[data.ActionTypeEnum]; | ||
|
|
||
| //Do Trivial Actions (actions that just require playing a single animation, and don't require any state tracking). | ||
| switch (actionDesc.Logic) | ||
| if (!Parent.IsAnimating && ActionFX.ShouldAnticipate(this, ref data)) | ||
| { | ||
| case ActionLogic.LaunchProjectile: | ||
| case ActionLogic.Revive: | ||
| case ActionLogic.Emote: | ||
| Parent.OurAnimator.SetTrigger(actionDesc.Anim); | ||
| return; | ||
| var actionFX = ActionFX.MakeActionFX(ref data, Parent); | ||
| actionFX.AnticipateAction(); | ||
| m_PlayingActions.Add(actionFX); | ||
| } | ||
| } | ||
|
|
||
| var actionFX = ActionFX.MakeActionFX(ref data, Parent); | ||
| actionFX.TimeStarted = Time.time; | ||
| public void PlayAction(ref ActionRequestData data) | ||
| { | ||
| var anticipatedActionIndex = FindAction(data.ActionTypeEnum, true); | ||
|
|
||
| var actionFX = anticipatedActionIndex>=0 ? m_PlayingActions[anticipatedActionIndex] : ActionFX.MakeActionFX(ref data, Parent); | ||
| if (actionFX.Start()) | ||
| { | ||
| m_PlayingActions.Add(actionFX); | ||
|
|
@@ -85,7 +132,7 @@ public void CancelAllActions() | |
|
|
||
| public void CancelAllActionsOfType(ActionType actionType) | ||
| { | ||
| for (int i = m_PlayingActions.Count-1; i >=0; --i) | ||
| for (int i = m_PlayingActions.Count - 1; i >= 0; --i) | ||
| { | ||
| if (m_PlayingActions[i].Description.ActionTypeEnum == actionType) | ||
| { | ||
|
|
@@ -100,7 +147,7 @@ public void CancelAllActionsOfType(ActionType actionType) | |
| /// </summary> | ||
| public void CancelAll() | ||
| { | ||
| foreach( var action in m_PlayingActions ) | ||
| foreach (var action in m_PlayingActions) | ||
| { | ||
| action.Cancel(); | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
action.TimeRunningdoes not directly imply that we're measuring seconds - I would adjust the name of this variable to reflect that it's indeed in seconds.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with that, but it's a large change (in number of files touched), and not directly related to this change--TimeRunning and TimeStarted have been around a while. I would like to come back and do that in a follow-on. I made a separate branch (dmw_rename_timerunning), containing a stab at it. These big refactors make my VS Studio throw exceptions 😢