gopeak 2.3.7 → 2.3.8
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/LICENSE +21 -21
- package/README.md +279 -411
- package/build/addon/auto_reload/auto_reload.gd +126 -126
- package/build/addon/auto_reload/plugin.cfg +7 -7
- package/build/addon/godot_mcp_editor/mcp_client.gd +178 -178
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -6
- package/build/addon/godot_mcp_editor/plugin.gd +84 -84
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -114
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -502
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -425
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +761 -761
- package/build/addon/godot_mcp_runtime/godot_mcp_runtime.gd +33 -33
- package/build/addon/godot_mcp_runtime/mcp_runtime_autoload.gd +671 -619
- package/build/addon/godot_mcp_runtime/plugin.cfg +7 -7
- package/build/cli/notify.js +4 -3
- package/build/cli.js +18 -18
- package/build/godot-bridge.js +11 -11
- package/build/index.js +238 -123
- package/build/scripts/godot_operations.gd +6823 -6823
- package/build/visualizer/events.js +19 -19
- package/build/visualizer/panel.js +34 -34
- package/build/visualizer/usages.js +14 -14
- package/build/visualizer-server.js +11 -11
- package/build/visualizer.html +2596 -2596
- package/package.json +106 -107
- package/scripts/postinstall.mjs +29 -29
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
[plugin]
|
|
2
|
-
|
|
3
|
-
name="Godot MCP Runtime"
|
|
4
|
-
description="Runtime communication addon for Godot MCP server. Enables real-time scene inspection, property modification, and method calling from AI assistants."
|
|
5
|
-
author="godot-mcp"
|
|
6
|
-
version="1.0.0"
|
|
7
|
-
script="godot_mcp_runtime.gd"
|
|
1
|
+
[plugin]
|
|
2
|
+
|
|
3
|
+
name="Godot MCP Runtime"
|
|
4
|
+
description="Runtime communication addon for Godot MCP server. Enables real-time scene inspection, property modification, and method calling from AI assistants."
|
|
5
|
+
author="godot-mcp"
|
|
6
|
+
version="1.0.0"
|
|
7
|
+
script="godot_mcp_runtime.gd"
|
package/build/cli/notify.js
CHANGED
|
@@ -42,9 +42,10 @@ export async function showNotification() {
|
|
|
42
42
|
}
|
|
43
43
|
// --- Star prompt (one-time) ---
|
|
44
44
|
if (!hasStarPrompted) {
|
|
45
|
-
await askYesNo(' \u2b50 Star GoPeak on GitHub? (y/n): ');
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
const wantsStar = await askYesNo(' \u2b50 Star GoPeak on GitHub? (y/n): ');
|
|
46
|
+
if (wantsStar) {
|
|
47
|
+
await handleStar();
|
|
48
|
+
}
|
|
48
49
|
writeFileSync(STAR_PROMPTED_FILE, new Date().toISOString());
|
|
49
50
|
console.log('');
|
|
50
51
|
}
|
package/build/cli.js
CHANGED
|
@@ -65,24 +65,24 @@ async function main() {
|
|
|
65
65
|
}
|
|
66
66
|
function printHelp() {
|
|
67
67
|
const version = getLocalVersion();
|
|
68
|
-
console.log(`
|
|
69
|
-
GoPeak v${version} — AI-Powered Godot Development via MCP
|
|
70
|
-
|
|
71
|
-
Usage:
|
|
72
|
-
gopeak Start MCP server (default)
|
|
73
|
-
gopeak setup Install shell hooks for update notifications
|
|
74
|
-
gopeak check Check for GoPeak updates
|
|
75
|
-
gopeak check --bg Background check (used by shell hooks)
|
|
76
|
-
gopeak check --quiet Print only if update available
|
|
77
|
-
gopeak star Star GoPeak on GitHub
|
|
78
|
-
gopeak uninstall Remove shell hooks
|
|
79
|
-
gopeak version Show current version
|
|
80
|
-
gopeak help Show this help
|
|
81
|
-
|
|
82
|
-
Shell hooks wrap these commands with update notifications:
|
|
83
|
-
claude, codex, gemini, opencode, omc, omx
|
|
84
|
-
|
|
85
|
-
More info: https://github.com/HaD0Yun/Gopeak-godot-mcp
|
|
68
|
+
console.log(`
|
|
69
|
+
GoPeak v${version} — AI-Powered Godot Development via MCP
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
gopeak Start MCP server (default)
|
|
73
|
+
gopeak setup Install shell hooks for update notifications
|
|
74
|
+
gopeak check Check for GoPeak updates
|
|
75
|
+
gopeak check --bg Background check (used by shell hooks)
|
|
76
|
+
gopeak check --quiet Print only if update available
|
|
77
|
+
gopeak star Star GoPeak on GitHub
|
|
78
|
+
gopeak uninstall Remove shell hooks
|
|
79
|
+
gopeak version Show current version
|
|
80
|
+
gopeak help Show this help
|
|
81
|
+
|
|
82
|
+
Shell hooks wrap these commands with update notifications:
|
|
83
|
+
claude, codex, gemini, opencode, omc, omx
|
|
84
|
+
|
|
85
|
+
More info: https://github.com/HaD0Yun/Gopeak-godot-mcp
|
|
86
86
|
`.trim());
|
|
87
87
|
}
|
|
88
88
|
main().catch((err) => {
|
package/build/godot-bridge.js
CHANGED
|
@@ -482,17 +482,17 @@ export class GodotBridge extends EventEmitter {
|
|
|
482
482
|
this.emit(eventName, payload);
|
|
483
483
|
}
|
|
484
484
|
getDefaultVisualizerHtml() {
|
|
485
|
-
return `<!doctype html>
|
|
486
|
-
<html lang="en">
|
|
487
|
-
<head>
|
|
488
|
-
<meta charset="utf-8" />
|
|
489
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
490
|
-
<title>Godot MCP Visualizer</title>
|
|
491
|
-
</head>
|
|
492
|
-
<body>
|
|
493
|
-
<h1>Godot MCP Visualizer</h1>
|
|
494
|
-
<p>Run the map_project tool to load visualization data.</p>
|
|
495
|
-
</body>
|
|
485
|
+
return `<!doctype html>
|
|
486
|
+
<html lang="en">
|
|
487
|
+
<head>
|
|
488
|
+
<meta charset="utf-8" />
|
|
489
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
490
|
+
<title>Godot MCP Visualizer</title>
|
|
491
|
+
</head>
|
|
492
|
+
<body>
|
|
493
|
+
<h1>Godot MCP Visualizer</h1>
|
|
494
|
+
<p>Run the map_project tool to load visualization data.</p>
|
|
495
|
+
</body>
|
|
496
496
|
</html>`;
|
|
497
497
|
}
|
|
498
498
|
rejectAllPending(error) {
|
package/build/index.js
CHANGED
|
@@ -475,10 +475,20 @@ class GodotServer {
|
|
|
475
475
|
const params = (args && typeof args === 'object') ? args : {};
|
|
476
476
|
const RUNTIME_PORT = 7777;
|
|
477
477
|
const RUNTIME_HOST = '127.0.0.1';
|
|
478
|
-
const
|
|
478
|
+
const timeoutOverride = Number.parseInt(process.env.GOPEAK_RUNTIME_TIMEOUT_MS || '', 10);
|
|
479
|
+
const TIMEOUT_MS = Number.isInteger(timeoutOverride) && timeoutOverride > 0 ? timeoutOverride : 10000;
|
|
480
|
+
const expectsScreenshot = command === 'capture_screenshot' || command === 'capture_viewport';
|
|
481
|
+
const screenshotDir = expectsScreenshot ? mkdtempSync(join(tmpdir(), 'gopeak-runtime-screenshot-')) : null;
|
|
482
|
+
const screenshotPath = screenshotDir ? join(screenshotDir, 'capture.png') : null;
|
|
483
|
+
const runtimeParams = screenshotPath ? { ...params, output_path: screenshotPath } : params;
|
|
484
|
+
const cleanupScreenshotDir = () => {
|
|
485
|
+
if (screenshotDir) {
|
|
486
|
+
rmSync(screenshotDir, { recursive: true, force: true });
|
|
487
|
+
}
|
|
488
|
+
};
|
|
479
489
|
return new Promise((resolve) => {
|
|
480
490
|
const socket = createTcpConnection({ port: RUNTIME_PORT, host: RUNTIME_HOST }, () => {
|
|
481
|
-
const payload = JSON.stringify({ command, params, id: Date.now() });
|
|
491
|
+
const payload = JSON.stringify({ command, params: runtimeParams, id: Date.now() });
|
|
482
492
|
socket.write(payload + '\n');
|
|
483
493
|
});
|
|
484
494
|
let responseBuffer = Buffer.alloc(0);
|
|
@@ -489,6 +499,7 @@ class GodotServer {
|
|
|
489
499
|
}
|
|
490
500
|
resolved = true;
|
|
491
501
|
socket.destroy();
|
|
502
|
+
cleanupScreenshotDir();
|
|
492
503
|
resolve({
|
|
493
504
|
content: [{ type: 'text', text: `Runtime command '${command}' timed out after ${TIMEOUT_MS}ms. Ensure the Godot game is running with the MCP runtime addon enabled.` }],
|
|
494
505
|
});
|
|
@@ -500,7 +511,36 @@ class GodotServer {
|
|
|
500
511
|
resolved = true;
|
|
501
512
|
clearTimeout(timer);
|
|
502
513
|
socket.destroy();
|
|
514
|
+
if (parsed.type === 'screenshot_file' && parsed.path) {
|
|
515
|
+
const returnedPath = String(parsed.path);
|
|
516
|
+
if (!screenshotPath || normalize(returnedPath) !== normalize(screenshotPath)) {
|
|
517
|
+
cleanupScreenshotDir();
|
|
518
|
+
resolve({
|
|
519
|
+
content: [{ type: 'text', text: `Rejected screenshot file path outside the GoPeak-managed capture path: '${returnedPath}'` }],
|
|
520
|
+
});
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
const imageData = readFileSync(screenshotPath).toString('base64');
|
|
525
|
+
cleanupScreenshotDir();
|
|
526
|
+
resolve({
|
|
527
|
+
content: [
|
|
528
|
+
{ type: 'text', text: `Screenshot captured: ${parsed.width}x${parsed.height} ${parsed.format}` },
|
|
529
|
+
{ type: 'image', data: imageData, mimeType: 'image/png' },
|
|
530
|
+
],
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
cleanupScreenshotDir();
|
|
535
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
536
|
+
resolve({
|
|
537
|
+
content: [{ type: 'text', text: `Failed to read screenshot file '${screenshotPath}': ${message}` }],
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
503
542
|
if (parsed.type === 'screenshot' && parsed.data) {
|
|
543
|
+
cleanupScreenshotDir();
|
|
504
544
|
resolve({
|
|
505
545
|
content: [
|
|
506
546
|
{ type: 'text', text: `Screenshot captured: ${parsed.width}x${parsed.height} ${parsed.format}` },
|
|
@@ -509,6 +549,7 @@ class GodotServer {
|
|
|
509
549
|
});
|
|
510
550
|
return;
|
|
511
551
|
}
|
|
552
|
+
cleanupScreenshotDir();
|
|
512
553
|
resolve({
|
|
513
554
|
content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }],
|
|
514
555
|
});
|
|
@@ -551,7 +592,8 @@ class GodotServer {
|
|
|
551
592
|
newlineIndex = responseBuffer.indexOf(0x0a);
|
|
552
593
|
}
|
|
553
594
|
if (parsedMessages.length > 0) {
|
|
554
|
-
const candidate = parsedMessages.find((message) => message?.type === '
|
|
595
|
+
const candidate = parsedMessages.find((message) => message?.type === 'screenshot_file' && message?.path)
|
|
596
|
+
?? parsedMessages.find((message) => message?.type === 'screenshot' && message?.data)
|
|
555
597
|
?? parsedMessages.find((message) => message?.type === 'pong')
|
|
556
598
|
?? parsedMessages.find((message) => message?.type && message.type !== 'welcome')
|
|
557
599
|
?? null;
|
|
@@ -567,6 +609,7 @@ class GodotServer {
|
|
|
567
609
|
clearTimeout(timer);
|
|
568
610
|
const responseData = responseBuffer.toString('utf8').trim();
|
|
569
611
|
resolved = true;
|
|
612
|
+
cleanupScreenshotDir();
|
|
570
613
|
try {
|
|
571
614
|
const parsed = JSON.parse(responseData);
|
|
572
615
|
resolve({
|
|
@@ -585,6 +628,7 @@ class GodotServer {
|
|
|
585
628
|
}
|
|
586
629
|
resolved = true;
|
|
587
630
|
clearTimeout(timer);
|
|
631
|
+
cleanupScreenshotDir();
|
|
588
632
|
resolve({
|
|
589
633
|
content: [{ type: 'text', text: `Failed to connect to Godot runtime addon at ${RUNTIME_HOST}:${RUNTIME_PORT}: ${error.message}. Ensure the game is running with the MCP runtime autoload enabled.` }],
|
|
590
634
|
});
|
|
@@ -1589,7 +1633,7 @@ class GodotServer {
|
|
|
1589
1633
|
this.logDebug('Killing existing Godot process before starting a new one');
|
|
1590
1634
|
this.activeProcess.process.kill();
|
|
1591
1635
|
}
|
|
1592
|
-
const cmdArgs = ['-d', '--path', args.projectPath];
|
|
1636
|
+
const cmdArgs = ['--headless', '-d', '--path', args.projectPath];
|
|
1593
1637
|
if (args.scene && this.validatePath(args.scene)) {
|
|
1594
1638
|
this.logDebug(`Adding scene parameter: ${args.scene}`);
|
|
1595
1639
|
cmdArgs.push(args.scene);
|
|
@@ -4667,6 +4711,80 @@ class GodotServer {
|
|
|
4667
4711
|
// ============================================
|
|
4668
4712
|
// Project Search Handlers
|
|
4669
4713
|
// ============================================
|
|
4714
|
+
searchProjectNatively(projectPath, query, fileTypes, useRegex, caseSensitive, maxResults) {
|
|
4715
|
+
const normalizedExtensions = new Set(fileTypes.map((ext) => ext.replace(/^\./, '').toLowerCase()).filter(Boolean));
|
|
4716
|
+
const result = {
|
|
4717
|
+
query,
|
|
4718
|
+
results: [],
|
|
4719
|
+
summary: {
|
|
4720
|
+
files_searched: 0,
|
|
4721
|
+
files_with_matches: 0,
|
|
4722
|
+
total_matches: 0,
|
|
4723
|
+
truncated: false,
|
|
4724
|
+
},
|
|
4725
|
+
};
|
|
4726
|
+
const regex = useRegex ? new RegExp(query, caseSensitive ? '' : 'i') : null;
|
|
4727
|
+
const queryToCheck = caseSensitive ? query : query.toLowerCase();
|
|
4728
|
+
const visit = (dirPath) => {
|
|
4729
|
+
if (result.summary.total_matches >= maxResults) {
|
|
4730
|
+
result.summary.truncated = true;
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
4734
|
+
if (result.summary.total_matches >= maxResults) {
|
|
4735
|
+
result.summary.truncated = true;
|
|
4736
|
+
return;
|
|
4737
|
+
}
|
|
4738
|
+
if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '.godot') {
|
|
4739
|
+
continue;
|
|
4740
|
+
}
|
|
4741
|
+
const entryPath = join(dirPath, entry.name);
|
|
4742
|
+
if (entry.isDirectory()) {
|
|
4743
|
+
visit(entryPath);
|
|
4744
|
+
continue;
|
|
4745
|
+
}
|
|
4746
|
+
if (!entry.isFile()) {
|
|
4747
|
+
continue;
|
|
4748
|
+
}
|
|
4749
|
+
const extension = entry.name.includes('.') ? entry.name.split('.').pop()?.toLowerCase() || '' : '';
|
|
4750
|
+
if (!normalizedExtensions.has(extension)) {
|
|
4751
|
+
continue;
|
|
4752
|
+
}
|
|
4753
|
+
result.summary.files_searched += 1;
|
|
4754
|
+
const content = readFileSync(entryPath, 'utf8');
|
|
4755
|
+
const lines = content.split('\n');
|
|
4756
|
+
const matches = [];
|
|
4757
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4758
|
+
if (result.summary.total_matches >= maxResults) {
|
|
4759
|
+
result.summary.truncated = true;
|
|
4760
|
+
break;
|
|
4761
|
+
}
|
|
4762
|
+
const line = lines[index];
|
|
4763
|
+
const match = regex
|
|
4764
|
+
? regex.exec(line)?.[0]
|
|
4765
|
+
: ((caseSensitive ? line : line.toLowerCase()).includes(queryToCheck) ? query : '');
|
|
4766
|
+
if (match) {
|
|
4767
|
+
matches.push({
|
|
4768
|
+
line: index + 1,
|
|
4769
|
+
content: line.trim(),
|
|
4770
|
+
match,
|
|
4771
|
+
});
|
|
4772
|
+
result.summary.total_matches += 1;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
if (matches.length > 0) {
|
|
4776
|
+
const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, '/');
|
|
4777
|
+
result.results.push({
|
|
4778
|
+
file: `res://${relativePath}`,
|
|
4779
|
+
matches,
|
|
4780
|
+
});
|
|
4781
|
+
result.summary.files_with_matches += 1;
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
};
|
|
4785
|
+
visit(projectPath);
|
|
4786
|
+
return result;
|
|
4787
|
+
}
|
|
4670
4788
|
/**
|
|
4671
4789
|
* Handle the search_project tool
|
|
4672
4790
|
*/
|
|
@@ -4690,12 +4808,9 @@ class GodotServer {
|
|
|
4690
4808
|
caseSensitive: args.caseSensitive || false,
|
|
4691
4809
|
maxResults: args.maxResults || 100,
|
|
4692
4810
|
};
|
|
4693
|
-
const
|
|
4694
|
-
if (stderr && stderr.includes('ERROR')) {
|
|
4695
|
-
return this.createErrorResponse(`Failed to search project: ${stderr}`, ['Check if the query/regex pattern is valid']);
|
|
4696
|
-
}
|
|
4811
|
+
const result = this.searchProjectNatively(args.projectPath, params.query, params.fileTypes, params.regex, params.caseSensitive, params.maxResults);
|
|
4697
4812
|
return {
|
|
4698
|
-
content: [{ type: 'text', text:
|
|
4813
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
4699
4814
|
};
|
|
4700
4815
|
}
|
|
4701
4816
|
catch (error) {
|
|
@@ -5123,146 +5238,146 @@ class GodotServer {
|
|
|
5123
5238
|
const shaderTemplates = {
|
|
5124
5239
|
medieval: {
|
|
5125
5240
|
description: 'Warm stone/aged material look',
|
|
5126
|
-
code: `shader_type spatial;
|
|
5127
|
-
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;
|
|
5128
|
-
|
|
5129
|
-
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5130
|
-
uniform float roughness : hint_range(0.0, 1.0) = 0.85;
|
|
5131
|
-
uniform float age_factor : hint_range(0.0, 1.0) = 0.3;
|
|
5132
|
-
uniform vec3 tint_color : source_color = vec3(0.9, 0.85, 0.75);
|
|
5133
|
-
|
|
5134
|
-
void fragment() {
|
|
5135
|
-
vec4 albedo = texture(albedo_texture, UV);
|
|
5136
|
-
vec3 aged = mix(albedo.rgb, albedo.rgb * tint_color, age_factor);
|
|
5137
|
-
ALBEDO = aged;
|
|
5138
|
-
ROUGHNESS = roughness;
|
|
5139
|
-
SPECULAR = 0.2;
|
|
5140
|
-
METALLIC = 0.0;
|
|
5241
|
+
code: `shader_type spatial;
|
|
5242
|
+
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;
|
|
5243
|
+
|
|
5244
|
+
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5245
|
+
uniform float roughness : hint_range(0.0, 1.0) = 0.85;
|
|
5246
|
+
uniform float age_factor : hint_range(0.0, 1.0) = 0.3;
|
|
5247
|
+
uniform vec3 tint_color : source_color = vec3(0.9, 0.85, 0.75);
|
|
5248
|
+
|
|
5249
|
+
void fragment() {
|
|
5250
|
+
vec4 albedo = texture(albedo_texture, UV);
|
|
5251
|
+
vec3 aged = mix(albedo.rgb, albedo.rgb * tint_color, age_factor);
|
|
5252
|
+
ALBEDO = aged;
|
|
5253
|
+
ROUGHNESS = roughness;
|
|
5254
|
+
SPECULAR = 0.2;
|
|
5255
|
+
METALLIC = 0.0;
|
|
5141
5256
|
}`,
|
|
5142
5257
|
},
|
|
5143
5258
|
cyberpunk: {
|
|
5144
5259
|
description: 'Neon glow with pulsing effect',
|
|
5145
|
-
code: `shader_type spatial;
|
|
5146
|
-
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5147
|
-
|
|
5148
|
-
uniform vec3 neon_color : source_color = vec3(1.0, 0.0, 0.8);
|
|
5149
|
-
uniform float pulse_speed : hint_range(0.0, 10.0) = 2.0;
|
|
5150
|
-
uniform float intensity : hint_range(0.0, 5.0) = 2.5;
|
|
5151
|
-
uniform float base_brightness : hint_range(0.0, 1.0) = 0.3;
|
|
5152
|
-
|
|
5153
|
-
void fragment() {
|
|
5154
|
-
float pulse = sin(TIME * pulse_speed) * 0.5 + 0.5;
|
|
5155
|
-
vec3 emissive = neon_color * intensity * (0.5 + pulse * 0.5);
|
|
5156
|
-
|
|
5157
|
-
ALBEDO = neon_color * base_brightness;
|
|
5158
|
-
EMISSION = emissive;
|
|
5159
|
-
METALLIC = 0.8;
|
|
5160
|
-
ROUGHNESS = 0.2;
|
|
5260
|
+
code: `shader_type spatial;
|
|
5261
|
+
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5262
|
+
|
|
5263
|
+
uniform vec3 neon_color : source_color = vec3(1.0, 0.0, 0.8);
|
|
5264
|
+
uniform float pulse_speed : hint_range(0.0, 10.0) = 2.0;
|
|
5265
|
+
uniform float intensity : hint_range(0.0, 5.0) = 2.5;
|
|
5266
|
+
uniform float base_brightness : hint_range(0.0, 1.0) = 0.3;
|
|
5267
|
+
|
|
5268
|
+
void fragment() {
|
|
5269
|
+
float pulse = sin(TIME * pulse_speed) * 0.5 + 0.5;
|
|
5270
|
+
vec3 emissive = neon_color * intensity * (0.5 + pulse * 0.5);
|
|
5271
|
+
|
|
5272
|
+
ALBEDO = neon_color * base_brightness;
|
|
5273
|
+
EMISSION = emissive;
|
|
5274
|
+
METALLIC = 0.8;
|
|
5275
|
+
ROUGHNESS = 0.2;
|
|
5161
5276
|
}`,
|
|
5162
5277
|
},
|
|
5163
5278
|
nature: {
|
|
5164
5279
|
description: 'Wind sway for foliage',
|
|
5165
|
-
code: `shader_type spatial;
|
|
5166
|
-
render_mode blend_mix, depth_draw_opaque, cull_disabled;
|
|
5167
|
-
|
|
5168
|
-
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5169
|
-
uniform float wind_strength : hint_range(0.0, 2.0) = 0.3;
|
|
5170
|
-
uniform float wind_speed : hint_range(0.0, 5.0) = 1.5;
|
|
5171
|
-
uniform float wind_scale : hint_range(0.1, 10.0) = 1.0;
|
|
5172
|
-
|
|
5173
|
-
void vertex() {
|
|
5174
|
-
float wind = sin(TIME * wind_speed + VERTEX.x * wind_scale + VERTEX.z * wind_scale * 0.7);
|
|
5175
|
-
float height_factor = UV.y;
|
|
5176
|
-
VERTEX.x += wind * wind_strength * height_factor;
|
|
5177
|
-
VERTEX.z += wind * wind_strength * height_factor * 0.5;
|
|
5178
|
-
}
|
|
5179
|
-
|
|
5180
|
-
void fragment() {
|
|
5181
|
-
vec4 albedo = texture(albedo_texture, UV);
|
|
5182
|
-
ALBEDO = albedo.rgb;
|
|
5183
|
-
ALPHA = albedo.a;
|
|
5184
|
-
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
|
5185
|
-
ROUGHNESS = 0.8;
|
|
5280
|
+
code: `shader_type spatial;
|
|
5281
|
+
render_mode blend_mix, depth_draw_opaque, cull_disabled;
|
|
5282
|
+
|
|
5283
|
+
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5284
|
+
uniform float wind_strength : hint_range(0.0, 2.0) = 0.3;
|
|
5285
|
+
uniform float wind_speed : hint_range(0.0, 5.0) = 1.5;
|
|
5286
|
+
uniform float wind_scale : hint_range(0.1, 10.0) = 1.0;
|
|
5287
|
+
|
|
5288
|
+
void vertex() {
|
|
5289
|
+
float wind = sin(TIME * wind_speed + VERTEX.x * wind_scale + VERTEX.z * wind_scale * 0.7);
|
|
5290
|
+
float height_factor = UV.y;
|
|
5291
|
+
VERTEX.x += wind * wind_strength * height_factor;
|
|
5292
|
+
VERTEX.z += wind * wind_strength * height_factor * 0.5;
|
|
5293
|
+
}
|
|
5294
|
+
|
|
5295
|
+
void fragment() {
|
|
5296
|
+
vec4 albedo = texture(albedo_texture, UV);
|
|
5297
|
+
ALBEDO = albedo.rgb;
|
|
5298
|
+
ALPHA = albedo.a;
|
|
5299
|
+
ALPHA_SCISSOR_THRESHOLD = 0.5;
|
|
5300
|
+
ROUGHNESS = 0.8;
|
|
5186
5301
|
}`,
|
|
5187
5302
|
},
|
|
5188
5303
|
scifi: {
|
|
5189
5304
|
description: 'Clean metallic with LED accents',
|
|
5190
|
-
code: `shader_type spatial;
|
|
5191
|
-
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5192
|
-
|
|
5193
|
-
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5194
|
-
uniform vec3 led_color : source_color = vec3(0.0, 0.8, 1.0);
|
|
5195
|
-
uniform float led_intensity : hint_range(0.0, 3.0) = 1.5;
|
|
5196
|
-
uniform float metallic_value : hint_range(0.0, 1.0) = 0.9;
|
|
5197
|
-
|
|
5198
|
-
void fragment() {
|
|
5199
|
-
vec4 albedo = texture(albedo_texture, UV);
|
|
5200
|
-
float led_mask = step(0.9, albedo.r) * step(0.9, albedo.g) * step(0.9, albedo.b);
|
|
5201
|
-
|
|
5202
|
-
ALBEDO = mix(albedo.rgb, albedo.rgb * 0.3, led_mask);
|
|
5203
|
-
EMISSION = led_color * led_intensity * led_mask;
|
|
5204
|
-
METALLIC = metallic_value;
|
|
5205
|
-
ROUGHNESS = mix(0.3, 0.1, led_mask);
|
|
5305
|
+
code: `shader_type spatial;
|
|
5306
|
+
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5307
|
+
|
|
5308
|
+
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5309
|
+
uniform vec3 led_color : source_color = vec3(0.0, 0.8, 1.0);
|
|
5310
|
+
uniform float led_intensity : hint_range(0.0, 3.0) = 1.5;
|
|
5311
|
+
uniform float metallic_value : hint_range(0.0, 1.0) = 0.9;
|
|
5312
|
+
|
|
5313
|
+
void fragment() {
|
|
5314
|
+
vec4 albedo = texture(albedo_texture, UV);
|
|
5315
|
+
float led_mask = step(0.9, albedo.r) * step(0.9, albedo.g) * step(0.9, albedo.b);
|
|
5316
|
+
|
|
5317
|
+
ALBEDO = mix(albedo.rgb, albedo.rgb * 0.3, led_mask);
|
|
5318
|
+
EMISSION = led_color * led_intensity * led_mask;
|
|
5319
|
+
METALLIC = metallic_value;
|
|
5320
|
+
ROUGHNESS = mix(0.3, 0.1, led_mask);
|
|
5206
5321
|
}`,
|
|
5207
5322
|
},
|
|
5208
5323
|
horror: {
|
|
5209
5324
|
description: 'Dark with subtle pulsing shadows',
|
|
5210
|
-
code: `shader_type spatial;
|
|
5211
|
-
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5212
|
-
|
|
5213
|
-
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5214
|
-
uniform float darkness : hint_range(0.0, 1.0) = 0.6;
|
|
5215
|
-
uniform float pulse_speed : hint_range(0.0, 5.0) = 0.5;
|
|
5216
|
-
uniform vec3 shadow_tint : source_color = vec3(0.1, 0.0, 0.15);
|
|
5217
|
-
|
|
5218
|
-
void fragment() {
|
|
5219
|
-
float pulse = sin(TIME * pulse_speed) * 0.1 + 0.9;
|
|
5220
|
-
vec4 albedo = texture(albedo_texture, UV);
|
|
5221
|
-
vec3 darkened = mix(albedo.rgb, shadow_tint, darkness);
|
|
5222
|
-
|
|
5223
|
-
ALBEDO = darkened * pulse;
|
|
5224
|
-
ROUGHNESS = 0.9;
|
|
5225
|
-
METALLIC = 0.0;
|
|
5325
|
+
code: `shader_type spatial;
|
|
5326
|
+
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5327
|
+
|
|
5328
|
+
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5329
|
+
uniform float darkness : hint_range(0.0, 1.0) = 0.6;
|
|
5330
|
+
uniform float pulse_speed : hint_range(0.0, 5.0) = 0.5;
|
|
5331
|
+
uniform vec3 shadow_tint : source_color = vec3(0.1, 0.0, 0.15);
|
|
5332
|
+
|
|
5333
|
+
void fragment() {
|
|
5334
|
+
float pulse = sin(TIME * pulse_speed) * 0.1 + 0.9;
|
|
5335
|
+
vec4 albedo = texture(albedo_texture, UV);
|
|
5336
|
+
vec3 darkened = mix(albedo.rgb, shadow_tint, darkness);
|
|
5337
|
+
|
|
5338
|
+
ALBEDO = darkened * pulse;
|
|
5339
|
+
ROUGHNESS = 0.9;
|
|
5340
|
+
METALLIC = 0.0;
|
|
5226
5341
|
}`,
|
|
5227
5342
|
},
|
|
5228
5343
|
cartoon: {
|
|
5229
5344
|
description: 'Cel-shaded toon look',
|
|
5230
|
-
code: `shader_type spatial;
|
|
5231
|
-
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5232
|
-
|
|
5233
|
-
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5234
|
-
uniform vec3 outline_color : source_color = vec3(0.0, 0.0, 0.0);
|
|
5235
|
-
uniform float shade_levels : hint_range(2.0, 8.0) = 3.0;
|
|
5236
|
-
|
|
5237
|
-
void fragment() {
|
|
5238
|
-
vec4 albedo = texture(albedo_texture, UV);
|
|
5239
|
-
ALBEDO = albedo.rgb;
|
|
5240
|
-
ROUGHNESS = 1.0;
|
|
5241
|
-
SPECULAR = 0.0;
|
|
5242
|
-
}
|
|
5243
|
-
|
|
5244
|
-
void light() {
|
|
5245
|
-
float NdotL = dot(NORMAL, LIGHT);
|
|
5246
|
-
float stepped = floor(NdotL * shade_levels) / shade_levels;
|
|
5247
|
-
DIFFUSE_LIGHT += stepped * ATTENUATION * LIGHT_COLOR;
|
|
5345
|
+
code: `shader_type spatial;
|
|
5346
|
+
render_mode blend_mix, depth_draw_opaque, cull_back;
|
|
5347
|
+
|
|
5348
|
+
uniform sampler2D albedo_texture : source_color, filter_linear_mipmap;
|
|
5349
|
+
uniform vec3 outline_color : source_color = vec3(0.0, 0.0, 0.0);
|
|
5350
|
+
uniform float shade_levels : hint_range(2.0, 8.0) = 3.0;
|
|
5351
|
+
|
|
5352
|
+
void fragment() {
|
|
5353
|
+
vec4 albedo = texture(albedo_texture, UV);
|
|
5354
|
+
ALBEDO = albedo.rgb;
|
|
5355
|
+
ROUGHNESS = 1.0;
|
|
5356
|
+
SPECULAR = 0.0;
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
void light() {
|
|
5360
|
+
float NdotL = dot(NORMAL, LIGHT);
|
|
5361
|
+
float stepped = floor(NdotL * shade_levels) / shade_levels;
|
|
5362
|
+
DIFFUSE_LIGHT += stepped * ATTENUATION * LIGHT_COLOR;
|
|
5248
5363
|
}`,
|
|
5249
5364
|
},
|
|
5250
5365
|
};
|
|
5251
5366
|
const effectTemplates = {
|
|
5252
|
-
glow: `
|
|
5253
|
-
uniform float glow_power : hint_range(0.0, 5.0) = 1.5;
|
|
5367
|
+
glow: `
|
|
5368
|
+
uniform float glow_power : hint_range(0.0, 5.0) = 1.5;
|
|
5254
5369
|
// Add to fragment(): EMISSION += ALBEDO * glow_power;`,
|
|
5255
|
-
hologram: `
|
|
5256
|
-
uniform float scan_speed : hint_range(0.0, 10.0) = 2.0;
|
|
5257
|
-
uniform float scan_lines : hint_range(10.0, 100.0) = 50.0;
|
|
5258
|
-
// Add to fragment():
|
|
5259
|
-
// float scan = sin(UV.y * scan_lines + TIME * scan_speed) * 0.5 + 0.5;
|
|
5370
|
+
hologram: `
|
|
5371
|
+
uniform float scan_speed : hint_range(0.0, 10.0) = 2.0;
|
|
5372
|
+
uniform float scan_lines : hint_range(10.0, 100.0) = 50.0;
|
|
5373
|
+
// Add to fragment():
|
|
5374
|
+
// float scan = sin(UV.y * scan_lines + TIME * scan_speed) * 0.5 + 0.5;
|
|
5260
5375
|
// ALPHA = 0.7 * scan;`,
|
|
5261
|
-
dissolve: `
|
|
5262
|
-
uniform sampler2D noise_texture : filter_linear;
|
|
5263
|
-
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
|
|
5264
|
-
// Add to fragment():
|
|
5265
|
-
// float noise = texture(noise_texture, UV).r;
|
|
5376
|
+
dissolve: `
|
|
5377
|
+
uniform sampler2D noise_texture : filter_linear;
|
|
5378
|
+
uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
|
|
5379
|
+
// Add to fragment():
|
|
5380
|
+
// float noise = texture(noise_texture, UV).r;
|
|
5266
5381
|
// if (noise < dissolve_amount) discard;`,
|
|
5267
5382
|
};
|
|
5268
5383
|
const theme = args.theme;
|