This commit is contained in:
SoulliesOfficial
2025-08-22 14:54:40 -04:00
parent 6aa498f6be
commit 70b2a43824
574 changed files with 173713 additions and 1884 deletions

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0b91bdc95925cee4eaec293d270c570c
guid: 486b800798d495647b597ab969133165
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame.Beatmap;
using UnityEngine;
namespace Ichni.RhythmGame
{
public partial class CameraFieldOfView : AnimationBase
{
public FlexibleFloat fieldOfView;
public GameCamera targetGameCamera;
public static CameraFieldOfView GenerateElement(string elementName, Guid id,
List<string> tags, bool isFirstGenerated, GameCamera gameCamera, FlexibleFloat fieldOfView)
{
CameraFieldOfView camFOV = Instantiate(GameManager.instance.basePrefabs.emptyObject)
.AddComponent<CameraFieldOfView>();
camFOV.Initialize(elementName, id, tags, isFirstGenerated, gameCamera);
camFOV.animatedObject = gameCamera;
camFOV.targetGameCamera = gameCamera;
camFOV.fieldOfView = fieldOfView;
camFOV.animationReturnType = FlexibleReturnType.Before;
return camFOV;
}
public override void SetDefaultSubmodules()
{
timeDurationSubmodule = new TimeDurationSubmodule(this);
}
protected override void UpdateAnimation(float songTime)
{
fieldOfView.UpdateFlexibleFloat(songTime);
if (fieldOfView.returnType == FlexibleReturnType.MiddleExecuting)
{
targetGameCamera.perspectiveAngle = fieldOfView.value;
targetGameCamera.gameCamera.fieldOfView = fieldOfView.value;
}
}
public override void ApplyTimeOffset(float offset)
{
base.ApplyTimeOffset(offset);
fieldOfView.animations.ForEach(anim => anim.ApplyTimeOffset(offset));
}
}
public partial class CameraFieldOfView
{
public override void SaveBM()
{
matchedBM = new CameraFieldOfView_BM(elementName, elementGuid, tags, targetGameCamera.matchedBM as GameCamera_BM, fieldOfView.ConvertToBM());
}
}
namespace Beatmap
{
public partial class CameraFieldOfView_BM : AnimationBase_BM
{
public FlexibleFloat_BM fieldOfView;
public CameraFieldOfView_BM()
{
}
public CameraFieldOfView_BM(string elementName, Guid elementGuid, List<string> tags,
GameElement_BM attachedElement, FlexibleFloat_BM fieldOfView)
: base(elementName, elementGuid, tags, attachedElement)
{
this.fieldOfView = fieldOfView;
}
public override void ExecuteBM()
{
matchedElement = CameraFieldOfView.GenerateElement(elementName, elementGuid, tags, false,
GetElement(attachedElementGuid) as GameCamera, fieldOfView.ConvertToGameType());
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f3060506b42e3df44962829ee40e95d9
guid: 8c20f8d0710209c43be21a05b1d89871
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -40,9 +40,16 @@ namespace Ichni.RhythmGame
positionY.UpdateFlexibleFloat(songTime);
positionZ.UpdateFlexibleFloat(songTime);
if ((positionX.returnType is FlexibleReturnType.MiddleExecuting || positionX.isSwitchingReturnType) ||
(positionY.returnType is FlexibleReturnType.MiddleExecuting || positionY.isSwitchingReturnType) ||
(positionZ.returnType is FlexibleReturnType.MiddleExecuting || positionZ.isSwitchingReturnType))
if (positionX.returnType is FlexibleReturnType.MiddleExecuting ||
positionY.returnType is FlexibleReturnType.MiddleExecuting ||
positionZ.returnType is FlexibleReturnType.MiddleExecuting)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentPosition = new Vector3(positionX.value, positionY.value, positionZ.value);
targetTransformSubmodule.positionOffset += currentPosition;
targetTransformSubmodule.positionDirtyMark = true;
}
else if (positionX.isSwitchingReturnType || positionY.isSwitchingReturnType || positionZ.isSwitchingReturnType)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentPosition = new Vector3(positionX.value, positionY.value, positionZ.value);

View File

@@ -43,9 +43,16 @@ namespace Ichni.RhythmGame
scaleY.UpdateFlexibleFloat(songTime);
scaleZ.UpdateFlexibleFloat(songTime);
if ((scaleX.returnType is FlexibleReturnType.MiddleExecuting || scaleX.isSwitchingReturnType) ||
(scaleY.returnType is FlexibleReturnType.MiddleExecuting || scaleY.isSwitchingReturnType) ||
(scaleZ.returnType is FlexibleReturnType.MiddleExecuting || scaleZ.isSwitchingReturnType))
if (scaleX.returnType is FlexibleReturnType.MiddleExecuting ||
scaleY.returnType is FlexibleReturnType.MiddleExecuting ||
scaleZ.returnType is FlexibleReturnType.MiddleExecuting)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentScale = new Vector3(scaleX.value, scaleY.value, scaleZ.value);
targetTransformSubmodule.scaleOffset += currentScale;
targetTransformSubmodule.scaleDirtyMark = true;
}
else if (scaleX.isSwitchingReturnType || scaleY.isSwitchingReturnType || scaleZ.isSwitchingReturnType)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentScale = new Vector3(scaleX.value, scaleY.value, scaleZ.value);
@@ -57,7 +64,7 @@ namespace Ichni.RhythmGame
animationReturnType = FlexibleReturnType.MiddleInterval;
}
}
public override void ApplyTimeOffset(float offset)
{
base.ApplyTimeOffset(offset);

View File

@@ -38,9 +38,16 @@ namespace Ichni.RhythmGame
eulerAngleY.UpdateFlexibleFloat(songTime);
eulerAngleZ.UpdateFlexibleFloat(songTime);
if ((eulerAngleX.returnType is FlexibleReturnType.MiddleExecuting || eulerAngleX.isSwitchingReturnType) ||
(eulerAngleY.returnType is FlexibleReturnType.MiddleExecuting || eulerAngleY.isSwitchingReturnType) ||
(eulerAngleZ.returnType is FlexibleReturnType.MiddleExecuting || eulerAngleZ.isSwitchingReturnType))
if (eulerAngleX.returnType is FlexibleReturnType.MiddleExecuting ||
eulerAngleY.returnType is FlexibleReturnType.MiddleExecuting ||
eulerAngleZ.returnType is FlexibleReturnType.MiddleExecuting)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentEulerAngles = new Vector3(eulerAngleX.value, eulerAngleY.value, eulerAngleZ.value);
targetTransformSubmodule.eulerAnglesOffset += currentEulerAngles;
targetTransformSubmodule.eulerAnglesDirtyMark = true;
}
else if (eulerAngleX.isSwitchingReturnType || eulerAngleY.isSwitchingReturnType || eulerAngleZ.isSwitchingReturnType)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentEulerAngles = new Vector3(eulerAngleX.value, eulerAngleY.value, eulerAngleZ.value);

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.RhythmGame.Beatmap;
using UnityEngine;
namespace Ichni.RhythmGame
@@ -84,6 +85,15 @@ namespace Ichni.RhythmGame
AnimatedFloat nowAnimatedFloat = GetAnimatedFloat(nowTime); //获取当前时间点对应的AnimatedFloat
if (nowAnimatedFloat != null) //如果能获取到,表明当前时间点存在动画
{
if (nowTime + Time.deltaTime >= nowAnimatedFloat.endTime)
{
value = nowAnimatedFloat.endValue;
returnType = FlexibleReturnType.After;
if (lastReturnType != returnType) isSwitchingReturnType = true;
lastReturnType = returnType;
return;
}
//获取songTime时间点时基于动画曲线的AnimatedFloat比例点->01
float nowPercent = AnimationCurveEvaluator.Evaluate(nowAnimatedFloat.animationCurveType,
(nowTime - nowAnimatedFloat.startTime) / nowAnimatedFloat.totalTime);

View File

@@ -46,8 +46,8 @@ namespace Ichni.RhythmGame
this.currentEmissionColor = Color.black;
this.currentEmissionIntensity = 0;
this.baseColorDirtyMark = false;
this.emissionColorDirtyMark = false;
this.baseColorDirtyMark = true;
this.emissionColorDirtyMark = true;
if (!HaveSameSubmodule)
{
@@ -68,8 +68,8 @@ namespace Ichni.RhythmGame
this.currentEmissionColor = originalEmissionColor;
this.currentEmissionIntensity = originalEmissionIntensity;
this.baseColorDirtyMark = false;
this.emissionColorDirtyMark = false;
this.baseColorDirtyMark = true;
this.emissionColorDirtyMark = true;
if (!HaveSameSubmodule)
{
@@ -116,13 +116,8 @@ namespace Ichni.RhythmGame
.AddTo(colorSubmodule.attachedGameElement);
}
public void UpdateColor()
public void UpdateColor(bool refreshAll = true)
{
if (!GameManager.instance.audioManager.isUpdating)
{
return;
}
bool willRefresh = false;
if (colorSubmodule.baseColorDirtyMark)
@@ -139,7 +134,7 @@ namespace Ichni.RhythmGame
willRefresh = true;
}
if (willRefresh)
if (willRefresh && refreshAll)
{
colorSubmodule.attachedGameElement.Refresh();
}

View File

@@ -89,19 +89,6 @@ namespace Ichni.RhythmGame
Default,
Note,
}
private static Dictionary<string, EffectBase> EffectCollection { get; } =
new Dictionary<string, EffectBase>()
{
{ "Bloom", new BloomEffect(1, 2, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "CameraShake", new CameraShakeEffect(1, 50, 1, 1, 1) },
{ "ChromaticAberration", new ChromaticAberrationEffect(1, 1, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "Vignette", new VignetteEffect(1, 1, 0.4f, Color.black, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "SetInteger", new SetIntegerEffect("New Variable", 0, false, 0, 1) },
{ "EnableControl", new EnableControlEffect(null, "New Variable", 0, false, "") },
{ "LowPassFilter", new LowPassFilterEffect(1, 10, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "HighPassFilter", new HighPassFilterEffect(1, 22000, CustomCurvePresets.Parabolic(1, 0, 1)) }
};
}
public interface IHaveEffectSubmodule
@@ -157,6 +144,8 @@ namespace Ichni.RhythmGame
public BaseElement_BM matchedBM { get; set; }
public GameElement attachedGameElement;
public virtual bool IsPost => false;
/// <summary>
/// 效果的持续时间如果为0则表示瞬间效果

View File

@@ -124,8 +124,8 @@ namespace Ichni.RhythmGame
remainingElementAmount.Value--;
float loadPercent = (float)(elementList.Count - remainingElementAmount.Value) / elementList.Count;
GameManager.instance.projectLoader.loadPercent = loadPercent;
GameManager.instance.gameLoadingCanvas.SetProgress(loadPercent * 100f);
GameManager.instance.projectLoader.realLoadPercent = loadPercent;
//GameManager.instance.gameLoadingCanvas.SetProgress(GameManager.instance.projectLoader.displayLoadPercent * 100f);
loadAmount++;
if (loadAmount >= loadMaximumAmountPerFrame)
@@ -147,8 +147,8 @@ namespace Ichni.RhythmGame
remainingElementAmount.Value--;
float loadPercent = (float)(elementList.Count - remainingElementAmount.Value) / elementList.Count;
GameManager.instance.projectLoader.loadPercent = loadPercent;
GameManager.instance.gameLoadingCanvas.SetProgress(loadPercent * 100f);
GameManager.instance.projectLoader.realLoadPercent = loadPercent;
//GameManager.instance.gameLoadingCanvas.SetProgress(GameManager.instance.projectLoader.displayLoadPercent * 100f);
loadAmount++;
if (loadAmount >= loadMaximumAmountPerFrame)
@@ -169,8 +169,6 @@ namespace Ichni.RhythmGame
GameManager.instance.noteManager.AllNotesRegistered();
Debug.Log("All elements loaded.");
GameManager.instance.gameLoadingCanvas.FadeOut();
GameManager.instance.audioManager.isLoading = false;
yield return null;
}
}

View File

@@ -23,7 +23,7 @@ namespace Ichni.RhythmGame
this.bpm = bpm;
this.delay = delay;
this.offset = offset;
GameManager.instance.audioManager.songPlayer.songTimeSegment = -delay; // 初始化时,歌曲时间为负
}

View File

@@ -88,7 +88,7 @@ namespace Ichni.RhythmGame
if (this is IHaveColorSubmodule colorSource)
{
colorSource.UpdateColor();
colorSource.UpdateColor(false);
}
}

