Compare commits
56 Commits
2.0.0.beta
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d97e66612 | ||
|
|
3355747f2e | ||
|
|
c07804f26c | ||
|
|
b2727924fb | ||
|
|
0381abf602 | ||
|
|
1ceec57422 | ||
|
|
7b20754920 | ||
|
|
e6c75c444e | ||
|
|
4609498ced | ||
|
|
e16c6e2847 | ||
|
|
74cb2e815b | ||
|
|
ca170d2a95 | ||
|
|
1ee6f00314 | ||
|
|
0a2b69f970 | ||
|
|
de631fa648 | ||
|
|
c009cfdcbb | ||
|
|
66244a15cc | ||
|
|
e25ebfb358 | ||
|
|
9fc39c0eb2 | ||
|
|
cf42114966 | ||
|
|
2c1dd0b1f2 | ||
|
|
fae214f6d3 | ||
|
|
23fb548e11 | ||
|
|
dab1b8fd75 | ||
|
|
3c40b8da25 | ||
|
|
5e7932a825 | ||
|
|
d8efe9c1f1 | ||
|
|
866eefabd3 | ||
|
|
7c0cc48734 | ||
|
|
f3c735936a | ||
|
|
d657fafab5 | ||
|
|
af2a5478f1 | ||
|
|
840ac79e38 | ||
|
|
cf45ac7697 | ||
|
|
2ecf542087 | ||
|
|
7f6a1b01f8 | ||
|
|
14db91ea0f | ||
|
|
b776940c48 | ||
|
|
d26e1bba1f | ||
|
|
37bd37a850 | ||
|
|
04d6db6ec4 | ||
|
|
b80a535c65 | ||
|
|
de387958fa | ||
|
|
c94206f398 | ||
|
|
8ddffec227 | ||
|
|
adc4d7ef56 | ||
|
|
870a2b7b32 | ||
|
|
3d47031bd2 | ||
|
|
2d6aba81fa | ||
|
|
787f3014d9 | ||
|
|
7fa93aab21 | ||
|
|
490db467f0 | ||
|
|
47d52d2653 | ||
|
|
38c96ad522 | ||
|
|
89d1617593 | ||
|
|
7089b3e722 |
83
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us address issues you are facing
|
||||
title: "[Bug] "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: duplication
|
||||
attributes:
|
||||
label: ⠀
|
||||
options:
|
||||
- label: This issue is not duplicated with any other open or closed issues
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
⠀
|
||||
Thanks for taking the time to make us better!
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is
|
||||
placeholder: |
|
||||
App crashes on startup every time after changing settings.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen
|
||||
placeholder: |
|
||||
App started normally, everything worked fine.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the bug
|
||||
placeholder: |
|
||||
1. Turn on "Show hitbox" in "Judgement" settings
|
||||
2. Restart the app
|
||||
3. Crash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Device information
|
||||
description: Provide details about your system environment
|
||||
placeholder: |
|
||||
Device: [e.g. Pixel 8 Pro]
|
||||
System: [e.g. Android 15 (AP41.240823.009)]
|
||||
Version: [e.g. v2.0.0.beta.2.patch.5]
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem
|
||||
placeholder: |
|
||||
modified_setting_items.jpg
|
||||
crashed_screen.jpg
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here
|
||||
placeholder: |
|
||||
Crash report:
|
||||
validations:
|
||||
required: false
|
||||
51
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Feature request
|
||||
description: Suggest features you want to add or suggest to modify existing features
|
||||
title: "[Feature] "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to make us better!
|
||||
|
||||
- type: textarea
|
||||
id: related
|
||||
attributes:
|
||||
label: Is your feature request related to a problem?
|
||||
description: A clear and concise description of what the problem
|
||||
placeholder: |
|
||||
I'm always frustrated when ...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the feature
|
||||
description: A clear and concise description of what the feature is
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reasons
|
||||
attributes:
|
||||
label: Reason for adding
|
||||
description: Explain why this feature would be useful to you
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Example(s)
|
||||
description: Post screenshots/drawings/links/etc of the feature request, or proof-of-concept images about the feature
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the feature here
|
||||
validations:
|
||||
required: false
|
||||
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Macintosh index files
|
||||
.DS_Store
|
||||
68
README.md
@@ -1,39 +1,61 @@
|
||||
> [!WARNING]
|
||||
> ### This GitHub repository is the ONLY official source for downloading the game.
|
||||
> AstroDX is **100% free**, and will never require payment from you.
|
||||
> If you purchased this game from any third-party website, store, or seller, you have been misled. These entities are not authorized to distribute or monetize this work. Request a refund immediately through the platform/store where you made the purchase. Report the listing to the platform's support team to help prevent further scams.
|
||||
|
||||
# AstroDX
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://discord.gg/6fpETgpvjZ)
|
||||
|
||||
AstroDX (Formerly named MaipadDX), is a mobile maimai simulator that builds on top of the traditional arcade experience and brings optimizations for mobile devices.
|
||||
AstroDX (Formerly MaipadDX) is a mobile maimai simulator intended for those who do not yet have access to a cabinet, those who want to practice, and everyone interested in maimai who otherwise could not play the arcade game.
|
||||
This game is a clean-room implementation of maimai, and has been developed without using any use of original arcade data.
|
||||
|
||||
# Project open-source status
|
||||
# Open-source projects
|
||||
|
||||
To ensure safety, we're not planning on making the project open-source. This repository serves solely as a knowledgebase for the game.
|
||||
However, If you have issues, please don't hesitate to point it out in issues and we'll try to answer them as best as we can.
|
||||
- Simai serializer/deserializer: [SimaiSharp](https://github.com/reflektone-games/SimaiSharp)
|
||||
- Gameplay logic: [AstroDX core-dump](https://github.com/2394425147/maipaddx/tree/main/core-dump)
|
||||
|
||||
We intended to open-source AstroDX after publishing to the iOS/Android app stores. Paid assets will be excluded to comply with licenses.
|
||||
|
||||
# Q&A
|
||||
|
||||
## Which version should I download?
|
||||
## How do I download the iOS version?
|
||||
|
||||
### 1.x
|
||||
> [!IMPORTANT]
|
||||
> Only 10k users can be in a group at any time. Don't join multiple groups if you joined one already.
|
||||
|
||||
As of now, **v1.1** is the latest stable release of AstroDX.
|
||||
Use this version if you want a bug-less experience.
|
||||
Join the testing program through [TestFlight Group A](https://testflight.apple.com/join/rACTLjPL), [TestFlight Group B](https://testflight.apple.com/join/ocj3yptn), [TestFlight Group C](https://testflight.apple.com/join/CuMxZE2M), or [TestFlight Group D](https://testflight.apple.com/join/T6qKfV6f) or [TestFlight Group E](https://testflight.apple.com/join/sMm1MCYc).
|
||||
|
||||
### 2.x
|
||||
Our [Discord server](https://discord.com/channels/892807792996536453/1210127565986205726/1210428179001380946) also offers a live tracker of available seats.
|
||||
|
||||
> You should always update to the latest version whenever possible.
|
||||
> 2.x is constantly updated and it's hard to manage multiple versions of the game algorithms at the same time,
|
||||
> so we suggest you to keep up-to-date.
|
||||
## How do I get songs/levels?
|
||||
|
||||
**v2.0** is currently undergoing many changes, including a massive UI overhaul, runtime optimizations, vfx / audio system reworks, just to name a few.
|
||||
However, new features are unstable and will eventually be updated as time progresses. If you live on cutting-edge technology, this is the version for you.
|
||||
- [Installation Guide for Android](https://wiki.astrodx.com/install/android)
|
||||
- [Installation Guide for iOS/iPadOS](https://wiki.astrodx.com/install/ios)
|
||||
|
||||
## Are there any tutorials on importing?
|
||||
|
||||
Importing guides are available in the wiki of this repo.
|
||||
|
||||
## Can I use charts transcribed from the official arcade game maimai?
|
||||
## Can I play converts from maimai?
|
||||
|
||||
We **don't recommend** doing this, as it violates SEGA's policies.
|
||||
|
||||
Happy playing!
|
||||
## I'm having some issues...
|
||||
|
||||
> [!TIP]
|
||||
> Writing your issue in English allows more people to understand and help you.
|
||||
> We also recommend searching for other people's issues for solutions first.
|
||||
|
||||
Open an issue [here](https://github.com/2394425147/astrodx/issues), or join our [Discord server](https://discord.gg/6fpETgpvjZ) to talk about it.
|
||||
|
||||
## 問題が発生した場合
|
||||
|
||||
> [!TIP]
|
||||
> イシューを英語で記載すると、より多くの人が理解し、助けてくれるでしょう。
|
||||
|
||||
[こちらから](https://github.com/2394425147/astrodx/issues)イシューを提出するか、[Discordサーバー](https://discord.com/channels/892807792996536453/1210127565986205726/1210428179001380946)に参加して相談してください。
|
||||
|
||||
## 我遇到了問題!
|
||||
|
||||
> [!TIP]
|
||||
> 我們强烈建議提供英文翻譯(或綫上翻譯),以便其他人理解,并幫助到你。我們也建議搜索現有的issue,以避免重複或疑慮。
|
||||
|
||||
在[這裏](https://github.com/2394425147/astrodx/issues)提交你的issue,或在我們的[Discord伺服器](https://discord.gg/6fpETgpvjZ)上一起討論。
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AstroDX.Contexts.Gameplay.Behaviours.Slide.SlideMarkers;
|
||||
using AstroDX.Contexts.Gameplay.Interactions;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Contexts.Gameplay.SlideGenerators;
|
||||
using AstroDX.Globals;
|
||||
using Medicine;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.Behaviours.Slide.Handlers
|
||||
{
|
||||
public class FanSlideSegmentHandler : SlideSegmentHandler
|
||||
{
|
||||
private const int Margin = 2;
|
||||
private const int ArrowCount = 11;
|
||||
|
||||
private readonly List<FanSlideArrow> _arrows = new();
|
||||
private readonly Vector2[] _endPositions;
|
||||
|
||||
private readonly List<List<TouchInteractable>> _interactableGroups = new();
|
||||
|
||||
private readonly Vector2 _startPosition;
|
||||
private readonly Vector2[] _ups;
|
||||
|
||||
private int _interactionReceivedState;
|
||||
|
||||
private SlideStarBehaviour[] _stars;
|
||||
|
||||
public FanSlideSegmentHandler(SlideBehaviour slideBehaviour,
|
||||
SlideSegment segment,
|
||||
Location startLocation) :
|
||||
base(slideBehaviour)
|
||||
{
|
||||
var vertices = segment.vertices.ToList();
|
||||
vertices.Insert(0, startLocation);
|
||||
|
||||
_startPosition = SlideGenerator.GetPosition(vertices[0]);
|
||||
|
||||
var ccwEndLocation = startLocation;
|
||||
ccwEndLocation.index += 3;
|
||||
|
||||
var centerEndLocation = startLocation;
|
||||
centerEndLocation.index += 4;
|
||||
|
||||
var cwEndLocation = startLocation;
|
||||
cwEndLocation.index += 5;
|
||||
|
||||
_endPositions = new[]
|
||||
{
|
||||
SlideGenerator.GetPosition(ccwEndLocation),
|
||||
SlideGenerator.GetPosition(centerEndLocation),
|
||||
SlideGenerator.GetPosition(cwEndLocation)
|
||||
};
|
||||
|
||||
_ups = new[]
|
||||
{
|
||||
_endPositions[0] - _startPosition,
|
||||
_endPositions[1] - _startPosition,
|
||||
_endPositions[2] - _startPosition
|
||||
};
|
||||
|
||||
_stars = new[]
|
||||
{
|
||||
SlideManager.slideStars.Get(),
|
||||
SlideManager.slideStars.Get(),
|
||||
SlideManager.slideStars.Get()
|
||||
};
|
||||
|
||||
foreach (var star in _stars) star.Initialize(ParentPath, ParentNote.parentCollection);
|
||||
|
||||
GenerateSensors();
|
||||
GenerateArrows();
|
||||
}
|
||||
|
||||
[Inject.Single]
|
||||
private SlideManager SlideManager { get; }
|
||||
|
||||
[Inject.Single]
|
||||
private TouchManager TouchManager { get; }
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return RenderManager.PlayFieldRadius * 2;
|
||||
}
|
||||
|
||||
public override void OnUpdate(float segmentT)
|
||||
{
|
||||
if (IsJudgementTarget)
|
||||
CheckInteraction();
|
||||
|
||||
UpdateSlideStarPosition(segmentT);
|
||||
UpdateArrowOpacity();
|
||||
}
|
||||
|
||||
private void UpdateArrowOpacity()
|
||||
{
|
||||
var timeSinceNoteStart = TimeSinceSlideStart + ParentPath.delay;
|
||||
|
||||
var alpha = 0.8f;
|
||||
if (timeSinceNoteStart < 0)
|
||||
{
|
||||
var colorPoint = Mathf.InverseLerp(-Persistent.Settings.Gameplay.slideFadeInDuration,
|
||||
0,
|
||||
(float)timeSinceNoteStart);
|
||||
|
||||
alpha = Mathf.Lerp(0, 0.8f, colorPoint);
|
||||
}
|
||||
|
||||
foreach (var arrow in _arrows.Select(a => a.spriteRenderer))
|
||||
{
|
||||
var color = arrow.color;
|
||||
color.a = alpha;
|
||||
|
||||
arrow.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckInteraction()
|
||||
{
|
||||
if (_interactionReceivedState == 0)
|
||||
{
|
||||
if (!_interactableGroups[0][0].GetSlideJudgement())
|
||||
return;
|
||||
|
||||
_interactionReceivedState = 1;
|
||||
foreach (var slideArrow in _arrows
|
||||
.Where(a => a.interactionGroupIndex == 0)
|
||||
.ToList())
|
||||
{
|
||||
_arrows.Remove(slideArrow);
|
||||
slideArrow.ExitSequence();
|
||||
}
|
||||
}
|
||||
|
||||
if (_interactionReceivedState >= 3)
|
||||
{
|
||||
Cleared = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (var groupIndex = _interactionReceivedState; groupIndex < 3; groupIndex++)
|
||||
{
|
||||
foreach (var interactable in
|
||||
_interactableGroups[groupIndex]
|
||||
.Where(interactable => interactable.GetSlideJudgement())
|
||||
.ToList())
|
||||
_interactableGroups[groupIndex].Remove(interactable);
|
||||
|
||||
if (_interactableGroups[groupIndex].Count > 0 ||
|
||||
_interactionReceivedState < groupIndex)
|
||||
continue;
|
||||
|
||||
foreach (var slideArrow in _arrows
|
||||
.Where(a => a.interactionGroupIndex == _interactionReceivedState)
|
||||
.ToList())
|
||||
{
|
||||
_arrows.Remove(slideArrow);
|
||||
slideArrow.ExitSequence();
|
||||
}
|
||||
|
||||
_interactionReceivedState++;
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetRemainingLength()
|
||||
{
|
||||
return (3 - _interactionReceivedState) / 3f * GetLength();
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
foreach (var slideArrow in _arrows)
|
||||
SlideManager.fanSlideArrows.Release(slideArrow);
|
||||
|
||||
foreach (var star in _stars)
|
||||
SlideManager.slideStars.Release(star);
|
||||
}
|
||||
|
||||
private void UpdateSlideStarPosition(float segmentT)
|
||||
{
|
||||
if (disposed || _stars is null)
|
||||
return;
|
||||
|
||||
for (var index = 0; index < _stars.Length; index++)
|
||||
{
|
||||
var star = _stars[index];
|
||||
|
||||
if (star == null)
|
||||
continue;
|
||||
|
||||
var transform = star.transform;
|
||||
|
||||
if (segmentT >= 1 && indexInSlide != ParentPath.segments.Count - 1)
|
||||
{
|
||||
transform.localScale = Vector3.one * Persistent.Settings.Gameplay.noteScale;
|
||||
star.Appearance.Color = new Color(1, 1, 1, 0);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var position = Vector3.Lerp(_startPosition,
|
||||
_endPositions[index],
|
||||
segmentT);
|
||||
|
||||
transform.localPosition = position;
|
||||
transform.up = _ups[index];
|
||||
|
||||
if (TimeSinceSlideStart < 0 &&
|
||||
indexInSlide == 0 &&
|
||||
ParentNote.slideMorph != SlideMorph.SuddenIn)
|
||||
{
|
||||
var interpolation =
|
||||
Mathf.InverseLerp(-ParentPath.delay, 0,
|
||||
(float)TimeSinceSlideStart);
|
||||
transform.localScale = new Vector3(interpolation, interpolation) *
|
||||
Persistent.Settings.Gameplay.noteScale;
|
||||
star.Appearance.Color = new Color(1, 1, 1, interpolation);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (segmentT < 0)
|
||||
return;
|
||||
|
||||
transform.localScale = Vector3.one * Persistent.Settings.Gameplay.noteScale;
|
||||
star.Appearance.Color = Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetJudgementVector(out Vector2 position, out float rotation)
|
||||
{
|
||||
position = _endPositions[1];
|
||||
rotation = Mathf.Atan2(_ups[1].y, _ups[1].x);
|
||||
}
|
||||
|
||||
private void GenerateSensors()
|
||||
{
|
||||
_interactableGroups.AddRange(new List<TouchInteractable>[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.ASensor,
|
||||
index = ParentNote.location.index
|
||||
})
|
||||
},
|
||||
new()
|
||||
{
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.BSensor,
|
||||
index = (ParentNote.location.index + 2) %
|
||||
8
|
||||
}),
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.BSensor,
|
||||
index = (ParentNote.location.index + 6) %
|
||||
8
|
||||
})
|
||||
},
|
||||
new()
|
||||
{
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.ASensor,
|
||||
index = (ParentNote.location.index + 5) %
|
||||
8
|
||||
}),
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.ASensor,
|
||||
index = (ParentNote.location.index + 3) %
|
||||
8
|
||||
}),
|
||||
TouchManager.GetCollider(new Location
|
||||
{
|
||||
group = NoteGroup.ASensor,
|
||||
index = (ParentNote.location.index + 4) %
|
||||
8
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void GenerateArrows()
|
||||
{
|
||||
var halfMargin = Mathf.RoundToInt(Margin / 2f);
|
||||
for (var i = 0;
|
||||
i < ArrowCount;
|
||||
i++)
|
||||
{
|
||||
var position = Vector3.Lerp(_startPosition,
|
||||
_endPositions[1],
|
||||
(float)(i + halfMargin) / (ArrowCount + Margin));
|
||||
|
||||
var arrow = SlideManager.fanSlideArrows.Get();
|
||||
arrow.Initialize(position,
|
||||
_ups[1],
|
||||
ArrowCount,
|
||||
i,
|
||||
isBreak,
|
||||
isEach);
|
||||
|
||||
AssignArrows(arrow, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssignArrows(FanSlideArrow arrow, int index)
|
||||
{
|
||||
_arrows.Add(arrow);
|
||||
|
||||
if (index < ArrowCount / 3f)
|
||||
arrow.interactionGroupIndex = 0;
|
||||
else if (index < ArrowCount * 2 / 3f)
|
||||
arrow.interactionGroupIndex = 1;
|
||||
else
|
||||
arrow.interactionGroupIndex = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AstroDX.Contexts.Gameplay.Behaviours.Slide.SlideMarkers;
|
||||
using AstroDX.Contexts.Gameplay.Interactions;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Contexts.Gameplay.SlideGenerators;
|
||||
using AstroDX.Globals;
|
||||
using Medicine;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.Behaviours.Slide.Handlers
|
||||
{
|
||||
public class RegularSlideSegmentHandler : SlideSegmentHandler
|
||||
{
|
||||
private readonly SlideGenerator _generator;
|
||||
|
||||
private readonly
|
||||
List<(TouchInteractable relevantSensor,
|
||||
List<RegularSlideArrow> relevantArrows)> _interactionPath = new();
|
||||
|
||||
/// <summary>
|
||||
/// Used in auto judgement
|
||||
/// </summary>
|
||||
private int _initialSensorCount;
|
||||
|
||||
/// <summary>
|
||||
/// Used in auto judgement
|
||||
/// </summary>
|
||||
private int _lastCheckedClearIndex;
|
||||
|
||||
private readonly SlideStarBehaviour _slideStar;
|
||||
|
||||
public RegularSlideSegmentHandler(SlideBehaviour slideBehaviour,
|
||||
SlideSegment segment,
|
||||
Location startLocation) :
|
||||
base(slideBehaviour)
|
||||
{
|
||||
var vertices = segment.vertices.ToList();
|
||||
vertices.Insert(0, startLocation);
|
||||
|
||||
_generator = SlideManager.GetGenerator(segment.slideType, vertices);
|
||||
|
||||
_slideStar = SlideManager.slideStars.Get();
|
||||
_slideStar.Initialize(ParentPath, ParentNote.parentCollection);
|
||||
|
||||
GenerateArrows();
|
||||
}
|
||||
|
||||
[Inject.Single]
|
||||
private SlideManager SlideManager { get; }
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _generator.GetLength();
|
||||
}
|
||||
|
||||
public override void OnUpdate(float segmentT)
|
||||
{
|
||||
if (IsJudgementTarget)
|
||||
CheckInteraction(segmentT);
|
||||
|
||||
UpdateSlideStarPosition(segmentT);
|
||||
UpdateArrowOpacity();
|
||||
}
|
||||
|
||||
private float _arrowOpacity;
|
||||
|
||||
private void UpdateArrowOpacity()
|
||||
{
|
||||
var timeSinceNoteStart = TimeSinceSlideStart + ParentPath.delay;
|
||||
|
||||
if (timeSinceNoteStart < 0)
|
||||
{
|
||||
_arrowOpacity = Mathf.InverseLerp(-Persistent.Settings.Gameplay.slideFadeInDuration,
|
||||
0,
|
||||
(float)timeSinceNoteStart);
|
||||
|
||||
_arrowOpacity = Mathf.Lerp(0, 0.8f, _arrowOpacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_arrowOpacity >= 1)
|
||||
return;
|
||||
|
||||
_arrowOpacity = 1;
|
||||
}
|
||||
|
||||
foreach (var (_, relevantArrows) in _interactionPath)
|
||||
{
|
||||
foreach (var arrow in relevantArrows)
|
||||
{
|
||||
arrow.SetAlpha(_arrowOpacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckInteraction(float segmentT)
|
||||
{
|
||||
var clearCount = GetClearCount(segmentT);
|
||||
|
||||
for (; clearCount > 0 && _interactionPath.Count > 0; clearCount--)
|
||||
{
|
||||
foreach (var slideArrow in _interactionPath[0].relevantArrows)
|
||||
slideArrow.ExitSequence();
|
||||
|
||||
_interactionPath.RemoveAt(0);
|
||||
}
|
||||
|
||||
Cleared = _interactionPath.Count == 0;
|
||||
}
|
||||
|
||||
public override float GetRemainingLength()
|
||||
{
|
||||
return (float)_interactionPath.Count / _initialSensorCount * GetLength();
|
||||
}
|
||||
|
||||
public override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
|
||||
foreach (var (_, arrows) in _interactionPath)
|
||||
foreach (var slideArrow in arrows)
|
||||
SlideManager.regularSlideArrows.Release(slideArrow);
|
||||
|
||||
SlideManager.slideStars.Release(_slideStar);
|
||||
}
|
||||
|
||||
public override void GetJudgementVector(out Vector2 position, out float rotation)
|
||||
{
|
||||
_generator.GetPoint(1, out position, out rotation);
|
||||
}
|
||||
|
||||
private void UpdateSlideStarPosition(float segmentT)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
var starTransform = _slideStar.transform;
|
||||
|
||||
if (segmentT >= 1 && IsLastSegment)
|
||||
{
|
||||
starTransform.localScale = Vector3.one * Persistent.Settings.Gameplay.noteScale;
|
||||
_slideStar.Appearance.Color = new Color(1, 1, 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
_generator.GetPoint(Mathf.Clamp01(segmentT), out var position, out var rotation);
|
||||
starTransform.SetLocalPositionAndRotation(position, Quaternion.Euler(0, 0, rotation * Mathf.Rad2Deg - 90));
|
||||
|
||||
if (TimeSinceSlideStart < 0 &&
|
||||
indexInSlide == 0 &&
|
||||
ParentNote.slideMorph != SlideMorph.SuddenIn)
|
||||
{
|
||||
var interpolation =
|
||||
Mathf.InverseLerp(-ParentPath.delay, 0,
|
||||
(float)TimeSinceSlideStart);
|
||||
starTransform.localScale = new Vector3(interpolation, interpolation) *
|
||||
Persistent.Settings.Gameplay.noteScale;
|
||||
_slideStar.Appearance.Color = new Color(1, 1, 1, interpolation);
|
||||
}
|
||||
|
||||
if (segmentT < 0)
|
||||
return;
|
||||
|
||||
starTransform.localScale = Vector3.one * Persistent.Settings.Gameplay.noteScale;
|
||||
_slideStar.Appearance.Color = Color.white;
|
||||
}
|
||||
|
||||
private void GenerateArrows()
|
||||
{
|
||||
var relevantArrows = new List<RegularSlideArrow>();
|
||||
TouchInteractable relevantSensor = null;
|
||||
|
||||
var totalLength = GetLength();
|
||||
|
||||
var arrowCount = Mathf.FloorToInt(totalLength / SlideManager.ArrowDistance);
|
||||
|
||||
for (var (distance, index) = (SlideManager.ArrowDistance, 0);
|
||||
distance <= totalLength;
|
||||
distance += SlideManager.ArrowDistance)
|
||||
{
|
||||
_generator.GetPoint(distance / totalLength, out var position, out var rotation);
|
||||
|
||||
var slideArrow = SlideManager.regularSlideArrows.Get();
|
||||
slideArrow.Initialize(position, rotation,
|
||||
arrowCount, index, isBreak, isEach);
|
||||
|
||||
var sensor = slideArrow.GetClosestSensor();
|
||||
|
||||
if (relevantSensor != sensor)
|
||||
{
|
||||
if (relevantSensor != null)
|
||||
_interactionPath.Add((relevantSensor, relevantArrows));
|
||||
|
||||
relevantArrows = new List<RegularSlideArrow>();
|
||||
relevantSensor = sensor;
|
||||
}
|
||||
|
||||
relevantArrows.Add(slideArrow);
|
||||
index++;
|
||||
}
|
||||
|
||||
if (relevantArrows.Count > 0) _interactionPath.Add((relevantSensor, relevantArrows));
|
||||
|
||||
_initialSensorCount = _interactionPath.Count;
|
||||
}
|
||||
|
||||
private int GetClearCount(double segmentT)
|
||||
{
|
||||
if (Persistent.Settings.Mods.Auto && segmentT >= 0)
|
||||
{
|
||||
var clearCount = Mathf.FloorToInt((float)(segmentT * _initialSensorCount));
|
||||
|
||||
|
||||
if (_lastCheckedClearIndex >= clearCount)
|
||||
return 0;
|
||||
|
||||
var lastCheckedClearIndex = _lastCheckedClearIndex;
|
||||
_lastCheckedClearIndex = clearCount;
|
||||
return clearCount - lastCheckedClearIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
var clearCount = 0;
|
||||
var scanIndex = 0;
|
||||
var tolerance = Persistent.Settings.Judgement.slideTolerance;
|
||||
|
||||
while (tolerance >= 0 && scanIndex < _interactionPath.Count)
|
||||
{
|
||||
if (_interactionPath[scanIndex].relevantSensor.GetSlideJudgement())
|
||||
{
|
||||
tolerance = Persistent.Settings.Judgement.slideTolerance;
|
||||
clearCount = scanIndex + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
tolerance--;
|
||||
}
|
||||
|
||||
scanIndex++;
|
||||
}
|
||||
|
||||
return clearCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Linq;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.Behaviours.Slide.Handlers
|
||||
{
|
||||
public abstract class SlideSegmentHandler
|
||||
{
|
||||
protected readonly bool isBreak;
|
||||
protected readonly bool isEach;
|
||||
|
||||
protected bool disposed;
|
||||
|
||||
protected int indexInSlide = -1;
|
||||
|
||||
protected SlideSegmentHandler(SlideBehaviour slideBehaviour)
|
||||
{
|
||||
disposed = false;
|
||||
|
||||
ParentSlide = slideBehaviour;
|
||||
|
||||
isEach = ParentSlide.ParentNote.parentCollection
|
||||
.Sum(n => n.slidePaths.Count) > 1;
|
||||
|
||||
isBreak = ParentSlide.Path.type == NoteType.Break;
|
||||
}
|
||||
|
||||
public bool Cleared { get; protected set; }
|
||||
|
||||
protected SlideBehaviour ParentSlide { get; }
|
||||
protected SlidePath ParentPath => ParentSlide.Path;
|
||||
protected Note ParentNote => ParentSlide.ParentNote;
|
||||
|
||||
protected double TimeSinceSlideStart => ParentSlide.TimeSinceStart;
|
||||
protected bool IsLastSegment => indexInSlide != ParentPath.segments.Count - 1;
|
||||
public bool IsJudgementTarget { get; set; }
|
||||
|
||||
public void SetIndex(int index)
|
||||
{
|
||||
indexInSlide = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the position and the up vector for the judgement text.
|
||||
/// </summary>
|
||||
public abstract void GetJudgementVector(out Vector2 position, out float rotation);
|
||||
|
||||
public abstract void OnUpdate(float segmentT);
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public abstract float GetLength();
|
||||
|
||||
public abstract float GetRemainingLength();
|
||||
|
||||
public static SlideSegmentHandler Recommend(SlideBehaviour slideBehaviour,
|
||||
SlideSegment segment,
|
||||
Location startLocation)
|
||||
{
|
||||
return segment.slideType switch
|
||||
{
|
||||
SlideType.Fan => new FanSlideSegmentHandler(slideBehaviour, segment, startLocation),
|
||||
_ => new RegularSlideSegmentHandler(slideBehaviour, segment, startLocation)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
190
core-dump/Scripts/Contexts/Gameplay/Behaviours/SlideBehaviour.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.Behaviours.Slide.Handlers;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Contexts.Gameplay.SceneScope;
|
||||
using AstroDX.Globals;
|
||||
using Medicine;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
using TimeSpan = AstroDX.Models.Scoring.TimeSpan;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.Behaviours.Slide
|
||||
{
|
||||
public sealed class SlideBehaviour : MonoBehaviour, IDisposable
|
||||
{
|
||||
private readonly List<(float distance, SlideSegmentHandler handler)> _segments = new();
|
||||
private float _endRotation;
|
||||
|
||||
private Vector2 _endPosition;
|
||||
private SlideType _endSegmentType;
|
||||
private bool _judged;
|
||||
|
||||
private int _judgedSegmentCount;
|
||||
|
||||
private float _length;
|
||||
|
||||
private float _slideStartTime;
|
||||
|
||||
[Inject.Single]
|
||||
private StatisticsManager StatisticsManager { get; }
|
||||
|
||||
[Inject.Single]
|
||||
private MusicManager MusicManager { get; }
|
||||
|
||||
[Inject.Single]
|
||||
private SlideManager SlideManager { get; }
|
||||
|
||||
public SlidePath Path { get; private set; }
|
||||
public Note ParentNote { get; private set; }
|
||||
|
||||
public double TimeSinceStart => MusicManager.Time - _slideStartTime;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
UpdateSegments();
|
||||
|
||||
if (TimeSinceStart >= 0)
|
||||
CheckInteraction();
|
||||
|
||||
if (Persistent.Settings.Mods.Auto &&
|
||||
TimeSinceStart > Path.duration)
|
||||
Judge();
|
||||
else if (TimeSinceStart > Path.duration +
|
||||
TimeSpan.SlideFinish.TimingWindows[^1].lateSpan)
|
||||
Judge();
|
||||
}
|
||||
|
||||
public void Init(in SlidePath path, in Note parent)
|
||||
{
|
||||
_judgedSegmentCount = 0;
|
||||
_judged = false;
|
||||
|
||||
Path = path;
|
||||
ParentNote = parent;
|
||||
_slideStartTime = ParentNote.parentCollection.time + Path.delay;
|
||||
|
||||
GenerateHandlers(path);
|
||||
}
|
||||
|
||||
private void UpdateSegments()
|
||||
{
|
||||
var t = (float)TimeSinceStart / Path.duration * _length;
|
||||
|
||||
var isJudgementTarget = TimeSinceStart + Path.delay >= 0;
|
||||
|
||||
for (var i = 0; i < _segments.Count; i++)
|
||||
{
|
||||
var (segmentStartT, handler) = _segments[i];
|
||||
var segmentEndT = i + 1 < _segments.Count ? _segments[i + 1].distance : _length;
|
||||
|
||||
var segmentT = (t - segmentStartT) / (segmentEndT - segmentStartT);
|
||||
|
||||
handler.IsJudgementTarget = isJudgementTarget;
|
||||
handler.OnUpdate(segmentT);
|
||||
|
||||
if (!handler.Cleared)
|
||||
isJudgementTarget = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckInteraction()
|
||||
{
|
||||
while (_judgedSegmentCount < _segments.Count)
|
||||
{
|
||||
var currentHandler = _segments[_judgedSegmentCount].handler;
|
||||
|
||||
var segmentCleared = currentHandler.Cleared;
|
||||
|
||||
if (!segmentCleared)
|
||||
return;
|
||||
|
||||
_judgedSegmentCount++;
|
||||
}
|
||||
|
||||
Judge();
|
||||
}
|
||||
|
||||
private void GenerateHandlers(in SlidePath slidePath)
|
||||
{
|
||||
if (_segments.Count > 0)
|
||||
_segments.Clear();
|
||||
|
||||
var totalDistance = 0f;
|
||||
|
||||
var handlers = new List<SlideSegmentHandler>();
|
||||
|
||||
for (var i = 0; i < slidePath.segments.Count; i++)
|
||||
{
|
||||
var segment = slidePath.segments[i];
|
||||
|
||||
var startLocation = i > 0 ? slidePath.segments[i - 1].vertices[^1] : ParentNote.location;
|
||||
|
||||
var handler = SlideSegmentHandler.Recommend(this, segment, startLocation);
|
||||
handlers.Add(handler);
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
foreach (var segmentHandler in handlers)
|
||||
{
|
||||
segmentHandler.SetIndex(index);
|
||||
_segments.Add((totalDistance, segmentHandler));
|
||||
totalDistance += segmentHandler.GetLength();
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
_segments[^1].handler.GetJudgementVector(out _endPosition, out _endRotation);
|
||||
_endSegmentType = Path.segments[^1].slideType;
|
||||
|
||||
_length = totalDistance;
|
||||
}
|
||||
|
||||
private void Judge()
|
||||
{
|
||||
if (_judged)
|
||||
return;
|
||||
|
||||
_judged = true;
|
||||
|
||||
var incomplete = _judgedSegmentCount < _segments.Count;
|
||||
|
||||
var startTime = ParentNote.parentCollection.time +
|
||||
Path.delay;
|
||||
|
||||
var timeFromEnd = MusicManager.Time - (startTime + Path.duration);
|
||||
|
||||
var distanceToEnd = incomplete
|
||||
? _segments[_judgedSegmentCount].handler.GetRemainingLength()
|
||||
: _length - Mathf.InverseLerp(startTime,
|
||||
startTime + Path.duration,
|
||||
(float)MusicManager.Time) * _length;
|
||||
|
||||
if (incomplete)
|
||||
{
|
||||
var multipleSegmentsRemaining = _judgedSegmentCount + 1 < _segments.Count;
|
||||
|
||||
if (multipleSegmentsRemaining)
|
||||
distanceToEnd += _length - _segments[_judgedSegmentCount + 1].distance;
|
||||
}
|
||||
|
||||
StatisticsManager.TallySlide(Path,
|
||||
distanceToEnd,
|
||||
timeFromEnd,
|
||||
_endPosition,
|
||||
_endRotation,
|
||||
_endSegmentType,
|
||||
incomplete);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var segment in _segments)
|
||||
segment.handler.OnDestroy();
|
||||
|
||||
SlideManager.slides.Release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class CurveCcwGenerator : SlideGenerator
|
||||
{
|
||||
private const float CurveRadius = RenderManager.CenterRadius;
|
||||
private const float RingRadius = RenderManager.PlayFieldRadius;
|
||||
private readonly float _curveLength;
|
||||
private readonly float _endForward;
|
||||
private readonly Vector2 _endPoint;
|
||||
|
||||
private readonly float _startForward;
|
||||
|
||||
private readonly float _startLength;
|
||||
|
||||
private readonly Vector2 _startPoint;
|
||||
private readonly Vector2 _tangentInPoint;
|
||||
private readonly float _tangentInRotation;
|
||||
private readonly Vector2 _tangentOutPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public CurveCcwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
_tangentInRotation = startRotation +
|
||||
Trigonometry.GetTangentAngleDelta(CurveRadius, RingRadius, false);
|
||||
var tangentOutRotation = endRotation +
|
||||
Trigonometry.GetTangentAngleDelta(CurveRadius, RingRadius, true);
|
||||
|
||||
_startPoint = GetPositionRadial(startRotation);
|
||||
_tangentInPoint = GetPositionRadial(_tangentInRotation, CurveRadius);
|
||||
_tangentOutPoint = GetPositionRadial(tangentOutRotation, CurveRadius);
|
||||
_endPoint = GetPositionRadial(endRotation);
|
||||
|
||||
var startSegment = _tangentInPoint - _startPoint;
|
||||
_startLength = startSegment.magnitude;
|
||||
_startForward = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_curveLength = Trigonometry.GetAngleSpan(_tangentInRotation, tangentOutRotation,
|
||||
false) * CurveRadius;
|
||||
|
||||
var endSegment = _endPoint - _tangentOutPoint;
|
||||
var endLength = endSegment.magnitude;
|
||||
_endForward = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startLength + _curveLength + endLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint,
|
||||
_tangentInPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _startForward;
|
||||
}
|
||||
else if (distanceFromStart < _startLength + _curveLength)
|
||||
{
|
||||
var localT = Mathf.InverseLerp(_startLength, _startLength + _curveLength, distanceFromStart);
|
||||
position = new Vector2(Mathf.Cos(_tangentInRotation + _curveLength / CurveRadius * localT) *
|
||||
CurveRadius,
|
||||
Mathf.Sin(_tangentInRotation + _curveLength / CurveRadius * localT) *
|
||||
CurveRadius);
|
||||
|
||||
var forward = position.Rotate(Trigonometry.Tau / 4);
|
||||
rotation = Mathf.Atan2(forward.y, forward.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_tangentOutPoint,
|
||||
_endPoint,
|
||||
Mathf.InverseLerp(_startLength + _curveLength,
|
||||
_totalLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _endForward;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class CurveCwGenerator : SlideGenerator
|
||||
{
|
||||
private const float CurveRadius = RenderManager.CenterRadius;
|
||||
private const float RingRadius = RenderManager.PlayFieldRadius;
|
||||
private readonly float _curveLength;
|
||||
private readonly float _endForward;
|
||||
private readonly Vector2 _endPoint;
|
||||
|
||||
private readonly float _startForward;
|
||||
|
||||
private readonly float _startLength;
|
||||
|
||||
private readonly Vector2 _startPoint;
|
||||
private readonly Vector2 _tangentInPoint;
|
||||
private readonly float _tangentInRotation;
|
||||
private readonly Vector2 _tangentOutPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public CurveCwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
_tangentInRotation = startRotation +
|
||||
Trigonometry.GetTangentAngleDelta(CurveRadius, RingRadius, true);
|
||||
var tangentOutRotation = endRotation +
|
||||
Trigonometry.GetTangentAngleDelta(CurveRadius, RingRadius, false);
|
||||
|
||||
_startPoint = GetPositionRadial(startRotation);
|
||||
_tangentInPoint = GetPositionRadial(_tangentInRotation, CurveRadius);
|
||||
_tangentOutPoint = GetPositionRadial(tangentOutRotation, CurveRadius);
|
||||
_endPoint = GetPositionRadial(endRotation);
|
||||
|
||||
var startSegment = _tangentInPoint - _startPoint;
|
||||
_startLength = startSegment.magnitude;
|
||||
_startForward = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_curveLength = Trigonometry.GetAngleSpan(_tangentInRotation, tangentOutRotation,
|
||||
true) * CurveRadius;
|
||||
|
||||
var endSegment = _endPoint - _tangentOutPoint;
|
||||
var endLength = endSegment.magnitude;
|
||||
_endForward = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startLength + _curveLength + endLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint,
|
||||
_tangentInPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _startForward;
|
||||
}
|
||||
else if (distanceFromStart < _startLength + _curveLength)
|
||||
{
|
||||
var localT = Mathf.InverseLerp(_startLength, _startLength + _curveLength, distanceFromStart);
|
||||
position = new Vector2(Mathf.Cos(_tangentInRotation - _curveLength / CurveRadius * localT) *
|
||||
CurveRadius,
|
||||
Mathf.Sin(_tangentInRotation - _curveLength / CurveRadius * localT) *
|
||||
CurveRadius);
|
||||
|
||||
var forward = position.Rotate(-Trigonometry.Tau / 4);
|
||||
rotation = Mathf.Atan2(forward.y, forward.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_tangentOutPoint,
|
||||
_endPoint,
|
||||
Mathf.InverseLerp(_startLength + _curveLength,
|
||||
_totalLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _endForward;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class EdgeCurveCcwGenerator : SlideGenerator
|
||||
{
|
||||
private const float CurveRadius = RenderManager.CenterRadius * 1.2f;
|
||||
private const float CenterAngularOffset = Trigonometry.Tau / 4 - Trigonometry.Tau / 16;
|
||||
private const float CenterRadialOffset = RenderManager.PlayFieldRadius * 0.4662f;
|
||||
|
||||
private readonly Vector2 _centerPosition;
|
||||
private readonly float _curveLength;
|
||||
private readonly Vector2 _endPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
private readonly float _endRotation;
|
||||
|
||||
private readonly float _startLength;
|
||||
private readonly Vector2 _startPoint;
|
||||
private readonly Vector2 _tangentInPoint;
|
||||
private readonly float _tangentInRotation;
|
||||
private readonly Vector2 _tangentOutPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public EdgeCurveCcwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
var centerAngle = startRotation - CenterAngularOffset;
|
||||
_centerPosition = new Vector2(CenterRadialOffset * Mathf.Cos(centerAngle),
|
||||
CenterRadialOffset * Mathf.Sin(centerAngle));
|
||||
|
||||
_startPoint = GetPositionRadial(startRotation);
|
||||
|
||||
var relativeStartRotation = Trigonometry.ToPolarAngle(_startPoint, _centerPosition);
|
||||
|
||||
var magnitude = (_centerPosition - _startPoint).magnitude;
|
||||
var startDelta = Trigonometry.GetTangentAngleDelta(CurveRadius, magnitude, false);
|
||||
|
||||
_tangentInRotation = relativeStartRotation + startDelta;
|
||||
_tangentInPoint = GetPositionRadial(_tangentInRotation, CurveRadius) +
|
||||
_centerPosition;
|
||||
|
||||
_endPoint = GetPositionRadial(endRotation);
|
||||
|
||||
var relativeEndRotation = Trigonometry.ToPolarAngle(_endPoint, _centerPosition);
|
||||
var endMagnitude = (_endPoint - _centerPosition).magnitude;
|
||||
var endDelta = Trigonometry.GetTangentAngleDelta(CurveRadius, endMagnitude, true);
|
||||
|
||||
var tangentOutRotation = relativeEndRotation + endDelta;
|
||||
_tangentOutPoint = GetPositionRadial(tangentOutRotation, CurveRadius) +
|
||||
_centerPosition;
|
||||
|
||||
var startSegment = _tangentInPoint - _startPoint;
|
||||
_startLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_curveLength = Trigonometry.GetAngleSpan(_tangentInRotation, tangentOutRotation,
|
||||
false, Trigonometry.Tau / 4f) * CurveRadius;
|
||||
|
||||
var endSegment = _endPoint - _tangentOutPoint;
|
||||
var endLength = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startLength + _curveLength + endLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint,
|
||||
_tangentInPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else if (distanceFromStart < _startLength + _curveLength)
|
||||
{
|
||||
var localT = Mathf.InverseLerp(_startLength, _startLength + _curveLength, distanceFromStart);
|
||||
position = new Vector2(Mathf.Cos(_tangentInRotation + _curveLength / CurveRadius * localT) *
|
||||
CurveRadius,
|
||||
Mathf.Sin(_tangentInRotation + _curveLength / CurveRadius * localT) *
|
||||
CurveRadius);
|
||||
|
||||
var forward = position.Rotate(Trigonometry.Tau / 4);
|
||||
rotation = Mathf.Atan2(forward.y, forward.x);
|
||||
position += _centerPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_tangentOutPoint,
|
||||
_endPoint,
|
||||
Mathf.InverseLerp(_startLength + _curveLength,
|
||||
_totalLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class EdgeCurveCwGenerator : SlideGenerator
|
||||
{
|
||||
private const float CurveRadius = RenderManager.CenterRadius * 1.2f;
|
||||
private const float CenterAngularOffset = Trigonometry.Tau / 4 - Trigonometry.Tau / 16;
|
||||
private const float CenterRadialOffset = RenderManager.PlayFieldRadius * 0.4662f;
|
||||
|
||||
private readonly Vector2 _centerPosition;
|
||||
private readonly float _curveLength;
|
||||
private readonly Vector2 _endPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
private readonly float _endRotation;
|
||||
|
||||
private readonly float _startLength;
|
||||
private readonly Vector2 _startPoint;
|
||||
private readonly Vector2 _tangentInPoint;
|
||||
private readonly float _tangentInRotation;
|
||||
private readonly Vector2 _tangentOutPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public EdgeCurveCwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
var centerAngle = startRotation + CenterAngularOffset;
|
||||
_centerPosition = new Vector2(CenterRadialOffset * Mathf.Cos(centerAngle),
|
||||
CenterRadialOffset * Mathf.Sin(centerAngle));
|
||||
|
||||
_startPoint = GetPositionRadial(startRotation);
|
||||
|
||||
var relativeStartRotation = Trigonometry.ToPolarAngle(_startPoint, _centerPosition);
|
||||
|
||||
var magnitude = (_centerPosition - _startPoint).magnitude;
|
||||
var startDelta = Trigonometry.GetTangentAngleDelta(CurveRadius, magnitude, true);
|
||||
|
||||
_tangentInRotation = relativeStartRotation + startDelta;
|
||||
_tangentInPoint = GetPositionRadial(_tangentInRotation, CurveRadius) +
|
||||
_centerPosition;
|
||||
|
||||
_endPoint = GetPositionRadial(endRotation);
|
||||
|
||||
var relativeEndRotation = Trigonometry.ToPolarAngle(_endPoint, _centerPosition);
|
||||
var endMagnitude = (_endPoint - _centerPosition).magnitude;
|
||||
var endDelta = Trigonometry.GetTangentAngleDelta(CurveRadius, endMagnitude, false);
|
||||
|
||||
var tangentOutRotation = relativeEndRotation + endDelta;
|
||||
_tangentOutPoint = GetPositionRadial(tangentOutRotation, CurveRadius) +
|
||||
_centerPosition;
|
||||
|
||||
var startSegment = _tangentInPoint - _startPoint;
|
||||
_startLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_curveLength = Trigonometry.GetAngleSpan(_tangentInRotation, tangentOutRotation,
|
||||
true, Trigonometry.Tau / 4f) * CurveRadius;
|
||||
|
||||
var endSegment = _endPoint - _tangentOutPoint;
|
||||
var endLength = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startLength + _curveLength + endLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint,
|
||||
_tangentInPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else if (distanceFromStart < _startLength + _curveLength)
|
||||
{
|
||||
var localT = Mathf.InverseLerp(_startLength, _startLength + _curveLength, distanceFromStart);
|
||||
position = new Vector2(Mathf.Cos(_tangentInRotation - _curveLength / CurveRadius * localT) *
|
||||
CurveRadius,
|
||||
Mathf.Sin(_tangentInRotation - _curveLength / CurveRadius * localT) *
|
||||
CurveRadius);
|
||||
|
||||
var forward = position.Rotate(-Trigonometry.Tau / 4);
|
||||
rotation = Mathf.Atan2(forward.y, forward.x);
|
||||
position += _centerPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_tangentOutPoint,
|
||||
_endPoint,
|
||||
Mathf.InverseLerp(_startLength + _curveLength,
|
||||
_totalLength,
|
||||
distanceFromStart));
|
||||
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class EdgeFoldGenerator : SlideGenerator
|
||||
{
|
||||
private readonly Vector2 _endPoint;
|
||||
private readonly Vector2 _midPoint;
|
||||
private readonly Vector2 _startPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
private readonly float _endRotation;
|
||||
|
||||
private readonly float _startSegmentLength;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public EdgeFoldGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
_startPoint = GetPosition(vertices[0]);
|
||||
_midPoint = GetPosition(vertices[1]);
|
||||
_endPoint = GetPosition(vertices[2]);
|
||||
|
||||
var startSegment = _midPoint - _startPoint;
|
||||
_startSegmentLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
var endSegment = _endPoint - _midPoint;
|
||||
var endSegmentSpan = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startSegmentLength + endSegmentSpan;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startSegmentLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint, _midPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startSegmentLength / _totalLength,
|
||||
t));
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_midPoint, _endPoint,
|
||||
Mathf.InverseLerp(_startSegmentLength / _totalLength,
|
||||
1,
|
||||
t));
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class FoldGenerator : SlideGenerator
|
||||
{
|
||||
private readonly Vector2 _endPoint;
|
||||
private readonly float _endRotation;
|
||||
private readonly Vector2 _midPoint;
|
||||
private readonly Vector2 _startPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
|
||||
private readonly float _startSegmentLength;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public FoldGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
_startPoint = GetPosition(vertices[0]);
|
||||
_endPoint = GetPosition(vertices[1]);
|
||||
_midPoint = Vector2.zero;
|
||||
|
||||
var startSegment = _midPoint - _startPoint;
|
||||
_startSegmentLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
var endSegment = _endPoint - _midPoint;
|
||||
var endSegmentSpan = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startSegmentLength + endSegmentSpan;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startSegmentLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint, _midPoint,
|
||||
Mathf.InverseLerp(0,
|
||||
_startSegmentLength / _totalLength,
|
||||
t));
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
position = Vector2.Lerp(_midPoint, _endPoint,
|
||||
Mathf.InverseLerp(_startSegmentLength / _totalLength,
|
||||
1,
|
||||
t));
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class RingCcwGenerator : SlideGenerator
|
||||
{
|
||||
private readonly float _angleSpan;
|
||||
private readonly float _endRadius;
|
||||
private readonly float _length;
|
||||
|
||||
private readonly float _startRadius;
|
||||
private readonly float _startRotation;
|
||||
|
||||
public RingCcwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var inPosition = GetPosition(vertices[0]);
|
||||
var inRadians = Trigonometry.ToPolarAngle(inPosition);
|
||||
|
||||
var outPosition = GetPosition(vertices[1]);
|
||||
var outRadians = Trigonometry.ToPolarAngle(outPosition);
|
||||
|
||||
_startRotation = inRadians;
|
||||
|
||||
_angleSpan = Trigonometry.GetAngleSpan(inRadians, outRadians, false);
|
||||
_startRadius = GetRadiusFromCenter(vertices[0]);
|
||||
_endRadius = GetRadiusFromCenter(vertices[1]);
|
||||
|
||||
_length = _angleSpan * (_startRadius + _endRadius) / 2;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t,
|
||||
out Vector2 position,
|
||||
out float rotation)
|
||||
{
|
||||
var radiusAtT = Mathf.Lerp(_startRadius, _endRadius, t);
|
||||
var rotationAtT = _startRotation + _angleSpan * t;
|
||||
|
||||
position = new Vector2(Mathf.Cos(rotationAtT) * radiusAtT,
|
||||
Mathf.Sin(rotationAtT) * radiusAtT);
|
||||
|
||||
rotation = rotationAtT + Trigonometry.Tau / 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class RingCwGenerator : SlideGenerator
|
||||
{
|
||||
private readonly float _angleSpan;
|
||||
private readonly float _endRadius;
|
||||
private readonly float _length;
|
||||
|
||||
private readonly float _startRadius;
|
||||
private readonly float _startRotation;
|
||||
|
||||
public RingCwGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
var inPosition = GetPosition(vertices[0]);
|
||||
var inRadians = Trigonometry.ToPolarAngle(inPosition);
|
||||
|
||||
var outPosition = GetPosition(vertices[1]);
|
||||
var outRadians = Trigonometry.ToPolarAngle(outPosition);
|
||||
|
||||
_startRotation = inRadians;
|
||||
|
||||
_angleSpan = Trigonometry.GetAngleSpan(_startRotation, outRadians, true);
|
||||
_startRadius = GetRadiusFromCenter(vertices[0]);
|
||||
_endRadius = GetRadiusFromCenter(vertices[1]);
|
||||
|
||||
_length = _angleSpan * (_startRadius + _endRadius) / 2;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t,
|
||||
out Vector2 position,
|
||||
out float rotation)
|
||||
{
|
||||
var radiusAtT = Mathf.Lerp(_startRadius, _endRadius, t);
|
||||
var rotationAtT = _startRotation - _angleSpan * t;
|
||||
|
||||
position = new Vector2(Mathf.Cos(rotationAtT) * radiusAtT,
|
||||
Mathf.Sin(rotationAtT) * radiusAtT);
|
||||
|
||||
rotation = rotationAtT - Trigonometry.Tau / 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public abstract class SlideGenerator
|
||||
{
|
||||
public abstract float GetLength();
|
||||
|
||||
public abstract void GetPoint(float t, out Vector2 position, out float rotation);
|
||||
|
||||
protected static float GetRotation(in Location location)
|
||||
{
|
||||
const float initialAngle = Trigonometry.Tau / 4f - Trigonometry.Tau / 16f;
|
||||
|
||||
var angle = initialAngle - Trigonometry.Tau / 8f * location.index;
|
||||
|
||||
if (location.group is NoteGroup.DSensor or NoteGroup.ESensor) angle += Trigonometry.Tau / 16f;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
public static Vector2 GetPosition(in Location location)
|
||||
{
|
||||
var radius = GetRadiusFromCenter(location);
|
||||
|
||||
return GetPositionRadial(GetRotation(location), radius);
|
||||
}
|
||||
|
||||
protected static Vector2 GetPositionRadial(in float rotationRadians,
|
||||
in float radius = RenderManager.PlayFieldRadius)
|
||||
{
|
||||
return new Vector2(Mathf.Cos(rotationRadians) * radius,
|
||||
Mathf.Sin(rotationRadians) * radius);
|
||||
}
|
||||
|
||||
protected static float GetRadiusFromCenter(Location location)
|
||||
{
|
||||
return location.group switch
|
||||
{
|
||||
NoteGroup.Tap => RenderManager.PlayFieldRadius,
|
||||
NoteGroup.ASensor => RenderManager.AreaARadius,
|
||||
NoteGroup.BSensor => RenderManager.AreaBRadius,
|
||||
NoteGroup.CSensor => 0,
|
||||
NoteGroup.DSensor => RenderManager.AreaDRadius,
|
||||
NoteGroup.ESensor => RenderManager.AreaERadius,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class StraightGenerator : SlideGenerator
|
||||
{
|
||||
private readonly Vector2 _endPoint;
|
||||
private readonly float _rotation;
|
||||
private readonly float _length;
|
||||
private readonly Vector2 _startPoint;
|
||||
|
||||
public StraightGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
_startPoint = GetPosition(vertices[0]);
|
||||
_endPoint = GetPosition(vertices[1]);
|
||||
|
||||
var segment = _endPoint - _startPoint;
|
||||
_length = segment.magnitude;
|
||||
_rotation = Mathf.Atan2(segment.y, segment.x);
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint, _endPoint, t);
|
||||
rotation = _rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class ZigZagSGenerator : SlideGenerator
|
||||
{
|
||||
private readonly Vector2 _endPoint;
|
||||
private readonly Vector2 _endZagPoint;
|
||||
private readonly float _midSegmentLength;
|
||||
private readonly Vector2 _startPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
private readonly float _midRotation;
|
||||
private readonly float _endRotation;
|
||||
|
||||
private readonly float _startSegmentLength;
|
||||
private readonly Vector2 _startZagPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public ZigZagSGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
const float distance = RenderManager.PlayFieldRadius;
|
||||
const float inner = RenderManager.CenterRadius;
|
||||
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
var startZag = startRotation + Trigonometry.Tau / 4f;
|
||||
var endZag = endRotation + Trigonometry.Tau / 4f;
|
||||
|
||||
_startPoint = new Vector2(distance * Mathf.Cos(startRotation),
|
||||
distance * Mathf.Sin(startRotation));
|
||||
|
||||
_startZagPoint = new Vector2(inner * Mathf.Cos(startZag),
|
||||
inner * Mathf.Sin(startZag));
|
||||
|
||||
var startSegment = _startZagPoint - _startPoint;
|
||||
_startSegmentLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_endZagPoint = new Vector2(inner * Mathf.Cos(endZag),
|
||||
inner * Mathf.Sin(endZag));
|
||||
|
||||
var midSegment = _endZagPoint - _startZagPoint;
|
||||
_midSegmentLength = midSegment.magnitude;
|
||||
_midRotation = Mathf.Atan2(midSegment.y, midSegment.x);
|
||||
|
||||
_endPoint = new Vector2(distance * Mathf.Cos(endRotation),
|
||||
distance * Mathf.Sin(endRotation));
|
||||
|
||||
var endSegment = _endPoint - _endZagPoint;
|
||||
var endSegmentLength = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startSegmentLength + _midSegmentLength + endSegmentLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startSegmentLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint, _startZagPoint,
|
||||
Mathf.InverseLerp(0, _startSegmentLength, distanceFromStart));
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else if (distanceFromStart < _startSegmentLength + _midSegmentLength)
|
||||
{
|
||||
var midLength = _startSegmentLength + _midSegmentLength;
|
||||
|
||||
position = Vector2.Lerp(_startZagPoint, _endZagPoint,
|
||||
Mathf.InverseLerp(_startSegmentLength, midLength, distanceFromStart));
|
||||
rotation = _midRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var midLength = _startSegmentLength + _midSegmentLength;
|
||||
|
||||
position = Vector2.Lerp(_endZagPoint, _endPoint,
|
||||
Mathf.InverseLerp(midLength, _totalLength, distanceFromStart));
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.Contexts.Gameplay.PlayerScope;
|
||||
using AstroDX.Utilities;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Contexts.Gameplay.SlideGenerators
|
||||
{
|
||||
public sealed class ZigZagZGenerator : SlideGenerator
|
||||
{
|
||||
private readonly Vector2 _endPoint;
|
||||
private readonly Vector2 _endZagPoint;
|
||||
private readonly float _midSegmentLength;
|
||||
private readonly Vector2 _startPoint;
|
||||
|
||||
private readonly float _startRotation;
|
||||
private readonly float _midRotation;
|
||||
private readonly float _endRotation;
|
||||
|
||||
private readonly float _startSegmentLength;
|
||||
private readonly Vector2 _startZagPoint;
|
||||
private readonly float _totalLength;
|
||||
|
||||
public ZigZagZGenerator(IReadOnlyList<Location> vertices)
|
||||
{
|
||||
const float distance = RenderManager.PlayFieldRadius;
|
||||
const float inner = RenderManager.CenterRadius;
|
||||
|
||||
var startRotation = GetRotation(vertices[0]);
|
||||
var endRotation = GetRotation(vertices[1]);
|
||||
|
||||
var startZag = startRotation - Trigonometry.Tau / 4f;
|
||||
var endZag = endRotation - Trigonometry.Tau / 4f;
|
||||
|
||||
_startPoint = new Vector2(distance * Mathf.Cos(startRotation),
|
||||
distance * Mathf.Sin(startRotation));
|
||||
|
||||
_startZagPoint = new Vector2(inner * Mathf.Cos(startZag),
|
||||
inner * Mathf.Sin(startZag));
|
||||
|
||||
var startSegment = _startZagPoint - _startPoint;
|
||||
_startSegmentLength = startSegment.magnitude;
|
||||
_startRotation = Mathf.Atan2(startSegment.y, startSegment.x);
|
||||
|
||||
_endZagPoint = new Vector2(inner * Mathf.Cos(endZag),
|
||||
inner * Mathf.Sin(endZag));
|
||||
|
||||
var midSegment = _endZagPoint - _startZagPoint;
|
||||
_midSegmentLength = midSegment.magnitude;
|
||||
_midRotation = Mathf.Atan2(midSegment.y, midSegment.x);
|
||||
|
||||
_endPoint = new Vector2(distance * Mathf.Cos(endRotation),
|
||||
distance * Mathf.Sin(endRotation));
|
||||
|
||||
var endSegment = _endPoint - _endZagPoint;
|
||||
var endSegmentLength = endSegment.magnitude;
|
||||
_endRotation = Mathf.Atan2(endSegment.y, endSegment.x);
|
||||
|
||||
_totalLength = _startSegmentLength + _midSegmentLength + endSegmentLength;
|
||||
}
|
||||
|
||||
public override float GetLength()
|
||||
{
|
||||
return _totalLength;
|
||||
}
|
||||
|
||||
public override void GetPoint(float t, out Vector2 position, out float rotation)
|
||||
{
|
||||
var distanceFromStart = t * _totalLength;
|
||||
|
||||
if (distanceFromStart < _startSegmentLength)
|
||||
{
|
||||
position = Vector2.Lerp(_startPoint, _startZagPoint,
|
||||
Mathf.InverseLerp(0, _startSegmentLength, distanceFromStart));
|
||||
rotation = _startRotation;
|
||||
}
|
||||
else if (distanceFromStart < _startSegmentLength + _midSegmentLength)
|
||||
{
|
||||
var midLength = _startSegmentLength + _midSegmentLength;
|
||||
|
||||
position = Vector2.Lerp(_startZagPoint, _endZagPoint,
|
||||
Mathf.InverseLerp(_startSegmentLength, midLength, distanceFromStart));
|
||||
rotation = _midRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var midLength = _startSegmentLength + _midSegmentLength;
|
||||
|
||||
position = Vector2.Lerp(_endZagPoint, _endPoint,
|
||||
Mathf.InverseLerp(midLength, _totalLength, distanceFromStart));
|
||||
rotation = _endRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using SimaiSharp.Structures;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Extended
|
||||
{
|
||||
public class HealthStats : IReactiveStatistic
|
||||
{
|
||||
public HealthStats(int maxHealth)
|
||||
{
|
||||
MaxHealth = maxHealth;
|
||||
}
|
||||
|
||||
public void Push(in NoteType type, in JudgeData data)
|
||||
{
|
||||
Loss += GameSettings.Settings.Profile.Mods.LifeDrain.GetHealthLoss(type, data);
|
||||
}
|
||||
|
||||
public int MaxHealth { get; }
|
||||
public int Loss { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using SimaiSharp.Structures;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics
|
||||
{
|
||||
public interface IReactiveStatistic
|
||||
{
|
||||
void Push(in NoteType type, in JudgeData data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Internal
|
||||
{
|
||||
public sealed class AchievementStats
|
||||
{
|
||||
private readonly Statistics _statistics;
|
||||
private readonly double _baseScore;
|
||||
|
||||
public AchievementStats(Statistics statistics)
|
||||
{
|
||||
_statistics = statistics;
|
||||
|
||||
var totalPointCount = statistics.JudgementStats.MaxTapCount +
|
||||
statistics.JudgementStats.MaxTouchCount +
|
||||
statistics.JudgementStats.MaxHoldCount * 2 +
|
||||
statistics.JudgementStats.MaxSlideCount * 3 +
|
||||
statistics.JudgementStats.MaxBreakCount * 5;
|
||||
|
||||
_baseScore = totalPointCount > 0 ? 100.00 / totalPointCount : 0;
|
||||
}
|
||||
|
||||
/// <param name="precise">Used when displaying achievement as text to avoid double rounding</param>
|
||||
public double GetAchievement(bool precise = false)
|
||||
{
|
||||
var baseAchievement = GetBaseAchievement();
|
||||
var extraAchievement = _statistics.JudgementStats.BreakRecord.MaxCount > 0
|
||||
? _statistics.JudgementStats.BreakRecord.Extras /
|
||||
_statistics.JudgementStats.BreakRecord.MaxCount
|
||||
: 0;
|
||||
|
||||
var totalAchievement = baseAchievement +
|
||||
extraAchievement;
|
||||
|
||||
return precise ? totalAchievement : Math.Round(totalAchievement, 4);
|
||||
}
|
||||
|
||||
private double GetBaseAchievement()
|
||||
{
|
||||
if (_baseScore == 0)
|
||||
return 0;
|
||||
|
||||
var tapAchievement = _statistics.JudgementStats.TapRecord.Points
|
||||
* _baseScore;
|
||||
|
||||
var touchAchievement = _statistics.JudgementStats.TouchRecord.Points
|
||||
* _baseScore;
|
||||
|
||||
var holdAchievement = _statistics.JudgementStats.HoldRecord.Points
|
||||
* _baseScore * 2;
|
||||
|
||||
var slideAchievement = _statistics.JudgementStats.SlideRecord.Points
|
||||
* _baseScore * 3;
|
||||
|
||||
var breakAchievement = _statistics.JudgementStats.BreakRecord.Points
|
||||
* _baseScore * 5;
|
||||
|
||||
return tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement;
|
||||
}
|
||||
|
||||
public double CalculateMaxAchievable(bool includeExtras)
|
||||
{
|
||||
var tapAchievement = _statistics.JudgementStats.TapRecord.PassedCount * _baseScore;
|
||||
var touchAchievement = _statistics.JudgementStats.TouchRecord.PassedCount * _baseScore;
|
||||
var holdAchievement = _statistics.JudgementStats.HoldRecord.PassedCount * _baseScore * 2;
|
||||
var slideAchievement = _statistics.JudgementStats.SlideRecord.PassedCount * _baseScore * 3;
|
||||
var breakAchievement = _statistics.JudgementStats.BreakRecord.PassedCount * _baseScore * 5;
|
||||
|
||||
return includeExtras && _statistics.JudgementStats.BreakRecord.MaxCount > 0
|
||||
? tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement +
|
||||
(double)_statistics.JudgementStats.BreakRecord.PassedCount /
|
||||
_statistics.JudgementStats.BreakRecord.MaxCount
|
||||
: tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement;
|
||||
}
|
||||
|
||||
public ClearType EvaluateClearType(bool whilePlaying = false)
|
||||
{
|
||||
var achievement = GetAchievement();
|
||||
|
||||
var cleared = achievement >= 80 && _statistics.JudgementStats.AllNotesPassed;
|
||||
var missed = _statistics.JudgementStats.JudgedMissCount >= 1;
|
||||
var anyGood = _statistics.JudgementStats.JudgedGoodCount >= 1;
|
||||
var anyGreat = _statistics.JudgementStats.JudgedGreatCount >= 1;
|
||||
|
||||
var chartHasBreaks = _statistics.JudgementStats.MaxBreakCount > 0;
|
||||
|
||||
if (!whilePlaying && !cleared)
|
||||
return ClearType.Failed;
|
||||
|
||||
if (missed)
|
||||
return ClearType.Clear;
|
||||
|
||||
if (anyGood)
|
||||
return ClearType.FullCombo;
|
||||
|
||||
if (anyGreat)
|
||||
return ClearType.FullComboPlus;
|
||||
|
||||
if (!chartHasBreaks)
|
||||
return ClearType.AllPerfect;
|
||||
|
||||
var maxBreak = _statistics.JudgementStats.BreakRecord.CriticalCount ==
|
||||
_statistics.JudgementStats.BreakRecord.PassedCount;
|
||||
|
||||
return maxBreak
|
||||
? ClearType.AllPerfectPlus
|
||||
: ClearType.AllPerfect;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using SimaiSharp.Structures;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Internal
|
||||
{
|
||||
public sealed class ComboStats : IReactiveStatistic
|
||||
{
|
||||
public long lastCombo { get; private set; }
|
||||
public long maxCombo { get; private set; }
|
||||
|
||||
public void Push(in NoteType type, in JudgeData data = default)
|
||||
{
|
||||
if (data.grade == JudgeGrade.Miss)
|
||||
{
|
||||
lastCombo = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
lastCombo++;
|
||||
maxCombo = Math.Max(lastCombo, maxCombo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Internal
|
||||
{
|
||||
public sealed class DxScoreStats
|
||||
{
|
||||
private readonly Statistics _statistics;
|
||||
|
||||
private const int CriticalWeight = 3;
|
||||
|
||||
private static readonly int[] Weights =
|
||||
{
|
||||
CriticalWeight, 2, 1, 0, 0
|
||||
};
|
||||
|
||||
public DxScoreStats(Statistics statistics) => _statistics = statistics;
|
||||
|
||||
public uint TotalDxScore => _statistics.JudgementStats.MaxNoteCount * CriticalWeight;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to generate max health or max dx score.
|
||||
/// </summary>
|
||||
public uint MaxAchievableDxScore => _statistics.JudgementStats.JudgedNoteCount * CriticalWeight;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to generate the current health or dx score.
|
||||
/// </summary>
|
||||
public uint DxScore =>
|
||||
(uint)(_statistics.JudgementStats.TapRecord.Judged.Sum(c => Weights[(int)c.grade]) +
|
||||
_statistics.JudgementStats.TouchRecord.Judged.Sum(c => Weights[(int)c.grade]) +
|
||||
_statistics.JudgementStats.HoldRecord.Judged.Sum(c => Weights[(int)c.grade]) +
|
||||
_statistics.JudgementStats.SlideRecord.Judged.Sum(c => Weights[(int)c.grade]) +
|
||||
_statistics.JudgementStats.BreakRecord.Judged.Sum(c => Weights[(int)c.grade]));
|
||||
|
||||
public int GetGrade()
|
||||
{
|
||||
var dxScorePercentage = (float)DxScore / TotalDxScore;
|
||||
|
||||
// // add 1 for every passed threshold
|
||||
for (var i = 0; i < DxGradeThresholds.Thresholds.Length; i++)
|
||||
{
|
||||
if (dxScorePercentage < DxGradeThresholds.Thresholds[i])
|
||||
return i;
|
||||
}
|
||||
|
||||
return DxGradeThresholds.Thresholds.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using AstroDX.Utilities.Extensions;
|
||||
using SimaiSharp.Structures;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Internal
|
||||
{
|
||||
public sealed class FluctuationStats : IReactiveStatistic
|
||||
{
|
||||
public long EarlyCount { get; private set; }
|
||||
public long LateCount { get; private set; }
|
||||
public double TotalDeviation { get; private set; }
|
||||
public long NotesCounted { get; private set; }
|
||||
public double Deviation => TotalDeviation / NotesCounted;
|
||||
|
||||
public void Push(in NoteType type, in JudgeData data)
|
||||
{
|
||||
if ((GameSettings.Settings.Profile.Metrics.fluctuationVisibility & data.grade.AsFlag()) == 0)
|
||||
return;
|
||||
|
||||
if (type == NoteType.Slide)
|
||||
return;
|
||||
|
||||
NotesCounted++;
|
||||
|
||||
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
|
||||
switch (data.state)
|
||||
{
|
||||
case JudgeState.Early:
|
||||
EarlyCount++;
|
||||
break;
|
||||
case JudgeState.Late:
|
||||
LateCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
TotalDeviation += data.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using SimaiSharp.Structures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics.Internal
|
||||
{
|
||||
public sealed class JudgementStats : IReactiveStatistic
|
||||
{
|
||||
public NoteRecord HoldRecord { get; }
|
||||
public NoteRecord SlideRecord { get; }
|
||||
public NoteRecord TapRecord { get; }
|
||||
public NoteRecord TouchRecord { get; }
|
||||
public NoteRecord BreakRecord { get; }
|
||||
|
||||
public uint MaxTapCount { get; }
|
||||
public uint MaxTouchCount { get; }
|
||||
public uint MaxHoldCount { get; }
|
||||
public uint MaxSlideCount { get; }
|
||||
public uint MaxBreakCount { get; }
|
||||
|
||||
public uint JudgedCriticalCount { get; private set; }
|
||||
public uint JudgedPerfectCount { get; private set; }
|
||||
public uint JudgedGreatCount { get; private set; }
|
||||
public uint JudgedGoodCount { get; private set; }
|
||||
public uint JudgedMissCount { get; private set; }
|
||||
public uint JudgedNoteCount { get; private set; }
|
||||
|
||||
public uint MaxNoteCount =>
|
||||
TapRecord.MaxCount + TouchRecord.MaxCount +
|
||||
HoldRecord.MaxCount + SlideRecord.MaxCount +
|
||||
BreakRecord.MaxCount;
|
||||
|
||||
public bool AllNotesPassed =>
|
||||
TapRecord.MaxCount == TapRecord.PassedCount &&
|
||||
TouchRecord.MaxCount == TouchRecord.PassedCount &&
|
||||
HoldRecord.MaxCount == HoldRecord.PassedCount &&
|
||||
SlideRecord.MaxCount == SlideRecord.PassedCount &&
|
||||
BreakRecord.MaxCount == BreakRecord.PassedCount;
|
||||
|
||||
public JudgementStats(ReadOnlyMemory<NoteCollection> noteCollections)
|
||||
{
|
||||
foreach (var noteCollection in noteCollections.Span)
|
||||
{
|
||||
foreach (var note in noteCollection)
|
||||
{
|
||||
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
|
||||
switch (note.type)
|
||||
{
|
||||
case NoteType.Tap:
|
||||
MaxTapCount++;
|
||||
break;
|
||||
case NoteType.Touch:
|
||||
MaxTouchCount++;
|
||||
break;
|
||||
case NoteType.Hold:
|
||||
MaxHoldCount++;
|
||||
break;
|
||||
case NoteType.Break:
|
||||
MaxBreakCount++;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var slide in note.slidePaths)
|
||||
{
|
||||
if (slide.type == NoteType.Break)
|
||||
MaxBreakCount++;
|
||||
else
|
||||
MaxSlideCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapRecord = new NoteRecord(MaxTapCount);
|
||||
TouchRecord = new NoteRecord(MaxTouchCount);
|
||||
HoldRecord = new NoteRecord(MaxHoldCount);
|
||||
SlideRecord = new NoteRecord(MaxSlideCount);
|
||||
BreakRecord = new NoteRecord(MaxBreakCount);
|
||||
}
|
||||
|
||||
public void Push(in NoteType type, in JudgeData data)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case NoteType.Tap:
|
||||
TapRecord.Push(in data);
|
||||
break;
|
||||
case NoteType.Touch:
|
||||
TouchRecord.Push(in data);
|
||||
break;
|
||||
case NoteType.Hold:
|
||||
HoldRecord.Push(in data);
|
||||
break;
|
||||
case NoteType.Slide:
|
||||
SlideRecord.Push(in data);
|
||||
break;
|
||||
case NoteType.Break:
|
||||
BreakRecord.Push(in data);
|
||||
BreakRecord.Extras += data.grade switch
|
||||
{
|
||||
JudgeGrade.CriticalPerfect => 1,
|
||||
JudgeGrade.Perfect =>
|
||||
Mathf.Abs((float)data.offset) <= 0.033335f ? 0.75 : 0.5,
|
||||
JudgeGrade.Great => 0.4,
|
||||
JudgeGrade.Good => 0.3,
|
||||
JudgeGrade.Miss => 0,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
break;
|
||||
case NoteType.ForceInvalidate:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data.grade)
|
||||
{
|
||||
case JudgeGrade.CriticalPerfect:
|
||||
JudgedCriticalCount++;
|
||||
break;
|
||||
case JudgeGrade.Perfect:
|
||||
JudgedPerfectCount++;
|
||||
break;
|
||||
case JudgeGrade.Great:
|
||||
JudgedGreatCount++;
|
||||
break;
|
||||
case JudgeGrade.Good:
|
||||
JudgedGoodCount++;
|
||||
break;
|
||||
case JudgeGrade.Miss:
|
||||
JudgedMissCount++;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
JudgedNoteCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
core-dump/Scripts/Models/Scoring/Metrics/Statistics.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AstroDX.Models.Scoring.Metrics.Internal;
|
||||
using Sigtrap.Relays;
|
||||
using SimaiSharp.Structures;
|
||||
|
||||
namespace AstroDX.Models.Scoring.Metrics
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class Statistics
|
||||
{
|
||||
public Relay<NoteType, JudgeData> OnJudgementReceived { get; } = new();
|
||||
|
||||
public FluctuationStats FluctuationStats { get; }
|
||||
public ComboStats ComboStats { get; }
|
||||
public JudgementStats JudgementStats { get; }
|
||||
public AchievementStats AchievementStats { get; }
|
||||
public DxScoreStats DxScoreStats { get; }
|
||||
private HashSet<IReactiveStatistic> Extensions { get; }
|
||||
|
||||
public Statistics(ReadOnlyMemory<NoteCollection> chart)
|
||||
{
|
||||
JudgementStats = new JudgementStats(chart);
|
||||
FluctuationStats = new FluctuationStats();
|
||||
ComboStats = new ComboStats();
|
||||
AchievementStats = new AchievementStats(this);
|
||||
DxScoreStats = new DxScoreStats(this);
|
||||
Extensions = new HashSet<IReactiveStatistic>();
|
||||
}
|
||||
|
||||
public void Push(in NoteType type, in JudgeData data)
|
||||
{
|
||||
JudgementStats.Push(in type, in data);
|
||||
ComboStats.Push(in type, in data);
|
||||
FluctuationStats.Push(in type, in data);
|
||||
|
||||
foreach (var statistic in Extensions)
|
||||
statistic.Push(in type, in data);
|
||||
|
||||
OnJudgementReceived.Dispatch(type, data);
|
||||
}
|
||||
|
||||
public void RegisterExtension(IReactiveStatistic extension)
|
||||
{
|
||||
Extensions.Add(extension);
|
||||
}
|
||||
|
||||
public IReactiveStatistic GetExtensionOrDefault<T>()
|
||||
where T : IReactiveStatistic
|
||||
{
|
||||
return Extensions.FirstOrDefault(e => e.GetType() == typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
72
core-dump/Scripts/Models/Scoring/NoteRecord.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AstroDX.Models.Scoring
|
||||
{
|
||||
public class NoteRecord
|
||||
{
|
||||
public IReadOnlyList<JudgeData> Judged { get; }
|
||||
|
||||
public uint MaxCount { get; }
|
||||
public uint PassedCount { get; private set; }
|
||||
public uint CriticalCount { get; private set; }
|
||||
public uint PerfectCount { get; private set; }
|
||||
public uint GreatCount { get; private set; }
|
||||
public uint GoodCount { get; private set; }
|
||||
public uint MissCount { get; private set; }
|
||||
public double Extras { get; set; }
|
||||
|
||||
public NoteRecord(uint maxCount)
|
||||
{
|
||||
Judged = new List<JudgeData>();
|
||||
MaxCount = maxCount;
|
||||
PassedCount = 0;
|
||||
Extras = 0;
|
||||
}
|
||||
|
||||
public void Push(in JudgeData data)
|
||||
{
|
||||
((List<JudgeData>)Judged).Add(data);
|
||||
|
||||
PassedCount++;
|
||||
switch (data.grade)
|
||||
{
|
||||
case JudgeGrade.CriticalPerfect:
|
||||
CriticalCount++;
|
||||
break;
|
||||
case JudgeGrade.Perfect:
|
||||
PerfectCount++;
|
||||
break;
|
||||
case JudgeGrade.Great:
|
||||
GreatCount++;
|
||||
break;
|
||||
case JudgeGrade.Good:
|
||||
GoodCount++;
|
||||
break;
|
||||
case JudgeGrade.Miss:
|
||||
MissCount++;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
Points += GetScore(data.grade);
|
||||
}
|
||||
|
||||
public double Points { get; private set; }
|
||||
|
||||
private static double GetScore(JudgeGrade grade)
|
||||
{
|
||||
return grade switch
|
||||
{
|
||||
JudgeGrade.CriticalPerfect => 1,
|
||||
JudgeGrade.Perfect => 1,
|
||||
JudgeGrade.Great => 0.8,
|
||||
JudgeGrade.Good => 0.5,
|
||||
JudgeGrade.Miss => 0,
|
||||
_ => throw new
|
||||
ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
3
core-dump/Scripts/UI/RecyclingScrollRect/Interfaces.meta
Executable file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a0d1a0a74b14c94af6f7d4d943075d8
|
||||
timeCreated: 1710933820
|
||||
101
core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs
Executable file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Pool;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AstroDX.UI.RecyclingScrollRect.Interfaces
|
||||
{
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
public abstract class ScrollRectRecyclingBase<TData> : MonoBehaviour,
|
||||
IDragHandler,
|
||||
IBeginDragHandler,
|
||||
IInitializePotentialDragHandler,
|
||||
IEndDragHandler
|
||||
{
|
||||
protected IList<TData> DataSource { get; private set; } = new List<TData>();
|
||||
|
||||
protected ObjectPool<ScrollViewItem<TData>> itemsPool;
|
||||
|
||||
[field: Disable, SerializeField]
|
||||
protected List<ScrollViewItem<TData>> ShownItemsUnordered { get; private set; } = new();
|
||||
|
||||
[BeginGroup("List View")]
|
||||
[NotNull, SerializeField, Tooltip("Where the items will be created.")]
|
||||
protected ScrollRect scrollRect;
|
||||
|
||||
[NotNull, SerializeField, Tooltip("The visual representation of each item.")]
|
||||
[EndGroup]
|
||||
protected ScrollViewItem<TData> itemPrefab;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
itemsPool = new ObjectPool<ScrollViewItem<TData>>(() =>
|
||||
{
|
||||
var item = Instantiate(itemPrefab, scrollRect.content);
|
||||
item.DisableItem();
|
||||
return item;
|
||||
},
|
||||
item => item.EnableItem(),
|
||||
item => item.DisableItem());
|
||||
}
|
||||
|
||||
public void SetDataSource(IList<TData> dataSource)
|
||||
{
|
||||
DataSource = dataSource;
|
||||
ReloadItems();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the scroll rect to match up with the data source.
|
||||
/// </summary>
|
||||
protected abstract void ReloadItems();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current visible range, load and discards items depending on the previous range.
|
||||
/// <param name="resetActiveItems">Set the item data again to items that can be recycled. Triggered when data source changes.</param>
|
||||
/// </summary>
|
||||
protected abstract void UpdateVisibleRange(bool resetActiveItems);
|
||||
|
||||
/// <summary>
|
||||
/// Jumps to the item in the scroll rect.
|
||||
/// </summary>
|
||||
public void JumpToItem(TData item)
|
||||
{
|
||||
// We don't do any error handling here so that the error is thrown and developers using this can debug what's happening.
|
||||
JumpToIndex(DataSource.IndexOf(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls to the item in the scroll rect.
|
||||
/// </summary>
|
||||
public void ScrollToItem(TData item, float duration, Ease easing = default)
|
||||
{
|
||||
// We don't do any error handling here so that the error is thrown and developers using this can debug what's happening.
|
||||
ScrollToIndex(DataSource.IndexOf(item), duration, easing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Jumps to the index provided by the data source in the scroll rect.
|
||||
/// </summary>
|
||||
public abstract void JumpToIndex(int index);
|
||||
|
||||
public abstract void ScrollToIndex(int index, float duration, Ease easing = default);
|
||||
|
||||
protected virtual void OnDestroy() => itemsPool.Dispose();
|
||||
|
||||
public abstract void OnDrag(PointerEventData eventData);
|
||||
|
||||
public abstract void OnBeginDrag(PointerEventData eventData);
|
||||
|
||||
public abstract void OnInitializePotentialDrag(PointerEventData eventData);
|
||||
|
||||
public abstract void OnEndDrag(PointerEventData eventData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5843eb5b52bc4cd085d670e17ff8be63
|
||||
timeCreated: 1710933385
|
||||
34
core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs
Executable file
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.UI.RecyclingScrollRect.Interfaces
|
||||
{
|
||||
public abstract class ScrollViewItem<T> : MonoBehaviour
|
||||
{
|
||||
public T Data { get; private set; }
|
||||
public int Index { get; private set; }
|
||||
|
||||
public void SetData(T data, int index)
|
||||
{
|
||||
Data = data;
|
||||
Index = index;
|
||||
|
||||
OnDataUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is called whenever this instance is requested to render a new item with a different index.
|
||||
/// </summary>
|
||||
protected abstract void OnDataUpdated();
|
||||
|
||||
/// <summary>
|
||||
/// When this object instance scrolls out of view, this is called to disable relevant components;
|
||||
/// Should be as lightweight as possible since disabled items might get reused instantly.
|
||||
/// </summary>
|
||||
internal abstract void DisableItem();
|
||||
|
||||
/// <summary>
|
||||
/// Called when an item is retrieved from the pool.
|
||||
/// </summary>
|
||||
internal abstract void EnableItem();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f1b83f97d7847728b5f776276799111
|
||||
timeCreated: 1710933857
|
||||
3
core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects.meta
Executable file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d313c27e4a974b3baea75f0d80917046
|
||||
timeCreated: 1710933392
|
||||
236
core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs
Executable file
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.GameSettings;
|
||||
using AstroDX.UI.RecyclingScrollRect.Interfaces;
|
||||
using AstroDX.Utilities.Extensions;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace AstroDX.UI.RecyclingScrollRect.ScrollRects
|
||||
{
|
||||
[Serializable]
|
||||
public class RecyclingGridView<TData> : ScrollRectRecyclingBase<TData>
|
||||
{
|
||||
[BeginGroup("Scrolling")]
|
||||
[SerializeField]
|
||||
private float gap;
|
||||
|
||||
[SerializeField]
|
||||
private float marginTop;
|
||||
|
||||
[SerializeField]
|
||||
private float marginBottom;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private Vector2 itemSize;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private int visibleStart;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private int columnCount;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private int rowCount;
|
||||
|
||||
[SerializeField, Disable]
|
||||
private float usableWidth;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private int maxVisibleCount;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private float contentHeight;
|
||||
|
||||
private Tweener _positionTween;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
itemSize = itemPrefab?.transform.AsRectTransform().sizeDelta ?? Vector2.zero;
|
||||
|
||||
scrollRect.content.anchorMin = new Vector2(0, 1);
|
||||
scrollRect.content.anchorMax = new Vector2(1, 1);
|
||||
scrollRect.content.pivot = new Vector2(0.5f, 1);
|
||||
scrollRect.content.localScale = Vector3.one;
|
||||
|
||||
var dt = new DrivenRectTransformTracker();
|
||||
dt.Clear();
|
||||
|
||||
//Object to drive the transform
|
||||
dt.Add(this, scrollRect.content, DrivenTransformProperties.All);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
Settings.Profile.Graphics.scaleFactorChanged.AddListener(ReloadItems);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (scrollRect.velocity.y == 0)
|
||||
return;
|
||||
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
protected override void ReloadItems()
|
||||
{
|
||||
columnCount = Mathf.FloorToInt((scrollRect.viewport.rect.width + gap) / (itemSize.x + gap));
|
||||
usableWidth = columnCount * itemSize.x + (columnCount - 1) * gap;
|
||||
|
||||
rowCount = Mathf.CeilToInt((float)DataSource.Count / columnCount);
|
||||
|
||||
maxVisibleCount = Mathf.CeilToInt((scrollRect.viewport.rect.size.y + gap) / (itemSize.y + gap)) * columnCount;
|
||||
|
||||
contentHeight = marginTop + rowCount * (itemSize.y + gap) + marginBottom;
|
||||
scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, contentHeight);
|
||||
|
||||
UpdateVisibleRange(true);
|
||||
}
|
||||
|
||||
private readonly HashSet<int> _itemsToAdd = new();
|
||||
|
||||
private int _previousStartIndexInclusive;
|
||||
private int _previousEndIndexExclusive;
|
||||
|
||||
protected override void UpdateVisibleRange(bool resetActiveItems)
|
||||
{
|
||||
visibleStart = scrollRect.content.anchoredPosition.y <= marginTop
|
||||
? 0
|
||||
: columnCount *
|
||||
Mathf.FloorToInt((scrollRect.content.anchoredPosition.y - marginTop) / (itemSize.y + gap));
|
||||
|
||||
var startIndexInclusive = visibleStart;
|
||||
var endIndexExclusive = Mathf.Min(startIndexInclusive + maxVisibleCount, DataSource.Count);
|
||||
|
||||
// If the visible range hasn't changed, just update the positions of all items in range
|
||||
if (startIndexInclusive == _previousStartIndexInclusive &&
|
||||
endIndexExclusive == _previousEndIndexExclusive)
|
||||
{
|
||||
foreach (var item in ShownItemsUnordered)
|
||||
{
|
||||
item.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(item.Index);
|
||||
|
||||
if (resetActiveItems)
|
||||
item.SetData(DataSource[item.Index], item.Index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_previousStartIndexInclusive = startIndexInclusive;
|
||||
_previousEndIndexExclusive = endIndexExclusive;
|
||||
|
||||
// Populate the list of items that should be shown by the end of this procedure
|
||||
for (var i = startIndexInclusive; i < endIndexExclusive; i++)
|
||||
_itemsToAdd.Add(i);
|
||||
|
||||
// Find and skip items that are already shown
|
||||
// Since the shown items are unordered (for example, we may request to show 1 after 2 and 3 are shown, resulting in [2, 3, 1]),
|
||||
// We can't make any assumptions about items not appearing later in the list (e.g. [1, 2, 4, 5, 6, 3])
|
||||
for (var i = 0; i < ShownItemsUnordered.Count; i++)
|
||||
{
|
||||
var item = ShownItemsUnordered[i];
|
||||
|
||||
if (_itemsToAdd.Contains(item.Index))
|
||||
{
|
||||
item.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(item.Index);
|
||||
|
||||
if (resetActiveItems)
|
||||
item.SetData(DataSource[item.Index], item.Index);
|
||||
|
||||
_itemsToAdd.Remove(item.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't need to render this item in the new visible range, so we can safely release it
|
||||
itemsPool.Release(item);
|
||||
|
||||
// Modifying a list while enumerating it isn't recommended
|
||||
// But we're doing it here for performance
|
||||
ShownItemsUnordered.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, generate the actual missing items
|
||||
foreach (var index in _itemsToAdd)
|
||||
{
|
||||
var itemInstance = itemsPool.Get();
|
||||
ShownItemsUnordered.Add(itemInstance);
|
||||
itemInstance.SetData(DataSource[index], index);
|
||||
itemInstance.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(index);
|
||||
}
|
||||
|
||||
_itemsToAdd.Clear();
|
||||
}
|
||||
|
||||
public override void JumpToIndex(int index)
|
||||
{
|
||||
scrollRect.velocity = new Vector3(0, 0);
|
||||
scrollRect.verticalNormalizedPosition = GetScrollPosition(index);
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
public override void ScrollToIndex(int index, float duration, Ease easing = default)
|
||||
{
|
||||
scrollRect.velocity = new Vector3(0, 0);
|
||||
_positionTween?.Kill();
|
||||
|
||||
_positionTween = DOTween.To(() => scrollRect.verticalNormalizedPosition,
|
||||
p =>
|
||||
{
|
||||
scrollRect.verticalNormalizedPosition = p;
|
||||
UpdateVisibleRange(false);
|
||||
},
|
||||
GetScrollPosition(index),
|
||||
duration).SetEase(easing);
|
||||
}
|
||||
|
||||
private float GetScrollPosition(int index)
|
||||
{
|
||||
// absolute position at index starts 0 at the top, and decreases when descending
|
||||
var normalizedPosition = 1 + AbsolutePositionAtIndex(index).y / contentHeight;
|
||||
normalizedPosition -= ((itemSize.y + marginBottom) * (1 - normalizedPosition) - marginTop * normalizedPosition) /
|
||||
contentHeight;
|
||||
|
||||
return normalizedPosition;
|
||||
}
|
||||
|
||||
public override void OnInitializePotentialDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
_positionTween?.Kill();
|
||||
}
|
||||
|
||||
public override void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
private Vector2 AbsolutePositionAtIndex(int index)
|
||||
{
|
||||
var column = index % columnCount;
|
||||
var fullContainerWidth = scrollRect.content.rect.size.x;
|
||||
var row = Mathf.FloorToInt((float)index / columnCount);
|
||||
|
||||
return new Vector2(column * (itemSize.x + gap) + (fullContainerWidth - usableWidth) / 2,
|
||||
-marginTop - row * (itemSize.y + gap));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 163b88140e634e719f1a2e8ae214523f
|
||||
timeCreated: 1710933419
|
||||
@@ -0,0 +1,207 @@
|
||||
using System.Collections.Generic;
|
||||
using AstroDX.UI.RecyclingScrollRect.Interfaces;
|
||||
using AstroDX.Utilities.Extensions;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace AstroDX.UI.RecyclingScrollRect.ScrollRects
|
||||
{
|
||||
public class RecyclingVerticalListView<TData> : ScrollRectRecyclingBase<TData>
|
||||
{
|
||||
[BeginGroup("Scrolling")]
|
||||
[SerializeField]
|
||||
private float gap;
|
||||
|
||||
[SerializeField]
|
||||
private float marginTop;
|
||||
|
||||
[SerializeField]
|
||||
[EndGroup]
|
||||
private float marginBottom;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private float itemHeight;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private int maxVisibleCount;
|
||||
|
||||
[Disable, SerializeField]
|
||||
private float contentHeight;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
itemHeight = itemPrefab?.GetRectTransform().sizeDelta.y ?? 0;
|
||||
|
||||
scrollRect.content.anchorMin = new Vector2(0, 1);
|
||||
scrollRect.content.anchorMax = new Vector2(1, 1);
|
||||
scrollRect.content.pivot = new Vector2(0.5f, 1);
|
||||
scrollRect.content.localScale = Vector3.one;
|
||||
|
||||
var dt = new DrivenRectTransformTracker();
|
||||
dt.Clear();
|
||||
|
||||
//Object to drive the transform
|
||||
dt.Add(this, scrollRect.content, DrivenTransformProperties.All);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (scrollRect.velocity.y == 0)
|
||||
return;
|
||||
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
protected override void ReloadItems()
|
||||
{
|
||||
maxVisibleCount = Mathf.CeilToInt((scrollRect.viewport.rect.size.y + gap) / (itemHeight + gap));
|
||||
|
||||
contentHeight = marginTop + (itemHeight + gap) + marginBottom;
|
||||
scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, contentHeight);
|
||||
|
||||
UpdateVisibleRange(true);
|
||||
}
|
||||
|
||||
private readonly HashSet<int> _itemsToAdd = new();
|
||||
|
||||
private int _previousStartIndexInclusive;
|
||||
private int _previousEndIndexExclusive;
|
||||
|
||||
protected override void UpdateVisibleRange(bool resetActiveItems)
|
||||
{
|
||||
var startIndexInclusive = scrollRect.content.anchoredPosition.y <= marginTop
|
||||
? 0
|
||||
: Mathf.FloorToInt((scrollRect.content.anchoredPosition.y - marginTop) /
|
||||
(itemHeight + gap));
|
||||
var endIndexExclusive = Mathf.Min(startIndexInclusive + maxVisibleCount, DataSource.Count);
|
||||
|
||||
// If the visible range hasn't changed, just update the positions of all items in range
|
||||
if (startIndexInclusive == _previousStartIndexInclusive &&
|
||||
endIndexExclusive == _previousEndIndexExclusive)
|
||||
{
|
||||
foreach (var item in ShownItemsUnordered)
|
||||
{
|
||||
var position = item.GetRectTransform().anchoredPosition;
|
||||
position.y = AbsolutePositionAtIndex(item.Index);
|
||||
item.GetRectTransform().anchoredPosition = position;
|
||||
|
||||
if (resetActiveItems)
|
||||
item.SetData(DataSource[item.Index], item.Index);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_previousStartIndexInclusive = startIndexInclusive;
|
||||
_previousEndIndexExclusive = endIndexExclusive;
|
||||
|
||||
// Populate the list of items that should be shown by the end of this procedure
|
||||
for (var i = startIndexInclusive; i < endIndexExclusive; i++)
|
||||
_itemsToAdd.Add(i);
|
||||
|
||||
// Find and skip items that are already shown
|
||||
// Since the shown items are unordered (for example, we may request to show 1 after 2 and 3 are shown, resulting in [2, 3, 1]),
|
||||
// We can't make any assumptions about items not appearing later in the list (e.g. [1, 2, 4, 5, 6, 3])
|
||||
for (var i = 0; i < ShownItemsUnordered.Count; i++)
|
||||
{
|
||||
var item = ShownItemsUnordered[i];
|
||||
|
||||
if (_itemsToAdd.Contains(item.Index))
|
||||
{
|
||||
var position = item.GetRectTransform().anchoredPosition;
|
||||
position.y = AbsolutePositionAtIndex(item.Index);
|
||||
item.GetRectTransform().anchoredPosition = position;
|
||||
|
||||
if (resetActiveItems)
|
||||
item.SetData(DataSource[item.Index], item.Index);
|
||||
|
||||
_itemsToAdd.Remove(item.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't need to render this item in the new visible range, so we can safely release it
|
||||
itemsPool.Release(item);
|
||||
|
||||
// Modifying a list while enumerating it isn't recommended
|
||||
// But we're doing it here for performance
|
||||
ShownItemsUnordered.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly, generate the actual missing items
|
||||
foreach (var index in _itemsToAdd)
|
||||
{
|
||||
var itemInstance = itemsPool.Get();
|
||||
ShownItemsUnordered.Add(itemInstance);
|
||||
itemInstance.SetData(DataSource[index], index);
|
||||
|
||||
var position = itemInstance.GetRectTransform().anchoredPosition;
|
||||
position.y = AbsolutePositionAtIndex(index);
|
||||
itemInstance.GetRectTransform().anchoredPosition = position;
|
||||
}
|
||||
|
||||
_itemsToAdd.Clear();
|
||||
}
|
||||
|
||||
public override void JumpToIndex(int index)
|
||||
{
|
||||
scrollRect.velocity = new Vector3(0, 0);
|
||||
scrollRect.verticalNormalizedPosition = GetScrollPosition(index);
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
private Tweener _positionTween;
|
||||
|
||||
public override void ScrollToIndex(int index, float duration, Ease easing = default)
|
||||
{
|
||||
scrollRect.velocity = Vector2.zero;
|
||||
_positionTween?.Kill();
|
||||
_positionTween = DOTween.To(() => scrollRect.verticalNormalizedPosition, p =>
|
||||
{
|
||||
scrollRect.verticalNormalizedPosition = p;
|
||||
UpdateVisibleRange(false);
|
||||
}, GetScrollPosition(index), duration).SetEase(easing);
|
||||
}
|
||||
|
||||
private float GetScrollPosition(int index)
|
||||
{
|
||||
// absolute position at index starts 0 at the top, and decreases when descending
|
||||
var normalizedPosition = 1 + AbsolutePositionAtIndex(index) / contentHeight;
|
||||
normalizedPosition -= ((itemHeight + marginBottom) * (1 - normalizedPosition) - marginTop * normalizedPosition) /
|
||||
contentHeight;
|
||||
|
||||
return normalizedPosition;
|
||||
}
|
||||
|
||||
public override void OnInitializePotentialDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
_positionTween?.Kill();
|
||||
}
|
||||
|
||||
public override void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
return;
|
||||
|
||||
UpdateVisibleRange(false);
|
||||
}
|
||||
|
||||
private float AbsolutePositionAtIndex(int index)
|
||||
{
|
||||
return -index * (itemHeight + gap) - marginTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6421e225fef64262bce238235e6f6e0a
|
||||
timeCreated: 1711013891
|
||||
69
core-dump/Scripts/Utilities/Deprecated/Timing/GameTime.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Timing
|
||||
{
|
||||
public class GameTime : MonoBehaviour
|
||||
{
|
||||
private double _lastCapturedDspTime;
|
||||
public static bool Active { get; private set; }
|
||||
public static double StartTime { get; private set; }
|
||||
public static double TimeSinceClipStart { get; private set; }
|
||||
|
||||
private static NativeLinearRegression TimePrediction { get; set; }
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!Active) return;
|
||||
|
||||
#region Smoothen DSP buffer time
|
||||
|
||||
if (AudioSettings.dspTime > _lastCapturedDspTime)
|
||||
{
|
||||
TimePrediction.Sample(new double2(Time.realtimeSinceStartupAsDouble, AudioSettings.dspTime));
|
||||
_lastCapturedDspTime = AudioSettings.dspTime;
|
||||
}
|
||||
|
||||
var smoothDspTime = TimePrediction.SampleCount < 2
|
||||
? AudioSettings.dspTime
|
||||
: TimePrediction.Predict(Time.realtimeSinceStartupAsDouble);
|
||||
|
||||
#endregion
|
||||
|
||||
TimeSinceClipStart = smoothDspTime - StartTime;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
TimePrediction.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the time manager to start counting time
|
||||
/// </summary>
|
||||
public static void StartFrom(double startTime)
|
||||
{
|
||||
StartTime = startTime;
|
||||
TimeSinceClipStart = AudioSettings.dspTime - startTime;
|
||||
Active = true;
|
||||
|
||||
TimePrediction ??= new NativeLinearRegression();
|
||||
|
||||
TimePrediction.Clear();
|
||||
}
|
||||
|
||||
public static void Pause()
|
||||
{
|
||||
Active = false;
|
||||
}
|
||||
|
||||
public static void UnPause(double timeSinceClipStart)
|
||||
{
|
||||
TimePrediction.Clear();
|
||||
StartTime = AudioSettings.dspTime - timeSinceClipStart;
|
||||
TimeSinceClipStart = timeSinceClipStart;
|
||||
|
||||
Active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Timing
|
||||
{
|
||||
public sealed class NativeLinearRegression : IDisposable
|
||||
{
|
||||
public int SampleCount { get; private set; }
|
||||
public int MaxSampleCount { get; }
|
||||
|
||||
private NativeArray<double2> _samples;
|
||||
|
||||
public NativeLinearRegression(int maxSampleCount = 12)
|
||||
{
|
||||
MaxSampleCount = maxSampleCount;
|
||||
_samples = new NativeArray<double2>(maxSampleCount, Allocator.Persistent);
|
||||
}
|
||||
|
||||
public void Sample(double2 plot)
|
||||
{
|
||||
if (SampleCount < MaxSampleCount)
|
||||
SampleCount++;
|
||||
else
|
||||
for (var i = 1; i < SampleCount; i++)
|
||||
_samples[i - 1] = _samples[i];
|
||||
|
||||
_samples[SampleCount - 1] = plot;
|
||||
}
|
||||
|
||||
public double Predict(in double x)
|
||||
{
|
||||
using var result = new NativeArray<double>(2, Allocator.TempJob);
|
||||
|
||||
var jobData = new LinearRegressionJob
|
||||
{
|
||||
samples = _samples.Slice(0, SampleCount),
|
||||
yInterceptAndSlope = result
|
||||
};
|
||||
|
||||
jobData.Schedule().Complete();
|
||||
|
||||
var yIntercept = result[0];
|
||||
var slope = result[1];
|
||||
|
||||
return x * slope + yIntercept;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SampleCount = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_samples.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public struct LinearRegressionJob : IJob
|
||||
{
|
||||
[ReadOnly]
|
||||
public NativeSlice<double2> samples;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeArray<double> yInterceptAndSlope;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
double sumOfX = 0;
|
||||
double sumOfY = 0;
|
||||
double sumOfXSq = 0;
|
||||
double sumCoDeviates = 0;
|
||||
|
||||
var sampleCount = samples.Length;
|
||||
|
||||
for (var i = 0; i < sampleCount; i++)
|
||||
{
|
||||
var plot = samples[i];
|
||||
sumCoDeviates += plot.x * plot.y;
|
||||
sumOfX += plot.x;
|
||||
sumOfY += plot.y;
|
||||
sumOfXSq += plot.x * plot.x;
|
||||
}
|
||||
|
||||
var ssX = sumOfXSq - sumOfX * sumOfX / sampleCount;
|
||||
var sCo = sumCoDeviates - sumOfX * sumOfY / sampleCount;
|
||||
|
||||
var meanX = sumOfX / sampleCount;
|
||||
var meanY = sumOfY / sampleCount;
|
||||
|
||||
// y-intercept
|
||||
yInterceptAndSlope[0] = meanY - sCo / ssX * meanX;
|
||||
|
||||
// slope
|
||||
yInterceptAndSlope[1] = sCo / ssX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,45 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Helpers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Title
|
||||
{
|
||||
public class TitleManager : MonoBehaviour
|
||||
{
|
||||
public Text anyKeyPressed;
|
||||
|
||||
public List<string> textList;
|
||||
|
||||
private int _textIndex;
|
||||
private void Start()
|
||||
{
|
||||
textList = new List<string>
|
||||
{
|
||||
Localization.ParseAuto($"TITLE_ANYKEY"),
|
||||
Localization.ParseAuto($"TITLE_THANKS"),
|
||||
$"{Localization.ParseAuto($"TITLE_BUILDVERSION")} {Application.version}"
|
||||
};
|
||||
|
||||
StartCoroutine(CycleThroughText());
|
||||
}
|
||||
|
||||
private IEnumerator CycleThroughText()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_textIndex = (_textIndex + 1) % textList.Count;
|
||||
anyKeyPressed.text = textList[_textIndex];
|
||||
yield return new WaitForSeconds(3);
|
||||
}
|
||||
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
|
||||
public void ChangeScene()
|
||||
{
|
||||
SceneSwapper.LoadScene("ModeSelection");
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Helpers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Title
|
||||
{
|
||||
public class TitleManager : MonoBehaviour
|
||||
{
|
||||
public Text anyKeyPressed;
|
||||
|
||||
public List<string> textList;
|
||||
|
||||
private int _textIndex;
|
||||
private void Start()
|
||||
{
|
||||
textList = new List<string>
|
||||
{
|
||||
Localization.ParseAuto($"TITLE_ANYKEY"),
|
||||
Localization.ParseAuto($"TITLE_THANKS"),
|
||||
$"{Localization.ParseAuto($"TITLE_BUILDVERSION")} {Application.version}"
|
||||
};
|
||||
|
||||
StartCoroutine(CycleThroughText());
|
||||
}
|
||||
|
||||
private IEnumerator CycleThroughText()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
_textIndex = (_textIndex + 1) % textList.Count;
|
||||
anyKeyPressed.text = textList[_textIndex];
|
||||
yield return new WaitForSeconds(3);
|
||||
}
|
||||
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
|
||||
public void ChangeScene()
|
||||
{
|
||||
SceneSwapper.LoadScene("ModeSelection");
|
||||
}
|
||||
}
|
||||
}
|
||||
84
core-dump/Scripts/Utilities/Trigonometry.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace AstroDX.Utilities
|
||||
{
|
||||
public static class Trigonometry
|
||||
{
|
||||
public const float Tau = Mathf.PI * 2;
|
||||
|
||||
public static Vector2 Rotate(in this Vector2 v, in float degreesRad)
|
||||
{
|
||||
var magnitude = v.magnitude;
|
||||
var originalDegrees = Mathf.Atan2(v.y, v.x);
|
||||
var newDegrees = originalDegrees + degreesRad;
|
||||
|
||||
var newX = Mathf.Cos(newDegrees) * magnitude;
|
||||
var newY = Mathf.Sin(newDegrees) * magnitude;
|
||||
|
||||
return new Vector2(newX, newY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a point's angle relative to a center point.
|
||||
/// </summary>
|
||||
/// <param name="position">The absolute position of a point.</param>
|
||||
/// <param name="offset">The offset of the calculation's center point.</param>
|
||||
/// <returns>The relative angle of a point from the given center point.</returns>
|
||||
internal static float ToPolarAngle(in Vector2 position, in Vector2? offset = null)
|
||||
{
|
||||
if (!offset.HasValue)
|
||||
return Mathf.Atan2(position.y, position.x);
|
||||
|
||||
var difference = position - offset.Value;
|
||||
return Mathf.Atan2(difference.y, difference.x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the angle between a line to a point on a ring
|
||||
/// and another line perpendicular from a tangent line of that point.
|
||||
/// </summary>
|
||||
/// <param name="adjacent"></param>
|
||||
/// <param name="hypotenuse"></param>
|
||||
/// <param name="clockwise"></param>
|
||||
/// <returns></returns>
|
||||
internal static float GetTangentAngleDelta(in float adjacent,
|
||||
in float hypotenuse,
|
||||
in bool clockwise)
|
||||
{
|
||||
var angleDiff = Mathf.Acos(adjacent / hypotenuse);
|
||||
return clockwise ? -angleDiff : angleDiff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Calculates the angle between <c>startRotation</c> and <c>endRotation</c>,
|
||||
/// given its traversing direction.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="startRotation">The starting rotation.</param>
|
||||
/// <param name="endRotation">The ending rotation.</param>
|
||||
/// <param name="clockwise">Traversing direction.</param>
|
||||
/// <param name="wrapThreshold">
|
||||
/// <para>Wraps to full circle for spans smaller than this value.</para>
|
||||
/// <para><code>Tau / 4f</code> is recommended for offset circles</para>
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The span between the starting rotation and the ending rotation on a unit circle,
|
||||
/// in radians.
|
||||
/// </returns>
|
||||
public static float GetAngleSpan(in float startRotation,
|
||||
in float endRotation,
|
||||
bool clockwise,
|
||||
float wrapThreshold = Tau / 32f)
|
||||
{
|
||||
var span = clockwise
|
||||
? (startRotation - endRotation + 2 * Tau) % Tau
|
||||
: (endRotation - startRotation + 2 * Tau) % Tau;
|
||||
|
||||
if (span <= wrapThreshold)
|
||||
span += Tau;
|
||||
|
||||
return span;
|
||||
}
|
||||
}
|
||||
}
|
||||
122
core-dump/Shader/Note.shader
Normal file
@@ -0,0 +1,122 @@
|
||||
Shader "AstroDX/Note"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
|
||||
_GradientTex ("Gradient Texture", 2D) = "white" {}
|
||||
|
||||
_Color ("Tint", Color) = (1,1,1,1)
|
||||
_ShadowColor ("Shadow Tint", Color) = (0,0,0,1)
|
||||
_Grayscale("Grayscale", Range(0, 1)) = 0
|
||||
[MaterialToggle] _Shine ("Shine", Float) = 0
|
||||
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue"="Transparent"
|
||||
"IgnoreProjector"="True"
|
||||
"RenderType"="Transparent"
|
||||
"PreviewType"="Plane"
|
||||
"CanUseSpriteAtlas"="True"
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile _ PIXELSNAP_ON
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
fixed4 vertex : POSITION;
|
||||
fixed4 color : COLOR;
|
||||
fixed2 texcoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
fixed4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
fixed2 texcoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
fixed4 _Color;
|
||||
fixed _Shine;
|
||||
|
||||
v2f vert(appdata_t IN)
|
||||
{
|
||||
v2f OUT;
|
||||
OUT.vertex = UnityObjectToClipPos(IN.vertex);
|
||||
OUT.texcoord = IN.texcoord;
|
||||
OUT.color = IN.color * _Color;
|
||||
#ifdef PIXELSNAP_ON
|
||||
OUT.vertex = UnityPixelSnap (OUT.vertex);
|
||||
#endif
|
||||
|
||||
return OUT;
|
||||
}
|
||||
|
||||
sampler2D _MainTex;
|
||||
sampler2D _GradientTex;
|
||||
sampler2D _AlphaTex;
|
||||
fixed4 _ShadowColor;
|
||||
fixed _AlphaSplitEnabled;
|
||||
fixed _Grayscale;
|
||||
|
||||
fixed4 sample_sprite_texture(float2 uv)
|
||||
{
|
||||
const fixed4 color = tex2D(_MainTex, uv);
|
||||
|
||||
// setting to 0.004 and 0.996 prevents getting wrong texture edge colors
|
||||
const fixed gradient_position = lerp(0.005, 0.995, saturate(color.r * 2 - 1));
|
||||
const fixed4 gradient_map = tex2D(_GradientTex, fixed2(gradient_position, 0));
|
||||
|
||||
#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED
|
||||
if (_AlphaSplitEnabled)
|
||||
{
|
||||
color.a = tex2D (_AlphaTex, uv).r;
|
||||
}
|
||||
#endif //UNITY_TEXTURE_ALPHASPLIT_ALLOWED
|
||||
|
||||
// choke starts at 0.1 higher than the gradient map threshold
|
||||
// to allow a smoother color blending (see touch)
|
||||
const fixed red_choke = smoothstep(0, 0.5, color.r);
|
||||
|
||||
const fixed3 shadow_color = fixed3(color.b * _ShadowColor.rgb);
|
||||
|
||||
fixed3 sum = gradient_map * red_choke + shadow_color * _ShadowColor.a * color.b;
|
||||
sum = color.g.xxxx + sum * (1 - color.g);
|
||||
|
||||
return fixed4(sum.rgb, color.a);
|
||||
}
|
||||
|
||||
fixed4 frag(v2f IN) : SV_Target
|
||||
{
|
||||
fixed4 c = sample_sprite_texture(IN.texcoord);
|
||||
|
||||
//Rough human eye adjusted grayscale computation
|
||||
const fixed mono_rgb = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
|
||||
|
||||
c.rgb *= IN.color.rgb;
|
||||
|
||||
const fixed shine_multiplier = (sin(_Time.y * 16) * 0.5 + 0.5) * _Shine;
|
||||
c.rgb += shine_multiplier.rrr * 0.2;
|
||||
c.rgb *= 1 + shine_multiplier.rrr * 0.3;
|
||||
|
||||
const fixed3 out_color = lerp(c.rgb, mono_rgb.rrr, _Grayscale);
|
||||
return fixed4(out_color, c.a * IN.color.a);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
core-dump/Sprites/IMG_GAME_HOLD_0.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
core-dump/Sprites/IMG_GAME_HOLD_1.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
core-dump/Sprites/IMG_GAME_STAR_0.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
core-dump/Sprites/IMG_GAME_STAR_1.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
core-dump/Sprites/IMG_GAME_TAP_0.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
core-dump/Sprites/IMG_GAME_TAP_1.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
core-dump/Sprites/IMG_GAME_TOUCH.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
core-dump/Sprites/IMG_GAME_TOUCH_STAR.png
Normal file
|
After Width: | Height: | Size: 18 KiB |