Megacity demo game for UOS
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

358 行
15 KiB

using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.MegaCity.UI;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
using Random = Unity.Mathematics.Random;
namespace Unity.MegaCity.Audio
{
[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation | WorldSystemFilterFlags.ClientSimulation, WorldSystemFilterFlags.LocalSimulation | WorldSystemFilterFlags.ClientSimulation)]
public partial class AudioFrame : ComponentSystemGroup
{
}
[UpdateInGroup(typeof(AudioFrame))]
public partial class SoundPoolSystem : SystemBase
{
static ProfilerMarker ProfilerMarkerCollectData = new ProfilerMarker("SoundPoolSystem.CollectData");
static ProfilerMarker ProfilerMarkerQueryTrees = new ProfilerMarker("SoundPoolSystem.QueryTrees");
static ProfilerMarker ProfilerMarkerA = new ProfilerMarker("SoundPoolSystem.A");
static ProfilerMarker ProfilerMarkerB = new ProfilerMarker("SoundPoolSystem.B");
static ProfilerMarker ProfilerMarkerC = new ProfilerMarker("SoundPoolSystem.C");
static ProfilerMarker ProfilerMarkerD = new ProfilerMarker("SoundPoolSystem.D");
private AudioSystemSettings m_AudioSettings;
private Scene m_AdditiveScene;
private SoundManager m_SoundManager;
private AudioTree [] m_AudioTrees;
private AudioSource [] m_AudioSources;
private NativeArray<ECSoundEmitterDefinition> m_Definitions;
private EntityQuery m_AllEmitterBlobsQuery;
private EntityQuery m_AddedEmitterBlobsQuery;
private EntityQuery m_RemovedEmitterBlobsQuery;
private Random m_Random;
private int m_DirtyTrees;
private int m_UpdateRoundRobin;
protected override void OnCreate()
{
base.OnCreate();
RequireForUpdate<AudioSystemSettings>();
var entityQueryDescAll = new EntityQueryDesc { All = new[] {ComponentType.ReadOnly<AudioBlobRef>()}};
RequireForUpdate(GetEntityQuery(entityQueryDescAll));
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
m_AdditiveScene = SceneManager.GetSceneByName("AdditiveBuildingAudioPoolScene");
}
else
#endif
{
m_AdditiveScene = SceneManager.CreateScene("AdditiveVehicleBuildingPoolScenePlaymode");
}
m_AllEmitterBlobsQuery = GetEntityQuery(new EntityQueryDesc
{
All = new[] { ComponentType.ReadOnly<AudioBlobRef>() }
});
m_AddedEmitterBlobsQuery = GetEntityQuery(new EntityQueryDesc
{
None = new [] { ComponentType.ReadOnly<TreeDataCollected>() },
All = new[] { ComponentType.ReadOnly<AudioBlobRef>() }
});
m_RemovedEmitterBlobsQuery = GetEntityQuery(new EntityQueryDesc
{
None = new[] { ComponentType.ReadOnly<AudioBlobRef>() },
All = new[] { ComponentType.ReadOnly<TreeDataCollected>() },
});
m_Random = new Random((uint)SystemAPI.Time.ElapsedTime + (uint)DateTime.Now.Ticks);
}
protected override void OnUpdate()
{
if (MainMenu.Instance != null && MainMenu.Instance.IsVisible && m_AudioSources != null)
{
foreach (var audioSource in m_AudioSources)
{
audioSource.Stop();
}
return;
}
if (m_SoundManager == null)
{
m_AudioSettings = SystemAPI.GetSingleton<AudioSystemSettings>();
m_SoundManager = Object.FindObjectOfType<SoundManager>();
var poolSize = m_SoundManager.m_Clips.Length * m_AudioSettings.ClosestEmitterPerClipCount;
m_AudioSources = new AudioSource[poolSize];
for (int i = 0; i < poolSize; i++)
{
var clipIdx = i / m_AudioSettings.ClosestEmitterPerClipCount;
var instance = i % m_AudioSettings.ClosestEmitterPerClipCount;
var gameObject = new GameObject($"AudioSource (clip {clipIdx} / instance {instance}");
m_AudioSources[i] = gameObject.AddComponent<AudioSource>();
m_AudioSources[i].clip = m_SoundManager.m_Clips[clipIdx];
m_AudioSources[i].loop = true;
m_AudioSources[i].spatialBlend = 1f;
m_AudioSources[i].dopplerLevel = 0f;
m_AudioSources[i].rolloffMode = AudioRolloffMode.Linear;
m_AudioSources[i].pitch = 1f + instance / 100f;
m_AudioSources[i].maxDistance = m_AudioSettings.MaxDistance;
m_AudioSources[i].playOnAwake = false;
m_AudioSources[i].outputAudioMixerGroup = AudioMaster.Instance.soundFX;
SceneManager.MoveGameObjectToScene(gameObject, m_AdditiveScene);
}
m_Definitions = new NativeArray<ECSoundEmitterDefinition>(m_SoundManager.m_SoundDefinitions.Length, Allocator.Persistent);
m_AudioTrees = new AudioTree[m_Definitions.Length];
for (int i = 0; i < m_Definitions.Length; i++)
{
m_Definitions[i] = m_SoundManager.m_SoundDefinitions[i].data;
var maxResultsPerAudioTree = m_AudioSources.Length / m_Definitions.Length;
m_AudioTrees[i].Initialize(maxResultsPerAudioTree);
}
}
if (UnityEngine.Camera.main == null)
{
return;
}
var camPos = UnityEngine.Camera.main.transform.position;
using (ProfilerMarkerQueryTrees.Auto())
{
// Do the queries before updating the trees, so we don't have to complete
// the dependency on the kd-trees during the same frame.
// This introduces a frame of latency but it doesn't matter because the
// sounds being loaded and unloaded are far from the camera anyway.
Dependency = GetTreeResults(camPos, Dependency);
}
using (ProfilerMarkerCollectData.Auto())
{
UpdateTreeData();
}
var currentAudioSourceIndex = 0;
for (int i = 0; i < m_AudioTrees.Length; i++)
{
if (!m_AudioTrees[i].Results.IsCreated)
continue;
var points = m_AudioTrees[i].Results;
var pointsCount = m_AudioTrees[i].ResultsCount.Value;
for (int j = 0; j < pointsCount; j++)
{
var position = points[j].position;
var definition = m_Definitions[i];
// Only assign a clip and recycle the Audio Source if the current position is out of the camera range, otherwise keep playing this current clip.
if (math.distance(m_AudioSources[currentAudioSourceIndex].transform.position, camPos) > definition.maxDist)
{
var clipIndex = GetRandomIndex(definition.soundPlayerIndexMin, definition.soundPlayerIndexMax, m_SoundManager.m_Probabilities);
var minMaxVolume = m_SoundManager.m_Volumes[clipIndex];
var clip = m_SoundManager.m_Clips[clipIndex];
var volume = m_Random.NextFloat(minMaxVolume.x, minMaxVolume.y);
var randomStartTime = m_Random.NextFloat(0f, clip.length);
m_AudioSources[currentAudioSourceIndex].transform.position = m_AudioTrees[i].Results[j].position;
m_AudioSources[currentAudioSourceIndex].maxDistance = definition.maxDist;
m_AudioSources[currentAudioSourceIndex].volume = definition.volume + volume;
m_AudioSources[currentAudioSourceIndex].clip = clip;
m_AudioSources[currentAudioSourceIndex].time = randomStartTime;
if (!m_AudioSources[currentAudioSourceIndex].isPlaying)
{
m_AudioSources[currentAudioSourceIndex].Play();
}
}
if(m_AudioSettings.DebugMode)
Debug.DrawLine(camPos, position, m_AudioTrees[i].DebugLineColor);
currentAudioSourceIndex++;
}
}
}
public int GetRandomIndex(int min, int max, float[] priorities)
{
// Create a list of available clip indices within the given range
var availableIndices = new List<int>();
for (int i = min; i <= max; i++)
{
availableIndices.Add(i);
}
// Calculate the total priority score
var totalPriority = 0f;
for (int i = 0; i < priorities.Length; i++)
{
totalPriority += priorities[i];
}
// Create a dictionary of clip index and probability pairs
var clipProbabilities = new Dictionary<int, float>();
foreach (int index in availableIndices)
{
float clipPriority = priorities[index];
float clipProbability = clipPriority / totalPriority;
clipProbabilities.Add(index, clipProbability);
}
// Use the probability distribution to randomly select a clip index
var randomValue = m_Random.NextFloat();
var cumulativeProbability = 0f;
foreach (KeyValuePair<int, float> pair in clipProbabilities)
{
cumulativeProbability += pair.Value;
if (randomValue < cumulativeProbability)
{
return pair.Key;
}
}
// If no clip index was selected, return the minimum index
return min;
}
protected override void OnDestroy()
{
if (m_Definitions.IsCreated)
m_Definitions.Dispose();
if (m_AudioTrees != null)
for (int i=0;i < m_AudioTrees.Length; i++)
{
m_AudioTrees[i].Dispose();
}
}
private void UpdateTreeData()
{
// The kd-trees are updated in a round-robin fashion. If anything changed, the amount of trees to be
// updated is set the count of trees. And every frame, one three (one sound definition) will be updated.
var defCount = m_Definitions.Length;
if (!m_AddedEmitterBlobsQuery.IsEmptyIgnoreFilter)
{
// Tag with system state component.
EntityManager.AddComponent<TreeDataCollected>(m_AddedEmitterBlobsQuery);
m_DirtyTrees = defCount;
}
if (!m_RemovedEmitterBlobsQuery.IsEmptyIgnoreFilter)
{
// System state component cleanup.
EntityManager.RemoveComponent<TreeDataCollected>(m_RemovedEmitterBlobsQuery);
m_DirtyTrees = defCount;
}
if (m_DirtyTrees == 0)
{
return;
}
ProfilerMarkerA.Begin();
var blobs = m_AllEmitterBlobsQuery.ToComponentDataArray<AudioBlobRef>(Allocator.TempJob);
var totalEmitterCountPerDefinition = new NativeArray<int>(defCount, Allocator.Temp);
Job.WithCode(() =>
{
for (int blobIdx = 0; blobIdx < blobs.Length; blobIdx++)
{
ref var indexBeg = ref blobs[blobIdx].Data.Value.DefIndexBeg;
ref var indexEnd = ref blobs[blobIdx].Data.Value.DefIndexEnd;
for (int i = 0; i < indexBeg.Length; i++)
{
// NB - the index arrays can be smaller than the definition array, in cases where
// scenes do not contain emitters matching the definitions at the end of the array.
totalEmitterCountPerDefinition[i] += indexEnd[i] - indexBeg[i];
}
}
}).Run();
ProfilerMarkerA.End();
var treeParams = KDTree.DefaultKDTreeParams;
treeParams.AdditionalDepthToAllocate = 2;
int defIdx = m_UpdateRoundRobin;
ref var tree = ref m_AudioTrees[defIdx].Tree;
if (tree.IsCreated)
{
ProfilerMarkerD.Begin();
tree.Dispose();
ProfilerMarkerD.End();
}
var emitterCount = totalEmitterCountPerDefinition[defIdx];
if (emitterCount != 0)
{
ProfilerMarkerB.Begin();
tree = new KDTree(emitterCount, Allocator.Persistent, treeParams);
ProfilerMarkerB.End();
var dependency = new CopyDataToTree
{
blobs = blobs,
defIdx = defIdx,
tree = tree
}.Schedule();
ProfilerMarkerC.Begin();
dependency = tree.BuildTree(emitterCount, dependency);
dependency = blobs.Dispose(dependency);
Dependency = JobHandle.CombineDependencies(Dependency, dependency);
ProfilerMarkerC.End();
}
else
{
blobs.Dispose();
}
m_DirtyTrees -= 1;
m_UpdateRoundRobin = (m_UpdateRoundRobin + 1) % defCount;
}
private JobHandle GetTreeResults(Vector3 camPos, JobHandle jobHandle)
{
for (int i = 0; i < m_AudioTrees.Length; i++)
{
if (!m_AudioTrees[i].Tree.IsCreated)
continue;
var definition = m_Definitions[i];
var searchJob = new GetEntriesInRangeJob
{
QueryPosition = camPos,
Range = definition.maxDist,
Tree = m_AudioTrees[i].Tree,
Neighbours = m_AudioTrees[i].Results,
ResultsCount = m_AudioTrees[i].ResultsCount,
};
jobHandle = searchJob.Schedule(jobHandle);
jobHandle.Complete();
}
return jobHandle;
}
}
}