View File

@@ -33,7 +33,18 @@ namespace Ichni.RhythmGame
public override void PreExecute()
{
offsetTweener = gameCameraTransform.DOBlendableLocalMoveBy(offsetValue, duration).SetEase(offsetCurve);
offsetTweener = gameCameraTransform.DOBlendableLocalMoveBy(offsetValue, duration).SetEase(offsetCurve).Play();
}
public override void Adjust()
{
}
public override void Disrupt()
{
offsetTweener?.Kill();
gameCameraTransform.DOLocalMove(Vector3.zero, 0.4f).Play();
}
public override EffectBase_BM ConvertToBM()

View File

@@ -31,7 +31,7 @@ namespace Ichni.RhythmGame
public override void PreExecute()
{
tiltTweener = gameCameraTransform.DOBlendableLocalRotateBy(tiltValue, duration, RotateMode.FastBeyond360).SetEase(tiltCurve);
tiltTweener = gameCameraTransform.DOBlendableLocalRotateBy(tiltValue, duration, RotateMode.FastBeyond360).SetEase(tiltCurve).Play();
}
public override void Adjust()
@@ -47,7 +47,7 @@ namespace Ichni.RhythmGame
public override void Disrupt()
{
tiltTweener?.Kill();
gameCameraTransform.DOLocalRotate(Vector3.zero, 0.4f);
gameCameraTransform.DOLocalRotate(Vector3.zero, 0.4f).Play();
}
}

View File

