浏览代码

adding ChaseAction. refactoring actions a bit so that ActionLogic metadata is used to serialize the ActionRequestData. Making Action queueing behavior specifiable in the ActionRequestData. Hooking up an example where a player 'moves and attacks' in ClientInputSender. Adding a ClientCharacterMovement component to move the base Player GameObject (previously only the visualization was moving)

/main
David Woodruff 4 年前
当前提交
63c07658
共有 12 个文件被更改,包括 322 次插入27 次删除
  1. 17
      Assets/BossRoom/Prefabs/Player.prefab
  2. 38
      Assets/BossRoom/Scripts/Client/ClientInputSender.cs
  3. 13
      Assets/BossRoom/Scripts/Server/Game/Action/Action.cs
  4. 19
      Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs
  5. 13
      Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs
  6. 27
      Assets/BossRoom/Scripts/Server/ServerCharacterMovement.cs
  7. 47
      Assets/BossRoom/Scripts/Shared/Game/Action/ActionRequestData.cs
  8. 37
      Assets/BossRoom/Scripts/Shared/NetworkCharacterState.cs
  9. 40
      Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterMovement.cs
  10. 11
      Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterMovement.cs.meta
  11. 76
      Assets/BossRoom/Scripts/Server/Game/Action/ChaseAction.cs
  12. 11
      Assets/BossRoom/Scripts/Server/Game/Action/ChaseAction.cs.meta

17
Assets/BossRoom/Prefabs/Player.prefab


- component: {fileID: -3542424572169399493}
- component: {fileID: 6970334877957368017}
- component: {fileID: 4602672899881656135}
- component: {fileID: 7690172137830037487}
m_TagString: Untagged
m_TagString: Player
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0

m_GameObject: {fileID: 4600110157238723781}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 0
m_Enabled: 1
m_Radius: 0.5
m_Height: 2
m_Direction: 1

m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 920a440eb254ba348915767fd046027a, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &7690172137830037487
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4600110157238723781}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 797d92969c575574d868e069887e8486, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1001 &7872000410579295758

38
Assets/BossRoom/Scripts/Client/ClientInputSender.cs


{
if (Input.GetMouseButtonDown(1))
{
var data = new ActionRequestData();
data.ActionTypeEnum = Action.TANK_BASEATTACK;
m_NetworkCharacter.C2S_DoAction(ref data);
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit) && GetTargetObject(ref hit) != 0)
{
//these two actions will queue one after the other, causing us to run over to our target and take a swing.
var chase_data = new ActionRequestData();
chase_data.ActionTypeEnum = Action.GENERAL_CHASE;
chase_data.Amount = 3f;
chase_data.TargetIds = new ulong[] { GetTargetObject(ref hit) };
m_NetworkCharacter.C2S_DoAction(ref chase_data);
var hit_data = new ActionRequestData();
hit_data.ShouldQueue = true; //wait your turn--don't clobber the chase action.
hit_data.ActionTypeEnum = Action.TANK_BASEATTACK;
m_NetworkCharacter.C2S_DoAction(ref hit_data);
}
else
{
var data = new ActionRequestData();
data.ActionTypeEnum = Action.TANK_BASEATTACK;
m_NetworkCharacter.C2S_DoAction(ref data);
}
}
/// <summary>
/// Gets the Target NetworkId from the Raycast hit, or 0 if Raycast didn't contact a Networked Object.
/// </summary>
private ulong GetTargetObject(ref RaycastHit hit )
{
if( hit.collider == null ) { return 0; }
var targetObj = hit.collider.GetComponent<NetworkedObject>();
if( targetObj == null ) { return 0; }
return targetObj.NetworkId;
}
}
}

13
Assets/BossRoom/Scripts/Server/Game/Action/Action.cs


/// </summary>
protected int m_level;
private ActionRequestData m_data;
protected ActionRequestData m_data;
/// <summary>
/// RequestData we were instantiated with. Value should be treated as readonly.

{
get
{
var list = ActionDescriptionList.LIST[Data.ActionTypeEnum];
var list = ActionData.ActionDescriptions[Data.ActionTypeEnum];
int level = Mathf.Min(m_level, list.Count - 1); //if we don't go up to the requested level, just cap at the max level.
return list[level];
}

/// <returns>true to keep running, false to stop. The Action will stop by default when its duration expires, if it has a duration set. </returns>
public abstract bool Update();
/// <summary>
/// This will get called when the Action gets canceled. The Action should clean up any ongoing effects at this point.
/// (e.g. an Action that involves moving should cancel the current active move).
/// </summary>
public virtual void Cancel() { }
/// <summary>
/// Factory method that creates Actions from their request data.

/// <returns>the newly created action. </returns>
public static Action MakeAction(ServerCharacter parent, ref ActionRequestData data, int level )
{
var logic = ActionDescriptionList.LIST[data.ActionTypeEnum][0].Logic;
var logic = ActionData.ActionDescriptions[data.ActionTypeEnum][0].Logic;
case ActionLogic.CHASE: return new ChaseAction(parent, ref data, level);
default: throw new System.NotImplementedException();
}
}

19
Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs


public void PlayAction(ref ActionRequestData data )
{
if( !data.ShouldQueue )
{
ClearActions();
}
int level = 0; //todo, get this from parent's networked vars, maybe.
var new_action = Action.MakeAction(m_parent, ref data, level);

{
AdvanceQueue(false);
}
}
public void ClearActions()
{
if( m_queue.Count > 0 )
{
m_queue[0].Cancel();
}
//only the first element of the queue is running, so it is the only one that needs to be canceled.
m_queue.Clear();
}
/// <summary>

if( this.m_queue.Count > 0 )
{
bool keepgoing = this.m_queue[0].Update();
bool time_expired = (Time.time - this.m_actionStarted_s) >= this.m_queue[0].Description.Duration_s;
bool expirable = (this.m_queue[0].Description.Duration_s > 0f); //non-positive value is a sentinel indicating the duration is indefinite.
bool time_expired = expirable && (Time.time - this.m_actionStarted_s) >= this.m_queue[0].Description.Duration_s;
if ( !keepgoing || time_expired )
{
this.AdvanceQueue(true);

13
Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs


}
}
/// <summary>
/// Play an action!
/// </summary>
/// <param name="data">Contains all data necessary to create the action</param>
/// <summary>
/// Clear all active Actions.
/// </summary>
public void ClearActions()
{
this.m_actionPlayer.ClearActions();
}
Debug.Log("Server receiving action request for " + data.ActionTypeEnum);
this.PlayAction(ref data);
}

27
Assets/BossRoom/Scripts/Server/ServerCharacterMovement.cs


