지난 이야기

토큰이 다 닳때까지 구현을 시도했지만, 결국 제대로 완성하지 못하고 끝났었다.
회복된 토큰과 함께 다시 달려보자.
나는 일하지 않는다(?)
- 저스트 회피 후 터치 시 적을 향해 달려가고, 로그를 보면 표식도 쌓이는 것 같아. 하지만 타격하며 표식을 새긴 후, 다시 원래 위치(회피 시 대시하는 마지막 지점)로 돌아와야 하는데, 계속 돌진한 자리에 멈춰.
- Enemy 위에 표식 수가 나타나지 않아.
스크립트를 읽고 원인을 분석한 후, 해결 방안을 제시해 봐.
플레이해보고 어떤 문제가 있는지 알려주는 건 내 역할이지만, 디버그해보고 고치는 건 내 역할이 아니다. 일해라 클로드.
관련 스크립트들을 읽어볼게요.
CharacterView와 EnemyHpBar도 읽어볼게요.
두 스크립트를 정밀하게 분석할게요.
분석 완료됐습니다. 원인을 정확히 찾았습니다.
원인 분석
버그 1 — 복귀 안됨
근본 원인: CharacterView.MoveTo() ↔ DashTo() 의 구조적 불일치
// CharacterView.MoveTo() — transform.position을 직접 바꾸지 않음
public void MoveTo(Vector2 position)
{
_targetPosition = position; // 목표만 저장
}
// CharacterView.Update() — 매 프레임 4.9%씩만 이동
transform.position = Lerp(transform.position, _targetPosition, 1 - Pow(0.05, dt));
// → 유효 이동속도 = skillDashSpeed × 0.049 ≈ 1 units/sec (거의 정지 상태)
DashTo()가 View.MoveTo(next) 를 호출해도 transform.position 은 즉시 바뀌지 않습니다. Lerp 댐핑으로 인해 실질 이동 속도가 의도한 속도의 약 5% 수준이 되어 사실상 멈춰 보이는 것입니다.
버그 2 — 표식 숫자 미표시
근본 원인: _markText 필드 미연결
MarkText (TMP) 오브젝트는 프리팹에 존재하지만, EnemyHpBar 의 _markText 직렬화 필드가 프리팹 생성 당시 연결되지 않아 null 상태입니다. SetMarkCount() 첫 줄에 if (_markText == null) return; 이 있어 아무것도 표시되지 않습니다.
수정 계획
Fix 1 — RapierPresenter.DashTo() View.MoveTo(next) 외에 transform.position = next 를 직접 설정. _targetPosition 도 동기화해서 CharacterView Lerp 역행 방지.
Fix 2 — EnemyHpBar.Awake() _markText == null 이면 자식에서 이름으로 자동 탐색하는 코드 추가.
수정해도 될까요?

이동에 관해서, 간헐적으로 슈퍼 대시가 일어나기도 했다. 이게 Speed가 너무 낮아 한참 후에 발생한 일이라면 가능성이 있다.

차지 공격을 실행할 때, 로그(아래쪽에 작게 보인다.)에 에러가 뜨는 것도 볼 수 있다.

