feat(map-camera): 맵별 고정 카메라 — MapCamera 스크립트로 11맵 카메라 framing 고정
맵 로드 시 플레이어 CameraComponent를 data/camera.json 값(현재 map01: zoom100·screenOffset0.5/0.655·confine)으로 설정. - data/camera.json: 카메라 framing 단일 설정 - tools/gen-camera.mjs: MapCamera.codeblock 생성 + 11맵 루트에 script.MapCamera 부착(idempotent) - 새 CameraComponent 미생성(엔진 소유) — 기존 플레이어 카메라 속성만 런타임 설정 - OnBeginPlay(client, ExecSpace 6) + LocalPlayer 카메라 재시도 타이머 - 메이커 Play 검증: zoom 60 테스트로 적용 입증, 100으로 복원. idempotent·결정적 - 참고: 맵 루트 client 스크립트는 ExecSpace 6 필요(1은 미발동) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
60
RootDesk/MyDesk/MapCamera.codeblock
Normal file
60
RootDesk/MyDesk/MapCamera.codeblock
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"Id": "",
|
||||
"GameId": "",
|
||||
"EntryKey": "codeblock://mapcamera",
|
||||
"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": "MapCamera",
|
||||
"Language": 1,
|
||||
"Name": "MapCamera",
|
||||
"Type": 1,
|
||||
"Source": 0,
|
||||
"Target": null,
|
||||
"Properties": [
|
||||
{
|
||||
"Type": "number",
|
||||
"DefaultValue": "0",
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": "CamTries"
|
||||
}
|
||||
],
|
||||
"Methods": [
|
||||
{
|
||||
"Return": {
|
||||
"Type": "void",
|
||||
"DefaultValue": null,
|
||||
"SyncDirection": 0,
|
||||
"Attributes": [],
|
||||
"Name": null
|
||||
},
|
||||
"Arguments": [],
|
||||
"Code": "self.CamTries = 0\nlocal eventId = 0\nlocal function apply()\n\tself.CamTries = self.CamTries + 1\n\tlocal cam = nil\n\tlocal lp = _UserService.LocalPlayer\n\tif lp ~= nil then\n\t\tcam = lp.CameraComponent\n\tend\n\tif cam == nil then\n\t\tcam = _CameraService:GetCurrentCameraComponent()\n\tend\n\tif cam ~= nil then\n\t\tcam.ZoomRatio = 100\n\t\tcam.ScreenOffset = Vector2(0.5, 0.655)\n\t\tcam.ConfineCameraArea = true\n\t\t_TimerService:ClearTimer(eventId)\n\telseif self.CamTries > 30 then\n\t\t_TimerService:ClearTimer(eventId)\n\tend\nend\neventId = _TimerService:SetTimerRepeat(apply, 0.1)",
|
||||
"Scope": 2,
|
||||
"ExecSpace": 6,
|
||||
"Attributes": [],
|
||||
"Name": "OnBeginPlay"
|
||||
}
|
||||
],
|
||||
"EntityEventHandlers": []
|
||||
}
|
||||
}
|
||||
}
|
||||
6
data/camera.json
Normal file
6
data/camera.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"zoomRatio": 100,
|
||||
"screenOffsetX": 0.5,
|
||||
"screenOffsetY": 0.655,
|
||||
"confineCameraArea": true
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "bdadf19a-cc27-4a45-99c6-7a439c858a1b",
|
||||
"path": "/maps/map01",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map01",
|
||||
"path": "/maps/map01",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6946,4 +6950,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "000007d0-0000-4000-8000-0000000007d0",
|
||||
"path": "/maps/map02",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map02",
|
||||
"path": "/maps/map02",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00000bb8-0000-4000-8000-000000000bb8",
|
||||
"path": "/maps/map03",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map03",
|
||||
"path": "/maps/map03",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00000fa0-0000-4000-8000-000000000fa0",
|
||||
"path": "/maps/map04",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map04",
|
||||
"path": "/maps/map04",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00001388-0000-4000-8000-000000001388",
|
||||
"path": "/maps/map05",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map05",
|
||||
"path": "/maps/map05",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00001770-0000-4000-8000-000000001770",
|
||||
"path": "/maps/map06",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map06",
|
||||
"path": "/maps/map06",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00001b58-0000-4000-8000-000000001b58",
|
||||
"path": "/maps/map07",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map07",
|
||||
"path": "/maps/map07",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00001f40-0000-4000-8000-000000001f40",
|
||||
"path": "/maps/map08",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map08",
|
||||
"path": "/maps/map08",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00002328-0000-4000-8000-000000002328",
|
||||
"path": "/maps/map09",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map09",
|
||||
"path": "/maps/map09",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00002710-0000-4000-8000-000000002710",
|
||||
"path": "/maps/map10",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map10",
|
||||
"path": "/maps/map10",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"id": "00002af8-0000-4000-8000-000000002af8",
|
||||
"path": "/maps/map11",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent",
|
||||
"componentNames": "MOD.Core.MapComponent,MOD.Core.FootholdComponent,script.MapCamera",
|
||||
"jsonString": {
|
||||
"name": "map11",
|
||||
"path": "/maps/map11",
|
||||
@@ -1103,6 +1103,10 @@
|
||||
]
|
||||
},
|
||||
"Enable": true
|
||||
},
|
||||
{
|
||||
"@type": "script.MapCamera",
|
||||
"Enable": true
|
||||
}
|
||||
],
|
||||
"@version": 1
|
||||
@@ -6653,4 +6657,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
98
tools/gen-camera.mjs
Normal file
98
tools/gen-camera.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
// 맵별 고정 카메라: 맵 로드 시 플레이어 CameraComponent를 data/camera.json 값으로 설정.
|
||||
// 새 CameraComponent를 만들지 않고(엔진 소유) 기존 카메라 속성만 런타임 설정한다.
|
||||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||||
const MAP_NUMBERS = Array.from({ length: 11 }, (_, i) => i + 1); // map01~11
|
||||
|
||||
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() {
|
||||
const cb = {
|
||||
Id: '',
|
||||
GameId: '',
|
||||
EntryKey: 'codeblock://mapcamera',
|
||||
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: 'MapCamera',
|
||||
Language: 1,
|
||||
Name: 'MapCamera',
|
||||
Type: 1,
|
||||
Source: 0,
|
||||
Target: null,
|
||||
Properties: [prop('number', 'CamTries', '0')],
|
||||
Methods: [
|
||||
method('OnBeginPlay', `self.CamTries = 0
|
||||
local eventId = 0
|
||||
local function apply()
|
||||
self.CamTries = self.CamTries + 1
|
||||
local cam = nil
|
||||
local lp = _UserService.LocalPlayer
|
||||
if lp ~= nil then
|
||||
cam = lp.CameraComponent
|
||||
end
|
||||
if cam == nil then
|
||||
cam = _CameraService:GetCurrentCameraComponent()
|
||||
end
|
||||
if cam ~= nil then
|
||||
cam.ZoomRatio = ${CAM.zoomRatio}
|
||||
cam.ScreenOffset = Vector2(${CAM.screenOffsetX}, ${CAM.screenOffsetY})
|
||||
cam.ConfineCameraArea = ${CAM.confineCameraArea}
|
||||
_TimerService:ClearTimer(eventId)
|
||||
elseif self.CamTries > 30 then
|
||||
_TimerService:ClearTimer(eventId)
|
||||
end
|
||||
end
|
||||
eventId = _TimerService:SetTimerRepeat(apply, 0.1)`),
|
||||
],
|
||||
EntityEventHandlers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
writeFileSync('RootDesk/MyDesk/MapCamera.codeblock', JSON.stringify(cb, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
function patchMap(nn) {
|
||||
const tag = String(nn).padStart(2, '0');
|
||||
const file = `map/map${tag}.map`;
|
||||
const map = JSON.parse(readFileSync(file, 'utf8'));
|
||||
const root = map.ContentProto.Entities.find((e) => e.path === `/maps/map${tag}`);
|
||||
if (!root) throw new Error(`[gen-camera] 맵 루트 없음: ${file}`);
|
||||
// idempotent: 기존 script.MapCamera 제거 후 재추가
|
||||
root.jsonString['@components'] = root.jsonString['@components'].filter((c) => c['@type'] !== 'script.MapCamera');
|
||||
root.jsonString['@components'].push({ '@type': 'script.MapCamera', Enable: true });
|
||||
const names = (root.componentNames || '').split(',').filter((s) => s && s !== 'script.MapCamera');
|
||||
names.push('script.MapCamera');
|
||||
root.componentNames = names.join(',');
|
||||
writeFileSync(file, JSON.stringify(map, null, 2), 'utf8');
|
||||
return `map${tag}`;
|
||||
}
|
||||
|
||||
writeCodeblock();
|
||||
const patched = MAP_NUMBERS.map(patchMap);
|
||||
console.log('MapCamera codeblock written; patched maps:', patched.join(', '));
|
||||
Reference in New Issue
Block a user