using UnityEngine; using UnityEngine.UI; using UnityEngine.Events; using System; using System.Collections; using System.Collections.Generic; namespace Fungus { public struct PortraitState { public bool onScreen; public bool dimmed; public DisplayType display; public Sprite portrait; public RectTransform position; public FacingDirection facing; public Image portraitImage; } public enum DisplayType { None, Show, Hide, Replace, MoveToFront } public enum FacingDirection { None, Left, Right } public enum PositionOffset { None, OffsetLeft, OffsetRight } [CommandInfo("Narrative", "Portrait", "Controls a character portrait. ")] public class Portrait : Command { [Tooltip("Stage to display portrait on")] public Stage stage; [Tooltip("Display type")] public DisplayType display; [Tooltip("Character to display")] public Character character; [Tooltip("Character to swap with")] public Character replacedCharacter; [Tooltip("Portrait to display")] public Sprite portrait; [Tooltip("Move the portrait from/to this offset position")] public PositionOffset offset; [Tooltip("Move the portrait from this position")] public RectTransform fromPosition; [Tooltip("Move the portrait to this positoin")] public RectTransform toPosition; [Tooltip("Direction character is facing")] public FacingDirection facing; [Tooltip("Use Default Settings")] public bool useDefaultSettings = true; [Tooltip("Fade Duration")] public float fadeDuration = 0.5f; [Tooltip("Movement Duration")] public float moveDuration = 1f; [Tooltip("Shift Offset")] public Vector2 shiftOffset; [Tooltip("Move")] public bool move; [Tooltip("Start from offset")] public bool shiftIntoPlace; [Tooltip("Wait until the tween has finished before executing the next command")] public bool waitUntilFinished = false; // Timer for waitUntilFinished functionality protected float waitTimer; public override void OnEnter() { // If no display specified, do nothing if (display == DisplayType.None) { Continue(); return; } // If no character specified, do nothing if (character == null) { Continue(); return; } // If Replace and no replaced character specified, do nothing if (display == DisplayType.Replace && replacedCharacter == null) { Continue(); return; } // Selected "use default Portrait Stage" if (stage == null) // Default portrait stage selected { if (stage == null) // If no default specified, try to get any portrait stage in the scene { stage = GameObject.FindObjectOfType(); } } // If portrait stage does not exist, do nothing if (stage == null) { Continue(); return; } // Use default settings if (useDefaultSettings) { fadeDuration = stage.fadeDuration; moveDuration = stage.moveDuration; shiftOffset = stage.shiftOffset; } if (character.state.portraitImage == null) { CreatePortraitObject(character, stage); } // if no previous portrait, use default portrait if (character.state.portrait == null) { character.state.portrait = character.profileSprite; } // Selected "use previous portrait" if (portrait == null) { portrait = character.state.portrait; } // if no previous position, use default position if (character.state.position == null) { character.state.position = stage.defaultPosition.rectTransform; } // Selected "use previous position" if (toPosition == null) { toPosition = character.state.position; } if (replacedCharacter != null) { // if no previous position, use default position if (replacedCharacter.state.position == null) { replacedCharacter.state.position = stage.defaultPosition.rectTransform; } } // If swapping, use replaced character's position if (display == DisplayType.Replace) { toPosition = replacedCharacter.state.position; } // Selected "use previous position" if (fromPosition == null) { fromPosition = character.state.position; } // if portrait not moving, use from position is same as to position if (!move) { fromPosition = toPosition; } if (display == DisplayType.Hide) { fromPosition = character.state.position; } // if no previous facing direction, use default facing direction if (character.state.facing == FacingDirection.None) { character.state.facing = character.portraitsFace; } // Selected "use previous facing direction" if (facing == FacingDirection.None) { facing = character.state.facing; } switch(display) { case (DisplayType.Show): Show(character, fromPosition, toPosition); character.state.onScreen = true; if (!stage.charactersOnStage.Contains(character)) { stage.charactersOnStage.Add(character); } break; case (DisplayType.Hide): Hide(character, fromPosition, toPosition); character.state.onScreen = false; stage.charactersOnStage.Remove(character); break; case (DisplayType.Replace): Show(character, fromPosition, toPosition); Hide(replacedCharacter, replacedCharacter.state.position, replacedCharacter.state.position); character.state.onScreen = true; replacedCharacter.state.onScreen = false; stage.charactersOnStage.Add(character); stage.charactersOnStage.Remove(replacedCharacter); break; case (DisplayType.MoveToFront): MoveToFront(character); break; } if (display == DisplayType.Replace) { character.state.display = DisplayType.Show; replacedCharacter.state.display = DisplayType.Hide; } else { character.state.display = display; } character.state.portrait = portrait; character.state.facing = facing; character.state.position = toPosition; waitTimer = 0f; if (!waitUntilFinished) { Continue(); } else { StartCoroutine(WaitUntilFinished(fadeDuration)); } } protected virtual IEnumerator WaitUntilFinished(float duration) { // Wait until the timer has expired // Any method can modify this timer variable to delay continuing. waitTimer = duration; while (waitTimer > 0f) { waitTimer -= Time.deltaTime; yield return null; } Continue(); } protected virtual void CreatePortraitObject(Character character, Stage stage) { // Create a new portrait object GameObject portraitObj = new GameObject(character.name, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image)); // Set it to be a child of the stage portraitObj.transform.SetParent(stage.portraitCanvas.transform, true); // Configure the portrait image Image portraitImage = portraitObj.GetComponent(); portraitImage.preserveAspect = true; portraitImage.sprite = character.profileSprite; portraitImage.color = new Color(1f, 1f, 1f, 0f); // LeanTween doesn't handle 0 duration properly float duration = (fadeDuration > 0f) ? fadeDuration : float.Epsilon; // Fade in character image (first time) LeanTween.alpha(portraitImage.transform as RectTransform, 1f, duration).setEase(stage.fadeEaseType); // Tell character about portrait image character.state.portraitImage = portraitImage; } protected void SetupPortrait(Character character, RectTransform fromPosition) { SetRectTransform(character.state.portraitImage.rectTransform, fromPosition); if (character.state.facing != character.portraitsFace) { character.state.portraitImage.rectTransform.localScale = new Vector3(-1f, 1f, 1f); } else { character.state.portraitImage.rectTransform.localScale = new Vector3(1f, 1f, 1f); } if (facing != character.portraitsFace) { character.state.portraitImage.rectTransform.localScale = new Vector3(-1f, 1f, 1f); } else { character.state.portraitImage.rectTransform.localScale = new Vector3(1f, 1f, 1f); } } public static void SetRectTransform(RectTransform oldRectTransform, RectTransform newRectTransform) { oldRectTransform.eulerAngles = newRectTransform.eulerAngles; oldRectTransform.position = newRectTransform.position; oldRectTransform.rotation = newRectTransform.rotation; oldRectTransform.anchoredPosition = newRectTransform.anchoredPosition; oldRectTransform.sizeDelta = newRectTransform.sizeDelta; oldRectTransform.anchorMax = newRectTransform.anchorMax; oldRectTransform.anchorMin = newRectTransform.anchorMin; oldRectTransform.pivot = newRectTransform.pivot; oldRectTransform.localScale = newRectTransform.localScale; } protected void Show(Character character, RectTransform fromPosition, RectTransform toPosition) { if (shiftIntoPlace) { fromPosition = Instantiate(toPosition) as RectTransform; if (offset == PositionOffset.OffsetLeft) { fromPosition.anchoredPosition = new Vector2(fromPosition.anchoredPosition.x - Mathf.Abs(shiftOffset.x), fromPosition.anchoredPosition.y - Mathf.Abs(shiftOffset.y)); } else if (offset == PositionOffset.OffsetRight) { fromPosition.anchoredPosition = new Vector2(fromPosition.anchoredPosition.x + Mathf.Abs(shiftOffset.x), fromPosition.anchoredPosition.y + Mathf.Abs(shiftOffset.y)); } else { fromPosition.anchoredPosition = new Vector2(fromPosition.anchoredPosition.x, fromPosition.anchoredPosition.y); } } SetupPortrait(character, fromPosition); // LeanTween doesn't handle 0 duration properly float duration = (fadeDuration > 0f) ? fadeDuration : float.Epsilon; // Fade out a duplicate of the existing portrait image if (character.state.portraitImage != null) { GameObject tempGO = GameObject.Instantiate(character.state.portraitImage.gameObject); tempGO.transform.SetParent(character.state.portraitImage.transform, false); tempGO.transform.localPosition = Vector3.zero; Image tempImage = tempGO.GetComponent(); tempImage.sprite = character.state.portraitImage.sprite; tempImage.preserveAspect = true; tempImage.color = character.state.portraitImage.color; LeanTween.alpha(tempImage.rectTransform, 0f, duration).setEase(stage.fadeEaseType).setOnComplete(() => { Destroy(tempGO); }); } // Fade in the new sprite image character.state.portraitImage.sprite = portrait; character.state.portraitImage.color = new Color(1f, 1f, 1f, 0f); LeanTween.alpha(character.state.portraitImage.rectTransform, 1f, duration).setEase(stage.fadeEaseType); DoMoveTween(character, fromPosition, toPosition); } protected void Hide(Character character, RectTransform fromPosition, RectTransform toPosition) { if (character.state.display == DisplayType.None) { return; } SetupPortrait(character, fromPosition); // LeanTween doesn't handle 0 duration properly float duration = (fadeDuration > 0f) ? fadeDuration : float.Epsilon; LeanTween.alpha(character.state.portraitImage.rectTransform, 0f, duration).setEase(stage.fadeEaseType); DoMoveTween(character, fromPosition, toPosition); } protected void MoveToFront(Character character) { character.state.portraitImage.transform.SetSiblingIndex(character.state.portraitImage.transform.parent.childCount); } protected void DoMoveTween(Character character, RectTransform fromPosition, RectTransform toPosition) { // LeanTween doesn't handle 0 duration properly float duration = (moveDuration > 0f) ? moveDuration : float.Epsilon; // LeanTween.move uses the anchoredPosition, so all position images must have the same anchor position LeanTween.move(character.state.portraitImage.rectTransform, toPosition.anchoredPosition3D, duration).setEase(stage.fadeEaseType); if (waitUntilFinished) { waitTimer = duration; } } public static void SetDimmed(Character character, Stage stage, bool dimmedState) { if (character.state.dimmed == dimmedState) { return; } character.state.dimmed = dimmedState; Color targetColor = dimmedState ? new Color(0.5f, 0.5f, 0.5f, 1f) : Color.white; // LeanTween doesn't handle 0 duration properly float duration = (stage.fadeDuration > 0f) ? stage.fadeDuration : float.Epsilon; LeanTween.color(character.state.portraitImage.rectTransform, targetColor, duration).setEase(stage.fadeEaseType); } public override string GetSummary() { if (display == DisplayType.None && character == null) { return "Error: No character or display selected"; } else if (display == DisplayType.None) { return "Error: No display selected"; } else if (character == null) { return "Error: No character selected"; } string displaySummary = ""; string characterSummary = ""; string fromPositionSummary = ""; string toPositionSummary = ""; string stageSummary = ""; string portraitSummary = ""; string facingSummary = ""; displaySummary = StringFormatter.SplitCamelCase(display.ToString()); if (display == DisplayType.Replace) { if (replacedCharacter != null) { displaySummary += " \"" + replacedCharacter.name + "\" with"; } } characterSummary = character.name; if (stage != null) { stageSummary = " on \"" + stage.name + "\""; } if (portrait != null) { portraitSummary = " " + portrait.name; } if (shiftIntoPlace) { if (offset != 0) { fromPositionSummary = offset.ToString(); fromPositionSummary = " from " + "\"" + fromPositionSummary + "\""; } } else if (fromPosition != null) { fromPositionSummary = " from " + "\"" + fromPosition.name + "\""; } if (toPosition != null) { string toPositionPrefixSummary = ""; if (move) { toPositionPrefixSummary = " to "; } else { toPositionPrefixSummary = " at "; } toPositionSummary = toPositionPrefixSummary + "\"" + toPosition.name + "\""; } if (facing != FacingDirection.None) { if (facing == FacingDirection.Left) { facingSummary = "<--"; } if (facing == FacingDirection.Right) { facingSummary = "-->"; } facingSummary = " facing \"" + facingSummary + "\""; } return displaySummary + " \"" + characterSummary + portraitSummary + "\"" + stageSummary + facingSummary + fromPositionSummary + toPositionSummary; } public override Color GetButtonColor() { return new Color32(230, 200, 250, 255); } public override void OnCommandAdded(Block parentBlock) { //Default to display type: show display = DisplayType.Show; } } }