HeavenStudioPlus/Assets/Scripts/Games/RhythmRally/RhythmRally.cs

504 lines
20 KiB
C#
Raw Normal View History

2022-02-03 02:09:50 +00:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
2022-02-14 08:53:58 +00:00
using NaughtyBezierCurves;
using DG.Tweening;
2022-02-03 02:09:50 +00:00
2022-03-14 14:21:05 +00:00
using HeavenStudio.Util;
namespace HeavenStudio.Games.Loaders
{
using static Minigames;
public static class NtrPingpongLoader
{
public static Minigame AddGame(EventCaller eventCaller) {
return new Minigame("rhythmRally", "Rhythm Rally", "FFFFFF", true, false, new List<GameAction>()
{
2022-08-21 03:13:52 +00:00
new GameAction("bop", "Bop")
{
function = delegate { RhythmRally.instance.Bop(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); },
defaultLength = 0.5f,
resizable = true
},
2022-08-23 13:09:43 +00:00
new GameAction("whistle", "Whistle")
2022-08-21 03:13:52 +00:00
{
preFunction = delegate { RhythmRally.PlayWhistle(eventCaller.currentEntity.beat); },
2022-08-21 03:13:52 +00:00
defaultLength = 0.5f
},
new GameAction("toss ball", "Toss Ball")
{
function = delegate { RhythmRally.instance.Toss(eventCaller.currentEntity.beat, eventCaller.currentEntity.length, 6f, true); },
defaultLength = 2f,
resizable = true
2022-08-21 03:13:52 +00:00
},
new GameAction("rally", "Rally")
{
function = delegate { RhythmRally.instance.Serve(eventCaller.currentEntity.beat, RhythmRally.RallySpeed.Normal); },
defaultLength = 4f,
resizable = true
},
new GameAction("slow rally", "Slow Rally")
{
function = delegate { RhythmRally.instance.Serve(eventCaller.currentEntity.beat, RhythmRally.RallySpeed.Slow); },
defaultLength = 8f,
resizable = true
},
new GameAction("fast rally", "Fast Rally")
{
function = delegate { RhythmRally.instance.PrepareFastRally(eventCaller.currentEntity.beat, RhythmRally.RallySpeed.Fast, eventCaller.currentEntity["muteAudio"]); },
defaultLength = 6f,
parameters = new List<Param>()
{
new Param("muteAudio", false, "Mute Cowbell", "Whether the cowbell sound should play or not.")
}
2022-08-21 03:13:52 +00:00
},
new GameAction("superfast rally", "Superfast Rally")
{
function = delegate { RhythmRally.instance.PrepareFastRally(eventCaller.currentEntity.beat, RhythmRally.RallySpeed.SuperFast, eventCaller.currentEntity["muteAudio"]); },
defaultLength = 12f,
parameters = new List<Param>()
{
new Param("muteAudio", false, "Mute Cowbell", "Whether the cowbell sound should play or not.")
}
2022-08-21 03:13:52 +00:00
},
new GameAction("pose", "End Pose")
{
function = delegate { RhythmRally.instance.Pose(); },
defaultLength = 0.5f
},
new GameAction("camera", "Camera Controls")
{
function = delegate {
var e = eventCaller.currentEntity;
var rotation = new Vector3(0, e["valA"], 0);
RhythmRally.instance.ChangeCameraAngle(rotation, e["valB"], e.length, (Ease)e["type"], (RotateMode)e["type2"]);
2022-08-21 03:13:52 +00:00
},
defaultLength = 4,
resizable = true,
parameters = new List<Param>() {
new Param("valA", new EntityTypes.Integer(-360, 360, 0), "Angle", "The rotation of the camera around the center of the table"),
new Param("valB", new EntityTypes.Float(0.5f, 4f, 1), "Zoom", "The camera's level of zoom (Lower value = Zoomed in)"),
new Param("type", Ease.Linear, "Ease", "The easing function to use"),
new Param("type2", RotateMode.Fast, "Rotation Mode", "The rotation mode to use")
}
},
});
}
}
}
2022-03-14 14:21:05 +00:00
namespace HeavenStudio.Games
2022-02-03 02:09:50 +00:00
{
2022-03-12 04:10:13 +00:00
using Scripts_RhythmRally;
public class RhythmRally : Minigame
2022-02-03 02:09:50 +00:00
{
2022-02-14 08:53:58 +00:00
public enum RallySpeed { Slow, Normal, Fast, SuperFast }
2022-02-14 08:53:58 +00:00
[Header("Camera")]
public Transform renderQuadTrans;
public Transform cameraPivot;
2022-02-03 02:09:50 +00:00
2022-02-14 08:53:58 +00:00
[Header("Ball and curve info")]
public GameObject ball;
2022-02-14 08:53:58 +00:00
public GameObject ballShadow;
public TrailRenderer ballTrail;
2022-02-14 08:53:58 +00:00
public BezierCurve3D serveCurve;
public BezierCurve3D returnCurve;
public BezierCurve3D tossCurve;
2022-03-05 19:30:05 +00:00
public BezierCurve3D missCurve;
2022-02-15 05:17:53 +00:00
public GameObject ballHitFX;
2022-02-14 08:53:58 +00:00
2022-02-14 08:53:58 +00:00
[Header("Animators")]
public Animator playerAnim;
public Animator opponentAnim;
2022-02-14 08:53:58 +00:00
[Header("Properties")]
public RallySpeed rallySpeed = RallySpeed.Normal;
public bool started;
public bool missed;
public bool served;
public bool tossing;
2022-02-14 08:53:58 +00:00
public float serveBeat;
public float targetBeat;
public float tossBeat;
2022-03-05 19:30:05 +00:00
public float missBeat;
public float tossLength;
2022-02-15 05:17:53 +00:00
private bool inPose;
2022-02-14 08:53:58 +00:00
public Paddlers paddlers;
public GameEvent bop = new GameEvent();
public static RhythmRally instance;
private void Awake()
{
instance = this;
paddlers.Init();
renderQuadTrans.gameObject.SetActive(true);
var cam = GameCamera.instance.camera;
var camHeight = 2f * cam.orthographicSize;
var camWidth = camHeight * cam.aspect;
renderQuadTrans.localScale = new Vector3(camWidth, camHeight, 1f);
playerAnim.Play("Idle", 0, 0);
opponentAnim.Play("Idle", 0, 0);
2022-02-03 02:09:50 +00:00
}
2022-02-14 08:53:58 +00:00
const float tableHitTime = 0.58f;
bool opponentServing = false; // Opponent serving this frame?
2022-02-03 02:09:50 +00:00
void Update()
{
var cond = Conductor.instance;
2022-02-14 08:53:58 +00:00
var currentBeat = cond.songPositionInBeats;
var hitBeat = serveBeat; // Beat when the last paddler hit the ball
var beatDur1 = 1f; // From paddle to table
var beatDur2 = 1f; // From table to other paddle
var playerState = playerAnim.GetCurrentAnimatorStateInfo(0);
var opponentState = opponentAnim.GetCurrentAnimatorStateInfo(0);
bool playerPrepping = false; // Player using prep animation?
bool opponentPrepping = false; // Opponent using prep animation?
if (started)
{
// Determine hitBeat and beatDurs.
switch (rallySpeed)
{
case RallySpeed.Normal:
if (!served)
{
hitBeat = serveBeat + 2f;
}
break;
case RallySpeed.Fast:
if (!served)
{
hitBeat = serveBeat + 1f;
beatDur1 = 1f;
beatDur2 = 2f;
}
else
{
beatDur1 = 0.5f;
beatDur2 = 0.5f;
}
break;
case RallySpeed.SuperFast:
if (!served)
{
hitBeat = serveBeat + 1f;
}
beatDur1 = 0.5f;
beatDur2 = 0.5f;
break;
case RallySpeed.Slow:
if (!served)
{
hitBeat = serveBeat + 4f;
}
beatDur1 = 2f;
beatDur2 = 2f;
break;
}
// Ball position.
var curveToUse = served ? serveCurve : returnCurve;
float curvePosition;
var hitPosition1 = cond.GetPositionFromBeat(hitBeat, beatDur1);
if (hitPosition1 >= 1f)
{
var hitPosition2 = cond.GetPositionFromBeat(hitBeat + beatDur1, beatDur2);
curvePosition = tableHitTime + hitPosition2 * (1f - tableHitTime);
}
else
{
curvePosition = hitPosition1 * tableHitTime;
}
if (!missed)
{
2022-03-05 19:30:05 +00:00
float curveHeight = 1.25f;
if ((rallySpeed == RallySpeed.Fast && served) || rallySpeed == RallySpeed.SuperFast)
curveHeight = 0.75f;
2022-02-15 05:17:53 +00:00
else if (rallySpeed == RallySpeed.Fast && !served && hitPosition1 >= 1f)
2022-02-14 08:53:58 +00:00
curveHeight = 2f;
else if (rallySpeed == RallySpeed.Slow)
curveHeight = 3f;
curveToUse.transform.localScale = new Vector3(1f, curveHeight, 1f);
2022-03-05 17:41:54 +00:00
ball.transform.position = curveToUse.GetPoint(Mathf.Max(0, curvePosition));
2022-03-05 19:30:05 +00:00
// Make ball inactive before it passes through the floor.
if (curvePosition > 1.05f)
ball.SetActive(false);
2022-02-14 08:53:58 +00:00
}
else
{
if (tossing)
{
TossUpdate(tossBeat, tossLength);
}
2022-03-05 19:30:05 +00:00
else
{
var missPosition = cond.GetPositionFromBeat(missBeat, 1f);
ball.transform.position = missCurve.GetPoint(Mathf.Max(0, missPosition));
if (missPosition > 1f)
ball.SetActive(false);
}
}
2022-02-14 08:53:58 +00:00
// TODO: Make conditional so ball shadow only appears when over table.
ballShadow.transform.position = new Vector3(ball.transform.position.x, -0.399f, ball.transform.position.z);
var timeBeforeNextHit = hitBeat + beatDur1 + beatDur2 - currentBeat;
// Check if the opponent should swing.
if (!served && timeBeforeNextHit <= 0f)
{
var rallies = GameManager.instance.Beatmap.entities.FindAll(c => c.datamodel == "rhythmRally/rally" || c.datamodel == "rhythmRally/slow rally");
2022-02-14 08:53:58 +00:00
for (int i = 0; i < rallies.Count; i++)
{
var rally = rallies[i];
if (rally.beat - currentBeat <= 0f && rally.beat + rally.length - currentBeat > 0f)
{
Serve(hitBeat + beatDur1 + beatDur2, rallySpeed);
opponentServing = true;
break;
}
}
}
// Check if paddler should do ready animation.
bool readyToPrep;
2022-03-05 19:30:05 +00:00
switch (rallySpeed)
{
case RallySpeed.Slow:
case RallySpeed.Fast:
readyToPrep = timeBeforeNextHit <= 2f;
break;
case RallySpeed.SuperFast:
readyToPrep = timeBeforeNextHit <= 0.5f;
break;
default:
readyToPrep = timeBeforeNextHit <= 1f;
break;
}
2022-02-14 08:53:58 +00:00
// Paddler ready animation.
2022-02-15 05:17:53 +00:00
if (readyToPrep && !opponentServing && !inPose)
2022-02-14 08:53:58 +00:00
{
if (served)
{
playerPrepping = true;
if ((playerState.IsName("Swing") && playerAnim.IsAnimationNotPlaying()) || (!playerState.IsName("Swing") && !playerState.IsName("Ready1")))
playerAnim.Play("Ready1");
}
else if (!opponentServing)
{
opponentPrepping = true;
if ((opponentState.IsName("Swing") && opponentAnim.IsAnimationNotPlaying()) || (!opponentState.IsName("Swing") && !opponentState.IsName("Ready1")))
{
2022-02-14 08:53:58 +00:00
opponentAnim.Play("Ready1");
// Toss ball if it fell off the table.
if (missed && !tossing)
{
float tossHeight = 3f;
if (rallySpeed == RallySpeed.Slow || rallySpeed == RallySpeed.Fast)
tossHeight = 6f;
Toss(hitBeat + beatDur1, beatDur2, tossHeight);
}
}
2022-02-14 08:53:58 +00:00
// If player never swung and is still in ready state, snap them out of it.
if (missed && playerState.IsName("Ready1"))
playerAnim.Play("Beat");
}
}
}
else
{
if (tossing)
{
TossUpdate(tossBeat, tossLength);
}
}
2022-02-14 08:53:58 +00:00
// Paddler bop animation.
if (cond.ReportBeat(ref bop.lastReportedBeat, bop.startBeat % 1))
{
2022-02-15 05:17:53 +00:00
if (currentBeat >= bop.startBeat && currentBeat < bop.startBeat + bop.length && !inPose)
{
2022-02-14 08:53:58 +00:00
if (!playerPrepping && (playerAnim.IsAnimationNotPlaying() || playerState.IsName("Idle") || playerState.IsName("Beat")))
playerAnim.Play("Beat", 0, 0);
if (!opponentPrepping && !opponentServing && !tossing && (opponentAnim.IsAnimationNotPlaying() || opponentState.IsName("Idle") || opponentState.IsName("Beat")))
opponentAnim.Play("Beat", 0, 0);
}
}
2022-02-14 08:53:58 +00:00
opponentServing = false;
}
public void Bop(float beat, float length)
{
bop.length = length;
bop.startBeat = beat;
2022-02-03 02:09:50 +00:00
}
2022-02-14 08:53:58 +00:00
public void Serve(float beat, RallySpeed speed)
{
2022-03-05 17:41:54 +00:00
if (!ball.activeSelf)
ball.SetActive(true);
2022-03-05 19:30:05 +00:00
if (!ballTrail.gameObject.activeSelf)
ballTrail.gameObject.SetActive(true);
2022-02-14 08:53:58 +00:00
served = true;
missed = false;
started = true;
opponentServing = true;
tossing = false;
2022-02-14 08:53:58 +00:00
serveBeat = beat;
rallySpeed = speed;
var bounceBeat = 0f;
switch (rallySpeed)
{
case RallySpeed.Normal:
targetBeat = 2f;
2022-02-14 08:53:58 +00:00
bounceBeat = serveBeat + 1f;
break;
case RallySpeed.Fast:
case RallySpeed.SuperFast:
targetBeat = 1f;
2022-02-14 08:53:58 +00:00
bounceBeat = serveBeat + 0.5f;
break;
case RallySpeed.Slow:
targetBeat = 4f;
2022-02-14 08:53:58 +00:00
bounceBeat = serveBeat + 2f;
break;
}
opponentAnim.Play("Swing", 0, 0);
MultiSound.Play(new MultiSound.Sound[] { new MultiSound.Sound("rhythmRally/Serve", serveBeat), new MultiSound.Sound("rhythmRally/ServeBounce", bounceBeat) });
2022-02-15 05:17:53 +00:00
paddlers.BounceFX(bounceBeat);
2022-02-14 08:53:58 +00:00
ScheduleInput(serveBeat, targetBeat, InputType.STANDARD_DOWN, paddlers.Just, paddlers.Miss, paddlers.Out);
2022-02-14 08:53:58 +00:00
}
public void Toss(float beat, float length, float height, bool firstToss = false)
{
2022-03-05 19:30:05 +00:00
// Hide trail while tossing to prevent weirdness while teleporting ball.
ballTrail.gameObject.SetActive(false);
if (firstToss)
height *= length/2f;
tossCurve.transform.localScale = new Vector3(1f, height, 1f);
tossBeat = beat;
tossLength = length;
tossing = true;
if (firstToss)
{
opponentAnim.Play("Ready1");
}
2022-03-05 17:41:54 +00:00
if (!ball.activeSelf)
ball.SetActive(true);
}
private void TossUpdate(float beat, float duration)
{
var tossPosition = Conductor.instance.GetPositionFromBeat(beat, duration);
ball.transform.position = tossCurve.GetPoint(Mathf.Clamp(tossPosition, 0, 1));
2022-03-05 19:30:05 +00:00
if (tossPosition > 1.05f)
ball.SetActive(false);
}
public static void PlayWhistle(float beat)
{
MultiSound.Play(new MultiSound.Sound[]
{
new MultiSound.Sound("rhythmRally/Whistle", beat),
}, forcePlay: true);
}
2022-02-15 05:17:53 +00:00
public void Pose()
{
playerAnim.Play("Pose", 0, 0);
opponentAnim.Play("Pose", 0, 0);
2022-03-05 17:41:54 +00:00
ball.SetActive(false); // temporary solution, should realistically just fall down
2022-02-15 05:17:53 +00:00
inPose = true;
}
2022-02-28 20:43:32 +00:00
public void ChangeCameraAngle(Vector3 rotation, float camZoom, float length, Ease ease, RotateMode rotateMode)
{
2022-02-28 20:43:32 +00:00
var len = length * Conductor.instance.secPerBeat;
cameraPivot.DORotate(rotation, len, rotateMode).SetEase(ease);
cameraPivot.DOScale(camZoom, len).SetEase(ease);
}
public void PrepareFastRally(float beat, RallySpeed speedChange, bool muteAudio = false)
2022-02-14 08:53:58 +00:00
{
if (speedChange == RallySpeed.Fast)
{
BeatAction.New(gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(beat + 2f, delegate { Serve(beat + 2f, RallySpeed.Fast); })
});
if (muteAudio) return;
2022-02-14 08:53:58 +00:00
MultiSound.Play(new MultiSound.Sound[]
{
new MultiSound.Sound("rhythmRally/Tonk", beat),
new MultiSound.Sound("rhythmRally/Tink", beat + 0.5f),
new MultiSound.Sound("rhythmRally/Tonk", beat + 1f)
});
}
else if (speedChange == RallySpeed.SuperFast)
{
BeatAction.New(gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(beat + 4f, delegate { Serve(beat + 4f, RallySpeed.SuperFast); }),
new BeatAction.Action(beat + 6f, delegate { Serve(beat + 6f, RallySpeed.SuperFast); }),
new BeatAction.Action(beat + 8f, delegate { Serve(beat + 8f, RallySpeed.SuperFast); }),
new BeatAction.Action(beat + 10f, delegate { Serve(beat + 10f, RallySpeed.SuperFast); })
});
if (muteAudio) return;
2022-02-14 08:53:58 +00:00
MultiSound.Play(new MultiSound.Sound[]
{
new MultiSound.Sound("rhythmRally/Tonk", beat),
new MultiSound.Sound("rhythmRally/Tink", beat + 0.5f),
new MultiSound.Sound("rhythmRally/Tonk", beat + 1f),
new MultiSound.Sound("rhythmRally/Tink", beat + 1.5f),
new MultiSound.Sound("rhythmRally/Tonk", beat + 2f),
new MultiSound.Sound("rhythmRally/Tink", beat + 2.5f),
new MultiSound.Sound("rhythmRally/Tonk", beat + 3f),
new MultiSound.Sound("rhythmRally/Tink", beat + 3.5f)
});
}
}
2022-02-03 02:09:50 +00:00
}
}