Reapply "Merge pull request '리듬게임' (#5) from CatsRhythmGame into main"

This reverts commit d7d5519fbf.
This commit is contained in:
skrwns304@gmail.com
2026-06-16 12:26:10 +09:00
parent d7d5519fbf
commit c92e9f648a
2320 changed files with 251716 additions and 154 deletions

View File

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

View File

@@ -0,0 +1,43 @@
using System;
using System.Threading;
using UnityEngine;
public static class Util
{
/// 특정 시간(초) 후에 액션을 실행
public static async Awaitable RunDelayed(float delay, Action action, CancellationToken token = default)
{
try
{
// 유니티 전용 비동기 대기
await Awaitable.WaitForSecondsAsync(delay, token);
// 함수 실행
action?.Invoke();
}
catch (OperationCanceledException)
{
// 취소되었을 때의 처리 (필요 시)
}
}
/// 다음 프레임에 액션을 실행
public static async Awaitable RunNextFrame(Action action, CancellationToken token = default)
{
try
{
await Awaitable.EndOfFrameAsync(token);
action?.Invoke();
}
catch (OperationCanceledException) { }
}
public static float NormalizeAngle(float angle)
{
while (angle > 180)
angle -= 360;
while (angle < -180)
angle += 360;
return angle;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 414cb910207b7644a8bbb9be341333df

View File

@@ -0,0 +1,184 @@
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; // 트랙 경계 보정
}
// [임시 디버그] MIDI에 실제로 들어있는 고유 음높이 확인 (LanePitches 번호 맞춘 뒤 삭제)
var uniquePitches = new HashSet<int>();
foreach (var raw in rawNotes) uniquePitches.Add(raw.pitch);
var sortedPitches = new List<int>(uniquePitches);
sortedPitches.Sort();
Debug.Log($"[MidiChartParser] 발견된 고유 pitch: {string.Join(", ", sortedPitches)} (노트 {rawNotes.Count}개)");
// ---- 틱 → 초 변환 ----
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,50 @@
using System;
using System.Collections.Generic;
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; // 노트가 생성돼 판정선까지 흐르는 시간(초)
public float SongTime => _audioSource.time; // 모든 타이밍의 기준
[SerializeField] private float _leadTime = 1f; // 노트가 생성돼 판정선까지 흐르는 시간(초)
[Header("판정 윈도우 (초, 절대 시간차 기준)")]
[SerializeField] private float _perfectWindow = 0.05f;
[SerializeField] private float _goodWindow = 0.10f;
[SerializeField] private float _badWindow = 0.15f; // 이 밖이면 입력 무시(헛침)
[Header("효과음")]
[SerializeField] private AudioClip _hitSfx; // 내려칠 때마다 재생 (판정과 무관, 헛침 포함)
[Header("오토플레이 (아이템)")]
[SerializeField] private bool _autoPlay; // 켜지면 모든 노트를 판정선 도달 순간 자동 Perfect 처리
[SerializeField] private RhythmStick[] _autoPlaySticks; // 인덱스 = Note.Lane. 오토플레이 시 자동으로 휘두를 스틱들
[SerializeField] private GameObject StartButtonObj;
// 모든 타이밍의 기준. 오디오 클럭(dspTime) 기반이라 리드인 동안 음수(-leadTime→0)로 흐른다
public float SongTime => (float)(AudioSettings.dspTime - _dspSongStart);
private List<Note> SongNoteList;
private int _nextNoteIndex; // 다음에 소환할 노트 인덱스
private bool _isPlaying; //곡이 재생 중인지 (종료 감지용)
private double _dspSongStart; // 오디오가 실제 시작되는 dsp 시각 (= SongTime 0 지점)
private float _clipLength; // 곡 길이 캐시 (종료 감지용)
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 RhythmCat[] _rhythmCats;
private void Awake()
{
@@ -24,18 +54,78 @@ private void Awake()
private void Start()
{
ChangeSong();
InputManager.Instance.OnKey_Left_Event += OnPlayerInput;
InputManager.Instance.OnKey_Right_Event += OnPlayerInput;
_rhythmCats = FindObjectsByType<RhythmCat>(FindObjectsSortMode.None);
}
private void Update()
{
//재생 중이던 곡이 끝나면 주변음 복구
if (_isPlaying && !_audioSource.isPlaying)
if (!_isPlaying) return;
//곡이 끝나면(리드인 지나 곡 길이 도달) 정지·주변음 복구
if (SongTime >= _clipLength)
{
StopSong();
return;
}
if (_isPlaying) SpawnDueNotes();
SpawnDueNotes();
if (_autoPlay)
{
DriveAutoPlaySticks(); // 다가오는 노트에 맞춰 스틱을 들었다 내림 (판정 전에 호출)
AutoHitDueNotes(); // 판정선 도달한 노트 자동 Perfect
}
}
// 오토플레이: 레인별로 가장 임박한 노트에 맞춰 스틱을 휘두름
private void DriveAutoPlaySticks()
{
if (_autoPlaySticks == null) return;
for (int lane = 0; lane < _autoPlaySticks.Length; lane++)
{
RhythmStick stick = _autoPlaySticks[lane];
if (stick == null) continue;
// 이 레인에서 아직 안 친(미래의) 노트 중 가장 가까운 타격 시각
float nextHit = float.PositiveInfinity;
foreach (RhythmNoteInstance note in _activeNotes)
{
int noteLane = Mathf.Clamp(note.Lane, 0, _autoPlaySticks.Length - 1);
if (noteLane != lane) continue;
if (note.HitTime < SongTime) continue; // 이미 지난(곧 처리될) 노트 제외
if (note.HitTime < nextHit) nextHit = note.HitTime;
}
stick.Drive(nextHit - SongTime); // 다가오는 노트 없으면 +∞ → 대기 자세
}
}
// 오토플레이: 판정선에 도달한(HitTime을 지난) 노트를 자동으로 Perfect 처리
private void AutoHitDueNotes()
{
// 뒤에서부터 순회해야 처리한 노트를 안전하게 리스트에서 제거할 수 있다
for (int i = _activeNotes.Count - 1; i >= 0; i--)
{
RhythmNoteInstance note = _activeNotes[i];
if (SongTime < note.HitTime) continue; // 아직 판정선 도달 전이면 건너뜀
// 수동 입력과 동일한 연출: 내려치는 효과음 + 히트 이펙트
if (_hitSfx != null && SoundManager.Instance != null)
SoundManager.Instance.PlaySFX(_hitSfx);
if (note.TryGetComponent(out RhythmProjectile projectile))
projectile.Detonate();
_activeNotes.RemoveAt(i);
Destroy(note.gameObject);
ApplyJudge(Result.Perfect); // 시간차 ≈ 0 → 항상 Perfect
}
}
// SongTime 기준으로 소환할 때가 된 노트들을 순서대로 생성
@@ -47,7 +137,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++;
}
}
@@ -57,41 +148,125 @@ public void ChangeSong()
_audioSource.clip = _currentChart.SongClip;
}
// 오토플레이 아이템 사용: 곡 시작 전(또는 도중)에 호출하면 남은 노트가 전부 자동 Perfect
public void EnableAutoPlay() => _autoPlay = true;
// 곡 재생 + 채보 로드
public void StartSong()
{
SongNoteList = _currentChart.GenerateNotes();
_nextNoteIndex = 0;
_activeNotes.Clear();
Score.Reset();
OnSongStarted?.Invoke(); // ScoreHud 표시 / ResultHud 숨김
OnScoreChanged?.Invoke(Score); // HUD 초기화(0점)
StartButtonObj.SetActive(false);
_clipLength = _audioSource.clip != null ? _audioSource.clip.length : 0f;
// 리드인: 지금부터 _leadTime 뒤에 오디오 시작.
// 그 사이 SongTime이 -_leadTime→0으로 흘러 첫 노트(0초 부근)도 충분히 날아온다
_dspSongStart = AudioSettings.dspTime + _leadTime;
_audioSource.PlayScheduled(_dspSongStart);
_audioSource.Play();
_isPlaying = true;
//BGM·환경음을 낮춰 리듬게임 소리만 들리게
if (SoundManager.Instance != null) SoundManager.Instance.EnterMinigameMode();
for(int i=0;i<_rhythmCats.Length;i++)
{
_rhythmCats[i].Dance(i*1f);
}
}
// 곡 정지 + 주변음 복구
public void StopSong()
{
if (!_isPlaying) return; // 중복 호출 방지(결과창 두 번 뜨는 것 차단)
_audioSource.Stop();
_isPlaying = false;
if (_autoPlay && _autoPlaySticks != null) // 곡 끝나면 들려있던 스틱 대기 자세로 복귀
foreach (RhythmStick stick in _autoPlaySticks)
if (stick != null) stick.ResetPose();
_autoPlay = false; // 아이템 효과는 한 곡만 — 곡이 끝나면 자동 해제
if (SoundManager.Instance != null) SoundManager.Instance.ExitMinigameMode();
OnSongFinished?.Invoke(Score); // 결과창 표시
for(int i=0;i<_rhythmCats.Length;i++)
{
_rhythmCats[i].DanceStop();
}
}
// 노트가 판정선을 지나쳐 스스로 Miss 처리될 때 호출
// 노트가 판정선을 지나쳐 스스로 Miss 처리될 때 호출 (노트는 이미 자기 파괴됨)
private void OnNoteMissed(RhythmNoteInstance note)
{
// TODO: 점수/콤보 시스템 연결 시 Miss 반영
_activeNotes.Remove(note);
ApplyJudge(Result.Miss);
Debug.Log("[Rhythm] Miss (지나침)");
}
public void OnPlayerInput()
{
if (!_isPlaying) return;
// 판정과 무관하게 내려칠 때마다 효과음 (헛침 포함)
if (_hitSfx != null && SoundManager.Instance != null)
SoundManager.Instance.PlaySFX(_hitSfx);
if (_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

@@ -11,6 +11,10 @@ public class InputManager : MonoBehaviour, GameInput.IPlayerActions
// ─── 입력 이벤트들 (PlayerController 등이 구독) ──────────────────────
public event Action OnJump_Event; // 한 번씩 (눌렀을 때)
//키보드로 테스트용
public event Action OnKey_Left_Event;
public event Action OnKey_Right_Event;
private void Awake()
{
@@ -39,4 +43,16 @@ public void OnJump(InputAction.CallbackContext ctx)
if (ctx.phase == InputActionPhase.Started)
OnJump_Event?.Invoke();
}
public void OnKey_Left(InputAction.CallbackContext ctx)
{
if (ctx.phase == InputActionPhase.Started)
OnKey_Left_Event?.Invoke();
}
public void OnKey_Right(InputAction.CallbackContext ctx)
{
if (ctx.phase == InputActionPhase.Started)
OnKey_Right_Event?.Invoke();
}
}

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

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

View File

@@ -0,0 +1,21 @@
using UnityEngine;
public class RhythmCat : MonoBehaviour
{
Animator anim;
private void Awake()
{
anim = GetComponent<Animator>();
}
public void Dance(float delay)
{
_ = Util.RunDelayed(delay,()=>{anim.SetBool("Dance",true);},this.destroyCancellationToken);
}
public void DanceStop()
{
anim.SetBool("Dance",false);
}
}

View File

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

View File

@@ -3,7 +3,8 @@
public class RhythmNoteInstance : MonoBehaviour
{
public float HitTime; // 이 노트의 도달 시각
[HideInInspector] public float HitTime; // 이 노트의 도달 시각
[HideInInspector] public int Lane; // 이 노트의 레인 (오토플레이 스틱 매칭용)
[SerializeField] private float _missWindow = 0.15f; // 판정선을 이만큼 지나면 자동 Miss
@@ -14,13 +15,14 @@ public class RhythmNoteInstance : MonoBehaviour
private Action<RhythmNoteInstance> _onMiss; // 지나쳐서 Miss 났을 때 통지
// 스폰 시 목적지·타이밍 주입 (B 방식)
public void Setup(Vector3 start, Vector3 target, float spawnTime, float hitTime,
public void Setup(Vector3 start, Vector3 target, float spawnTime, float hitTime, int lane,
Func<float> songTime, Action<RhythmNoteInstance> onMiss = null)
{
_start = start;
_target = target;
_spawnTime = spawnTime;
HitTime = hitTime;
Lane = lane;
_songTime = songTime;
_onMiss = onMiss;

View File

@@ -3,17 +3,38 @@
public class RhythmNoteSpawner : MonoBehaviour
{
[SerializeField] private RhythmNoteInstance _notePrefab; // 생성할 노트 프리팹
// 레인(손)별 설정 - 전용 프리팹 + 가로 위치 오프셋
[Serializable]
public class LaneVisual
{
public RhythmNoteInstance Prefab; // 이 레인 전용 노트 프리팹 (왼손/오른손 다른 모양)
public Vector3 Offset; // 스폰/판정 위치 가로 오프셋 (왼손 -x, 오른손 +x 등)
}
[SerializeField] private RhythmNoteInstance _notePrefab; // 기본 프리팹 (레인 전용 미지정 시 사용)
[SerializeField] private Transform _spawnPoint; // 노트가 생겨나는 위치(미지정 시 자기 위치)
[SerializeField] private Transform _judgmentLine; // 목적지(판정선)
[SerializeField] private LaneVisual[] _lanes; // 인덱스 = Note.Lane (RhythmChart.LanePitches 순서와 일치)
// 노트 프리팹 생성, 목적지·타이밍 주입
// 노트 생성, 목적지·타이밍 주입. 레인에 따라 프리팹/위치만 다르게(판정은 동일)
public RhythmNoteInstance SpawnNote(Note note, float spawnTime,
Func<float> songTime, Action<RhythmNoteInstance> onMiss = null)
{
Transform origin = _spawnPoint != null ? _spawnPoint : transform;
RhythmNoteInstance instance = Instantiate(_notePrefab, origin.position, origin.rotation, transform);
instance.Setup(origin.position, _judgmentLine.position, spawnTime, note.Time, songTime, onMiss);
// 레인 범위 밖이면 기본 프리팹/오프셋 0
LaneVisual lane = (_lanes != null && note.Lane >= 0 && note.Lane < _lanes.Length)
? _lanes[note.Lane] : null;
RhythmNoteInstance prefab = (lane != null && lane.Prefab != null) ? lane.Prefab : _notePrefab;
// 오프셋을 origin 방향 기준으로 적용해 레인을 평행 이동(start·target 동일 오프셋이라 경로가 곧음)
Vector3 worldOffset = lane != null ? origin.rotation * lane.Offset : Vector3.zero;
Vector3 start = origin.position + worldOffset;
Vector3 target = _judgmentLine.position + worldOffset;
RhythmNoteInstance instance = Instantiate(prefab, start, origin.rotation, transform);
instance.Setup(start, target, spawnTime, note.Time, note.Lane, songTime, onMiss);
return instance;
}
}

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,99 @@
using UnityEngine;
// 오토플레이용 스틱 포즈 드라이버.
// 매니저가 매 프레임 Drive(dt)를 호출하면 dt(타격까지 남은 시간)에 따라 회전을 세팅한다.
// 모션: 대기 → (윈드업) 위로 들기 → (스트라이크) 내려치며 대기 자세보다 _strikeOvershoot 만큼 더 깊이 →
// (팔로스루) 대기 자세로 부드럽게 복귀.
// 노트는 타격 순간 파괴돼 그 뒤 dt가 끊기므로, 팔로스루만 Time.deltaTime으로 스틱이 자체 처리한다.
public class RhythmStick : MonoBehaviour
{
[Tooltip("들어올릴 때 회전할 로컬 축 (예: 손목 꺾이는 축)")]
[SerializeField] private Vector3 _raiseAxis = Vector3.right;
[Tooltip("대기 자세에서 위로 들어올리는 각도(도)")]
[SerializeField] private float _raiseAngle = 50f;
[Tooltip("타격 시 대기 자세보다 더 깊이 내려가는 각도(도)")]
[SerializeField] private float _strikeOvershoot = 10f;
[Tooltip("타격 몇 초 전부터 들어올리기 시작하는지")]
[SerializeField] private float _windupTime = 0.1f;
[Tooltip("내려치는 데 걸리는 시간(초). _windupTime 보다 작아야 한다")]
[SerializeField] private float _strikeTime = 0.04f;
[Tooltip("타격 후 대기 자세로 되돌아오는 시간(초)")]
[SerializeField] private float _recoverTime = 0.08f;
private Quaternion _restRot; // 대기 자세
private Quaternion _raisedRot; // 들어올린 자세
private Quaternion _overshootRot; // 타격 시 더 깊이 내려간 자세
private bool _armed; // 내려치는 중 → 다음 프레임 타격 예정
private bool _recovering; // 타격 후 복귀 중
private float _recoverElapsed;
private Quaternion _recoverFrom; // 복귀 시작 회전(스냅 방지)
private void Awake()
{
_restRot = transform.localRotation;
Vector3 axis = _raiseAxis.normalized;
_raisedRot = _restRot * Quaternion.AngleAxis(_raiseAngle, axis);
_overshootRot = _restRot * Quaternion.AngleAxis(-_strikeOvershoot, axis); // 반대 방향 = 더 내려감
}
// dt = 다음 타격까지 남은 시간(초). 다가오는 노트가 없으면 +∞.
public void Drive(float dt)
{
// 1) 임박한 스윙(윈드업~타격)이 최우선
if (dt > 0f && dt <= _windupTime)
{
_recovering = false;
_armed = true; // 곧 타격함
if (dt > _strikeTime)
{
// 들어올리는 구간: dt가 _windupTime→_strikeTime 으로 줄며 rest→raised
float u = Mathf.InverseLerp(_windupTime, _strikeTime, dt);
transform.localRotation = Quaternion.Slerp(_restRot, _raisedRot, Mathf.SmoothStep(0f, 1f, u));
}
else
{
// 내려치는 구간: dt가 _strikeTime→0 으로 줄며 raised→overshoot (dt=0에 가장 깊이)
float s = Mathf.InverseLerp(_strikeTime, 0f, dt);
transform.localRotation = Quaternion.Slerp(_raisedRot, _overshootRot, Mathf.SmoothStep(0f, 1f, s));
}
return;
}
// 2) 방금 타격이 끝났다면(노트 파괴로 dt가 끊김) 팔로스루 시작
if (_armed)
{
_armed = false;
_recovering = true;
_recoverElapsed = 0f;
_recoverFrom = transform.localRotation; // 현재 위치에서 복귀(스냅 방지)
}
// 3) 팔로스루: 현재 → 대기 자세로 부드럽게
if (_recovering)
{
_recoverElapsed += Time.deltaTime;
float r = _recoverTime > 0f ? Mathf.Clamp01(_recoverElapsed / _recoverTime) : 1f;
transform.localRotation = Quaternion.Slerp(_recoverFrom, _restRot, Mathf.SmoothStep(0f, 1f, r));
if (r >= 1f) _recovering = false;
return;
}
// 4) 평상시 대기 자세
transform.localRotation = _restRot;
}
// 즉시 대기 자세로 되돌림 (오토플레이 종료 시 호출)
public void ResetPose()
{
_armed = false;
_recovering = false;
transform.localRotation = _restRot;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6ae74de6faba37f4ca52b1c6177a98bf

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

View File

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

View File

@@ -0,0 +1,159 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1101 &-7037429233185517977
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 1
m_ConditionEvent: Dance
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 7364389275174809312}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1107 &-6722103551222370508
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 7364389275174809312}
m_Position: {x: 560, y: 60, z: 0}
- serializedVersion: 1
m_State: {fileID: -2066135457659248307}
m_Position: {x: 310, y: 60, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: -2066135457659248307}
--- !u!1101 &-4038710398895558165
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 2
m_ConditionEvent: Dance
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: -2066135457659248307}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1102 &-2066135457659248307
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Idle
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: -7037429233185517977}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 0}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: CatAnimController
serializedVersion: 5
m_AnimatorParameters:
- m_Name: Dance
m_Type: 4
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -6722103551222370508}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1102 &7364389275174809312
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Dance
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: -4038710398895558165}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 510ba0cd20bbf4f41b2d9227ab2c397e, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7faadcc5b471ee64f966a1c28ba20fd3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 510ba0cd20bbf4f41b2d9227ab2c397e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

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: 6df6ce53099740a4da7e7770a360c63d
guid: f17aaa526f39cef4a854fe7ea551e938
PrefabImporter:
externalObjects: {}
userData:

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 64d706dccf193ad4c9250a92f1782af8
guid: 21e36439b68755f47b8a18f88c2b9ce4
PrefabImporter:
externalObjects: {}
userData:

View File

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

View File

@@ -1,8 +1,8 @@
fileFormatVersion: 2
guid: 076ebe501640a6847a4e4544170534d9
guid: 3572846cef94ee149a9f46f38903a4d2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

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

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 076ebe501640a6847a4e4544170534d9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8a4a85f29dd93ba4cbb7bbfc3aedcfee
guid: 0a5f97bd2867c6948aee4084e4ff6f3f
TextScriptImporter:
externalObjects: {}
userData:

Binary file not shown.

View File

@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 0e08aeecbf5318f48b313c36c73fec53
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 0
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1 +0,0 @@
지울것

Binary file not shown.

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d6b71773bcf77be4bbf06915c1cabb06
guid: bfc25b7b5f6b3bd49a801d28d4367354
AudioImporter:
externalObjects: {}
serializedVersion: 8

View File

@@ -100,6 +100,24 @@ public @GameInput()
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Key_Left"",
""type"": ""Button"",
""id"": ""5afd9129-22e8-4e22-9843-f936922fc2a9"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
},
{
""name"": ""Key_Right"",
""type"": ""Button"",
""id"": ""5d342104-f81c-46cf-af37-f9388136115b"",
""expectedControlType"": """",
""processors"": """",
""interactions"": """",
""initialStateCheck"": false
}
],
""bindings"": [
@@ -113,6 +131,28 @@ public @GameInput()
""action"": ""Jump"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""8de0c981-8048-4b3e-baf8-d77cf3dbcc39"",
""path"": ""<Keyboard>/leftArrow"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Key_Left"",
""isComposite"": false,
""isPartOfComposite"": false
},
{
""name"": """",
""id"": ""e2dedd13-89db-4921-8a87-303eaded5f2e"",
""path"": ""<Keyboard>/rightArrow"",
""interactions"": """",
""processors"": """",
""groups"": """",
""action"": ""Key_Right"",
""isComposite"": false,
""isPartOfComposite"": false
}
]
}
@@ -122,6 +162,8 @@ public @GameInput()
// Player
m_Player = asset.FindActionMap("Player", throwIfNotFound: true);
m_Player_Jump = m_Player.FindAction("Jump", throwIfNotFound: true);
m_Player_Key_Left = m_Player.FindAction("Key_Left", throwIfNotFound: true);
m_Player_Key_Right = m_Player.FindAction("Key_Right", throwIfNotFound: true);
}
~@GameInput()
@@ -203,6 +245,8 @@ public int FindBinding(InputBinding bindingMask, out InputAction action)
private readonly InputActionMap m_Player;
private List<IPlayerActions> m_PlayerActionsCallbackInterfaces = new List<IPlayerActions>();
private readonly InputAction m_Player_Jump;
private readonly InputAction m_Player_Key_Left;
private readonly InputAction m_Player_Key_Right;
/// <summary>
/// Provides access to input actions defined in input action map "Player".
/// </summary>
@@ -219,6 +263,14 @@ public struct PlayerActions
/// </summary>
public InputAction @Jump => m_Wrapper.m_Player_Jump;
/// <summary>
/// Provides access to the underlying input action "Player/Key_Left".
/// </summary>
public InputAction @Key_Left => m_Wrapper.m_Player_Key_Left;
/// <summary>
/// Provides access to the underlying input action "Player/Key_Right".
/// </summary>
public InputAction @Key_Right => m_Wrapper.m_Player_Key_Right;
/// <summary>
/// Provides access to the underlying input action map instance.
/// </summary>
public InputActionMap Get() { return m_Wrapper.m_Player; }
@@ -247,6 +299,12 @@ public void AddCallbacks(IPlayerActions instance)
@Jump.started += instance.OnJump;
@Jump.performed += instance.OnJump;
@Jump.canceled += instance.OnJump;
@Key_Left.started += instance.OnKey_Left;
@Key_Left.performed += instance.OnKey_Left;
@Key_Left.canceled += instance.OnKey_Left;
@Key_Right.started += instance.OnKey_Right;
@Key_Right.performed += instance.OnKey_Right;
@Key_Right.canceled += instance.OnKey_Right;
}
/// <summary>
@@ -261,6 +319,12 @@ private void UnregisterCallbacks(IPlayerActions instance)
@Jump.started -= instance.OnJump;
@Jump.performed -= instance.OnJump;
@Jump.canceled -= instance.OnJump;
@Key_Left.started -= instance.OnKey_Left;
@Key_Left.performed -= instance.OnKey_Left;
@Key_Left.canceled -= instance.OnKey_Left;
@Key_Right.started -= instance.OnKey_Right;
@Key_Right.performed -= instance.OnKey_Right;
@Key_Right.canceled -= instance.OnKey_Right;
}
/// <summary>
@@ -308,5 +372,19 @@ public interface IPlayerActions
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
void OnJump(InputAction.CallbackContext context);
/// <summary>
/// Method invoked when associated input action "Key_Left" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
/// </summary>
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
void OnKey_Left(InputAction.CallbackContext context);
/// <summary>
/// Method invoked when associated input action "Key_Right" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
/// </summary>
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
void OnKey_Right(InputAction.CallbackContext context);
}
}

View File

@@ -14,6 +14,24 @@
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Key_Left",
"type": "Button",
"id": "5afd9129-22e8-4e22-9843-f936922fc2a9",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
},
{
"name": "Key_Right",
"type": "Button",
"id": "5d342104-f81c-46cf-af37-f9388136115b",
"expectedControlType": "",
"processors": "",
"interactions": "",
"initialStateCheck": false
}
],
"bindings": [
@@ -27,6 +45,28 @@
"action": "Jump",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "8de0c981-8048-4b3e-baf8-d77cf3dbcc39",
"path": "<Keyboard>/leftArrow",
"interactions": "",
"processors": "",
"groups": "",
"action": "Key_Left",
"isComposite": false,
"isPartOfComposite": false
},
{
"name": "",
"id": "e2dedd13-89db-4921-8a87-303eaded5f2e",
"path": "<Keyboard>/rightArrow",
"interactions": "",
"processors": "",
"groups": "",
"action": "Key_Right",
"isComposite": false,
"isPartOfComposite": false
}
]
}

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:

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