@@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using Ichni.RhythmGame.Beatmap;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Ichni.RhythmGame
{
public partial class ParticleEmitter : GameElement, IHaveParticles, IHaveTimeDurationSubmodule, IHaveTransformSubmodule, IHaveColorSubmodule
{
public ParticleSystem particle { get; set; }
private IHaveParticles particlesContainer => this;
public TimeDurationSubmodule timeDurationSubmodule { get; set; }
public TransformSubmodule transformSubmodule { get; set; }
public ColorSubmodule colorSubmodule { get; set; }
public bool haveBaseColor => true;
public bool haveEmissionColor => true;
private List<string> themeBundleList;
private List<string> materialNameList;
public string themeBundleName;
public string materialName;
public bool prewarm;
public float playTime;
public float stopTime;
public ParticleSystemSimulationSpace simulationSpace;
public float density;
public float lifeTime;
public float speed;
public float radius;
public bool isAutoOrient;
public Vector3 particleRotation;
public static ParticleEmitter GenerateElement(string elementName, Guid id, List<string> tags,
bool isFirstGenerated, GameElement parentElement, string themeBundleName, string materialName,
bool prewarm, float playTime, float stopTime, ParticleSystemSimulationSpace simulationSpace,
float density, float lifeTime, float speed, float radius,
bool isAutoOrient, Vector3 particleRotation)
{
ParticleEmitter particleEmitter = Instantiate(GameManager.instance.basePrefabs.particleEmitter, parentElement.transform)
.GetComponent<ParticleEmitter>();
particleEmitter.particle = particleEmitter.GetComponent<ParticleSystem>();
particleEmitter.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
particleEmitter.playTime = playTime;
particleEmitter.stopTime = stopTime;
particleEmitter.themeBundleList = ThemeBundleManager.instance.loadedThemeBundleList.ConvertAll(x => x.themeBundleName);
particleEmitter.materialNameList = new List<string>();
particleEmitter.themeBundleName = themeBundleName;
particleEmitter.materialName = materialName;
particleEmitter.particlesContainer.SetParticleMaterial(themeBundleName, materialName);
particleEmitter.SetParticleSettings(prewarm, simulationSpace, density, lifeTime, speed, radius, isAutoOrient, particleRotation, false);
return particleEmitter;
}
public void SetParticleSettings(bool prewarm, ParticleSystemSimulationSpace simulationSpace,
float density, float lifeTime, float speed, float radius, bool isAutoOrient, Vector3 particleRotation, bool mark)
{
//这个Mark没有任何作用只是为了让解释器把interface中的函数和这个函数区分开。否则会Stackoverflow。
this.prewarm = prewarm;
this.simulationSpace = simulationSpace;
this.density = density;
this.lifeTime = lifeTime;
this.speed = speed;
this.radius = radius;
this.isAutoOrient = isAutoOrient;
this.particleRotation = particleRotation;
(this as IHaveParticles).SetParticleSettings(prewarm, simulationSpace, density, lifeTime, speed, radius, isAutoOrient, particleRotation);
}
public override void SetDefaultSubmodules()
{
timeDurationSubmodule = new TimeDurationSubmodule(this);
transformSubmodule = new TransformSubmodule(this);
colorSubmodule = new ColorSubmodule(this, Color.white, true, Color.white, 0);
}
}
public partial class ParticleEmitter
{
private void Update()
{
float songTime = GameManager.instance.songTime;
if (playTime > songTime || stopTime < songTime)
{
particle.Stop();
}
else
{
if (!particle.isPlaying)
{
particle.Play();
}
}
}
public override void Refresh()
{
base.Refresh();
ParticleSystemRenderer particleSystemRenderer = particle.GetComponent<ParticleSystemRenderer>();
particleSystemRenderer.material.SetColor("_BaseColor", colorSubmodule.currentBaseColor);
if (colorSubmodule.emissionEnabled)
{
particleSystemRenderer.material.EnableKeyword("_EMISSION_ON");
particleSystemRenderer.material.SetColor("_EmissionColor", colorSubmodule.GetCurrentEmissionColor());
}
else
{
particleSystemRenderer.material.DisableKeyword("_EMISSION_ON");
}
}
}
public partial class ParticleEmitter
{
public override void SaveBM()
{
matchedBM = new ParticleEmitter_BM(elementName, elementGuid, tags, parentElement.matchedBM as GameElement_BM,
prewarm, playTime, stopTime, simulationSpace, density, lifeTime, speed, radius,
isAutoOrient, particleRotation, themeBundleName, materialName);
}
}
namespace Beatmap
{
public class ParticleEmitter_BM : GameElement_BM
{
public bool prewarm = false;
public float playTime = 0f;
public float stopTime = 1f;
public ParticleSystemSimulationSpace simulationSpace;
public float density = 10;
public float lifeTime = 5;
public float speed;
public float radius;
public bool isAutoOrient = true;
public Vector3 particleRotation = Vector3.zero;
public string materialThemeBundleName = string.Empty;
public string materialName = string.Empty;
public ParticleEmitter_BM()
{
}
public ParticleEmitter_BM(string elementName, Guid elementGuid, List<string> tags, GameElement_BM attachedElement,
bool prewarm, float playTime, float stopTime, ParticleSystemSimulationSpace simulationSpace,
float density, float lifeTime, float speed, float radius, bool isAutoOrient, Vector3 particleRotation,
string materialThemeBundleName, string materialName) :
base(elementName, elementGuid, tags, attachedElement)
{
this.prewarm = prewarm;
this.playTime = playTime;
this.stopTime = stopTime;
this.simulationSpace = simulationSpace;
this.density = density;
this.lifeTime = lifeTime;
this.speed = speed;
this.radius = radius;
this.isAutoOrient = isAutoOrient;
this.particleRotation = particleRotation;
this.materialThemeBundleName = materialThemeBundleName;
this.materialName = materialName;
}
public override void ExecuteBM()
{
matchedElement = ParticleEmitter.GenerateElement(elementName, elementGuid, tags, false,
GetElement(attachedElementGuid), materialThemeBundleName, materialName,
prewarm, playTime, stopTime, simulationSpace, density, lifeTime, speed, radius,
isAutoOrient, particleRotation);
}
}
}
public interface IHaveParticles
{
public ParticleSystem particle { get; set; }
public virtual void SetParticleMaterial(string themeBundleName, string materialName)
{
Material material = ThemeBundleManager.instance.GetObject<Material>(themeBundleName, materialName);
if (material == null)
{
material = ThemeBundleManager.instance.GetObject<Material>("basic", "Basic_Track_Default");
}
Renderer particleRenderer = particle.GetComponent<Renderer>();
particleRenderer.material = Object.Instantiate(material);
particleRenderer.InitializeShader();
}
public virtual void SetParticleSettings(bool prewarm, ParticleSystemSimulationSpace simulationSpace, float density, float lifeTime,
float speed, float radius, bool isAutoOrient, Vector3 particleRotation)
{
SetPrewarm(prewarm);
SetSimulationSpace(simulationSpace);
SetDensity(density);
SetLifeTime(lifeTime);
SetSpeed(speed);
SetRadius(radius);
SetAlignment(isAutoOrient, particleRotation);
}
public void SetPrewarm(bool prewarm)
{
var mainModule = particle.main;
mainModule.prewarm = prewarm;
}
public void SetSimulationSpace(ParticleSystemSimulationSpace simulationSpace)
{
var mainModule = particle.main;
mainModule.simulationSpace = simulationSpace;
}
public void SetDensity(float density)
{
var emission = particle.emission;
emission.rateOverTime = density;
}
public void SetLifeTime(float lifeTime)
{
var mainModule = particle.main;
mainModule.startLifetime = lifeTime;
}
public void SetSpeed(float speed)
{
var mainModule = particle.main;
mainModule.startSpeed = speed;
}
public void SetRadius(float radius)
{
var shapeModule = particle.shape;
shapeModule.radius = radius;
}
public void SetAlignment(bool isAutoOrient, Vector3 particleRotation = default)
{
ParticleSystemRenderer particleSystemRenderer = particle.GetComponent<ParticleSystemRenderer>();
var mainModule = particle.main;
if (isAutoOrient)
{
particleSystemRenderer.alignment = ParticleSystemRenderSpace.View;
mainModule.startRotation3D = false; // 禁用3D旋转
}
else
{
particleSystemRenderer.alignment = ParticleSystemRenderSpace.Local;
mainModule.startRotation3D = true; // 启用3D旋转
SetParticleRotation(particleRotation);
}
}
public void SetParticleRotation(Vector3 particleRotation)
{
Vector3 vector3Rotation = particleRotation * Mathf.Deg2Rad;
var mainModule = particle.main;
mainModule.startRotationX = vector3Rotation.x;
mainModule.startRotationY = vector3Rotation.y;
mainModule.startRotationZ = vector3Rotation.z;
}
}
}

View File

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

View File

@@ -43,7 +43,6 @@ namespace Ichni.RhythmGame
{
float x = Mathf.Lerp(Screen.width, bottomX, intensityCurve.Evaluate(effectProgressPercent));
float y = Mathf.Lerp(Screen.height, bottomY, intensityCurve.Evaluate(effectProgressPercent));
//Debug.Log(x + ", " + y);
GameManager.instance.postProcessingManager.SetPixelateStrength(x,y);
}
@@ -52,6 +51,8 @@ namespace Ichni.RhythmGame
{
GameManager.instance.postProcessingManager.SetPixelateStrength(Screen.width, Screen.height);
GameManager.instance.postProcessingManager.SetFeatureActive(false);
Debug.Log("PixelateEffect Adjusted");
}
public override EffectBase_BM ConvertToBM()

View File

@@ -26,7 +26,7 @@ namespace Ichni.RhythmGame
public override void UpdateJudge()
{
if (note.isFirstJudged) return;
if ((note is not Hold && note.isFirstJudged)||(note is Hold && note.isFinalJudged)) return;
Vector2 noteScreenPosition = note.noteScreenPosition;
RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent<RectTransform>();
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, noteScreenPosition, null, out Vector2 uiPosition))

View File

