643 lines
24 KiB
C#
643 lines
24 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.InputSystem;
|
||
|
||
/// <summary>
|
||
/// Контроллер вступительной кат-сцены «Синтезия» (2D-арты / слайд-шоу).
|
||
/// Воспроизводит 5 атмосферных иллюстрированных кадров с эффектом Ken Burns (панорамирование/зум),
|
||
/// плавными растворениями (cross-fade), процедурным звуковым сопровождением и финальным логотипом.
|
||
/// </summary>
|
||
public class IntroCutsceneController : MonoBehaviour
|
||
{
|
||
private static IntroCutsceneController _instance;
|
||
public static IntroCutsceneController Instance => _instance;
|
||
|
||
[Header("Спрайты кадров")]
|
||
public Sprite frame1Sprite;
|
||
public Sprite frame2Sprite;
|
||
public Sprite frame3Sprite;
|
||
public Sprite frame4Sprite;
|
||
public Sprite frame5Sprite;
|
||
public Sprite frameTitleSprite;
|
||
|
||
[Header("Слои отображения изображений")]
|
||
public CanvasGroup cutsceneRootGroup;
|
||
public Image imageLayerA;
|
||
public Image imageLayerB;
|
||
public CanvasGroup layerAGroup;
|
||
public CanvasGroup layerBGroup;
|
||
|
||
[Header("Интерфейс субтитров")]
|
||
public CanvasGroup subtitleGroup;
|
||
public Text speakerLabelText;
|
||
public Text subtitleBodyText;
|
||
|
||
[Header("Подсказка пропуска")]
|
||
public CanvasGroup skipPromptGroup;
|
||
public Text skipPromptText;
|
||
|
||
[Header("Аудио источники")]
|
||
public AudioSource ambienceSource;
|
||
public AudioSource heartbeatSource;
|
||
public AudioSource sfxSource;
|
||
|
||
[Header("Состояние")]
|
||
public bool isPlaying = false;
|
||
private bool isSkipping = false;
|
||
private Coroutine cutsceneCoroutine;
|
||
private PlayerMovement cachedPlayerMovement;
|
||
|
||
public class CutsceneSceneData
|
||
{
|
||
public Sprite sprite;
|
||
public string speaker;
|
||
public string subtitle;
|
||
public float duration;
|
||
public float zoomStart;
|
||
public float zoomEnd;
|
||
public Vector2 panOffsetStart;
|
||
public Vector2 panOffsetEnd;
|
||
public System.Action onStartAction;
|
||
}
|
||
|
||
private List<CutsceneSceneData> scenes = new List<CutsceneSceneData>();
|
||
|
||
private void Awake()
|
||
{
|
||
_instance = this;
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// По умолчанию скрываем до запуска
|
||
if (cutsceneRootGroup != null)
|
||
{
|
||
cutsceneRootGroup.alpha = 0f;
|
||
cutsceneRootGroup.blocksRaycasts = false;
|
||
}
|
||
|
||
// Автоматически запускаем кат-сцену при старте, если не была просмотрена
|
||
// или если активен флаг принудительного воспроизведения
|
||
bool shouldPlay = PlayerPrefs.GetInt("PlayIntroCutscene", 1) == 1;
|
||
if (shouldPlay)
|
||
{
|
||
StartCoroutine(RoutineAutoPlayOnStart());
|
||
}
|
||
}
|
||
|
||
private IEnumerator RoutineAutoPlayOnStart()
|
||
{
|
||
yield return null; // ждем 1 кадр для инициализации всех систем игрока
|
||
PlayCutscene();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Запуск кат-сцены
|
||
/// </summary>
|
||
public void PlayCutscene()
|
||
{
|
||
if (isPlaying) return;
|
||
isPlaying = true;
|
||
isSkipping = false;
|
||
|
||
// Блокируем движение игрока на время кат-сцены
|
||
cachedPlayerMovement = FindAnyObjectByType<PlayerMovement>();
|
||
if (cachedPlayerMovement != null)
|
||
{
|
||
cachedPlayerMovement.enabled = false;
|
||
}
|
||
|
||
var gun = FindAnyObjectByType<PlayerGun>();
|
||
if (gun != null) gun.enabled = false;
|
||
|
||
var attack = FindAnyObjectByType<PlayerAttack>();
|
||
if (attack != null) attack.enabled = false;
|
||
|
||
LoadSpritesIfNull();
|
||
PrepareSceneData();
|
||
|
||
if (cutsceneCoroutine != null) StopCoroutine(cutsceneCoroutine);
|
||
cutsceneCoroutine = StartCoroutine(RoutineCutsceneSequence());
|
||
}
|
||
|
||
private void LoadSpritesIfNull()
|
||
{
|
||
if (frame1Sprite == null) frame1Sprite = Resources.Load<Sprite>("Cutscene/frame_1") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_1.jpg");
|
||
if (frame2Sprite == null) frame2Sprite = Resources.Load<Sprite>("Cutscene/frame_2") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_2.jpg");
|
||
if (frame3Sprite == null) frame3Sprite = Resources.Load<Sprite>("Cutscene/frame_3") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_3.jpg");
|
||
if (frame4Sprite == null) frame4Sprite = Resources.Load<Sprite>("Cutscene/frame_4") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_4.jpg");
|
||
if (frame5Sprite == null) frame5Sprite = Resources.Load<Sprite>("Cutscene/frame_5") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_5.jpg");
|
||
if (frameTitleSprite == null) frameTitleSprite = Resources.Load<Sprite>("Cutscene/frame_title") ?? LoadSpriteFromAsset("Assets/Textures/Cutscene/frame_title.jpg");
|
||
}
|
||
|
||
private Sprite LoadSpriteFromAsset(string path)
|
||
{
|
||
#if UNITY_EDITOR
|
||
return UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>(path);
|
||
#else
|
||
return null;
|
||
#endif
|
||
}
|
||
|
||
private void PrepareSceneData()
|
||
{
|
||
scenes.Clear();
|
||
|
||
// КАДР 1: ОПЕРАЦИОННАЯ
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frame1Sprite,
|
||
speaker = "ГОЛОС ГГ",
|
||
subtitle = "Проект «Синтезия». Они выключили свет и забрали наши глаза, надеясь, что звук станет нашим новым зрением.",
|
||
duration = 7.5f,
|
||
zoomStart = 1.0f,
|
||
zoomEnd = 1.10f,
|
||
panOffsetStart = new Vector2(0f, -10f),
|
||
panOffsetEnd = new Vector2(0f, 15f),
|
||
onStartAction = () =>
|
||
{
|
||
// Запускаем фоновый гул и пиканье монитора
|
||
StartAmbienceAndHeartbeat();
|
||
StartCoroutine(RoutineHeartMonitorBeeps(7.5f));
|
||
}
|
||
});
|
||
|
||
// КАДР 2: БЕЗУМИЕ ВО ТЬМЕ
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frame2Sprite,
|
||
speaker = "ГОЛОС ГГ",
|
||
subtitle = "Но чуда не вышло. Темнота свела их с ума, оставив наедине с обостренным, невыносимым слухом.",
|
||
duration = 7.0f,
|
||
zoomStart = 1.05f,
|
||
zoomEnd = 1.08f,
|
||
panOffsetStart = new Vector2(-15f, 0f),
|
||
panOffsetEnd = new Vector2(10f, 0f),
|
||
onStartAction = () =>
|
||
{
|
||
// Скрежет ногтей по стене и стон
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateScratchAndGroanClip(), 0.9f);
|
||
}
|
||
}
|
||
});
|
||
|
||
// КАДР 3: ПРОБУЖДЕНИЕ ГГ
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frame3Sprite,
|
||
speaker = "ГОЛОС ГГ",
|
||
subtitle = "Я не знаю, почему выжил только мой разум. Но теперь каждый шорох для меня — это вспышка света.",
|
||
duration = 7.5f,
|
||
zoomStart = 1.0f,
|
||
zoomEnd = 1.11f,
|
||
panOffsetStart = new Vector2(0f, -20f),
|
||
panOffsetEnd = new Vector2(0f, 10f),
|
||
onStartAction = () =>
|
||
{
|
||
// Скрип сжатого металла гаечного ключа
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateMetalGripClip(), 0.85f);
|
||
}
|
||
}
|
||
});
|
||
|
||
// КАДР 4: УГРОЗА
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frame4Sprite,
|
||
speaker = "ГОЛОС ГГ",
|
||
subtitle = "Чтобы видеть путь — мне нужно шуметь.",
|
||
duration = 6.2f,
|
||
zoomStart = 1.12f,
|
||
zoomEnd = 1.0f,
|
||
panOffsetStart = new Vector2(0f, 20f),
|
||
panOffsetEnd = new Vector2(0f, 0f),
|
||
onStartAction = () =>
|
||
{
|
||
// Гортанный рык мутанта вдалеке
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateDistantGrowlClip(), 0.95f);
|
||
}
|
||
}
|
||
});
|
||
|
||
// КАДР 5: ДЕЙСТВИЕ И ЛОГОТИП
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frame5Sprite,
|
||
speaker = "ГОЛОС ГГ",
|
||
subtitle = "Но каждый звук привлекает тех, кто остался во тьме навсегда.",
|
||
duration = 6.5f,
|
||
zoomStart = 1.02f,
|
||
zoomEnd = 1.12f,
|
||
panOffsetStart = new Vector2(-25f, -10f),
|
||
panOffsetEnd = new Vector2(25f, 10f),
|
||
onStartAction = () =>
|
||
{
|
||
// ГРОМКИЙ УДАР МЕТАЛЛА И ВИЗГ МУТАНТА
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateMetalClangClip(), 1.0f);
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateFuriousScreechClip(), 0.85f);
|
||
}
|
||
}
|
||
});
|
||
|
||
// ФИНАЛ: ЛОГОТИП SYNTHESIA
|
||
scenes.Add(new CutsceneSceneData
|
||
{
|
||
sprite = frameTitleSprite,
|
||
speaker = "",
|
||
subtitle = "",
|
||
duration = 5.0f,
|
||
zoomStart = 1.0f,
|
||
zoomEnd = 1.05f,
|
||
panOffsetStart = Vector2.zero,
|
||
panOffsetEnd = Vector2.zero,
|
||
onStartAction = () =>
|
||
{
|
||
// Низкий басовый кинематографичный удар (Cinematic Boom)
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(IntroCutsceneAudio.CreateCinematicBoomClip(), 1.0f);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
private void StartAmbienceAndHeartbeat()
|
||
{
|
||
if (ambienceSource != null)
|
||
{
|
||
ambienceSource.clip = IntroCutsceneAudio.CreateBunkerHumClip(6f);
|
||
ambienceSource.loop = true;
|
||
ambienceSource.volume = 0.55f;
|
||
ambienceSource.Play();
|
||
}
|
||
|
||
if (heartbeatSource != null)
|
||
{
|
||
heartbeatSource.clip = IntroCutsceneAudio.CreateHeartbeatClip(62f);
|
||
heartbeatSource.loop = true;
|
||
heartbeatSource.volume = 0.65f;
|
||
heartbeatSource.Play();
|
||
}
|
||
}
|
||
|
||
private IEnumerator RoutineHeartMonitorBeeps(float duration)
|
||
{
|
||
float timer = 0f;
|
||
AudioClip beep = IntroCutsceneAudio.CreateHeartMonitorBeepClip();
|
||
|
||
while (timer < duration && !isSkipping)
|
||
{
|
||
if (sfxSource != null)
|
||
{
|
||
sfxSource.PlayOneShot(beep, 0.4f);
|
||
}
|
||
yield return new WaitForSeconds(1.15f);
|
||
timer += 1.15f;
|
||
}
|
||
}
|
||
|
||
private IEnumerator RoutineCutsceneSequence()
|
||
{
|
||
// 1. Показываем корневой холст кат-сцены
|
||
cutsceneRootGroup.alpha = 1f;
|
||
cutsceneRootGroup.blocksRaycasts = true;
|
||
|
||
if (skipPromptGroup != null)
|
||
{
|
||
skipPromptGroup.alpha = 0.75f;
|
||
}
|
||
|
||
bool useLayerA = true;
|
||
|
||
for (int i = 0; i < scenes.Count; i++)
|
||
{
|
||
if (isSkipping) break;
|
||
|
||
var scene = scenes[i];
|
||
|
||
// Назначаем спрайт на активный слой
|
||
Image activeImg = useLayerA ? imageLayerA : imageLayerB;
|
||
Image inactiveImg = useLayerA ? imageLayerB : imageLayerA;
|
||
CanvasGroup activeGroup = useLayerA ? layerAGroup : layerBGroup;
|
||
CanvasGroup inactiveGroup = useLayerA ? layerBGroup : layerAGroup;
|
||
|
||
activeImg.sprite = scene.sprite;
|
||
activeImg.rectTransform.localScale = Vector3.one * scene.zoomStart;
|
||
activeImg.rectTransform.anchoredPosition = scene.panOffsetStart;
|
||
|
||
// Вызываем звуковой триггер кадра
|
||
scene.onStartAction?.Invoke();
|
||
|
||
// Обновляем субтитры
|
||
UpdateSubtitles(scene.speaker, scene.subtitle);
|
||
|
||
// Плавное перетекание (Cross-fade)
|
||
float crossFadeDuration = (i == 0) ? 1.5f : 1.1f;
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < crossFadeDuration)
|
||
{
|
||
if (isSkipping) yield break;
|
||
|
||
elapsed += Time.deltaTime;
|
||
float t = Mathf.Clamp01(elapsed / crossFadeDuration);
|
||
activeGroup.alpha = t;
|
||
inactiveGroup.alpha = 1f - t;
|
||
yield return null;
|
||
}
|
||
|
||
activeGroup.alpha = 1f;
|
||
inactiveGroup.alpha = 0f;
|
||
|
||
// Анимация кадра Ken Burns (панорама и наезд камеры)
|
||
float sceneElapsed = 0f;
|
||
float totalDuration = scene.duration;
|
||
|
||
while (sceneElapsed < totalDuration)
|
||
{
|
||
if (isSkipping) yield break;
|
||
|
||
sceneElapsed += Time.deltaTime;
|
||
float progress = Mathf.Clamp01(sceneElapsed / totalDuration);
|
||
|
||
// Плавная интерполяция масштаба и смещения
|
||
float currentZoom = Mathf.Lerp(scene.zoomStart, scene.zoomEnd, progress);
|
||
Vector2 currentPan = Vector2.Lerp(scene.panOffsetStart, scene.panOffsetEnd, progress);
|
||
|
||
activeImg.rectTransform.localScale = Vector3.one * currentZoom;
|
||
activeImg.rectTransform.anchoredPosition = currentPan;
|
||
|
||
yield return null;
|
||
}
|
||
|
||
// Переключаем слой для следующего кадра
|
||
useLayerA = !useLayerA;
|
||
}
|
||
|
||
// Финальный переход в игру
|
||
yield return StartCoroutine(RoutineEndCutscene());
|
||
}
|
||
|
||
private void UpdateSubtitles(string speaker, string text)
|
||
{
|
||
if (subtitleGroup == null) return;
|
||
|
||
if (string.IsNullOrEmpty(text))
|
||
{
|
||
subtitleGroup.alpha = 0f;
|
||
return;
|
||
}
|
||
|
||
subtitleGroup.alpha = 1f;
|
||
if (speakerLabelText != null)
|
||
{
|
||
speakerLabelText.text = speaker;
|
||
}
|
||
|
||
if (subtitleBodyText != null)
|
||
{
|
||
subtitleBodyText.text = text;
|
||
}
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (!isPlaying || isSkipping) return;
|
||
|
||
// Пропуск по клавишам ПРОБЕЛ, ESC или ENTER
|
||
var keyboard = Keyboard.current;
|
||
if (keyboard != null)
|
||
{
|
||
if (keyboard.spaceKey.wasPressedThisFrame || keyboard.escapeKey.wasPressedThisFrame || keyboard.enterKey.wasPressedThisFrame)
|
||
{
|
||
SkipCutscene();
|
||
}
|
||
}
|
||
}
|
||
|
||
public void SkipCutscene()
|
||
{
|
||
if (isSkipping || !isPlaying) return;
|
||
isSkipping = true;
|
||
|
||
if (cutsceneCoroutine != null) StopCoroutine(cutsceneCoroutine);
|
||
StartCoroutine(RoutineEndCutscene());
|
||
}
|
||
|
||
private IEnumerator RoutineEndCutscene()
|
||
{
|
||
// Плавное затухание в темноту
|
||
float fadeOut = 1.0f;
|
||
float elapsed = 0f;
|
||
float startAlpha = cutsceneRootGroup != null ? cutsceneRootGroup.alpha : 1f;
|
||
|
||
// Плавное затухание эмбиента
|
||
float startAmbienceVol = ambienceSource != null ? ambienceSource.volume : 0f;
|
||
float startHeartVol = heartbeatSource != null ? heartbeatSource.volume : 0f;
|
||
|
||
while (elapsed < fadeOut)
|
||
{
|
||
elapsed += Time.deltaTime;
|
||
float t = Mathf.Clamp01(elapsed / fadeOut);
|
||
|
||
if (cutsceneRootGroup != null) cutsceneRootGroup.alpha = Mathf.Lerp(startAlpha, 0f, t);
|
||
if (ambienceSource != null) ambienceSource.volume = Mathf.Lerp(startAmbienceVol, 0f, t);
|
||
if (heartbeatSource != null) heartbeatSource.volume = Mathf.Lerp(startHeartVol, 0f, t);
|
||
|
||
yield return null;
|
||
}
|
||
|
||
if (ambienceSource != null) ambienceSource.Stop();
|
||
if (heartbeatSource != null) heartbeatSource.Stop();
|
||
|
||
if (cutsceneRootGroup != null)
|
||
{
|
||
cutsceneRootGroup.alpha = 0f;
|
||
cutsceneRootGroup.blocksRaycasts = false;
|
||
}
|
||
|
||
isPlaying = false;
|
||
isSkipping = false;
|
||
|
||
// Помечаем, что кат-сцена была просмотрена
|
||
PlayerPrefs.SetInt("PlayIntroCutscene", 0);
|
||
PlayerPrefs.Save();
|
||
|
||
// Возвращаем управление игроку
|
||
if (cachedPlayerMovement != null) cachedPlayerMovement.enabled = true;
|
||
|
||
var gun = FindAnyObjectByType<PlayerGun>();
|
||
if (gun != null) gun.enabled = true;
|
||
|
||
var attack = FindAnyObjectByType<PlayerAttack>();
|
||
if (attack != null) attack.enabled = true;
|
||
|
||
// Финальная вспышка эхолокации в месте старта игрока (мир оживает желтым звуковым пульсом)
|
||
var player = GameObject.FindWithTag("Player") ?? GameObject.Find("Player");
|
||
if (player != null && EchoTrigger.Instance != null)
|
||
{
|
||
EchoTrigger.Instance.TriggerExternalWave(player.transform.position, 25f, 3.2f);
|
||
}
|
||
|
||
Debug.Log("<color=green>[IntroCutscene] Кат-сцена завершена. Управление передано игроку!</color>");
|
||
}
|
||
|
||
/// <summary>
|
||
/// Фабричный метод создания всего UI и компонентов кат-сцены
|
||
/// </summary>
|
||
public static IntroCutsceneController Create(Transform parentCanvas)
|
||
{
|
||
var old = parentCanvas.Find("IntroCutsceneRoot");
|
||
if (old != null) DestroyImmediate(old.gameObject);
|
||
|
||
// 1. Корневой контейнер
|
||
GameObject root = new GameObject("IntroCutsceneRoot", typeof(RectTransform), typeof(CanvasGroup));
|
||
root.transform.SetParent(parentCanvas, false);
|
||
|
||
RectTransform rootRt = root.GetComponent<RectTransform>();
|
||
rootRt.anchorMin = Vector2.zero;
|
||
rootRt.anchorMax = Vector2.one;
|
||
rootRt.sizeDelta = Vector2.zero;
|
||
|
||
// Черный глухой фон
|
||
Image bgImg = root.AddComponent<Image>();
|
||
bgImg.color = Color.black;
|
||
|
||
// 2. Слой A (для перетекания)
|
||
GameObject layerA = new GameObject("ImageLayerA", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup));
|
||
layerA.transform.SetParent(root.transform, false);
|
||
RectTransform layerARt = layerA.GetComponent<RectTransform>();
|
||
layerARt.anchorMin = Vector2.zero;
|
||
layerARt.anchorMax = Vector2.one;
|
||
layerARt.sizeDelta = Vector2.zero;
|
||
Image imgA = layerA.GetComponent<Image>();
|
||
imgA.preserveAspect = true;
|
||
CanvasGroup groupA = layerA.GetComponent<CanvasGroup>();
|
||
groupA.alpha = 0f;
|
||
|
||
// 3. Слой B (для перетекания)
|
||
GameObject layerB = new GameObject("ImageLayerB", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup));
|
||
layerB.transform.SetParent(root.transform, false);
|
||
RectTransform layerBRt = layerB.GetComponent<RectTransform>();
|
||
layerBRt.anchorMin = Vector2.zero;
|
||
layerBRt.anchorMax = Vector2.one;
|
||
layerBRt.sizeDelta = Vector2.zero;
|
||
Image imgB = layerB.GetComponent<Image>();
|
||
imgB.preserveAspect = true;
|
||
CanvasGroup groupB = layerB.GetComponent<CanvasGroup>();
|
||
groupB.alpha = 0f;
|
||
|
||
// 4. Панель субтитров
|
||
Font defaultFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||
|
||
GameObject subPanel = new GameObject("SubtitlePanel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(CanvasGroup));
|
||
subPanel.transform.SetParent(root.transform, false);
|
||
RectTransform subRt = subPanel.GetComponent<RectTransform>();
|
||
subRt.anchorMin = new Vector2(0.08f, 0.04f);
|
||
subRt.anchorMax = new Vector2(0.92f, 0.22f);
|
||
subRt.sizeDelta = Vector2.zero;
|
||
|
||
Image subBg = subPanel.GetComponent<Image>();
|
||
subBg.color = new Color(0.03f, 0.03f, 0.04f, 0.78f);
|
||
Outline subOutline = subPanel.AddComponent<Outline>();
|
||
subOutline.effectColor = new Color(0.85f, 0.75f, 0.25f, 0.25f);
|
||
subOutline.effectDistance = new Vector2(1f, -1f);
|
||
|
||
// Имя говорящего
|
||
GameObject speakerObj = new GameObject("SpeakerLabel", typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
|
||
speakerObj.transform.SetParent(subPanel.transform, false);
|
||
RectTransform spkRt = speakerObj.GetComponent<RectTransform>();
|
||
spkRt.anchorMin = new Vector2(0f, 0.65f);
|
||
spkRt.anchorMax = new Vector2(1f, 1.0f);
|
||
spkRt.offsetMin = new Vector2(20f, 0f);
|
||
spkRt.offsetMax = new Vector2(-20f, -8f);
|
||
|
||
Text spkText = speakerObj.GetComponent<Text>();
|
||
spkText.font = defaultFont;
|
||
spkText.fontSize = 15;
|
||
spkText.fontStyle = FontStyle.Bold;
|
||
spkText.alignment = TextAnchor.MiddleCenter;
|
||
spkText.color = new Color(0.95f, 0.8f, 0.25f, 1.0f);
|
||
spkText.text = "ГОЛОС ГГ";
|
||
|
||
// Текст субтитров
|
||
GameObject subTextObj = new GameObject("SubtitleText", typeof(RectTransform), typeof(CanvasRenderer), typeof(Text));
|
||
subTextObj.transform.SetParent(subPanel.transform, false);
|
||
RectTransform stRt = subTextObj.GetComponent<RectTransform>();
|
||
stRt.anchorMin = new Vector2(0f, 0f);
|
||
stRt.anchorMax = new Vector2(1f, 0.65f);
|
||
stRt.offsetMin = new Vector2(30f, 8f);
|
||
stRt.offsetMax = new Vector2(-30f, 0f);
|
||
|
||
Text subBody = subTextObj.GetComponent<Text>();
|
||
subBody.font = defaultFont;
|
||
subBody.fontSize = 18;
|
||
subBody.lineSpacing = 1.25f;
|
||
subBody.alignment = TextAnchor.MiddleCenter;
|
||
subBody.color = new Color(0.95f, 0.95f, 0.95f, 0.95f);
|
||
subBody.text = "...";
|
||
|
||
// 5. Подсказка пропуска
|
||
GameObject skipObj = new GameObject("SkipPrompt", typeof(RectTransform), typeof(CanvasRenderer), typeof(Text), typeof(CanvasGroup));
|
||
skipObj.transform.SetParent(root.transform, false);
|
||
RectTransform skipRt = skipObj.GetComponent<RectTransform>();
|
||
skipRt.anchorMin = new Vector2(0.68f, 0.015f);
|
||
skipRt.anchorMax = new Vector2(0.98f, 0.05f);
|
||
skipRt.sizeDelta = Vector2.zero;
|
||
|
||
Text skipText = skipObj.GetComponent<Text>();
|
||
skipText.font = defaultFont;
|
||
skipText.fontSize = 13;
|
||
skipText.alignment = TextAnchor.MiddleRight;
|
||
skipText.color = new Color(0.7f, 0.7f, 0.7f, 0.75f);
|
||
skipText.text = "[ ПРОБЕЛ / ESC : ПРОПУСТИТЬ ]";
|
||
|
||
// 6. Аудио источники
|
||
GameObject audioHolder = new GameObject("CutsceneAudioSources");
|
||
audioHolder.transform.SetParent(root.transform, false);
|
||
|
||
AudioSource ambSrc = audioHolder.AddComponent<AudioSource>();
|
||
ambSrc.playOnAwake = false;
|
||
|
||
AudioSource heartSrc = audioHolder.AddComponent<AudioSource>();
|
||
heartSrc.playOnAwake = false;
|
||
|
||
AudioSource sfxSrc = audioHolder.AddComponent<AudioSource>();
|
||
sfxSrc.playOnAwake = false;
|
||
|
||
// 7. Сборка компонента
|
||
IntroCutsceneController controller = root.AddComponent<IntroCutsceneController>();
|
||
controller.cutsceneRootGroup = root.GetComponent<CanvasGroup>();
|
||
controller.cutsceneRootGroup.alpha = 0f;
|
||
controller.cutsceneRootGroup.blocksRaycasts = false;
|
||
|
||
controller.imageLayerA = imgA;
|
||
controller.imageLayerB = imgB;
|
||
controller.layerAGroup = groupA;
|
||
controller.layerBGroup = groupB;
|
||
|
||
controller.subtitleGroup = subPanel.GetComponent<CanvasGroup>();
|
||
controller.speakerLabelText = spkText;
|
||
controller.subtitleBodyText = subBody;
|
||
|
||
controller.skipPromptGroup = skipObj.GetComponent<CanvasGroup>();
|
||
controller.skipPromptText = skipText;
|
||
|
||
controller.ambienceSource = ambSrc;
|
||
controller.heartbeatSource = heartSrc;
|
||
controller.sfxSource = sfxSrc;
|
||
|
||
return controller;
|
||
}
|
||
}
|