Big Nail Carpenter Updates (#743)

* Made Nail half-speed, changed long nail from hold B to press B, touched up animations

- Made Nail Carpenter half-speed -- it shouldn't change much, just remember to double your tempo!

- Also changed the long nail input from hold and release to simply pressing B -- the old way gives less reaction time and is just less fun overall

- Also touched up the animations to make them snappier and improve gamefeel

* Cleaned up animations a bit further, changed 'fusuma' to 'shoji'

* Further animation improvements

Hit animations are now instant, and the hammer animation is about snappy and responsive as it should be

* Fixed touch controls

FIxed touch controls being a miss no matter what

* reimplemented touch mode whiffing

cuz i forgor

* fixed a sound effect playing at the wrong time

* spawning logic overhaul

also fixed issue where sweets would be hittable if a nail could be instead
cues at full speed for testing need to halve the speed of everything later

* remove stray sfx call

* add missing exclaim animations to the forced object types

* improve chunk based spawning

improve animator setup

* remove unused stuff

* new animations

girl randomly blinks
puddings bounce when passing the girl
nails pop out when missing

* fix the sweet break check

---------

Co-authored-by: minenice55 <star.elementa@gmail.com>
This commit is contained in:
wookywok
2024-03-15 14:18:21 -05:00
committed by GitHub
parent a99a6f0380
commit 2fab37113d
44 changed files with 4593 additions and 912 deletions

View File

@ -304,7 +304,7 @@ namespace HeavenStudio.Games
{
foreach (var evt in scheduledInputs)
{
evt.Disable();
evt.QueueDeletion();
}
}

View File

@ -1,16 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NaughtyBezierCurves;
using DG.Tweening;
using HeavenStudio.Util;
using HeavenStudio.InputSystem;
namespace HeavenStudio.Games.Scripts_NailCarpenter
{
public class LongNail : MonoBehaviour
{
public double targetBeat;
public float targetX;
public float metresPerSecond;
public Animator nailAnim;
private NailCarpenter game;
@ -18,49 +17,52 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
public void Init()
{
game = NailCarpenter.instance;
game.ScheduleInput(targetBeat, 0.5f, NailCarpenter.InputAction_AltFinish, HammmerJust, HammmerMiss, Empty);
game.ScheduleInput(targetBeat, 0, NailCarpenter.InputAction_AltPress, HammmerJust, HammmerMiss, null);
// wrongInput
game.ScheduleUserInput(targetBeat, 0.5f, NailCarpenter.InputAction_BasicPress, weakHammmerJust, Empty, Empty);
if (PlayerInput.CurrentControlStyle != InputController.ControlStyles.Touch)
{
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_RegPress, WeakHammmerJust, null, null);
}
Update();
}
private void HammmerJust(PlayerActionEvent caller, float state)
{
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
if (state >= 1f || state <= -1f)
{
nailAnim.DoScaledAnimationAsync(
(state >= 1f ? "longNailBendRight" : "longNailBendLeft"), 0.5f);
(state >= 1f ? "longNailBendRight" : "longNailBendLeft"), 0.25f);
SoundByte.PlayOneShot("miss");
return;
}
SoundByte.PlayOneShotGame("nailCarpenter/HammerStrong");
nailAnim.DoScaledAnimationAsync("longNailHammered", 0.5f);
game.EyeAnim.DoScaledAnimationAsync("eyeSmile", 0.5f);
nailAnim.DoScaledAnimationAsync("longNailHammered", 0.25f);
game.Carpenter.DoScaledAnimationAsync("eyeSmile", 0.25f, animLayer: 1);
}
private void weakHammmerJust(PlayerActionEvent caller, float state)
private void WeakHammmerJust(PlayerActionEvent caller, float state)
{
game.ScoreMiss();
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
if (state >= 1f || state <= -1f)
{
nailAnim.DoScaledAnimationAsync(
(state >= 1f ? "longNailBendRight" : "longNailBendLeft"), 0.5f);
(state >= 1f ? "longNailBendRight" : "longNailBendLeft"), 0.25f);
SoundByte.PlayOneShot("miss");
return;
}
SoundByte.PlayOneShotGame("nailCarpenter/HammerWeak");
nailAnim.DoScaledAnimationAsync("longNailWeakHammered", 0.5f);
nailAnim.DoScaledAnimationAsync("longNailWeakHammered", 0.25f);
}
private void HammmerMiss(PlayerActionEvent caller)
{
game.EyeAnim.DoScaledAnimationAsync("eyeBlink", 0.5f);
game.Carpenter.DoScaledAnimationAsync("eyeBlink", 0.25f, animLayer: 1);
nailAnim.DoScaledAnimationAsync("longNailMiss", 0.5f);
}
private void Empty(PlayerActionEvent caller) { }
private void Update()
{
var cond = Conductor.instance;
@ -68,7 +70,10 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
if (cond.isPlaying && !cond.isPaused)
{
double beat = cond.songPositionInBeats;
if (targetBeat != double.MinValue)
Vector3 pos = transform.position;
pos.x = targetX + (float)((beat - targetBeat) * metresPerSecond);
transform.position = pos;
if (targetBeat != double.MinValue)
{
if (beat >= targetBeat + 9) Destroy(gameObject);
}

View File

@ -1,16 +1,15 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NaughtyBezierCurves;
using DG.Tweening;
using HeavenStudio.Util;
using HeavenStudio.InputSystem;
namespace HeavenStudio.Games.Scripts_NailCarpenter
{
public class Nail : MonoBehaviour
{
public double targetBeat;
public float targetX;
public float metresPerSecond;
public Animator nailAnim;
private NailCarpenter game;
@ -18,46 +17,50 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
public void Init()
{
game = NailCarpenter.instance;
game.ScheduleInput(targetBeat, 0, NailCarpenter.InputAction_BasicPress, HammmerJust, HammmerMiss, Empty);
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_AltFinish, strongHammmerJust, Empty, Empty);
game.ScheduleInput(targetBeat, 0, NailCarpenter.InputAction_RegPress, HammmerJust, HammmerMiss, null);
//wrong input
if (PlayerInput.CurrentControlStyle != InputController.ControlStyles.Touch)
{
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_AltPress, StrongHammmerJust, null, null);
}
Update();
}
private void HammmerJust(PlayerActionEvent caller, float state)
{
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
if (state >= 1f || state <= -1f)
{
nailAnim.DoScaledAnimationAsync(
(state >= 1f ? "nailBendRight" : "nailBendLeft"), 0.5f);
(state >= 1f ? "nailBendRight" : "nailBendLeft"), 0.25f);
SoundByte.PlayOneShot("miss");
return;
}
SoundByte.PlayOneShotGame("nailCarpenter/HammerWeak");
nailAnim.DoScaledAnimationAsync("nailHammered", 0.5f);
nailAnim.DoScaledAnimationAsync("nailHammered", 0.25f);
}
private void strongHammmerJust(PlayerActionEvent caller, float state)
private void StrongHammmerJust(PlayerActionEvent caller, float state)
{
game.ScoreMiss();
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
if (state >= 1f || state <= -1f)
{
nailAnim.DoScaledAnimationAsync(
(state >= 1f ? "nailBendRight" : "nailBendLeft"), 0.5f);
(state >= 1f ? "nailBendRight" : "nailBendLeft"), 0.25f);
SoundByte.PlayOneShot("miss");
return;
}
SoundByte.PlayOneShotGame("nailCarpenter/HammerStrong");
nailAnim.DoScaledAnimationAsync("nailStrongHammered", 0.5f);
nailAnim.DoScaledAnimationAsync("nailStrongHammered", 0.25f);
}
private void HammmerMiss(PlayerActionEvent caller)
{
game.EyeAnim.DoScaledAnimationAsync("eyeBlink", 0.5f);
game.Carpenter.DoScaledAnimationAsync("eyeBlink", 0.25f, animLayer: 1);
nailAnim.DoScaledAnimationAsync("nailMiss", 0.5f);
}
private void Empty(PlayerActionEvent caller) { }
private void Update()
{
var cond = Conductor.instance;
@ -65,7 +68,10 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
if (cond.isPlaying && !cond.isPaused)
{
double beat = cond.songPositionInBeats;
if (targetBeat != double.MinValue)
Vector3 pos = transform.position;
pos.x = targetX + (float)((beat - targetBeat) * metresPerSecond);
transform.position = pos;
if (targetBeat != double.MinValue)
{
if (beat >= targetBeat + 9) Destroy(gameObject);
}

View File

@ -16,43 +16,40 @@ namespace HeavenStudio.Games.Loaders
{
new GameAction("puddingNail", "Pudding Nail")
{
function = delegate {NailCarpenter.instance.PlaySound();},
defaultLength = 4f,
defaultLength = 8f,
resizable = true
},
new GameAction("cherryNail", "Cherry Nail")
{
function = delegate {NailCarpenter.instance.PlaySound();},
defaultLength = 2f,
defaultLength = 4f,
resizable = true
},
new GameAction("cakeNail", "Cake Nail")
{
function = delegate {NailCarpenter.instance.PlaySound();},
defaultLength = 2f,
defaultLength = 4f,
resizable = true
},
new GameAction("cakeLongNail", "Cake Long Nail")
{
function = delegate {NailCarpenter.instance.PlaySound();},
defaultLength = 2f,
defaultLength = 4f,
resizable = true
},
new GameAction("slideFusuma", "Slide Fusuma")
new GameAction("slideFusuma", "Slide Shoji")
{
function = delegate {
var e = eventCaller.currentEntity;
NailCarpenter.instance.SlideFusuma(e.beat, e.length, e["fillRatio"], e["ease"], e["mute"]);
var e = eventCaller.currentEntity;
NailCarpenter.instance.SlideShoji(e.beat, e.length, e["fillRatio"], e["ease"], e["mute"]);
},
defaultLength = 0.5f,
defaultLength = 1f,
resizable = true,
parameters = new List<Param>()
{
new Param("fillRatio", new EntityTypes.Float(0f, 1f, 0.3f), "Ratio", "Set the ratio of closing the fusuma."),
new Param("fillRatio", new EntityTypes.Float(0f, 1f, 0.3f), "Ratio", "Set how much of the screen the shoji covers."),
new Param("ease", Util.EasingFunction.Ease.Linear, "Ease", "Set the easing of the action."),
new Param("mute", false, "Mute", "Toggle if the cue should be muted.")
}
},
},
new List<string>() { "pco", "normal" },
"pconail", "en",
@ -68,27 +65,69 @@ namespace HeavenStudio.Games
public class NailCarpenter : Minigame
{
const double PATTERN_SEEK_TIME = 8.0;
[Serializable]
public struct ObjectPatternItem
{
public double beat;
public ObjectType type;
}
public enum ObjectType
{
Nail,
LongNail,
Sweet,
ForceCherry,
ForcePudding,
ForceCherryPudding,
ForceShortCake,
ForceLayerCake,
None,
LongCharge
}
struct ScheduledPattern
{
public double beat;
public double length;
public PatternType type;
}
enum PatternType
{
Pudding,
Cherry,
Cake,
CakeLong,
None
}
[SerializeField] ObjectPatternItem[] puddingPattern;
[SerializeField] ObjectPatternItem[] cherryPattern;
[SerializeField] ObjectPatternItem[] cakePattern;
[SerializeField] ObjectPatternItem[] cakeLongPattern;
[SerializeField] float scrollMetresPerBeat = 4f;
[SerializeField] float boardWidth = 19.2f;
public GameObject baseNail;
public GameObject baseLongNail;
public GameObject baseSweet;
public Animator Carpenter;
public Animator EyeAnim;
public Animator EffectExclamRed;
public Animator EffectExclamBlue;
public Transform scrollingHolder;
public Transform nailHolder;
public Transform boardTrans;
public Transform fusumaTrans;
const float nailDistance = -8f;
const float boardWidth = 19.2f;
float scrollRate => nailDistance / (Conductor.instance.pitchedSecPerBeat * 2f);
public Transform shojiTrans;
private bool missed;
private bool hasSlurped;
const int IAAltDownCat = IAMAXCAT;
const int IAAltUpCat = IAMAXCAT + 1;
const int IASweetsCat = IAMAXCAT + 1;
protected static bool IA_PadAltPress(out double dt)
{
@ -98,30 +137,46 @@ namespace HeavenStudio.Games
{
return PlayerInput.GetSqueezeDown(out dt);
}
protected static bool IA_TouchRegularPress(out double dt)
{
return PlayerInput.GetTouchDown(InputController.ActionsTouch.Tap, out dt)
&& !instance.IsExpectingInputNow(InputAction_AltPress);
}
protected static bool IA_TouchAltPress(out double dt)
{
return PlayerInput.GetTouchDown(InputController.ActionsTouch.Tap, out dt)
&& instance.IsExpectingInputNow(InputAction_AltStart);
&& instance.IsExpectingInputNow(InputAction_AltPress)
&& !instance.IsExpectingInputNow(InputAction_RegPress);
}
protected static bool IA_PadAltRelease(out double dt)
protected static bool IA_PadSweetsCheck(out double dt)
{
return PlayerInput.GetPadUp(InputController.ActionsPad.South, out dt);
return (PlayerInput.GetPadDown(InputController.ActionsPad.South, out dt)
|| PlayerInput.GetPadDown(InputController.ActionsPad.East, out dt))
&& !(instance.IsExpectingInputNow(InputAction_RegPress) || instance.IsExpectingInputNow(InputAction_AltPress));
}
protected static bool IA_BatonAltRelease(out double dt)
protected static bool IA_BatonSweetsCheck(out double dt)
{
return PlayerInput.GetSqueezeUp(out dt);
return (PlayerInput.GetBatonDown(InputController.ActionsBaton.Face, out dt)
|| PlayerInput.GetSqueezeDown(out dt))
&& !(instance.IsExpectingInputNow(InputAction_RegPress) || instance.IsExpectingInputNow(InputAction_AltPress));
}
protected static bool IA_TouchSweetsCheck(out double dt)
{
return PlayerInput.GetTouchDown(InputController.ActionsTouch.Tap, out dt)
&& !(instance.IsExpectingInputNow(InputAction_RegPress) || instance.IsExpectingInputNow(InputAction_AltPress));
}
public static PlayerInput.InputAction InputAction_AltStart =
public static PlayerInput.InputAction InputAction_RegPress =
new("PcoNailRegStart", new int[] { IAPressCat, IAPressCat, IAPressCat },
IA_PadBasicPress, IA_TouchRegularPress, IA_BatonBasicPress);
public static PlayerInput.InputAction InputAction_AltPress =
new("PcoNailAltStart", new int[] { IAAltDownCat, IAAltDownCat, IAAltDownCat },
IA_PadAltPress, IA_TouchAltPress, IA_BatonAltPress);
public static PlayerInput.InputAction InputAction_AltFinish =
new("PcoNailAltFinish", new int[] { IAAltUpCat, IAFlickCat, IAAltUpCat },
IA_PadAltRelease, IA_TouchFlick, IA_BatonAltRelease);
public static PlayerInput.InputAction InputAction_TouchRelease =
new("PcoNailTouchRelease", new int[] { IAEmptyCat, IAReleaseCat, IAEmptyCat },
IA_Empty, IA_TouchBasicRelease, IA_Empty);
public static PlayerInput.InputAction InputAction_SweetsHit =
new("PcoNailSweetsHit", new int[] { IASweetsCat, IASweetsCat, IASweetsCat },
IA_PadSweetsCheck, IA_TouchSweetsCheck, IA_BatonSweetsCheck);
public static NailCarpenter instance;
@ -131,183 +186,102 @@ namespace HeavenStudio.Games
instance = this;
}
List<ScheduledPattern> scheduledPatterns = new List<ScheduledPattern>();
double patternStartBeat, gameStartBeat;
PatternType patternType, lastPatternType;
int patternIndex, lastPatternIndex = -1;
double slideBeat = double.MaxValue;
double slideLength;
double cachedPatternLengthPudding, cachedPatternLengthCherry, cachedPatternLengthCake, cachedPatternLengthCakeLong;
Util.EasingFunction.Ease slideEase;
float slideRatioLast = 0, slideRatioNext = 0;
void Start()
{
if (!conductor.isPlaying) return;
UpdatePatterns();
}
public override void OnBeatPulse(double beat)
{
if (!IsExpectingInputNow(InputAction_AltPress) && UnityEngine.Random.value < 0.1f)
{
Carpenter.Play("eyeBlinkFast", 1, 0);
}
}
void Update()
{
var cond = Conductor.instance;
var currentBeat = cond.songPositionInBeatsAsDouble;
var currentBeat = conductor.songPositionInBeatsAsDouble;
if (!cond.isPlaying) return;
if (!conductor.isPlaying) return;
// Debug.Log(newBeat);
if (PlayerInput.GetIsAction(InputAction_BasicPress) && !IsExpectingInputNow(InputAction_BasicPress))
if (PlayerInput.GetIsAction(InputAction_RegPress) && !IsExpectingInputNow(InputAction_RegPress))
{
ScoreMiss();
SoundByte.PlayOneShot("miss");
Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
hasSlurped = false;
// ScoreMiss();
}
if (PlayerInput.GetIsAction(InputAction_AltFinish) && !IsExpectingInputNow(InputAction_AltFinish))
if (PlayerInput.GetIsAction(InputAction_AltPress) && !IsExpectingInputNow(InputAction_AltPress))
{
ScoreMiss();
SoundByte.PlayOneShot("miss");
Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
Carpenter.DoScaledAnimationAsync("carpenterHit", 0.25f);
hasSlurped = false;
// ScoreMiss();
}
// Object scroll.
var scrollPos = scrollingHolder.localPosition;
var newScrollX = scrollPos.x + (scrollRate * Time.deltaTime);
scrollingHolder.localPosition = new Vector3(newScrollX, scrollPos.y, scrollPos.z);
// Board scroll.
var boardPos = boardTrans.localPosition;
var newBoardX = boardPos.x + (scrollRate * Time.deltaTime);
var newBoardX = currentBeat * scrollMetresPerBeat;
newBoardX %= boardWidth;
boardTrans.localPosition = new Vector3(newBoardX, boardPos.y, boardPos.z);
boardTrans.localPosition = new Vector3((float)newBoardX, boardPos.y, boardPos.z);
UpdateFusuma(currentBeat);
UpdatePatterns();
UpdateShoji(currentBeat);
}
public override void OnGameSwitch(double beat)
{
double startBeat;
cachedPatternLengthPudding = puddingPattern[^1].beat;
cachedPatternLengthCherry = cherryPattern[^1].beat;
cachedPatternLengthCake = cakePattern[^1].beat;
cachedPatternLengthCakeLong = cakeLongPattern[^1].beat;
double endBeat = double.MaxValue;
var entities = GameManager.instance.Beatmap.Entities;
var entities = gameManager.Beatmap.Entities;
startBeat = beat;
gameStartBeat = beat;
patternStartBeat = gameStartBeat;
// find out when the next game switch (or remix end) happens
RiqEntity firstEnd = entities.Find(c => (c.datamodel.StartsWith("gameManager/switchGame") || c.datamodel.Equals("gameManager/end")) && c.beat > startBeat);
endBeat = firstEnd?.beat ?? double.MaxValue;
RiqEntity firstEnd = entities.Find(c => (c.datamodel.StartsWith("gameManager/switchGame") || c.datamodel.Equals("gameManager/end")) && c.beat > gameStartBeat);
endBeat = firstEnd?.beat ?? endBeat;
// Nail events.
List<RiqEntity> pudNailEvents = entities.FindAll(v => v.datamodel == "nailCarpenter/puddingNail");
List<RiqEntity> chrNailEvents = entities.FindAll(v => v.datamodel == "nailCarpenter/cherryNail");
List<RiqEntity> cakeNailEvents = entities.FindAll(v => v.datamodel == "nailCarpenter/cakeNail");
List<RiqEntity> cklNailEvents = entities.FindAll(v => v.datamodel == "nailCarpenter/cakeLongNail");
var cherryTargetBeats = new List<double>(){};
// Spawn cake and nail.
for (int i = 0; i < cakeNailEvents.Count; i++) {
var nailBeat = cakeNailEvents[i].beat;
var nailLength = cakeNailEvents[i].length;
// Only consider nailgie events that aren't past the start point.
if (startBeat <= nailBeat + nailLength) {
int nailInEvent = Mathf.CeilToInt(nailLength + 1) / 2;
for (int b = 0; b < nailInEvent; b++)
List<RiqEntity> events = entities.FindAll(v => (v.datamodel is "nailCarpenter/puddingNail" or "nailCarpenter/cherryNail" or "nailCarpenter/cakeNail" or "nailCarpenter/cakeLongNail") && v.beat >= gameStartBeat && v.beat < endBeat);
scheduledPatterns.Clear();
patternIndex = 0;
foreach (var evt in events)
{
if (evt.length == 0) continue;
int patternDivisions = (int)Math.Ceiling(evt.length / PATTERN_SEEK_TIME);
PatternType patternType = evt.datamodel switch
{
"nailCarpenter/puddingNail" => PatternType.Pudding,
"nailCarpenter/cherryNail" => PatternType.Cherry,
"nailCarpenter/cakeNail" => PatternType.Cake,
"nailCarpenter/cakeLongNail" => PatternType.CakeLong,
_ => throw new NotImplementedException()
};
for (int i = 0; i < patternDivisions; i++)
{
var pattern = new ScheduledPattern
{
var targetNailBeat = nailBeat + (2f * b);
if (startBeat <= targetNailBeat && targetNailBeat < endBeat)
{
sounds.Add(new MultiSound.Sound("nailCarpenter/alarm", targetNailBeat));
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(targetNailBeat, delegate
{
EffectExclamRed.DoScaledAnimationAsync("exclamAppear", 0.5f);
})
});
SpawnSweet(targetNailBeat, startBeat,
(b==0 ? Sweet.sweetsType.ShortCake : Sweet.sweetsType.Cherry));
SpawnNail(targetNailBeat+0.5f, startBeat);
SpawnSweet(targetNailBeat+1.0f, startBeat, Sweet.sweetsType.Cherry);
SpawnNail(targetNailBeat+1.25f, startBeat);
SpawnNail(targetNailBeat+1.75f, startBeat);
}
}
cherryTargetBeats.Add(nailBeat + 2f * nailInEvent);
}
}
// Spawn pudding and nail.
for (int i = 0; i < pudNailEvents.Count; i++) {
var nailBeat = pudNailEvents[i].beat;
var nailLength = pudNailEvents[i].length;
// Only consider nailgie events that aren't past the start point.
if (startBeat <= nailBeat + nailLength) {
int nailInEvent = Mathf.CeilToInt(nailLength);
for (int b = 0; b < nailInEvent; b++)
{
var targetNailBeat = nailBeat + (1f * b);
if (startBeat <= targetNailBeat && targetNailBeat < endBeat)
{
sounds.Add(new MultiSound.Sound("nailCarpenter/one", targetNailBeat));
SpawnSweet(targetNailBeat, startBeat,
(IsInRange(cherryTargetBeats, targetNailBeat) ? Sweet.sweetsType.Cherry :
Sweet.sweetsType.Pudding));
SpawnNail(targetNailBeat+0.5f, startBeat);
}
}
}
}
// Spawn cherrypudding and nail.
for (int i = 0; i < chrNailEvents.Count; i++) {
var nailBeat = chrNailEvents[i].beat;
var nailLength = chrNailEvents[i].length;
// Only consider nailgie events that aren't past the start point.
if (startBeat <= nailBeat + nailLength) {
int nailInEvent = Mathf.CeilToInt(nailLength + 1) / 2;
for (int b = 0; b < nailInEvent; b++)
{
var targetNailBeat = nailBeat + (2f * b);
if (startBeat <= targetNailBeat && targetNailBeat < endBeat)
{
sounds.Add(new MultiSound.Sound("nailCarpenter/three", targetNailBeat));
SpawnSweet(targetNailBeat, startBeat,
(IsInRange(cherryTargetBeats, targetNailBeat) ? Sweet.sweetsType.Cherry :
Sweet.sweetsType.CherryPudding));
SpawnNail(targetNailBeat+0.5f, startBeat);
SpawnNail(targetNailBeat+1.0f, startBeat);
SpawnNail(targetNailBeat+1.5f, startBeat);
}
}
}
}
// Spawn long nail.
for (int i = 0; i < cklNailEvents.Count; i++) {
var nailBeat = cklNailEvents[i].beat;
var nailLength = cklNailEvents[i].length;
// Only consider nailgie events that aren't past the start point.
if (startBeat <= nailBeat + nailLength) {
int nailInEvent = Mathf.CeilToInt(nailLength + 1) / 2;
for (int b = 0; b < nailInEvent; b++)
{
var targetNailBeat = nailBeat + (2f * b);
if (startBeat <= targetNailBeat && targetNailBeat < endBeat)
{
sounds.Add(new MultiSound.Sound("nailCarpenter/signal1", targetNailBeat));
sounds.Add(new MultiSound.Sound("nailCarpenter/signal2", targetNailBeat+1f));
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(targetNailBeat, delegate
{
EffectExclamBlue.DoScaledAnimationAsync("exclamAppear", 0.5f);
}),
new BeatAction.Action(targetNailBeat+1f, delegate
{
Carpenter.DoScaledAnimationAsync("carpenterArmUp", 0.5f);
}),
});
SpawnSweet(targetNailBeat, startBeat,
(IsInRange(cherryTargetBeats, targetNailBeat) ? Sweet.sweetsType.Cherry :
Sweet.sweetsType.LayerCake));
SpawnNail(targetNailBeat+0.5f, startBeat);
SpawnLongNail(targetNailBeat+1f, startBeat);
}
}
beat = evt.beat + (PATTERN_SEEK_TIME * i),
length = Math.Min(evt.length - (PATTERN_SEEK_TIME * i), PATTERN_SEEK_TIME),
type = patternType
};
scheduledPatterns.Add(pattern);
}
}
}
@ -317,26 +291,193 @@ namespace HeavenStudio.Games
OnGameSwitch(beat);
}
public void SlideFusuma(double beat, double length, float fillRatio, int ease, bool mute)
void UpdatePatterns()
{
if (!mute) MultiSound.Play(new MultiSound.Sound[]{ new MultiSound.Sound("nailCarpenter/open", beat)});
double beat = conductor.songPositionInBeatsAsDouble;
while (patternStartBeat < beat + PATTERN_SEEK_TIME)
{
if (patternIndex < scheduledPatterns.Count)
{
var pattern = scheduledPatterns[patternIndex];
if (pattern.type == PatternType.None)
{
patternIndex++;
continue;
}
if (pattern.beat + pattern.length < patternStartBeat)
{
patternIndex++;
continue;
}
SpawnPattern(pattern.beat, pattern.length, pattern.type);
patternStartBeat = pattern.beat + pattern.length;
lastPatternIndex = patternIndex;
patternIndex++;
}
else
{
break;
}
}
}
public void SlideShoji(double beat, double length, float fillRatio, int ease, bool mute)
{
if (!mute) SoundByte.PlayOneShotGame("nailCarpenter/open", beat, forcePlay: true);
slideBeat = beat;
slideLength = length;
slideEase = (Util.EasingFunction.Ease)ease;
slideRatioLast = slideRatioNext;
slideRatioNext = fillRatio;
}
void UpdateFusuma(double beat)
void UpdateShoji(double beat)
{
if (beat >= slideBeat)
{
float slideLast = 17.8f *(1-slideRatioLast);
float slideNext = 17.8f *(1-slideRatioNext);
float slideLast = 17.8f * (1 - slideRatioLast);
float slideNext = 17.8f * (1 - slideRatioNext);
Util.EasingFunction.Function func = Util.EasingFunction.GetEasingFunction(slideEase);
float slideProg = Conductor.instance.GetPositionFromBeat(slideBeat, slideLength, true);
slideProg = Mathf.Clamp01(slideProg);
float slide = func(slideLast, slideNext, slideProg);
fusumaTrans.localPosition = new Vector3(slide, 0, 0);
shojiTrans.localPosition = new Vector3(slide, 0, 0);
}
}
private void SpawnPattern(double beat, double length, PatternType pattern)
{
if (pattern == PatternType.None) return;
double patternLength = pattern switch
{
PatternType.Pudding => cachedPatternLengthPudding,
PatternType.Cherry => cachedPatternLengthCherry,
PatternType.Cake => cachedPatternLengthCake,
PatternType.CakeLong => cachedPatternLengthCakeLong,
_ => throw new NotImplementedException()
};
patternType = pattern;
int patternIterations = (int)Math.Ceiling(length / patternLength);
for (int i = 0; i < patternIterations; i++)
{
SpawnPatternSegment(beat + (patternLength * i), gameStartBeat, pattern switch
{
PatternType.Pudding => puddingPattern,
PatternType.Cherry => cherryPattern,
PatternType.Cake => cakePattern,
PatternType.CakeLong => cakeLongPattern,
_ => throw new NotImplementedException()
});
lastPatternType = patternType;
}
}
private void SpawnPatternSegment(double beat, double startbeat, ObjectPatternItem[] pattern)
{
foreach (var item in pattern)
{
double itemBeat = beat + item.beat;
switch (item.type)
{
case ObjectType.LongCharge:
SoundByte.PlayOneShotGame("nailCarpenter/signal2", itemBeat, forcePlay: true);
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(itemBeat, delegate
{
Carpenter.DoScaledAnimationAsync("carpenterArmUp", 0.25f);
}),
});
break;
case ObjectType.Nail:
SpawnNail(itemBeat, startbeat);
break;
case ObjectType.LongNail:
SpawnLongNail(itemBeat, startbeat);
break;
case ObjectType.Sweet:
// dynamically determine sweet based on pattern and last pattern
Sweet.sweetsType sweetType = Sweet.sweetsType.None;
switch (patternType)
{
case PatternType.Pudding:
SoundByte.PlayOneShotGame("nailCarpenter/one", itemBeat, forcePlay: true);
sweetType = Sweet.sweetsType.Pudding;
break;
case PatternType.Cherry:
SoundByte.PlayOneShotGame("nailCarpenter/three", itemBeat, forcePlay: true);
sweetType = Sweet.sweetsType.CherryPudding;
break;
case PatternType.Cake:
SoundByte.PlayOneShotGame("nailCarpenter/alarm", itemBeat, forcePlay: true);
sweetType = Sweet.sweetsType.ShortCake;
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(itemBeat, delegate
{
EffectExclamRed.DoScaledAnimationAsync("exclamAppear", 0.25f);
})
});
break;
case PatternType.CakeLong:
SoundByte.PlayOneShotGame("nailCarpenter/signal1", itemBeat, forcePlay: true);
sweetType = Sweet.sweetsType.LayerCake;
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(itemBeat, delegate
{
EffectExclamBlue.DoScaledAnimationAsync("exclamAppear", 0.25f);
}),
});
break;
default:
break;
}
if (lastPatternType == PatternType.Cake)
{
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.Cherry);
}
else if (sweetType != Sweet.sweetsType.None)
{
SpawnSweet(itemBeat, startbeat, sweetType);
}
break;
case ObjectType.ForceCherry:
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.Cherry);
break;
case ObjectType.ForcePudding:
SoundByte.PlayOneShotGame("nailCarpenter/one", itemBeat);
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.Pudding);
break;
case ObjectType.ForceCherryPudding:
SoundByte.PlayOneShotGame("nailCarpenter/three", itemBeat, forcePlay: true);
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.CherryPudding);
break;
case ObjectType.ForceShortCake:
SoundByte.PlayOneShotGame("nailCarpenter/alarm", itemBeat, forcePlay: true);
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(itemBeat, delegate
{
EffectExclamRed.DoScaledAnimationAsync("exclamAppear", 0.25f);
})
});
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.ShortCake);
break;
case ObjectType.ForceLayerCake:
SoundByte.PlayOneShotGame("nailCarpenter/signal1", itemBeat, forcePlay: true);
BeatAction.New(instance, new List<BeatAction.Action>()
{
new BeatAction.Action(itemBeat, delegate
{
EffectExclamBlue.DoScaledAnimationAsync("exclamAppear", 0.25f);
}),
});
SpawnSweet(itemBeat, startbeat, Sweet.sweetsType.LayerCake);
break;
default:
break;
}
}
}
@ -345,9 +486,9 @@ namespace HeavenStudio.Games
var newNail = Instantiate(baseNail, nailHolder).GetComponent<Nail>();
newNail.targetBeat = beat;
newNail.targetX = nailHolder.position.x;
newNail.metresPerSecond = scrollMetresPerBeat;
var nailX = (beat - startBeat) * -nailDistance / 2f;
newNail.transform.localPosition = new Vector3((float)nailX, 0f, 0f);
newNail.Init();
newNail.gameObject.SetActive(true);
}
@ -356,9 +497,9 @@ namespace HeavenStudio.Games
var newNail = Instantiate(baseLongNail, nailHolder).GetComponent<LongNail>();
newNail.targetBeat = beat;
newNail.targetX = nailHolder.position.x;
newNail.metresPerSecond = scrollMetresPerBeat;
var nailX = (beat - startBeat + 0.5f) * -nailDistance / 2f;
newNail.transform.localPosition = new Vector3((float)nailX, 0f, 0f);
newNail.Init();
newNail.gameObject.SetActive(true);
}
@ -368,37 +509,11 @@ namespace HeavenStudio.Games
newSweet.targetBeat = beat;
newSweet.sweetType = sweetType;
newSweet.targetX = nailHolder.position.x;
newSweet.metresPerSecond = scrollMetresPerBeat;
var sweetX = (beat - startBeat) * -nailDistance / 2f;
newSweet.transform.localPosition = new Vector3((float)sweetX, 0f, 0f);
newSweet.gameObject.SetActive(true);
newSweet.Init();
}
bool IsInRange(List<double> list, double num)
{
foreach (double item in list)
{
if (num >= item && num <= item + 0.25f)
{
return true;
}
}
return false;
}
// MultiSound.Play may not work in OnPlay (OnGameSwitch?), so I play the audio using an alternative method.
List<MultiSound.Sound> sounds = new List<MultiSound.Sound>(){};
bool isPlayed = false;
public void PlaySound()
{
if (isPlayed) return;
if (sounds.Count > 0) {
MultiSound.Play(sounds.ToArray());
isPlayed = true;
sounds = null;
}
}
}
}

