Rockers + Rhythm Tweezers Call and Response API (#444)

* rock hers

* Rockers is a real game and also color maps have been added

* little more set up

* anims and mor sprites

* First version of CallAndResponseHandler added

* You can mute now wow

* Got some stuff working

* anim city

* Fixed Inputs

* Visual goodies

* Changed how some events work

* Rockers is now stack proof

* Fixed a bug

* Bend early stages

* bendbendbendbendbendover

* bend fully implemented

* Removed "noise"

* pain

* Many animation

* Bend anims implemented

* strum effect implemented

* Made bends work way better

* dfgdfsgsdffsd

* Implemented strumstart and countin

* hi

* Made strumstart transition into strumidle

* Implemented samples

* All of the btsds samples are in now too

* many anim2

* A buggy version of the custom together system has been implemented

* Ok now it's unbuggified

* fixed a small thing

* lightning eff

* anim fixes

* oops

* you can now mute voiceline and also put in a standalone voiceline block

* Tweaks to dropdowns

* more tiny anim thing

* more animation stuff

* Bug fixes

* implemented mute and gotomiddle sliders for custom together event

* Default cmon and last one added

* You can chain last ones and cmons now

* Applause sounds added

* Fixed some bugs

* Made it so you can disable camera movement

* fixed an inconsistency

* Rhythm tweezers is actually kinda playable now, not finished though, i need to make beat offset work with this

* Rhythm tweezers now works between game switches

* Beat offset eradication

* Made eye size work properly

* Camera quad ease rather than quint

* Inactive intervals added

* Rockers works inactively too now

* Bug fix

* No peeking! No way!

* Alt smile added for tweezers

* early and late riff

* jj miss anim

* icon and miss

* Long hair works properly now

* Miss anims implemented for rockers

---------

Co-authored-by: Rapandrasmus <78219215+Rapandrasmus@users.noreply.github.com>
Co-authored-by: minenice55 <star.elementa@gmail.com>
This commit is contained in:
ev
2023-05-29 16:09:34 -04:00
committed by GitHub
parent 9caa3ecd29
commit 4d2bb8c782
277 changed files with 98607 additions and 195 deletions

View File

@ -0,0 +1,174 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Starpelly;
namespace HeavenStudio.Games
{
public class CallAndResponseHandler
{
public struct CallAndResponseEventParam
{
public string propertyName;
public dynamic value;
public CallAndResponseEventParam(string propertyName, dynamic value)
{
this.propertyName = propertyName;
this.value = value;
}
}
public class CallAndResponseEvent
{
public float beat;
public float length;
public float relativeBeat; // this beat is relative to the intervalStartBeat
public Dictionary<string, dynamic> DynamicData; //if you need more properties for your queued event
public string tag;
public CallAndResponseEvent(float beat, float relativeBeat, string tag, float length = 0)
{
this.beat = beat;
this.length = length;
this.relativeBeat = relativeBeat;
DynamicData = new Dictionary<string, dynamic>();
this.tag = tag;
this.length = length;
}
public void CreateProperty(string name, dynamic defaultValue)
{
if (!DynamicData.ContainsKey(name))
{
DynamicData.Add(name, defaultValue);
}
}
public dynamic this[string propertyName]
{
get
{
if (DynamicData.ContainsKey(propertyName))
{
return DynamicData[propertyName];
}
else
{
Debug.LogWarning("This property does not exist on this callAndResponse event.");
return null;
}
}
set
{
if (DynamicData.ContainsKey(propertyName))
{
DynamicData[propertyName] = value;
}
else
{
Debug.LogError($"This callAndRespone event does not have a property named {propertyName}! Attempted to insert value of type {value.GetType()}");
}
}
}
}
public float intervalStartBeat = -1; // the first beat of the interval
public float intervalLength = -1; // the duration of the interval in beats
public float defaultIntervalLength; // when an event is queued and the interval has not started yet, it will use this as the interval length.
public CallAndResponseHandler(float defaultIntervalLength)
{
this.defaultIntervalLength = defaultIntervalLength;
}
public List<CallAndResponseEvent> queuedEvents = new List<CallAndResponseEvent>();
/// <summary>
/// Returns the normalized progress of the interval
/// </summary>
public float GetIntervalProgress(float lengthOffset = 0)
{
return Conductor.instance.GetPositionFromBeat(intervalStartBeat, intervalLength - lengthOffset);
}
public float GetIntervalProgressFromBeat(float beat, float lengthOffset = 0)
{
return Mathp.Normalize(beat, intervalStartBeat, intervalStartBeat + intervalLength - lengthOffset);
}
/// <summary>
/// Is the interval currently on-going?
/// </summary>
public bool IntervalIsActive()
{
float progress = GetIntervalProgress();
return progress >= 0 && progress <= 1;
}
/// <summary>
/// Starts the interval.
/// </summary>
/// <param name="beat">The interval start beat.</param>
/// <param name="length">The length of the interval.</param>
public void StartInterval(float beat, float length)
{
intervalStartBeat = beat;
intervalLength = length;
defaultIntervalLength = length;
}
/// <summary>
/// Adds an event to the queued events list.
/// </summary>
/// <param name="beat">The current beat.</param>
/// <param name="crParams">Extra properties to add to the event.</param>
/// <param name="ignoreInterval">If true, this function will not start a new interval if the interval isn't active.</param>
/// <param name="overrideInterval">If true, overrides the current interval.</param>
public void AddEvent(float beat, float length = 0, string tag = "", List<CallAndResponseEventParam> crParams = null, bool ignoreInterval = false, bool overrideInterval = false)
{
if ((!IntervalIsActive() && !ignoreInterval) || overrideInterval)
{
StartInterval(beat, defaultIntervalLength);
}
CallAndResponseEvent addedEvent = new CallAndResponseEvent(beat, beat - intervalStartBeat, tag, length);
if (crParams != null && crParams.Count > 0)
{
foreach (var param in crParams)
{
addedEvent.CreateProperty(param.propertyName, param.value);
}
}
queuedEvents.Add(addedEvent);
}
/// <summary>
/// Check if an event exists at beat.
/// </summary>
/// <param name="beat">The beat to check.</param>
public bool EventExistsAtBeat(float beat)
{
if (queuedEvents.Count == 0)
{
return false;
}
CallAndResponseEvent foundEvent = queuedEvents.Find(x => x.beat == beat);
return foundEvent != null;
}
/// <summary>
/// Check if an event exists at relativeBeat.
/// </summary>
/// <param name="beat">The beat to check.</param>
public bool EventExistsAtRelativetBeat(float relativeBeat)
{
if (queuedEvents.Count == 0)
{
return false;
}
CallAndResponseEvent foundEvent = queuedEvents.Find(x => x.relativeBeat == relativeBeat);
return foundEvent != null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4af79422cc731bc489888dd72a37e11a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -15,20 +15,15 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
private Tweezers tweezers;
private bool plucked;
PlayerActionEvent pluckEvent;
private void Awake()
{
game = RhythmTweezers.instance;
tweezers = game.Tweezers;
}
private void Start() {
pluckEvent = game.ScheduleInput(createBeat, game.tweezerBeatOffset + game.beatInterval, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, Just, Miss, Out);
}
private void Update()
public void StartInput(float beat, float length)
{
game.ScheduleInput(beat, length, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, Just, Miss, Out);
}
public void Ace()
@ -60,11 +55,5 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
}
private void Out(PlayerActionEvent caller) {}
void OnDestroy()
{
if (pluckEvent != null)
pluckEvent.Disable();
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using UnityEngine;
using HeavenStudio.Util;
using UnityEngine.UIElements;
namespace HeavenStudio.Games.Scripts_RhythmTweezers
{
@ -21,9 +22,9 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
private Sound pullSound;
PlayerActionEvent pluckEvent;
private float inputBeat;
PlayerActionEvent endEvent;
InputType endInput;
private void Awake()
{
@ -32,34 +33,31 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
tweezers = game.Tweezers;
}
private void Start() {
game.ScheduleInput(createBeat, game.tweezerBeatOffset + game.beatInterval, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, StartJust, StartMiss, Out);
public void StartInput(float beat, float length)
{
inputBeat = beat + length;
game.ScheduleInput(beat, length, InputType.STANDARD_DOWN | InputType.DIRECTION_DOWN, StartJust, StartMiss, Out);
}
private void Update()
{
if (pluckState == 1)
{
bool input = PlayerInput.PressedUp();
if (endInput == InputType.DIRECTION_UP) input = PlayerInput.GetAnyDirectionUp();
if (input && !game.IsExpectingInputNow(endInput))
{
endEvent.isEligible = false;
EndEarly();
return;
}
Vector3 tst = tweezers.tweezerSpriteTrans.position;
var hairDirection = new Vector3(tst.x + 0.173f, tst.y) - holder.transform.position;
holder.transform.rotation = Quaternion.FromToRotation(Vector3.down, hairDirection);
float normalizedBeat = Conductor.instance.GetPositionFromBeat(createBeat + game.tweezerBeatOffset + game.beatInterval, 0.5f);
float normalizedBeat = Conductor.instance.GetPositionFromBeat(inputBeat, 0.5f);
anim.Play("LoopPull", 0, normalizedBeat);
tweezers.anim.Play("Tweezers_LongPluck", 0, normalizedBeat);
if (!game.IsExpectingInputNow(InputType.STANDARD_UP | InputType.DIRECTION_DOWN_UP) && PlayerInput.PressedUp(true) && normalizedBeat < 1f)
{
EndEarly();
endEvent.Disable();
}
// Auto-release if holding at release time.
if (normalizedBeat >= 1f)
endEvent.Hit(0f, 1f);
if (normalizedBeat >= 1f && !game.IsExpectingInputNow(InputType.STANDARD_UP | InputType.DIRECTION_DOWN_UP))
EndAce();
}
loop.transform.localScale = Vector2.one / holder.transform.localScale;
@ -67,6 +65,7 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
public void EndAce()
{
if (pluckState != 1) return;
tweezers.LongPluck(true, this);
tweezers.hitOnFrame++;
@ -78,9 +77,9 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
public void EndEarly()
{
var normalized = Conductor.instance.GetPositionFromBeat(createBeat + game.tweezerBeatOffset + game.beatInterval, 0.5f);
var normalized = Conductor.instance.GetPositionFromBeat(inputBeat, 0.5f);
anim.Play("LoopPullReverse", 0, normalized);
tweezers.anim.Play("Idle", 0, 0);
tweezers.anim.Play("Tweezers_Idle", 0, 0);
if (pullSound != null)
pullSound.Stop();
@ -95,17 +94,9 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
pluckState = -1;
return;
}
if (PlayerInput.GetAnyDirectionDown())
{
endInput = InputType.DIRECTION_UP;
}
else
{
endInput = InputType.STANDARD_UP;
}
pullSound = Jukebox.PlayOneShotGame($"rhythmTweezers/longPull{UnityEngine.Random.Range(1, 5)}");
pluckState = 1;
endEvent = game.ScheduleInput(createBeat, game.tweezerBeatOffset + game.beatInterval + 0.5f, endInput, EndJust, Out, Out);
endEvent = game.ScheduleInput(inputBeat, 0.5f, InputType.STANDARD_UP | InputType.DIRECTION_DOWN_UP, EndJust, Out, Out);
}
private void StartMiss(PlayerActionEvent caller)
@ -123,13 +114,5 @@ namespace HeavenStudio.Games.Scripts_RhythmTweezers
}
EndAce();
}
void OnDestroy()
{
if (pluckEvent != null)
pluckEvent.Disable();
if (endEvent != null)
endEvent.Disable();
}
}
}

View File

@ -20,73 +20,104 @@ namespace HeavenStudio.Games.Loaders
function = delegate { RhythmTweezers.instance.SetIntervalStart(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); },
defaultLength = 4f,
resizable = true,
priority = 1
priority = 1,
inactiveFunction = delegate { RhythmTweezers.InactiveInterval(eventCaller.currentEntity.beat, eventCaller.currentEntity.length); }
},
new GameAction("short hair", "Short Hair")
{
inactiveFunction = delegate { RhythmTweezers.SpawnHairInactive(eventCaller.currentEntity.beat); },
function = delegate { RhythmTweezers.instance.SpawnHair(eventCaller.currentEntity.beat); },
defaultLength = 0.5f
},
new GameAction("long hair", "Curly Hair")
{
inactiveFunction = delegate { RhythmTweezers.SpawnLongHairInactive(eventCaller.currentEntity.beat); },
function = delegate { RhythmTweezers.instance.SpawnLongHair(eventCaller.currentEntity.beat); },
defaultLength = 0.5f
},
new GameAction("passTurn", "Pass Turn")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.PassTurn(e.beat, e.length); },
resizable = true,
preFunction = delegate { var e = eventCaller.currentEntity; RhythmTweezers.PrePassTurn(e.beat, e.length); }
},
new GameAction("next vegetable", "Swap Vegetable")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.NextVegetable(e.beat, e["type"], e["colorA"], e["colorB"]); },
function = delegate
{
var e = eventCaller.currentEntity;
if (!e["instant"]) RhythmTweezers.instance.NextVegetable(e.beat, e["type"], e["colorA"], e["colorB"]);
else RhythmTweezers.instance.ChangeVegetableImmediate(e["type"], e["colorA"], e["colorB"]);
},
defaultLength = 0.5f,
parameters = new List<Param>()
{
new Param("type", RhythmTweezers.VegetableType.Onion, "Type", "The vegetable to switch to"),
new Param("colorA", RhythmTweezers.defaultOnionColor, "Onion Color", "The color of the onion"),
new Param("colorB", RhythmTweezers.defaultPotatoColor, "Potato Color", "The color of the potato"),
new Param("instant", false, "Instant", "Instantly change vegetable?")
},
priority = 3
},
new GameAction("noPeek", "No Peeking Sign")
{
preFunction = delegate { var e = eventCaller.currentEntity; RhythmTweezers.PreNoPeeking(e.beat, e.length); },
defaultLength = 4f,
resizable = true
},
new GameAction("fade background color", "Background Fade")
{
function = delegate
{
var e = eventCaller.currentEntity;
if (e["instant"])
{
RhythmTweezers.instance.ChangeBackgroundColor(e["colorA"], 0f);
}
else
{
RhythmTweezers.instance.FadeBackgroundColor(e["colorA"], e["colorB"], e.length);
}
},
resizable = true,
parameters = new List<Param>()
{
new Param("colorA", Color.white, "Start Color", "The starting color in the fade"),
new Param("colorB", RhythmTweezers.defaultBgColor, "End Color", "The ending color in the fade"),
new Param("instant", false, "Instant", "Instantly change color to start color?")
}
},
new GameAction("altSmile", "Use Alt Smile")
{
function = delegate
{
RhythmTweezers.instance.VegetableAnimator.SetBool("UseAltSmile", !RhythmTweezers.instance.VegetableAnimator.GetBool("UseAltSmile"));
},
defaultLength = 0.5f
},
//backwards compatibility
new GameAction("change vegetable", "Change Vegetable (Instant)")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.ChangeVegetableImmediate(e["type"], e["colorA"], e["colorB"]); },
defaultLength = 0.5f,
parameters = new List<Param>()
{
new Param("type", RhythmTweezers.VegetableType.Onion, "Type", "The vegetable to switch to"),
new Param("colorA", RhythmTweezers.defaultOnionColor, "Onion Color", "The color of the onion"),
new Param("colorB", RhythmTweezers.defaultPotatoColor, "Potato Color", "The color of the potato")
},
priority = 3
},
new GameAction("change vegetable", "Change Vegetable (Instant)")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.ChangeVegetableImmediate(e["type"], e["colorA"], e["colorB"]); },
defaultLength = 0.5f,
parameters = new List<Param>()
{
new Param("type", RhythmTweezers.VegetableType.Onion, "Type", "The vegetable to switch to"),
new Param("colorA", RhythmTweezers.defaultOnionColor, "Onion Color", "The color of the onion"),
new Param("colorB", RhythmTweezers.defaultPotatoColor, "Potato Color", "The color of the potato")
}
},
new GameAction("set tweezer delay", "Offset Tweezer")
{
function = delegate { RhythmTweezers.instance.tweezerBeatOffset = eventCaller.currentEntity.length; },
resizable = true,
priority = 2
},
new GameAction("reset tweezer delay", "Reset Tweezer Offset")
{
function = delegate { RhythmTweezers.instance.tweezerBeatOffset = 0f; },
defaultLength = 0.5f,
priority = 2
hidden = true,
},
new GameAction("set background color", "Background Colour")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.ChangeBackgroundColor(e["colorA"], 0f); },
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.ChangeBackgroundColor(e["colorA"], 0f); },
defaultLength = 0.5f,
parameters = new List<Param>()
parameters = new List<Param>()
{
new Param("colorA", RhythmTweezers.defaultBgColor, "Background Color", "The background color to change to")
}
},
hidden = true
},
new GameAction("fade background color", "Background Fade")
{
function = delegate { var e = eventCaller.currentEntity; RhythmTweezers.instance.FadeBackgroundColor(e["colorA"], e["colorB"], e.length); },
resizable = true,
parameters = new List<Param>()
{
new Param("colorA", Color.white, "Start Color", "The starting color in the fade"),
new Param("colorB", RhythmTweezers.defaultBgColor, "End Color", "The ending color in the fade")
}
}
},
new List<string>() {"agb", "repeat"},
"agbhair", "en",
@ -108,6 +139,12 @@ namespace HeavenStudio.Games
Potato
}
private struct QueuedPeek
{
public float beat;
public float length;
}
[Header("References")]
public Transform VegetableHolder;
public SpriteRenderer Vegetable;
@ -118,16 +155,16 @@ namespace HeavenStudio.Games
public GameObject hairBase;
public GameObject longHairBase;
public GameObject pluckedHairBase;
[SerializeField] private Animator noPeeking;
public GameObject HairsHolder;
public GameObject DroppedHairsHolder;
[NonSerialized] public int hairsLeft = 0;
[Header("Variables")]
public float beatInterval = 4f;
float intervalStartBeat;
bool intervalStarted;
public float tweezerBeatOffset = 0f;
private float passTurnBeat;
private float passTurnEndBeat = 2;
private static List<QueuedPeek> queuedPeeks = new List<QueuedPeek>();
[Header("Sprites")]
public Sprite pluckedHairSprite;
@ -172,67 +209,172 @@ namespace HeavenStudio.Games
}
public static RhythmTweezers instance { get; set; }
private static CallAndResponseHandler crHandlerInstance;
private List<Hair> spawnedHairs = new List<Hair>();
private List<LongHair> spawnedLongs = new List<LongHair>();
private static List<float> passedTurns = new List<float>();
private float peekBeat = -1;
private bool peekRising;
private void Awake()
{
instance = this;
if (crHandlerInstance == null)
{
crHandlerInstance = new CallAndResponseHandler(4);
}
if (crHandlerInstance.queuedEvents.Count > 0)
{
foreach (var crEvent in crHandlerInstance.queuedEvents)
{
if (crEvent.tag == "Hair")
{
Hair hair = Instantiate(hairBase, HairsHolder.transform).GetComponent<Hair>();
spawnedHairs.Add(hair);
hair.gameObject.SetActive(true);
hair.GetComponent<Animator>().Play("SmallAppear", 0, 1);
float rot = -58f + 116 * Mathp.Normalize(crEvent.relativeBeat, 0, crHandlerInstance.intervalLength - 1);
hair.transform.eulerAngles = new Vector3(0, 0, rot);
hair.createBeat = crEvent.beat;
}
else if (crEvent.tag == "Long")
{
LongHair hair = Instantiate(longHairBase, HairsHolder.transform).GetComponent<LongHair>();
spawnedLongs.Add(hair);
hair.gameObject.SetActive(true);
hair.GetComponent<Animator>().Play("LongAppear", 0, 1);
float rot = -58f + 116 * Mathp.Normalize(crEvent.relativeBeat, 0, crHandlerInstance.intervalLength - 1);
hair.transform.eulerAngles = new Vector3(0, 0, rot);
hair.createBeat = crEvent.beat;
}
}
}
}
private void OnDestroy()
{
if (crHandlerInstance != null && !Conductor.instance.isPlaying)
{
crHandlerInstance = null;
}
}
public static void SpawnHairInactive(float beat)
{
if (crHandlerInstance == null)
{
crHandlerInstance = new CallAndResponseHandler(4);
}
if (crHandlerInstance.queuedEvents.Count > 0 && crHandlerInstance.queuedEvents.Find(x => x.beat == beat || (beat >= x.beat && beat <= x.beat + x.length)) != null) return;
crHandlerInstance.AddEvent(beat, 0, "Hair");
}
public static void SpawnLongHairInactive(float beat)
{
if (crHandlerInstance == null)
{
crHandlerInstance = new CallAndResponseHandler(4);
}
if (crHandlerInstance.queuedEvents.Count > 0 && crHandlerInstance.queuedEvents.Find(x => x.beat == beat || (beat >= x.beat && beat <= x.beat + x.length)) != null) return;
crHandlerInstance.AddEvent(beat, 0.5f, "Long");
}
public void SpawnHair(float beat)
{
if (crHandlerInstance.queuedEvents.Count > 0 && crHandlerInstance.queuedEvents.Find(x => x.beat == beat || (beat >= x.beat && beat <= x.beat + x.length)) != null) return;
// End transition early if the next hair is a lil early.
StopTransitionIfActive();
// If interval hasn't started, assume this is the first hair of the interval.
if (!intervalStarted)
{
SetIntervalStart(beat, beatInterval);
}
crHandlerInstance.AddEvent(beat, 0, "Hair");
Jukebox.PlayOneShotGame("rhythmTweezers/shortAppear", beat);
Hair hair = Instantiate(hairBase, HairsHolder.transform).GetComponent<Hair>();
spawnedHairs.Add(hair);
hair.gameObject.SetActive(true);
hair.GetComponent<Animator>().Play("SmallAppear", 0, 0);
float rot = -58f + 116 * Mathp.Normalize(beat, intervalStartBeat, intervalStartBeat + beatInterval - 1f);
float rot = -58f + 116 * crHandlerInstance.GetIntervalProgressFromBeat(beat, 1);
hair.transform.eulerAngles = new Vector3(0, 0, rot);
hair.createBeat = beat;
hairsLeft++;
}
public void SpawnLongHair(float beat)
{
if (crHandlerInstance.queuedEvents.Count > 0 && crHandlerInstance.queuedEvents.Find(x => x.beat == beat || (beat >= x.beat && beat <= x.beat + x.length)) != null) return;
StopTransitionIfActive();
if (!intervalStarted)
{
SetIntervalStart(beat, beatInterval);
}
crHandlerInstance.AddEvent(beat, 0.5f, "Long");
Jukebox.PlayOneShotGame("rhythmTweezers/longAppear", beat);
LongHair hair = Instantiate(longHairBase, HairsHolder.transform).GetComponent<LongHair>();
spawnedLongs.Add(hair);
hair.gameObject.SetActive(true);
hair.GetComponent<Animator>().Play("LongAppear", 0, 0);
float rot = -58f + 116 * Mathp.Normalize(beat, intervalStartBeat, intervalStartBeat + beatInterval - 1f);
float rot = -58f + 116 * crHandlerInstance.GetIntervalProgressFromBeat(beat, 1);
hair.transform.eulerAngles = new Vector3(0, 0, rot);
hair.createBeat = beat;
hairsLeft++;
}
public void SetIntervalStart(float beat, float interval = 4f)
{
// Don't do these things if the interval was already started.
if (!intervalStarted)
{
// End transition early if the interval starts a lil early.
StopTransitionIfActive();
hairsLeft = 0;
intervalStarted = true;
}
StopTransitionIfActive();
hairsLeft = 0;
eyeSize = 0;
crHandlerInstance.StartInterval(beat, interval);
}
intervalStartBeat = beat;
beatInterval = interval;
public static void InactiveInterval(float beat, float interval)
{
if (crHandlerInstance == null)
{
crHandlerInstance = new CallAndResponseHandler(4);
}
crHandlerInstance.StartInterval(beat, interval);
}
public void PassTurn(float beat, float length)
{
if (crHandlerInstance.queuedEvents.Count > 0)
{
hairsLeft = crHandlerInstance.queuedEvents.Count;
foreach (var crEvent in crHandlerInstance.queuedEvents)
{
if (crEvent.tag == "Hair")
{
Hair hairToInput = spawnedHairs.Find(x => x.createBeat == crEvent.beat);
hairToInput.StartInput(beat + length, crEvent.relativeBeat);
}
else if (crEvent.tag == "Long")
{
LongHair hairToInput = spawnedLongs.Find(x => x.createBeat == crEvent.beat);
hairToInput.StartInput(beat + length, crEvent.relativeBeat);
}
}
crHandlerInstance.queuedEvents.Clear();
}
}
public static void PrePassTurn(float beat, float length)
{
if (GameManager.instance.currentGame == "rhythmTweezers")
{
instance.SetPassTurnValues(beat + length);
}
else
{
passedTurns.Add(beat + length);
}
}
private void SetPassTurnValues(float startBeat)
{
if (crHandlerInstance.intervalLength <= 0) return;
passTurnBeat = startBeat - 1f;
passTurnEndBeat = startBeat + crHandlerInstance.intervalLength;
}
const float vegDupeOffset = 16.7f;
@ -261,7 +403,6 @@ namespace HeavenStudio.Games
ResetVegetable();
transitioning = false;
intervalStarted = false;
}).SetEase(Ease.InOutSine);
}
@ -302,13 +443,57 @@ namespace HeavenStudio.Games
ChangeBackgroundColor(end, beats);
}
public static void PreNoPeeking(float beat, float length)
{
if (GameManager.instance.currentGame == "rhythmTweezers")
{
instance.NoPeeking(beat, length);
}
else
{
queuedPeeks.Add(new QueuedPeek()
{
beat = beat,
length = length
});
}
}
public void NoPeeking(float beat, float length)
{
peekBeat = beat - 1f;
peekRising = true;
BeatAction.New(instance.gameObject, new List<BeatAction.Action>()
{
new BeatAction.Action(beat + length, delegate { peekBeat = beat + length; peekRising = false; })
});
}
private void Update()
{
if (!Conductor.instance.isPlaying && !Conductor.instance.isPaused && intervalStarted)
if (Conductor.instance.isPlaying && !Conductor.instance.isPaused)
{
StopTransitionIfActive();
ResetVegetable();
intervalStarted = false;
if (passedTurns.Count > 0)
{
foreach (var turn in passedTurns)
{
SetPassTurnValues(turn);
}
passedTurns.Clear();
}
if (queuedPeeks.Count > 0)
{
foreach (var peek in queuedPeeks)
{
NoPeeking(peek.beat, peek.length);
}
queuedPeeks.Clear();
}
float normalizedBeat = Conductor.instance.GetPositionFromBeat(peekBeat, 1);
if (normalizedBeat >= 0f && normalizedBeat <= 1f)
{
noPeeking.DoNormalizedAnimation(peekRising ? "NoPeekRise" : "NoPeekLower", normalizedBeat);
}
}
}
@ -317,12 +502,9 @@ namespace HeavenStudio.Games
// Set tweezer angle.
var tweezerAngle = -180f;
if (intervalStarted)
{
var tweezerTime = Conductor.instance.songPositionInBeats - beatInterval - tweezerBeatOffset;
var unclampedAngle = -58f + 116 * Mathp.Normalize(tweezerTime, intervalStartBeat, intervalStartBeat + beatInterval - 1f);
tweezerAngle = Mathf.Clamp(unclampedAngle, -180f, 180f);
}
var tweezerTime = Conductor.instance.songPositionInBeats;
var unclampedAngle = -58f + 116 * Mathp.Normalize(tweezerTime, passTurnBeat + 1f, passTurnEndBeat - 1f);
tweezerAngle = Mathf.Clamp(unclampedAngle, -180f, 180f);
Tweezers.transform.eulerAngles = new Vector3(0, 0, tweezerAngle);

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9946cfa50b9c13f4fa89f8d447238b13
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace HeavenStudio.Games.Scripts_Rockers
{
public class RockerBendInput : MonoBehaviour
{
private int pitch;
private Rockers game;
public void Init(int pitch, float beat, float length)
{
game = Rockers.instance;
this.pitch = pitch;
game.ScheduleInput(beat, length, InputType.DIRECTION_DOWN, Just, Miss, Empty);
}
private void Just(PlayerActionEvent caller, float state)
{
if (state >= 1f || state <= -1f)
{
game.Soshi.BendUp(pitch);
Destroy(gameObject);
return;
}
game.Soshi.BendUp(pitch);
Destroy(gameObject);
}
private void Miss(PlayerActionEvent caller)
{
game.JJ.Miss();
Destroy(gameObject);
}
private void Empty(PlayerActionEvent caller)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40bd44c5381eb69468a65caa371f3876
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8fb47fde533de754e9d8fa37bcd9dd63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,56 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace HeavenStudio.Games.Scripts_Rockers
{
public class RockersInput : MonoBehaviour
{
private List<int> pitches = new List<int>();
private bool gleeClub;
private Rockers.PremadeSamples sample;
private int sampleTones;
private bool jump;
private Rockers game;
public void Init(bool gleeClub, int[] pitches, float beat, float length, Rockers.PremadeSamples sample, int sampleTones, bool jump = false)
{
game = Rockers.instance;
this.gleeClub = gleeClub;
this.pitches = pitches.ToList();
this.sample = sample;
this.sampleTones = sampleTones;
this.jump = jump;
game.ScheduleInput(beat, length, InputType.STANDARD_UP, Just, Miss, Empty);
}
private void Just(PlayerActionEvent caller, float state)
{
if (state >= 1f || state <= -1f)
{
game.Soshi.StrumStrings(gleeClub, pitches.ToArray(), sample, sampleTones, false, jump, true);
Destroy(gameObject);
return;
}
game.Soshi.StrumStrings(gleeClub, pitches.ToArray(), sample, sampleTones, false, jump);
Destroy(gameObject);
}
private void Miss(PlayerActionEvent caller)
{
game.JJ.Miss();
Destroy(gameObject);
}
private void Empty(PlayerActionEvent caller)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 061139f39edbd4340814a1cc568dbb9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,291 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HeavenStudio.Util;
using System;
namespace HeavenStudio.Games.Scripts_Rockers
{
public class RockersRocker : MonoBehaviour
{
private Sound[] stringSounds = new Sound[6];
private Sound chordSound;
private Animator anim;
public int[] lastPitches = new int[6];
public int lastBendPitch;
[SerializeField] private GameObject strumEffect;
[SerializeField] private bool JJ;
[NonSerialized] public bool muted;
private bool strumming;
private bool bending;
[NonSerialized] public bool together;
[SerializeField] List<Sprite> bluSprites = new List<Sprite>();
[SerializeField] List<Sprite> yelSprites = new List<Sprite>();
[SerializeField] List<Sprite> normalSprites = new List<Sprite>();
[SerializeField] List<SpriteRenderer> lightningLefts = new List<SpriteRenderer>();
[SerializeField] List<SpriteRenderer> lightningRights = new List<SpriteRenderer>();
private void Awake()
{
anim = GetComponent<Animator>();
}
private void OnDestroy()
{
StopSounds();
}
private void StopSounds()
{
foreach (var sound in stringSounds)
{
if (sound != null)
{
sound.KillLoop(0);
}
}
if (chordSound != null)
{
chordSound.KillLoop(0);
}
}
public void PrepareTogether(bool forceMute = false)
{
together = true;
if ((PlayerInput.Pressing() && !JJ) || forceMute)
{
DoScaledAnimationAsync("ComeOnPrepare", 0.5f);
if (forceMute) Mute(true, true);
}
else
{
DoScaledAnimationAsync("ComeOnPrepareNoMute", 0.5f);
if (strumming) strumEffect.GetComponent<Animator>().Play("StrumRight", 0, 0);
}
}
public void Miss()
{
if (strumming) return;
DoScaledAnimationAsync(together ? "Miss" : "MissComeOn", 0.5f);
}
public void ReturnBack()
{
together = false;
if (JJ)
{
muted = false;
DoScaledAnimationAsync("Return", 0.5f);
}
else
{
if (strumming) strumEffect.GetComponent<Animator>().Play("StrumIdle", 0, 0);
if (PlayerInput.Pressing() || (GameManager.instance.autoplay && muted))
{
DoScaledAnimationAsync("Crouch", 0.5f);
}
else
{
muted = false;
DoScaledAnimationAsync("Return", 0.5f);
}
}
}
public void StrumStrings(bool gleeClub, int[] pitches, Rockers.PremadeSamples sample, int sampleTones, bool disableStrumEffect = false, bool jump = false, bool barely = false)
{
muted = false;
strumming = true;
StopSounds();
if (sample == Rockers.PremadeSamples.None)
{
lastPitches = pitches;
for (int i = 0; i < pitches.Length; i++)
{
if (pitches[i] == -1) continue;
float pitch = Jukebox.GetPitchFromSemiTones(pitches[i], true);
float volume = GetVolumeBasedOnAmountOfStrings(pitches.Length);
string soundName = "rockers/strings/" + (gleeClub ? "gleeClub/" : "normal/" + (i + 1));
Debug.Log("Pitch: " + pitch + " Volume: " + volume + " Name: " + soundName);
stringSounds[i] = Jukebox.PlayOneShotGame(soundName, -1, pitch, volume, true);
}
}
else
{
float pitch = Jukebox.GetPitchFromSemiTones(sampleTones, true);
string soundName = sample switch
{
Rockers.PremadeSamples.None => "",
Rockers.PremadeSamples.BendG5 => "rockers/BendG5",
Rockers.PremadeSamples.BendC6 => "rockers/BendC6",
Rockers.PremadeSamples.ChordA => "rockers/rocker/ChordA",
Rockers.PremadeSamples.ChordAsus4 => "rockers/rocker/ChordAsus4",
Rockers.PremadeSamples.ChordBm => "rockers/rocker/ChordBm",
Rockers.PremadeSamples.ChordCSharpm7 => "rockers/rocker/ChordC#m7",
Rockers.PremadeSamples.ChordDmaj7 => "rockers/rocker/ChordDmaj7",
Rockers.PremadeSamples.ChordDmaj9 => "rockers/rocker/ChordDmaj9",
Rockers.PremadeSamples.ChordFSharp5 => "rockers/rocker/ChordF#5",
Rockers.PremadeSamples.ChordG => "rockers/rocker/ChordG",
Rockers.PremadeSamples.ChordG5 => "rockers/rocker/ChordG5",
Rockers.PremadeSamples.ChordGdim7 => "rockers/rocker/ChordGdim7",
Rockers.PremadeSamples.ChordGm => "rockers/rocker/ChordGm",
Rockers.PremadeSamples.NoteASharp4 => "rockers/rocker/NoteA#4",
Rockers.PremadeSamples.NoteA5 => "rockers/rocker/NoteA5",
Rockers.PremadeSamples.PracticeChordD => "rockers/rocker/PracticeChordD",
Rockers.PremadeSamples.Remix6ChordA => "rockers/rocker/Remix6ChordA",
Rockers.PremadeSamples.Remix10ChordD => "rockers/rocker/Remix10ChordD",
Rockers.PremadeSamples.Remix10ChordFSharpm => "rockers/rocker/Remix10ChordF#m",
Rockers.PremadeSamples.DoremiChordA7 => "rockers/doremi/ChordA7",
Rockers.PremadeSamples.DoremiChordAm7 => "rockers/doremi/ChordAm7",
Rockers.PremadeSamples.DoremiChordC => "rockers/doremi/ChordC",
Rockers.PremadeSamples.DoremiChordC7 => "rockers/doremi/ChordC7",
Rockers.PremadeSamples.DoremiChordCadd9 => "rockers/doremi/ChordCadd9",
Rockers.PremadeSamples.DoremiChordDm => "rockers/doremi/ChordDm",
Rockers.PremadeSamples.DoremiChordDm7 => "rockers/doremi/ChordDm7",
Rockers.PremadeSamples.DoremiChordEm => "rockers/doremi/ChordEm",
Rockers.PremadeSamples.DoremiChordF => "rockers/doremi/ChordF",
Rockers.PremadeSamples.DoremiChordFadd9 => "rockers/doremi/ChordFadd9",
Rockers.PremadeSamples.DoremiChordFm => "rockers/doremi/ChordFm",
Rockers.PremadeSamples.DoremiChordG => "rockers/doremi/ChordG",
Rockers.PremadeSamples.DoremiChordG7 => "rockers/doremi/ChordG7",
Rockers.PremadeSamples.DoremiChordGm => "rockers/doremi/ChordGm",
Rockers.PremadeSamples.DoremiChordGsus4 => "rockers/doremi/ChordGsus4",
Rockers.PremadeSamples.DoremiNoteA2 => "rockers/doremi/NoteA2",
Rockers.PremadeSamples.DoremiNoteE2 => "rockers/doremi/NoteE2",
_ => throw new System.NotImplementedException(),
};
chordSound = Jukebox.PlayOneShotGame(soundName, -1, pitch, 1, true);
}
if (together)
{
DoScaledAnimationAsync(jump ? "Jump" : "ComeOnStrum", 0.5f);
if (disableStrumEffect) return;
strumEffect.SetActive(true);
bool strumLeft = JJ && jump;
strumEffect.GetComponent<Animator>().Play(strumLeft ? "StrumStartLeft" : "StrumStartRIght", 0, 0);
}
else
{
DoScaledAnimationAsync("Strum", 0.5f);
if (disableStrumEffect) return;
strumEffect.SetActive(true);
strumEffect.GetComponent<Animator>().Play("StrumStart", 0, 0);
}
if (!JJ)
{
if (barely)
{
bool useYel = UnityEngine.Random.Range(1, 3) == 1;
for (int i = 0; i < 3; i++)
{
if (lightningRights[i].gameObject.activeSelf) lightningRights[i].sprite = useYel ? yelSprites[i] : bluSprites[i];
if (lightningLefts[i].gameObject.activeSelf) lightningLefts[i].sprite = useYel ? yelSprites[i] : bluSprites[i];
}
}
else
{
for (int i = 0; i < 3; i++)
{
if (lightningRights[i].gameObject.activeSelf) lightningRights[i].sprite = normalSprites[i];
if (lightningLefts[i].gameObject.activeSelf) lightningLefts[i].sprite = normalSprites[i];
}
}
}
}
public void BendUp(int pitch)
{
if (bending || !strumming) return;
bending = true;
lastBendPitch = pitch;
if (chordSound != null)
{
chordSound.BendUp(0.05f, Jukebox.GetPitchFromSemiTones(Jukebox.GetSemitonesFromPitch(chordSound.pitch) + pitch, true));
}
else
{
for (int i = 0; i < stringSounds.Length; i++)
{
if (stringSounds[i] != null)
{
stringSounds[i].BendUp(0.05f, Jukebox.GetPitchFromSemiTones(Jukebox.GetSemitonesFromPitch(stringSounds[i].pitch) + pitch, true));
}
}
}
Jukebox.PlayOneShotGame("rockers/bendUp");
DoScaledAnimationAsync("Bend", 0.5f);
}
public void BendDown()
{
if (!bending) return;
bending = false;
foreach (var sound in stringSounds)
{
if (sound != null)
{
sound.BendDown(0.05f);
}
}
if (chordSound != null)
{
chordSound.BendDown(0.05f);
}
Jukebox.PlayOneShotGame("rockers/bendDown");
DoScaledAnimationAsync("Unbend", 0.5f);
}
private float GetVolumeBasedOnAmountOfStrings(int stringAmount)
{
switch (stringAmount)
{
default:
return 1;
case 3:
return 0.893f;
case 4:
return 0.75f;
case 5:
return 0.686f;
case 6:
return 0.62f;
}
}
public void Mute(bool soundExists = true, bool noAnim = false)
{
strumming = false;
strumEffect.SetActive(false);
bending = false;
StopSounds();
if (soundExists) Jukebox.PlayOneShotGame("rockers/mute");
if (!noAnim) DoScaledAnimationAsync(together ? "ComeOnMute" : "Crouch", 0.5f);
muted = true;
}
public void UnHold()
{
if (!muted) return;
muted = false;
if (!together) DoScaledAnimationAsync("UnCrouch", 0.5f);
}
private void DoScaledAnimationAsync(string name, float time)
{
anim.DoScaledAnimationAsync((JJ ? "JJ" : "") + name, time);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e879fe35102c7154aaed4934ab31bed1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -301,6 +301,14 @@ namespace HeavenStudio.Util
return Mathf.Pow(2f, (1f / 12f) * semiTones);
}
}
/// <summary>
/// Returns the semitones from a pitch. Does not work with pitches pitched to music.
/// </summary>
/// <param name="pitch">The pitch of the sound.</param>
public static int GetSemitonesFromPitch(float pitch)
{
return (int)(12f * Mathf.Log(pitch, 2));
}
/// <summary>
/// Gets a pitch multiplier from cents.

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Starpelly;
namespace HeavenStudio.Util
{
@ -8,6 +9,7 @@ namespace HeavenStudio.Util
{
public AudioClip clip;
public float pitch = 1;
public float bendedPitch = 1; //only used with rockers
public float volume = 1;
// For use with PlayOneShotScheduled
@ -136,12 +138,69 @@ namespace HeavenStudio.Util
audioSource.Stop();
}
public void Pause()
{
if (audioSource != null)
audioSource.Pause();
}
public void UnPause()
{
if (audioSource != null)
audioSource.UnPause();
}
public void Delete()
{
GameManager.instance.SoundObjects.Remove(gameObject);
Destroy(gameObject);
}
#region Bend
// All of these should only be used with rockers.
public void BendUp(float bendTime, float bendedPitch)
{
this.bendedPitch = bendedPitch;
StartCoroutine(BendUpLoop(bendTime));
}
public void BendDown(float bendTime)
{
StartCoroutine(BendDownLoop(bendTime));
}
IEnumerator BendUpLoop(float bendTime)
{
float startingPitch = audioSource.pitch;
float bendTimer = 0f;
while (bendTimer < bendTime)
{
bendTimer += Time.deltaTime;
float normalizedProgress = Mathp.Normalize(bendTimer, 0, bendTime);
float currentPitch = Mathf.Lerp(startingPitch, bendedPitch, normalizedProgress);
audioSource.pitch = Mathf.Min(currentPitch, bendedPitch);
yield return null;
}
}
IEnumerator BendDownLoop(float bendTime)
{
float bendTimer = 0f;
float startingPitch = pitch;
while (bendTimer < bendTime)
{
bendTimer += Time.deltaTime;
float normalizedProgress = Mathp.Normalize(bendTimer, 0, bendTime);
float currentPitch = Mathf.Lerp(startingPitch, bendedPitch, 1 - normalizedProgress);
audioSource.pitch = Mathf.Max(currentPitch, pitch);
yield return null;
}
}
#endregion
public void KillLoop(float fadeTime)
{
StartCoroutine(FadeLoop(fadeTime));