수정사항
This commit is contained in:
89
Assets/02_Scripts/Story/CharacterPoses.cs
Normal file
89
Assets/02_Scripts/Story/CharacterPoses.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// 캐릭터 본체에 붙는다. Pose(공통 감정 축) → 실제 그림 오브젝트 매핑.
|
||||
//
|
||||
// 왜 스프라이트만 갈아끼우지 않고 포즈마다 오브젝트를 따로 두는가:
|
||||
// 콜라이더 때문이다. 클릭 판정과 부위 터치(DialogTouchZone)는 포즈마다 다른 자리에 있어야 하는데,
|
||||
// SpriteRenderer의 sprite만 바꾸면 콜라이더는 그대로 남아 손이 엉뚱한 곳에 있게 된다.
|
||||
// 포즈를 통째로 켜고 끄면 그림과 판정이 항상 같이 움직인다.
|
||||
//
|
||||
// 배치:
|
||||
// MayaFey CharacterVoiceObject + DialogPlayer + CharacterPoses
|
||||
// ├── Pose_기본 SpriteRenderer + Collider2D (+ DialogTouchZone…)
|
||||
// ├── Pose_긍정 SpriteRenderer + Collider2D (+ DialogTouchZone…)
|
||||
// └── Pose_부루퉁 …
|
||||
//
|
||||
// 그 캐릭터에 없는 포즈를 요청하면 경고만 남기고 무시한다 —
|
||||
// 대사는 공통 축으로 쓰되 캐릭터마다 가진 그림이 다를 수 있기 때문이다.
|
||||
//
|
||||
// "오브젝트가 켜져 있다"와 "화면에 보인다"는 별개다. 캐릭터 본체는 계속 활성이어야
|
||||
// CharacterVoiceObject가 등록돼 대사가 찾을 수 있는데, 그렇다고 그림까지 보일 필요는 없다.
|
||||
// Start Pose를 '숨김'으로 두면 살아는 있되 안 보이는 상태가 되고, 이게 동행자(마요이)처럼
|
||||
// 장소에 서 있지 않고 대화 중에만 끼어드는 캐릭터의 기본 상태다.
|
||||
public class CharacterPoses : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
private struct Entry
|
||||
{
|
||||
[Tooltip("공통 감정 축")]
|
||||
public Pose Pose;
|
||||
|
||||
[Tooltip("이 포즈일 때 켤 오브젝트 (그림 + 콜라이더 + 터치존)")]
|
||||
public GameObject Object;
|
||||
}
|
||||
|
||||
[Tooltip("이 캐릭터가 가진 포즈들")]
|
||||
[SerializeField] private Entry[] _entries;
|
||||
|
||||
[Tooltip("시작할 때 켤 포즈.\n\n" +
|
||||
"'숨김'으로 두면 오브젝트는 살아 있되 화면에는 안 나온다 — 장소에 서 있는 인물이 아니라 " +
|
||||
"대화 중에만 끼어드는 동행자(마요이 등)가 이 경우다. 대사가 부를 때 나타났다가 " +
|
||||
"대화가 끝나면 다시 숨겨진다.\n\n" +
|
||||
"'변경 없음'이면 목록에서 실제 포즈인 첫 항목을 쓴다")]
|
||||
[SerializeField] private Pose _startPose = Pose.Keep;
|
||||
|
||||
// 지금 켜져 있는 포즈. DialogPlayer가 대화 전 상태를 기억했다가 되돌리는 데 쓴다.
|
||||
public Pose Current { get; private set; } = Pose.Hide;
|
||||
|
||||
// 인스펙터에서 여러 포즈를 켜 둔 채 저장해도 시작 시 한 장만 남게 정리한다
|
||||
private void Awake() => Apply(DefaultPose());
|
||||
|
||||
// 시작할 때 켤 포즈. 지정했으면 그대로, '변경 없음'이면 목록의 실제 포즈 첫 항목.
|
||||
// 인스펙터에서 배열에 항목을 추가하면 Pose가 0(변경 없음)으로 시작하므로,
|
||||
// 그걸 그대로 기본값으로 쓰면 아무것도 안 켜진 채 조용히 넘어간다. 그래서 건너뛴다.
|
||||
private Pose DefaultPose()
|
||||
{
|
||||
if (_startPose != Pose.Keep) return _startPose;
|
||||
|
||||
if (_entries != null)
|
||||
foreach (var e in _entries)
|
||||
if (e.Pose is not (Pose.Keep or Pose.Hide)) return e.Pose;
|
||||
return Pose.Hide;
|
||||
}
|
||||
|
||||
// Pose.Keep은 "변경 없음"이라 여기까지 오면 안 된다 — 호출하는 쪽에서 걸러낸다.
|
||||
public void Apply(Pose pose)
|
||||
{
|
||||
if (_entries == null || pose == Pose.Keep) return;
|
||||
|
||||
if (pose != Pose.Hide && !Has(pose))
|
||||
{
|
||||
Debug.LogWarning($"[CharacterPoses] {name}에 없는 포즈: {pose} — 무시");
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide면 전부 꺼진다 (어느 항목과도 일치하지 않으므로)
|
||||
foreach (var e in _entries)
|
||||
if (e.Object != null) e.Object.SetActive(e.Pose == pose);
|
||||
|
||||
Current = pose;
|
||||
}
|
||||
|
||||
private bool Has(Pose pose)
|
||||
{
|
||||
foreach (var e in _entries)
|
||||
if (e.Pose == pose) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Story/CharacterPoses.cs.meta
Normal file
2
Assets/02_Scripts/Story/CharacterPoses.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1afc24429162fa646b3e858362cab257
|
||||
@@ -1,20 +1,46 @@
|
||||
using UnityEngine;
|
||||
|
||||
// 장소 프리팹 안의 캐릭터 자리 하나.
|
||||
// 이 캐릭터가 지금 이 장소에서 걸 수 있는 대화(StoryBeat)가 하나라도 있으면 캐릭터를 켜고, 없으면 끈다.
|
||||
// 캐릭터 하나의 등장 자리.
|
||||
// 이 캐릭터가 지금 장소에서 걸 수 있는 대화(StoryBeat)가 하나라도 있으면 캐릭터를 켜고, 없으면 끈다.
|
||||
// 갱신은 LocationManager가 장소 입장 시 / 스토리 상태 변화 시 Refresh를 호출해서 이뤄진다.
|
||||
//
|
||||
// 슬롯은 장소 프리팹이 아니라 **씬**에 두고 LocationManager의 Character Slots에 연결한다.
|
||||
// 어느 장소에 나타날지는 오브젝트 배치가 아니라 StoryDatabase가 정한다 —
|
||||
// 배치까지 같은 사실을 표현하면 원본이 둘이 되어 "비트는 넣었는데 슬롯을 안 넣음" 같은
|
||||
// 어긋남이 생기기 때문이다. 캐릭터가 늘 같은 자리에 선다는 전제이고,
|
||||
// 장소마다 자리가 달라져야 하면 그때 LocationData에 앵커를 두고 옮기면 된다.
|
||||
//
|
||||
// 배치법: 이 컴포넌트는 항상 켜져 있는 빈 오브젝트에 붙이고,
|
||||
// 캐릭터 본체(CharacterVoiceObject + DialogPlayer)는 그 자식으로 두고 _character에 연결.
|
||||
// 참고: 조건 없는 잡담 비트를 하나 등록해 두면 그 캐릭터는 항상 등장한다.
|
||||
// 참고: 조건 없는 잡담 비트를 하나 등록해 두면 그 캐릭터는 그 장소에 항상 등장한다.
|
||||
public class CharacterSlot : MonoBehaviour
|
||||
{
|
||||
[Tooltip("캐릭터 본체 (CharacterVoiceObject + DialogPlayer가 붙은 자식 오브젝트)")]
|
||||
[SerializeField] private CharacterVoiceObject _character;
|
||||
|
||||
[Tooltip("켜면 걸 대화가 없어도 오브젝트를 끄지 않는다.\n\n" +
|
||||
"대화 중에만 끼어드는 동행자(마요이 등)용이다. 오브젝트가 살아 있어야 " +
|
||||
"CharacterVoiceObject가 등록돼 대사가 포즈·보이스로 찾을 수 있는데, " +
|
||||
"그렇다고 화면에 서 있어야 하는 건 아니다 — 화면 표시는 CharacterPoses의 " +
|
||||
"Start Pose를 '숨김'으로 두어 따로 정한다.\n\n" +
|
||||
"이 인물이 그 장소의 대화 상대가 되는 때(예: 구치소에 갇힘)에는 " +
|
||||
"그 장소·인물의 StoryBeat를 조건과 함께 등록하면 된다 — " +
|
||||
"그러면 아래 IsPresent가 켜지면서 「이야기한다」 대상이 된다")]
|
||||
[SerializeField] private bool _alwaysActive;
|
||||
|
||||
// 이 슬롯의 캐릭터에게 지금 걸 수 있는 대화가 있는가 = 이 장소의 대화 상대인가.
|
||||
// 동행자 슬롯은 오브젝트가 켜져 있어도 이 값이 false라 「이야기한다」 대상이 되지 않는다.
|
||||
public bool IsPresent { get; private set; }
|
||||
|
||||
// 이 슬롯 캐릭터의 대화 재생기 (캐릭터 본체에 같이 붙어 있다)
|
||||
public DialogPlayer Player => _character != null ? _character.GetComponent<DialogPlayer>() : null;
|
||||
|
||||
public void Refresh(StoryDatabase database, LocationData location)
|
||||
{
|
||||
IsPresent = false;
|
||||
if (_character == null || _character.Character == null) return;
|
||||
_character.gameObject.SetActive(database.HasPlayableBeat(location, _character.Character));
|
||||
|
||||
IsPresent = database.HasPlayableBeat(location, _character.Character);
|
||||
_character.gameObject.SetActive(IsPresent || _alwaysActive);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user