浏览代码

Feature/wave spawner (#30)

wave spawner for networked objects with modifiable wave params, state tracked with networkspawnerstate, spawner prefab, spawner visualization tracks hits
/main
GitHub 4 年前
当前提交
83af5e62
共有 8 个文件被更改,包括 601 次插入0 次删除
  1. 232
      Assets/BossRoom/Prefabs/EnemySpawner.prefab
  2. 7
      Assets/BossRoom/Prefabs/EnemySpawner.prefab.meta
  3. 54
      Assets/BossRoom/Scripts/Client/ClientSpawnerVisualization.cs
  4. 11
      Assets/BossRoom/Scripts/Client/ClientSpawnerVisualization.cs.meta
  5. 258
      Assets/BossRoom/Scripts/Server/ServerWaveSpawner.cs
  6. 11
      Assets/BossRoom/Scripts/Server/ServerWaveSpawner.cs.meta
  7. 17
      Assets/BossRoom/Scripts/Shared/NetworkSpawnerState.cs
  8. 11
      Assets/BossRoom/Scripts/Shared/NetworkSpawnerState.cs.meta

232
Assets/BossRoom/Prefabs/EnemySpawner.prefab


%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &4132435922419856147
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 9136068542165043480}
- component: {fileID: 8205974096611502342}
m_Layer: 0
m_Name: Client_Visuals
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &9136068542165043480
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4132435922419856147}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 3784477753691126980}
m_Father: {fileID: 8727022540156222958}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8205974096611502342
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4132435922419856147}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 65e25a152b731564ebb6096eb5e9f0ed, type: 3}
m_Name:
m_EditorClassIdentifier:
m_NetworkSpawnerState: {fileID: 9132488263322830174}
m_Animator: {fileID: 0}
--- !u!1 &6205854018081152875
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8727022540156222958}
- component: {fileID: 7323075965458333149}
- component: {fileID: -1287160331463954971}
- component: {fileID: 4844841199312666291}
- component: {fileID: 9132488263322830174}
m_Layer: 6
m_Name: EnemySpawner
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &8727022540156222958
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6205854018081152875}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children:
- {fileID: 9136068542165043480}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!136 &7323075965458333149
CapsuleCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6205854018081152875}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
m_Radius: 0.5
m_Height: 2
m_Direction: 1
m_Center: {x: 0, y: 0, z: 0}
--- !u!114 &-1287160331463954971
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6205854018081152875}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier:
NetworkedInstanceId: 0
PrefabHash: 15589198230175896383
PrefabHashGenerator: Spawner
AlwaysReplicateAsRoot: 0
DontDestroyWithOwner: 0
--- !u!114 &4844841199312666291
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6205854018081152875}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 49b962917cae99f48a2a598485996b8d, type: 3}
m_Name:
m_EditorClassIdentifier:
m_NetworkSpawnerState: {fileID: 9132488263322830174}
m_NetworkedPrefab: {fileID: -7852138837387268381, guid: f520174fcd7d61441aa46a9923655dcd, type: 3}
m_BlockingMask:
serializedVersion: 2
m_Bits: 1
m_PlayerProximityValidationTimestep: 2
m_NumberOfWaves: 2
m_SpawnsPerWave: 2
m_TimeBetweenSpawns: 0.5
m_TimeBetweenWaves: 5
m_RestartDelay: 10
m_ProximityDistance: 30
m_DormantCooldown: 10
--- !u!114 &9132488263322830174
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6205854018081152875}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4cfeccaf46c46e1458c3f7351d6753b3, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &6882351788430859438
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3784477753691126980}
- component: {fileID: 1033544756360169842}
- component: {fileID: 4871652750507758710}
m_Layer: 0
m_Name: Capsule
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3784477753691126980
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6882351788430859438}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 9136068542165043480}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &1033544756360169842
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6882351788430859438}
m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &4871652750507758710
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6882351788430859438}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}

7
Assets/BossRoom/Prefabs/EnemySpawner.prefab.meta


fileFormatVersion: 2
guid: a6647793a8bc7c846abba94be1d257e5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

54
Assets/BossRoom/Scripts/Client/ClientSpawnerVisualization.cs


using System;
using MLAPI;
using UnityEngine;
namespace BossRoom
{
public class ClientSpawnerVisualization : NetworkedBehaviour
{
[SerializeField]
NetworkSpawnerState m_NetworkSpawnerState;
// TODO: Integrate visuals (GOMPS-123)
[SerializeField]
Animator m_Animator;
public override void NetworkStart()
{
if (!IsClient)
{
enabled = false;
return;
}
m_NetworkSpawnerState.Broken.OnValueChanged += BrokenStateChanged;
m_NetworkSpawnerState.HitPoints.OnValueChanged += HitPointsChanged;
}
void BrokenStateChanged(bool previousValue, bool newValue)
{
if (newValue)
{
if (previousValue == false)
{
// spawner is newly broken
}
}
else
{
if (previousValue)
{
// spawner is newly revived
}
}
}
void HitPointsChanged(int previousValue, int newValue)
{
if (previousValue > newValue && newValue > 0)
{
// received a hit
}
}
}
}

11
Assets/BossRoom/Scripts/Client/ClientSpawnerVisualization.cs.meta


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

258
Assets/BossRoom/Scripts/Server/ServerWaveSpawner.cs


using System.Collections;
using UnityEngine;
using MLAPI;
namespace BossRoom.Server
{
/// <summary>
/// Component responsible for spawning prefab clones in waves on the server.
/// </summary>
[RequireComponent(typeof(Collider))]
public class ServerWaveSpawner : NetworkedBehaviour
{
[SerializeField]
NetworkSpawnerState m_NetworkSpawnerState;
// amount of hits it takes to break any spawner
const int k_MaxHealth = 3;
// networked object that will be spawned in waves
[SerializeField]
NetworkedObject m_NetworkedPrefab;
// cache reference to our own transform
Transform m_Transform;
// track wave index and reset once all waves are complete
int m_WaveIndex;
// keep reference to our wave spawning coroutine
Coroutine m_WaveSpawning;
// cache array of RaycastHit as it will be reused for player visibility
RaycastHit[] m_Hit;
int m_PlayerLayerMask;
// cache Collider array of OverlapSphere results for player proximity
Collider[] m_Colliders;
[Tooltip("Select which layers will block visibility.")]
[SerializeField]
LayerMask m_BlockingMask;
[Tooltip("Time between player distance & visibility scans, in seconds.")]
[SerializeField]
float m_PlayerProximityValidationTimestep;
[Header("Wave parameters")]
[Tooltip("Total number of waves.")]
[SerializeField]
int m_NumberOfWaves;
[Tooltip("Number of spawns per wave.")]
[SerializeField]
int m_SpawnsPerWave;
[Tooltip("Time between individual spawns, in seconds.")]
[SerializeField]
float m_TimeBetweenSpawns;
[Tooltip("Time between waves, in seconds.")]
[SerializeField]
float m_TimeBetweenWaves;
[Tooltip("Once last wave is spawned, the spawner waits this long to restart wave spawns, in seconds.")]
[SerializeField]
float m_RestartDelay;
[Tooltip("A player must be withing this distance to commence first wave spawn.")]
[SerializeField]
float m_ProximityDistance;
[Tooltip("After being broken, the spawner waits this long to restart wave spawns, in seconds.")]
[SerializeField]
float m_DormantCooldown;
void Awake()
{
m_Transform = transform;
m_PlayerLayerMask = LayerMask.GetMask("PCs");
}
public override void NetworkStart()
{
base.NetworkStart();
if (!IsServer)
{
enabled = false;
return;
}
ReviveSpawner();
m_Hit = new RaycastHit[1];
m_Colliders = new Collider[8];
StartCoroutine(ValidatePlayersProximity(StartWaveSpawning));
}
/// <summary>
/// Coroutine for continually validating proximity to players and invoking an action when any is near.
/// </summary>
/// <param name="validationAction"></param>
/// <returns></returns>
IEnumerator ValidatePlayersProximity(System.Action validationAction)
{
while (true)
{
if (m_NetworkSpawnerState.Broken.Value)
{
yield return new WaitForSeconds(m_DormantCooldown);
ReviveSpawner();
}
if (m_WaveSpawning == null)
{
if (IsAnyPlayerNearbyAndVisible())
{
validationAction();
}
}
else
{
// do nothing, a wave spawning routine is currently underway
}
yield return new WaitForSeconds(m_PlayerProximityValidationTimestep);
}
}
void StartWaveSpawning()
{
StopWaveSpawning();
m_WaveSpawning = StartCoroutine(SpawnWaves());
}
void StopWaveSpawning()
{
if (m_WaveSpawning != null)
{
StopCoroutine(m_WaveSpawning);
}
m_WaveSpawning = null;
}
/// <summary>
/// Coroutine for spawning prefabs clones in waves, waiting a duration before spawning a new wave.
/// Once all waves are completed, it waits a restart time before termination.
/// </summary>
/// <returns></returns>
IEnumerator SpawnWaves()
{
m_WaveIndex = 0;
while (m_WaveIndex < m_NumberOfWaves)
{
yield return SpawnWave();
yield return new WaitForSeconds(m_TimeBetweenWaves);
}
yield return new WaitForSeconds(m_RestartDelay);
m_WaveSpawning = null;
}
/// <summary>
/// Coroutine that spawns a wave of prefab clones, with some time between spawns.
/// </summary>
/// <returns></returns>
IEnumerator SpawnWave()
{
for (int i = 0; i < m_SpawnsPerWave; i++)
{
SpawnPrefab();
yield return new WaitForSeconds(m_TimeBetweenSpawns);
}
m_WaveIndex++;
}
/// <summary>
/// Spawn a NetworkedObject prefab clone.
/// </summary>
void SpawnPrefab()
{
if (m_NetworkedPrefab == null)
{
throw new System.ArgumentNullException("m_NetworkedPrefab");
}
// spawn clone right in front of spawner
var spawnPosition = m_Transform.position + m_Transform.forward;
var clone = Instantiate(m_NetworkedPrefab, spawnPosition, Quaternion.identity);
if (!clone.IsSpawned)
{
clone.Spawn();
}
}
/// <summary>
/// Determines whether any player is within range & visible through RaycastNonAlloc check.
/// </summary>
/// <returns> True if visible and within range, else false. </returns>
bool IsAnyPlayerNearbyAndVisible()
{
var spawnerPosition = m_Transform.position;
var ray = new Ray();
int hits = Physics.OverlapSphereNonAlloc(spawnerPosition,
m_ProximityDistance, m_Colliders, m_PlayerLayerMask);
if (hits == 0)
{
return false;
}
// iterate through players and only return true if a player is in range
// and is not occluded by a blocking collider.
foreach (var playerCollider in m_Colliders)
{
var playerPosition = playerCollider.transform.position;
var direction = playerPosition - spawnerPosition;
ray.origin = spawnerPosition;
ray.direction = direction;
var hit = Physics.RaycastNonAlloc(ray, m_Hit,
Mathf.Min(direction.magnitude, m_ProximityDistance),m_BlockingMask);
if (hit == 0)
{
return true;
}
}
return false;
}
void ReviveSpawner()
{
m_NetworkSpawnerState.HitPoints.Value = k_MaxHealth;
m_NetworkSpawnerState.Broken.Value = false;
}
// TODO: David will create interface hookup for receiving hits on non-NPC/PC objects (GOMPS-ID TBD)
void ReceiveHP(ServerCharacter inflicter, int HP)
{
if (!IsServer)
{
return;
}
m_NetworkSpawnerState.HitPoints.Value += HP;
if (m_NetworkSpawnerState.HitPoints.Value <= 0)
{
m_NetworkSpawnerState.Broken.Value = true;
StopWaveSpawning();
}
}
}
}

11
Assets/BossRoom/Scripts/Server/ServerWaveSpawner.cs.meta


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

17
Assets/BossRoom/Scripts/Shared/NetworkSpawnerState.cs


using MLAPI;
using MLAPI.NetworkedVar;
using UnityEngine;
namespace BossRoom
{
/// <summary>
/// Contains all NetworkedVars of a spawner.
/// This component is present on both client and server objects.
/// </summary>
public class NetworkSpawnerState : NetworkedBehaviour
{
public NetworkedVarInt HitPoints { get; } = new NetworkedVarInt();
public NetworkedVarBool Broken { get; } = new NetworkedVarBool();
}
}

11
Assets/BossRoom/Scripts/Shared/NetworkSpawnerState.cs.meta


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