@@ -7,7 +7,7 @@ namespace Ichni.RhythmGame
{
public abstract class NotePerfectEffect : NoteEffectBase
{
public override bool IsPost => true;
}
namespace Beatmap

View File

@@ -140,7 +140,7 @@ namespace Ichni.RhythmGame
{
if (inputUnitSwipe.isGeneric)
{
Debug.Log($"输入方向 {inputUnitSwipe.swipeDirection} 是通用的,直接匹配成功。");
//Debug.Log($"输入方向 {inputUnitSwipe.swipeDirection} 是通用的,直接匹配成功。");
return true;
}

View File

@@ -79,7 +79,7 @@ namespace Ichni.RhythmGame
noteAudioSubmodule.PlayHoldStartAudio();
Debug.Log($"Hold Note Start Judge: {startJudgeType} at {triggerTime}");
//Debug.Log($"Hold Note Start Judge: {startJudgeType} at {triggerTime}");
}
public void ExecuteProcessJudge()
@@ -89,6 +89,19 @@ namespace Ichni.RhythmGame
public void ExecuteFinalJudge()
{
foreach (EffectBase effect in noteVisual.effectSubmodule.effectCollection["StartHold"])
{
if (effect.nowEffectState == EffectBase.EffectState.Middle)
{
effect.Adjust();
}
}
foreach (EffectBase effect in noteVisual.effectSubmodule.effectCollection["Holding"])
{
effect.Adjust();
}
float triggerTime = GameManager.instance.songTime;
postTimeDifference = holdEndTime - triggerTime;
@@ -105,7 +118,7 @@ namespace Ichni.RhythmGame
postJudgeType = NoteJudgeType.Bad;
}
Debug.Log($"Hold Note Final Judge: {postJudgeType} at {triggerTime} of difference {postTimeDifference}");
//Debug.Log($"Hold Note Final Judge: {postJudgeType} at {triggerTime} of difference {postTimeDifference}");
NoteJudgeType finalJudge = GetLowerType(preJudgeType, postJudgeType);
float finalTimeDifference = Mathf.Min(preTimeDifference, postTimeDifference);
@@ -168,6 +181,24 @@ namespace Ichni.RhythmGame
GameManager.instance.noteJudgeManager.checkingHoldList.Remove(this);
}
}
public override void SetPerfectPosition()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
holdingTime = holdEndTime - exactJudgeTime;
(noteVisual as INoteVisualHold)?.UpdateHoldInMovableTrack();
}
}
protected override void SlowOffsetAfterExactJudgeTime()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
holdingTime = GameManager.instance.songTime - exactJudgeTime;
(noteVisual as INoteVisualHold)?.UpdateHoldInMovableTrack();
}
}
}
public partial class Hold
@@ -270,6 +301,11 @@ namespace Ichni.RhythmGame
SetJudgeArea();
if (!isFirstJudged && exactJudgeTime < GameManager.instance.songTime)
{
SlowOffsetAfterExactJudgeTime();
}
foreach (EffectBase e in noteVisual.effectSubmodule.effectCollection["Generate"])
{
e.UpdateEffect(exactJudgeTime);
@@ -297,6 +333,12 @@ namespace Ichni.RhythmGame
{
isHolding = false;
isFinalJudged = true;
foreach (EffectBase e in noteVisual.effectSubmodule.effectCollection["StartHold"])
{
e.Disrupt();
}
ExecuteFinalJudge();
RemoveFromCheckingList();
}

View File

@@ -129,6 +129,11 @@ namespace Ichni.RhythmGame
e.UpdateEffect(exactJudgeTime);
}
if (!isFirstJudged && exactJudgeTime < GameManager.instance.songTime)
{
SlowOffsetAfterExactJudgeTime();
}
if (!isFirstJudged && GameManager.instance.songTime > exactJudgeTime + judgeIntervals.afterMiss)
{
Miss(exactJudgeTime + judgeIntervals.afterMiss);
@@ -177,7 +182,23 @@ namespace Ichni.RhythmGame
Observable.EveryUpdate().Subscribe(_ =>
{
noteVisual.effectSubmodule.effectCollection["GeneralJudge"].ForEach(e => e.UpdateEffect(triggerTime));
noteVisual.effectSubmodule.effectCollection["Perfect"].ForEach(e => e.UpdateEffect(triggerTime));
foreach (var e in noteVisual.effectSubmodule.effectCollection["Perfect"])
{
if (!e.IsPost)
{
e.UpdateEffect(triggerTime);
}
}
foreach (var e in noteVisual.effectSubmodule.effectCollection["Perfect"])
{
if (e.IsPost)
{
e.UpdateEffect(triggerTime);
}
}
noteVisual.effectSubmodule.effectCollection["AfterJudge"].ForEach(e => e.UpdateEffect(exactJudgeTime));
}).AddTo(gameObject);
@@ -328,7 +349,7 @@ namespace Ichni.RhythmGame
}
}
public void SetPerfectPosition()
public virtual void SetPerfectPosition()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
@@ -336,6 +357,30 @@ namespace Ichni.RhythmGame
trackPositioner.SetPercent(notePercent);
}
}
protected virtual void SlowOffsetAfterExactJudgeTime()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * 0.8f;
float notePercent = movable.GetTrackPercent(exactJudgeTime + slowedTime);
trackPositioner.SetPercent(notePercent);
}
}
/*public virtual void SlowOffsetAfterExactJudgeTime()
{
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleMovable movable)
{
float timeDifference = GameManager.instance.songTime - exactJudgeTime;
float percent = Mathf.Lerp(0f, 1f, timeDifference / (judgeIntervals.afterMiss + 0.2f));
float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * percent;
//float percent = Mathf.Lerp(0, 0.5f, timeDifference / judgeIntervals.afterMiss);
//float slowedTime = (GameManager.instance.songTime - exactJudgeTime) * 0.5f;
float notePercent = movable.GetTrackPercent(exactJudgeTime + slowedTime);
trackPositioner.SetPercent(notePercent);
}
}*/
}
public abstract partial class NoteBase

View File

@@ -8,11 +8,11 @@ using UnityEngine.Serialization;
namespace Ichni.RhythmGame
{
public partial class ParticleTracker : GameElement, IHaveColorSubmodule
public partial class ParticleTracker : GameElement, IHaveParticles, IHaveColorSubmodule
{
public Track track;
public ParticleController particleController;
public ParticleSystem particle;
public ParticleSystem particle { get; set; }
public ColorSubmodule colorSubmodule { get; set; }
public bool haveBaseColor => true;
public bool haveEmissionColor => true;
@@ -44,6 +44,7 @@ namespace Ichni.RhythmGame
{
ParticleTracker particleTracker = Instantiate(GameManager.instance.basePrefabs.particleTracker, track.transform)
.GetComponent<ParticleTracker>();
particleTracker.particle = particleTracker.GetComponent<ParticleSystem>();
particleTracker.Initialize(elementName, id, tags, isFirstGenerated, track);
particleTracker.track = track;
particleTracker.particleController.spline = track.trackPathSubmodule.path;
@@ -51,7 +52,7 @@ namespace Ichni.RhythmGame
particleTracker.stopTime = stopTime;
particleTracker.themeBundleName = themeBundleName;
particleTracker.materialName = materialName;
particleTracker.SetParticleMaterial(themeBundleName, materialName);
(particleTracker as IHaveParticles).SetParticleMaterial(themeBundleName, materialName);
particleTracker.SetParticleSettings(prewarm, is3D, width, extendDirection, density, lifeTime, isAutoOrient, particleRotation);
return particleTracker;
}
@@ -60,21 +61,9 @@ namespace Ichni.RhythmGame
{
colorSubmodule = new ColorSubmodule(this, Color.white, true, Color.white, 0);
}
public void SetParticleMaterial(string themeBundleName, string materialName)
{
Material material = ThemeBundleManager.instance.GetObject<Material>(themeBundleName, materialName) ??
GameManager.instance.basePrefabs.defaultParticleMaterial;
Renderer particleRenderer = particle.GetComponent<Renderer>();
particleRenderer.material = material;
particleRenderer.InitializeShader();
}
public void SetParticleSettings(bool prewarm,
bool is3D, float width, Vector3 extendDirection,
float density, float lifeTime,
bool isAutoOrient, Vector3 particleRotation)
public void SetParticleSettings(bool prewarm, bool is3D, float width, Vector3 extendDirection,
float density, float lifeTime, bool isAutoOrient, Vector3 particleRotation)
{
this.prewarm = prewarm;
this.is3D = is3D;
@@ -85,12 +74,9 @@ namespace Ichni.RhythmGame
this.prewarm = prewarm;
this.isAutoOrient = isAutoOrient;
this.particleRotation = particleRotation;
SetPrewarm();
(this as IHaveParticles).SetParticleSettings(prewarm, ParticleSystemSimulationSpace.Local, density,
lifeTime, 0, 1, isAutoOrient, particleRotation);
SetShape();
SetDensity();
SetLifeTime();
SetAlignment();
}
}
@@ -130,50 +116,7 @@ namespace Ichni.RhythmGame
particleController.extendDirection = extendDirection;
particleController.Rebuild();
}
private void SetDensity()
{
var emission = particle.emission;
emission.rateOverTime = density;
}
private void SetLifeTime()
{
var mainModule = particle.main;
mainModule.startLifetime = lifeTime;
}
private void SetPrewarm()
{
var mainModule = particle.main;
mainModule.prewarm = prewarm;
}
private void SetAlignment()
{
ParticleSystemRenderer particleSystemRenderer = particle.GetComponent<ParticleSystemRenderer>();
var mainModule = particle.main;
if (isAutoOrient)
{
particleSystemRenderer.alignment = ParticleSystemRenderSpace.View;
mainModule.startRotation3D = false; // 禁用3D旋转
}
else
{
particleSystemRenderer.alignment = ParticleSystemRenderSpace.Local;
mainModule.startRotation3D = true; // 启用3D旋转
SetParticleRotation();
}
}
private void SetParticleRotation()
{
var mainModule = particle.main;
mainModule.startRotationX = particleRotation.x * Mathf.Deg2Rad;
mainModule.startRotationY = particleRotation.y * Mathf.Deg2Rad;
mainModule.startRotationZ = particleRotation.z * Mathf.Deg2Rad;
}
public override void Refresh()
{
base.Refresh();

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using Dreamteck.Splines;
using Ichni.RhythmGame.Beatmap;
using UniRx;
using UnityEngine;
namespace Ichni.RhythmGame
@@ -60,6 +61,11 @@ namespace Ichni.RhythmGame
}
trackPositioner.SetPercent(trackPercent.value);
trackPositioner.RebuildImmediate();
/*Debug.Log(trackSwitch.value + " " + trackPercent.value + " " +
nowAttachedTrack.trackPathSubmodule.path.EvaluatePosition(trackPercent.value) + " " +
transform.position + " " + transform.eulerAngles);*/
}
}

View File

@@ -40,7 +40,6 @@ namespace Ichni.RhythmGame
{
timeDurationSubmodule = new TimeDurationSubmodule(this);
}
public void Update()
{
if (track.timeDurationSubmodule.CheckTimeInDuration(GameManager.instance.songTime))

View File

@@ -60,11 +60,14 @@ namespace Ichni.RhythmGame
SetEnableEmission();
SetEmissionIntensity();
SetUV();
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable)
}
protected void SetMesh()
{
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable trackTimeSubmoduleMovable)
{
meshGenerator.clipFrom = 0;
meshGenerator.clipTo = 0;
meshGenerator.clipFrom = trackTimeSubmoduleMovable.tailPercent;
meshGenerator.clipTo = trackTimeSubmoduleMovable.headPercent;
}
else
{
@@ -124,6 +127,7 @@ namespace Ichni.RhythmGame
this.splineRenderer.color = Color.white;
this.splineRenderer.uvRotation = 90;
this.splineRenderer.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
}
public override void SaveBM()
@@ -178,7 +182,7 @@ namespace Ichni.RhythmGame
{
public PathGenerator pathGenerator;
public TrackRendererSubmodulePathGenerator(Track track, bool enableEmission, float emissionIntensity,
public TrackRendererSubmodulePathGenerator(Track track, bool enableEmission, float emissionIntensity,
bool zWrite, Vector2 uvScale, Vector2 uvOffset, Material material = null) :
base(track, enableEmission, emissionIntensity, zWrite, uvScale, uvOffset)
{
@@ -194,8 +198,10 @@ namespace Ichni.RhythmGame
this.pathGenerator.color = Color.white;
this.pathGenerator.uvRotation = 90;
this.pathGenerator.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
}
public override void SaveBM()
{
matchedBM = new TrackRendererSubmodulePathGenerator_BM(attachedGameElement, this);
@@ -268,6 +274,7 @@ namespace Ichni.RhythmGame
this.tubeGenerator.uvRotation = 90;
this.tubeGenerator.sides = sideCount;
this.tubeGenerator.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
}
public override void SaveBM()
@@ -340,6 +347,7 @@ namespace Ichni.RhythmGame
this.surface.color = Color.white;
this.surface.uvRotation = 90;
this.surface.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
}
public override void SaveBM()

View File

@@ -134,11 +134,6 @@ namespace Ichni.RhythmGame
public override void Refresh()
{
if (track.trackRendererSubmodule != null)
{
track.trackRendererSubmodule.meshGenerator.clipFrom = tailPercent;
track.trackRendererSubmodule.meshGenerator.clipTo = headPercent;
}
track.childElementList.ForEach(child =>
{
if (child is NoteBase note)

View File

@@ -1,19 +1,69 @@
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Ichni.UI;
using Michsky.MUIP;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.RhythmGame.UI
{
public class GameLoadingCanvas : UIPageBase
{
public bool allPartsSet;
public RectTransform leftParts;
public RectTransform rightParts;
public TMP_Text progressText;
public RectTransform progressMark;
public ProgressBar progressBar;
public TMP_Text songNameText;
public Image illustrationImage;
public TMP_Text difficultyText;
public TMP_Text levelText;
public TMP_Text composerText;
public TMP_Text illustratorText;
public TMP_Text chartDesignerText;
private void Start()
{
Initialize();
}
private void Initialize()
{
InformationTransistor info = InformationTransistor.instance;
songNameText.text = info.song.songName;
illustrationImage.sprite = info.song.illustration;
difficultyText.text = info.difficulty.GetDifficultyName();
levelText.text = info.difficulty.difficultyValue.ToString();
composerText.text = info.song.composer;
illustratorText.text = info.song.illustratorName;
chartDesignerText.text = info.difficulty.charterName;
}
public void SetProgress(float value)
{
progressBar.currentPercent = value;
progressText.text = Mathf.RoundToInt(value) + "%";
float markXPosition = 1920 * (value / 100f);
progressMark.anchoredPosition = new Vector2(markXPosition, progressMark.anchoredPosition.y);
progressBar.UpdateUI();
}
public void MoveParts()
{
leftParts.anchoredPosition = new Vector2(-1030f, 0f);
rightParts.anchoredPosition = new Vector2(250, 0f);
allPartsSet = false;
leftParts.DOAnchorPosX(1030f, 0.5f).OnComplete(() => allPartsSet = true).Play();
rightParts.DOAnchorPosX(-250f, 0.5f).Play();
}
}
}

View File

@@ -12,11 +12,16 @@ namespace Ichni.RhythmGame.UI
public class GameUICanvas : UIPageBase
{
public Button pauseButton;
public TMP_Text difficultyNameText;
public TMP_Text difficultyValueText;
public TMP_Text readyText;
public TMP_Text accuracyText;
public TMP_Text comboText;
public Image progressBar;
public GamePauseInterface pauseInterface;
[Title("Debug")]
@@ -26,16 +31,28 @@ namespace Ichni.RhythmGame.UI
{
pauseButton.onClick.AddListener(()=>
{
GameManager.instance.audioManager.songPlayer.PauseSong();
pauseButton.interactable = false;
pauseInterface.FadeIn(0.5f, true);
if (GameManager.instance.audioManager.isPlaying)
{
GameManager.instance.audioManager.songPlayer.PauseSong();
pauseButton.interactable = false;
pauseInterface.FadeIn(0.5f, true);
}
});
difficultyNameText.text = InformationTransistor.instance.difficulty.difficultyName;
difficultyNameText.color = InformationTransistor.instance.difficulty.color / 2f;
difficultyValueText.text = InformationTransistor.instance.difficulty.difficultyValue.ToString();
}
private void Update()
{
fpsText.text = (1.0f / Time.unscaledDeltaTime).ToString("F2");
}
if (GameManager.instance.audioManager.isPlaying)
{
float songLength = GameManager.instance.songInformation.songLength;
progressBar.fillAmount = songLength > 0 ? GameManager.instance.songTime / songLength : 0f; // 如果歌曲长度为0填充量为0
}
}
public void UpdateAccuracy(float accuracy)
{

View File

@@ -32,6 +32,7 @@ public partial class BasePrefabsCollection : SerializedScriptableObject
public GameObject triggerHint;
[Title("Effect相关")] public Material defaultParticleMaterial;
public GameObject particleEmitter;
public GameObject bloomEffect;
public GameObject cameraShakeEffect;
public GameObject cameraZoomEffect;

View File

@@ -70,8 +70,14 @@ public class GameInputManager : SerializedMonoBehaviour
private void Update()
{
if (!GameManager.instance.audioManager.isUpdating)
{
return;
}
// 使用预处理指令区分平台
#if UNITY_EDITOR || UNITY_STANDALONE
ProcessMouseInput();
HandleHolding();
#else
ProcessRealTouchInput();

View File

@@ -11,7 +11,9 @@ namespace Ichni
{
public class ProjectLoader
{
public float loadPercent;
public float fakeLoadPercent;
public float realLoadPercent;
public float displayLoadPercent => Mathf.Min(realLoadPercent, fakeLoadPercent);
public static readonly ES3Settings SaveSettings = new ES3Settings
{
@@ -39,34 +41,53 @@ namespace Ichni
LoadProjectInfo(beatMapFolderPath);
LoadSongInfo(beatMapFolderPath);
LoadCommandScripts(beatMapFolderPath);
GameManager.instance.gameLoadingCanvas.MoveParts();
ThemeBundleManager.instance.LoadThemeBundles(GameManager.instance.projectInformation.selectedThemeBundleList);
loadPercent = 0f;
realLoadPercent = 0f;
fakeLoadPercent = 0f;
Observable.EveryUpdate()
.Where(_ => ThemeBundleManager.instance.waitingBundleAmount.Value == 0)
.Where(_ => ThemeBundleManager.instance.waitingBundleAmount.Value == 0 &&
GameManager.instance.gameLoadingCanvas.allPartsSet)
.First()
.Subscribe(_ =>
{
LoadBeatMap(beatMapFolderPath);
IDisposable fakeLoadObserver = Observable.EveryUpdate().Subscribe(_ =>
{
fakeLoadPercent += Time.deltaTime * 0.2f;
GameManager.instance.gameLoadingCanvas.SetProgress(displayLoadPercent * 100f);
});
Observable.EveryUpdate()
.Where(_ => ((BeatmapContainer_BM)GameManager.instance.beatmapContainer.matchedBM).remainingElementAmount.Value == 0)
.Where(_ => ((BeatmapContainer_BM)GameManager.instance.beatmapContainer.matchedBM).remainingElementAmount.Value == 0 &&
fakeLoadPercent >= 1f)
.First()
.Subscribe(_ =>
{
GameManager.instance.beatmapContainer.gameElementList.ForEach(element => element.BeforeStart());
GameManager.instance.gameUICanvas.readyText.DOFade(1, 0.5f)
.SetLoops(4, LoopType.Yoyo).SetEase(Ease.InOutFlash).Play();
Observable.Timer(TimeSpan.FromSeconds(2)).First().Subscribe(_ =>
fakeLoadObserver.Dispose();
Observable.Timer(TimeSpan.FromSeconds(1f)).First().Subscribe(_ =>
{
GameManager.instance.audioManager.isDelaying = true;
GameManager.instance.audioManager.isStarting = false;
Observable.NextFrame().Subscribe(_=>
GameManager.instance.gameLoadingCanvas.FadeOut();
GameManager.instance.audioManager.isLoading = false;
GameManager.instance.beatmapContainer.gameElementList.ForEach(element => element.BeforeStart());
GameManager.instance.gameUICanvas.readyText.DOFade(1, 0.5f)
.SetLoops(4, LoopType.Yoyo).SetEase(Ease.InOutFlash).Play();
Observable.Timer(TimeSpan.FromSeconds(2)).First().Subscribe(_ =>
{
GameManager.instance.beatmapContainer.gameElementList.ForEach(element => element.WhenStart());
GameManager.instance.audioManager.isDelaying = true;
GameManager.instance.audioManager.isStarting = false;
Observable.NextFrame().Subscribe(_ =>
{
GameManager.instance.beatmapContainer.gameElementList.ForEach(element => element.WhenStart());
});
});
});
});

View File

@@ -2,9 +2,9 @@ using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.Menu;
using Ichni.RhythmGame;
using Ichni.UI;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni
{
@@ -25,9 +25,5 @@ namespace Ichni
instance = this;
}
public ChapterSelectionUnit GetChapterByName(string chapterName)
{
return chapters.Find(chapter => chapter.chapterName == chapterName);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AK.Wwise;
using Ichni.RhythmGame;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Serialization;
@@ -56,6 +57,49 @@ namespace Ichni.Menu
{
return (from song in songs where song.storyUnlockKey == key select song.songName).ToList();
}
public (int, int, int, int, int) GetChapterSaveInfo()
{
int beatmapCount = 0;
int finishedSongCount = 0;
int finishedBeatmapCount = 0;
int fullComboCount = 0;
int allPerfectCount = 0;
foreach (SongItemData song in songs)
{
if (GameSaveManager.instance.SongSaveModule.songStatusSaves.TryGetValue(song.songName, out var songStatus))
{
bool thisSongFinished = false;
foreach (BeatmapSave beatmapSave in songStatus.beatmapSaves)
{
beatmapCount++;
if (!beatmapSave.IsEmpty())
{
thisSongFinished = true;
finishedBeatmapCount++;
if (beatmapSave.isFullCombo)
{
fullComboCount++;
}
if (beatmapSave.isAllPerfect)
{
allPerfectCount++;
}
}
}
if (thisSongFinished)
{
finishedSongCount++;
}
}
}
return (beatmapCount, finishedSongCount, finishedBeatmapCount, fullComboCount, allPerfectCount);
}
}
[InlineProperty]

View File

@@ -21,7 +21,11 @@ namespace Ichni
public StoryUIPage storyUIPage;
public DialogUIPage dialogUIPage;
public SongSelectionUIPage songSelectionUIPage;
public TransitionUIPage transitionUIPage;
public SettingsUIPage settingsUIPage;
public List<string> languageList;
public List<string> displayLanguageList;
}
public partial class MenuManager
@@ -31,11 +35,11 @@ namespace Ichni
private void Awake()
{
instance = this;
Application.targetFrameRate = 120;
}
private void Start()
{
Application.targetFrameRate = SettingsManager.instance.gameSettings.targetFrame;
asyncOperation = SceneManager.LoadSceneAsync("GameScene");
asyncOperation.allowSceneActivation = false;
}
@@ -48,16 +52,5 @@ namespace Ichni
MenuInputManager.instance.gameInput.Menu.Disable();
asyncOperation.allowSceneActivation = true;
}
[Button]
public void LanguageSwitch(string language)
{
//Set I2 Localization language
I2.Loc.LocalizationManager.CurrentLanguage = language;
//Debug.Log("Language switched to: " + language);
// Update the UI or any other components that depend on the language change
I2.Loc.LocalizationManager.UpdateSources();
}
}
}

View File

@@ -21,5 +21,10 @@ namespace Ichni.RhythmGame
this.isFullCombo = isFullCombo;
this.isAllPerfect = isAllPerfect;
}
public bool IsEmpty()
{
return accuracy == 0f && !isFullCombo && !isAllPerfect;
}
}
}

View File

@@ -18,6 +18,8 @@ namespace Ichni
public int resolutionLevel = 3;
public int beatmapOffset = 0;
public int languageIndex = 0;
public bool debugMode = false;
@@ -27,7 +29,7 @@ namespace Ichni
}
public GameSettings(int masterVolume, int musicVolume, int soundEffectVolume, int uiVolume,
int beatmapOffset, int targetFrame, int resolutionLevel, bool debugMode)
int beatmapOffset, int targetFrame, int resolutionLevel, int languageIndex, bool debugMode)
{
this.masterVolume = masterVolume;
this.musicVolume = musicVolume;
@@ -36,6 +38,7 @@ namespace Ichni
this.beatmapOffset = beatmapOffset;
this.targetFrame = targetFrame;
this.resolutionLevel = resolutionLevel;
this.languageIndex = languageIndex;
this.debugMode = debugMode;
}
@@ -53,5 +56,11 @@ namespace Ichni
UniversalRenderPipelineAsset currentUrpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
currentUrpAsset.renderScale = 0.7f + 0.1f * resolutionLevel;
}
public void ApplyLanguage()
{
I2.Loc.LocalizationManager.CurrentLanguage = MenuManager.instance.languageList[languageIndex];
I2.Loc.LocalizationManager.UpdateSources();
}
}
}

View File

@@ -50,7 +50,7 @@ namespace Ichni
{
gameSettings = new GameSettings(
50, 50, 50, 50,
0, 60, 3, false);
0, 60, 3, 0, false);
}
gameSettings.ApplyVolume();

