HeavenStudioPlus/Assets/Scripts/Games/Minigame.cs
minenice55 52ecce8c3e
Gameplay: Timing windows use a "smart" BPM scaling system (#86)
* Game: Pajama Party

* Pajama Party: bg setup, Mako jump

* Pajama Party: mako jumping

* Pajama Party: prep landing anim references

* Pajama Party: several anims, improved 3d scene

* Pajama Party: bg cleanup

* Pajama Party: Mako sleeping anims

* Pajama Party: All Mako anims, add sounds to fs

* Pajama Party: prep monkey prefab

* Pajama Party: thrown pillow, prep sequences

* Pajama Party: make embarrassed catch not loop

whoops

* Pajama Party: sound adjust, prefab work

* Pajama Party: monkey spawning, basic jumping

* Pajama Party: jump sequence

no input detection or landing yet

* Pajama Party: anims override

* Pajama Party: jump cue functional

comes with improvements and bugfixes to PlayerActionEvent

* Pajama Party: sleep cue functional

* Pajama Party: some notes

* PlayerActionEvent: more bugfixes

* Pajama Party: fully functional throw cue

* Pajama Party: start animating sleep cue

* Pajama Party: feature-complete

* Pajama Party: unfuck layering

* Pajama Party: icon

also adds Fan Club's concept icon

* Pajama Party: cues while unloaded

* inverse-scale timing windows based on speed

* Fan Club: move to new input system

* Fan Club: allow forced animations during calls

* Crop Stomp: fix camera shake regression
2022-06-03 20:15:56 -07:00

211 lines
No EOL
7.1 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace HeavenStudio.Games
{
public class Minigame : MonoBehaviour
{
public static float earlyTime = 0.1f, perfectTime = 0.06f, lateTime = 0.06f, endTime = 0.1f;
public List<Minigame.Eligible> EligibleHits = new List<Minigame.Eligible>();
[System.Serializable]
public class Eligible
{
public GameObject gameObject;
public bool early;
public bool perfect;
public bool late;
public bool notPerfect() { return early || late; }
public bool eligible() { return early || perfect || late; }
public float createBeat;
}
public List<PlayerActionEvent> scheduledInputs = new List<PlayerActionEvent>();
/**
* Schedule an Input for a later time in the minigame. Executes the methods put in parameters
*
* float startBeat : When the scheduling started (In beats)
* float timer : How many beats later should the input be expected
* InputType inputType : The type of the input that's expected (Press, release, A, B, Directions..) (Check InputType class for a list)
* ActionEventCallbackState OnHit : Method to run if the Input has been Hit
* ActionEventCallback OnMiss : Method to run if the Input has been Missed
* ActionEventCallback OnBlank : Method to run whenever there's an Input while this is Scheduled (Shouldn't be used too much)
*/
public PlayerActionEvent ScheduleInput(float startBeat,
float timer,
InputType inputType,
PlayerActionEvent.ActionEventCallbackState OnHit,
PlayerActionEvent.ActionEventCallback OnMiss,
PlayerActionEvent.ActionEventCallback OnBlank)
{
GameObject evtObj = new GameObject("ActionEvent" + (startBeat+timer));
evtObj.AddComponent<PlayerActionEvent>();
PlayerActionEvent evt = evtObj.GetComponent<PlayerActionEvent>();
evt.startBeat = startBeat;
evt.timer = timer;
evt.inputType = inputType;
evt.OnHit = OnHit;
evt.OnMiss = OnMiss;
evt.OnBlank = OnBlank;
evt.OnDestroy = RemoveScheduledInput;
evt.canHit = true;
evt.enabled = true;
evt.transform.parent = this.transform.parent;
evtObj.SetActive(true);
scheduledInputs.Add(evt);
return evt;
}
public PlayerActionEvent ScheduleAutoplayInput(float startBeat,
float timer,
InputType inputType,
PlayerActionEvent.ActionEventCallbackState OnHit,
PlayerActionEvent.ActionEventCallback OnMiss,
PlayerActionEvent.ActionEventCallback OnBlank)
{
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputType, OnHit, OnMiss, OnBlank);
evt.autoplayOnly = true;
return evt;
}
public PlayerActionEvent ScheduleUserInput(float startBeat,
float timer,
InputType inputType,
PlayerActionEvent.ActionEventCallbackState OnHit,
PlayerActionEvent.ActionEventCallback OnMiss,
PlayerActionEvent.ActionEventCallback OnBlank)
{
PlayerActionEvent evt = ScheduleInput(startBeat, timer, inputType, OnHit, OnMiss, OnBlank);
evt.noAutoplay = true;
return evt;
}
//Clean up method used whenever a PlayerActionEvent has finished
public void RemoveScheduledInput(PlayerActionEvent evt)
{
scheduledInputs.Remove(evt);
}
//Get the scheduled input that should happen the **Soonest**
//Can return null if there's no scheduled inputs
public PlayerActionEvent GetClosestScheduledInput()
{
PlayerActionEvent closest = null;
foreach(PlayerActionEvent toCompare in scheduledInputs)
{
if(closest == null)
{
closest = toCompare;
} else
{
float t1 = closest.startBeat + closest.timer;
float t2 = toCompare.startBeat + toCompare.timer;
Debug.Log("t1=" + t1 + " -- t2=" + t2);
if (t2 < t1) closest = toCompare;
}
}
return closest;
}
//Hasn't been tested yet. *Should* work.
//Can be used to detect if the user is expected to input something now or not
//Useful for strict call and responses games like Tambourine
public bool IsExpectingInputNow()
{
PlayerActionEvent input = GetClosestScheduledInput();
if (input == null) return false;
return input.IsExpectingInputNow();
}
// now should fix the fast bpm problem
public static float EarlyTime()
{
return 1f - ScaleTimingMargin(earlyTime);
}
public static float PerfectTime()
{
return 1f - ScaleTimingMargin(perfectTime);
}
public static float LateTime()
{
return 1f + ScaleTimingMargin(lateTime);
}
public static float EndTime()
{
return 1f + ScaleTimingMargin(endTime);
}
//scales timing windows to the BPM in an ""intelligent"" manner
static float ScaleTimingMargin(float f)
{
float bpm = Conductor.instance.songBpm * Conductor.instance.musicSource.pitch;
float a = bpm / 120f;
float b = (Mathf.Log(a) + 2f) * 0.5f;
float r = Mathf.Lerp(a, b, 0.25f);
return r * f;
}
public int firstEnable = 0;
public virtual void OnGameSwitch(float beat)
{
//Below is a template that can be used for handling previous entities.
//section below is if you only want to look at entities that overlap the game switch
/*
List<Beatmap.Entity> prevEntities = GameManager.instance.Beatmap.entities.FindAll(c => c.beat <= beat && c.datamodel.Split(0) == [insert game name]);
foreach(Beatmap.Entity entity in prevEntities)
{
if(entity.beat + entity.length >= beat)
{
EventCaller.instance.CallEvent(entity, true);
}
}
*/
}
public virtual void OnTimeChange()
{
}
public int MultipleEventsAtOnce()
{
int sameTime = 0;
for (int i = 0; i < EligibleHits.Count; i++)
{
float createBeat = EligibleHits[i].createBeat;
if (EligibleHits.FindAll(c => c.createBeat == createBeat).Count > 0)
{
sameTime += 1;
}
}
if (sameTime == 0 && EligibleHits.Count > 0)
sameTime = 1;
return sameTime;
}
}
}