/// <summary>
/// Component responsible for moving a character on the server side based on inputs.
/// </summary>
[RequireComponent(typeof(NetworkCharacterState), typeof(NavMeshAgent))]
[RequireComponent(typeof(NetworkCharacterState), typeof(NavMeshAgent), typeof(ServerCharacter))]
public class ServerCharacterMovement : NetworkedBehaviour
{
private NavMeshAgent m_NavMeshAgent;

private MovementState m_MovementState;
private ServerCharacter m_CharLogic;
[SerializeField]
private float m_MovementSpeed; // TODO [GOMPS-86] this should be assigned based on character definition

// On the server enable navMeshAgent and initialize
m_NavMeshAgent.enabled = true;
m_NetworkCharacterState.OnReceivedClientInput += SetMovementTarget;
m_NetworkCharacterState.OnReceivedClientInput += OnReceivedClientInput;
private void SetMovementTarget(Vector3 position)
private void OnReceivedClientInput(Vector3 position )
{
m_CharLogic.ClearActions(); //a fresh movement request trumps whatever we were doing before.
SetMovementTarget(position);
}
/// <summary>
/// Sets a movement target. We will path to this position, avoiding static obstacles.
/// </summary>
/// <param name="position">Position in world space to path to. </param>
public void SetMovementTarget(Vector3 position)
{
m_MovementState = MovementState.PathFollowing;

/// <summary>
/// Cancels any moves that are currently in progress.
/// </summary>
public void CancelMove()
{
//Luke, is there anything else I should do to clear move state here?
m_MovementState = MovementState.Idle;
}
m_CharLogic = GetComponent<ServerCharacter>();
}
private void FixedUpdate()

47
Assets/BossRoom/Scripts/Shared/Game/Action/ActionRequestData.cs


{
TANK_BASEATTACK,
ARCHER_BASEATTACK,
GENERAL_CHASE,
}

MELEE,
RANGED,
RANGEDTARGETED,
CHASE,
//O__O adding a new ActionLogic branch? Update Action.MakeAction!
}
/// <summary>

}
/// <summary>
/// metadata about each kind of ActionLogic. This basically just informs us what fields to serialize for each kind of ActionLogic.
/// </summary>
public class ActionLogicInfo
{
public bool HasPosition;
public bool HasDirection;
public bool HasTarget;
public bool HasAmount;
}
/// <summary>
public class ActionDescriptionList
public class ActionData
public static Dictionary<Action, List<ActionDescription>> LIST = new Dictionary<Action, List<ActionDescription>>
public static Dictionary<ActionLogic, ActionLogicInfo> LogicInfos = new Dictionary<ActionLogic, ActionLogicInfo>
{
{ActionLogic.MELEE, new ActionLogicInfo{} },
{ActionLogic.RANGED, new ActionLogicInfo{HasDirection=true} },
{ActionLogic.RANGEDTARGETED, new ActionLogicInfo{HasTarget=true} },
{ActionLogic.CHASE, new ActionLogicInfo{HasTarget=true, HasAmount=true} },
};
public static Dictionary<Action, List<ActionDescription>> ActionDescriptions = new Dictionary<Action, List<ActionDescription>>
{ Action.TANK_BASEATTACK , new List<ActionDescription>
{ Action.TANK_BASEATTACK , new List<ActionDescription>
}
}
{ Action.ARCHER_BASEATTACK, new List<ActionDescription>
{ Action.ARCHER_BASEATTACK, new List<ActionDescription>
}
},
{ Action.GENERAL_CHASE, new List<ActionDescription>
{
{new ActionDescription{Logic=ActionLogic.CHASE } }
};
}

public Action ActionTypeEnum; //the action to play.
public Vector3 Position; //center position of skill, e.g. "ground zero" of a fireball skill.
public Vector3 Direction; //direction of skill, if not inferrable from the character's current facing.
public int[] TargetIds; //networkIds of targets, or null if untargeted.
public ulong[] TargetIds; //networkIds of targets, or null if untargeted.
public bool ShouldQueue; //if true, this action should queue. If false, it should clear all current actions and play immediately.
//O__O Hey, are you adding something? Be sure to update ActionLogicInfo and NetworkCharacterState.SerializeAction, RecvDoAction as well.
}
}

37
Assets/BossRoom/Scripts/Shared/NetworkCharacterState.cs


private void SerializeAction( ref ActionRequestData data, PooledBitStream stream )
{
var Logic = ActionDescriptionList.LIST[data.ActionTypeEnum][0].Logic;
var Logic = ActionData.ActionDescriptions[data.ActionTypeEnum][0].Logic;
var Info = ActionData.LogicInfos[Logic];
if (Logic == ActionLogic.RANGED)
writer.WriteBool(data.ShouldQueue);
if( Info.HasPosition )
{
writer.WriteVector3(data.Position);
}
if (Info.HasDirection)
if (Logic == ActionLogic.RANGEDTARGETED)
if (Info.HasTarget )
writer.WriteIntArray(data.TargetIds);
writer.WriteULongArray(data.TargetIds);
}
if( Info.HasAmount )
{
writer.WriteSingle(data.Amount);
}
}
}

private void RecvDoActionServer(ulong clientId, Stream stream)
{
ActionRequestData data = RecvDoAction(clientId, stream);
DoActionEventServer?.Invoke(data);
}