View File

@@ -0,0 +1,48 @@
using System.Collections;
using System.Collections.Generic;
using Michsky.MUIP;
using UnityEngine;
namespace Ichni.UI
{
public class Dropdown : SettingsUIElementBase
{
public List<string> options;
public CustomDropdown dropdown;
public int selectedIndex;
public string selectedOption;
public void SetUp(int initialValue, List<string> options, string title = "")
{
base.SetUp(title);
this.options = options;
this.dropdown.items = new List<CustomDropdown.Item>();
foreach (string option in options)
{
dropdown.items.Add(new CustomDropdown.Item { itemName = option });
}
dropdown.onValueChanged.AddListener(value =>
{
SetValue(value);
updateValueAction?.Invoke();
});
SetValue(initialValue);
}
public int GetOptionIndex(string option)
{
return options.IndexOf(option);
}
public void SetValue(int index)
{
selectedIndex = index;
selectedOption = options[selectedIndex];
dropdown.selectedItemIndex = selectedIndex;
dropdown.UpdateItemLayout();
}
}
}

View File

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

View File

@@ -1,10 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using I2.Loc;
using Michsky.MUIP;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
namespace Ichni.UI
{

View File

@@ -1,5 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using I2.Loc;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@@ -8,10 +10,12 @@ namespace Ichni.UI
public class TextButton : SettingsUIElementBase
{
public Button button;
public void SetUp(string title = "")
public TMP_Text buttonText;
public void SetUp(string title, string subTitle, string textContent)
{
base.SetUp(title);
base.SetUp(title, subTitle);
buttonText.GetComponent<Localize>().SetTerm(textContent);
button.onClick.AddListener(() =>
{

View File

@@ -35,18 +35,29 @@ namespace Ichni.UI
public Image avatarMask;
public TMP_Text decorationChapterText0;
public TMP_Text decorationChapterText1;
public TMP_Text progressText;
public TMP_Text outerSongProgressText;
public RectTransform titleRect;
public TMP_Text titleText;
[Title("展开内容")]
[Title("展开内容")]
public RectTransform expansionBackground;
public Image expansionRipple;
public Material rippleMaterial;
public Sequence rippleSequence;
public RectTransform expansionFunctions;
public RectTransform expansionInfos;
public TMP_Text innerSongProgressText;
public TMP_Text fullComboText;
public TMP_Text allPerfectText;
public TMP_Text beatmapProgressText;
public void Initialize(ChapterSelectionUnit chapter)
{
connectedChapter = chapter;
expansionRipple.material = new Material(rippleMaterial);
expandButton.onClick.AddListener(() =>
{
@@ -90,6 +101,14 @@ namespace Ichni.UI
ChapterSelectionManager.instance.chapterSelectionUIPage.FadeOut();
SongSelectionManager.instance.songSelectionUIPage.FadeIn();
});
(int beatmapCount, int finishedSongCount, int finishedBeatmapCount, int fullComboCount, int allPerfectCount) = chapter.GetChapterSaveInfo();
float songProgressPercent = (float)finishedSongCount / chapter.songs.Count * 100f;
outerSongProgressText.text = songProgressPercent.ToString("F2") + "%";
innerSongProgressText.text = songProgressPercent.ToString("F2") + "%";
fullComboText.text = fullComboCount.ToString();
allPerfectText.text = allPerfectCount.ToString();
beatmapProgressText.text = $"{finishedBeatmapCount}/{beatmapCount}";
}
private void Expand()
@@ -122,7 +141,16 @@ namespace Ichni.UI
expandSequence.Join(upperTip.DOFade(1f, 0.3f));
expandSequence.OnStart(() => isDuringAnimation = true);
expandSequence.OnComplete(() => isDuringAnimation = false);
expandSequence.OnComplete(() =>
{
isDuringAnimation = false;
expansionRipple.material.SetFloat("_RippleTime", 0f);
rippleSequence = DOTween.Sequence();
rippleSequence.AppendInterval(5f);
rippleSequence.Append(expansionRipple.material.DOFloat(1, "_RippleTime", 2f));
rippleSequence.SetLoops(-1);
rippleSequence.Play();
});
expandSequence.Play();
}
@@ -131,6 +159,8 @@ namespace Ichni.UI
{
isExpanded = false;
rippleSequence?.Kill(true);
Sequence shrinkSequence = DOTween.Sequence();
shrinkSequence.Append(bottomTip.DOFade(0f, 0.3f));
@@ -147,18 +177,17 @@ namespace Ichni.UI
expansionFunctions.gameObject.SetActive(false);
}));
shrinkSequence.Join(titleRect.DOSizeDelta(new Vector2(322, 100), 0.3f).SetEase(Ease.InQuad));
shrinkSequence.Join(titleRect.DOSizeDelta(new Vector2(322, 100), 0.3f));
shrinkSequence.Append(expansionBackground.DOSizeDelta(new Vector2(322, 826), 0.3f)
.SetEase(Ease.InQuad)
.OnComplete(() =>
{
expansionBackground.gameObject.SetActive(false);
}));
shrinkSequence.Join(DOTween.To(() => layoutElement.preferredWidth,
x => layoutElement.preferredWidth = x, 322, 0.3f).SetEase(Ease.InQuad));
shrinkSequence.Join(avatarMask.rectTransform.DOSizeDelta(new Vector2(322, 826), 0.3f).SetEase(Ease.InQuad));
x => layoutElement.preferredWidth = x, 322, 0.3f));
shrinkSequence.Join(avatarMask.rectTransform.DOSizeDelta(new Vector2(322, 826), 0.3f));
shrinkSequence.OnStart(() => isDuringAnimation = true);
shrinkSequence.OnComplete(() => isDuringAnimation = false);