View File

@ -1,8 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using NaughtyBezierCurves;
using DG.Tweening;
using HeavenStudio.Util;
@ -11,17 +8,21 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
public class Sweet : MonoBehaviour
{
public double targetBeat;
public float targetX;
public float metresPerSecond;
public sweetsType sweetType;
public Animator sweetAnim;
// public SpriteRenderer sweetSprite;
bool broken = false;
public enum sweetsType
{
Pudding=0,
CherryPudding=1,
ShortCake=2,
Cherry=3,
LayerCake=4,
None = -1,
Pudding = 0,
CherryPudding = 1,
ShortCake = 2,
Cherry = 3,
LayerCake = 4,
};
private NailCarpenter game;
@ -29,14 +30,30 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
public void Init()
{
game = NailCarpenter.instance;
broken = false;
AwakeAnim();
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_BasicPress, HammmerJust, Empty, Empty);
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_AltFinish, HammmerJust, Empty, Empty);
game.ScheduleUserInput(targetBeat, 0, NailCarpenter.InputAction_SweetsHit, HammmerJust, null, null);
Update();
if (sweetType == sweetsType.Pudding)
{
BeatAction.New(this, new List<BeatAction.Action>()
{
new BeatAction.Action(targetBeat, delegate
{
if (!broken)
{
sweetAnim.Play("puddingBeat", -1, 0);
}
})
});
}
}
private void AwakeAnim()
{
switch(sweetType)
switch (sweetType)
{
case sweetsType.Pudding:
sweetAnim.Play("puddingIdle", -1, 0);
@ -55,24 +72,26 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
break;
}
}
private void BreakAnim()
{
switch(sweetType)
broken = true;
switch (sweetType)
{
case sweetsType.Pudding:
sweetAnim.DoScaledAnimationAsync("puddingBreak", 0.5f);
sweetAnim.DoScaledAnimationAsync("puddingBreak", 0.25f);
break;
case sweetsType.CherryPudding:
sweetAnim.DoScaledAnimationAsync("cherryPuddingBreak", 0.5f);
sweetAnim.DoScaledAnimationAsync("cherryPuddingBreak", 0.25f);
break;
case sweetsType.ShortCake:
sweetAnim.DoScaledAnimationAsync("shortCakeBreak", 0.5f);
sweetAnim.DoScaledAnimationAsync("shortCakeBreak", 0.25f);
break;
case sweetsType.Cherry:
sweetAnim.DoScaledAnimationAsync("cherryBreak", 0.5f);
sweetAnim.DoScaledAnimationAsync("cherryBreak", 0.25f);
break;
case sweetsType.LayerCake:
sweetAnim.DoScaledAnimationAsync("layerCakeBreak", 0.5f);
sweetAnim.DoScaledAnimationAsync("layerCakeBreak", 0.25f);
break;
}
}
@ -81,13 +100,9 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
{
game.ScoreMiss();
BreakAnim();
game.Carpenter.DoScaledAnimationAsync("carpenterHit", 0.5f);
SoundByte.PlayOneShot("miss");
game.EyeAnim.DoScaledAnimationAsync("eyeBlink", 0.5f);
game.Carpenter.DoScaledAnimationAsync("eyeBlink", 0.25f, animLayer: 1);
}
private void Empty(PlayerActionEvent caller) { }
private void Update()
{
var cond = Conductor.instance;
@ -95,7 +110,10 @@ namespace HeavenStudio.Games.Scripts_NailCarpenter
if (cond.isPlaying && !cond.isPaused)
{
double beat = cond.songPositionInBeats;
if (targetBeat != double.MinValue)
Vector3 pos = transform.position;
pos.x = targetX + (float)((beat - targetBeat) * metresPerSecond);
transform.position = pos;
if (targetBeat != double.MinValue)
{
if (beat >= targetBeat + 9) Destroy(gameObject);
}