using (PooledBitReader reader = PooledBitReader.Get(stream))
{
data.ActionTypeEnum = (Action)reader.ReadInt16();
data.ShouldQueue = reader.ReadBool();
var Logic = ActionDescriptionList.LIST[data.ActionTypeEnum][0].Logic;
var Logic = ActionData.ActionDescriptions[data.ActionTypeEnum][0].Logic;
var Info = ActionData.LogicInfos[Logic];
if( Logic == ActionLogic.RANGED )
if (Info.HasPosition)
{
data.Position = reader.ReadVector3();
}
if (Info.HasDirection)
if( Logic == ActionLogic.RANGEDTARGETED )
if (Info.HasTarget)
{
data.TargetIds = reader.ReadULongArray();
}
if (Info.HasAmount)
data.TargetIds = reader.ReadIntArray(data.TargetIds);
data.Amount = reader.ReadSingle();
}
}

40
Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterMovement.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BossRoom.Client
{
/// <summary>
/// Client-side of character movement game logic.
/// </summary>
[RequireComponent(typeof(NetworkCharacterState))]
public class ClientCharacterMovement : MLAPI.NetworkedBehaviour
{
private NetworkCharacterState m_NetState;
// Start is called before the first frame update
void Start()
{
m_NetState = GetComponent<NetworkCharacterState>();
}
public override void NetworkStart()
{
if (IsServer)
{
//this component is not needed on the host (or dedicated server), because ServerCharacterMovement will directly
//update the character's position.
this.enabled = false;
}
}
// Update is called once per frame
void Update()
{
transform.position = m_NetState.NetworkPosition.Value;
transform.rotation = Quaternion.Euler(0, m_NetState.NetworkRotationY.Value, 0);
}
}
}

11
Assets/BossRoom/Scripts/Client/Game/Character/ClientCharacterMovement.cs.meta


fileFormatVersion: 2
guid: 797d92969c575574d868e069887e8486
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

76
Assets/BossRoom/Scripts/Server/Game/Action/ChaseAction.cs


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MLAPI;
namespace BossRoom.Server
{
public class ChaseAction : Action
{
private NetworkedObject m_Target;
private ServerCharacterMovement m_Movement;
private Vector3 m_CurrentTargetPos;
public ChaseAction(ServerCharacter parent, ref ActionRequestData data, int level) : base(parent, ref data, level)
{
}
/// <summary>
/// Called when the Action starts actually playing (which may be after it is created, because of queueing).
/// </summary>
/// <returns>false if the action decided it doesn't want to run after all, true otherwise. </returns>
public override bool Start()
{
if(m_data.TargetIds == null || m_data.TargetIds.Length == 0 || !MLAPI.Spawning.SpawnManager.SpawnedObjects.ContainsKey(m_data.TargetIds[0]) )
{
Debug.Log("Failed to start ChaseAction. The target entity wasn't submitted or doesn't exist anymore" );
return false;
}
m_Target = MLAPI.Spawning.SpawnManager.SpawnedObjects[m_data.TargetIds[0]];
m_Movement = m_parent.GetComponent<ServerCharacterMovement>();
m_Movement.SetMovementTarget(m_Target.transform.position);
m_CurrentTargetPos = m_Target.transform.position;
return true;
}
/// <summary>
/// Called each frame while the action is running.
/// </summary>
/// <returns>true to keep running, false to stop. The Action will stop by default when its duration expires, if it has a duration set. </returns>
public override bool Update()
{
float dist_to_target = (m_parent.transform.position - m_Target.transform.position).magnitude;
if( m_data.Amount > dist_to_target )
{
//we made it! we're done.
Cancel();
return false;
}
float target_moved = (m_Target.transform.position - m_CurrentTargetPos).magnitude;
if( m_data.Amount < target_moved )
{
//target has moved past our range tolerance. Must repath.
this.m_Movement.SetMovementTarget(m_Target.transform.position);
m_CurrentTargetPos = m_Target.transform.position;
}
return true;
}
public override void Cancel()
{
if( m_Movement != null )
{
m_Movement.CancelMove();
}
}
}
}

11
Assets/BossRoom/Scripts/Server/Game/Action/ChaseAction.cs.meta


fileFormatVersion: 2
guid: bc4a33f0facd1914f81611eb7038be06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存