[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBGG7XP18Z4BJMC2TCGPRV":3,"skill-same:01M6CBGG7XP18Z4BJMC2TCGPRV":45},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":20,"linkOnly":21,"markdownContent":22,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":13,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":29,"updatedAt":30,"install":31,"resources":44},"01M6CBGG7XP18Z4BJMC2TCGPRV","11-input-controller",null,"devwinsoft\u002Fdevian","devwinsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F22570160?v=4","01M6C6RR8W05NH8NPKQNCE3Q72","maoshy","01M6C6RRDJ5TJJCZXRTY801YDH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F11-input-system\u002F11-input-controller\u002FSKILL.md",[],[18,19],"controller","11 input controller","2026-11-10 10:12:30",false,"# 11-input-controller\n\nStatus: ACTIVE\nAppliesTo: v11\nType: Component Specification\n\n## 목적\n\n`BaseInputController`는 **ActorController\\\u003CActorObject\\> 기반 입력 소비** 컨트롤러이다.\n\n- `ActorController\u003CActorObject>`를 상속하여 Actor lifecycle에 통합\n- `onInit(actor)`에서 `InputManager.Instance.RegisterController(this)` — 등록\n- `Clear()` override에서 `UnregisterController(this)` + `base.Clear()` — 해제\n  - `BaseApplication.IsShuttingDown` 시 매니저 접근 없이 `base.Clear()` 호출\n  - `SingletonRegistry.TryGet\u003CInputManager>` 로 안전한 싱글톤 접근\n- `IInputSpace` 전략으로 Move 입력을 월드 공간 벡터로 변환\n- `InputEnabled` guard로 입력 수신 on\u002Foff\n- **변화가 있을 때만** 4개 virtual 콜백 호출 (change-only)\n\n---\n\n## 범위\n\n### 포함\n\n- `IBaseInputController` — 입력 컨트롤러 계약\n- `BaseInputController` — ActorController\\\u003CActorObject\\> 상속, Actor lifecycle 기반 컨트롤러 등록\u002F해제, change-only 콜백\n- `IInputSpace` — Move(Vector2) → World(Vector3) 변환 전략 인터페이스\n- `WorldXZSpace` — `(x, y) → (x, 0, y)` 탑다운\u002F2D용\n- `ViewFlattenedSpace` — 카메라 forward\u002Fright y=0 평탄화 후 합성\n\n### 제외\n\n- 입력 수집\u002F정규화\u002F발행 (→ `10-input-manager`)\n- 구체적 게임 입력 로직 (서브클래스 구현)\n\n---\n\n## 네임스페이스\n\n```csharp\nnamespace Devian\n```\n\n---\n\n## 핵심 규약 (Hard Rule)\n\n### 1. 컨트롤러 등록 lifecycle (Actor 기반)\n\n- `ActorController\u003CActorObject>`를 상속 → Actor의 Init\u002FClear lifecycle에 통합\n- `onInit(ActorObject actor)`: `InputManager.Instance.RegisterController(this)` + prev 상태 리셋\n- `Clear()` override: 종료 방어 + `UnregisterController(this)` + prev 리셋 + `base.Clear()`\n  - `BaseApplication.IsShuttingDown` 시 매니저 접근 없이 `base.Clear()` 호출\n  - `IsCleared` 체크 + `SingletonRegistry.TryGet\u003CInputManager>` 로 안전한 해제\n- Actor가 `RegisterController\u003CT>()`로 등록, `Init()` 루프에서 초기화 (자동 등록 없음)\n- Priority는 ActorController\\\u003CTOwner\\>에서 상속 (`virtual int Priority => 0`) — 중복 선언 없음\n- InputManager는 싱글톤을 신뢰 (consumer bootstrap\u002Fprefab 보장, SerializeField 없음)\n- Bus(IInputBus\u002FInputBus)는 삭제됨 — InputManager가 직접 `__Consume(frame)`을 호출\n\n### 2. InputEnabled guard\n\n`InputEnabled == false`이면 `__Consume` 내부에서 모든 콜백을 무시한다.\n\n### 3. Priority\n\n기본값 0. 서브클래스에서 override 가능.\nInputManager가 `_controllersDirty`일 때 Priority 내림차순으로 정렬하여 호출.\n\n### 4. IInputSpace\n\n- `InputSpace` 프로퍼티로 전략 주입\n- 서브클래스에서 `InputSpace.ResolveMove(move)`로 월드 벡터 획득\n\n### 5. Change-only 콜백\n\nInputFrame을 매 프레임 받지만, 파생 클래스 콜백은 **변화가 있을 때만** 호출한다.\n\n| 콜백 | 호출 조건 |\n|------|----------|\n| `onInputMove(Vector2 move)` | 축이 유효(non-NaN) + `(cur - prev).sqrMagnitude > epsilon²` |\n| `onInputLook(Vector2 look)` | 축이 유효(non-NaN) + `(cur - prev).sqrMagnitude > epsilon²` |\n| `onButtonPress(string key, int index)` | bit가 0→1로 전환된 각 버튼마다 1회 |\n| `onButtonRelease(string key, int index)` | bit가 1→0으로 전환된 각 버튼마다 1회 |\n\n- `_axisEpsilon` (SerializeField, default 0.001f) — Move\u002FLook 변화 감지 임계값\n- 축이 초기화되지 않은 경우(NaN — 키 미설정\u002FAction 없음) 해당 축의 콜백은 발생하지 않는다\n- 유효한 축의 첫 프레임(`_prevMoveValid`\u002F`_prevLookValid == false`)은 항상 콜백 호출\n\n### 6. 버튼 이벤트 규약\n\n- down\u002Fup mask를 외부로 넘기지 않고, 내부에서 bitwise로 개별 index를 펼침\n- key는 `InputManager.Instance.ButtonKeys[index]`에서 가져온 `\"Map\u002FAction\"` 문자열\n- index가 ButtonKeys 범위 밖이면 key는 빈 문자열 (예외 없음)\n\n### 7. `__Consume` 엔트리 포인트\n\n- `public void __Consume(InputFrame frame)` — InputManager가 호출하는 진입점\n- 외부 코드에서 직접 호출하지 않는다\n- 내부에서 `_onInputFrame(frame)`을 호출하여 change detection + 4개 콜백 dispatch\n\n---\n\n## API 시그니처\n\n```csharp\n\u002F\u002F --- IBaseInputController ---\npublic interface IBaseInputController\n{\n    bool InputEnabled { get; set; }\n    int Priority { get; }\n    IInputSpace InputSpace { get; set; }\n}\n\n\u002F\u002F --- BaseInputController ---\npublic abstract class BaseInputController : ActorController\u003CActorObject>, IBaseInputController\n{\n    public bool InputEnabled { get; set; }\n    public IInputSpace InputSpace { get; set; }\n\n    \u002F\u002F Actor lifecycle (ActorController\u003CActorObject> overrides)\n    protected override void onInit(ActorObject actor);   \u002F\u002F RegisterController + reset prev\n    public override void Clear();                       \u002F\u002F UnregisterController + reset prev + base.Clear()\n\n    public void __Consume(InputFrame frame);\n\n    protected virtual void onInputMove(Vector2 move) { }\n    protected virtual void onInputLook(Vector2 look) { }\n    protected virtual void onButtonPress(string key, int index) { }\n    protected virtual void onButtonRelease(string key, int index) { }\n}\n\n\u002F\u002F --- IInputSpace ---\npublic interface IInputSpace\n{\n    Vector3 ResolveMove(Vector2 raw);\n}\n\n\u002F\u002F --- WorldXZSpace ---\npublic class WorldXZSpace : IInputSpace\n{\n    public Vector3 ResolveMove(Vector2 raw);  \u002F\u002F → (x, 0, y)\n}\n\n\u002F\u002F --- ViewFlattenedSpace ---\npublic class ViewFlattenedSpace : IInputSpace\n{\n    public ViewFlattenedSpace(Transform cameraTransform);\n    public Vector3 ResolveMove(Vector2 raw);  \u002F\u002F camera forward\u002Fright 평탄화\n}\n```\n\n---\n\n## 파일 경로\n\n| 타입 | 경로 |\n|------|------|\n| IBaseInputController | `com.devian.foundation\u002FSamples~\u002FMobilePackage\u002FRuntime\u002FInput\u002FIBaseInputController.cs` |\n| BaseInputController | `com.devian.foundation\u002FSamples~\u002FMobilePackage\u002FRuntime\u002FInput\u002FBaseInputController.cs` |\n| IInputSpace | `com.devian.foundation\u002FSamples~\u002FMobilePackage\u002FRuntime\u002FInput\u002FIInputSpace.cs` |\n| WorldXZSpace | `com.devian.foundation\u002FSamples~\u002FMobilePackage\u002FRuntime\u002FInput\u002FInputSpaces\u002FWorldXZSpace.cs` |\n| ViewFlattenedSpace | `com.devian.foundation\u002FSamples~\u002FMobilePackage\u002FRuntime\u002FInput\u002FInputSpaces\u002FViewFlattenedSpace.cs` |\n\n---\n\n## DoD (Definition of Done)\n\n- [ ] 모든 파일이 `namespace Devian` 사용\n- [ ] BaseInputController가 ActorController\\\u003CActorObject\\>를 상속 (abstract class)\n- [ ] 자동 등록 없음 — Actor가 `RegisterController\u003CT>()`로 등록\n- [ ] `onInit`에서 RegisterController (Actor lifecycle)\n- [ ] `Clear()` override에서 UnregisterController + base.Clear()\n- [ ] `Clear()` 종료 방어: `BaseApplication.IsShuttingDown` + `SingletonRegistry.TryGet`\n- [ ] Priority는 ActorController\\\u003CTOwner\\>에서 상속 (중복 선언 없음)\n- [ ] InputManager.Instance(싱글톤)으로만 접근 (SerializeField 없음)\n- [ ] InputEnabled guard 적용\n- [ ] 4개 virtual 콜백: onInputMove, onInputLook, onButtonPress, onButtonRelease\n- [ ] 변화 없으면 콜백 호출되지 않음 (change-only)\n- [ ] 버튼 이벤트는 개별 key\u002Findex로 펼쳐서 호출 (mask 외부 전달 없음)\n- [ ] `__Consume` 엔트리 포인트 존재\n- [ ] WorldXZSpace \u002F ViewFlattenedSpace 구현\n- [ ] UPM ↔ UnityExample 동일\n\n---\n\n## Reference\n\n- Parent: `..\u002F00-overview\u002FSKILL.md`\n- 입력 수집: `..\u002F10-input-manager\u002FSKILL.md`\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F50-mobile-package\u002F11-input-system\u002F11-input-controller","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft","Kim Hyong Joon","2026-04-12 18:44:10",{"source":7,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add devwinsoft\u002Fdevian --skill 11-input-controller",{"tool":37,"command":38},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 11-input-controller",{"tool":40,"command":41},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 11-input-controller","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F11-input-system\u002F11-input-controller\u002FSKILL.md",true,[],[46,52,60,68,77,84],{"skillNo":47,"name":48,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":49,"categories":50,"keywords":51,"publishedAt":20},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[48],{"skillNo":53,"name":54,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":20},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[58,59],"archive","90 project archive",{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":20},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[66,67],"management","30 env management",{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":20},"01M6CBWYD3TGWBQV7V35551THF","19-page-push-send","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F19-page-push-send\u002FSKILL.md",[],[74,75,76],"push","page","19 page push send",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":20},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[75,83],"18 page savedata",{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":20},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[75,90],"17 page deobfuscate"]