View File

@@ -15,6 +15,8 @@ namespace Ichni.UI
protected override void Awake()
{
base.Awake();
fadeInStartAction = Initialize;
settingsButton.onClick.AddListener(() =>
{
@@ -23,8 +25,14 @@ namespace Ichni.UI
});
}
private void Start()
private void Initialize()
{
// Clear existing chapters
foreach (Transform child in chapterContainer)
{
Destroy(child.gameObject);
}
foreach (var chapter in ChapterSelectionManager.instance.chapters)
{
ChapterSelectionUI item = Instantiate(chapterSelectionUIPrefab, chapterContainer).GetComponent<ChapterSelectionUI>();

View File

@@ -0,0 +1,122 @@
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using TMPro;
using UnityEngine;
namespace Ichni.UI
{
public class TextCatapult : MonoBehaviour
{
[Header("UI 元素")] [Tooltip("第一个文本组件")] [SerializeField]
private TMP_Text textA;
[Tooltip("第二个文本组件")] [SerializeField] private TMP_Text textB;
[Header("动画参数")] [Tooltip("文本在中央停留显示的时间")] [SerializeField]
private float displayDuration = 4f;
[Tooltip("向上移动和淡入淡出的动画时间")] [SerializeField]
private float animationDuration = 1f;
[Tooltip("文本向上移动的距离")] [SerializeField]
private float moveDistance = 200f;
// --- 可选: 如果你想让文字内容循环变化 ---
[Header("可选的文本内容")] [Tooltip("在这里填入你想循环显示的文字列表")] [SerializeField]
private List<string> textContents = new List<string>();
private TMP_Text[] _textElements;
private int _currentTextIndex = 0; // 当前显示在中央的文本的索引 (0 或 1)
private int _contentIndex = 0; // 当前显示内容的索引
private Vector2 _centerPosition;
private Sequence _animationSequence;
void Start()
{
Setup();
// 直接启动第一个动画过渡
AnimateTransition();
}
void OnDestroy()
{
// 安全地杀死所有相关的DoTween动画防止在场景切换或对象销毁时出错
_animationSequence?.Kill();
}
/// <summary>
/// 初始化设置
/// </summary>
private void Setup()
{
_textElements = new[] { textA, textB };
// 记录中心位置
_centerPosition = textA.rectTransform.anchoredPosition;
// 设置 textA 的初始状态
textA.rectTransform.anchoredPosition = _centerPosition;
textA.alpha = 1f;
UpdateTextContent(textA); // 设置初始文字
// 设置 textB 的初始状态 (在下方,透明)
textB.rectTransform.anchoredPosition = _centerPosition - new Vector2(0, moveDistance);
textB.alpha = 0f;
// textB 的内容将在它动画开始前更新,所以这里无需设置
}
/// <summary>
/// 执行一次完整的文本过渡动画,并在完成后自我调用以形成循环
/// </summary>
private void AnimateTransition()
{
// 确定哪个文本要移出,哪个要移入
TMP_Text textToAnimateOut = _textElements[_currentTextIndex];
TMP_Text textToAnimateIn = _textElements[(_currentTextIndex + 1) % 2];
// 创建一个新的动画序列
_animationSequence = DOTween.Sequence();
_animationSequence
.AppendInterval(displayDuration) // 1. 等待当前文本显示4秒
.AppendCallback(() => {
// 2. 在动画开始前,更新即将进入的文本的内容
UpdateTextContent(textToAnimateIn);
})
// 3. 同时执行两个文本的动画
.Append(textToAnimateOut.rectTransform.DOAnchorPosY(_centerPosition.y + moveDistance, animationDuration).SetEase(Ease.OutCubic))
.Join(textToAnimateOut.DOFade(0f, animationDuration).SetEase(Ease.OutCubic))
.Join(textToAnimateIn.rectTransform.DOAnchorPosY(_centerPosition.y, animationDuration).SetEase(Ease.OutCubic))
.Join(textToAnimateIn.DOFade(1f, animationDuration).SetEase(Ease.OutCubic))
.OnComplete(() => {
// 4. 当整个过渡动画完成时
// 将刚刚移出的文本复位到起始位置,为下一次进入做准备
textToAnimateOut.rectTransform.anchoredPosition = _centerPosition - new Vector2(0, moveDistance);
// 切换当前文本的索引,为下个循环做准备
_currentTextIndex = (_currentTextIndex + 1) % 2;
// 启动下一次动画过渡,形成循环
AnimateTransition();
});
_animationSequence.Play();
}
/// <summary>
/// 更新文本内容(如果列表不为空)
/// </summary>
private void UpdateTextContent(TMP_Text textElement)
{
if (textContents == null || textContents.Count == 0)
{
return; // 如果列表为空,则不改变文字
}
textElement.text = textContents[_contentIndex];
_contentIndex = (_contentIndex + 1) % textContents.Count; // 移动到下一个内容索引,并循环
}
}
}

View File

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

View File

@@ -1,67 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Ichni.Menu;
using Ichni.Story;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.UI
{
public partial class PrepareUIPage : UIPageBase
{
public TextButton enterGameButton;
public TextButton switchDifficultyButton;
public string songName;
public List<string> difficulties;
public string difficultyName;
private ChapterSelectionUnit chapter;
private SongItemData songItem;
private DifficultyData difficultyData;
}
public partial class PrepareUIPage
{
public void SetUpPrepareUIPage(string songName)
{
chapter = ChapterSelectionManager.instance.currentChapter;
songItem = chapter.songs.FirstOrDefault(s => s.songName == songName);
this.songName = songName;
this.difficulties = new List<string>();
foreach (DifficultyData difficulty in songItem.difficultyDataList)
{
this.difficulties.Add(difficulty.difficultyName);
}
this.difficultyName = difficulties[0];
difficultyData = songItem.difficultyDataList.FirstOrDefault(d => d.difficultyName == difficultyName);
switchDifficultyButton.GetComponentInChildren<TMP_Text>().text = difficultyName + " Lv." + difficultyData.difficultyValue;
switchDifficultyButton.GetComponentInChildren<TMP_Text>().color = difficultyData.color;
}
public void SwitchDifficulty()
{
int currentIndex = difficulties.IndexOf(difficultyName);
int nextIndex = (currentIndex + 1) % difficulties.Count;
difficultyName = difficulties[nextIndex];
difficultyData = songItem.difficultyDataList
.FirstOrDefault(d => d.difficultyName == difficultyName);
switchDifficultyButton.GetComponentInChildren<TMP_Text>().text = difficultyName + " Lv." + difficultyData.difficultyValue;
switchDifficultyButton.GetComponentInChildren<TMP_Text>().color = difficultyData.color;
}
public void EnterGame()
{
InformationTransistor.instance.SetInformation(ChapterSelectionManager.instance.currentChapter, songItem, difficultyData);
MenuAudioManager.instance.audioContainer.StopEvent("PlayPreview");
MenuManager.instance.TestEnterGame();
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.Menu;
using MoreMountains.Tools;
using UnityEngine;
namespace Ichni.UI
@@ -8,9 +9,10 @@ namespace Ichni.UI
public class GamePlaySettingsWindow : SettingsWindow
{
public TextButton offsetEditorButton;
public Dropdown languageDropdown;
public override void Initialize()
{
offsetEditorButton.SetUp("Menu UI/Settings_OffsetEditor");
offsetEditorButton.SetUp("Menu UI/Settings_OffsetEditor", "", "Menu UI/Settings_Enter");
offsetEditorButton.updateValueAction = () =>
{
gameObject.SetActive(false);
@@ -18,11 +20,18 @@ namespace Ichni.UI
MenuManager.instance.settingsUIPage.offsetEditor.offsetEditingContainer.SetActive(true);
MenuManager.instance.settingsUIPage.offsetEditor.Play();
};
languageDropdown.SetUp(gameSettings.languageIndex, MenuManager.instance.displayLanguageList, "Menu UI/Settings_Language");
languageDropdown.updateValueAction = () =>
{
gameSettings.languageIndex = languageDropdown.selectedIndex;
gameSettings.ApplyLanguage();
};
}
public override void SetValuesFromSettings()
{
languageDropdown.SetValue(gameSettings.languageIndex);
}
}
}

View File

@@ -28,7 +28,7 @@ namespace Ichni.Menu
data.rebindButton.SetUp(data.title, data.subtitle, data.actionName, data.bindingIndex);
}
resetButton.SetUp("MenuUI/Settings_ResetRebinding");
resetButton.SetUp("Menu UI/Settings_ResetRebinding", "", "Menu UI/Settings_Confirm");
resetButton.updateValueAction = ResetAllBindings;
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
@@ -39,10 +40,14 @@ namespace Ichni.Menu
{
arrowSeq.Join(arrow.DOAnchorPosX(-584.5f, 0.2f));
}
arrowSeq.OnComplete(() =>
{
MenuManager.instance.TestEnterGame();
MenuManager.instance.transitionUIPage.FadeIn();
Observable.Timer(TimeSpan.FromSeconds(0.6f)).Subscribe(_ =>
{
MenuManager.instance.TestEnterGame();
});
});
arrowSeq.Play();

View File

@@ -22,8 +22,8 @@ namespace Ichni.Menu
[Title("对齐与动画")]
[SerializeField] public RectTransform centerPoint;
[SerializeField] private float snapSpeed = 5f;
[SerializeField] private float decelerationRate = 0.135f;
[SerializeField] private float snapSpeed = 10f;
[SerializeField] private float decelerationRate = 0.15f;
[Title("平滑度优化")]
[SerializeField] [Range(1f, 20f)]
@@ -263,7 +263,10 @@ namespace Ichni.Menu
selectedTab?.SetSelection(false);
selectedTab = null; // 清除当前选中的Tab
if(isDuringSnap && SnapCoroutine != null) StopCoroutine(SnapCoroutine);
if (isDuringSnap && SnapCoroutine != null)
{
StopCoroutine(SnapCoroutine);
}
SnapCoroutine = SnapToItem(tab.GetComponent<RectTransform>(), false);
@@ -289,8 +292,7 @@ namespace Ichni.Menu
Vector3 closestItemLocalPos = viewport.InverseTransformPoint(targetItem.position);
Vector3 centerPointLocalPos = viewport.InverseTransformPoint(centerPoint.position);
float localOffsetY = centerPointLocalPos.y - closestItemLocalPos.y;
// 【核心修正 #3】吸附动画现在也是通过更新targetPosition来实现
Vector2 finalTargetPosition = content.anchoredPosition + new Vector2(0, localOffsetY);
finalTargetPosition.y = Mathf.Clamp(finalTargetPosition.y, bottomBound, topBound);

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using TMPro;
using UniRx;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
@@ -43,8 +44,17 @@ namespace Ichni.Menu
{
if (MenuManager.instance.songSelectionUIPage.songListController.selectedTab == this)
{
// MenuManager.instance.prepareUIPage.SetUpPrepareUIPage(song.songName);
// MenuManager.instance.prepareUIPage.FadeIn();
InformationTransistor.instance.SetInformation(
ChapterSelectionManager.instance.currentChapter,
MenuManager.instance.songSelectionUIPage.selectedSong,
MenuManager.instance.songSelectionUIPage.selectedDifficulty);
MenuAudioManager.instance.audioContainer.PlaySoundFX("EnterToGame");
MenuAudioManager.instance.audioContainer.StopEvent("PlayPreview");
MenuManager.instance.transitionUIPage.FadeIn();
Observable.Timer(TimeSpan.FromSeconds(0.6f)).Subscribe(_ =>
{
MenuManager.instance.TestEnterGame();
});
}
else
{

View File

@@ -8,9 +8,10 @@ namespace Ichni.UI
public class StartUIPage : UIPageBase
{
public AudioContainer audioContainer;
private void Awake()
protected override void Awake()
{
base.Awake();
audioContainer = GetComponent<AudioContainer>();
}

View File

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

View File

@@ -0,0 +1,12 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.UI;
using UnityEngine;
namespace Ichni.Menu
{
public class TransitionUIPage : UIPageBase
{
}
}

View File

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

View File

@@ -57,21 +57,13 @@ namespace Ichni
if (audioManager.isPlaying)
{
songTimeSegment = PlaySegment() / 1000f - (judgeOffset / 1000f);
if (songTimeSegment > 0)
float currentSongSegment = PlaySegment() / 1000f - (judgeOffset / 1000f);
if (recordedSongSeg < currentSongSegment)
{
recordedSongSeg = songTimeSegment;
songTimeSegment = currentSongSegment;
recordedSongSeg = currentSongSegment;
}
/*
else if (songTimeSegment == 0 && recordedSongSeg > 0)
{
audioManager.isFinished = true;
songTimeSegment = recordedSongSeg;
}*/
//Debug.Log($"Song Time Segment: {songTimeSegment}, Recorded Segment: {recordedSongSeg}");
}
else if (audioManager.isPausing)
{
@@ -98,7 +90,7 @@ namespace Ichni
[Button]
public void PauseSong()
{
pauseTimeSegment = PlaySegment() / 1000f - (judgeOffset / 1000f);
pauseTimeSegment = songTimeSegment;
audioManager.isPlaying = false;
audioManager.isPausing = true;
PauseMusicEvent.Post(gameObject);
@@ -130,6 +122,7 @@ namespace Ichni
{
if (in_info is AkMusicSyncCallbackInfo musicInfo)
{
GameManager.instance.songInformation.songLength = musicInfo.segmentInfo_iActiveDuration / 1000f;
InformationTransistor.instance.songLength = musicInfo.segmentInfo_iActiveDuration / 1000f;
InformationTransistor.instance.bpm = GameManager.instance.songInformation.bpm;
}

View File

@@ -4,7 +4,7 @@ MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
executionOrder: -3
icon: {instanceID: 0}
userData:
assetBundleName: