using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; using Starpelly; namespace HeavenStudio.Editor.Track { public class Timeline : MonoBehaviour { [Header("Song Positions")] [SerializeField] private TMP_Text SongBeat; [SerializeField] private TMP_Text SongPos; [SerializeField] private TMP_Text CurrentTempo; [Header("Timeline Properties")] private float lastBeatPos = 0; private Vector2 lastMousePos; public List eventObjs = new List(); private bool lastFrameDrag; public int LayerCount = 4; public bool metronomeEnabled; public bool resizable; private bool movingPlayback; public CurrentTimelineState timelineState = new CurrentTimelineState(); public float snapInterval = 0.25f; // 4/4 public static float SnapInterval() { return instance.snapInterval; } public void SetSnap(float snap) { snapInterval = snap; } public class CurrentTimelineState { public bool selected; public bool tempoChange; public bool musicVolume; public void SetState(bool selected, bool tempoChange, bool musicVolume) { if (Conductor.instance.NotStopped()) return; this.selected = selected; this.tempoChange = tempoChange; this.musicVolume = musicVolume; if (selected) instance.SelectionsBTN.transform.GetChild(0).GetComponent().color = Color.white; else instance.SelectionsBTN.transform.GetChild(0).GetComponent().color = Color.gray; if (tempoChange) instance.TempoChangeBTN.transform.GetChild(0).GetComponent().color = Color.white; else instance.TempoChangeBTN.transform.GetChild(0).GetComponent().color = Color.gray; if (musicVolume) instance.MusicVolumeBTN.transform.GetChild(0).GetComponent().color = Color.white; else instance.MusicVolumeBTN.transform.GetChild(0).GetComponent().color = Color.gray; } } [Header("Timeline Components")] [SerializeField] private RectTransform TimelineSlider; [SerializeField] private RectTransform TimelineGridSelect; [SerializeField] private TMP_Text TimelinePlaybackBeat; public RectTransform TimelineContent; [SerializeField] private RectTransform TimelineSongPosLineRef; [SerializeField] private RectTransform TimelineEventObjRef; [SerializeField] private RectTransform LayersRect; public TempoTimeline TempoInfo; public VolumeTimeline VolumeInfo; private RectTransform TimelineSongPosLine; [Header("Timeline Playbar")] public Button PlayBTN; public Button PauseBTN; public Button StopBTN; public Button MetronomeBTN; public Button AutoplayBTN; public Button SelectionsBTN; public Button TempoChangeBTN; public Button MusicVolumeBTN; public Slider PlaybackSpeed; public Vector3[] LayerCorners = new Vector3[4]; public static Timeline instance { get; private set; } public bool userIsEditingInputField { get { var selectedGO = EventSystem.current.currentSelectedGameObject; return selectedGO != null && (selectedGO.GetComponent() != null || selectedGO.GetComponent() != null); } } #region Initializers public void LoadRemix() { // beatmap entities for (int i = 0; i < eventObjs.Count; i++) { Destroy(eventObjs[i].gameObject); } eventObjs.Clear(); for (int i = 0; i < GameManager.instance.Beatmap.entities.Count; i++) { var e = GameManager.instance.Beatmap.entities[i]; AddEventObject(e.datamodel, false, new Vector3(e.beat, -e.track * LayerHeight()), e, false, RandomID()); } //tempo changes TempoInfo.ClearTempoTimeline(); for (int i = 0; i < GameManager.instance.Beatmap.tempoChanges.Count; i++) { var t = GameManager.instance.Beatmap.tempoChanges[i]; TempoInfo.AddTempoChange(false, t); } //volume changes } public void Init() { instance = this; LoadRemix(); TimelineSlider.GetChild(0).GetComponent().color = EditorTheme.theme.properties.BeatMarkerCol.Hex2RGB(); TimelineSlider.GetChild(1).GetComponent().color = EditorTheme.theme.properties.BeatMarkerCol.Hex2RGB(); TimelineSlider.GetChild(2).GetComponent().color = EditorTheme.theme.properties.BeatMarkerCol.Hex2RGB(); TimelineSlider.GetChild(3).GetComponent().color = EditorTheme.theme.properties.BeatMarkerCol.Hex2RGB(); TimelineSongPosLineRef.GetComponent().color = EditorTheme.theme.properties.CurrentTimeMarkerCol.Hex2RGB(); PlayBTN.onClick.AddListener(delegate { if (Conductor.instance.isPaused) PlayCheck(false); else PlayCheck(true); }); PauseBTN.onClick.AddListener(delegate { if (Conductor.instance.isPlaying && !Conductor.instance.isPaused) PlayCheck(false); }); StopBTN.onClick.AddListener(delegate { if (Conductor.instance.isPlaying || Conductor.instance.isPaused) PlayCheck(true); }); MetronomeBTN.onClick.AddListener(delegate { MetronomeToggle(); }); AutoplayBTN.onClick.AddListener(delegate { AutoPlayToggle(); }); SelectionsBTN.onClick.AddListener(delegate { timelineState.SetState(true, false, false); }); TempoChangeBTN.onClick.AddListener(delegate { timelineState.SetState(false, true, false); }); MusicVolumeBTN.onClick.AddListener(delegate { timelineState.SetState(false, false, true); }); Tooltip.AddTooltip(SongBeat.gameObject, "Current Beat"); Tooltip.AddTooltip(SongPos.gameObject, "Current Time"); Tooltip.AddTooltip(CurrentTempo.gameObject, "Current Tempo (BPM)"); Tooltip.AddTooltip(PlayBTN.gameObject, "Play [Space]"); Tooltip.AddTooltip(PauseBTN.gameObject, "Pause [Shift + Space]"); Tooltip.AddTooltip(StopBTN.gameObject, "Stop [Space]"); Tooltip.AddTooltip(MetronomeBTN.gameObject, "Metronome [M]"); Tooltip.AddTooltip(AutoplayBTN.gameObject, "Autoplay [P]"); Tooltip.AddTooltip(SelectionsBTN.gameObject, "Tool: Selection [1]"); Tooltip.AddTooltip(TempoChangeBTN.gameObject, "Tool: Tempo Change [2]"); Tooltip.AddTooltip(MusicVolumeBTN.gameObject, "Tool: Music Volume [3]"); Tooltip.AddTooltip(PlaybackSpeed.gameObject, "The preview's playback speed. Right click to reset to 1.0"); SetTimeButtonColors(true, false, false); MetronomeBTN.transform.GetChild(0).GetComponent().color = Color.gray; timelineState.SetState(true, false, false); AutoBtnUpdate(); } public void FitToSong() { var currentSizeDelta = TimelineContent.sizeDelta; float songBeats = Conductor.instance.SongLengthInBeats(); if (songBeats == 0) songBeats = 320; else songBeats += 10; TimelineContent.sizeDelta = new Vector2(songBeats, currentSizeDelta.y); } public void AutoBtnUpdate() { var animName = GameManager.instance.autoplay ? "Idle" : "Disabled"; AutoplayBTN.GetComponent().Play(animName, 0, 0); } public void AutoPlayToggle() { if (!GameManager.instance.autoplay) { AutoplayBTN.GetComponent().Play("Idle", 0, 0); GameManager.instance.autoplay = true; } else { AutoplayBTN.GetComponent().Play("Disabled", 0, 0); GameManager.instance.autoplay = false; } } public void MetronomeToggle() { if (!Conductor.instance.metronome) { MetronomeBTN.transform.GetChild(0).GetComponent().color = "009FC6".Hex2RGB(); Conductor.instance.metronome = true; } else { MetronomeBTN.transform.GetChild(0).GetComponent().color = Color.gray; Conductor.instance.metronome = false; } } public static string RandomID() { return Starpelly.Random.Strings.RandomString(Starpelly.Enums.Strings.StringType.Alphanumeric, 128); } #endregion private void Update() { if (!Conductor.instance.isPlaying && !Conductor.instance.isPaused) { SongBeat.text = $"Beat {string.Format("{0:0.000}", TimelineSlider.localPosition.x)}"; SongPos.text = FormatTime(Conductor.instance.GetSongPosFromBeat(TimelineSlider.localPosition.x)); } else { SongBeat.text = $"Beat {string.Format("{0:0.000}", Conductor.instance.songPositionInBeats)}"; SongPos.text = FormatTime(Conductor.instance.songPosition); } SliderControl(); #region Keyboard Shortcuts if (!userIsEditingInputField) { if (Input.GetKeyDown(KeyCode.Space)) { if (Input.GetKey(KeyCode.LeftShift)) { PlayCheck(false); } else { PlayCheck(true); } } if (Input.GetKeyDown(KeyCode.P)) { AutoPlayToggle(); } if (Input.GetKeyDown(KeyCode.M)) { MetronomeToggle(); } if (Input.GetKeyDown(KeyCode.Alpha1)) { timelineState.SetState(true, false, false); } else if (Input.GetKeyDown(KeyCode.Alpha2)) { timelineState.SetState(false, true, false); } else if (Input.GetKeyDown(KeyCode.Alpha3)) { timelineState.SetState(false, false, true); } float moveSpeed = 750; if (Input.GetKey(KeyCode.LeftShift)) moveSpeed *= 2; if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A)) { TimelineContent.transform.localPosition += new Vector3(moveSpeed * Time.deltaTime, 0); } else if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D)) { TimelineContent.transform.localPosition += new Vector3(-moveSpeed * Time.deltaTime, 0); } } #endregion if (Input.GetMouseButton(1) && !Conductor.instance.isPlaying && Editor.MouseInRectTransform(TimelineGridSelect)) { movingPlayback = true; } else if (Input.GetMouseButtonUp(1) || Conductor.instance.isPlaying) { movingPlayback = false; } if (movingPlayback) { RectTransformUtility.ScreenPointToLocalPointInRectangle(TimelineContent, Input.mousePosition, Editor.instance.EditorCamera, out lastMousePos); TimelineSlider.localPosition = new Vector3(Mathf.Max(Mathp.Round2Nearest(lastMousePos.x + 0.12f, Timeline.SnapInterval()), 0), TimelineSlider.transform.localPosition.y); if (TimelineSlider.localPosition.x != lastBeatPos) Conductor.instance.SetBeat(TimelineSlider.transform.localPosition.x); lastBeatPos = TimelineSlider.localPosition.x; } if (Conductor.instance.isPlaying) TimelineContent.transform.localPosition = new Vector3((-Conductor.instance.songPositionInBeats * 100) + 200, TimelineContent.transform.localPosition.y); TimelineContent.transform.localPosition = new Vector3(Mathf.Clamp(TimelineContent.transform.localPosition.x, Mathf.NegativeInfinity, 0), TimelineContent.transform.localPosition.y); CurrentTempo.text = $" = {Conductor.instance.songBpm}"; LayersRect.GetWorldCorners(LayerCorners); } public static float GetScaleModifier() { Camera cam = Editor.instance.EditorCamera; return Mathf.Pow(cam.pixelWidth/1280f, 1f) * Mathf.Pow(cam.pixelHeight/720f, 0f); } public Vector2 LayerCornersToDist() { Vector3[] v = LayerCorners; return new Vector2(Mathf.Abs(v[1].x - v[2].x), Mathf.Abs(v[3].y - v[1].y)); } private void SliderControl() { TimelinePlaybackBeat.text = $"Beat {string.Format("{0:0.000}", TimelineSlider.localPosition.x)}"; if (TimelineSongPosLine != null) { TimelineSongPosLine.transform.localPosition = new Vector3(Conductor.instance.songPositionInBeats, TimelineSongPosLine.transform.localPosition.y); TimelineSongPosLine.transform.localScale = new Vector3(1f / TimelineContent.transform.localScale.x, TimelineSongPosLine.transform.localScale.y, 1); } } #region PlayChecks public void PlayCheck(bool fromStart) { if (fromStart) { if (!Conductor.instance.isPlaying && !Conductor.instance.isPaused) { Play(false, TimelineSlider.transform.localPosition.x); } else { Stop(TimelineSlider.transform.localPosition.x); } } else { if (!Conductor.instance.isPlaying) { if (TimelineSongPosLine == null) { Play(false, TimelineSlider.transform.localPosition.x); } else { Play(false, TimelineSongPosLine.transform.localPosition.x); } } else if (!Conductor.instance.isPaused) { Pause(); } } } public void Play(bool fromStart, float time) { // if (fromStart) Stop(); if (!Conductor.instance.isPaused) { TimelineSongPosLine = Instantiate(TimelineSongPosLineRef, TimelineSongPosLineRef.parent).GetComponent(); TimelineSongPosLine.gameObject.SetActive(true); } GameManager.instance.Play(time); SetTimeButtonColors(false, true, true); } public void Pause() { // isPaused = true; GameManager.instance.Pause(); SetTimeButtonColors(true, false, true); } public void Stop(float time) { // isPaused = true; // timelineSlider.value = 0; if (TimelineSongPosLine != null) Destroy(TimelineSongPosLine.gameObject); GameManager.instance.Stop(time); SetTimeButtonColors(true, false, false); } public void SetTimeButtonColors(bool playEnabled, bool pauseEnabled, bool stopEnabled) { if (playEnabled) { PlayBTN.transform.GetChild(0).GetComponent().color = Color.green; PlayBTN.enabled = true; } else { PlayBTN.transform.GetChild(0).GetComponent().color = Color.gray; PlayBTN.enabled = false; } if (pauseEnabled) { PauseBTN.enabled = true; PauseBTN.transform.GetChild(0).GetComponent().color = Color.blue; } else { PauseBTN.transform.GetChild(0).GetComponent().color = Color.gray; PauseBTN.enabled = false; } if (stopEnabled) { StopBTN.enabled = true; StopBTN.transform.GetChild(0).GetComponent().color = Color.red; } else { StopBTN.transform.GetChild(0).GetComponent().color = Color.gray; StopBTN.enabled = false; } } #endregion #region Extras private string FormatTime(float time) { int minutes = (int)time / 60; int seconds = (int)time - 60 * minutes; int milliseconds = (int)(1000 * (time - minutes * 60 - seconds)); return string.Format("{0:00}:{1:00}:{2:000}", minutes, seconds, milliseconds); } public bool CheckIfMouseInTimeline() { return (this.gameObject.activeSelf && RectTransformUtility.RectangleContainsScreenPoint(TimelineContent.transform.parent.gameObject.GetComponent(), Input.mousePosition, Editor.instance.EditorCamera)); } #endregion #region Functions public TimelineEventObj AddEventObject(string eventName, bool dragNDrop = false, Vector3 pos = new Vector3(), Beatmap.Entity entity = null, bool addEvent = false, string eventId = "") { GameObject g = Instantiate(TimelineEventObjRef.gameObject, TimelineEventObjRef.parent); g.transform.localPosition = pos; g.transform.GetChild(3).GetComponent().text = eventName.Split('/')[1]; TimelineEventObj eventObj = g.GetComponent(); if (eventName.Split(1) == "switchGame") eventObj.Icon.sprite = Editor.GameIcon(eventName.Split(2)); else eventObj.Icon.sprite = Editor.GameIcon(eventName.Split(0)); Minigames.GameAction gameAction = EventCaller.instance.GetGameAction(EventCaller.instance.GetMinigame(eventName.Split(0)), eventName.Split(1)); if (gameAction != null) { if (gameAction.resizable == false) { g.GetComponent().sizeDelta = new Vector2(gameAction.defaultLength, LayerHeight()); float length = gameAction.defaultLength; eventObj.length = length; } else { eventObj.resizable = true; if (entity != null && gameAction.defaultLength != entity.length && dragNDrop == false) { g.GetComponent().sizeDelta = new Vector2(entity.length, LayerHeight()); } else { g.GetComponent().sizeDelta = new Vector2(gameAction.defaultLength, LayerHeight()); } } } g.SetActive(true); if (dragNDrop) { var mousePos = Editor.instance.EditorCamera.ScreenToWorldPoint(Input.mousePosition); g.transform.position = new Vector3(mousePos.x, mousePos.y, 0); Selections.instance.ClickSelect(eventObj); eventObj.moving = true; } else { entity.eventObj = g.GetComponent(); entity.track = entity.eventObj.GetTrack(); } if (addEvent) { Beatmap.Entity tempEntity = entity; if (entity == null) { Beatmap.Entity en = new Beatmap.Entity(); en.datamodel = eventName; en.eventObj = eventObj; GameManager.instance.Beatmap.entities.Add(en); GameManager.instance.SortEventsList(); tempEntity = en; // default param value var game = EventCaller.instance.GetMinigame(eventName.Split(0)); var ep = EventCaller.instance.GetGameAction(game, eventName.Split(1)).parameters; if (ep != null) { for (int i = 0; i < ep.Count; i++) { object returnVal = ep[i].parameter; var propertyType = returnVal.GetType(); if (propertyType == typeof(EntityTypes.Integer)) { returnVal = ((EntityTypes.Integer)ep[i].parameter).val; } else if (propertyType == typeof(EntityTypes.Float)) { returnVal = ((EntityTypes.Float)ep[i].parameter).val; } tempEntity[ep[i].propertyName] = returnVal; } } } else { GameManager.instance.Beatmap.entities.Add(entity); GameManager.instance.SortEventsList(); } } eventObjs.Add(eventObj); eventObj.eventObjID = eventId; return eventObj; } private List duplicatedEventObjs = new List(); public TimelineEventObj CopyEventObject(TimelineEventObj e) { Beatmap.Entity clone = e.entity.DeepCopy(); TimelineEventObj dup = AddEventObject(clone.datamodel, false, new Vector3(clone.beat, -clone.track * Timeline.instance.LayerHeight()), clone, true, RandomID()); duplicatedEventObjs.Add(dup); return dup; } public void FinalizeDuplicateEventStack() { CommandManager.instance.Execute(new Commands.Duplicate(duplicatedEventObjs)); duplicatedEventObjs = new List(); } public void DestroyEventObject(Beatmap.Entity entity) { if (EventParameterManager.instance.entity == entity) EventParameterManager.instance.Disable(); eventObjs.Remove(entity.eventObj); GameManager.instance.Beatmap.entities.Remove(entity); Timeline.instance.eventObjs.Remove(entity.eventObj); Destroy(entity.eventObj.gameObject); GameManager.instance.SortEventsList(); } public bool IsMouseAboveEvents() { return Timeline.instance.eventObjs.FindAll(c => c.mouseHovering == true).Count > 0; } public bool InteractingWithEvents() { return eventObjs.FindAll(c => c.moving == true).Count > 0 || eventObjs.FindAll(c => c.resizing == true).Count > 0; } public float SnapToLayer(float y) { float size = LayerHeight(); return Mathf.Clamp(Mathp.Round2Nearest(y, size), -size * 3f, 0f); } public float LayerHeight() { return LayersRect.rect.height / 4f; } public void SetPlaybackSpeed(float speed) { float spd = Mathp.Round2Nearest(speed, Timeline.SnapInterval()); PlaybackSpeed.transform.GetChild(3).GetComponent().text = $"Playback Speed: {spd}x"; Conductor.instance.musicSource.pitch = spd; PlaybackSpeed.value = spd; } public void ResetPlaybackSpeed() { if (Input.GetMouseButton(1)) { PlaybackSpeed.transform.GetChild(3).GetComponent().text = $"Playback Speed: 1x"; PlaybackSpeed.value = 1f; } } #endregion #region Commands public void Move() { } public void Undo() { } #endregion } }