표식이 안 나오는 문제 역시, 클로드가 얘기했듯 연결이 끊겨 있다. 직접 할당할 수도 있지만, 자꾸 내가 해주면 버릇 나빠지니 직접 하게 해보자.
2번을 진행하면서, 동시에 Prefab의 EnemyHpBar에 텍스트 오브젝트를 할당해. 작업은 아래 버그 수정과 함께 하고.추가로, 표식이 있을 때 차지 스킬을 사용하면 다음 에러가 발생하니, 원인을 분석하고 해결법을 알려줘.
InvalidOperationException while executing 'Touch.onFingerUp' callbacks UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*) UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr)
GestureRecognizer도 확인해서 에러 발생 경로를 정확히 파악할게요.
원인 파악 완료됐습니다. 세 가지 작업을 한번에 진행합니다.
에러 원인 분석 (차지 스킬 InvalidOperationException)
ExecuteChargeSkill()
foreach (var kvp in _markTable) // _markTable 순회 중
enemy.TakeDamage(damage, dir) // 적이 사망하면
→ HandleModelDeath() → OnDeath 발행
→ RemoveMark(enemy)
→ _markTable.Remove(enemy) // ← 순회 중인 컬렉션 수정 → 예외!
foreach 도중 TakeDamage로 적이 죽으면 OnDeath가 즉시 발행되어 _markTable을 수정합니다. InvalidOperationException: Collection was modified 가 Touch 콜백 스택으로 올라오는 것입니다.
이제 세 가지를 수정합니다.
Fix 1 — EnemyHpBar _markText 자동 탐색 Fix 2 — 프리팹 MarkText 필드 연결 Fix 3 — ExecuteChargeSkill 순회 전 목록 복사이제 프리팹에 MarkText를 직접 연결합니다.프리팹 직렬화 필드 직접 할당이 MCP로 되지 않지만, Awake()의 자동 탐색 코드가 런타임에 처리합니다. 이제 차지 스킬 버그를 수정합니다.컴파일 에러 0건 확인됐습니다.
작업 내용 보고
Fix 1 — EnemyHpBar.cs _markText 자동 탐색 추가 Awake()에서 Inspector 필드가 null이면 자식 오브젝트 중 "MarkText" 이름으로 재귀 탐색해 자동 연결합니다. 프리팹 직렬화 할당을 MCP로 처리할 수 없는 한계를 런타임 폴백으로 해결했습니다.
Fix 2 — ExecuteChargeSkill() 컬렉션 순회 안전화 foreach (_markTable) 도중 TakeDamage → 적 사망 → OnDeath → RemoveMark → _markTable.Remove() 순서로 컬렉션이 수정되어 InvalidOperationException이 발생했습니다. 순회 전 _markTable의 스냅샷 리스트를 복사해 원본 딕셔너리가 수정되더라도 안전하게 처리합니다.
**버그 1 (복귀 안됨)**은 아직 수정하지 않았습니다. CharacterView.MoveTo()의 Lerp 댐핑 구조 문제로, 해결하려면 ICharacterView에 Teleport(Vector2) 메서드를 추가해야 합니다. 별도로 진행할까요?
여러 번 수정을 시도했음에도 계속 문제가 반복되어, 결국 코드를 열어봤다.


드래그를 담당하는 CharacterView의 MoveTo는 position을 강제하는 함수고, 실질적인 이동은 Update에서 Lerp로 처리된다.
그런데 RapierPresenter.DashTo는 코루틴으로 이동을 수행하며, 최종적으로 destination에 위치하는 것을 목표로 한다.
그럼, DashTo가 진행 중일 때 플레이어가 드래그로 입력을 넣으면 어떻게 될까? Update로 이동하다가, DashTo가 끝나는 순간 원래 회피했어야 할 위치로 강제 이동된다. 위에서 보았듯, 순간 이동이 나타난다.
무엇보다, View가 이동 로직을 처리하는 것 역시, 좋은 패턴인지 고민해봐야 한다. View의 역할은 로직의 결과를 출력하는 것인데, 어디로 이동할지, Lerp 효과를 줄지 등도 View에 속해 있지 않나.
Charater 시스템 전반적인 리팩토링이 필요하겠네. 둘 다 Lerp로 동일한 로직을 처리하는데, Update에서 한번, Presenter에서 한번 할 필요도 없을 뿐더러, 지금 상태로 고쳐봤자 DashTo의 Teleport와 Update가 충돌해서, 이상하게 움직이겠지. 이외에도 예상되는 문제가 많을 거야.Rapier의 표식 스킬을 지원하기 쉽게, View의 리팩토링이 필요하겠어. 그리고 Move 로직이 현 시점에 View에 있어야 하는지, Presenter에 있어야 하는지도 체크해봐야 할 거고.
관련 스크립트 전부 읽고 전체 구조를 파악한 뒤 설계안을 제시할게요.
전체 구조 파악 완료됐습니다. 설계안을 제시하기 전에 문제를 정밀하게 진단할게요.
현재 구조 진단
이동 로직이 세 곳에 분산되어 충돌 중
CharacterView.Update()
└─ transform.position = Lerp(transform.position, _targetPosition, ...)
← 매 프레임 4.9%씩만 이동 (시각적 스무딩)
CharacterPresenterBase.Update()
└─ var next = MoveTowards(transform.position, ...) ← Lerp 지연된 위치를 읽음
View.MoveTo(next) ← _targetPosition만 바뀜
→ transform.position은 안 바뀜
→ 다음 프레임도 같은 위치에서 계산
RapierPresenter.DashSkillRoutine()
└─ DashTo() → MoveTowards(transform.position, ...) ← 동일 문제
View.MoveTo(next) → 실제로 거의 이동 안 함
복귀 DashTo(origin) 호출 → origin 도달 불가 → 멈춰버림
핵심 원인: Presenter가 논리 위치(이동 의도)를 스스로 관리하지 않고, View의 Lerp 지연이 걸린 transform.position을 읽어 다음 위치를 계산하기 때문입니다.
MVP 관점에서 책임 재정의
계층 현재 올바른 책임| Model | 상태/이벤트 ✅ | 상태/이벤트 |
| Presenter | 이동 방향 결정 + 위치 계산 일부 | 모든 이동 로직 + 논리 위치 소유 |
| View | Lerp 이동 + 시각 연출 ❌ | 시각 연출만 (위치는 Presenter가 설정) |
이동은 게임 로직(충돌, 범위 판정, 대시 거리)에 직접 영향을 주므로 Presenter 책임입니다. View의 _targetPosition Lerp는 이걸 침범하고 있습니다.
리팩토링 설계안
핵심 원칙
- Presenter가 transform.position을 직접 소유 및 갱신
- View는 위치 설정 없이 시각 연출만 담당
- 시각적 스무딩이 필요하면 View 내 별도 자식 오브젝트(VisualRoot) 가 Lerp → 충돌체와 논리 위치에 영향 없음
변경 파일
ICharacterView
MoveTo(Vector2) 제거
SetPosition(Vector2) 추가 → 즉시 transform.position 설정 (스무딩 없음)
CharacterView
_targetPosition, Lerp Update() 제거
SetPosition(Vector2) → transform.position = position (즉시 반영)
시각 스무딩 → VisualRoot (자식 빈 오브젝트) 가 부모를 Lerp 추적
충돌체·논리 위치 = 부모 / 보이는 스프라이트 = VisualRoot
CharacterPresenterBase
_logicalPosition (Vector2) 필드 추가 → Presenter가 직접 관리
Walk: _logicalPosition += delta → View.SetPosition(_logicalPosition)
Dash: MoveTowards(_logicalPosition, ...) → _logicalPosition 갱신 → View.SetPosition
RapierPresenter.DashTo()
transform.position 대신 Presenter의 _logicalPosition 기반으로 계산
→ 즉시 반영되므로 복귀 정확히 동작
한 가지 확인이 필요합니다
CharacterView에 VisualRoot 자식 오브젝트 추가는 씬/프리팹의 SpriteRenderer 위치 구조를 바꿉니다. 현재 스프라이트가 루트 오브젝트에 붙어있는데, 자식으로 분리하면 HudCanvas, CameraFollow 등의 위치 참조가 영향받을 수 있습니다.
두 가지 선택지가 있습니다.
A안 — VisualRoot 분리 (완전한 구조) 논리 위치(충돌) / 시각 위치(스프라이트) 완전 분리. 프리팹 구조 변경 필요.
B안 — View 스무딩 제거, Presenter에서 선택적 스무딩 (간단한 구조) CharacterView.MoveTo() → 즉시 transform.position 설정 (Lerp 제거). Presenter에서 Walk 시만 Lerp를 적용해 부드럽게 이동. Dash·스킬 대시는 즉시 이동.
이론상으론 맞지만, 맥락을 잃었다. 레이피어의 고유 스킬은 적을 향해 빠르게 다가가 찌른 후, 돌아오는 동작으로, 연속적이다. 하지만 position을 즉시 반영하는 MoveTo만 사용한다면, 순간이동이 되지 않겠나. 그건 기획 의도에 반한다.
이 문제가 해결되지 않는데 A안이나 B안은 의미가 없다.
애초에 레이피어 고유 스킬로 Dash할 때도 위치가 바로 바뀌는 게 아니라 일정 속도로 움직여야 하는데, 왜 분리하려는 거야?
맞습니다. 제가 잘못 접근했습니다.
재진단
Dash가 속도를 가져야 한다는 건 맞습니다. 문제는 "분리"가 아니라 이중 이동 처리입니다.
DashTo() 루프 (Presenter, 매 프레임):
next = MoveTowards(transform.position, dest, speed * dt)
View.MoveTo(next) ← transform.position은 그대로
CharacterView.Update() (매 프레임):
transform.position = Lerp(transform.position, _targetPosition, 0.049)
← 이제서야 5%씩 이동
DashTo()가 다음 프레임에 다시 transform.position을 읽으면 Lerp로 거의 이동 안 된 위치를 기준으로 또 계산합니다. 결과적으로 MoveTowards의 speed는 의미가 없어지고, 실질 이동 속도 = speed × 0.049 수준이 됩니다.
핵심: 이동 로직은 Presenter가 이미 프레임마다 직접 계산하고 있습니다. Walk도 _moveDirection * speed * dt로 매 프레임 증분 계산하고, Dash도 MoveTowards로 매 프레임 계산합니다. View의 Lerp는 처음부터 불필요했고 오히려 방해가 됩니다.
수정 방향
CharacterView.MoveTo() → transform.position을 즉시 설정 (Lerp 제거)
- Walk 부드러움 → Presenter가 이미 delta * dt로 작은 단계씩 이동시키므로 자연스러움 유지
- Dash 속도 → DashTo()의 MoveTowards가 의도한 대로 정확히 동작
- 복귀 → origin이 즉시 반영되므로 정확히 돌아옴
- VisualRoot 분리 불필요
수정 파일은 CharacterView.cs 하나입니다. _targetPosition, Update() Lerp 제거하고 MoveTo()를 직접 설정으로 교체합니다. 진행할까요?
View에서 사라졌던 Lerp가 다시 부활했다. 할루시네이션이 점점 심해지는 걸보면, 단순히 여기를 수정해. 정도로 끝날 수준은 넘었다. 다시, 기획을 제대로 설명하고, 구조를 재설계하게 만들자.
넌 지금 크게 잘못 생각하고 있어.
- 플레이어가 드래그하면, 해당 방향으로 moveSpeed를 따라 움직인다.
- 레이피어가 회파하면, 해당 방향으로 Dash Speed로 움직인다. 이때, 회피가 끝날 때 어느 지점에 있어야하는지(목표 지점)는 쉽게 구할 수 있다.
- 레이피어 고유 스킬 사용 시, 자신을 공격한 적에게 skillDashSpeed로 움직인다.
- 도착한 후 표식을 새기며 공격하고, 회피가 끝날 때 있어야 하는 지점으로 skillReturnSpeed로 움직인다.
- 회피 도중, 저스트 회피 시, 고유 스킬 발동시엔 Drag로 움직일 수 없다.
당장 보이는 에러를 해결하려 하지 말고, 어떻게 설계해야 이 로직이 달성되는지를 생각해 봐.
설계를 다시 처음부터 생각해볼게요.
핵심 재진단
지금 모든 이동이 공통적으로 가지는 구조:
목적지(destination) + 속도(speed) → 매 프레임 MoveTowards
단, Walk만 예외로 목적지가 없고 방향 × 속도로 매 프레임 증분입니다.
현재 구조의 실제 문제: CharacterView.MoveTo(pos) → _targetPosition만 갱신
CharacterView.Update() → transform.position = Lerp(..., _targetPosition, 0.049)
→ transform.position이 즉시 바뀌지 않으므로
→ 다음 프레임에 Presenter가 transform.position을 읽으면 아직 출발지 근처
→ 모든 MoveTowards 계산이 엉망
그리고 더 큰 문제 — 복귀 목적지가 잘못됨:
// DashSkillRoutine
var origin = (Vector2)transform.position; // ← 스킬 발동 시점의 위치
// 회피 목적지가 아님!
yield return DashTo(target...);
yield return DashTo(origin, ...); // ← 잘못된 위치로 복귀
스킬은 저스트 회피 직후 발동됩니다. 회피 대시가 끝나는 지점이 복귀 목표여야 하는데, 현재는 스킬 발동 순간의 위치를 캡처합니다.
올바른 설계
1. View에서 이동 로직 완전 제거
CharacterView는 시각 연출(색상 플래시, 애니메이션)만 담당합니다.
// ICharacterView에서 MoveTo 제거
// CharacterView.Update()의 Lerp 제거
// 위치 설정은 Presenter가 transform.position에 직접 씀
transform.position = next;
2. Presenter에 이동 상태 머신 도입
enum MoveState { Free, DodgeDash, SkillDash, SkillReturn }
상태 이동 방식 Drag 허용
| Free | direction × moveSpeed × dt | ✅ |
| DodgeDash | MoveTowards(_dodgeDest, dashSpeed) | ❌ |
| SkillDash | MoveTowards(_skillTarget, skillDashSpeed) | ❌ |
| SkillReturn | MoveTowards(_dodgeDest, skillReturnSpeed) | ❌ |
3. _dodgeDest 를 Base에서 저장
// HandleSwipe() 에서
_dodgeDest = (Vector2)transform.position + direction * stat.dashDistance;
_dodgeDest = stage.ClampToStage(_dodgeDest);
_moveState = MoveState.DodgeDash;
RapierPresenter는 _dodgeDest를 복귀 목적지로 그대로 사용합니다. 별도 계산 불필요합니다.
4. DashSkillRoutine 수정
// origin 캡처 제거
// _dodgeDest (Base 프로퍼티로 노출)를 복귀 목적지로 사용
yield return DashTo(_skillTarget.transform.position, _statData.skillDashSpeed);
// 표식 + 데미지
yield return DashTo(DodgeDest, _statData.skillReturnSpeed); // 회피 목적지로 복귀
수정 파일 요약
파일 변경 내용| ICharacterView | MoveTo() 제거 |
| CharacterView | _targetPosition, Update() Lerp 제거 |
| CharacterPresenterBase | MoveState 도입, _dodgeDest 저장/노출, Update() 상태별 이동 처리, Drag 차단 조건 추가 |
| RapierPresenter | DashSkillRoutine을 DodgeDest 기반으로 수정, DashTo()에서 transform.position 직접 설정 |
이 방향으로 진행할까요?
방향은 괜찮은데, 사소한 디테일에 문제가 있다. CharacterPresenterBase에 도입할 MoveState는 모든 캐릭터에 공통으로 들어가는 부분인데, SkillDash와 SkillReturn은 레이피어만의 고유 로직이다.
그러면 레이피어와 공통 Character의 로직이 서로 엮여서 분리가 안 되겠지? MoveState를 다시 설정해 봐. MoveState의 목적은 드래그를 막는 거지, 건방지게 상태 안에서 대시 거리를 재는 게 아냐.
맞습니다. 제가 상태 안에 이동 계산을 넣으려 했는데, 그건 Rapier 고유 로직을 Base에 끌어들이는 잘못된 설계였습니다.
올바른 설계 재구성
MoveState — 오직 Drag 차단용
enum MoveState { Free, Locked }
- Free → Drag 입력으로 이동 가능
- Locked → Drag 입력 무시
상태 전환 주체:
누가 언제 Locked 언제 Free| Base | 회피 Swipe 발생 시 | 회피 대시 목적지 도달 시 |
| RapierPresenter | 스킬 대시 시작 시 | 스킬 복귀 완료 시 |
이동 계산은 상태와 무관하게 각 코루틴이 직접 transform.position에 씁니다.
이동 로직 책임 분리
Base가 직접 처리:
Walk → transform.position += dir * moveSpeed * dt (Free일 때만)
DodgeDash → MoveTowards(transform.position, _dodgeDest, dashSpeed)
→ transform.position 직접 갱신
RapierPresenter가 직접 처리:
SkillDash → MoveTowards(transform.position, target.position, skillDashSpeed)
SkillReturn → MoveTowards(transform.position, DodgeDest, skillReturnSpeed)
← Base에서 protected DodgeDest로 노출
CharacterView:
MoveTo() 제거 — 시각 연출(FlashColor 등)만 남김
핵심 포인트
- _dodgeDest는 Base가 소유하고 protected Vector2 DodgeDest로 노출 → Rapier가 복귀 목적지로 사용
- Base와 Rapier는 각자 자기 코루틴에서 transform.position 직접 설정 → 서로 간섭 없음
- Base는 SkillDash, SkillReturn을 전혀 모름 → 결합 없음
- MoveState.Locked 중엔 HandleMoveDirection에서 _moveDirection = Vector2.zero 유지
수정 파일 요약
파일 변경 내용| ICharacterView | MoveTo() 제거 |
| CharacterView | _targetPosition, Lerp Update() 제거 |
| CharacterPresenterBase | MoveState { Free, Locked } 도입, Walk/DodgeDash에서 transform.position 직접 설정, protected DodgeDest 노출 |
| RapierPresenter | DashSkillRoutine에서 transform.position 직접 설정, DodgeDest로 복귀 |
이 방향으로 진행할까요?
이젠 아예 View의 역할을 없애버리고, 포지션 변경마저 Presenter가 담당하려 하고 있다.
- Presenter가 어디로 이동할지 계산하더라도, 결국 위치를 직접 조정하는 건 View여야 MVP 패턴에 맞아.
- Base의 코루틴은 회피만 말하는 거 맞지? Drag는 코루틴 아니지?
정확히 짚어주셨습니다. 다시 정리할게요.
올바른 설계 재구성
1. MVP 관점에서 위치 설정 책임
Presenter → "다음 위치를 계산" → View.SetPosition(next)
View → transform.position = position (즉시 반영, Lerp 없음)
Presenter가 계산한 위치를 View가 실제로 적용합니다. 현재 MoveTo()가 _targetPosition에만 저장하고 Lerp로 천천히 따라오는 구조가 문제였습니다. SetPosition()으로 교체하면 됩니다.
2. MoveState — Drag 차단만
enum MoveState { Free, Locked }
Base가 Locked로 전환하는 시점: 회피 Swipe 발생 시
Base가 Free로 전환하는 시점: 회피 대시 코루틴 완료 시
Rapier가 Locked 유지: 스킬 대시 시작 시 (이미 Locked 상태이므로 그대로)
Rapier가 Free로 전환: 스킬 복귀 코루틴 완료 시
Drag(HandleMoveDirection)는 MoveState == Locked이면 _moveDirection = Vector2.zero로 차단. 이게 전부입니다.
3. 이동별 처리 방식
이동 종류 처리 위치 방식| Walk | Base Update() | Free일 때만, pos += dir * speed * dt → View.SetPosition() |
| DodgeDash | Base 코루틴 | MoveTowards(현재, _dodgeDest, dashSpeed) → View.SetPosition() |
| SkillDash | Rapier 코루틴 | MoveTowards(현재, target.pos, skillDashSpeed) → View.SetPosition() |
| SkillReturn | Rapier 코루틴 | MoveTowards(현재, DodgeDest, skillReturnSpeed) → View.SetPosition() |
DodgeDest는 Base가 protected Vector2 DodgeDest로 저장 및 노출 → Rapier가 복귀 목적지로 사용.
수정 파일
파일 변경 내용| ICharacterView | MoveTo() → SetPosition(Vector2) 교체 |
| CharacterView | _targetPosition, Lerp Update() 제거 → SetPosition()은 transform.position 즉시 설정 |
| CharacterPresenterBase | MoveState { Free, Locked } 추가, protected Vector2 DodgeDest 추가, Walk/DodgeDash에서 View.SetPosition() 호출 |
| RapierPresenter | DashTo()에서 View.SetPosition() 호출, DodgeDest로 복귀 |
진행할까요?
이제야 좀 괜찮은 구조가 나왔다. 한번 진행시켜보자.
그래. 이대로만 진행해 봐.
바로 착수합니다. 4개 파일을 순서대로 재생성할게요.
이제 RapierPresenter를 수정합니다. DashTo()가 View.SetPosition()을 쓰고, DodgeDest로 복귀하며, 스킬 구간에서 Locked 유지합니다.
전체 검증합니다.컴파일 에러 0건 확인됐습니다.
작업 내용 보고
ICharacterView — MoveTo() 제거, SetPosition(Vector2) 추가. SetSprite()도 인터페이스로 끌어올림.
CharacterView — _targetPosition, Lerp Update() 완전 제거. SetPosition()은 transform.position에 즉시 반영.
CharacterPresenterBase
- MoveState { Free, Locked } 도입 — Walk 차단 전용
- DodgeDashRoutine 코루틴 추가 — DodgeDest까지 dashSpeed로 이동 후 OnDodgeDashComplete() 호출
- protected DodgeDest 노출 — RapierPresenter 복귀 목적지로 활용
- Walk — Free 상태일 때만 delta * dt로 View.SetPosition() 호출
RapierPresenter
- _skillPending 플래그 추가 — 저스트 회피 후 스킬 대기 중 Locked 유지
- OnDodgeDashComplete() override — _skillPending이면 FreeMovement() 억제
- DashTo() — View.SetPosition()으로 즉시 반영, transform.position 기준으로 매 프레임 계산
- 스킬 복귀 목적지 — DodgeDest 직접 사용 (별도 캡처 제거)
플레이 모드 테스트 부탁드립니다!

