浏览代码
ActionVisualization and MeleeActionFX (#40)
ActionVisualization and MeleeActionFX (#40)
-- ActionVisualization and ActionFX base classes added. -- MeleeActionFX logic added, and some logic to only play the hit react if the target is still in range. -- folded "Level" into ActionRequestData. (The server takes care to sanitize the field if a request comes from the client). -- hit detection logic has moved to a shared ActionUtils class. I wound up not making use of it in this change, but I still think it's likely this logic will need to be shared between server and client, or between different actions on the server. (It will also likely grow more complex). -- Added a BrainEnabled switch. This is mainly intended for debugging, although it's possible it could be part of a "Stun" effect. Switch it to false to turn monsters into helpless punching bags. -- split off some of Action into a shared ActionBase class. This is because Action and ActionFX ended up sharing several concepts. -- Changed server-duration to match the animat.../main
GitHub
4 年前
当前提交
645f009c
共有 28 个文件被更改,包括 589 次插入 和 227 次删除
-
25Assets/BossRoom/Models/BossSetController.controller
-
27Assets/BossRoom/Models/CharacterSet.fbx.meta
-
41Assets/BossRoom/Models/CharacterSetController.controller
-
3Assets/BossRoom/Prefabs/Character/Boss.prefab
-
3Assets/BossRoom/Prefabs/Player.prefab
-
73Assets/BossRoom/Scripts/Client/ClientCharacterVisualization.cs
-
9Assets/BossRoom/Scripts/Client/ClientInputSender.cs
-
23Assets/BossRoom/Scripts/Server/Game/Action/Action.cs
-
52Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs
-
2Assets/BossRoom/Scripts/Server/Game/Action/ChaseAction.cs
-
35Assets/BossRoom/Scripts/Server/Game/Action/MeleeAction.cs
-
6Assets/BossRoom/Scripts/Server/Game/Action/ReviveAction.cs
-
56Assets/BossRoom/Scripts/Server/Game/Character/AIBrain.cs
-
81Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs
-
15Assets/BossRoom/Scripts/Server/ServerCharacterMovement.cs
-
17Assets/BossRoom/Scripts/Shared/Game/Action/ActionRequestData.cs
-
8Assets/BossRoom/Scripts/Client/Game/Action.meta
-
42Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs
-
11Assets/BossRoom/Scripts/Shared/Game/Action/ActionBase.cs.meta
-
40Assets/BossRoom/Scripts/Shared/Game/Action/ActionUtils.cs
-
11Assets/BossRoom/Scripts/Shared/Game/Action/ActionUtils.cs.meta
-
66Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs
-
11Assets/BossRoom/Scripts/Client/Game/Action/ActionFX.cs.meta
-
67Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs
-
11Assets/BossRoom/Scripts/Client/Game/Action/ActionVisualization.cs.meta
-
70Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs
-
11Assets/BossRoom/Scripts/Client/Game/Action/MeleeActionFX.cs.meta
|
|||
fileFormatVersion: 2 |
|||
guid: 469848a43d22a88499451600cee68f03 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom |
|||
{ |
|||
/// <summary>
|
|||
/// Abstract base class containing some common members shared by Action (server) and ActionFX (client visual)
|
|||
/// </summary>
|
|||
public abstract class ActionBase |
|||
{ |
|||
protected ActionRequestData m_Data; |
|||
|
|||
/// <summary>
|
|||
/// Time when this Action was started (from Time.time) in seconds. Set by the ActionPlayer or ActionVisualization.
|
|||
/// </summary>
|
|||
public float TimeStarted { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// RequestData we were instantiated with. Value should be treated as readonly.
|
|||
/// </summary>
|
|||
public ref ActionRequestData Data { get { return ref m_Data; } } |
|||
|
|||
/// <summary>
|
|||
/// Data Description for this action.
|
|||
/// </summary>
|
|||
public ActionDescription Description |
|||
{ |
|||
get |
|||
{ |
|||
var list = ActionData.ActionDescriptions[Data.ActionTypeEnum]; |
|||
int level = Mathf.Min(Data.Level, list.Count - 1); //if we don't go up to the requested level, just cap at the max level.
|
|||
return list[level]; |
|||
} |
|||
} |
|||
|
|||
public ActionBase(ref ActionRequestData data) |
|||
{ |
|||
m_Data = data; |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5b435f2cb67bfbd4da38c5a06e04a432 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom |
|||
{ |
|||
public static class ActionUtils |
|||
{ |
|||
//cache Physics Cast hits, to minimize allocs.
|
|||
private static RaycastHit[] s_Hits = new RaycastHit[4]; |
|||
|
|||
/// <summary>
|
|||
/// Does a melee foe hit detect.
|
|||
/// </summary>
|
|||
/// <param name="isNPC">true if the attacker is an NPC (and therefore should hit PCs). False for the reverse.</param>
|
|||
/// <param name="attacker">The collider of the attacking GameObject.</param>
|
|||
/// <param name="description">The Description of the Action being played (containing things like Range that control the physics query.</param>
|
|||
/// <param name="results">Place an uninitialized RayCastHit[] ref in here. It will be set to the results array. </param>
|
|||
/// <remarks>
|
|||
/// This method does not alloc. It returns a maximum of 4 results. Consume the results immediately, as the array will be overwritten with
|
|||
/// the next similar query.
|
|||
/// </remarks>
|
|||
/// <returns>Total number of foes encountered. </returns>
|
|||
public static int DetectMeleeFoe(bool isNPC, Collider attacker, ActionDescription description, out RaycastHit[] results) |
|||
{ |
|||
//this simple detect just does a boxcast out from our position in the direction we're facing, out to the range of the attack.
|
|||
|
|||
var myBounds = attacker.bounds; |
|||
|
|||
//NPCs (monsters) can hit PCs, and vice versa. No friendly fire allowed on either side.
|
|||
int mask = LayerMask.GetMask(isNPC ? "PCs" : "NPCs"); |
|||
|
|||
int numResults = Physics.BoxCastNonAlloc(attacker.transform.position, myBounds.extents, |
|||
attacker.transform.forward, s_Hits, Quaternion.identity, description.Range, mask); |
|||
|
|||
results = s_Hits; |
|||
return numResults; |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: da8e533df9f2e3d4f9498ba76434ff4b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
namespace BossRoom.Visual |
|||
{ |
|||
/// <summary>
|
|||
/// Abstract base class for playing back the visual feedback of an Action.
|
|||
/// </summary>
|
|||
public abstract class ActionFX : ActionBase |
|||
{ |
|||
protected ClientCharacterVisualization m_Parent; |
|||
|
|||
public ActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data) |
|||
{ |
|||
m_Parent = parent; |
|||
} |
|||
|
|||
public ActionLogic Logic |
|||
{ |
|||
get |
|||
{ |
|||
return ActionData.ActionDescriptions[Data.ActionTypeEnum][0].Logic; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Starts the ActionFX. Derived classes may return false if they wish to end immediately without their Update being called.
|
|||
/// </summary>
|
|||
/// <returns>true to play, false to be immediately cleaned up.</returns>
|
|||
public abstract bool Start(); |
|||
|
|||
public abstract bool Update(); |
|||
|
|||
/// <summary>
|
|||
/// End is always called when the ActionFX finishes playing. This is a good place for derived classes to put
|
|||
/// wrap-up logic (perhaps playing the "puff of smoke" that rises when a persistent fire AOE goes away). Derived
|
|||
/// classes should aren't required to call base.End(); by default, the method just calls 'Cancel', to handle the
|
|||
/// common case where Cancel and End do the same thing.
|
|||
/// </summary>
|
|||
public virtual void End() |
|||
{ |
|||
Cancel(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cancel is called when an ActionFX is interrupted prematurely. It is kept logically distinct from End to allow
|
|||
/// for the possibility that an Action might want to play something different if it is interrupted, rather than
|
|||
/// completing. For example, a "ChargeShot" action might want to emit a projectile object in its End method, but
|
|||
/// instead play a "Stagger" animation in its Cancel method.
|
|||
/// </summary>
|
|||
protected virtual void Cancel() { } |
|||
|
|||
public static ActionFX MakeActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) |
|||
{ |
|||
ActionLogic logic = ActionData.ActionDescriptions[data.ActionTypeEnum][0].Logic; |
|||
switch (logic) |
|||
{ |
|||
case ActionLogic.MELEE: return new MeleeActionFX(ref data, parent); |
|||
default: throw new System.NotImplementedException(); |
|||
} |
|||
} |
|||
|
|||
public virtual void OnAnimEvent(string id) { } |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: bd5022fe94e2de7418ca7b00cb3431b9 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom.Visual |
|||
{ |
|||
/// <summary>
|
|||
/// This is a companion class to ClientCharacterVisualization that is specifically responsible for visualizing Actions. Action visualizations have lifetimes
|
|||
/// and ongoing state, making this class closely analogous in spirit to the BossRoom.Server.ActionPlayer class.
|
|||
/// </summary>
|
|||
public class ActionVisualization |
|||
{ |
|||
private List<ActionFX> m_PlayingActions; |
|||
|
|||
public ClientCharacterVisualization Parent { get; private set; } |
|||
|
|||
public ActionVisualization(ClientCharacterVisualization parent) |
|||
{ |
|||
Parent = parent; |
|||
m_PlayingActions = new List<ActionFX>(); |
|||
} |
|||
|
|||
public void Update() |
|||
{ |
|||
//do a reverse-walk so we can safely remove inside the loop.
|
|||
for (int i = m_PlayingActions.Count - 1; i >= 0; --i) |
|||
{ |
|||
var action = m_PlayingActions[i]; |
|||
bool keepGoing = action.Update(); |
|||
bool expirable = action.Description.Duration_s > 0f; //non-positive value is a sentinel indicating the duration is indefinite.
|
|||
bool timeExpired = expirable && (Time.time - action.TimeStarted) >= action.Description.Duration_s; |
|||
if (!keepGoing || timeExpired) |
|||
{ |
|||
action.End(); |
|||
m_PlayingActions.RemoveAt(i); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void OnAnimEvent(string id) |
|||
{ |
|||
foreach (var action in m_PlayingActions) |
|||
{ |
|||
action.OnAnimEvent(id); |
|||
} |
|||
} |
|||
|
|||
public void PlayAction(ref ActionRequestData data) |
|||
{ |
|||
//Do Trivial Actions (actions that just require playing a single animation, and don't require any state trackincg).
|
|||
switch (data.ActionTypeEnum) |
|||
{ |
|||
case ActionType.GENERAL_REVIVE: |
|||
Parent.OurAnimator.SetTrigger("BeginRevive"); |
|||
return; |
|||
} |
|||
|
|||
ActionFX action = ActionFX.MakeActionFX(ref data, Parent); |
|||
action.TimeStarted = Time.time; |
|||
if (action.Start()) |
|||
{ |
|||
m_PlayingActions.Add(action); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: ac73f3a9b424550468f04fc5362275b8 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using MLAPI; |
|||
using MLAPI.Spawning; |
|||
|
|||
namespace BossRoom.Visual |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// The visual part of a MeleeAction. See MeleeAction.cs for more about this action type.
|
|||
/// </summary>
|
|||
public class MeleeActionFX : ActionFX |
|||
{ |
|||
public MeleeActionFX(ref ActionRequestData data, ClientCharacterVisualization parent) : base(ref data, parent) { } |
|||
|
|||
//have we actually played an impact? This won't necessarily happen for all swings. Sometimes you're just swinging at space.
|
|||
private bool m_ImpactPlayed; |
|||
|
|||
/// <summary>
|
|||
/// When we detect if our original target is still around, we use a bit of padding on the range check.
|
|||
/// </summary>
|
|||
private const float k_RangePadding = 3f; |
|||
|
|||
public override bool Start() |
|||
{ |
|||
m_Parent.OurAnimator.SetTrigger(Description.Anim); |
|||
return true; |
|||
} |
|||
|
|||
public override bool Update() |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
public override void OnAnimEvent(string id) |
|||
{ |
|||
if (id == "impact" && !m_ImpactPlayed) |
|||
{ |
|||
PlayHitReact(); |
|||
} |
|||
} |
|||
|
|||
public override void End() |
|||
{ |
|||
//if this didn't already happen, make sure it gets a chance to run. This could have failed to run because
|
|||
//our animationclip didn't have the "impact" event properly configured (as one possibility).
|
|||
PlayHitReact(); |
|||
} |
|||
|
|||
private void PlayHitReact() |
|||
{ |
|||
if (m_ImpactPlayed) { return; } |
|||
m_ImpactPlayed = true; |
|||
|
|||
//Is my original target still in range? Then definitely get him!
|
|||
if (Data.TargetIds != null && Data.TargetIds.Length > 0 && SpawnManager.SpawnedObjects.ContainsKey(Data.TargetIds[0])) |
|||
{ |
|||
NetworkedObject originalTarget = SpawnManager.SpawnedObjects[Data.TargetIds[0]]; |
|||
float padRange = Description.Range + k_RangePadding; |
|||
|
|||
if ((m_Parent.transform.position - originalTarget.transform.position).sqrMagnitude < (padRange * padRange)) |
|||
{ |
|||
ClientCharacterVisualization targetViz = originalTarget.GetComponent<Client.ClientCharacter>().ChildVizObject; |
|||
targetViz.OurAnimator.SetTrigger("HitReact1"); |
|||
} |
|||
} |
|||
|
|||
//in the future we may do another physics check to handle the case where a target "ran under our weapon".
|
|||
//But for now, if the original target is no longer present, then we just don't play our hit react on anything.
|
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 87ac9a12dfd0b47478c2ce8ede685033 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue