其他
用 MSW 原生 API 搭建 2D 战斗系统(攻击判定…
在 MSW(MapleStory Worlds)中基于原生组件与 API 实现 2D 战斗全流程时,按此清单逐层落地攻击判定、伤害模型、受击反应、手感、状态与 AI。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
用 MSW 原生 API 搭建 2D 战特定
解决思路
- 攻击判定:用 AttackComponent 的 Attack(size, offset, attackInfo, cg) / Attack(shape, attackInfo, cg) / AttackFrom(...) 触发,返回命中表;大规模弹幕用 AttackFast(shape, attackInfo, cg)(不建命中表,性能更好)。
- 形状只支持 Box/Circle/Polygon:用 CircleShape(position, radius) / BoxShape(position, size, angle) / PolygonShape(position, points, angle);轴对齐矩形用 BoxShape(center, size, 0),无 RectangleShape;不需要旋转时 angle 传 0。多边形命中面用 HitComponent.PolygonPoints: SyncList<Vector2>。
- 目标过滤:攻击方重写 AttackComponent:IsAttackTarget(defender, attackInfo) → boolean,受击方重写 HitComponent:IsHitTarget(attackInfo) → boolean;任一返回 false 即排除该次命中;父类调用用 __base:IsAttackTarget(...)。
- 重写 IsAttackTarget / IsHitTarget 时不要加 @ExecSpace 注解(父类为未指定 ExecSpace = All),否则运行时报 LEA-3014 SignatureMismatch。
- Attack*(..., cg) 的 cg 是对受击方 HitComponent.CollisionGroup 的精确匹配过滤(默认 CollisionGroups.HitBox),不查碰撞矩阵、与攻击方自身分组无关;nil(省略)= 所有 HitComponent 都是候选;传了有效但与目标实际分组不同的组会静默命中 0 个目标;传本世界不存在的组引用等同 nil;传组前先把目标怪物 HitComponent.CollisionGroup 设为同一组(示例工程怪物覆盖为 Monster)。
- 用 attackInfo 字符串做扩展标记(如 "melee.light"、"dot.poison"),它会传入 CalcDamage/IsHitTarget/GetDisplayHitCount。
- HitComponent 的 ColliderType/ColliderOffset/PolygonPoints 仅在 IsLegacy = false 时有效;BoxOffset/ColliderName 已废弃。
- 按攻击形态选形状:正面近战 Box 用 BoxShape(pos + LookDirectionX*offset, size, 0);圆形 AoE 用 CircleShape(self.WorldPos, radius);投射物用无 Body 模型(仅 Sprite+Transform),在 OnUpdate(delta) 中 TransformComponent:Translate(speed*delta, 0) 并按距离判定命中后 _EntityService:Destroy。
- 连续移动(追击、飞行、自动移动)一律用每帧 OnUpdate(delta):怪物/NPC/AI 用 MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeed(第二个参数 0,deltaTime 只在梯子上生效);投射物/掉落物/特效等无 Body 实体用 TransformComponent:Translate(speed*delta, 0)。
- 禁止用 _TimerService:SetTimerRepeat(move, 0.1~0.15) 做移动(6~10Hz 瞬移、无插值);禁止在 OnUpdate 里用 body:SetPosition(...) / MovementComponent:SetPosition(...)(都是瞬移方法,仅用于一次性生成/复活/吸附);禁止对有活动 Body 的实体直接赋值 TransformComponent.Position(物理引擎下一帧覆盖且网络同步被阻断);禁止不带 delta 的固定步长移动(如 Translate(0.009, 0))。
- 忽略官方检索返回的“Entity Movement Control Using MovementComponent”文档(其正文是 OnUpdate 内每帧 SetPosition 的反模式),以及 FlappyFish Remake、Stopping the Taxi、Making a Moving Foothold 等缺 delta 或直接赋值 Position 的示例。
- 怪物/NPC 的 .model 默认不含 MovementComponent,需包含:MOD.Core.TransformComponent、MOD.Core.SpriteRendererComponent、对应地图类型的 Body(RigidbodyComponent(MapleTile) / KinematicbodyComponent(RectTile) / SideviewbodyComponent(SideViewRectTile))、MOD.Core.MovementComponent(如 InputSpeed = 2.0)。
- 碰撞式命中按意图选检测方式:主动攻击挥击/命中盒/投射物爆炸用 AttackComponent:Attack/AttackFast/AttackFrom(走完整管线:伤害皮肤、命中特效、IsHitTarget 无敌帧、OnHit);身体接触/区域/陷阱重叠且目标任意或很多用 TriggerComponent + OnEnterTriggerBody/OnStayTriggerBody/OnLeaveTriggerBody(或 TriggerEnter/Stay/LeaveEvent)+ CollisionGroup 过滤,再把伤害走 HitEvent 管线;只有单个已知目标的追踪才用每帧 OnUpdate 距离判定。
- 不要用每帧 math.sqrt(dx*dx+dy*dy) < r 做玩家↔怪物或多目标碰撞:它把碰撞体近似为以 transform 为心的圆(忽略各碰撞体形状/大小/ColliderOffset)、跳过 CollisionGroup 过滤,且每帧 O(攻击者×目标) 开销。
- TriggerComponent 的 CollisionGroup 默认 CollisionGroups.TriggerBox;OnStayTriggerBody 在重叠期间每帧触发,需用冷却门控伤害,不要每帧结算;检测不等于结算,原生重叠触发后仍要通过 HitEvent / AttackComponent 造成伤害,绝不直接扣 HP。
- 伤害模型:重写 AttackComponent 的 CalcDamage(attacker, defender, attackInfo)(默认 1)、CalcCritical(...)(默认 false)、GetCriticalDamageRate()(默认 2.0)、GetDisplayHitCount(attackInfo)(默认 1)、OnAttack(defender);受击方重写 HitComponent:OnHit(attacker, damage, isCritical, attackInfo, hitCount) 或用 EmitHitEvent(HitEvent)。
- 上述伤害钩子父类均为未指定 ExecSpace(= All),重写时不要加 @ExecSpace("ServerOnly") 等注解,否则 LEA-3014 SignatureMismatch;只写 method ... 即可。
- HitEvent 载荷:AttackCenter、AttackerEntity(可 nil)、Damages: List<integer>(多段拆分)、Extra: any(扩展槽,用于击退/眩晕/元素/标签)、IsCritical、TotalDamage、FeedbackAction(整个枚举已废弃);辅助信息(击退向量、硬直时间、元素)放 Extra 表。AttackEvent 只有一个字段 DefenderEntity,攻击方即处理器的 self。
- 禁止直接扣 HP(如 monster.Hp -= damage / target.MonsterAI.HP -= damage):不会发出 HitEvent,伤害皮肤、命中特效、IsHitTarget 免疫、OnHit 重写全部静默跳过;玩家侧自定义伤害还会破坏 avatar 动画(AvatarStateAnimationComponent 只响应 StateChangeEvent),必须绕过时手动调用 StateComponent:ChangeState("HIT")(大写键名)并在死亡/复活时调用 PlayerComponent:ProcessDead() / ProcessRevive()。
- 击退按 Body 类型实现:Rigidbody(MapleTile) 用 body:AddForce(Vector2(dir*5, 3))(推荐)/ SetForce / JustJump(Vector2(0, 4));Kinematicbody(RectTile/俯视) 用 body.MoveVelocity = Vector2(dir*5, 0)(无 AddForce);Sideviewbody(SideViewRectTile) 用 body.MoveVelocity + body.JumpSpeed。Rigidbody 由引擎自动阻尼,Kinematic/Sideview 需在 OnUpdate 手动阻尼(MoveVelocity *= 0.9);撞墙反弹订阅 FootholdCollisionEvent 并翻转速度。
- 无敌帧(i-frame):标准做法是基于 _UtilLogic.ElapsedSeconds 的截止时间检查 + 在 HitComponent:IsHitTarget 返回 false(DefaultPlayer 的 PlayerHit.mlua 已提供该模式);更精确的做法是无敌期间把 HitComponent.CollisionGroup 换成单独分组,使判定本身被排除。
- 状态效果(Buff/Debuff)无原生支持:自定义 @Component BuffComponent + _TimerService:SetTimerRepeat 逐帧 tick + 广播自定义 StatusAppliedEvent/StatusExpiredEvent;单一简单眩晕用 StateComponent:ChangeState("STUN") + 输入/AI 屏蔽标志即可。
- 手感全部原生:全局顿帧 _UtilLogic:SetClientTimeScale(float)(0~100,ClientOnly);单体顿帧 renderer.PlayRate = 0(Sprite/Skeleton/Avatar,@Sync);慢动作 _UtilLogic:SetClientTimeScale(0.3) + 定时恢复;镜头震动 cameraComp:ShakeCamera(intensity, duration, targetUserId?);镜头缩放 cameraComp:SetZoomTo(percent, duration, targetUserId?)(需先 IsAllowZoomInOut=true);受击闪白 spriteRenderer.Color = Color(r,g,b,a) + 定时恢复;HDR 过亮 Color.HSVToRGB(h, s, v, hdr=true)(允许 > 1.0)。
- 特效与音效:固定特效 _EffectService:PlayEffect(clipRUID, instigator, pos, zRot, scale, isLoop?, options?) 返回 serial;挂点特效 PlayEffectAttached(clipRUID, parent, localPos, localZRot, localScale, isLoop?, options?);移除 RemoveEffect(serial)。2D 音效 _SoundService:PlaySound(id, volume, targetUserId?);3D 音效 PlaySoundAtPos(id, pos, listener, volume);循环 PlayLoopSound / PlayLoopSoundAtPos;挂点 SoundComponent:Play() + Pitch 0~3 随机音高;BGM PlayBGM(id, volume) / StopBGM(immediately);预加载 LoadSound(id)(ClientOnly)。PlayEffect 的 options 键:FlipX, FlipY, SortingLayer, OrderInLayer, Alpha, StartFrameIndex, EndFrameIndex, PlayRate, SyncFlip, Color, MaterialID, IgnoreMapLayerCheck, LitMode。当前相机用 _CameraService:GetCurrentCameraComponent()。
- 内置粒子用 _ParticleService:通用预设 PlayBasicParticle(BasicParticleType, instigator, pos, zRot, scale, isLoop, options) / PlayBasicParticleAttached(...);自定义贴图粒子 PlaySpriteParticle(SpriteParticleType, spriteRUID, instigator, pos, zRot, scale, isLoop, options) / PlaySpriteParticleAttached(...);环境区域粒子 PlayAreaParticle(AreaParticleType, areaSize, instigator, pos, zRot, scale, isLoop, options);移除 RemoveParticle(serial)。options 键:Color, SortingLayer, OrderInLayer, ParticleSize, ParticleCount。isLoop=true 的粒子必须用 RemoveParticle(serial) 清理,serial 存到 self._T。
- 特效方案选择:MapleStory 技能/命中动画(特定画面)用 EffectService(指定 RUID);通用命中/爆炸(快速实现)用 ParticleService.BasicParticle;把自定义图片散成粒子用 SpriteParticle;雨雪雾等环境氛围用 AreaParticle;持续效果(buff 光环)任选其一并 isLoop=true;层次丰富的效果把 EffectService 与 ParticleService 组合。服务端事件→客户端特效的标准模式:@Sync 属性变化 → 在 OnSyncProperty(ClientOnly) 中检测 → 调用 EffectService/ParticleService。
- 死亡/复活:DeadEvent 在 StateComponent:ChangeState("DEAD") 时自动发出(无载荷);ReviveEvent 在 PlayerComponent:Respawn() 时自动发出(仅玩家,无载荷);StateChangeEvent 在每次状态切换时自动发出(CurrentStateName、PrevStateName)。DeadEvent 无载荷,需在 HandleHitEvent 中缓存 self.LastAttacker = event.AttackerEntity 以追踪击杀者;玩家死亡/复活优先用 PlayerComponent.Respawn/ProcessDead/ProcessRevive。
- 事件总线:OnAttackStart 用 OnAttack 钩子或自定义 AttackStartEvent;OnAttackHit/OnDamageTaken 用原生 HitEvent;OnAttackMiss 自定义(IsAttackTarget 返回 false 时 SendEvent);OnCriticalHit 用 HitEvent.IsCritical;OnDeath/OnRevive 用原生 DeadEvent/ReviveEvent;OnStateChange 用原生 StateChangeEvent;OnKill/OnBlocked/OnParry/OnStatusApplied 用自定义 @Event。
- 自定义事件规则:定义 @Event script XxxEvent extends EventType + property 声明;接收用 handler 关键字(不是 method)+ @EventSender("Self"|"Service","XxxService"|"Logic","XxxLogic");连接/断开用 entity:ConnectEvent(XxxEvent, self.Handler) 并在 OnEndPlay 中显式 DisconnectEvent(引擎不自动断开);全局事件用 @Logic CombatEventBusLogic 单例 + @EventSender("Logic","CombatEventBusLogic")。
- AI 三选一:简单敌人(3~5 状态)、玩家 IDLE/HIT/DEAD、Boss 阶段、动画同步用 FSM(StateComponent + @State,需 StateComponent.IsLegacy=false 才能让 StateAnimationComponent 自动换 clip);巡逻+追击+攻击组合、多样 Boss 模式、Composite/Decorator 复用、概率加权动作用 BT(AIComponent + 4 种原生 Composite + @BTNode,需 IsLegacy=false);不适合 AIChase/AIWander 的行为(漫游↔站立↔说话↔攻击、按距离门控攻击、说话待机)用自持状态的自定义脚本(@Component 持有 CurrentAIState + 直接赋 SpriteRUID,Soldier 风格,无需 AIChaseComponent/AIWanderComponent、无需 IsLegacy=false,StateComponent 只保留 IDLE↔DEAD)。
- FSM:StateComponent + @State script XxxStateType extends StateType(生命周期 OnEnter/OnUpdate/OnExit/OnConditionCheck);只有 IDLE/DEAD 自动注册(有 HitComponent 时加 HIT,有 AIChase/AIWander 时加 MOVE),ATTACK/PATROL/STUN/PHASE2 等必须在 OnBeginPlay 中用 AddState("name", XxxStateType) 预注册;自动切换用 AddCondition(from, to) + 每帧 OnConditionCheck()。状态名必须大写,未注册名立即报 [LEA-3005] InvalidArgument : 'stateName';在 AvatarStateAnimationComponent.StateToAvatarBodyActionSheet 中注册键不会自动注册到 StateComponent,两者独立。
- BT:AIComponent + SequenceNode/SelectorNode/RandomSelectorNode/ParallelNode + @BTNode 动作节点 + 原生 AIChaseComponent/AIWanderComponent;4 种 Composite 全部原生,可用 @BTNode ... extends CompositeNode(ChildCount/ChildBehave)自定义子节点流转策略;Decorator/Memory(Blackboard)/Threat Table 需手写。使用自定义 BT 时要从 .model 中移除 AIChaseComponent/AIWanderComponent。
- UI:屏幕固定 HP 条用 SpriteGUIRendererComponent 以 HP gauge 切片图(image_ruid = "f0911af597259044aa624a11332c0595")填充 + 左轴点 UITransformComponent 改宽度(只读 HP 条不要用 SliderComponent,SliderComponent 用于用户可拖动控件,且仅限 UI 实体);伤害数字用 3 个 DamageSkin* 组件 + DamageSkinService;准星用 .ui 中的 SpriteGUIRendererComponent;连击计数/buff 图标用 TextGUIRendererComponent + SpriteGUIRendererComponent。
- 世界空间头顶 HP 条无原生支持,两种实现:轻量版改子实体 SpriteRendererComponent 的 LocalScale.x = hp/maxHp,或用 TiledSize.x(配 SpriteDrawMode.Tiled),适合快速原型/简单血条;完整版基于 PixelRendererComponent,适合生产级、同屏大量怪物。
- 玩家战斗原生能力:HP 扣减用 self.Entity.PlayerComponent.Hp -= event.TotalDamage;死亡检查 PlayerComponent:IsDead();复活 PlayerComponent:Respawn()(RespawnPosition → SpawnLocation → 地图入口,自动发 DeadEvent/ReviveEvent);客户端死亡处理 @ExecSpace("Client") ProcessDead(targetUserId?) / ProcessRevive(targetUserId?);方向判断用 PlayerControllerComponent.LookDirectionX(+1 右,-1 左),不要用 TransformComponent.Scale.x;动作钩子重写 ActionAttack / ActionJump / ActionInteraction(key, isKeyDown);动作事件接收 EmitPlayerActionEvent(PlayerActionEvent)。不要自建 Hp/MaxHp 属性,用 PlayerComponent。
- 自动攻击/AI 触发攻击前必须先设朝向:LookDirectionX 是可写 @Sync 属性,赋 1/-1 可翻转玩家朝向;默认输入管线只在移动输入时更新它,所以无移动的攻击(自动战斗循环、AI tick、技能按钮)会保持上次朝向(默认左),导致朝向错误且基于 LookDirectionX 的攻击盒在错误一侧结算。攻击前指向目标:controller.LookDirectionX = target.TransformComponent.WorldPosition.x >= self.Entity.TransformComponent.WorldPosition.x and 1 or -1;只赋符号(±1),绝不赋原始位置差(攻击偏移会乘 LookDirectionX,非单位值会缩放/错位命中盒)。
- PlayerActionEvent 载荷:ActionName("Attack"/"Jump"/"Crouch" 等)、PlayerEntity。默认模式是 PlayerAttack extends AttackComponent 接收 @EventSender("Self") handler HandlePlayerActionEvent(...) 并按 event.ActionName == "Attack" 分支。
- 可直接复制使用的默认模板(RootDesk/MyDesk/):PlayerAttack.mlua(正面 Box 攻击,用 LookDirectionX 定方向,AttackFast + CollisionGroups.Monster,CalcDamage=50,30% 暴击);PlayerHit.mlua(无敌帧,ImmuneCooldown 属性,_UtilLogic.ElapsedSeconds 截止时间,重写 IsHitTarget);Monster.mlua(怪物 HP,自定义 @Sync Hp,HandleHitEvent → Dead/Respawn);MonsterAttack.mlua(按 Sprite 尺寸的近战,isvalid(defender.PlayerComponent) + 在 IsAttackTarget 中 __base:IsAttackTarget(...))。
- 时间基准用 _UtilLogic.ElapsedSeconds(世界时钟,暂停/恢复一致),不要用 os.clock()。
- 标准 CollisionGroups:CollisionGroups.Player(怪物→玩家攻击)、CollisionGroups.Monster(玩家→怪物攻击)、CollisionGroups.HitBox(HitComponent.CollisionGroup 默认值)。
- Avatar 动作:AvatarStateAnimationComponent 自动把 StateComponent 切换映射到 avatar 动画;IsLegacy=false 时用 @Sync SyncDictionary<string, AvatarBodyActionElement> StateToAvatarBodyActionSheet(IsLegacy=true 的 ActionSheet 已废弃);方法 SetActionSheet(key, animationClipRuid) / RemoveActionSheet(key) / StateStringToAnimationKey(stateName);事件 EmitBodyActionStateChangeEvent。ChangeState("HIT") 会自动播放映射的 MapleAvatarBodyActionState.Hit;战斗相关状态值:Attack=3、Hit=14、Dead=10、Alert=4、Heal=13。
- 伤害皮肤自动模式:攻击方加 DamageSkinSettingComponent,受击方加 DamageSkinSpawnerComponent + DamageSkinComponent,三者齐全时 Attack/AttackFast 自动显示伤害数字,零脚本。DamageSkinSettingComponent 属性:DamageSkinId(DataRef,默认命中 RUID)、DamageSkinScale(Vector2,默认 (1,1))、Alpha(默认 1)、PlayRate(默认 1)、DelayPerAttack(默认 0.05,多段间隔秒)、TweenType(DamageSkinTweenType,默认 Default)、LitMode。DamageSkinSpawnerComponent 属性:DamageSkinOffset(Vector2,默认 (0,0))。
- 默认 RUID:命中 3271c3e79bf04ecba9a107d55495970d(攻击方 DamageSkinSettingComponent.DamageSkinId 默认);受击 02c22d93421b4038b3c413b3e40b57ec(受击方显示,需手动调 _DamageSkinService:Play);治疗 d58b67cf0f3a4eaf9fe1ad87c0ffac8a(需手动调 Play)。
- 手动模式用 _DamageSkinService:Play(targetEntity, skinRuid, delay, damages:List<int>, tweenType, isCritical, offset, scale, playRate, alpha, litMode);PlayTextDamage(targetEntity, skinRuid, textType, tweenType);PreloadAsync(skinRuid, callback(success))(ClientOnly)。DamageSkinTextType:Miss / Guard / Resist / Shot / Counter。Play() 有 6 个必填参数,只传部分可选参数会报 LEA-3005 InvalidArgument。_DamageSkinService:Play 在 Client 空间,从服务端逻辑调用需包在 @ExecSpace("Client") 方法中,或改 @Sync 属性后在 OnSyncProperty 中触发。
- 伤害皮肤配方:暴击强调用自动模式(IsCritical=true 自动红字)+ 动态放大攻击方 scale,并用 TweenType = Volcano(扇形散开)或 Blade(重叠)区分;治疗/恢复手动调 Play 并传治疗 RUID、offset 如 Vector2(0, 0.5)(头顶上方);Miss/Guard/Resist 用 PlayTextDamage 配 DamageSkinTextType.Miss(在 IsAttackTarget 返回 false 时调用,实现“闪避动画 + 伤害 0”);多段命中把 List 传给 damages 即按 DelayPerAttack 间隔依次显示(自动模式行为相同,用 GetDisplayHitCount(attackInfo) 控制拆分数量);进图时用 PreloadAsync 预加载三个默认 RUID 防止首次显示卡顿;TweenType 选用:Default 普通命中、Volcano 暴击/范围命中、Blade 连续斩击/连招、*Mini 毒/灼烧等 DoT(减少画面杂乱);分阵营皮肤在运行时改 DamageSkinSettingComponent.DamageSkinId。
- 命中特效:给受击方加 HitEffectSpawnerComponent,HitEvent 时自动播放命中特效,无属性,加进 .model 即可。
- 完整战斗检查清单:攻击方模型有 AttackComponent 派生脚本(可选 DamageSkinSettingComponent);受击方模型有 HitComponent + HitEffectSpawnerComponent(可选 DamageSkinSpawnerComponent + DamageSkinComponent);HitComponent 设 IsLegacy=false、ColliderType/BoxSize/CircleRadius、CollisionGroup;在 StateComponent 与 AvatarStateAnimationComponent.StateToAvatarBodyActionSheet 中注册 ATTACK/HIT/DEAD;HP 处理玩家用 PlayerComponent.Hp、怪物用自定义 @Sync Hp;方向判断用 LookDirectionX(不用 Scale.x),自动/AI 攻击前先设朝向(±1);时间基准用 _UtilLogic.ElapsedSeconds(不用 os.clock);OnEndPlay 中显式 DisconnectEvent;有活动 Body 的实体不要直接赋 TransformComponent.Position。
- 以下需自行实现:Buff/Debuff、BT Decorator/Memory(Blackboard)、仇恨/威胁表、投射物池化、穿透/最大命中数、硬直等级系统、资源(MP/Stamina/Rage)、连招/取消窗口、格挡/招架、世界→屏幕坐标转换、世界空间 HP 条。
适用边界 · 注意事项
- 不在 MSW(MapleStory Worlds)引擎中开发、或无法使用其原生组件与 API 时,本经验不适用。
- 需要 Buff/Debuff、BT Decorator/Memory、仇恨表、投射物池化、穿透/最大命中数、硬直等级、MP/Stamina/Rage、连招/取消窗口、格挡/招架、世界→屏幕坐标转换、世界空间 HP 条等原生不支持的功能时,必须自行实现,不能直接照搬本流程。
- 通用玩家主题(HP/移动/相机/时装)、mlua 语法与生命周期、.model 编写规则请分别参考 msw-defaultplayer、msw-scripting、msw-general,不在本经验范围内。