Loading...
Loading...
MSW 전투 시스템 통합 가이드. Attack→Hit 파이프라인, 데미지 모델, i-frame, 넉백, Hit Stop, Camera Shake, Sprite Flash, SFX, 사망/부활, 데미지 스킨, 히트 이펙트, 아바타 전투 모션, 커스텀 이벤트, AI FSM 까지 MSW 네이티브 API 기반 2D 다장르 대응. '공격', '피격', '데미지', '전투', '몬스터', '히트', '이펙트', '크리티컬', '투사체', '데미지 스킨', '넉백', '히트스톱', '콤보', 'HP바' 등.
npx skill4agent add msw-git/msw-ai-coding-plugins-official msw-combat-systemdefault-local-workspace/Environment/NativeScripts/**/*.d.mluadocs/engine-source-index.md| # | 레이어 | 네이티브 | 커스텀 필요 |
|---|---|---|---|
| 1 | Attack Resolution | | Capsule/Cone/Ray, 관통 카운트 |
| 2 | Damage Model | | 속성 상성, 복합 공식 |
| 3 | Hit Reaction | Body별 넉백 API, | 경직 레벨, 상태이상 |
| 4 | Game Feel | 6개 전부 네이티브 (Hit Stop, Shake, Zoom, Flash, VFX, SFX) | — |
| 5 | Combat State | | MP/Stamina/Rage, 어그로 |
| 6 | Event Bus | | OnKill/OnBlocked |
| 7 | AI | | Decorator/Memory(Blackboard), Threat Table |
| + | Damage Skin | | — |
| + | Hit Effect | | — |
| + | Avatar Motion | | — |
| 파일 | 다루는 범위 | 언제 읽나 |
|---|---|---|
| 몬스터 | 전투 가능한 몬스터 만들 때 |
| | 머리 위 HP 바 붙일 때 |
| 투사체 (Body 없는 엔티티 + | 화살·총알·마법탄 등 원거리 공격 만들 때 |
| BehaviourTree — | BT 기반 몬스터·보스 AI, 다층 의사결정 필요할 때 |
우선순위: *본 SKILL.md (개념·API 표) → 해당 references/ (구현 전체) →검증 (의심 시)**.mod/*.cs
HitComponent.ColliderTypeAttackComponent:
Attack(Vector2 size, Vector2 offset, string attackInfo, CollisionGroup? cg) → table<Component>
Attack(Shape shape, string attackInfo, CollisionGroup? cg) → table<Component>
AttackFast(Shape shape, string attackInfo, CollisionGroup? cg) → void (대량 판정용, 탄막)
AttackFrom(Vector2 size, Vector2 position, string attackInfo, CollisionGroup? cg) → table<Component>
emitter EmitAttackEvent(AttackEvent)RectangleShape(center, size)CircleShape(center, radius)BoxShape(pos, size, angle)HitComponent.PolygonPoints: SyncList<Vector2>AttackFast| 지점 | 오버라이드 | 용도 |
|---|---|---|
| 공격자 | | 진영·거리·상태 |
| 피격자 | | 무적·면역 |
__base:IsAttackTarget(...)HitComponent.CollisionGroupCollisionGroups.HitBoxAttack(..., cg)Attacktable<Entity, boolean>attackInfoCalcDamageIsHitTargetGetDisplayHitCount"melee.light""dot.poison"HitComponent.IsLegacy = falseColliderTypeColliderOffsetPolygonPointsBoxOffsetColliderName| 형태 | Shape 구성 |
|---|---|
| 근접 전방 Box | |
| 원형 AoE | |
| 투사체 | Body 없는 모델(Sprite+Transform만) 스폰 + |
AttackComponentOnUpdate(delta)SetTimerRepeat(0.1~0.15s)| 대상 | Body 유무 | 이동 API | 근거 |
|---|---|---|---|
| 몬스터 / NPC / AI | 있음 (맵 타입 Body + | | |
| 투사체 / 젬 / 드롭 아이템 / 이펙트 | 없음 (Sprite+Transform+Trigger) | | Body 없으면 Transform 직접 조작 안전. 공식 "Create a Long-Range Projectile" 튜토리얼 패턴 |
| Rigidbody 엔티티 직접 제어 (고급) | 있음 | | |
맵 타입별 실제 velocity 환산은MovementComponent.InputSpeed§10 참조 (MapleTile=×1, RectTile=÷1.2, SideView=×1.5).msw-general/references/platform.md
| ❌ | 사유 |
|---|---|
| 6~10Hz teleport, 프레임 보간 없음 → 뚝뚝 끊김 |
| 둘 다 텔레포트 메서드 ( |
| 다음 프레임 물리엔진이 덮어쓰고 네트워크 동기화 차단 |
| 프레임률 의존. 60FPS·30FPS에서 속도 다름 |
mlua_Document_RetrieverOnUpdateMovementComponent:SetPosition(...)MoveToDirectionTranslateFlappyFish RemakeStopping the TaxiMaking a Moving FootholdPosition.model| 컴포넌트 | 비고 |
|---|---|
| 기본 |
| 렌더 |
| Body (맵 타입) | |
| |
msw-general/references/platform.mdAttackComponent:
method integer CalcDamage(attacker, defender, attackInfo) -- default 1
method boolean CalcCritical(attacker, defender, attackInfo) -- default false
method float GetCriticalDamageRate() -- default 2.0
method int32 GetDisplayHitCount(attackInfo) -- default 1
method void OnAttack(defender)
HitComponent:
method void OnHit(Entity attacker, integer damage, boolean isCritical, string attackInfo, int32 hitCount)
emitter EmitHitEvent(HitEvent)HitEventAttackCenter: Vector2
AttackerEntity: Entity (nilable)
Damages: List<integer> -- 멀티히트 분할
Extra: any -- ★ 확장 슬롯 (knockback/stun/element/tags)
IsCritical: boolean
TotalDamage: integer
FeedbackAction: HitFeedbackAction -- ⚠ 전체 enum deprecatedExtraAttackEventDefenderEntity: EntityselfHitEventmonster.Hp -= damagetarget.MonsterAI.HP -= damageHitEvent| Body (맵 타입) | 구현 |
|---|---|
| Rigidbody (MapleTile) | |
| Kinematicbody (RectTile/탑다운) | |
| Sideviewbody (SideViewRectTile) | |
OnUpdateMoveVelocity *= 0.9FootholdCollisionEventTransformComponent.Positionbody:SetPosition(...)OnUpdate_UtilLogic.ElapsedSecondsHitComponent:IsHitTargetDefaultPlayerPlayerHit.mluaHitComponent.CollisionGroup@Component BuffComponent_TimerService:SetTimerRepeatStatusAppliedEventStatusExpiredEventStateComponent:ChangeState("STUN")| 요소 | API | ExecSpace |
|---|---|---|
| Hit Stop (전역) | | ClientOnly |
| Hit Stop (개별) | | @Sync |
| Slow Motion | | ClientOnly |
| Camera Shake | | Client |
| Camera Zoom | | Client |
| Hit Flash | | @Sync |
| Color HDR 오버브라이트 | | — |
| VFX 고정 | | — |
| VFX 부착 | | — |
| VFX 제거 | | — |
| SFX 2D | | Client |
| SFX 3D | | Client |
| SFX 루프 | | Client |
| SFX 부착 | | Client |
| BGM | | Client |
| 프리로드 | | ClientOnly |
PlayEffectFlipX, FlipY, SortingLayer, OrderInLayer, Alpha, StartFrameIndex, EndFrameIndex, PlayRate, SyncFlip, Color, MaterialID, IgnoreMapLayerCheck, LitMode_CameraService:GetCurrentCameraComponent()-- BasicParticle: 범용 프리셋 (RUID 불필요)
integer _ParticleService:PlayBasicParticle(BasicParticleType, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlayBasicParticleAttached(BasicParticleType, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)
-- SpriteParticle: 커스텀 스프라이트를 파티클로 (spriteRUID 필요)
integer _ParticleService:PlaySpriteParticle(SpriteParticleType, string spriteRUID, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlaySpriteParticleAttached(SpriteParticleType, string spriteRUID, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)
-- AreaParticle: 넓은 영역 환경 파티클 (areaSize 추가)
integer _ParticleService:PlayAreaParticle(AreaParticleType, Vector2 areaSize, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
void _ParticleService:RemoveParticle(integer serial)Color, SortingLayer, OrderInLayer, ParticleSize, ParticleCountisLoop=trueRemoveParticle(serial)self._T| 계열 | 이름 | 설명 |
|---|---|---|
| 폭발/충격 | | 스파크 (1회) — 범용 피격 |
| 연속 스파크 | |
| 원형으로 튀는 스파크 | |
| 작은 폭발 + 연기 | |
| 큰 폭발 + 연기 | |
| 아주 작은 폭발 (Color 옵션 무효) | |
| 원형 파동 + 연기 (Color 옵션 무효) | |
| 원형 파동 후 중심 수렴 | |
| 원형 빛 폭발 | |
| 원형 빛 폭발 + 방향성 빛 | |
| 불/화염 | | 만화풍 불꽃 |
| 강한 만화풍 불꽃 | |
| 한 곳에 화염 생성 | |
| 화염 방출 | |
| 바닥에서 큰 화염 | |
| 바닥에서 중간 화염 | |
| 바닥에서 작은 화염 | |
| 거대한 화염 기둥 (Color 옵션 무효) | |
| 번개/전기 | | 구형 전기 파티클 |
| 번개 | |
| 긴 번개 | |
| 전기파 방출 | |
| 주기적 전기파 | |
| 주기적 번개 | |
| 주기적 긴 번개 | |
| 버프/마법 | | 바닥에서 오로라 빛 |
| 바닥에서 강한 빛 상승 | |
| 큰 파티클이 한 점으로 수렴 | |
| 파티클이 한 점으로 수렴 | |
| 큰 빛 주변에 빛과 파티클 | |
| 회전하는 원 주변에 파티클 | |
| 별빛이 중심으로 수렴 | |
| 넓은 원형 파동 | |
| 바닥에서 기둥 형태 상승 | |
| 기타 | | 불꽃놀이 |
| 여러 폭죽 동시 | |
| 반딧불이 | |
| 옆으로 액체 분출 | |
| 바닥으로 액체 분출 | |
| 넓은 모래 폭풍 | |
| 바닥에서 흰 안개 상승 | |
| 큰 물보라 | |
| 한 곳에 물 뿌림 |
| 이름 | 설명 |
|---|---|
| 스프라이트가 원형으로 퍼지며 등장 |
| 파티클+스프라이트가 원형 영역에 등장 |
| 파티클+스프라이트가 원형으로 폭발 |
| 파티클+스프라이트 단순 등장 |
| 파티클+스프라이트가 퍼짐 |
| 특정 방향으로 이동하며 생성 |
| 가느다란 선으로 특정 방향 이동 |
| 스프라이트에 색상 효과 적용 |
| 이름 | 설명 |
|---|---|
| 비 |
| 눈 |
| 안개 |
| 짙은 내려오는 안개 |
| 올라오는 안개 |
| 올라오는 별 무리 |
| 반짝이는 별 무리 |
| 별+성운 파티클 (제자리) |
| 별+성운 파티클 (상승) |
| 가는 선 |
| 가는 선 + 굵은 선 |
| 빠른 직선 |
| 상황 | 권장 |
|---|---|
| 메이플스토리 스킬/피격 애니메이션 (구체적 이미지) | |
| 범용 피격·폭발 (빠른 구현) | |
| 커스텀 이미지를 파티클로 흩뿌리기 | |
| 비·눈·안개 등 환경 연출 | |
| 버프 오라 등 지속 효과 | 둘 다 |
| 풍성한 연출 | EffectService + ParticleService 동시 조합 |
서버 이벤트 → 클라이언트 이펙트 표준 패턴:프로퍼티 변경 →@Sync감지 → EffectService/ParticleService 호출.OnSyncProperty(ClientOnly)
| 이벤트 | 발행 조건 | 페이로드 |
|---|---|---|
| | 없음 |
| | 없음 |
| 모든 상태 전환 시 자동 | |
HandleHitEventself.LastAttacker = event.AttackerEntityHandleDeadEventPlayerComponent.Respawn/ProcessDead/ProcessRevive| 논리 이벤트 | MSW 구현 |
|---|---|
| OnAttackStart | |
| OnAttackHit / OnDamageTaken | 네이티브 |
| OnAttackMiss | 커스텀 — |
| OnCriticalHit | |
| OnDeath / OnRevive | 네이티브 |
| OnStateChange | 네이티브 |
| OnKill / OnBlocked / OnParry / OnStatusApplied | 커스텀 |
@Event script XxxEvent extends EventTypepropertyhandler@EventSender("Self" | "Service","XxxService" | "Logic","XxxLogic")entity:ConnectEvent(XxxEvent, self.Handler)OnEndPlayDisconnectEvent@Logic CombatEventBusLogic@EventSender("Logic","CombatEventBusLogic")| 패턴 | 적합 | 참조 |
|---|---|---|
FSM ( | 단순 적 (3~5상태), 플레이어 IDLE/HIT/DEAD, 보스 페이즈, 애니메이션 동기 ( | |
BT ( | 패트롤+추적+공격 조합, 다양한 보스 패턴, Composite/Decorator 재사용, 확률 가중 행동 | |
StateComponentStateComponent@State script XxxStateType extends StateTypeOnEnterOnUpdateOnExitOnConditionCheckIDLEDEADHitComponentHITAIChaseAIWanderMOVEATTACKPATROLSTUNPHASE2OnBeginPlayAddState("이름", XxxStateType)AddCondition(from, to)OnConditionCheck()⚠ 상태 이름은 UPPERCASE 강제, 미등록 이름은 즉시.[LEA-3005] InvalidArgument : 'stateName'키를 등록해도AvatarStateAnimationComponent.StateToAvatarBodyActionSheet엔 자동 등록되지 않음 — 둘은 별개.StateComponent
references/fsm-state.mdAIComponentAIComponentSequenceNodeSelectorNodeRandomSelectorNodeParallelNode@BTNodeAIChaseComponentAIWanderComponent⚠ 커스텀 BT 사용 시에서.model/AIChaseComponent제거AIWanderComponent
references/ai-bt.md몬스터 엔티티 전체 구성 →references/monster-setup.md본 SKILL.md는 전투 특화(ATTACK/HIT/DEAD + DeadEvent/ReviveEvent + BT 진입점)만 다룬다. 일반 mlua 상태 머신/스크립팅 패턴은참조.msw-scripting
| UI | API |
|---|---|
| HP 바 (화면 고정) | |
| 데미지 숫자 | |
| 크로스헤어 | |
| 콤보 카운터 / 버프 아이콘 | |
| 옵션 | 방식 | 적합 상황 |
|---|---|---|
| 간이형 | 자식 엔티티 | 임시 프로토타입, 단순 게이지 |
| 완성형 | | 양산용, 다중 몬스터 동시 표시 |
HpMaxHpPlayerComponent/PlayerComponent전체 프로퍼티·메서드 표는PlayerControllerComponent참조. 여기서는 전투 핵심만.msw-defaultplayer/SKILL.md
| 항목 | 사용법 |
|---|---|
| HP 감소 | |
| 사망 판정 | |
| 부활 | |
| 클라 전용 사망 처리 | |
| 방향 판정 ★ | |
| 액션 훅 오버라이드 | |
| 액션 이벤트 수신 | |
PlayerActionEventproperty string ActionName -- "Attack" / "Jump" / "Crouch" / ...
property Entity PlayerEntityPlayerAttack extends AttackComponent@EventSender("Self") handler HandlePlayerActionEvent(...)event.ActionName == "Attack"RootDesk/MyDesk/| 파일 | 역할 | 핵심 포인트 |
|---|---|---|
| 전방 Box 공격 | |
| i-frame | |
| 몬스터 HP | 커스텀 |
| sprite 크기 기반 근접 | |
_UtilLogic.ElapsedSecondsos.clock()| 상수 | 용도 |
|---|---|
| 몬스터 → 플레이어 공격 |
| 플레이어 → 몬스터 공격 |
| |
AvatarStateAnimationComponentStateComponent@Sync property SyncDictionary<string, AvatarBodyActionElement> StateToAvatarBodyActionSheet -- IsLegacy=false
@Sync property SyncDictionary<string, string> ActionSheet -- IsLegacy=true (deprecated)
method void SetActionSheet(string key, string animationClipRuid)
method void RemoveActionSheet(string key)
method string StateStringToAnimationKey(string stateName)
emitter EmitBodyActionStateChangeEvent(BodyActionStateChangeEvent)ChangeState("HIT")MapleAvatarBodyActionState.HitAttackHitDeadAlertHealIsLegacy=falseStateToAvatarBodyActionSheetAvatar 컴포넌트(등) 전반은AvatarRendererComponent참조. 본 섹션은 전투 모션 매핑만 다룬다.msw-defaultplayer
| 용도 | RUID | 사용처 |
|---|---|---|
| 타격 | | 공격자 |
| 피격 | | 피격자 측 표시 — |
| 회복 | | 힐/포션 — |
Attack/AttackFast| 위치 | 컴포넌트 | 역할 |
|---|---|---|
| 공격자 | | 어떤 스킨/스타일로 표시할지 |
| 피격자 | | 표시 위치 오프셋 |
| 피격자 | | 데미지 숫자 본체(엔티티 위) |
.modelDamageSkinSettingComponent| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| DataRef | 타격 RUID (위 표) | 데미지 숫자 스킨 RUID |
| Vector2 | (1, 1) | 숫자 크기 |
| float | 1 | 투명도 |
| float | 1 | 재생 속도 |
| float | 0.05 | 멀티히트 간 딜레이(초) |
| DamageSkinTweenType | Default | 연출 타입 |
| LitMode | Default | 조명 영향 |
DamageSkinTweenTypeDefaultVolcanoBlade*MiniDamageSkinSpawnerComponent| 속성 | 타입 | 기본값 |
|---|---|---|
| Vector2 | (0,0) |
DamageSkinService_DamageSkinService_DamageSkinService:Play(targetEntity, skinRuid, delay, damages:List<int>, tweenType, isCritical, offset, scale, playRate, alpha, litMode)
_DamageSkinService:PlayTextDamage(targetEntity, skinRuid, textType, tweenType)
_DamageSkinService:PreloadAsync(skinRuid, callback(success)) -- ClientOnlyDamageSkinTextTypeMissGuardResistShotCounter⚠는_DamageSkinService:Play영역 — 서버 로직(HP 차감 등)에서 호출하려면Client메서드로 래핑하거나@ExecSpace("Client")프로퍼티 변경 후@Sync에서 트리거.OnSyncProperty
⚠의 필수 파라미터는 6개. 옵셔널 5개를 부분만 흘리면 LEA-3005Play()InvalidArgument
IsCritical=true@ExecSpace("ServerOnly")
method int32 CalcDamage(Entity attacker, Entity defender, string attackInfo)
return 100
end
@ExecSpace("ServerOnly")
method boolean CalcCritical(Entity attacker, Entity defender, string attackInfo)
return math.random() < 0.3
end
method float GetCriticalDamageRate()
return 2.5 -- 100 → 250
endDamageSkinSettingComponent.TweenType = VolcanoBladelocal HEAL_RUID = "d58b67cf0f3a4eaf9fe1ad87c0ffac8a"
@ExecSpace("Client")
method void ShowHeal(Entity target, integer amount)
_DamageSkinService:Play(
target, HEAL_RUID, 0,
{ amount }, -- damages
DamageSkinTweenType.Default,
false, -- isCritical
Vector2(0, 0.5), -- offset (머리 위)
Vector2(1, 1), 1.0, 1.0, LitMode.Default
)
endlocal HIT_RUID = "02c22d93421b4038b3c413b3e40b57ec"
@ExecSpace("Client")
method void ShowMiss(Entity target)
_DamageSkinService:PlayTextDamage(
target, HIT_RUID, DamageSkinTextType.Miss, DamageSkinTweenType.Default
)
endAttackComponent:IsAttackTarget_DamageSkinService:PlaydamagesDelayPerAttack_DamageSkinService:Play(target, ATTACK_RUID, 0, { 12, 8, 14, 11, 9 },
DamageSkinTweenType.Default, false, Vector2(0,0), Vector2(1,1), 1, 1, LitMode.Default)HitEvent.DamagesGetDisplayHitCount(attackInfo)@ExecSpace("ClientOnly")
method void OnBeginPlay()
_DamageSkinService:PreloadAsync("3271c3e79bf04ecba9a107d55495970d", function(ok) end)
_DamageSkinService:PreloadAsync("02c22d93421b4038b3c413b3e40b57ec", function(ok) end)
_DamageSkinService:PreloadAsync("d58b67cf0f3a4eaf9fe1ad87c0ffac8a", function(ok) end)
end| TweenType | 추천 상황 |
|---|---|
| 일반 타격 |
| 크리티컬 / 광역 타격 (위로 흩뿌림) |
| 연속 베기 / 콤보 (숫자 겹침) |
| DoT(독/화상) 같은 작은 데미지 — 화면 점유 줄임 |
DamageSkinSettingComponent.DamageSkinIdself.Entity.DamageSkinSettingComponent.DamageSkinId = MY_TEAM_SKIN_RUIDHitEffectSpawnerComponentHitEvent.modelAttackComponentDamageSkinSettingComponentHitComponentHitEffectSpawnerComponentDamageSkinSpawnerComponentDamageSkinComponentIsLegacy=falseColliderTypeBoxSizeCircleRadiusCollisionGroupStateComponentAvatarStateAnimationComponent.StateToAvatarBodyActionSheetATTACKHITDEADPlayerComponent.Hp@Sync HpLookDirectionX_UtilLogic.ElapsedSecondsOnEndPlayDisconnectEventTransformComponent.Positionmsw-defaultplayermsw-scripting.modelmsw-general