4 Commits

Author SHA1 Message Date
skrwns304@gmail.com
19c26533f8 2026-06-14 커스텀 채보 2026-06-14 16:03:24 +09:00
skrwns304@gmail.com
fab54e162e 2026-06-13 리듬게임 프로토타입 2026-06-13 03:24:18 +09:00
skrwns304@gmail.com
e8086180a0 2026-06-13 리듬게임 판정 2026-06-13 00:16:11 +09:00
b0aede20f2 2026-06-12 리듬게임 (진행중) 2026-06-12 18:20:23 +09:00
2277 changed files with 250985 additions and 120 deletions

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using UnityEngine;
// 표준 MIDI 파일(.mid)을 파싱해 Note 리스트로 변환하는 미니 파서.
// 단일 템포 가정. Note-On(velocity>0) 이벤트만 사용한다.
public static class MidiChartParser
{
// data: .mid 원본 바이트 / offset: 곡 시작 보정(초)
// bpmOverride: 0이면 MIDI 내장 템포 사용, >0이면 이 BPM으로 강제
// lanePitches: 비우면 모든 노트를 레인 0으로, 채우면 해당 음높이만 레인으로 매핑
public static List<Note> Parse(byte[] data, float offset, float bpmOverride, List<int> lanePitches)
{
var notes = new List<Note>();
if (data == null || data.Length < 14)
{
Debug.LogWarning("[MidiChartParser] MIDI 데이터가 비었거나 너무 짧습니다.");
return notes;
}
int p = 0;
// ---- 헤더 청크 (MThd) ----
if (!MatchId(data, p, 'M', 'T', 'h', 'd'))
{
Debug.LogWarning("[MidiChartParser] MThd 헤더가 아닙니다. (TextAsset이 .mid가 맞는지 확인)");
return notes;
}
p += 4;
ReadUInt32(data, ref p); // 헤더 길이(=6), 사용 안 함
ReadUInt16(data, ref p); // 포맷
int trackCount = ReadUInt16(data, ref p);
int division = ReadUInt16(data, ref p);
if ((division & 0x8000) != 0)
Debug.LogWarning("[MidiChartParser] SMPTE 타임코드는 미지원입니다. PPQN으로 가정합니다.");
int ticksPerQuarter = division & 0x7FFF;
if (ticksPerQuarter <= 0) ticksPerQuarter = 480;
// (절대틱, 음높이) 수집 + 첫 템포
var rawNotes = new List<(int tick, int pitch)>();
int tempoMicros = -1; // 4분음표당 마이크로초 (미발견 시 -1)
// ---- 트랙 청크들 (MTrk) ----
for (int t = 0; t < trackCount && p + 8 <= data.Length; t++)
{
if (!MatchId(data, p, 'M', 'T', 'r', 'k'))
break;
p += 4;
long len = ReadUInt32(data, ref p);
int trackEnd = p + (int)len;
if (trackEnd > data.Length) trackEnd = data.Length;
int absTicks = 0;
int runningStatus = 0;
while (p < trackEnd)
{
absTicks += ReadVarLen(data, ref p);
int status = data[p];
if (status < 0x80)
{
status = runningStatus; // 러닝 스테이터스: 상태 바이트 생략
if (status == 0) { p = trackEnd; break; } // 손상된 데이터 방어
}
else
{
p++;
runningStatus = status;
}
if (status == 0xFF) // 메타 이벤트
{
int metaType = data[p++];
int mlen = ReadVarLen(data, ref p);
if (metaType == 0x51 && mlen == 3 && tempoMicros < 0)
tempoMicros = (data[p] << 16) | (data[p + 1] << 8) | data[p + 2];
p += mlen;
runningStatus = 0;
}
else if (status == 0xF0 || status == 0xF7) // SysEx
{
int slen = ReadVarLen(data, ref p);
p += slen;
runningStatus = 0;
}
else // 채널 메시지
{
int type = status & 0xF0;
switch (type)
{
case 0x90: // Note-On
int pitch = data[p++];
int velocity = data[p++];
if (velocity > 0) rawNotes.Add((absTicks, pitch));
break;
case 0x80: // Note-Off
case 0xA0: // 폴리 애프터터치
case 0xB0: // 컨트롤 체인지
case 0xE0: // 피치 벤드
p += 2;
break;
case 0xC0: // 프로그램 체인지
case 0xD0: // 채널 애프터터치
p += 1;
break;
default:
p = trackEnd; // 알 수 없는 상태 → 트랙 종료(무한루프 방지)
break;
}
}
}
p = trackEnd; // 트랙 경계 보정
}
// ---- 틱 → 초 변환 ----
double usPerQuarter = bpmOverride > 0f
? 60000000.0 / bpmOverride
: (tempoMicros > 0 ? tempoMicros : 500000.0); // 기본 120 BPM
double secPerTick = usPerQuarter / 1000000.0 / ticksPerQuarter;
bool useLanes = lanePitches != null && lanePitches.Count > 0;
foreach (var raw in rawNotes)
{
int lane = 0;
if (useLanes)
{
lane = lanePitches.IndexOf(raw.pitch);
if (lane < 0) continue; // 레인에 매핑 안 된 음높이는 무시
}
notes.Add(new Note
{
Time = offset + (float)(raw.tick * secPerTick),
Lane = lane
});
}
notes.Sort((a, b) => a.Time.CompareTo(b.Time));
return notes;
}
// ---- 바이트 읽기 헬퍼 (MIDI는 빅엔디안) ----
private static bool MatchId(byte[] d, int p, char a, char b, char c, char e)
=> p + 4 <= d.Length && d[p] == a && d[p + 1] == b && d[p + 2] == c && d[p + 3] == e;
private static uint ReadUInt32(byte[] d, ref int p)
{
uint v = (uint)((d[p] << 24) | (d[p + 1] << 16) | (d[p + 2] << 8) | d[p + 3]);
p += 4;
return v;
}
private static int ReadUInt16(byte[] d, ref int p)
{
int v = (d[p] << 8) | d[p + 1];
p += 2;
return v;
}
// 가변 길이 수치(Variable-Length Quantity)
private static int ReadVarLen(byte[] d, ref int p)
{
int value = 0;
byte b;
do
{
b = d[p++];
value = (value << 7) | (b & 0x7F);
}
while ((b & 0x80) != 0 && p < d.Length);
return value;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4930b4a6230ad694da5ca148280f1d07

View File

@@ -3,22 +3,28 @@
public class Note
{
[HideInInspector] public float Time;
[HideInInspector] public float Time; // 판정 시각(초)
[HideInInspector] public int Lane; // 레인 인덱스 (단일 레인이면 0)
}
[CreateAssetMenu(fileName = "RhythmChart", menuName = "CatsRoom/RhythmChart")]
public class RhythmChart : ScriptableObject
{
public AudioClip SongClip; // 노래 파일
public float Bpm; // 분당 박자
public float Offset; // 첫 박자 시작 시각
public int NoteCount; // 노트 개수 (또는 곡 길이로 자동)
public List<Note> GenerateNotes() // BPM·offset으로 시간 목록 자동 생성
public AudioClip SongClip; // 노래 파일
public TextAsset MidiFile; // FL Studio에서 내보낸 .mid (확장자를 .bytes로 임포트)
public float Offset; // 곡 시작과 MIDI 0틱 사이 보정(초)
public float BpmOverride; // 0이면 MIDI 내장 템포 사용, >0이면 이 BPM으로 강제
public List<int> LanePitches = new(); // 비우면 모든 노트를 레인 0으로. 채우면 (index=레인) 매핑
// MIDI를 파싱해 노트 시간 목록 생성
public List<Note> GenerateNotes()
{
List<Note> notes = new List<Note>();
float interval = 60f / Bpm;
for (int i = 0; i < NoteCount; i++)
notes.Add(new Note { Time = Offset + interval * i });
return notes;
if (MidiFile == null)
{
Debug.LogWarning($"[RhythmChart] {name}: MidiFile이 비어 있습니다.");
return new List<Note>();
}
return MidiChartParser.Parse(MidiFile.bytes, Offset, BpmOverride, LanePitches);
}
}

View File

@@ -1,20 +1,39 @@
using System;
using System.Collections.Generic;
using Hovl;
using UnityEngine;
using UnityEngine.InputSystem;
public enum Result { Perfect, Good, Miss }
public enum Result { Perfect, Good, Bad, Miss }
public class RhythmManager : MonoBehaviour
{
[SerializeField] private AudioSource _audioSource;
[SerializeField] private RhythmChart _currentChart;
[SerializeField] private RhythmNoteSpawner _spawner;
[SerializeField] private float _leadTime = 2f; // 노트가 생성돼 판정선까지 흐르는 시간(초)
[Header("판정 윈도우 (초, 절대 시간차 기준)")]
[SerializeField] private float _perfectWindow = 0.05f;
[SerializeField] private float _goodWindow = 0.10f;
[SerializeField] private float _badWindow = 0.15f; // 이 밖이면 입력 무시(헛침)
[SerializeField] private GameObject StartButtonObj;
public float SongTime => _audioSource.time; // 모든 타이밍의 기준
private List<Note> SongNoteList;
private int _nextNoteIndex; // 다음에 소환할 노트 인덱스
private bool _isPlaying; //곡이 재생 중인지 (종료 감지용)
private Func<float> _songTimeProvider; // 노트에 넘길 시간 제공자 (매 스폰마다 람다 재생성 방지용 캐시)
private readonly List<RhythmNoteInstance> _activeNotes = new(); // 화면에 떠 있는(아직 처리 안 된) 노트들
// 점수 상태 (HUD가 읽음). 누적 로직은 RhythmScore가 전담
public RhythmScore Score { get; } = new();
public event Action OnSongStarted; // 곡 시작 (ScoreHud 표시 / ResultHud 숨김)
public event Action<Result> OnJudged; // 노트 하나가 판정될 때마다 (HUD 연출용)
public event Action<RhythmScore> OnScoreChanged; // 점수/콤보가 바뀔 때 (실시간 HUD)
public event Action<RhythmScore> OnSongFinished; // 곡이 끝났을 때 (결과창)
private void Awake()
{
@@ -35,7 +54,13 @@ private void Update()
return;
}
if (_isPlaying) SpawnDueNotes();
if (!_isPlaying) return;
SpawnDueNotes();
// 테스트용: 스페이스로 판정 (실제 VR에서는 컨트롤러/콜라이더 입력으로 교체)
if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
OnPlayerInput();
}
// SongTime 기준으로 소환할 때가 된 노트들을 순서대로 생성
@@ -47,7 +72,8 @@ private void SpawnDueNotes()
{
Note note = SongNoteList[_nextNoteIndex];
// spawnTime은 실제 프레임 시각이 아니라 이론값(note.Time - _leadTime)으로 줘야 보간이 정확
_spawner.SpawnNote(note, note.Time - _leadTime, _songTimeProvider, OnNoteMissed);
RhythmNoteInstance instance = _spawner.SpawnNote(note, note.Time - _leadTime, _songTimeProvider, OnNoteMissed);
_activeNotes.Add(instance);
_nextNoteIndex++;
}
}
@@ -62,6 +88,12 @@ public void StartSong()
{
SongNoteList = _currentChart.GenerateNotes();
_nextNoteIndex = 0;
_activeNotes.Clear();
Score.Reset();
OnSongStarted?.Invoke(); // ScoreHud 표시 / ResultHud 숨김
OnScoreChanged?.Invoke(Score); // HUD 초기화(0점)
StartButtonObj.SetActive(false);
_audioSource.Play();
_isPlaying = true;
@@ -73,25 +105,72 @@ public void StartSong()
// 곡 정지 + 주변음 복구
public void StopSong()
{
if (!_isPlaying) return; // 중복 호출 방지(결과창 두 번 뜨는 것 차단)
_audioSource.Stop();
_isPlaying = false;
if (SoundManager.Instance != null) SoundManager.Instance.ExitMinigameMode();
OnSongFinished?.Invoke(Score); // 결과창 표시
}
// 노트가 판정선을 지나쳐 스스로 Miss 처리될 때 호출
// 노트가 판정선을 지나쳐 스스로 Miss 처리될 때 호출 (노트는 이미 자기 파괴됨)
private void OnNoteMissed(RhythmNoteInstance note)
{
// TODO: 점수/콤보 시스템 연결 시 Miss 반영
_activeNotes.Remove(note);
ApplyJudge(Result.Miss);
Debug.Log("[Rhythm] Miss (지나침)");
}
public void OnPlayerInput()
{
if (!_isPlaying || _activeNotes.Count == 0) return;
// 판정선에 가장 가까운(시간차 최소) 노트 탐색
RhythmNoteInstance target = null;
float bestDiff = float.MaxValue;
foreach (RhythmNoteInstance note in _activeNotes)
{
float diff = Mathf.Abs(SongTime - note.HitTime);
if (diff < bestDiff)
{
bestDiff = diff;
target = note;
}
}
Result result = Judge(bestDiff);
if (result == Result.Miss) return; // 히트 범위 밖이면 입력 무시 (노트 소비 안 함)
// 성공 판정 → 히트 이펙트 + 노트 소비
if (result == Result.Perfect || result == Result.Good)
{
// 이펙트는 노트에서 분리돼 따로 재생되므로 노트를 바로 파괴해도 됨
if (target.TryGetComponent(out RhythmProjectile projectile))
projectile.Detonate();
}
_activeNotes.Remove(target);
Destroy(target.gameObject);
ApplyJudge(result);
Debug.Log($"[Rhythm] {result} (diff {bestDiff:F3}s)");
}
// 판정 결과를 점수에 반영하고 이벤트로 알림
private void ApplyJudge(Result result)
{
Score.Apply(result);
OnJudged?.Invoke(result);
OnScoreChanged?.Invoke(Score);
}
// diff = 절대 시간차(초). 윈도우 안이면 Perfect/Good/Bad, 밖이면 Miss(입력 무시)
public Result Judge(float diff)
{
if (diff <= _perfectWindow) return Result.Perfect;
if (diff <= _goodWindow) return Result.Good;
if (diff <= _badWindow) return Result.Bad;
return Result.Miss;
}
}

View File

@@ -2,14 +2,14 @@
public class RoomMoveButton : MonoBehaviour
{
[Header("이 버튼을 눌렀을 때 이동할 방 번호 입력")]
[Header("이 버튼을 눌렀을 때 이동할 방 번호 입력")]
[SerializeField] private int targetRoomNumber;
public void OnClickMoveRoom()
{
if (RoomRouteManager.Instance == null)
{
Debug.LogError("RoomRouteManager가 없습니다.");
Debug.LogError("RoomRouteManager가 없습니다.");
return;
}

View File

@@ -19,50 +19,50 @@ private void Update()
return;
}
// C키: 방문 안 한 방 중 랜덤 후보 뽑기
// C키: 방문 안 한 방 중 랜덤 후보 뽑기
if (Keyboard.current.cKey.wasPressedThisFrame)
{
currentChoices = RoomRouteManager.Instance.GetRandomNextRooms();
Debug.Log($"현재 선택 가능한 후보 개수: {currentChoices.Count}");
Debug.Log($"현재 선택 가능한 후보 개수: {currentChoices.Count}");
for (int i = 0; i < currentChoices.Count; i++)
{
Debug.Log($"{i + 1}번 선택지 → 방 번호: {currentChoices[i].roomNumber}, 씬 이름: {currentChoices[i].sceneName}");
Debug.Log($"{i + 1}번 선택지 → 방 번호: {currentChoices[i].roomNumber}, 씬 이름: {currentChoices[i].sceneName}");
}
}
// 1키: 첫 번째 후보 선택
// 1키: 첫 번째 후보 선택
if (Keyboard.current.digit1Key.wasPressedThisFrame)
{
MoveToChoice(0);
}
// 2키: 두 번째 후보 선택
// 2키: 두 번째 후보 선택
if (Keyboard.current.digit2Key.wasPressedThisFrame)
{
MoveToChoice(1);
}
// T키: 방문 상태 확인
// T키: 방문 상태 확인
if (Keyboard.current.tKey.wasPressedThisFrame)
{
Debug.Log($"방문한 방 개수: {RoomRouteManager.Instance.VisitedRoomCount} / {RoomRouteManager.Instance.TotalRoomCount}");
Debug.Log($"현재 방 번호: {RoomRouteManager.Instance.CurrentRoomNumber}");
Debug.Log($"방문한 방 개수: {RoomRouteManager.Instance.VisitedRoomCount} / {RoomRouteManager.Instance.TotalRoomCount}");
Debug.Log($"현재 방 번호: {RoomRouteManager.Instance.CurrentRoomNumber}");
}
// X키: 테스트용 방문 기록 초기화
// X키: 테스트용 방문 기록 초기화
if (Keyboard.current.xKey.wasPressedThisFrame)
{
Debug.Log("방문 기록 초기화");
Debug.Log("방문 기록 초기화");
currentChoices.Clear();
RoomRouteManager.Instance.ResetVisitedRooms();
}
// F키: 모든 방 방문 후 마지막 씬 이동 테스트
// F키: 모든 방 방문 후 마지막 씬 이동 테스트
if (Keyboard.current.fKey.wasPressedThisFrame)
{
Debug.Log("마지막 씬 이동 테스트");
Debug.Log("마지막 씬 이동 테스트");
RoomRouteManager.Instance.MoveToFinalScene();
}
}
@@ -71,19 +71,19 @@ private void MoveToChoice(int index)
{
if (currentChoices == null || currentChoices.Count == 0)
{
Debug.LogWarning("먼저 C키를 눌러 랜덤 후보를 뽑아야 합니다.");
Debug.LogWarning("먼저 C키를 눌러 랜덤 후보를 뽑아야 합니다.");
return;
}
if (index < 0 || index >= currentChoices.Count)
{
Debug.LogWarning("해당 번호의 선택지가 없습니다.");
Debug.LogWarning("해당 번호의 선택지가 없습니다.");
return;
}
int targetRoomNumber = currentChoices[index].roomNumber;
Debug.Log($"{index + 1}번 선택지 선택 → 방 {targetRoomNumber} 이동");
Debug.Log($"{index + 1}번 선택지 선택 → 방 {targetRoomNumber} 이동");
currentChoices.Clear();

View File

@@ -8,23 +8,23 @@ public class RoomRouteManager : MonoBehaviour
[System.Serializable]
public class RoomData
{
[Header("방 번호 입력")]
[Header("방 번호 입력")]
public int roomNumber;
[Header("이 방에 해당하는 Scene 이름 입력")]
[Header("이 방에 해당하는 Scene 이름 입력")]
public string sceneName;
}
[Header("전체 방 정보 입력")]
[Header("전체 방 정보 입력")]
[SerializeField] private List<RoomData> rooms = new List<RoomData>();
[Header("시작 방 번호 입력")]
[Header("시작 방 번호 입력")]
[SerializeField] private int startRoomNumber;
[Header("랜덤 선택지 개수")]
[Header("랜덤 선택지 개수")]
[SerializeField] private int randomChoiceCount = 2;
[Header("모든 방 방문 후 이동할 마지막 Scene 이름")]
[Header("모든 방 방문 후 이동할 마지막 Scene 이름")]
[SerializeField] private string finalSceneName;
private int _currentRoomNumber;
@@ -54,7 +54,7 @@ private void Awake()
}
}
// 방문하지 않은 방 전체 반환
// 방문하지 않은 방 전체 반환
public List<RoomData> GetUnvisitedRooms()
{
List<RoomData> result = new List<RoomData>();
@@ -70,7 +70,7 @@ public List<RoomData> GetUnvisitedRooms()
return result;
}
// 대화 선택지에 보여줄 랜덤 방 목록 반환
// 대화 선택지에 보여줄 랜덤 방 목록 반환
public List<RoomData> GetRandomNextRooms()
{
List<RoomData> unvisitedRooms = GetUnvisitedRooms();
@@ -89,18 +89,18 @@ public List<RoomData> GetRandomNextRooms()
return randomRooms;
}
// 버튼이나 대화 선택지에서 호출
// 버튼이나 대화 선택지에서 호출
public void MoveToRoom(int roomNumber)
{
if (SceneLoadManager.Instance == null)
{
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
return;
}
if (SceneLoadManager.Instance.IsChangingScene)
{
Debug.Log("이미 씬 이동 중입니다.");
Debug.Log("이미 씬 이동 중입니다.");
return;
}
@@ -108,19 +108,19 @@ public void MoveToRoom(int roomNumber)
if (targetRoom == null)
{
Debug.LogWarning($"방 정보를 찾을 수 없습니다. 방 번호: {roomNumber}");
Debug.LogWarning($"방 정보를 찾을 수 없습니다. 방 번호: {roomNumber}");
return;
}
if (_visitedRooms.Contains(roomNumber))
{
Debug.Log($"이미 방문한 방입니다. 방 번호: {roomNumber}");
Debug.Log($"이미 방문한 방입니다. 방 번호: {roomNumber}");
return;
}
if (string.IsNullOrEmpty(targetRoom.sceneName))
{
Debug.LogWarning($"방 {roomNumber}의 Scene 이름이 비어있습니다.");
Debug.LogWarning($"방 {roomNumber}의 Scene 이름이 비어있습니다.");
return;
}
@@ -130,14 +130,14 @@ public void MoveToRoom(int roomNumber)
SceneLoadManager.Instance.RequestSceneChange(targetRoom.sceneName);
}
// 랜덤 방 하나로 바로 이동하고 싶을 때 사용
// 랜덤 방 하나로 바로 이동하고 싶을 때 사용
public void MoveToRandomRoom()
{
List<RoomData> unvisitedRooms = GetUnvisitedRooms();
if (unvisitedRooms.Count <= 0)
{
Debug.Log("방문하지 않은 방이 없습니다.");
Debug.Log("방문하지 않은 방이 없습니다.");
if (IsAllRoomsVisited())
{
@@ -162,25 +162,25 @@ public void MoveToFinalScene()
{
if (!IsAllRoomsVisited())
{
Debug.Log("아직 모든 방을 방문하지 않았습니다.");
Debug.Log("아직 모든 방을 방문하지 않았습니다.");
return;
}
if (SceneLoadManager.Instance == null)
{
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
return;
}
if (SceneLoadManager.Instance.IsChangingScene)
{
Debug.Log("이미 씬 이동 중입니다.");
Debug.Log("이미 씬 이동 중입니다.");
return;
}
if (string.IsNullOrEmpty(finalSceneName))
{
Debug.LogWarning("마지막 Scene 이름이 비어있습니다.");
Debug.LogWarning("마지막 Scene 이름이 비어있습니다.");
return;
}

View File

@@ -3,7 +3,7 @@
public class RhythmNoteInstance : MonoBehaviour
{
public float HitTime; // 이 노트의 도달 시각
[HideInInspector] public float HitTime; // 이 노트의 도달 시각
[SerializeField] private float _missWindow = 0.15f; // 판정선을 이만큼 지나면 자동 Miss

View File

@@ -0,0 +1,37 @@
using Hovl;
using UnityEngine;
// Hovl의 HS_ProjectileMover(에셋)를 "수정하지 않고" 히트 이펙트를 코드로 터뜨리기 위한 서브클래스.
// 핵심: protected 멤버(hit/hitPS/projectilePS/collided)는 '파생 클래스'에서는 접근 가능하다.
// 그래서 에셋 원본은 그대로 두고, 충돌(Collision) 없이 판정 성공 시 Detonate()로 이펙트만 재생한다.
public class RhythmProjectile : HS_ProjectileMover
{
// 판정 성공 시 호출. 노트가 곧 Destroy되어도 이펙트가 보이도록 노트에서 분리 후 재생.
public void Detonate()
{
if (collided) return;
collided = true;
// 날아가는 동안의 트레일 파티클은 정지
if (projectilePS != null)
projectilePS.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
// 히트 이펙트 컨테이너(없으면 파티클 본체)를 노트에서 떼어내 현재 위치에서 재생
GameObject fx = hit != null ? hit : (hitPS != null ? hitPS.gameObject : null);
if (fx == null) return;
fx.transform.SetParent(null, true);
fx.transform.position = transform.position;
fx.SetActive(true);
if (hitPS != null)
{
hitPS.Clear(true);
hitPS.Play(true);
}
// 재생이 끝나면 분리한 이펙트도 정리
float life = hitPS != null ? hitPS.main.duration + hitPS.main.startLifetime.constantMax : 2f;
Destroy(fx, life);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9840334459414e240b44e1b0cfe4ebb2

View File

@@ -0,0 +1,59 @@
// 리듬게임 점수/콤보/판정수 누적 (Unity 비의존 순수 로직 - 테스트하기 쉬움)
public class RhythmScore
{
// 판정별 기본 점수
private const int PerfectPoint = 100;
private const int GoodPoint = 50;
private const int BadPoint = 10;
public int Score { get; private set; }
public int Combo { get; private set; }
public int MaxCombo { get; private set; }
public int PerfectCount { get; private set; }
public int GoodCount { get; private set; }
public int BadCount { get; private set; }
public int MissCount { get; private set; }
public int TotalJudged => PerfectCount + GoodCount + BadCount + MissCount;
// 판정 하나를 점수에 반영
public void Apply(Result result)
{
switch (result)
{
case Result.Perfect:
PerfectCount++;
Combo++;
Score += PerfectPoint + Combo; // 콤보가 쌓일수록 보너스 (연속 보상)
break;
case Result.Good:
GoodCount++;
Combo++;
Score += GoodPoint + Combo;
break;
case Result.Bad:
BadCount++;
Combo = 0; // 콤보 끊김
Score += BadPoint;
break;
case Result.Miss:
MissCount++;
Combo = 0; // 콤보 끊김
break;
}
if (Combo > MaxCombo) MaxCombo = Combo;
}
public void Reset()
{
Score = 0;
Combo = 0;
MaxCombo = 0;
PerfectCount = 0;
GoodCount = 0;
BadCount = 0;
MissCount = 0;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: adfd55d5980f58940ad4cb735db42f89

View File

@@ -0,0 +1,50 @@
using TMPro;
using UnityEngine;
// 곡 종료 후 결과창. 총점/판정수/정확도/등급/최대콤보를 표시한다.
public class RhythmResultHud : MonoBehaviour
{
[SerializeField] private RhythmManager _manager;
[SerializeField] private GameObject _root; // 결과창 루트
[SerializeField] private TMP_Text _scoreText;
[SerializeField] private TMP_Text _maxComboText;
[SerializeField] private TMP_Text _countText; // Perfect/Good/Bad/Miss 모아서 표시
private void Awake()
{
if (_root != null) _root.SetActive(false);
}
private void OnEnable()
{
if (_manager == null) return;
_manager.OnSongStarted += Hide;
_manager.OnSongFinished += Show;
}
private void OnDisable()
{
if (_manager == null) return;
_manager.OnSongStarted -= Hide;
_manager.OnSongFinished -= Show;
}
private void Hide()
{
if (_root != null) _root.SetActive(false); // 곡 시작/재시작 시 이전 결과창 숨김
}
private void Show(RhythmScore score)
{
if (_scoreText != null) _scoreText.text = $"{score.Score:N0}";
if (_maxComboText != null) _maxComboText.text = $"{score.MaxCombo}";
if (_countText != null)
_countText.text =
$"Perfect {score.PerfectCount}\n" +
$"Good {score.GoodCount}\n" +
$"Bad {score.BadCount}\n" +
$"Miss {score.MissCount}";
if (_root != null) _root.SetActive(true);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b187e420986956a4ba9407759ed4050f

View File

@@ -0,0 +1,56 @@
using TMPro;
using UnityEngine;
// 플레이 중 실시간 점수/콤보 HUD. RhythmManager 이벤트만 구독하고 표시만 한다.
public class RhythmScoreHud : MonoBehaviour
{
[SerializeField] private RhythmManager _manager;
[SerializeField] private GameObject _root; // HUD 루트 (곡 시작/종료에 켜고 끔)
[SerializeField] private TMP_Text _scoreText;
[SerializeField] private TMP_Text _comboText;
[SerializeField] private TMP_Text _judgeText; // Perfect/Good 등 최근 판정 표시(선택)
private void Awake()
{
if (_root != null) _root.SetActive(false); // 곡 시작 전엔 숨김
}
private void OnEnable()
{
if (_manager == null) return;
_manager.OnSongStarted += HandleSongStarted;
_manager.OnScoreChanged += HandleScoreChanged;
_manager.OnJudged += HandleJudged;
_manager.OnSongFinished += HandleSongFinished;
}
private void OnDisable()
{
if (_manager == null) return;
_manager.OnSongStarted -= HandleSongStarted;
_manager.OnScoreChanged -= HandleScoreChanged;
_manager.OnJudged -= HandleJudged;
_manager.OnSongFinished -= HandleSongFinished;
}
private void HandleSongStarted()
{
if (_root != null) _root.SetActive(true); // 곡 시작 시 실시간 HUD 표시
}
private void HandleScoreChanged(RhythmScore score)
{
if (_scoreText != null) _scoreText.text = $"SCORE {score.Score:N0}";
if (_comboText != null) _comboText.text = score.Combo > 0 ? $"{score.Combo}" : "";
}
private void HandleJudged(Result result)
{
if (_judgeText != null) _judgeText.text = result.ToString();
}
private void HandleSongFinished(RhythmScore score)
{
if (_root != null) _root.SetActive(false); // 곡 끝나면 실시간 HUD는 숨김(결과창이 대신 뜸)
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2d2c41822c633294a986c6803818303d

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 016b7abd765146448b6113c33897f846
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2f3e152888fa71247b0e4c40ea069d7d
guid: f17aaa526f39cef4a854fe7ea551e938
PrefabImporter:
externalObjects: {}
userData:

Binary file not shown.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 754edce0f04b6f9439e9e265191c3194
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

8
Assets/Hovl Studio.meta Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: 859e8a749e01ae7468b1b3ab458011ff
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 11
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/2DSceneGround.png
uploadId: 883376

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 50611382ffa50c54a944ab4b11b5f311
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 25800000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/LightingData.asset
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: e620f791dd3aad0468c5433f169817c6
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-0_comp_dir.png
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: bedbfe6fe9ff7914aad490bf2e9e3283
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 0
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 6
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-0_comp_light.exr
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: fa6be30dfbe92314c8d176a8eec4257b
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-1_comp_dir.png
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: 231f20cb5603bff409380ec0b03baf96
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 0
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 6
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-1_comp_light.exr
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: ef821dfa7c9bf544aa760c25c610cb6b
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-2_comp_dir.png
uploadId: 883376

View File

@@ -0,0 +1,91 @@
fileFormatVersion: 2
guid: 35c320ad9e9dc0d40bfa81b6cd7e2e8d
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 3
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 0
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 6
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/Lightmap-2_comp_light.exr
uploadId: 883376

View File

@@ -0,0 +1,92 @@
fileFormatVersion: 2
guid: eea823e4f8384c043ac514026f3ac126
TextureImporter:
fileIDToRecycleName:
8900000: generatedCubemap
externalObjects: {}
serializedVersion: 5
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 1
seamlessCubemap: 1
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 2
aniso: 0
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 2
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 2
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 100
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
vertices: []
indices:
edges: []
weights: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/ReflectionProbe-0.exr
uploadId: 883376

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 353e4ca07ddad704b8539591387bee38
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo files/SceneSmoke.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: d6851c279dc96cc469706280632c676c
timeCreated: 1536001560
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo projectiles
2D.unity
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: fbaf00a0124cb34408e401d2c9e82d44
timeCreated: 1536001560
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo projectiles
simple spawning.unity
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: d77992b5fd5ef82498c6286aa500bdd6
timeCreated: 1536001560
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Demo projectiles(Full
particles).unity
uploadId: 883376

View File

@@ -0,0 +1,145 @@
Asset Creator - Vladyslav Horobets (Hovl).
All that is in the folder "AAA Projectiles" can be used in commerce, even demo scene files.
-----------------------------------------------------
If you want to use post-effects like in the demo video:
https://youtu.be/hZSZ2Q8MF3k
Using:
1) Shaders
1.1)The "Use depth" on the material from the custom shaders is the Soft Particle Factor.
1.2)Use "Center glow"[MaterialToggle] only with particle system. This option is used to darken the main texture with a white texture (white is visible, black is invisible).
If you turn on this feature, you need to use "Custom vertex stream" (Uv0.Custom.xy) in tab "Render". And don't forget to use "Custom data" parameters in your PS.
1.3)The distortion shader only works with standard rendering. Delete (if exist) distortion particles from effects if you use LWRP or HDRP!
1.4)You can change the cutoff in all shaders (except Add_CenterGlow and Blend_CenterGlow ) using (Uv0.Custom.xy) in particle system.
2)Light.
2.1)You can disable light in the main effect component (delete light and disable light in PS).
Light strongly loads the game if you don't use light probes or something else.
3)Scripts
HS_ProjectileMover — Documentation
Description
HS_ProjectileMover controls the movement, collision behavior, and visual effects of projectile objects.
It handles projectile speed, hit effects, particle systems, detached VFX elements, and supports both destruction and pooling workflows.
The script is designed for VFX projectiles used in spells, bullets, energy blasts, or similar effects.
Main Features:
Moves projectile forward using Rigidbody velocity
Spawns hit effects on collision
Supports pooled projectiles (reuse instead of destroy)
Handles particle systems properly on impact
Allows detached particle effects to continue playing after collision
Automatically restores detached objects when projectile is reused
Key Parameters:
Speed -
Controls the forward velocity of the projectile.
Hit Offset -
Moves the hit effect slightly away from the surface normal to avoid clipping.
Use Fire Point Rotation -
If enabled, the hit effect rotation will match the fire point orientation.
Rotation Offset -
Optional rotation override applied to the hit effect.
Hit -
GameObject used as the hit effect container.
Hit PS -
Particle system played when the projectile collides.
Flash -
Optional muzzle flash object that detaches on spawn.
Projectile PS -
Main projectile particle system.
Detached -
Array of objects that contain particle systems (such as trails or smoke).
These objects detach on impact so their particles can finish playing naturally.
Components:
RB -
Rigidbody used for projectile movement.
Col -
Collider used for collision detection.
Light Source -
Optional light attached to the projectile.
Lifetime Settings
Not Destroy
If enabled, the projectile will be disabled instead of destroyed.
This allows it to be reused with an object pool.
Life Time
Maximum lifetime of the projectile if it does not hit anything.
Detached Life Time
How long detached particle objects remain alive after impact.
Collision Behavior
When the projectile collides:
Rigidbody movement is stopped
Light and collider are disabled
Projectile particle emission stops
Hit effect is positioned and played
Detached objects are unparented
Detached particle systems stop emitting but existing particles finish their lifetime
If Not Destroy is enabled:
The projectile will be disabled after the hit effect finishes
Detached objects will be restored when the projectile is reused
If Not Destroy is disabled:
The projectile will be destroyed after the hit effect duration
Detached objects will be destroyed after Detached Life Time
Detached Objects Logic:
Detached objects must be child objects of the projectile.
Each detached object can contain multiple particle systems.
On collision:
The object is unparented
Emission stops
Existing particles finish their lifetime
If pooling is enabled, the objects are restored to their original parent when the projectile is reactivated.
Typical Use Case:
Projectile Prefab Structure Example
Projectile
├── Mesh
├── Collider
├── Rigidbody
├── Projectile_PS
├── Flash
└── Detached_Trail
├── Smoke
└── Sparks
Pooling Support
When using an object pool:
Set:
Not Destroy = true
The projectile will be disabled instead of destroyed and can be reused safely.
Detached particle objects will automatically return to their original positions when the projectile is activated again.
Notes:
Detached objects should only contain particle systems.
Ensure Rigidbody and Collider references are assigned.
Projectile should face forward in the Z direction for correct movement.
4)Quality
4.1) For better sparks quality enable "Anisotropic textures: Forced On" in quality settings.
SUPPORT ASSET FOR BiRP, URP or HDRP is here --> Tools > RP changer for Hovl Studio Assets
Contact me if you have any questions.
My email: hovlstudio1@gmail.com

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 9037c8e5bfd08444291e6100663e92b8
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Demo scenes/Readme.txt
uploadId: 883376

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: ccc22a316744f104eb233bd6122a3ec3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Dragon
punch flash.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: a311f5de96916bf44bcb1dae61840939
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
1 nature arrow.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 4f2f9845677ab3f49be1179d6bd15b78
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
10 blue laser.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: d2bf40a98ef3560439f0badec6115db3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
11 orange arrow.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 55b36a9461ee12b4b9ed1546b95e8ba4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
12 slime.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 284aa8115675f03488377d544e50cbf5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
13 red laser.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 058c6c6f7bee2fc45a91fbdb528a9035
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
14 blue rapid.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: dfb11343e1f738848a39590c5e36c883
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
15 pink crystal.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: f326479f7cef0794d95f29731f4bbdb3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
16 fire.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 6c45d61ea68f82b4eb2c7f1ff7fdcb81
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
17 nova violet.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: df8bef245b1373047a1e1320f5ed6ac2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
18 nova orange.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 2ad5c1a0f3dfb2a479c897ce5c0c6d82
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
19 circle bomb.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 5b0ac738fb141244d96b28bbd07be221
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
2 electro.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 0254f8e683e300c4eb1b598011e9b7c8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
22 cute star.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: d629efdae70f475409893aa79aeafd1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
23 cube.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 918bed4b1a6905d4aaa25af94c2caa31
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
24 green explosion.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: d6042f989cc140942a980eadd962e2db
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
25 orange explosion.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 25f0a817a944f794d8af1e1ea326f16c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
26 blue crystal.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 28538e5931854494fbe4d98d2d4cd548
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
27 heart.prefab
uploadId: 883376

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: f82a1a892a042c1438f8f4704a37e11b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 100100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 315729
packageName: BIG Projectiles bundle
packageVersion: 3.1
assetPath: Assets/Hovl Studio/AAA Projectiles Vol 1/Prefabs/Flash and hits/Flash
3 black fire.prefab
uploadId: 883376

Some files were not shown because too many files have changed in this diff Show More