using System.Linq; using System.Collections.Generic; using UnityEngine; using NaughtyBezierCurves; using HeavenStudio.Util; using HeavenStudio.InputSystem; namespace HeavenStudio.Games.Loaders { using static Minigames; public static class AgbBouncyRoadLoader { public static Minigame AddGame(EventCaller eventCaller) { return new Minigame("bouncyRoad", "Bouncy Road", "0296FF", false, false, new List() { new GameAction("ball", "Ball") { defaultLength = 1f, resizable = true, parameters = new List() { new Param("goal", true, "Play Goal Sound"), new Param("color", Color.white, "Color", "Choose the color of the ball."), } }, }, new List() { "agb", "normal" }, "agbbouncy", "en", new List() { }, chronologicalSortKey: 24 ); } } } namespace HeavenStudio.Games { using Scripts_BouncyRoad; public class BouncyRoad : Minigame { [SerializeField] GameObject baseBall; [SerializeField] GameObject baseBounceCurve; [SerializeField] Transform CurveHolder; [SerializeField] Transform ThingsTrans; [System.NonSerialized] public Animator[] ThingsAnim; [System.NonSerialized] public Dictionary CurveCache; [SerializeField] BezierCurve3D PosCurve; [SerializeField] float fallY; const double BALL_SEEK_TIME = 1.0; private struct ScheduledBall { public double beat; public double length; public bool goal; public Color color; } List scheduledBalls = new(); int ballIndex; public static BouncyRoad instance; const int IALeft = 0; const int IARight = 1; protected static bool IA_PadLeft(out double dt) { return PlayerInput.GetPadDown(InputController.ActionsPad.Up, out dt) || PlayerInput.GetPadDown(InputController.ActionsPad.Down, out dt) || PlayerInput.GetPadDown(InputController.ActionsPad.Left, out dt) || PlayerInput.GetPadDown(InputController.ActionsPad.Right, out dt); } protected static bool IA_TouchLeft(out double dt) { return PlayerInput.GetTouchDown(InputController.ActionsTouch.Left, out dt); } protected static bool IA_PadRight(out double dt) { return PlayerInput.GetPadDown(InputController.ActionsPad.East, out dt); } protected static bool IA_TouchRight(out double dt) { return PlayerInput.GetTouchDown(InputController.ActionsTouch.Right, out dt); } public static PlayerInput.InputAction InputAction_Left = new("AgbBouncyLeft", new int[] { IALeft, IALeft, IALeft }, IA_PadLeft, IA_TouchLeft, IA_BatonBasicPress); public static PlayerInput.InputAction InputAction_Right = new("AgbBouncyRight", new int[] { IARight, IARight, IAEmptyCat }, IA_PadRight, IA_TouchRight, IA_Empty); void Awake() { instance = this; ThingsAnim = new Animator[ThingsTrans.childCount]; int childIndex = 0; foreach (Transform child in ThingsTrans) { // var prog = (float)childIndex/(ThingsTrans.childCount-1); // child.transform.localPosition = PosCurve.GetPoint(prog); ThingsAnim[childIndex++] = child.GetComponent(); } var newCurves = new BezierCurve3D[ThingsTrans.childCount + 3]; // for (var i = 0; i < ThingsAnim.Length + 1; ++i) // { // var prog1 = (float)(i-1)/(ThingsTrans.childCount-1); // var prog2 = (float)(i)/(ThingsTrans.childCount-1); // var pos1 = PosCurve.GetPoint(prog1); // var pos2 = PosCurve.GetPoint(prog2); // var newCurve = GenerateInitCurve(pos1, pos2); // newCurves[i] = newCurve.GetComponent(); // } { Vector3 pos1, pos2; pos1 = PosCurve.GetPoint((float)(-1)/(ThingsAnim.Length-1)); pos2 = PosCurve.GetPoint(0); newCurves[0] = GenerateInitCurve(pos1, pos2).GetComponent(); for (var i = 0; i < ThingsAnim.Length-1; ++i) { pos1 = ThingsTrans.GetChild(i).transform.localPosition; pos2 = ThingsTrans.GetChild(i+1).transform.localPosition; var newCurve = GenerateInitCurve(pos1, pos2); newCurves[1+i] = newCurve.GetComponent(); } pos1 = PosCurve.GetPoint(1); pos2 = PosCurve.GetPoint((float)(ThingsAnim.Length)/(ThingsAnim.Length-1)); newCurves[ThingsAnim.Length] = GenerateInitCurve(pos1, pos2).GetComponent(); } newCurves[^2] = GenerateMissCurve(13).GetComponent(); newCurves[^1] = GenerateMissCurve(14).GetComponent(); CurveCache = new Dictionary(); CurveCache.Add(1, newCurves); } public override void OnGameSwitch(double beat) { double gameStartBeat = beat, gameEndBeat = double.MaxValue; var firstEnd = EventCaller.GetAllInGameManagerList("gameManager", new string[] { "switchGame", "end" }).Find(x => x.beat > gameStartBeat); gameEndBeat = firstEnd?.beat ?? gameEndBeat; scheduledBalls.Clear(); ballIndex = 0; var events = EventCaller.GetAllInGameManagerList("bouncyRoad", new string[] { "ball" }).FindAll(x => x.beat >= gameStartBeat && x.beat < gameEndBeat); foreach (var e in events) { if (e.length == 0) continue; var ball = new ScheduledBall { beat = e.beat, length = e.length, goal = e["goal"], color = e["color"], }; scheduledBalls.Add(ball); } scheduledBalls.Sort((x, y) => (x.beat - x.length).CompareTo(y.beat - y.length)); } public override void OnPlay(double beat) { OnGameSwitch(beat); } void Update() { var cond = Conductor.instance; if (!cond.isPlaying || cond.isPaused) return; if (PlayerInput.GetIsAction(InputAction_Right) && !IsExpectingInputNow(InputAction_Right)) { ThingsAnim[12].Play("podium", 0, 0); } if (PlayerInput.GetIsAction(InputAction_Left) && !IsExpectingInputNow(InputAction_Left)) { ThingsAnim[13].Play("podium", 0, 0); } UpdateBalls(); } void UpdateBalls() { double beat = conductor.songPositionInBeatsAsDouble; while(ballIndex < scheduledBalls.Count) { var ball = scheduledBalls[ballIndex]; if (ball.beat - ball.length < beat + BALL_SEEK_TIME) { SpawnBall(ball.beat, ball.length, ball.goal, ball.color); ballIndex++; } else { break; } } } public void SpawnBall(double beat, double length, bool goal, Color color) { var newBall = Instantiate(baseBall, transform).GetComponent(); newBall.startBeat = beat; newBall.lengthBeat = length; newBall.goal = goal; newBall.color = color; newBall.curve = GetHeightCurve((float)length); BeatAction.New(instance, new List() { new BeatAction.Action(beat - length, delegate { newBall.Init(); newBall.gameObject.SetActive(true); }) }); } List bounceBeats = new(); public void PlayBounceSound(double beat, double length) { var sounds = new List(); for (int i = 0; i < 12 ; i++) { var bounceBeat = beat + i * length; if (!bounceBeats.Contains(bounceBeat)) sounds.Add(new MultiSound.Sound("bouncyRoad/ballBounce", bounceBeat)); bounceBeats.Add(bounceBeat); } MultiSound.Play(sounds.ToArray()); } private Transform GenerateInitCurve(Vector3 pos1, Vector3 pos2) { float dist = Vector3.Distance(pos1, pos2); float angle = Mathf.Atan2(pos1.z - pos2.z, pos1.x - pos2.x) * Mathf.Rad2Deg; var newCurve = Instantiate(baseBounceCurve, CurveHolder).transform; var point0 = newCurve.GetChild(0); var point1 = newCurve.GetChild(1); point0.transform.localPosition = pos1; point0.transform.localEulerAngles = new Vector3(0, -angle, 0); point0.transform.localScale = new Vector3(dist, 1, 1); point1.transform.localPosition = pos2; point1.transform.localEulerAngles = new Vector3(0, -angle, 0); point1.transform.localScale = new Vector3(dist, 1, 1); return newCurve; } private Transform GenerateMissCurve(int number) { var curve = CurveHolder.GetChild(number); var newCurve = Instantiate(curve, CurveHolder).transform; var point0 = newCurve.GetChild(0); var point1 = newCurve.GetChild(1); Vector3 pos1 = point1.transform.localPosition; point1.transform.localPosition = pos1 + new Vector3(0, fallY, 0); return newCurve; } private BezierCurve3D[] GetHeightCurve(float length) { BezierCurve3D[] newCurves; CurveCache.TryGetValue(length, out newCurves); if (newCurves is null) { var newCurveHolder = Instantiate(CurveHolder, transform); newCurveHolder.name = $"CurveHolder_{length}"; var newCurvesTrans = newCurveHolder.transform; newCurves = new BezierCurve3D[newCurvesTrans.childCount]; int childIndex = 0; foreach (Transform child in newCurvesTrans) { var point0 = child.GetChild(0); var point1 = child.GetChild(1); Vector3 scale = point0.transform.localScale; point0.transform.localScale = new Vector3(scale.x, length * scale.y, scale.z); point1.transform.localScale = new Vector3(scale.x, length * scale.y, scale.z); newCurves[childIndex++] = child.GetComponent(); } CurveCache.Add(length, newCurves); } return newCurves; } } }