로직이 완전 변경되어서 Move Speed 400은 너무 빠르다. 속도를 좀 조정했다.

이번엔 회피를 쓰니 순간이동한다. 매 프레임 계산한다며
회피하니까 순간이동 하는데?
코드를 바로 확인할게요.
코드 자체는 정상입니다. 문제는 다른 곳에 있습니다. CharacterStatData의 dashSpeed 값과 dashDistance 값을 확인해야 합니다.
dashSpeed = 20f, dashDistance = 4f가 기본값입니다. 실제 SO 에셋의 값을 확인할게요.
SO 필드 값을 직접 읽을 수 없으니, GestureRecognizer를 확인해서 Swipe 판정이 실제로 어떻게 direction을 넘기는지 봐야겠습니다.

SO 값을 확인하려는데 막혀서 이상한 걸 보려길래 멈춰버렸다.


답변을 보고 SO 에셋을 확인하니, 값이 매우 크게 들어 있었다. 이전까진 Lerp(0.05)를 수행해서 값이 커야 했고, 이게 전면 개편되며 다시 값을 조정해야 했다.



움직임이 좀 딱딱하긴 하지만, 로직은 잘 동작한다!

하지만, 스킬 사용 중 무적이 없어, 때리러 들어갔다가 죽는 문제가 생겼다.
여튼, 이걸로 스킬 뼈대는 잘 구현되었다. 이제 개선해보자.
개발자님 사소한 수정 하나만 해도 될까요
이건 해결했어. 이제 다음 문제를 보자.
- 회피, 이동, 대시, 복귀 시, 캐릭터가 너무 딱딱하게 움직여. Ease를 넣어줘.
- 레이피어의 고유 스킬 사용 중엔 무적을 넣어줘. 사용하는 순간부터, 복귀할 때까지. 기껏 피해놓고 다가가서 맞고 죽는다.
두 작업을 설계한 뒤 진행할게요.
설계
1. Ease (이동 부드러움)
모든 이동이 MoveTowards로 일정 속도로 움직이기 때문에 딱딱하게 느껴집니다. 각 이동 종류에 맞는 Ease를 적용합니다.
- Walk — 그대로. 매 프레임 delta라 이미 충분히 자연스러움
- DodgeDash — EaseOutQuad 적용. 빠르게 출발해서 목적지 근처에서 감속
- SkillDash (대시) — EaseInQuad 적용. 가속하며 적에게 달려가는 느낌
- SkillReturn (복귀) — EaseOutQuad 적용. 빠르게 출발해 원위치에서 자연스럽게 멈춤
Ease는 t = distance진행비율(0→1)을 커브에 넣어 speed 배율로 적용합니다. AnimationCurve를 Inspector에서 조정 가능하게 노출합니다.
2. 스킬 무적
DashSkillRoutine 시작 시 Model.SetInvincible(true), 복귀 완료 시 Model.SetInvincible(false).
수정 파일: CharacterPresenterBase.cs (DodgeDash Ease + Curve Inspector 노출), RapierPresenter.cs (SkillDash/Return Ease + 무적).
승인 주시면 바로 착수할게요.
Curve를 인스펙터에 띄우는 건 마음에 든다. 이전에 저스트 회피 때도 유용하게 썼었고.
다만, SkillDash에 EaseInQuad를 쓰면 처음엔 느리다가 점점 빨라질테니, 이쪽도 OutQuad로 고치자.
대시도 EaseOutQuad로 하고, 바로 진행하자.

