@musashishao/agent-kit 1.9.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/game-developer.md +4 -4
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/project-planner.md +67 -0
- package/.agent/agents/unity-mobile-master.md +949 -0
- package/.agent/mcp/config/registry.json +65 -51
- package/.agent/mcp/servers/notebooklm/README.md +114 -0
- package/.agent/mcp/servers/notebooklm/package.json +35 -0
- package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
- package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
- package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
- package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
- package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
- package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
- package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
- package/.agent/mcp-gateway/README.md +169 -20
- package/.agent/mcp-gateway/package.json +22 -7
- package/.agent/mcp-gateway/src/auth/index.ts +55 -0
- package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
- package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
- package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
- package/.agent/mcp-gateway/src/index.ts +252 -105
- package/.agent/mcp-gateway/src/observability/index.ts +5 -0
- package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
- package/.agent/mcp-gateway/src/transports/index.ts +5 -0
- package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
- package/.agent/rules/CODEX.md +89 -0
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +25 -0
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +33 -141
- package/.agent/rules/REF_SKILLS.md +116 -0
- package/.agent/rules/REF_WORKFLOWS.md +81 -0
- package/.agent/scripts/ak_cli.py +106 -5
- package/.agent/scripts/memory_manager.py +48 -9
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
- package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
- package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
- package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
- package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
- package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
- package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
- package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
- package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
- package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
- package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
- package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
- package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
- package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
- package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
- package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
- package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
- package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
- package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
- package/.agent/skills/game-development/liveops/SKILL.md +308 -0
- package/.agent/skills/game-development/localization/SKILL.md +286 -0
- package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
- package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
- package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
- package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
- package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
- package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
- package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
- package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
- package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
- package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/mcp-composition/SKILL.md +362 -0
- package/.agent/skills/mcp-observability/SKILL.md +323 -0
- package/.agent/skills/mcp-security/SKILL.md +314 -0
- package/.agent/skills/trust-spectrum/SKILL.md +291 -0
- package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
- package/.agent/templates/AGENTS.game.md +63 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
- package/.agent/workflows/ai-agent.md +2 -0
- package/.agent/workflows/autofix.md +1 -0
- package/.agent/workflows/brainstorm.md +1 -0
- package/.agent/workflows/context.md +1 -0
- package/.agent/workflows/create.md +39 -8
- package/.agent/workflows/dashboard.md +1 -0
- package/.agent/workflows/debug.md +14 -0
- package/.agent/workflows/deploy.md +14 -0
- package/.agent/workflows/enhance.md +44 -0
- package/.agent/workflows/gamekit-init.md +177 -0
- package/.agent/workflows/gamekit-launch.md +338 -0
- package/.agent/workflows/gamekit-plan.md +204 -0
- package/.agent/workflows/gamekit-qa.md +153 -0
- package/.agent/workflows/gamekit-spec.md +243 -0
- package/.agent/workflows/gamekit-tasks.md +208 -0
- package/.agent/workflows/marketing.md +2 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +2 -0
- package/.agent/workflows/plan.md +42 -0
- package/.agent/workflows/preview.md +1 -0
- package/.agent/workflows/quality.md +1 -0
- package/.agent/workflows/saas.md +2 -0
- package/.agent/workflows/spec.md +42 -0
- package/.agent/workflows/status.md +1 -0
- package/.agent/workflows/test.md +14 -0
- package/.agent/workflows/ui-ux-pro-max.md +1 -0
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/.agent/agents/game-asset-curator.md +0 -317
- package/.agent/agents/game-narrative-designer.md +0 -310
- package/.agent/agents/game-qa-agent.md +0 -441
- package/.agent/workflows/game-prototype.md +0 -154
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -141
- package/docs/MIGRATION_GUIDE_V1.9.md +0 -55
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: game-audio-advanced
|
|
3
|
+
description: Advanced game audio systems. Spatial audio, music systems, sound pooling, FMOD/Wwise basics, footstep systems.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, WebSearch
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Advanced Game Audio Skill
|
|
8
|
+
|
|
9
|
+
> Create immersive audio experiences for games.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Building audio manager systems
|
|
16
|
+
- Implementing spatial 3D audio
|
|
17
|
+
- Creating adaptive music
|
|
18
|
+
- Setting up sound pooling
|
|
19
|
+
- Designing footstep systems
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Audio Manager
|
|
24
|
+
|
|
25
|
+
### Singleton Pattern
|
|
26
|
+
|
|
27
|
+
```csharp
|
|
28
|
+
public class AudioManager : MonoBehaviour
|
|
29
|
+
{
|
|
30
|
+
public static AudioManager Instance { get; private set; }
|
|
31
|
+
|
|
32
|
+
[Header("Mixers")]
|
|
33
|
+
[SerializeField] private AudioMixerGroup masterGroup;
|
|
34
|
+
[SerializeField] private AudioMixerGroup musicGroup;
|
|
35
|
+
[SerializeField] private AudioMixerGroup sfxGroup;
|
|
36
|
+
[SerializeField] private AudioMixerGroup voiceGroup;
|
|
37
|
+
|
|
38
|
+
[Header("Sources")]
|
|
39
|
+
[SerializeField] private AudioSource musicSource;
|
|
40
|
+
[SerializeField] private AudioSource ambienceSource;
|
|
41
|
+
[SerializeField] private int sfxPoolSize = 20;
|
|
42
|
+
|
|
43
|
+
private List<AudioSource> _sfxPool = new();
|
|
44
|
+
private Dictionary<string, AudioClip> _clipCache = new();
|
|
45
|
+
|
|
46
|
+
void Awake()
|
|
47
|
+
{
|
|
48
|
+
if (Instance != null)
|
|
49
|
+
{
|
|
50
|
+
Destroy(gameObject);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
Instance = this;
|
|
54
|
+
DontDestroyOnLoad(gameObject);
|
|
55
|
+
|
|
56
|
+
InitializeSFXPool();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private void InitializeSFXPool()
|
|
60
|
+
{
|
|
61
|
+
for (int i = 0; i < sfxPoolSize; i++)
|
|
62
|
+
{
|
|
63
|
+
var go = new GameObject($"SFX_Source_{i}");
|
|
64
|
+
go.transform.SetParent(transform);
|
|
65
|
+
|
|
66
|
+
var source = go.AddComponent<AudioSource>();
|
|
67
|
+
source.outputAudioMixerGroup = sfxGroup;
|
|
68
|
+
source.playOnAwake = false;
|
|
69
|
+
|
|
70
|
+
_sfxPool.Add(source);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#region SFX
|
|
75
|
+
|
|
76
|
+
public void PlaySFX(AudioClip clip, float volume = 1f)
|
|
77
|
+
{
|
|
78
|
+
if (clip == null) return;
|
|
79
|
+
|
|
80
|
+
var source = GetAvailableSource();
|
|
81
|
+
if (source != null)
|
|
82
|
+
{
|
|
83
|
+
source.clip = clip;
|
|
84
|
+
source.volume = volume;
|
|
85
|
+
source.spatialBlend = 0f; // 2D
|
|
86
|
+
source.Play();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public void PlaySFX(string clipPath, float volume = 1f)
|
|
91
|
+
{
|
|
92
|
+
var clip = LoadClip(clipPath);
|
|
93
|
+
PlaySFX(clip, volume);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public void PlaySFXAtPosition(AudioClip clip, Vector3 position, float volume = 1f)
|
|
97
|
+
{
|
|
98
|
+
if (clip == null) return;
|
|
99
|
+
|
|
100
|
+
var source = GetAvailableSource();
|
|
101
|
+
if (source != null)
|
|
102
|
+
{
|
|
103
|
+
source.transform.position = position;
|
|
104
|
+
source.clip = clip;
|
|
105
|
+
source.volume = volume;
|
|
106
|
+
source.spatialBlend = 1f; // 3D
|
|
107
|
+
source.Play();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public AudioSource PlayLoopingAtPosition(AudioClip clip, Vector3 position, float volume = 1f)
|
|
112
|
+
{
|
|
113
|
+
var source = GetAvailableSource();
|
|
114
|
+
if (source != null)
|
|
115
|
+
{
|
|
116
|
+
source.transform.position = position;
|
|
117
|
+
source.clip = clip;
|
|
118
|
+
source.volume = volume;
|
|
119
|
+
source.spatialBlend = 1f;
|
|
120
|
+
source.loop = true;
|
|
121
|
+
source.Play();
|
|
122
|
+
}
|
|
123
|
+
return source;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private AudioSource GetAvailableSource()
|
|
127
|
+
{
|
|
128
|
+
foreach (var source in _sfxPool)
|
|
129
|
+
{
|
|
130
|
+
if (!source.isPlaying)
|
|
131
|
+
{
|
|
132
|
+
source.loop = false;
|
|
133
|
+
return source;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// All busy - return oldest
|
|
138
|
+
Debug.LogWarning("SFX pool exhausted!");
|
|
139
|
+
return _sfxPool[0];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#endregion
|
|
143
|
+
|
|
144
|
+
#region Music
|
|
145
|
+
|
|
146
|
+
public void PlayMusic(AudioClip clip, float fadeDuration = 1f)
|
|
147
|
+
{
|
|
148
|
+
StartCoroutine(CrossfadeMusic(clip, fadeDuration));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private IEnumerator CrossfadeMusic(AudioClip newClip, float duration)
|
|
152
|
+
{
|
|
153
|
+
float startVolume = musicSource.volume;
|
|
154
|
+
|
|
155
|
+
// Fade out
|
|
156
|
+
float elapsed = 0f;
|
|
157
|
+
while (elapsed < duration / 2f)
|
|
158
|
+
{
|
|
159
|
+
elapsed += Time.deltaTime;
|
|
160
|
+
musicSource.volume = Mathf.Lerp(startVolume, 0f, elapsed / (duration / 2f));
|
|
161
|
+
yield return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Switch clip
|
|
165
|
+
musicSource.clip = newClip;
|
|
166
|
+
musicSource.Play();
|
|
167
|
+
|
|
168
|
+
// Fade in
|
|
169
|
+
elapsed = 0f;
|
|
170
|
+
while (elapsed < duration / 2f)
|
|
171
|
+
{
|
|
172
|
+
elapsed += Time.deltaTime;
|
|
173
|
+
musicSource.volume = Mathf.Lerp(0f, startVolume, elapsed / (duration / 2f));
|
|
174
|
+
yield return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public void StopMusic(float fadeDuration = 1f)
|
|
179
|
+
{
|
|
180
|
+
StartCoroutine(FadeOutMusic(fadeDuration));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private IEnumerator FadeOutMusic(float duration)
|
|
184
|
+
{
|
|
185
|
+
float startVolume = musicSource.volume;
|
|
186
|
+
float elapsed = 0f;
|
|
187
|
+
|
|
188
|
+
while (elapsed < duration)
|
|
189
|
+
{
|
|
190
|
+
elapsed += Time.deltaTime;
|
|
191
|
+
musicSource.volume = Mathf.Lerp(startVolume, 0f, elapsed / duration);
|
|
192
|
+
yield return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
musicSource.Stop();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#endregion
|
|
199
|
+
|
|
200
|
+
#region Volume Control
|
|
201
|
+
|
|
202
|
+
public void SetMasterVolume(float volume)
|
|
203
|
+
{
|
|
204
|
+
SetMixerVolume("MasterVolume", volume);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public void SetMusicVolume(float volume)
|
|
208
|
+
{
|
|
209
|
+
SetMixerVolume("MusicVolume", volume);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public void SetSFXVolume(float volume)
|
|
213
|
+
{
|
|
214
|
+
SetMixerVolume("SFXVolume", volume);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private void SetMixerVolume(string parameter, float linearVolume)
|
|
218
|
+
{
|
|
219
|
+
// Convert linear (0-1) to dB
|
|
220
|
+
float dB = linearVolume > 0.001f
|
|
221
|
+
? Mathf.Log10(linearVolume) * 20f
|
|
222
|
+
: -80f;
|
|
223
|
+
|
|
224
|
+
masterGroup.audioMixer.SetFloat(parameter, dB);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#endregion
|
|
228
|
+
|
|
229
|
+
private AudioClip LoadClip(string path)
|
|
230
|
+
{
|
|
231
|
+
if (_clipCache.TryGetValue(path, out var cached))
|
|
232
|
+
{
|
|
233
|
+
return cached;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var clip = Resources.Load<AudioClip>(path);
|
|
237
|
+
if (clip != null)
|
|
238
|
+
{
|
|
239
|
+
_clipCache[path] = clip;
|
|
240
|
+
}
|
|
241
|
+
return clip;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Adaptive Music System
|
|
249
|
+
|
|
250
|
+
```csharp
|
|
251
|
+
public class AdaptiveMusicManager : MonoBehaviour
|
|
252
|
+
{
|
|
253
|
+
[System.Serializable]
|
|
254
|
+
public class MusicLayer
|
|
255
|
+
{
|
|
256
|
+
public string Name;
|
|
257
|
+
public AudioClip Clip;
|
|
258
|
+
public AudioSource Source;
|
|
259
|
+
[Range(0, 1)] public float TargetVolume = 1f;
|
|
260
|
+
public float FadeSpeed = 1f;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
[SerializeField] private MusicLayer[] layers;
|
|
264
|
+
[SerializeField] private float bpm = 120f;
|
|
265
|
+
|
|
266
|
+
private float _beatDuration;
|
|
267
|
+
private float _nextBeatTime;
|
|
268
|
+
|
|
269
|
+
// Music states
|
|
270
|
+
public enum MusicState { Exploration, Combat, Danger, Boss }
|
|
271
|
+
private MusicState _currentState = MusicState.Exploration;
|
|
272
|
+
|
|
273
|
+
void Start()
|
|
274
|
+
{
|
|
275
|
+
_beatDuration = 60f / bpm;
|
|
276
|
+
InitializeLayers();
|
|
277
|
+
SyncAllLayers();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private void InitializeLayers()
|
|
281
|
+
{
|
|
282
|
+
foreach (var layer in layers)
|
|
283
|
+
{
|
|
284
|
+
layer.Source.clip = layer.Clip;
|
|
285
|
+
layer.Source.loop = true;
|
|
286
|
+
layer.Source.volume = 0f;
|
|
287
|
+
layer.Source.Play();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Start with exploration layers
|
|
291
|
+
SetState(MusicState.Exploration);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
void Update()
|
|
295
|
+
{
|
|
296
|
+
// Smooth volume transitions
|
|
297
|
+
foreach (var layer in layers)
|
|
298
|
+
{
|
|
299
|
+
layer.Source.volume = Mathf.MoveTowards(
|
|
300
|
+
layer.Source.volume,
|
|
301
|
+
layer.TargetVolume,
|
|
302
|
+
layer.FadeSpeed * Time.deltaTime
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Keep layers in sync
|
|
307
|
+
if (Time.time > _nextBeatTime)
|
|
308
|
+
{
|
|
309
|
+
_nextBeatTime = Time.time + _beatDuration;
|
|
310
|
+
OnBeat();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private void OnBeat()
|
|
315
|
+
{
|
|
316
|
+
// Could trigger beat-synced events
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public void SetState(MusicState newState)
|
|
320
|
+
{
|
|
321
|
+
_currentState = newState;
|
|
322
|
+
UpdateLayerVolumes();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private void UpdateLayerVolumes()
|
|
326
|
+
{
|
|
327
|
+
switch (_currentState)
|
|
328
|
+
{
|
|
329
|
+
case MusicState.Exploration:
|
|
330
|
+
SetLayerVolume("Base", 1f);
|
|
331
|
+
SetLayerVolume("Melody", 0.5f);
|
|
332
|
+
SetLayerVolume("Drums", 0f);
|
|
333
|
+
SetLayerVolume("Intensity", 0f);
|
|
334
|
+
break;
|
|
335
|
+
|
|
336
|
+
case MusicState.Danger:
|
|
337
|
+
SetLayerVolume("Base", 1f);
|
|
338
|
+
SetLayerVolume("Melody", 0.3f);
|
|
339
|
+
SetLayerVolume("Drums", 0.5f);
|
|
340
|
+
SetLayerVolume("Intensity", 0.3f);
|
|
341
|
+
break;
|
|
342
|
+
|
|
343
|
+
case MusicState.Combat:
|
|
344
|
+
SetLayerVolume("Base", 1f);
|
|
345
|
+
SetLayerVolume("Melody", 0f);
|
|
346
|
+
SetLayerVolume("Drums", 1f);
|
|
347
|
+
SetLayerVolume("Intensity", 0.7f);
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case MusicState.Boss:
|
|
351
|
+
SetLayerVolume("Base", 1f);
|
|
352
|
+
SetLayerVolume("Melody", 0.5f);
|
|
353
|
+
SetLayerVolume("Drums", 1f);
|
|
354
|
+
SetLayerVolume("Intensity", 1f);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private void SetLayerVolume(string layerName, float volume)
|
|
360
|
+
{
|
|
361
|
+
var layer = layers.FirstOrDefault(l => l.Name == layerName);
|
|
362
|
+
if (layer != null)
|
|
363
|
+
{
|
|
364
|
+
layer.TargetVolume = volume;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private void SyncAllLayers()
|
|
369
|
+
{
|
|
370
|
+
float syncTime = layers[0].Source.time;
|
|
371
|
+
foreach (var layer in layers)
|
|
372
|
+
{
|
|
373
|
+
layer.Source.time = syncTime;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Footstep System
|
|
382
|
+
|
|
383
|
+
```csharp
|
|
384
|
+
public class FootstepSystem : MonoBehaviour
|
|
385
|
+
{
|
|
386
|
+
[System.Serializable]
|
|
387
|
+
public class SurfaceAudio
|
|
388
|
+
{
|
|
389
|
+
public string SurfaceTag;
|
|
390
|
+
public PhysicMaterial PhysicsMaterial;
|
|
391
|
+
public AudioClip[] FootstepClips;
|
|
392
|
+
public AudioClip[] LandingClips;
|
|
393
|
+
[Range(0, 1)] public float Volume = 1f;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
[SerializeField] private SurfaceAudio[] surfaces;
|
|
397
|
+
[SerializeField] private SurfaceAudio defaultSurface;
|
|
398
|
+
|
|
399
|
+
[Header("Settings")]
|
|
400
|
+
[SerializeField] private float footstepInterval = 0.5f;
|
|
401
|
+
[SerializeField] private float runMultiplier = 0.7f;
|
|
402
|
+
[SerializeField] private LayerMask groundMask;
|
|
403
|
+
|
|
404
|
+
private AudioSource _audioSource;
|
|
405
|
+
private float _lastFootstepTime;
|
|
406
|
+
private CharacterController _controller;
|
|
407
|
+
|
|
408
|
+
void Awake()
|
|
409
|
+
{
|
|
410
|
+
_audioSource = gameObject.AddComponent<AudioSource>();
|
|
411
|
+
_audioSource.spatialBlend = 1f;
|
|
412
|
+
_controller = GetComponent<CharacterController>();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public void PlayFootstep(bool isRunning = false)
|
|
416
|
+
{
|
|
417
|
+
float interval = isRunning ? footstepInterval * runMultiplier : footstepInterval;
|
|
418
|
+
|
|
419
|
+
if (Time.time - _lastFootstepTime < interval) return;
|
|
420
|
+
_lastFootstepTime = Time.time;
|
|
421
|
+
|
|
422
|
+
var surface = GetCurrentSurface();
|
|
423
|
+
if (surface != null && surface.FootstepClips.Length > 0)
|
|
424
|
+
{
|
|
425
|
+
var clip = surface.FootstepClips[Random.Range(0, surface.FootstepClips.Length)];
|
|
426
|
+
_audioSource.PlayOneShot(clip, surface.Volume);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
public void PlayLanding()
|
|
431
|
+
{
|
|
432
|
+
var surface = GetCurrentSurface();
|
|
433
|
+
if (surface != null && surface.LandingClips.Length > 0)
|
|
434
|
+
{
|
|
435
|
+
var clip = surface.LandingClips[Random.Range(0, surface.LandingClips.Length)];
|
|
436
|
+
_audioSource.PlayOneShot(clip, surface.Volume * 1.2f);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private SurfaceAudio GetCurrentSurface()
|
|
441
|
+
{
|
|
442
|
+
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 2f, groundMask))
|
|
443
|
+
{
|
|
444
|
+
// Check by tag
|
|
445
|
+
foreach (var surface in surfaces)
|
|
446
|
+
{
|
|
447
|
+
if (hit.collider.CompareTag(surface.SurfaceTag))
|
|
448
|
+
{
|
|
449
|
+
return surface;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Check by physics material
|
|
454
|
+
if (hit.collider.material != null)
|
|
455
|
+
{
|
|
456
|
+
foreach (var surface in surfaces)
|
|
457
|
+
{
|
|
458
|
+
if (surface.PhysicsMaterial == hit.collider.material)
|
|
459
|
+
{
|
|
460
|
+
return surface;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return defaultSurface;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Called from animation events
|
|
470
|
+
public void OnFootstepLeft() => PlayFootstep();
|
|
471
|
+
public void OnFootstepRight() => PlayFootstep();
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Spatial Audio Setup
|
|
478
|
+
|
|
479
|
+
```csharp
|
|
480
|
+
public class SpatialAudioSource : MonoBehaviour
|
|
481
|
+
{
|
|
482
|
+
[Header("3D Settings")]
|
|
483
|
+
[SerializeField] private float minDistance = 1f;
|
|
484
|
+
[SerializeField] private float maxDistance = 50f;
|
|
485
|
+
[SerializeField] private AudioRolloffMode rolloffMode = AudioRolloffMode.Logarithmic;
|
|
486
|
+
|
|
487
|
+
[Header("Occlusion")]
|
|
488
|
+
[SerializeField] private bool useOcclusion = true;
|
|
489
|
+
[SerializeField] private LayerMask occlusionMask;
|
|
490
|
+
[SerializeField] private float occlusionDamping = 0.5f;
|
|
491
|
+
|
|
492
|
+
private AudioSource _source;
|
|
493
|
+
private AudioLowPassFilter _lowPass;
|
|
494
|
+
private Transform _listener;
|
|
495
|
+
|
|
496
|
+
void Awake()
|
|
497
|
+
{
|
|
498
|
+
_source = GetComponent<AudioSource>();
|
|
499
|
+
_source.spatialBlend = 1f;
|
|
500
|
+
_source.minDistance = minDistance;
|
|
501
|
+
_source.maxDistance = maxDistance;
|
|
502
|
+
_source.rolloffMode = rolloffMode;
|
|
503
|
+
|
|
504
|
+
if (useOcclusion)
|
|
505
|
+
{
|
|
506
|
+
_lowPass = gameObject.AddComponent<AudioLowPassFilter>();
|
|
507
|
+
_lowPass.cutoffFrequency = 22000f;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
void Start()
|
|
512
|
+
{
|
|
513
|
+
_listener = Camera.main.transform;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
void Update()
|
|
517
|
+
{
|
|
518
|
+
if (!useOcclusion || _listener == null) return;
|
|
519
|
+
|
|
520
|
+
Vector3 direction = _listener.position - transform.position;
|
|
521
|
+
|
|
522
|
+
if (Physics.Raycast(transform.position, direction.normalized, direction.magnitude, occlusionMask))
|
|
523
|
+
{
|
|
524
|
+
// Occluded - lower frequencies
|
|
525
|
+
_lowPass.cutoffFrequency = Mathf.Lerp(
|
|
526
|
+
_lowPass.cutoffFrequency,
|
|
527
|
+
1000f,
|
|
528
|
+
occlusionDamping * Time.deltaTime * 10f
|
|
529
|
+
);
|
|
530
|
+
_source.volume = Mathf.Lerp(_source.volume, 0.5f, occlusionDamping * Time.deltaTime * 10f);
|
|
531
|
+
}
|
|
532
|
+
else
|
|
533
|
+
{
|
|
534
|
+
// Clear path
|
|
535
|
+
_lowPass.cutoffFrequency = Mathf.Lerp(
|
|
536
|
+
_lowPass.cutoffFrequency,
|
|
537
|
+
22000f,
|
|
538
|
+
occlusionDamping * Time.deltaTime * 10f
|
|
539
|
+
);
|
|
540
|
+
_source.volume = Mathf.Lerp(_source.volume, 1f, occlusionDamping * Time.deltaTime * 10f);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Audio Cue Variations
|
|
549
|
+
|
|
550
|
+
```csharp
|
|
551
|
+
[CreateAssetMenu(fileName = "New Audio Cue", menuName = "Audio/Cue")]
|
|
552
|
+
public class AudioCue : ScriptableObject
|
|
553
|
+
{
|
|
554
|
+
[System.Serializable]
|
|
555
|
+
public class ClipVariant
|
|
556
|
+
{
|
|
557
|
+
public AudioClip Clip;
|
|
558
|
+
[Range(0, 1)] public float Weight = 1f;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
public ClipVariant[] Variants;
|
|
562
|
+
|
|
563
|
+
[Header("Pitch")]
|
|
564
|
+
public float PitchMin = 0.95f;
|
|
565
|
+
public float PitchMax = 1.05f;
|
|
566
|
+
|
|
567
|
+
[Header("Volume")]
|
|
568
|
+
public float VolumeMin = 0.9f;
|
|
569
|
+
public float VolumeMax = 1f;
|
|
570
|
+
|
|
571
|
+
private int _lastPlayedIndex = -1;
|
|
572
|
+
|
|
573
|
+
public void Play(AudioSource source)
|
|
574
|
+
{
|
|
575
|
+
if (Variants.Length == 0) return;
|
|
576
|
+
|
|
577
|
+
source.clip = GetRandomClip();
|
|
578
|
+
source.pitch = Random.Range(PitchMin, PitchMax);
|
|
579
|
+
source.volume = Random.Range(VolumeMin, VolumeMax);
|
|
580
|
+
source.Play();
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
public void PlayOneShot(AudioSource source)
|
|
584
|
+
{
|
|
585
|
+
if (Variants.Length == 0) return;
|
|
586
|
+
|
|
587
|
+
var clip = GetRandomClip();
|
|
588
|
+
float volume = Random.Range(VolumeMin, VolumeMax);
|
|
589
|
+
|
|
590
|
+
// Temporarily adjust pitch
|
|
591
|
+
float originalPitch = source.pitch;
|
|
592
|
+
source.pitch = Random.Range(PitchMin, PitchMax);
|
|
593
|
+
source.PlayOneShot(clip, volume);
|
|
594
|
+
source.pitch = originalPitch;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private AudioClip GetRandomClip()
|
|
598
|
+
{
|
|
599
|
+
// Weighted random, avoiding immediate repeat
|
|
600
|
+
float totalWeight = Variants.Sum(v => v.Weight);
|
|
601
|
+
float random = Random.Range(0f, totalWeight);
|
|
602
|
+
|
|
603
|
+
float cumulative = 0f;
|
|
604
|
+
for (int i = 0; i < Variants.Length; i++)
|
|
605
|
+
{
|
|
606
|
+
cumulative += Variants[i].Weight;
|
|
607
|
+
if (random <= cumulative && i != _lastPlayedIndex)
|
|
608
|
+
{
|
|
609
|
+
_lastPlayedIndex = i;
|
|
610
|
+
return Variants[i].Clip;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
_lastPlayedIndex = 0;
|
|
615
|
+
return Variants[0].Clip;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## Quick Reference
|
|
623
|
+
|
|
624
|
+
| System | Key Approach |
|
|
625
|
+
|--------|--------------|
|
|
626
|
+
| **SFX** | Pool audio sources, reuse |
|
|
627
|
+
| **Music** | Crossfade, layer-based |
|
|
628
|
+
| **Spatial** | Set spatialBlend = 1 |
|
|
629
|
+
| **Footsteps** | Surface detection + variations |
|
|
630
|
+
| **Ambience** | Looping, zone-based |
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## Anti-Patterns
|
|
635
|
+
|
|
636
|
+
| ❌ Don't | ✅ Do |
|
|
637
|
+
|----------|-------|
|
|
638
|
+
| Create new AudioSource each time | Use pooling |
|
|
639
|
+
| Same clip every time | Add variations |
|
|
640
|
+
| Hard transitions | Crossfade |
|
|
641
|
+
| Ignore occlusion | Use low-pass for walls |
|
|
642
|
+
| Max volume everything | Dynamic mixing |
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
> **Remember:** Audio is 50% of immersion. Invest in it.
|