Skip to content

Instantly share code, notes, and snippets.

@michaelperce
Last active April 30, 2019 20:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelperce/c50bf0b97c91fc78a199198fb6af1dfa to your computer and use it in GitHub Desktop.
Save michaelperce/c50bf0b97c91fc78a199198fb6af1dfa to your computer and use it in GitHub Desktop.
These four scripts form the logic backbone behind the rhythm component of Masking The Murder
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioTest : MonoBehaviour {
public float bpm = 120;
//the current position of the song (in seconds)
[HideInInspector] public float songPosition;
//the current position of the song (in beats)
[HideInInspector] public float songPosInBeats;
//the duration of a beat
[HideInInspector] public float secPerBeat;
//how much time (in seconds) has passed since the song started
[HideInInspector] public float dsptimesong;
[HideInInspector] public float[] notes;
[HideInInspector] public KeyCode[] keyPresses;
[HideInInspector] public GameObject[] NoteDisplay;
[HideInInspector] public GameObject dancerToSpawnOver;
public float numBeatsToCross = 4;
public Sprite[] spritesToSpawn;
public AudioClip[] songs;
public GameObject measureNote;
//Initialization
void Start() {
//notes represents when the note's input should be measured
notes = new float[] { 4.0f, 5.0f, 6.0f, 7.0f };
keyPresses = new KeyCode[] { KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, KeyCode.Alpha4 };
secPerBeat = 60f / bpm;
dsptimesong = (float)AudioSettings.dspTime;
//If a dancer has already been chosen, Update NoteDisplay with the objects that hold ButtonNotes
if (dancerToSpawnOver != null) {
NoteDisplay = new GameObject[dancerToSpawnOver.transform.childCount];
for (int i = 0; i < NoteDisplay.Length; i++) {
NoteDisplay[i] = dancerToSpawnOver.transform.GetChild(i).gameObject;
}
//Create moves for it
SpawnNewMove(notes, keyPresses);
}
//Choose a random song and play it
GetComponent<AudioSource>().clip = songs[Random.Range(0, songs.Length)];
GetComponent<AudioSource>().Play();
}
// Update is called once per frame
void Update() {
//calculate the song position in seconds and beats
songPosition = (float)(AudioSettings.dspTime - dsptimesong);
songPosInBeats = songPosition / secPerBeat;
}
//Spawns the new dance moves
public void SpawnNewMove(float[] timeValues, KeyCode[] buttonPresses) {
if (timeValues.Length != buttonPresses.Length) {
throw new System.Exception("The length of timeValues and buttonPresses must be identical");
}
notes = timeValues;
keyPresses = buttonPresses;
//Creates the correct sprites and updates the ButtonNote with needed information
for (int i = 0; i < NoteDisplay.Length; i++) {
if (i < notes.Length) {
switch (keyPresses[i]) {
case KeyCode.Alpha1: NoteDisplay[i].GetComponent<SpriteRenderer>().sprite = spritesToSpawn[0]; break;
case KeyCode.Alpha2: NoteDisplay[i].GetComponent<SpriteRenderer>().sprite = spritesToSpawn[1]; break;
case KeyCode.Alpha3: NoteDisplay[i].GetComponent<SpriteRenderer>().sprite = spritesToSpawn[2]; break;
case KeyCode.Alpha4: NoteDisplay[i].GetComponent<SpriteRenderer>().sprite = spritesToSpawn[3]; break;
default: throw new System.Exception("You have provided a KeyCode that is not 1, 2, 3, or 4.");
}
ButtonNote CurrentButton = NoteDisplay[i].GetComponent<ButtonNote>();
CurrentButton.correctInput = keyPresses[i];
CurrentButton.conductor = this;
CurrentButton.beatOfThisNote = timeValues[i];
CurrentButton.ResetFade();
}
}
}
//Updates the Dancer and establishes
public bool AssignNewBeatDisplay(GameObject newBeatDisplay) {
dancerToSpawnOver = newBeatDisplay;
//Catch state if newBeatDisplay hasn't been established and disables the current BeatNotes
if (newBeatDisplay == null) {
for (int i = 0; i < NoteDisplay.Length; i++) {
NoteDisplay[i].GetComponent<ButtonNote>().currentColor[3] = 0;
}
return false;
}
//Update NoteDisplay with the objects that hold ButtonNotes
NoteDisplay = new GameObject[dancerToSpawnOver.transform.childCount];
for (int i = 0; i < NoteDisplay.Length; i++) {
NoteDisplay[i] = dancerToSpawnOver.transform.GetChild(i).gameObject;
}
SpawnNewMove(notes, keyPresses);
return true;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonNote : MonoBehaviour {
public KeyCode correctInput;
[HideInInspector] public AudioTest conductor;
[HideInInspector] public double beatOfThisNote;
[HideInInspector] public CircleNote tracker;
[HideInInspector] public Color defaultColor;
[HideInInspector] public Color currentColor;
double startGrowTime;
double startFadeTime;
double audioDelay;
double audioDelaySD;
Vector3 defaultSize;
Vector3 currentSize;
float defaultTransparency = 1f;
float currentTransparency = 0f;
SpriteRenderer currentSprite;
bool canChangeTransparency = true;
PlayerDanceLogic logicCenter;
//Initialization
void Start() {
//Find objects in the scene
conductor = FindObjectOfType<AudioTest>();
currentSprite = GetComponent<SpriteRenderer>();
logicCenter = FindObjectOfType<PlayerDanceLogic>();
//Set basic defaults
defaultSize = new Vector3(-1, 1, 1);
defaultColor = currentSprite.color;
currentColor = defaultColor;
audioDelay = logicCenter.audioDelay;
audioDelaySD = logicCenter.audioDelaySD;
if (beatOfThisNote == 0) {
currentColor[3] = 0;
}
}
// Update is called once per frame
void Update() {
//Pulse with the beat
if (currentSize != new Vector3(-.9f, .9f, .9f) && Mathf.Repeat(conductor.songPosInBeats, 1) <= .125f) {
currentSize = new Vector3(-.9f, .9f, .9f);
transform.localScale = Vector2.Lerp(
currentSize,
defaultSize,
(float)(conductor.songPosInBeats - audioDelay + .125f) / .125f);
}
//Check if the player provides input
if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Alpha4)) {
//Gets the position of the song in comparison to the correct timing
//Absolute value becuase no distinction is made between slightly late and slightly early
float measuredInputTime = Mathf.Abs((float)(conductor.songPosInBeats - beatOfThisNote - audioDelay));
//Pulse when pressing the correct key to provide player feedback
if (Input.GetKeyDown(correctInput)) {
currentSize = new Vector3(-1.1f, 1.1f, 1.1f);
transform.localScale = currentSize;
startGrowTime = conductor.songPosInBeats;
}
//Checks for WAY too early input and ignores it
if (tracker == null || measuredInputTime >= .6f) {
}
//Incorrect Input
else if (!Input.GetKeyDown(correctInput) && (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Alpha4))) {
InputResults(PlayerDanceLogic.HitType.MISSED);
}
else if (Input.GetKeyDown(correctInput)) {
//Perfect Result
if (measuredInputTime < audioDelaySD) {
InputResults(PlayerDanceLogic.HitType.PERFECT_HIT);
}
//Okay Result
else if (measuredInputTime < 2 * audioDelaySD) {
InputResults(PlayerDanceLogic.HitType.OKAY_HIT);
}
//Missed Result
else {
InputResults(PlayerDanceLogic.HitType.MISSED);
}
}
}
//When the player fails to provide input
if (currentColor[3] != 0 && conductor.songPosInBeats - beatOfThisNote - audioDelay > 2 * audioDelaySD) {
InputResults(PlayerDanceLogic.HitType.MISSED);
}
//Resize the button to original
if (currentSprite.color[3] != 0 && transform.localScale != defaultSize) {
transform.localScale = Vector2.Lerp(
currentSize,
defaultSize,
(float)(conductor.songPosInBeats - audioDelay - startGrowTime + .125f) / .125f);
}
//Makes it so inputs fade out over time
if (currentColor[3] == 0) {
currentSprite.color = Color.Lerp(defaultColor, currentColor,
(float)(conductor.songPosInBeats - audioDelay - startFadeTime) / 1f);
if ((float)(conductor.songPosInBeats - audioDelay - startFadeTime) / 1f >= 1) {
defaultColor = currentColor;
}
canChangeTransparency = false;
}
}
//Passes the result of player input to the Dance Manager and changes the sprite appropriately
public void InputResults(PlayerDanceLogic.HitType typeOfHit) {
//Remove the circle if it exists that indicates when to give input
if (tracker) {
Destroy(tracker.gameObject);
}
tracker = null;
logicCenter.notesOnScreen.RemoveAt(0);
logicCenter.boop.Play();
//Begin fading out
if (currentColor[3] == 1 && canChangeTransparency) {
currentColor[3] = 0f;
startFadeTime = conductor.songPosInBeats;
}
//Change the image to the correct sprite
currentSprite.sprite = logicCenter.feedBackResults[(int)typeOfHit - 1];
//Update the Dancing Manager of the result
logicCenter.successfulHit = true;
logicCenter.SuccessfulHit(typeOfHit);
//Update Metrics
conductor.gameObject.GetComponent<MetricsCollection>().AddToDanceResults(typeOfHit);
}
//Resets the fade out function
public void ResetFade() {
currentColor[3] = 1;
if (currentSprite != null && currentColor != null) {
currentSprite.color = currentColor;
}
defaultColor = currentColor;
canChangeTransparency = true;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CircleNote : MonoBehaviour {
[HideInInspector] public PlayerDanceLogic danceLogic;
[HideInInspector] public AudioTest controller;
[HideInInspector] public float beatOfThisNote;
[HideInInspector] public float audioDelay;
[HideInInspector] public float sizeBeforeKill = 0;
[HideInInspector] public bool givenInput = false;
float numBeatsToDeath;
bool hasCrossed = false;
//Initialization
void Start() {
numBeatsToDeath = controller.numBeatsToCross * (sizeBeforeKill - 1) / (1 - 3);
gameObject.transform.localRotation = Quaternion.identity;
}
// Update is called once per frame
void Update() {
//Shrinks to the edge of the target
if (!hasCrossed) {
transform.localScale = Vector2.Lerp(
new Vector3(3, 3, 3),
new Vector3(1, 1, 1),
(controller.songPosInBeats - audioDelay - beatOfThisNote + controller.numBeatsToCross) / controller.numBeatsToCross);
//When it reaches the edge of the target, update hasCrossed to true
if ((controller.songPosInBeats - audioDelay - beatOfThisNote + controller.numBeatsToCross) / controller.numBeatsToCross >= 1) {
hasCrossed = true;
}
}
//Shrinks past the edge of the target
else {
transform.localScale = Vector2.Lerp(
new Vector3(1, 1, 1),
new Vector3(sizeBeforeKill, sizeBeforeKill, sizeBeforeKill),
(controller.songPosInBeats - audioDelay - beatOfThisNote) / numBeatsToDeath);
//When done, destroy this object and remove references to itself
if ((controller.songPosInBeats - audioDelay - beatOfThisNote) / numBeatsToDeath >= .5f) {
danceLogic.notesOnScreen.RemoveAt(0);
Destroy(gameObject);
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerDanceLogic : MonoBehaviour {
[HideInInspector] public enum HitType { DEFAULT, PERFECT_HIT, OKAY_HIT, MISSED, SHOT_MISS }
[HideInInspector] public List<CircleNote> notesOnScreen = new List<CircleNote>();
[HideInInspector] public AudioSource boop;
[HideInInspector] public HitType hitType;
[HideInInspector] public int numCirclesCrossed;
[HideInInspector] public int numSequencesCompleted;
[HideInInspector] public bool successfulHit;
[HideInInspector] public bool showLabel;
[HideInInspector] public KeyCode nextCorrectKeyPress;
public float audioDelay = 0.23f;
public float audioDelaySD = 0.1f;
public Sprite[] feedBackResults;
public Texture2D[] shotResults;
public GameObject noteToSpawn;
KeyCode[] possibleKeyInputs;
StealthMeter sneakyLevel;
AudioTest songInformation;
int nextIndex = 0;
//Initialization
void Start() {
possibleKeyInputs = new KeyCode[] { KeyCode.Alpha1, KeyCode.Alpha2, KeyCode.Alpha3, KeyCode.Alpha4 };
songInformation = FindObjectOfType<AudioTest>();
sneakyLevel = FindObjectOfType<StealthMeter>();
boop = GetComponent<AudioSource>();
hitType = HitType.DEFAULT;
successfulHit = false;
showLabel = false;
numCirclesCrossed = 0;
numSequencesCompleted = 0;
}
// Update is called once per frame
void Update() {
showLabel = false;
//Checks to see if a CircleNote Needs to be spawned
if (songInformation.dancerToSpawnOver != null && nextIndex < songInformation.NoteDisplay.Length && nextIndex < songInformation.notes.Length &&
songInformation.notes[nextIndex] < songInformation.songPosInBeats + songInformation.numBeatsToCross) {
//instantiate the CircleNote
GameObject note = Instantiate(noteToSpawn, songInformation.NoteDisplay[nextIndex].transform.position, Quaternion.identity, songInformation.dancerToSpawnOver.transform);
//initializes the CircleNote fields
CircleNote noteAttributes = note.GetComponent<CircleNote>();
noteAttributes.danceLogic = gameObject.GetComponent<PlayerDanceLogic>();
noteAttributes.controller = songInformation;
noteAttributes.beatOfThisNote = songInformation.notes[nextIndex];
noteAttributes.audioDelay = audioDelay;
notesOnScreen.Add(noteAttributes);
note.transform.localRotation = Quaternion.identity;
songInformation.NoteDisplay[nextIndex].GetComponent<ButtonNote>().tracker = noteAttributes;
nextIndex++;
}
//At the end of each measure, randomly create the next sequence of ButtonNotes for the player
if (songInformation.songPosInBeats > songInformation.notes[songInformation.notes.Length - 1] + 1f && songInformation.dancerToSpawnOver != null) {
CreateNewMoves(0);
numSequencesCompleted++;
}
//Reset SuccessfulHit and showLabel
successfulHit = false;
showLabel = true;
//Reset the value of numCirclesCrossed
if (numCirclesCrossed >= songInformation.keyPresses.Length) {
numCirclesCrossed -= songInformation.keyPresses.Length;
showLabel = false;
}
}
//Passes the information of the result to the stealth meter
public float SuccessfulHit(PlayerDanceLogic.HitType typeOfHit) {
return sneakyLevel.SuccessfulHit(typeOfHit);
}
//Create a new set of information to create correct ButtonNotes
public bool CreateNewMoves(double startingBeatOfNewSet) {
//Randomly choose the correct inputs
KeyCode[] newMove = new KeyCode[4];
for (int i = 0; i < newMove.Length; i++) {
newMove[i] = possibleKeyInputs[Random.Range(0, possibleKeyInputs.Length)];
}
//Select the correct beat to have the input on
float[] newPos = new float[4];
for (int i = 0; i < newPos.Length; i++) {
//normalizes the input values to the next measure
newPos[i] = Mathf.Ceil(songInformation.songPosInBeats / 4) * 4 + i + 8;
}
//Create the Button Notes with these inputs and song positions
songInformation.SpawnNewMove(newPos, newMove);
nextIndex = 0;
//Clear any old extraneous data
for (int i = 0; i < notesOnScreen.Count; i++) {
if (notesOnScreen[i].gameObject != null) {
Destroy(notesOnScreen[i].gameObject);
}
}
notesOnScreen.Clear();
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment