浏览代码
Simple AI based on Actions
Simple AI based on Actions
- added AIBrain which can switch between Idle and Attack. Owned by ServerCharacter - renamed Action enum to ActionType to prevent name conflicts with Action class - gave NetworkingManager its own prefab (this is the only change to MainMenu) - testing hack: press the "E" key on the server/host to spawn an enemy at 0,0,0/main
eheimburg
4 年前
当前提交
3bee0ea3
共有 22 个文件被更改,包括 808 次插入 和 177 次删除
-
1Assets/BossRoom/Prefabs/State/BossRoomState.prefab
-
226Assets/BossRoom/Scenes/MainMenu.unity
-
6Assets/BossRoom/Scripts/Client/ClientInputSender.cs
-
17Assets/BossRoom/Scripts/Server/Game/Action/ActionPlayer.cs
-
38Assets/BossRoom/Scripts/Server/Game/Character/ServerCharacter.cs
-
25Assets/BossRoom/Scripts/Server/Game/State/ServerBossRoomState.cs
-
12Assets/BossRoom/Scripts/Shared/Game/Action/ActionRequestData.cs
-
2Assets/BossRoom/Scripts/Shared/NetworkCharacterState.cs
-
75Assets/BossRoom/Prefabs/Enemy.prefab
-
7Assets/BossRoom/Prefabs/Enemy.prefab.meta
-
172Assets/BossRoom/Prefabs/NetworkingManager.prefab
-
7Assets/BossRoom/Prefabs/NetworkingManager.prefab.meta
-
8Assets/BossRoom/Scripts/Server/Game/AIState.meta
-
119Assets/BossRoom/Scripts/Server/Game/Character/AIBrain.cs
-
11Assets/BossRoom/Scripts/Server/Game/Character/AIBrain.cs.meta
-
32Assets/BossRoom/Scripts/Server/Game/AIState/AIState.cs
-
11Assets/BossRoom/Scripts/Server/Game/AIState/AIState.cs.meta
-
146Assets/BossRoom/Scripts/Server/Game/AIState/AttackAIState.cs
-
11Assets/BossRoom/Scripts/Server/Game/AIState/AttackAIState.cs.meta
-
48Assets/BossRoom/Scripts/Server/Game/AIState/IdleAIState.cs
-
11Assets/BossRoom/Scripts/Server/Game/AIState/IdleAIState.cs.meta
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!1001 &2457547766902456859 |
|||
PrefabInstance: |
|||
m_ObjectHideFlags: 0 |
|||
serializedVersion: 2 |
|||
m_Modification: |
|||
m_TransformParent: {fileID: 0} |
|||
m_Modifications: |
|||
- target: {fileID: 4600110157238723776, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_Enabled |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723781, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_Name |
|||
value: Enemy |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723790, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: PrefabHash |
|||
value: 7042126347811011599 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723790, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: PrefabHashGenerator |
|||
value: Enemy |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_RootOrder |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalPosition.x |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalPosition.y |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalPosition.z |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalRotation.w |
|||
value: 1 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalRotation.x |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalRotation.y |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalRotation.z |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalEulerAnglesHint.x |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalEulerAnglesHint.y |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4600110157238723791, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: m_LocalEulerAnglesHint.z |
|||
value: 0 |
|||
objectReference: {fileID: 0} |
|||
- target: {fileID: 4602672899881656135, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
propertyPath: IsNPC |
|||
value: 1 |
|||
objectReference: {fileID: 0} |
|||
m_RemovedComponents: [] |
|||
m_SourcePrefab: {fileID: 100100000, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|
|||
fileFormatVersion: 2 |
|||
guid: febd58976027e484884aa5357092da15 |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
%YAML 1.1 |
|||
%TAG !u! tag:unity3d.com,2011: |
|||
--- !u!1 &5436007408952557947 |
|||
GameObject: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
serializedVersion: 6 |
|||
m_Component: |
|||
- component: {fileID: 5436007408952557925} |
|||
- component: {fileID: 5436007408952557924} |
|||
- component: {fileID: 5436007408952557927} |
|||
- component: {fileID: 5436007408952557926} |
|||
- component: {fileID: 5436007408952557945} |
|||
m_Layer: 0 |
|||
m_Name: NetworkingManager |
|||
m_TagString: Untagged |
|||
m_Icon: {fileID: 0} |
|||
m_NavMeshLayer: 0 |
|||
m_StaticEditorFlags: 0 |
|||
m_IsActive: 1 |
|||
--- !u!4 &5436007408952557925 |
|||
Transform: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5436007408952557947} |
|||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} |
|||
m_LocalPosition: {x: 0, y: 1, z: -10} |
|||
m_LocalScale: {x: 1, y: 1, z: 1} |
|||
m_Children: [] |
|||
m_Father: {fileID: 0} |
|||
m_RootOrder: 0 |
|||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} |
|||
--- !u!114 &5436007408952557924 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5436007408952557947} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
DontDestroy: 1 |
|||
RunInBackground: 1 |
|||
LogLevel: 1 |
|||
NetworkConfig: |
|||
ProtocolVersion: 0 |
|||
NetworkTransport: {fileID: 5436007408952557927} |
|||
RegisteredScenes: |
|||
- MainMenu |
|||
- CharSelect |
|||
- SampleScene |
|||
- DungeonTest |
|||
AllowRuntimeSceneChanges: 1 |
|||
NetworkedPrefabs: |
|||
- Prefab: {fileID: 4600110157238723781, guid: bb87f9bac2595f8499c048016c4b2e1d, type: 3} |
|||
PlayerPrefab: 1 |
|||
- Prefab: {fileID: 3565665953789623672, guid: 1a58a2c4657fe6d4890d9ad39f43894e, type: 3} |
|||
PlayerPrefab: 0 |
|||
- Prefab: {fileID: 297185343939699586, guid: 8b9c63e7d70c5ff48a03aad51e17103c, type: 3} |
|||
PlayerPrefab: 0 |
|||
- Prefab: {fileID: 2147136737739531998, guid: febd58976027e484884aa5357092da15, type: 3} |
|||
PlayerPrefab: 0 |
|||
PlayerPrefabHash: |
|||
id: 0 |
|||
CreatePlayerPrefab: 0 |
|||
ReceiveTickrate: 0 |
|||
MaxReceiveEventsPerTickRate: 1000 |
|||
EventTickrate: 100 |
|||
ClientConnectionBufferTimeout: 10 |
|||
ConnectionApproval: 1 |
|||
ConnectionData: |
|||
SecondsHistory: 0 |
|||
EnableTimeResync: 0 |
|||
TimeResyncInterval: 30 |
|||
EnableNetworkedVar: 1 |
|||
EnsureNetworkedVarLengthSafety: 0 |
|||
EnableSceneManagement: 1 |
|||
ForceSamePrefabs: 0 |
|||
UsePrefabSync: 0 |
|||
RecycleNetworkIds: 1 |
|||
NetworkIdRecycleDelay: 120 |
|||
RpcHashSize: 0 |
|||
LoadSceneTimeOut: 120 |
|||
EnableMessageBuffering: 0 |
|||
MessageBufferTimeout: 20 |
|||
EnableNetworkLogs: 1 |
|||
EnableEncryption: 0 |
|||
SignKeyExchange: 0 |
|||
ServerBase64PfxCertificate: |
|||
references: |
|||
version: 1 |
|||
00000000: |
|||
type: {class: NullableBoolSerializable, ns: MLAPI.Configuration, asm: Unity.Multiplayer.MLAPI.Runtime} |
|||
data: |
|||
Value: 1897319656204293034 |
|||
--- !u!114 &5436007408952557927 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5436007408952557947} |
|||
m_Enabled: 1 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: b84c2d8dfe509a34fb59e2b81f8e1319, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
MessageBufferSize: 5120 |
|||
MaxConnections: 100 |
|||
MaxSentMessageQueueSize: 128 |
|||
ConnectAddress: 127.0.0.1 |
|||
ConnectPort: 7777 |
|||
ServerListenPort: 7777 |
|||
ServerWebsocketListenPort: 8887 |
|||
SupportWebsocket: 0 |
|||
Channels: [] |
|||
UseMLAPIRelay: 0 |
|||
MLAPIRelayAddress: 184.72.104.138 |
|||
MLAPIRelayPort: 8888 |
|||
MessageSendMode: 0 |
|||
--- !u!114 &5436007408952557926 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5436007408952557947} |
|||
m_Enabled: 0 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: db28424c2ae12f64da25c9ecccded6b1, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
Port: 7777 |
|||
Address: 127.0.0.1 |
|||
PingInterval: 1 |
|||
DisconnectTimeout: 5 |
|||
ReconnectDelay: 0.5 |
|||
MaxConnectAttempts: 10 |
|||
channels: [] |
|||
MessageBufferSize: 5120 |
|||
SimulatePacketLossChance: 0 |
|||
SimulateMinLatency: 0 |
|||
SimulateMaxLatency: 0 |
|||
--- !u!114 &5436007408952557945 |
|||
MonoBehaviour: |
|||
m_ObjectHideFlags: 0 |
|||
m_CorrespondingSourceObject: {fileID: 0} |
|||
m_PrefabInstance: {fileID: 0} |
|||
m_PrefabAsset: {fileID: 0} |
|||
m_GameObject: {fileID: 5436007408952557947} |
|||
m_Enabled: 0 |
|||
m_EditorHideFlags: 0 |
|||
m_Script: {fileID: 11500000, guid: 52c005b32a68a254cbe502a4e5cb8eb6, type: 3} |
|||
m_Name: |
|||
m_EditorClassIdentifier: |
|||
appId: bc5b8b0d-edf3-4c61-9593-ce38da7f0c79 |
|||
gameVersion: 0.0.0 |
|||
region: EU |
|||
nickName: |
|||
roomName: |
|||
maxPlayers: 4 |
|||
batchedTransportEventCode: 129 |
|||
channelIdCodesStartRange: 130 |
|||
attachSupportLogger: 1 |
|||
sendQueueBatchSize: 4096 |
|
|||
fileFormatVersion: 2 |
|||
guid: 8f5ddd70561bc0b42bbbe5a8a155bb7b |
|||
PrefabImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 1f710d234d4cd2c4da99bedcd533b6d7 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom.Server |
|||
{ |
|||
/// <summary>
|
|||
/// Handles enemy AI. Contains AIStateLogics that handle some of the details,
|
|||
/// and has various utility functions that are called by those AIStateLogics
|
|||
/// </summary>
|
|||
public class AIBrain |
|||
{ |
|||
private enum AIStateType |
|||
{ |
|||
ATTACK, |
|||
//WANDER,
|
|||
IDLE, |
|||
} |
|||
|
|||
private ServerCharacter m_serverCharacter; |
|||
private ActionPlayer m_actionPlayer; |
|||
private AIStateType m_currentState; |
|||
private Dictionary<AIStateType, AIState> m_logics; |
|||
private List<ServerCharacter> m_hatedEnemies; |
|||
|
|||
public AIBrain(ServerCharacter me, ActionPlayer myActionPlayer) |
|||
{ |
|||
m_serverCharacter = me; |
|||
m_actionPlayer = myActionPlayer; |
|||
|
|||
m_logics = new Dictionary<AIStateType, AIState> |
|||
{ |
|||
[ AIStateType.IDLE ] = new IdleAIState(this), |
|||
//[ AIStateType.WANDER ] = new WanderAIState(this), // not written yet
|
|||
[ AIStateType.ATTACK ] = new AttackAIState(this, m_actionPlayer), |
|||
}; |
|||
m_hatedEnemies = new List<ServerCharacter>(); |
|||
m_currentState = AIStateType.IDLE; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Should be called by the AIBrain's owner each Update()
|
|||
/// </summary>
|
|||
public void Update() |
|||
{ |
|||
AIStateType newState = FindBestEligibleAIState(); |
|||
if (m_currentState != newState) |
|||
{ |
|||
m_logics[ newState ].Initialize(); |
|||
} |
|||
m_currentState = newState; |
|||
m_logics[ m_currentState ].Update(); |
|||
} |
|||
|
|||
private AIStateType FindBestEligibleAIState() |
|||
{ |
|||
// for now we assume the AI states are in order of appropriateness,
|
|||
// which may be nonsensical when there are more states
|
|||
foreach (AIStateType type in Enum.GetValues(typeof(AIStateType))) |
|||
{ |
|||
if (m_logics[ type ].IsEligible()) |
|||
{ |
|||
return type; |
|||
} |
|||
} |
|||
Debug.LogError("No AI states are valid!?!"); |
|||
return AIStateType.IDLE; |
|||
} |
|||
|
|||
#region Functions for AIStateLogics
|
|||
/// <summary>
|
|||
/// Returns true if it be appropriate for us to murder this character, starting right now!
|
|||
/// </summary>
|
|||
public bool IsAppropriateFoe(ServerCharacter potentialFoe) |
|||
{ |
|||
if (potentialFoe == null || potentialFoe.IsNPC) |
|||
{ |
|||
return false; |
|||
} |
|||
// FIXME: check for dead!
|
|||
// Also, we could use NavMesh.Raycast() to see if we have line of sight to foe?
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Notify the AIBrain that we should consider this character an enemy.
|
|||
/// </summary>
|
|||
/// <param name="character"></param>
|
|||
public void Hate(ServerCharacter character) |
|||
{ |
|||
if (!m_hatedEnemies.Contains(character)) |
|||
{ |
|||
m_hatedEnemies.Add(character); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the raw list of hated enemies -- treat as read-only!
|
|||
/// </summary>
|
|||
public List<ServerCharacter> GetHatedEnemies() |
|||
{ |
|||
// first we clean the list -- remove any enemies that have disappeared (became null), are dead, etc.
|
|||
m_hatedEnemies.RemoveAll(enemy => !IsAppropriateFoe(enemy)); |
|||
return m_hatedEnemies; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieve info about who we are. Treat as read-only!
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public ServerCharacter GetMyServerCharacter() |
|||
{ |
|||
return m_serverCharacter; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 25fa6461eb4b4fc4e8ec96487989fb8b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom.Server |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// Base class for all AIStates
|
|||
/// </summary>
|
|||
public abstract class AIState |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates whether this state thinks it can become/continue to be the active state.
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public abstract bool IsEligible(); |
|||
|
|||
/// <summary>
|
|||
/// Called once each time this state becomes the active state.
|
|||
/// (This will only happen if IsEligible() has returned true for this state)
|
|||
/// </summary>
|
|||
public abstract void Initialize(); |
|||
|
|||
/// <summary>
|
|||
/// Called once per frame while this is the active state. Initialize() will have
|
|||
/// already been called prior to Update() being called
|
|||
/// </summary>
|
|||
public abstract void Update(); |
|||
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: cebcb66dc48def84abd1cfbc995f7c6b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom.Server |
|||
{ |
|||
public class AttackAIState : AIState |
|||
{ |
|||
private AIBrain m_brain; |
|||
private ActionPlayer m_actionPlayer; |
|||
private ServerCharacter m_foe; |
|||
private ActionType m_curAttackAction; |
|||
|
|||
public AttackAIState(AIBrain brain, ActionPlayer actionPlayer) |
|||
{ |
|||
m_brain = brain; |
|||
m_actionPlayer = actionPlayer; |
|||
} |
|||
|
|||
public override bool IsEligible() |
|||
{ |
|||
return m_foe != null || ChooseFoe() != null; |
|||
} |
|||
|
|||
public override void Initialize() |
|||
{ |
|||
m_curAttackAction = ActionType.TANK_BASEATTACK; |
|||
|
|||
// clear any old foe info; we'll choose a new one in Update()
|
|||
m_foe = null; |
|||
} |
|||
|
|||
public override void Update() |
|||
{ |
|||
if (!m_brain.IsAppropriateFoe(m_foe)) |
|||
{ |
|||
// time for a new foe!
|
|||
m_foe = ChooseFoe(); |
|||
// whatever we used to be doing, stop that. New plan is coming!
|
|||
m_actionPlayer.ClearActions(); |
|||
} |
|||
|
|||
// if we're out of foes, stop! IsEligible() will now return false so we'll soon switch to a new state
|
|||
if (!m_foe) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// see if we're already chasing or attacking our active foe!
|
|||
if (m_actionPlayer.GetActiveActionInfo(out var info)) |
|||
{ |
|||
if (info.ActionTypeEnum == ActionType.GENERAL_CHASE) |
|||
{ |
|||
foreach (var id in info.TargetIds) |
|||
{ |
|||
if (id == m_foe.NetworkId) |
|||
{ |
|||
// yep we're chasing our foe; all set! (The attack is enqueued after it)
|
|||
return; |
|||
} |
|||
} |
|||
} |
|||
else if (info.ActionTypeEnum == m_curAttackAction) |
|||
{ |
|||
foreach (var id in info.TargetIds) |
|||
{ |
|||
if (id == m_foe.NetworkId) |
|||
{ |
|||
// yep we're attacking our foe; all set!
|
|||
return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Choose whether we can attack our foe directly, or if we need to get closer first
|
|||
var attackInfo = GetCurrentAttackInfo(); |
|||
Vector3 diff = m_brain.GetMyServerCharacter().transform.position - m_foe.transform.position; |
|||
if (diff.sqrMagnitude < attackInfo.Range * attackInfo.Range) |
|||
{ |
|||
// yes! We are in range
|
|||
var attack_data = new ActionRequestData |
|||
{ |
|||
ActionTypeEnum = m_curAttackAction, |
|||
Amount = attackInfo.Amount, |
|||
ShouldQueue = false, |
|||
TargetIds = new ulong[] { m_foe.NetworkId } |
|||
}; |
|||
m_actionPlayer.PlayAction(ref attack_data); |
|||
} |
|||
else |
|||
{ |
|||
// we are not in range so we will need to chase them
|
|||
var chase_data = new ActionRequestData |
|||
{ |
|||
ActionTypeEnum = ActionType.GENERAL_CHASE, |
|||
Amount = attackInfo.Range, |
|||
ShouldQueue = false, |
|||
TargetIds = new ulong[] { m_foe.NetworkId } |
|||
}; |
|||
m_actionPlayer.PlayAction(ref chase_data); |
|||
|
|||
// queue up the actual attack for when we're in range
|
|||
var attack_data = new ActionRequestData |
|||
{ |
|||
ActionTypeEnum = m_curAttackAction, |
|||
Amount = attackInfo.Amount, |
|||
ShouldQueue = true, |
|||
TargetIds = new ulong[] { m_foe.NetworkId } |
|||
}; |
|||
m_actionPlayer.PlayAction(ref attack_data); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Picks the most appropriate foe for us to attack right now, or null if none are appropriate
|
|||
/// (Currently just chooses the foe closest to us in distance)
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
private ServerCharacter ChooseFoe() |
|||
{ |
|||
Vector3 myPosition = m_brain.GetMyServerCharacter().transform.position; |
|||
|
|||
float closestDistanceSqr = int.MaxValue; |
|||
ServerCharacter closestFoe = null; |
|||
foreach (var foe in m_brain.GetHatedEnemies()) |
|||
{ |
|||
float distanceSqr = (myPosition - foe.transform.position).sqrMagnitude; |
|||
if (distanceSqr < closestDistanceSqr) |
|||
{ |
|||
closestDistanceSqr = distanceSqr; |
|||
closestFoe = foe; |
|||
} |
|||
} |
|||
return closestFoe; |
|||
} |
|||
|
|||
|
|||
private ActionDescription GetCurrentAttackInfo() |
|||
{ |
|||
List<ActionDescription> actionLevels = ActionData.ActionDescriptions[ m_curAttackAction ]; |
|||
int level = 0; // FIXME: pull this level from some character state var?
|
|||
return actionLevels[ level ]; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 53ec60f5ec1af1549b282973368a6f19 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace BossRoom.Server |
|||
{ |
|||
public class IdleAIState : AIState |
|||
{ |
|||
private AIBrain m_brain; |
|||
|
|||
public IdleAIState(AIBrain brain) |
|||
{ |
|||
m_brain = brain; |
|||
} |
|||
|
|||
public override bool IsEligible() |
|||
{ |
|||
return m_brain.GetHatedEnemies().Count == 0; |
|||
} |
|||
|
|||
public override void Initialize() |
|||
{ |
|||
} |
|||
|
|||
public override void Update() |
|||
{ |
|||
// while idle, we are scanning for jerks to hate
|
|||
DetectFoes(); |
|||
} |
|||
|
|||
protected void DetectFoes() |
|||
{ |
|||
float detectionRange = m_brain.GetMyServerCharacter().DetectRange; |
|||
// we are doing this check every Update, so we'll use square-magnitude distance to avoid the expensive sqrt (that's implicit in Vector3.magnitude)
|
|||
float detectionRangeSqr = detectionRange * detectionRange; |
|||
Vector3 position = m_brain.GetMyServerCharacter().transform.position; |
|||
|
|||
foreach (ServerCharacter character in ServerCharacter.GetAllActiveServerCharacters()) |
|||
{ |
|||
if (m_brain.IsAppropriateFoe(character) |
|||
&& (character.transform.position - position).sqrMagnitude <= detectionRangeSqr) |
|||
{ |
|||
m_brain.Hate(character); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e12014cb6bdbcb54eb08524c9d9301cc |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue