Files
maplecontest/tools/player/gen-lobby-npc.mjs
gahusb 9cb5e1abff feat(lobby): 로비 카메라를 플레이어 추종(follow)으로 — 전투맵은 고정 유지
로비 루트에서 script.MapCamera 제거(고정 framing 억제 해제) + LobbyMobility가 진입 시
ConfineCameraArea=false·ScreenOffset(0.5,0.5)·Zoom 90으로 플레이어 추종 카메라 설정.
MSW 카메라는 기본 follow이고 ConfineCameraArea=true가 그걸 억제하므로 false가 핵심.
검증: 로비 우측 이동 시 플레이어 중앙 유지+배경 스크롤, 런 시작→map01 Confine=true 고정, 복귀→follow 복원(누설 없음).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:05:22 +09:00

115 lines
5.4 KiB
JavaScript

import { writeFileSync } from 'node:fs';
// 로비 codeblock 2종 emit (맵/엔티티 부착은 tools/map/gen-lobby-map.mjs 소관):
// · LobbyNpc — NPC 엔티티에 부착. 근접 폴링→머리위 마크 토글, TouchEvent(클릭)/UpArrow(근접)→Interact→컨트롤러 호출.
// · LobbyMobility — 로비 맵 루트에 부착. 진입 시 플레이어 이동 잠금 해제(정찰 확정: WalkAcceleration이 진짜 레버).
// 공격 키(LeftControl) 바인딩은 SlayDeckController(/common, 1회 등록·로비 가드)에서 처리 — 여기 두지 않음.
// 정찰: 이동에는 RigidbodyComponent.WalkAcceleration(가속)과 MovementComponent.InputSpeed(속도)가
// 둘 다 양수여야 함 — freeze-turn-player가 둘 다 0으로 만들었으므로 둘 다 복원해야 걷는다(실측 확인).
// 값은 freeze가 건드리지 않은 intact RigidbodyComponent.WalkSpeed(1.4)/WalkJump(1.23) = 기본값에 맞춤.
// (실측: InputSpeed 1.4→보행 ~2.5u/s, JumpForce 1.23→점프 상승 1.79u. 이전 5/5는 ~9u/s·상승 14u로 과함.)
const WALK_ACCEL = 0.7;
const WALK_SPEED = 1.4;
const JUMP_FORCE = 1.23;
const PROX = 1.2; // 근접 임계(맵 NPC 간격 2.5와 분리)
// 로비 카메라는 전투맵의 고정 framing(ConfineCameraArea=true)과 달리 플레이어를 추종(follow).
// MSW 카메라는 기본이 follow이고 ConfineCameraArea=true가 그걸 억제하므로, false로 풀고 ScreenOffset을 중앙에 둔다.
const LOBBY_ZOOM = 90; // 전투맵과 동일 줌(전환 시 위화감 최소). 플레이테스트로 조정 가능.
function prop(Type, Name, DefaultValue = 'nil') {
return { Type, DefaultValue, SyncDirection: 0, Attributes: [], Name };
}
function method(Name, Code, Arguments = [], ExecSpace = 6) {
return {
Return: { Type: 'void', DefaultValue: null, SyncDirection: 0, Attributes: [], Name: null },
Arguments, Code, Scope: 2, ExecSpace, Attributes: [], Name,
};
}
function writeCodeblock(name, properties, methods) {
const cb = {
Id: '', GameId: '', EntryKey: `codeblock://${name.toLowerCase()}`, ContentType: 'x-mod/codeblock',
Content: '', Usage: 0, UsePublish: 1, UseService: 0, CoreVersion: '26.5.0.0', StudioVersion: '', DynamicLoading: 0,
ContentProto: { Use: 'Json', Json: {
CoreVersion: { Major: 0, Minor: 2 }, ScriptVersion: { Major: 1, Minor: 0 },
Description: '', Id: name, Language: 1, Name: name, Type: 1, Source: 0, Target: null,
Properties: properties, Methods: methods, EntityEventHandlers: [],
} },
};
writeFileSync(`RootDesk/MyDesk/${name}.codeblock`, JSON.stringify(cb, null, 2) + '\n', 'utf8');
}
// ── LobbyNpc ──────────────────────────────────────────────────────────────
const npcInteract = method('Interact', `local c = _EntityService:GetEntityByPath("/common")
if c ~= nil and c.SlayDeckController ~= nil then
c.SlayDeckController:OnLobbyNpcInteract(self.NpcId)
end`);
const npcBegin = method('OnBeginPlay', `self.InRange = false
local mark = _EntityService:GetEntityByPath("/maps/lobby/" .. self.MarkName)
if mark ~= nil then mark:SetVisible(false) end
self.Entity:ConnectEvent(TouchEvent, function(e)
self:Interact()
end)
_InputService:ConnectEvent(KeyDownEvent, function(e)
if self.InRange and e.key == KeyboardKey.UpArrow then
self:Interact()
end
end)
local eventId = 0
local function tick()
local lp = _UserService.LocalPlayer
if lp == nil then return end
if mark == nil then mark = _EntityService:GetEntityByPath("/maps/lobby/" .. self.MarkName) end
local a = lp.TransformComponent.WorldPosition
local b = self.Entity.TransformComponent.WorldPosition
local d = Vector2.Distance(Vector2(a.x, a.y), Vector2(b.x, b.y))
local near = d < ${PROX}
if near ~= self.InRange then
self.InRange = near
if mark ~= nil then mark:SetVisible(near) end
end
end
eventId = _TimerService:SetTimerRepeat(tick, 0.15)`);
writeCodeblock('LobbyNpc', [
prop('string', 'NpcId', '""'),
prop('string', 'MarkName', '""'),
prop('boolean', 'InRange', 'false'),
], [npcBegin, npcInteract]);
// ── LobbyMobility ─────────────────────────────────────────────────────────
const mobBegin = method('OnBeginPlay', `self.Tries = 0
local eventId = 0
local function apply()
self.Tries = self.Tries + 1
local lp = _UserService.LocalPlayer
if lp ~= nil and lp.PlayerControllerComponent ~= nil then
local pc = lp.PlayerControllerComponent
pc.Enable = true
pc.FixedLookAt = 0
local rb = lp.RigidbodyComponent
if rb ~= nil then rb.WalkAcceleration = ${WALK_ACCEL} end
local mv = lp.MovementComponent
if mv ~= nil then
mv.InputSpeed = ${WALK_SPEED}
mv.JumpForce = ${JUMP_FORCE}
end
local cam = lp.CameraComponent
if cam == nil then cam = _CameraService:GetCurrentCameraComponent() end
if cam ~= nil then
cam.ZoomRatio = ${LOBBY_ZOOM}
cam.ConfineCameraArea = false
cam.ScreenOffset = Vector2(0.5, 0.5)
cam.CameraOffset = Vector2(0, 0)
end
_TimerService:ClearTimer(eventId)
elseif self.Tries > 50 then
_TimerService:ClearTimer(eventId)
end
end
eventId = _TimerService:SetTimerRepeat(apply, 0.1)`);
writeCodeblock('LobbyMobility', [prop('number', 'Tries', '0')], [mobBegin]);
console.log('[gen-lobby-npc] LobbyNpc / LobbyMobility codeblock 생성 완료');