summer-engine 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { registerSceneTools } from "./tools/scene-tools.js";
|
|
|
6
6
|
import { registerDebugTools } from "./tools/debug-tools.js";
|
|
7
7
|
import { registerProjectTools } from "./tools/project-tools.js";
|
|
8
8
|
import { registerAssetTools } from "./tools/asset-tools.js";
|
|
9
|
+
import { registerGenerateTools } from "./tools/generate-tools.js";
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
const { version } = require("../../package.json");
|
|
11
12
|
let cachedClient = null;
|
|
@@ -34,6 +35,7 @@ export async function startMcpServer() {
|
|
|
34
35
|
registerDebugTools(server);
|
|
35
36
|
registerProjectTools(server);
|
|
36
37
|
registerAssetTools(server);
|
|
38
|
+
registerGenerateTools(server);
|
|
37
39
|
const transport = new StdioServerTransport();
|
|
38
40
|
await server.connect(transport);
|
|
39
41
|
process.stderr.write(`[summer-mcp] MCP server running v${version}.\n`);
|
|
@@ -56,6 +56,9 @@ async function searchAssetsApi(params) {
|
|
|
56
56
|
if (params.limit) {
|
|
57
57
|
searchParams.set("limit", String(params.limit));
|
|
58
58
|
}
|
|
59
|
+
if (params.source) {
|
|
60
|
+
searchParams.set("source", params.source);
|
|
61
|
+
}
|
|
59
62
|
const res = await fetch(`${GATEWAY_URL}/api/mcp/assets?${searchParams}`, {
|
|
60
63
|
headers: { Authorization: `Bearer ${token}` },
|
|
61
64
|
signal: AbortSignal.timeout(15000),
|
|
@@ -85,17 +88,23 @@ async function searchAssetsApi(params) {
|
|
|
85
88
|
return data;
|
|
86
89
|
}
|
|
87
90
|
export function registerAssetTools(server) {
|
|
88
|
-
server.tool("summer_search_assets", `Search
|
|
91
|
+
server.tool("summer_search_assets", `Search for game assets in the Summer Engine ecosystem.
|
|
92
|
+
|
|
93
|
+
Sources:
|
|
94
|
+
- "library" (default) — Public asset library (25k+ community assets). Requires Pro plan.
|
|
95
|
+
- "my_assets" — Your own generated/uploaded assets. Free for all users. Query is optional.
|
|
96
|
+
- "all" — Search both library and your assets.
|
|
89
97
|
|
|
90
98
|
Uses hybrid search: keywords + semantic similarity. Finds assets by name AND by meaning.
|
|
91
99
|
Returns asset names, types, preview URLs, and import-ready file URLs.
|
|
92
100
|
|
|
93
|
-
Requires authentication
|
|
94
|
-
query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon',
|
|
101
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
102
|
+
query: z.string().describe("Natural language search, e.g. 'low-poly tree', 'sci-fi weapon'. For my_assets, can be empty to list recent."),
|
|
95
103
|
assetType: z.enum(["2d_image", "animation", "3d_model", "audio", "music", "all"]).default("all").describe("Filter by asset type"),
|
|
96
104
|
limit: z.number().default(10).describe("Max results (1-20)"),
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
source: z.enum(["library", "my_assets", "all"]).default("library").describe("Where to search: library (public 25k+), my_assets (your generated assets), all"),
|
|
106
|
+
}, async ({ query, assetType, limit, source }) => {
|
|
107
|
+
const result = await searchAssetsApi({ query, assetType, limit: Math.min(limit, 20), source });
|
|
99
108
|
if (result.error) {
|
|
100
109
|
return {
|
|
101
110
|
content: [
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { getAuthToken } from "../../lib/auth.js";
|
|
6
|
+
const GATEWAY_URL = process.env.SUMMER_GATEWAY_URL || "https://www.summerengine.com";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Shared helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
async function mcpGenerate(endpoint, body, timeoutMs = 120_000) {
|
|
11
|
+
const token = await getAuthToken();
|
|
12
|
+
if (!token) {
|
|
13
|
+
return {
|
|
14
|
+
error: "Not signed in. Run in your terminal:\n npx summer-engine login\nOr open: https://www.summerengine.com/login",
|
|
15
|
+
status: 401,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(`${GATEWAY_URL}${endpoint}`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${token}`,
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify(body),
|
|
25
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
26
|
+
});
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
let message = data.message || data.error || "Request failed";
|
|
30
|
+
if (res.status === 402) {
|
|
31
|
+
message =
|
|
32
|
+
(data.message || "Insufficient credits.") +
|
|
33
|
+
"\nTop up at: https://www.summerengine.com/dashboard\nOr upgrade your plan at: https://www.summerengine.com/pricing";
|
|
34
|
+
}
|
|
35
|
+
if (res.status === 401) {
|
|
36
|
+
message =
|
|
37
|
+
(data.message || "Auth token expired.") +
|
|
38
|
+
"\nRe-authenticate:\n npx summer-engine login";
|
|
39
|
+
}
|
|
40
|
+
return { error: message, data, status: res.status };
|
|
41
|
+
}
|
|
42
|
+
return { data, status: res.status };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Download an image URL to a temp file so the AI can Read it and show the user.
|
|
46
|
+
*/
|
|
47
|
+
async function downloadToTemp(url, prefix, ext = "png") {
|
|
48
|
+
try {
|
|
49
|
+
const dir = join(tmpdir(), "summer-gen");
|
|
50
|
+
await mkdir(dir, { recursive: true });
|
|
51
|
+
const filename = `${prefix}-${Date.now()}.${ext}`;
|
|
52
|
+
const filepath = join(dir, filename);
|
|
53
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
return null;
|
|
56
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
57
|
+
await writeFile(filepath, buffer);
|
|
58
|
+
return filepath;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Poll a job until completed, failed, or timeout.
|
|
66
|
+
* Returns the final job status object.
|
|
67
|
+
*/
|
|
68
|
+
async function pollJob(jobId, maxWaitMs = 300_000, intervalMs = 5_000) {
|
|
69
|
+
const token = await getAuthToken();
|
|
70
|
+
if (!token)
|
|
71
|
+
return { error: "Not signed in" };
|
|
72
|
+
const deadline = Date.now() + maxWaitMs;
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`${GATEWAY_URL}/api/mcp/jobs/${encodeURIComponent(jobId)}`, {
|
|
76
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
77
|
+
signal: AbortSignal.timeout(15_000),
|
|
78
|
+
});
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
if (!res.ok)
|
|
81
|
+
return { error: data.message || "Poll failed", data };
|
|
82
|
+
if (data.status === "completed")
|
|
83
|
+
return { data };
|
|
84
|
+
if (data.status === "failed")
|
|
85
|
+
return { error: data.error || "Job failed", data };
|
|
86
|
+
// Still running — wait and retry
|
|
87
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
88
|
+
// Back off slightly after 30s
|
|
89
|
+
if (Date.now() - (deadline - maxWaitMs) > 30_000) {
|
|
90
|
+
intervalMs = Math.min(intervalMs * 1.2, 15_000);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
95
|
+
return { error: `Poll error: ${msg}` };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { error: `Timed out after ${maxWaitMs / 1000}s. Use summer_check_job({ jobId: "${jobId}" }) to check later.` };
|
|
99
|
+
}
|
|
100
|
+
function errorResult(message, extra) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify({ error: true, message, ...extra }, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function successResult(data) {
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify(data, null, 2),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Registration
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
export function registerGenerateTools(server) {
|
|
125
|
+
// ========================================================================
|
|
126
|
+
// summer_generate_image
|
|
127
|
+
// ========================================================================
|
|
128
|
+
server.tool("summer_generate_image", `Generate an image using AI models via Summer Engine Studio.
|
|
129
|
+
|
|
130
|
+
Known models:
|
|
131
|
+
- "nano-banana-2" (default) — High quality, supports txt2img and img2img
|
|
132
|
+
- "gemini-flash" — Google Gemini 2.5 Flash, fast
|
|
133
|
+
- "flux-2" — FLUX.2, good for specific styles
|
|
134
|
+
- Any fal-ai model ID works as a passthrough
|
|
135
|
+
|
|
136
|
+
Two modes:
|
|
137
|
+
- txt2img (default): just pass prompt
|
|
138
|
+
- img2img (edit): pass prompt + referenceImageUrl to edit/transform an existing image
|
|
139
|
+
|
|
140
|
+
Style presets: "realistic" (default), "cartoon", "anime", "none"
|
|
141
|
+
|
|
142
|
+
The 'options' object is passed directly to the AI provider for full control.
|
|
143
|
+
|
|
144
|
+
Returns the asset with fileUrl (hosted) and localPath (temp file on disk).
|
|
145
|
+
Use the Read tool on localPath to show the image to the user for approval.
|
|
146
|
+
|
|
147
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
148
|
+
prompt: z.string().describe("Description of the image to generate"),
|
|
149
|
+
model: z
|
|
150
|
+
.string()
|
|
151
|
+
.default("nano-banana-2")
|
|
152
|
+
.describe("Model name or full provider ID"),
|
|
153
|
+
style: z
|
|
154
|
+
.string()
|
|
155
|
+
.default("realistic")
|
|
156
|
+
.describe("Style preset: realistic, cartoon, anime, or 'none' to skip"),
|
|
157
|
+
referenceImageUrl: z
|
|
158
|
+
.string()
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("Source image URL for img2img / edit mode. The prompt describes how to transform this image."),
|
|
161
|
+
options: z
|
|
162
|
+
.record(z.any())
|
|
163
|
+
.optional()
|
|
164
|
+
.describe("Provider-specific params (guidance_scale, seed, image_size, negative_prompt, etc.)"),
|
|
165
|
+
}, async ({ prompt, model, style, referenceImageUrl, options }) => {
|
|
166
|
+
const result = await mcpGenerate("/api/mcp/generate/image", {
|
|
167
|
+
prompt,
|
|
168
|
+
model,
|
|
169
|
+
style,
|
|
170
|
+
referenceImageUrl,
|
|
171
|
+
options,
|
|
172
|
+
});
|
|
173
|
+
if (result.error) {
|
|
174
|
+
return errorResult(result.error, result.data);
|
|
175
|
+
}
|
|
176
|
+
// Download to temp so the AI can Read and show the image
|
|
177
|
+
const imageUrl = result.data?.asset?.fileUrl || result.data?.asset?.thumbnailUrl;
|
|
178
|
+
let localPath = null;
|
|
179
|
+
if (imageUrl) {
|
|
180
|
+
localPath = await downloadToTemp(imageUrl, "img");
|
|
181
|
+
}
|
|
182
|
+
return successResult({
|
|
183
|
+
...result.data,
|
|
184
|
+
localPath,
|
|
185
|
+
hint: localPath
|
|
186
|
+
? `Image saved to ${localPath}. Use the Read tool on this path to show it to the user.`
|
|
187
|
+
: undefined,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// ========================================================================
|
|
191
|
+
// summer_generate_audio
|
|
192
|
+
// ========================================================================
|
|
193
|
+
server.tool("summer_generate_audio", `Generate audio using AI providers via Summer Engine Studio.
|
|
194
|
+
|
|
195
|
+
Capabilities:
|
|
196
|
+
- "text_to_speech" — Convert text to spoken audio. Requires 'text'. Optional 'voiceId'.
|
|
197
|
+
- "sound_effects" — Generate sound effects from description. Requires 'text'. Optional 'durationSeconds'.
|
|
198
|
+
- "music" — Generate background music. Requires 'prompt'. Optional 'durationSeconds'.
|
|
199
|
+
- "text_to_dialogue" — Multi-voice dialogue. Requires 'inputs' array of {text, voiceId} objects.
|
|
200
|
+
|
|
201
|
+
The 'options' object is passed directly to ElevenLabs, so you can set any
|
|
202
|
+
provider-specific parameter: stability, similarity_boost, style, speed, etc.
|
|
203
|
+
|
|
204
|
+
Returns the generated audio URL and asset metadata.
|
|
205
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
206
|
+
capability: z
|
|
207
|
+
.string()
|
|
208
|
+
.describe("Type of audio: text_to_speech, sound_effects, music, text_to_dialogue"),
|
|
209
|
+
text: z
|
|
210
|
+
.string()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe("Text for TTS, or description for sound effects"),
|
|
213
|
+
prompt: z
|
|
214
|
+
.string()
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Music description (for 'music' capability)"),
|
|
217
|
+
voiceId: z
|
|
218
|
+
.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("ElevenLabs voice ID for TTS"),
|
|
221
|
+
modelId: z
|
|
222
|
+
.string()
|
|
223
|
+
.optional()
|
|
224
|
+
.describe("ElevenLabs model ID (e.g. 'eleven_multilingual_v2')"),
|
|
225
|
+
durationSeconds: z
|
|
226
|
+
.number()
|
|
227
|
+
.optional()
|
|
228
|
+
.describe("Duration in seconds for SFX or music"),
|
|
229
|
+
inputs: z
|
|
230
|
+
.array(z.object({ text: z.string(), voiceId: z.string() }))
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("Dialogue lines for text_to_dialogue"),
|
|
233
|
+
options: z
|
|
234
|
+
.record(z.any())
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Provider-specific params (stability, similarity_boost, style, speed, etc.)"),
|
|
237
|
+
}, async ({ capability, text, prompt, voiceId, modelId, durationSeconds, inputs, options }) => {
|
|
238
|
+
const body = { capability };
|
|
239
|
+
if (text)
|
|
240
|
+
body.text = text;
|
|
241
|
+
if (prompt)
|
|
242
|
+
body.prompt = prompt;
|
|
243
|
+
if (voiceId)
|
|
244
|
+
body.voiceId = voiceId;
|
|
245
|
+
if (modelId)
|
|
246
|
+
body.modelId = modelId;
|
|
247
|
+
if (durationSeconds)
|
|
248
|
+
body.durationSeconds = durationSeconds;
|
|
249
|
+
if (inputs)
|
|
250
|
+
body.inputs = inputs;
|
|
251
|
+
if (options)
|
|
252
|
+
body.options = options;
|
|
253
|
+
const result = await mcpGenerate("/api/mcp/generate/audio", body);
|
|
254
|
+
if (result.error) {
|
|
255
|
+
return errorResult(result.error, result.data);
|
|
256
|
+
}
|
|
257
|
+
return successResult(result.data);
|
|
258
|
+
});
|
|
259
|
+
// ========================================================================
|
|
260
|
+
// summer_generate_3d
|
|
261
|
+
// ========================================================================
|
|
262
|
+
server.tool("summer_generate_3d", `Generate a 3D model via Summer Engine Studio.
|
|
263
|
+
|
|
264
|
+
Available models:
|
|
265
|
+
- "hunyuan" (default) — Hunyuan 3D v3.1 Pro, high quality
|
|
266
|
+
- "trellis" — Trellis 2, fast and detailed
|
|
267
|
+
- "meshy" — Meshy, legacy option
|
|
268
|
+
- Any fal-ai model ID works as a passthrough
|
|
269
|
+
|
|
270
|
+
Available kinds:
|
|
271
|
+
- "text-to-3d" (default) — From text description. Requires 'prompt'.
|
|
272
|
+
Internally generates a 3D-optimized reference image first, then converts to 3D.
|
|
273
|
+
- "image-to-3d" — From your own image. Requires 'imageUrl'.
|
|
274
|
+
- "texture" — Generate textures for a model. Requires 'imageUrl'.
|
|
275
|
+
|
|
276
|
+
By default, waits for completion (up to 5 min) and returns the result directly.
|
|
277
|
+
Set wait=false to get the jobId immediately and poll manually with summer_check_job.
|
|
278
|
+
|
|
279
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
280
|
+
prompt: z
|
|
281
|
+
.string()
|
|
282
|
+
.optional()
|
|
283
|
+
.describe("Description of the 3D model, e.g. 'a low-poly treasure chest'"),
|
|
284
|
+
kind: z
|
|
285
|
+
.string()
|
|
286
|
+
.default("text-to-3d")
|
|
287
|
+
.describe("Generation type: text-to-3d, image-to-3d, texture"),
|
|
288
|
+
model: z
|
|
289
|
+
.string()
|
|
290
|
+
.default("hunyuan")
|
|
291
|
+
.describe("Model: hunyuan (default), trellis, meshy, or any fal-ai model ID"),
|
|
292
|
+
imageUrl: z
|
|
293
|
+
.string()
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Source image URL for image-to-3d or texture"),
|
|
296
|
+
wait: z
|
|
297
|
+
.boolean()
|
|
298
|
+
.default(true)
|
|
299
|
+
.describe("Wait for completion (default true, up to 5 min). Set false to get jobId immediately."),
|
|
300
|
+
options: z
|
|
301
|
+
.record(z.any())
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Provider-specific params (target_polycount, topology, art_style, etc.)"),
|
|
304
|
+
}, async ({ prompt, kind, model, imageUrl, wait, options }) => {
|
|
305
|
+
const result = await mcpGenerate("/api/mcp/generate/3d", {
|
|
306
|
+
prompt,
|
|
307
|
+
kind,
|
|
308
|
+
model,
|
|
309
|
+
imageUrl,
|
|
310
|
+
options,
|
|
311
|
+
});
|
|
312
|
+
if (result.error) {
|
|
313
|
+
return errorResult(result.error, result.data);
|
|
314
|
+
}
|
|
315
|
+
const jobId = result.data?.jobId;
|
|
316
|
+
// If not waiting or no jobId, return immediately
|
|
317
|
+
if (!wait || !jobId) {
|
|
318
|
+
return successResult(result.data);
|
|
319
|
+
}
|
|
320
|
+
// Auto-poll until done
|
|
321
|
+
const pollResult = await pollJob(jobId);
|
|
322
|
+
if (pollResult.error) {
|
|
323
|
+
return errorResult(pollResult.error, { ...pollResult.data, jobId });
|
|
324
|
+
}
|
|
325
|
+
return successResult({
|
|
326
|
+
...pollResult.data,
|
|
327
|
+
jobId,
|
|
328
|
+
message: "3D generation complete.",
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
// ========================================================================
|
|
332
|
+
// summer_generate_video
|
|
333
|
+
// ========================================================================
|
|
334
|
+
server.tool("summer_generate_video", `Generate a video using AI models via Summer Engine Studio.
|
|
335
|
+
|
|
336
|
+
Known models (text-to-video):
|
|
337
|
+
- "ltx" (default) — LTX Video, fast (~$0.10)
|
|
338
|
+
- "kling" — Kling 2 Master, high quality (~$0.50)
|
|
339
|
+
- "kling-turbo" — Kling 2.5 Turbo Pro (~$0.30)
|
|
340
|
+
- "minimax" — MiniMax (~$0.25)
|
|
341
|
+
- "veo3" — Google Veo 3, premium (~$1.00)
|
|
342
|
+
|
|
343
|
+
Known models (image-to-video, when imageUrl provided):
|
|
344
|
+
- "ltx" (default), "kling", "minimax"
|
|
345
|
+
|
|
346
|
+
If imageUrl is provided, switches to image-to-video mode.
|
|
347
|
+
Pass provider-specific params in 'options' (negative_prompt, num_frames, etc.).
|
|
348
|
+
|
|
349
|
+
Returns the generated video URL and asset metadata.
|
|
350
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
351
|
+
prompt: z
|
|
352
|
+
.string()
|
|
353
|
+
.describe("Description of the video content"),
|
|
354
|
+
model: z
|
|
355
|
+
.string()
|
|
356
|
+
.default("ltx")
|
|
357
|
+
.describe("Model name: ltx, kling, kling-turbo, minimax, veo3 (or any new model)"),
|
|
358
|
+
imageUrl: z
|
|
359
|
+
.string()
|
|
360
|
+
.optional()
|
|
361
|
+
.describe("Source image URL — if provided, uses image-to-video mode"),
|
|
362
|
+
duration: z
|
|
363
|
+
.number()
|
|
364
|
+
.default(5)
|
|
365
|
+
.describe("Duration in seconds (5 or 10)"),
|
|
366
|
+
aspectRatio: z
|
|
367
|
+
.string()
|
|
368
|
+
.default("16:9")
|
|
369
|
+
.describe("Aspect ratio: 16:9, 9:16, 1:1, 4:3"),
|
|
370
|
+
options: z
|
|
371
|
+
.record(z.any())
|
|
372
|
+
.optional()
|
|
373
|
+
.describe("Provider-specific params (negative_prompt, num_frames, guidance_scale, etc.)"),
|
|
374
|
+
}, async ({ prompt, model, imageUrl, duration, aspectRatio, options }) => {
|
|
375
|
+
const result = await mcpGenerate("/api/mcp/generate/video", {
|
|
376
|
+
prompt,
|
|
377
|
+
model,
|
|
378
|
+
imageUrl,
|
|
379
|
+
duration,
|
|
380
|
+
aspectRatio,
|
|
381
|
+
options,
|
|
382
|
+
});
|
|
383
|
+
if (result.error) {
|
|
384
|
+
return errorResult(result.error, result.data);
|
|
385
|
+
}
|
|
386
|
+
return successResult(result.data);
|
|
387
|
+
});
|
|
388
|
+
// ========================================================================
|
|
389
|
+
// summer_check_job
|
|
390
|
+
// ========================================================================
|
|
391
|
+
server.tool("summer_check_job", `Check the status of an async generation job.
|
|
392
|
+
|
|
393
|
+
Use this when you called summer_generate_3d with wait=false, or need to re-check
|
|
394
|
+
a job that timed out. Most of the time you won't need this — summer_generate_3d
|
|
395
|
+
waits automatically.
|
|
396
|
+
|
|
397
|
+
Status values: waiting, active, completed, failed, delayed, unknown.
|
|
398
|
+
|
|
399
|
+
Requires authentication: run 'npx summer-engine login' first.`, {
|
|
400
|
+
jobId: z.string().describe("The job ID returned by an async generation tool"),
|
|
401
|
+
}, async ({ jobId }) => {
|
|
402
|
+
const token = await getAuthToken();
|
|
403
|
+
if (!token) {
|
|
404
|
+
return errorResult("Not signed in. Run: npx summer-engine login");
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
const res = await fetch(`${GATEWAY_URL}/api/mcp/jobs/${encodeURIComponent(jobId)}`, {
|
|
408
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
409
|
+
signal: AbortSignal.timeout(15_000),
|
|
410
|
+
});
|
|
411
|
+
const data = await res.json();
|
|
412
|
+
if (!res.ok) {
|
|
413
|
+
return errorResult(data.message || data.error || "Failed to check job status", data);
|
|
414
|
+
}
|
|
415
|
+
return successResult(data);
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
419
|
+
return errorResult(`Job status check failed: ${msg}`);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: asset-strategy
|
|
3
|
+
description: Decision guide for how to create different game asset types. Use when deciding whether to generate 3D models, textures, or use primitives. Includes prompt templates for AI image-to-3D pipeline.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility:
|
|
6
|
+
- Cursor
|
|
7
|
+
- Claude Code
|
|
8
|
+
- Windsurf
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Asset Strategy for Summer Engine
|
|
12
|
+
|
|
13
|
+
How to create different types of game assets. Pick the right pipeline for each asset type.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Decision Tree
|
|
18
|
+
|
|
19
|
+
| Asset type | Pipeline | Why |
|
|
20
|
+
|-----------|----------|-----|
|
|
21
|
+
| Props (sword, barrel, chair, crate) | Image-to-3D | Best quality for isolated objects |
|
|
22
|
+
| Characters (player, NPC, enemy) | Image-to-3D (mannequin ref) | T-pose reference preserves proportions |
|
|
23
|
+
| Organic (trees, rocks, mushrooms) | Image-to-3D | Handles complex shapes well |
|
|
24
|
+
| Vehicles (car, spaceship, boat) | Image-to-3D | Good for hard-surface objects |
|
|
25
|
+
| Walls / floors / ceilings | Texture + BoxMesh/CSGBox | Tiled textures on simple geometry |
|
|
26
|
+
| Terrain / ground | Texture + PlaneMesh | Tileable ground textures |
|
|
27
|
+
| Buildings / structures | Texture + CSG or modular kit | Combine primitive shapes with textures |
|
|
28
|
+
| UI elements / icons / HUD | 2D image only | No 3D conversion needed |
|
|
29
|
+
| Sprites (2D games) | 2D image only | Direct use as Sprite2D texture |
|
|
30
|
+
| Skyboxes / backgrounds | Panoramic image gen | Environment, not mesh |
|
|
31
|
+
| Particles / VFX | Built in engine | GPUParticles3D, no generation |
|
|
32
|
+
| Audio / music / SFX | summer_generate_audio | Separate pipeline |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Pipeline 1: Image-to-3D (Props, Characters, Organic)
|
|
37
|
+
|
|
38
|
+
### How it works
|
|
39
|
+
|
|
40
|
+
1. A reference template image (mannequin on white bg) is stored server-side
|
|
41
|
+
2. nano-banana-2 img2img replaces the mannequin with the desired asset
|
|
42
|
+
3. The result is a clean, 3D-ready reference image
|
|
43
|
+
4. That image feeds into Hunyuan 3D (default) or Trellis 2 for 3D conversion
|
|
44
|
+
5. You get a 3D model back
|
|
45
|
+
|
|
46
|
+
Available 3D models:
|
|
47
|
+
- **hunyuan** (default) — Hunyuan 3D v3.1 Pro, highest quality
|
|
48
|
+
- **trellis** — Trellis 2, fast and detailed
|
|
49
|
+
- **meshy** — Legacy option
|
|
50
|
+
|
|
51
|
+
From the CLI, the reference image step is invisible. Just call:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
summer_generate_3d(prompt="a treasure chest with gold coins")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The server generates the reference image and feeds it to 3D automatically.
|
|
58
|
+
|
|
59
|
+
To pick a specific model:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
summer_generate_3d(prompt="a treasure chest", model="trellis")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For manual control (you want to see/approve the reference image first):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
summer_generate_image(prompt="a treasure chest with gold coins, game asset, white background, stylized, not ultra realistic")
|
|
69
|
+
# Review the image with Read tool
|
|
70
|
+
summer_generate_3d(kind="image-to-3d", imageUrl="<fileUrl from above>")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Prompt template for 3D reference images
|
|
74
|
+
|
|
75
|
+
When generating images intended for 3D conversion, use this suffix:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
[your asset description]. Game asset, centered in frame, 3D model render style,
|
|
79
|
+
stylized game art, not ultra realistic, clean white background, soft studio
|
|
80
|
+
lighting. Isolated object ready for 3D mesh generation.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
|
|
85
|
+
**Weapon:**
|
|
86
|
+
```
|
|
87
|
+
A medieval broadsword with ornate crossguard and leather-wrapped grip. Game asset,
|
|
88
|
+
centered in frame, 3D model render style, stylized game art, not ultra realistic,
|
|
89
|
+
clean white background, soft studio lighting. Isolated object ready for 3D mesh
|
|
90
|
+
generation.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Character:**
|
|
94
|
+
```
|
|
95
|
+
A fantasy knight in full plate armor, red cape, detailed pauldrons. T-pose,
|
|
96
|
+
front-facing. Game character asset, 3D model render style, stylized game art,
|
|
97
|
+
not ultra realistic, clean white background, soft studio lighting. Ready for
|
|
98
|
+
3D mesh generation.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Organic prop:**
|
|
102
|
+
```
|
|
103
|
+
Cluster of bioluminescent mushrooms growing from a small mossy rock base. Pale
|
|
104
|
+
green glow, translucent caps, alien organic shapes. Game asset, centered in frame,
|
|
105
|
+
3D model render style, stylized game art, not ultra realistic, clean white
|
|
106
|
+
background, soft studio lighting. Isolated object ready for 3D mesh generation.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Vehicle:**
|
|
110
|
+
```
|
|
111
|
+
A rusted sci-fi hover bike with exposed engine parts and worn paint. Game asset,
|
|
112
|
+
centered in frame, 3D model render style, stylized game art, not ultra realistic,
|
|
113
|
+
clean white background, soft studio lighting. Isolated vehicle ready for 3D mesh
|
|
114
|
+
generation.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### What makes a BAD 3D reference
|
|
118
|
+
|
|
119
|
+
Avoid these in prompts:
|
|
120
|
+
- Scene context ("sword lying on a table") -- the table becomes part of the mesh
|
|
121
|
+
- Strong directional shadows -- baked into the mesh as geometry
|
|
122
|
+
- Ultra-realistic rendering -- too much detail for game-ready meshes
|
|
123
|
+
- Flat 2D angles (pure front/side view) -- 3D gen needs depth cues
|
|
124
|
+
- Multiple objects ("a sword and a shield") -- generates as one fused mesh
|
|
125
|
+
- Busy backgrounds -- bleeds into the model
|
|
126
|
+
|
|
127
|
+
### Polycount guidance
|
|
128
|
+
|
|
129
|
+
Pass in options when quality matters:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
summer_generate_3d(prompt="...", options={ target_polycount: 5000 })
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Rough targets:
|
|
136
|
+
- Mobile games: 1k-3k tris
|
|
137
|
+
- Indie/stylized PC: 3k-10k tris
|
|
138
|
+
- Detailed hero assets: 10k-30k tris
|
|
139
|
+
- Cinematics/showcase: 30k-100k tris
|
|
140
|
+
|
|
141
|
+
Default if not specified: provider decides (usually 10k-30k range).
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Pipeline 2: Textures + Geometry (Walls, Floors, Structures)
|
|
146
|
+
|
|
147
|
+
For flat surfaces and repeating structures, don't generate 3D models. Generate textures and apply them to simple geometry.
|
|
148
|
+
|
|
149
|
+
### Walls
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
summer_generate_image(prompt="seamless brick wall texture, tileable, game texture, diffuse map")
|
|
153
|
+
summer_import_from_url(url="<fileUrl>", path="res://assets/textures/brick_wall.png")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then in the scene:
|
|
157
|
+
```
|
|
158
|
+
summer_add_node(parent="./Level", type="CSGBox3D", name="Wall")
|
|
159
|
+
summer_set_prop(path="./Level/Wall", key="size", value="Vector3(10, 3, 0.3)")
|
|
160
|
+
summer_set_prop(path="./Level/Wall", key="position", value="Vector3(0, 1.5, -5)")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Attach material with texture via script or manual setup.
|
|
164
|
+
|
|
165
|
+
### Floors / ground
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
summer_generate_image(prompt="seamless grass ground texture, top-down, tileable, game texture")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Apply to PlaneMesh or CSGBox3D scaled flat.
|
|
172
|
+
|
|
173
|
+
### Modular building kit
|
|
174
|
+
|
|
175
|
+
For buildings, combine CSG shapes:
|
|
176
|
+
- CSGBox3D for walls
|
|
177
|
+
- CSGBox3D for floors/ceilings
|
|
178
|
+
- CSGCylinder3D for pillars
|
|
179
|
+
- CSGPolygon3D for custom shapes
|
|
180
|
+
|
|
181
|
+
Generate different textures for each material:
|
|
182
|
+
- Wall texture (brick, stone, wood)
|
|
183
|
+
- Floor texture (tile, wood planks, concrete)
|
|
184
|
+
- Roof texture (shingles, metal)
|
|
185
|
+
|
|
186
|
+
### Texture prompt tips
|
|
187
|
+
|
|
188
|
+
For tileable textures, always include:
|
|
189
|
+
```
|
|
190
|
+
[material description], seamless, tileable, game texture, diffuse map,
|
|
191
|
+
top-down view, uniform lighting, no perspective
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Pipeline 3: 2D Only (UI, Sprites, Icons)
|
|
197
|
+
|
|
198
|
+
For 2D game assets and UI elements:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
summer_generate_image(prompt="pixel art heart icon, 64x64, red, game UI element",
|
|
202
|
+
style="pixel")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
For sprite sheets, consider the sprite animation tools (separate from this pipeline).
|
|
206
|
+
|
|
207
|
+
### UI prompt tips
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
[element description], flat design, clean edges, game UI style,
|
|
211
|
+
transparent background preferred, [size]px
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Pipeline 4: Audio
|
|
217
|
+
|
|
218
|
+
Separate from visual assets. See summer_generate_audio tool:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
summer_generate_audio(capability="sound_effects", text="metal sword clash impact")
|
|
222
|
+
summer_generate_audio(capability="music", prompt="ambient fantasy forest theme", durationSeconds=60)
|
|
223
|
+
summer_generate_audio(capability="text_to_speech", text="You shall not pass!", voiceId="<id>")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Edge Cases (add to this list as they come up)
|
|
229
|
+
|
|
230
|
+
- **Transparent/glass objects**: Add "translucent material, see-through" to prompt. 3D gen may struggle -- consider using engine materials instead.
|
|
231
|
+
- **Animated objects**: Generate static mesh first, animate in engine with AnimationPlayer.
|
|
232
|
+
- **Multi-part objects**: Generate each part separately, assemble in scene tree.
|
|
233
|
+
- **Extremely thin objects** (paper, cloth, flags): 3D gen produces thick meshes. Better to use MeshInstance3D with a plane + texture + shader.
|
|
234
|
+
- **Emissive/glowing objects**: Generate the mesh, add emissive material in engine. Don't rely on the glow being in the mesh.
|
|
235
|
+
- **LOD (Level of Detail)**: Generate one version. If perf needs LOD, use Godot's LOD system or generate a second low-poly version.
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-game
|
|
3
|
+
description: End-to-end game creation workflow. Use when the user wants to build a complete game, start a new project, or says "make me a game". Orchestrates project setup, asset creation, scene building, scripting, and testing.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility:
|
|
6
|
+
- Cursor
|
|
7
|
+
- Claude Code
|
|
8
|
+
- Windsurf
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Make a Game with Summer Engine
|
|
12
|
+
|
|
13
|
+
Step-by-step workflow for building a complete game. Follow phases 0-6 in order. Each phase must complete before moving to the next.
|
|
14
|
+
|
|
15
|
+
**Rule**: Scene operations (.tscn) use MCP tools. Script files (.gd) use host file-edit tools (Write/Edit). Never cross the streams.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Phase 0: Gather Requirements
|
|
20
|
+
|
|
21
|
+
Before touching any tools, ask the user:
|
|
22
|
+
1. **What kind of game?** (platformer, FPS, top-down, puzzle, racing, etc.)
|
|
23
|
+
2. **2D or 3D?**
|
|
24
|
+
3. **Art style?** (realistic, low-poly, cartoon, pixel art)
|
|
25
|
+
4. **Core mechanic?** One sentence. ("player jumps between platforms collecting coins")
|
|
26
|
+
5. **Scope?** (1 level prototype, or multi-level)
|
|
27
|
+
|
|
28
|
+
Use this decision matrix:
|
|
29
|
+
|
|
30
|
+
| Genre | Root Type | Camera | Player Body | Dimension |
|
|
31
|
+
|-------|-----------|--------|-------------|-----------|
|
|
32
|
+
| FPS | Node3D | Camera3D on player | CharacterBody3D | 3D |
|
|
33
|
+
| Platformer 2D | Node2D | Camera2D following | CharacterBody2D | 2D |
|
|
34
|
+
| Platformer 3D | Node3D | Camera3D following | CharacterBody3D | 3D |
|
|
35
|
+
| Top-down | Node2D | Camera2D | CharacterBody2D | 2D |
|
|
36
|
+
| Puzzle | Node2D or Control | Camera2D or none | varies | 2D |
|
|
37
|
+
| Racing | Node3D | Camera3D behind car | VehicleBody3D | 3D |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Phase 1: Project Bootstrap
|
|
42
|
+
|
|
43
|
+
### 1a. Get context
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
summer_get_agent_playbook()
|
|
47
|
+
summer_get_project_context()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If a scene is open: `summer_get_scene_tree()` to see what exists.
|
|
51
|
+
|
|
52
|
+
### 1b. Configure project
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
summer_project_setting(key="application/config/name", value="<game name>")
|
|
56
|
+
summer_project_setting(key="display/window/size/viewport_width", value=1280)
|
|
57
|
+
summer_project_setting(key="display/window/size/viewport_height", value=720)
|
|
58
|
+
summer_project_setting(key="application/run/main_scene", value="res://scenes/main_level.tscn")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For 2D pixel art, add:
|
|
62
|
+
```
|
|
63
|
+
summer_project_setting(key="rendering/textures/canvas_textures/default_texture_filter", value=0)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 1c. Create scenes
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
summer_create_scene(path="res://scenes/main_level.tscn", rootName="World", allow_temporary_scene_mutation=true)
|
|
70
|
+
summer_create_scene(path="res://scenes/player.tscn", rootName="Player", allow_temporary_scene_mutation=true)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 1d. Folder conventions
|
|
74
|
+
|
|
75
|
+
- `res://scenes/` -- .tscn files
|
|
76
|
+
- `res://scripts/` -- .gd files
|
|
77
|
+
- `res://assets/models/` -- 3D models
|
|
78
|
+
- `res://assets/textures/` -- images, sprites
|
|
79
|
+
- `res://assets/audio/` -- sounds, music
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Phase 2: Asset Pipeline
|
|
84
|
+
|
|
85
|
+
Pick the right strategy per asset. Prefer this order: Library > Generate > Primitives.
|
|
86
|
+
|
|
87
|
+
### Strategy A: Asset Library (fastest, 25k+ free models)
|
|
88
|
+
|
|
89
|
+
For environment props, furniture, nature, vehicles:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
summer_search_assets(query="low-poly tree", assetType="3d_model", limit=5)
|
|
93
|
+
summer_import_asset(query="wooden barrel", parent="./World/Props", assetType="3d_model")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Strategy B: AI Generation (custom assets)
|
|
97
|
+
|
|
98
|
+
**Images** (textures, sprites, UI art) -- sync, returns immediately:
|
|
99
|
+
```
|
|
100
|
+
summer_generate_image(prompt="top-down grass tileset, seamless, 512x512", style="cartoon")
|
|
101
|
+
```
|
|
102
|
+
Returns `localPath` -- use Read tool to show the user for approval.
|
|
103
|
+
Then import:
|
|
104
|
+
```
|
|
105
|
+
summer_import_from_url(url="<asset.fileUrl>", path="res://assets/textures/grass.png")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**3D models** -- async, waits up to 5 min by default:
|
|
109
|
+
```
|
|
110
|
+
summer_generate_3d(prompt="low-poly treasure chest with gold coins", kind="text-to-3d")
|
|
111
|
+
```
|
|
112
|
+
Returns completed result directly. Then import the model URL.
|
|
113
|
+
|
|
114
|
+
**Image-to-3D** -- generate concept art first, then convert:
|
|
115
|
+
```
|
|
116
|
+
summer_generate_image(prompt="medieval sword, ornate handle, game asset, white background")
|
|
117
|
+
# Show to user, get approval
|
|
118
|
+
summer_generate_3d(kind="image-to-3d", imageUrl="<asset.fileUrl>")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Audio** -- sync:
|
|
122
|
+
```
|
|
123
|
+
summer_generate_audio(capability="sound_effects", text="sword swing whoosh")
|
|
124
|
+
summer_generate_audio(capability="music", prompt="upbeat adventure theme", durationSeconds=30)
|
|
125
|
+
summer_generate_audio(capability="text_to_speech", text="Welcome, adventurer!", voiceId="<id>")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Parallel generation**: Start multiple 3D jobs with `wait=false`, work on scene setup, then check later:
|
|
129
|
+
```
|
|
130
|
+
summer_generate_3d(prompt="...", wait=false) -> jobId: "abc"
|
|
131
|
+
summer_generate_3d(prompt="...", wait=false) -> jobId: "def"
|
|
132
|
+
# ... do scene work ...
|
|
133
|
+
summer_check_job(jobId="abc")
|
|
134
|
+
summer_check_job(jobId="def")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Strategy C: Primitive Meshes (instant, offline)
|
|
138
|
+
|
|
139
|
+
For prototyping or blocking out levels:
|
|
140
|
+
```
|
|
141
|
+
summer_add_node(parent="./World", type="MeshInstance3D", name="Floor")
|
|
142
|
+
summer_set_prop(path="./World/Floor", key="mesh", value="BoxMesh")
|
|
143
|
+
summer_set_resource_property(nodePath="./World/Floor", resourceProperty="mesh", subProperty="size", value="Vector3(20, 0.2, 20)")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Phase 3: Scene Construction
|
|
149
|
+
|
|
150
|
+
### 3a. Build environment with batch
|
|
151
|
+
|
|
152
|
+
Use `summer_batch` for multi-step setup in one undo step:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
summer_batch(ops=[
|
|
156
|
+
{"op":"AddNode","parent":"./","type":"Node3D","name":"Level"},
|
|
157
|
+
{"op":"AddNode","parent":"./Level","type":"MeshInstance3D","name":"Ground"},
|
|
158
|
+
{"op":"SetProp","path":"./Level/Ground","key":"mesh","value":"PlaneMesh"},
|
|
159
|
+
{"op":"SetResourceProperty","nodePath":"./Level/Ground","resourceProperty":"mesh","subProperty":"size","value":"Vector2(50,50)"},
|
|
160
|
+
{"op":"AddNode","parent":"./","type":"DirectionalLight3D","name":"Sun"},
|
|
161
|
+
{"op":"SetProp","path":"./Sun","key":"rotation_degrees","value":"Vector3(-45,30,0)"},
|
|
162
|
+
{"op":"SetProp","path":"./Sun","key":"shadow_enabled","value":"true"},
|
|
163
|
+
{"op":"AddNode","parent":"./","type":"WorldEnvironment","name":"Env"}
|
|
164
|
+
])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3b. Build player
|
|
168
|
+
|
|
169
|
+
**3D FPS/Third-person:**
|
|
170
|
+
```
|
|
171
|
+
summer_open_scene(path="res://scenes/player.tscn")
|
|
172
|
+
summer_batch(ops=[
|
|
173
|
+
{"op":"AddNode","parent":"./","type":"CollisionShape3D","name":"Collision"},
|
|
174
|
+
{"op":"SetProp","path":"./Collision","key":"shape","value":"CapsuleShape3D"},
|
|
175
|
+
{"op":"AddNode","parent":"./","type":"Camera3D","name":"Camera"},
|
|
176
|
+
{"op":"SetProp","path":"./Camera","key":"position","value":"Vector3(0,1.6,0)"}
|
|
177
|
+
])
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**2D Platformer:**
|
|
181
|
+
```
|
|
182
|
+
summer_open_scene(path="res://scenes/player.tscn")
|
|
183
|
+
summer_batch(ops=[
|
|
184
|
+
{"op":"AddNode","parent":"./","type":"CollisionShape2D","name":"Collision"},
|
|
185
|
+
{"op":"SetProp","path":"./Collision","key":"shape","value":"RectangleShape2D"},
|
|
186
|
+
{"op":"AddNode","parent":"./","type":"Sprite2D","name":"Sprite"},
|
|
187
|
+
{"op":"AddNode","parent":"./","type":"Camera2D","name":"Camera"}
|
|
188
|
+
])
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 3c. Input bindings
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
summer_input_map_bind(name="move_forward", events=[{"type":"key","key":"W"}])
|
|
195
|
+
summer_input_map_bind(name="move_back", events=[{"type":"key","key":"S"}])
|
|
196
|
+
summer_input_map_bind(name="move_left", events=[{"type":"key","key":"A"}])
|
|
197
|
+
summer_input_map_bind(name="move_right", events=[{"type":"key","key":"D"}])
|
|
198
|
+
summer_input_map_bind(name="jump", events=[{"type":"key","key":"Space"}])
|
|
199
|
+
summer_input_map_bind(name="interact", events=[{"type":"key","key":"E"}])
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 3d. Place assets in scene
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
summer_open_scene(path="res://scenes/main_level.tscn")
|
|
206
|
+
summer_instantiate_scene(parent="./Level", scene="res://scenes/player.tscn", name="Player")
|
|
207
|
+
summer_instantiate_scene(parent="./Level", scene="res://assets/models/tree.glb", name="Tree1")
|
|
208
|
+
summer_set_prop(path="./Level/Tree1", key="position", value="Vector3(5, 0, 3)")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 3e. Save
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
summer_save_scene()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Phase 4: Scripting
|
|
220
|
+
|
|
221
|
+
**Write .gd files with host file-edit tools (Write/Edit). Attach via MCP.**
|
|
222
|
+
|
|
223
|
+
Workflow:
|
|
224
|
+
1. Write the script file to disk
|
|
225
|
+
2. Attach: `summer_set_prop(path="./Player", key="script", value="res://scripts/player.gd")`
|
|
226
|
+
3. Check: `summer_get_script_errors(path="res://scripts/player.gd")`
|
|
227
|
+
4. Wire signals: `summer_connect_signal(emitter="./Coin", signal="body_entered", receiver="./Coin", method="_on_body_entered")`
|
|
228
|
+
|
|
229
|
+
### 3D Player Controller (FPS)
|
|
230
|
+
|
|
231
|
+
Write to `res://scripts/player.gd`:
|
|
232
|
+
|
|
233
|
+
```gdscript
|
|
234
|
+
extends CharacterBody3D
|
|
235
|
+
|
|
236
|
+
@export var speed: float = 5.0
|
|
237
|
+
@export var jump_velocity: float = 4.5
|
|
238
|
+
@export var mouse_sensitivity: float = 0.002
|
|
239
|
+
|
|
240
|
+
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
|
241
|
+
|
|
242
|
+
func _ready() -> void:
|
|
243
|
+
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
244
|
+
|
|
245
|
+
func _unhandled_input(event: InputEvent) -> void:
|
|
246
|
+
if event is InputEventMouseMotion:
|
|
247
|
+
rotate_y(-event.relative.x * mouse_sensitivity)
|
|
248
|
+
$Camera.rotate_x(-event.relative.y * mouse_sensitivity)
|
|
249
|
+
$Camera.rotation.x = clampf($Camera.rotation.x, -PI/2, PI/2)
|
|
250
|
+
if event.is_action_pressed("ui_cancel"):
|
|
251
|
+
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
252
|
+
|
|
253
|
+
func _physics_process(delta: float) -> void:
|
|
254
|
+
if not is_on_floor():
|
|
255
|
+
velocity.y -= gravity * delta
|
|
256
|
+
|
|
257
|
+
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
258
|
+
velocity.y = jump_velocity
|
|
259
|
+
|
|
260
|
+
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
|
261
|
+
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
|
262
|
+
|
|
263
|
+
if direction:
|
|
264
|
+
velocity.x = direction.x * speed
|
|
265
|
+
velocity.z = direction.z * speed
|
|
266
|
+
else:
|
|
267
|
+
velocity.x = move_toward(velocity.x, 0, speed)
|
|
268
|
+
velocity.z = move_toward(velocity.z, 0, speed)
|
|
269
|
+
|
|
270
|
+
move_and_slide()
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 2D Platformer Controller
|
|
274
|
+
|
|
275
|
+
Write to `res://scripts/player.gd`:
|
|
276
|
+
|
|
277
|
+
```gdscript
|
|
278
|
+
extends CharacterBody2D
|
|
279
|
+
|
|
280
|
+
@export var speed: float = 200.0
|
|
281
|
+
@export var jump_velocity: float = -350.0
|
|
282
|
+
|
|
283
|
+
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
|
|
284
|
+
|
|
285
|
+
func _physics_process(delta: float) -> void:
|
|
286
|
+
if not is_on_floor():
|
|
287
|
+
velocity.y += gravity * delta
|
|
288
|
+
|
|
289
|
+
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
290
|
+
velocity.y = jump_velocity
|
|
291
|
+
|
|
292
|
+
var direction := Input.get_axis("move_left", "move_right")
|
|
293
|
+
velocity.x = direction * speed if direction else move_toward(velocity.x, 0, speed)
|
|
294
|
+
|
|
295
|
+
move_and_slide()
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Collectible Pickup
|
|
299
|
+
|
|
300
|
+
Write to `res://scripts/collectible.gd`:
|
|
301
|
+
|
|
302
|
+
```gdscript
|
|
303
|
+
extends Area3D
|
|
304
|
+
|
|
305
|
+
signal collected
|
|
306
|
+
|
|
307
|
+
func _ready() -> void:
|
|
308
|
+
body_entered.connect(_on_body_entered)
|
|
309
|
+
|
|
310
|
+
func _on_body_entered(body: Node3D) -> void:
|
|
311
|
+
if body.is_in_group("player"):
|
|
312
|
+
collected.emit()
|
|
313
|
+
queue_free()
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Simple Enemy Patrol
|
|
317
|
+
|
|
318
|
+
Write to `res://scripts/enemy_patrol.gd`:
|
|
319
|
+
|
|
320
|
+
```gdscript
|
|
321
|
+
extends CharacterBody3D
|
|
322
|
+
|
|
323
|
+
@export var speed: float = 2.0
|
|
324
|
+
@export var patrol_distance: float = 5.0
|
|
325
|
+
|
|
326
|
+
var start_pos: Vector3
|
|
327
|
+
var direction: float = 1.0
|
|
328
|
+
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
|
|
329
|
+
|
|
330
|
+
func _ready() -> void:
|
|
331
|
+
start_pos = global_position
|
|
332
|
+
|
|
333
|
+
func _physics_process(delta: float) -> void:
|
|
334
|
+
if not is_on_floor():
|
|
335
|
+
velocity.y -= gravity * delta
|
|
336
|
+
|
|
337
|
+
velocity.x = direction * speed
|
|
338
|
+
move_and_slide()
|
|
339
|
+
|
|
340
|
+
if global_position.distance_to(start_pos) > patrol_distance:
|
|
341
|
+
direction *= -1
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Phase 5: Test and Debug
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
summer_clear_console()
|
|
350
|
+
summer_play()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
After a few seconds:
|
|
354
|
+
```
|
|
355
|
+
summer_get_diagnostics()
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If errors:
|
|
359
|
+
```
|
|
360
|
+
summer_get_console(type="error")
|
|
361
|
+
summer_get_script_errors(path="res://scripts/player.gd")
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Fix, then:
|
|
365
|
+
```
|
|
366
|
+
summer_stop()
|
|
367
|
+
# apply fix
|
|
368
|
+
summer_play()
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Common errors
|
|
372
|
+
|
|
373
|
+
| Error | Cause | Fix |
|
|
374
|
+
|-------|-------|-----|
|
|
375
|
+
| "Node not found" | Bad $NodePath | Check path with `summer_get_scene_tree()` |
|
|
376
|
+
| "Invalid call" on move_and_slide | Wrong base class | Script must extend CharacterBody3D/2D |
|
|
377
|
+
| Falling through floor | No collision | Add StaticBody3D + CollisionShape3D to ground |
|
|
378
|
+
| "No main scene" | Not configured | `summer_project_setting(key="application/run/main_scene", ...)` |
|
|
379
|
+
| Mouse not captured | Missing in _ready | Add `Input.mouse_mode = Input.MOUSE_MODE_CAPTURED` |
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Phase 6: Iterate
|
|
384
|
+
|
|
385
|
+
After first playable, ask the user what to improve. Common next steps:
|
|
386
|
+
|
|
387
|
+
- **More levels**: Create new .tscn, instantiate player, add to scene list
|
|
388
|
+
- **Enemies**: New scene + patrol script + spawn in level
|
|
389
|
+
- **UI/HUD**: Add CanvasLayer + Control nodes (see ui-basics skill)
|
|
390
|
+
- **Sound**: Generate with `summer_generate_audio`, attach AudioStreamPlayer3D
|
|
391
|
+
- **Lighting**: Set up WorldEnvironment + lights (see 3d-lighting skill)
|
|
392
|
+
- **Game states**: Main menu > gameplay > game over flow via SceneTree.change_scene_to_file()
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Anti-Patterns
|
|
397
|
+
|
|
398
|
+
- Never write .tscn files with host file tools -- always use MCP scene tools
|
|
399
|
+
- Never guess node paths -- call `summer_get_scene_tree()` first
|
|
400
|
+
- Never edit scenes while game is running -- call `summer_stop()` first
|
|
401
|
+
- Never skip `summer_save_scene()` -- unsaved changes are editor-only
|
|
402
|
+
- Script .gd files must exist on disk BEFORE attaching via `summer_set_prop`
|
|
403
|
+
- `summer_create_scene` requires `allow_temporary_scene_mutation=true`
|
|
404
|
+
- Node paths use `./` prefix. File paths use `res://` prefix. Never mix them.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Tool Quick Reference
|
|
409
|
+
|
|
410
|
+
| Category | Tool | Purpose |
|
|
411
|
+
|----------|------|---------|
|
|
412
|
+
| Setup | `summer_get_agent_playbook` | Safe workflow guide |
|
|
413
|
+
| Setup | `summer_get_project_context` | Project name, paths, status |
|
|
414
|
+
| Setup | `summer_project_setting` | Set project.godot values |
|
|
415
|
+
| Scene | `summer_create_scene` | New scene file |
|
|
416
|
+
| Scene | `summer_open_scene` | Switch to scene |
|
|
417
|
+
| Scene | `summer_get_scene_tree` | Read scene structure |
|
|
418
|
+
| Scene | `summer_save_scene` | Save to disk |
|
|
419
|
+
| Nodes | `summer_add_node` | Add node |
|
|
420
|
+
| Nodes | `summer_set_prop` | Set property |
|
|
421
|
+
| Nodes | `summer_set_resource_property` | Set sub-resource prop |
|
|
422
|
+
| Nodes | `summer_remove_node` | Delete node |
|
|
423
|
+
| Nodes | `summer_instantiate_scene` | Place .tscn/.glb |
|
|
424
|
+
| Nodes | `summer_batch` | Multi-op, one undo |
|
|
425
|
+
| Nodes | `summer_connect_signal` | Wire signals |
|
|
426
|
+
| Nodes | `summer_inspect_node` | Read node details |
|
|
427
|
+
| Input | `summer_input_map_bind` | Bind keys to actions |
|
|
428
|
+
| Assets | `summer_search_assets` | Search 25k+ library |
|
|
429
|
+
| Assets | `summer_import_asset` | Search + import + place |
|
|
430
|
+
| Assets | `summer_import_from_url` | Import from URL |
|
|
431
|
+
| Generate | `summer_generate_image` | AI image gen |
|
|
432
|
+
| Generate | `summer_generate_audio` | AI audio/SFX/music |
|
|
433
|
+
| Generate | `summer_generate_3d` | AI 3D model gen |
|
|
434
|
+
| Generate | `summer_generate_video` | AI video gen |
|
|
435
|
+
| Generate | `summer_check_job` | Poll async jobs |
|
|
436
|
+
| Debug | `summer_play` | Run game |
|
|
437
|
+
| Debug | `summer_stop` | Stop game |
|
|
438
|
+
| Debug | `summer_get_diagnostics` | Error overview |
|
|
439
|
+
| Debug | `summer_get_console` | Read output |
|
|
440
|
+
| Debug | `summer_get_script_errors` | Check .gd compile |
|
|
441
|
+
| Debug | `summer_clear_console` | Clear output |
|