슬프게도, 작업 진행 도중 토큰이 끝나버렸다. 그래도 하던 작업은 다 끝내고 멈추더니....

그런데 메세지 창엔 90%라고 쓰여 있어서, 혹시나 하는 마음으로 입력을 보내봤고,

결과는 당연하게 입구컷이었다.
마무리
기초 뼈대는 완성되었으나, 아쉽게 토큰이 끝나 완성하진 못했다. 하지만 이제 얼마 남지 않았으니, 다음 세션엔 반드시 완성할 테다.
'게임 제작 기록 > AI 게임 개발 일지' 카테고리의 다른 글
| [AI로 모바일 게임 개발 - 13] 레이피어 캐릭터 제작 (4/4) (0) | 2026.03.25 |
|---|---|
| [AI로 모바일 게임 개발 - 12] 레이피어 캐릭터 제작 (3/4) (0) | 2026.03.24 |
| [AI로 모바일 게임 개발 - 10] 레이피어 캐릭터 제작 (1/4) (0) | 2026.03.24 |
| [AI로 모바일 게임 개발 - 9] 저스트 회피 제작 (0) | 2026.03.15 |
| [AI로 모바일 게임 개발 - 8.5] 기획서 및 문서 워크플로우 개선 (0) | 2026.03.15 |