Compare commits
17 Commits
cocos-main
...
11d7496b54
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11d7496b54 | ||
|
|
1012af6039 | ||
|
|
37e0413576 | ||
|
|
8dfd42397f | ||
|
|
19bb63cd87 | ||
|
|
bae333ba7c | ||
|
|
aaccb73773 | ||
|
|
9c27042e27 | ||
|
|
6a3e797a9a | ||
| 74b983c74e | |||
| a699896116 | |||
|
|
43b983fb99 | ||
|
|
4fc07b1458 | ||
|
|
3bafb1e10d | ||
| b08562d603 | |||
| 33070ad827 | |||
| 6e0a1fbcbb |
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
.secrets
|
||||||
38
nginx.conf
@@ -4,10 +4,6 @@ server {
|
|||||||
index index.html index.htm default.htm default.html;
|
index index.html index.htm default.htm default.html;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
# 1. 添加 resolver 指令
|
|
||||||
# 127.0.0.11 是 Docker 内置的 DNS 服务器地址 并且将缓存设置为10s
|
|
||||||
# resolver 127.0.0.11 valid=10s;
|
|
||||||
|
|
||||||
# gzip 配置
|
# gzip 配置
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_static on;
|
gzip_static on;
|
||||||
@@ -22,37 +18,21 @@ server {
|
|||||||
return 301 /competition.html?competition_id=13;
|
return 301 /competition.html?competition_id=13;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unity 预压缩文件:禁止再次 gzip,避免 Content-Length 与实际内容不一致
|
# Brotli 压缩 WebAssembly 文件处理
|
||||||
location ~* \.wasm\.br$ {
|
location ~* \.wasm\.br$ {
|
||||||
gzip off;
|
default_type "";
|
||||||
gzip_static off;
|
|
||||||
default_type application/wasm;
|
|
||||||
add_header Content-Encoding br;
|
add_header Content-Encoding br;
|
||||||
add_header Content-Type application/wasm always;
|
add_header Content-Type application/wasm always;
|
||||||
add_header Vary Accept-Encoding;
|
add_header Vary Accept-Encoding;
|
||||||
add_header Accept-Ranges bytes;
|
expires 30d;
|
||||||
expires 1h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~* \.js\.br$ {
|
location ~* \.asm\.js\.br$ {
|
||||||
gzip off;
|
default_type "";
|
||||||
gzip_static off;
|
|
||||||
default_type application/javascript;
|
|
||||||
add_header Content-Encoding br;
|
add_header Content-Encoding br;
|
||||||
add_header Content-Type application/javascript always;
|
add_header Content-Type application/javascript always;
|
||||||
add_header Vary Accept-Encoding;
|
add_header Vary Accept-Encoding;
|
||||||
add_header Accept-Ranges bytes;
|
expires 30d;
|
||||||
expires 1h;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~* \.data\.br$ {
|
|
||||||
gzip off;
|
|
||||||
gzip_static off;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
add_header Content-Encoding br;
|
|
||||||
add_header Vary Accept-Encoding;
|
|
||||||
add_header Accept-Ranges bytes;
|
|
||||||
expires 1h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 通用 Brotli 文件处理
|
# 通用 Brotli 文件处理
|
||||||
@@ -78,12 +58,10 @@ server {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 纯 CDN:/cdn-unity/ → OSS 游戏包(config.js unityCdnUseSiteProxy: true)
|
||||||
# 路径需与 unityCdnRemote 保持一致,更新 OSS 目录时同步修改
|
# 路径需与 unityCdnRemote 保持一致,更新 OSS 目录时同步修改
|
||||||
# location /cdn-unity/ {
|
# location /cdn-unity/ {
|
||||||
# # 2. 将域名赋值给一个变量
|
# proxy_pass https://oss.eanic.cn/001_code_cocos_res_20260616/;
|
||||||
# set $oss_backend "https://oss.eanic.cn";
|
|
||||||
# # 3. proxy_pass 使用变量,这样 Nginx 就会在运行时解析域名
|
|
||||||
# proxy_pass $oss_backend/001_code_cocos_res_20260616/;
|
|
||||||
# proxy_ssl_server_name on;
|
# proxy_ssl_server_name on;
|
||||||
# proxy_set_header Host oss.eanic.cn;
|
# proxy_set_header Host oss.eanic.cn;
|
||||||
# proxy_hide_header Access-Control-Allow-Origin;
|
# proxy_hide_header Access-Control-Allow-Origin;
|
||||||
|
|||||||
0
scratch-blocks/build.py
Normal file → Executable file
0
scratch-blocks/cleanup.sh
Normal file → Executable file
0
scratch-blocks/i18n/create_messages.py
Normal file → Executable file
0
scratch-blocks/i18n/dedup_json.py
Normal file → Executable file
0
scratch-blocks/i18n/js_to_json.py
Normal file → Executable file
0
scratch-blocks/i18n/json_to_js.py
Normal file → Executable file
0
scratch-blocks/i18n/sync_tx_translations.js
Normal file → Executable file
0
scratch-blocks/i18n/xliff_to_json.py
Normal file → Executable file
0
scratch-blocks/local_build.sh
Normal file → Executable file
0
scratch-blocks/media/icons/control_forever.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
0
scratch-blocks/media/icons/control_repeat.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
0
scratch-blocks/media/icons/control_wait.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
0
scratch-blocks/media/icons/event_broadcast_blue.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_broadcast_coral.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_broadcast_green.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_broadcast_magenta.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_broadcast_orange.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_broadcast_purple.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_blue.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_coral.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_green.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_magenta.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_orange.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_when-broadcast-received_purple.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
0
scratch-blocks/media/icons/event_whenflagclicked.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
0
scratch-blocks/media/icons/set-led_blue.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_coral.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_green.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_magenta.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_mystery.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
0
scratch-blocks/media/icons/set-led_orange.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_purple.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_white.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/set-led_yellow.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
0
scratch-blocks/media/icons/wedo_motor-clockwise.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
0
scratch-blocks/media/icons/wedo_motor-counterclockwise.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
scratch-blocks/media/icons/wedo_when-distance_close.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
0
scratch-blocks/media/icons/wedo_when-tilt-backward.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
0
scratch-blocks/media/icons/wedo_when-tilt-forward.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
0
scratch-blocks/media/icons/wedo_when-tilt-left.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
scratch-blocks/media/icons/wedo_when-tilt-right.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
0
scratch-blocks/media/icons/wedo_when-tilt.svg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
7
scratch-blocks/package-lock.json
generated
@@ -144,7 +144,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
|
||||||
"integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
|
"integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.23.5",
|
"@babel/code-frame": "^7.23.5",
|
||||||
@@ -915,7 +914,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||||
"integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==",
|
"integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"co": "^4.6.0",
|
"co": "^4.6.0",
|
||||||
"fast-deep-equal": "^1.0.0",
|
"fast-deep-equal": "^1.0.0",
|
||||||
@@ -1573,7 +1571,6 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001565",
|
"caniuse-lite": "^1.0.30001565",
|
||||||
"electron-to-chromium": "^1.4.601",
|
"electron-to-chromium": "^1.4.601",
|
||||||
@@ -6912,7 +6909,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -7808,7 +7804,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -8946,7 +8941,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz",
|
||||||
"integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==",
|
"integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.9.0",
|
"@webassemblyjs/ast": "1.9.0",
|
||||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||||
@@ -9101,7 +9095,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
|||||||
0
scratch-blocks/pull_from_blockly.sh
Normal file → Executable file
9
scratch-gui/package-lock.json
generated
@@ -7688,9 +7688,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001649",
|
"version": "1.0.30001810",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
|
||||||
"integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==",
|
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -7704,7 +7704,8 @@
|
|||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/canvas-toBlob": {
|
"node_modules/canvas-toBlob": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"build:cdn-pure": "npm run clean && cross-env NODE_ENV=production UNITY_CDN_PURE=1 webpack --colors --bail",
|
"build:cdn-pure": "npm run clean && cross-env NODE_ENV=production UNITY_CDN_PURE=1 webpack --colors --bail",
|
||||||
"build:test": "npm run clean && cross-env NODE_ENV=production node scripts\\patch-config.js && webpack --colors --bail",
|
"build:test": "npm run clean && cross-env NODE_ENV=production node scripts\\patch-config.js && webpack --colors --bail",
|
||||||
"build:prod": "npm run clean && cross-env NODE_ENV=production node scripts\\patch-config.js && webpack --colors --bail",
|
"build:prod": "npm run clean && cross-env NODE_ENV=production node scripts\\patch-config.js && webpack --colors --bail",
|
||||||
"clean": "rimraf ./build && mkdirp build && rimraf ./dist && mkdirp dist",
|
"clean": "rimraf ./build && mkdirp build && rimraf ./dist && mkdirp dist && rimraf ./node_modules/.cache",
|
||||||
"deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"[skip ci] Build for $(git log --pretty=format:%H -n1)\"",
|
"deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"[skip ci] Build for $(git log --pretty=format:%H -n1)\"",
|
||||||
"prepublish": "node scripts/prepublish.mjs",
|
"prepublish": "node scripts/prepublish.mjs",
|
||||||
"prune": "./prune-gh-pages.sh",
|
"prune": "./prune-gh-pages.sh",
|
||||||
|
|||||||
0
scratch-gui/prune-gh-pages.sh
Normal file → Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 14px;
|
||||||
|
background: rgba(8, 8, 8, 0.92);
|
||||||
|
pointer-events: auto;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.28s ease-out, visibility 0.28s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: #f2f2f2;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barTrack {
|
||||||
|
width: min(72%, 280px);
|
||||||
|
height: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.16);
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barFill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #4fc3f7 0%, #29b6f6 100%);
|
||||||
|
border-radius: 999px;
|
||||||
|
transition: width 0.25s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percent {
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import styles from './game-loading-overlay.css';
|
||||||
|
|
||||||
|
const GameLoadingOverlay = ({ visible, progress }) => {
|
||||||
|
const pct = Math.max(0, Math.min(100, Math.round(Number(progress) || 0)));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.overlay}${visible ? '' : ` ${styles.hidden}`}`}
|
||||||
|
aria-hidden={!visible}
|
||||||
|
>
|
||||||
|
<div className={styles.text}>正在加载中</div>
|
||||||
|
<div className={styles.barTrack}>
|
||||||
|
<div className={styles.barFill} style={{ width: `${pct}%` }} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.percent}>{pct}%</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GameLoadingOverlay.propTypes = {
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
progress: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
GameLoadingOverlay.defaultProps = {
|
||||||
|
visible: true,
|
||||||
|
progress: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameLoadingOverlay;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const alreadyReady = () => typeof window !== 'undefined' && window.iscreate === 1;
|
||||||
|
|
||||||
|
const useGameLoadProgress = () => {
|
||||||
|
const [progress, setProgress] = useState(() => (alreadyReady() ? 100 : 0));
|
||||||
|
const [visible, setVisible] = useState(() => !alreadyReady());
|
||||||
|
const hidingRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible) return undefined;
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (window.iscreate === 1) {
|
||||||
|
if (!hidingRef.current) {
|
||||||
|
hidingRef.current = true;
|
||||||
|
setProgress(100);
|
||||||
|
setTimeout(() => setVisible(false), 280);
|
||||||
|
}
|
||||||
|
clearInterval(timer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setProgress(prev => (prev >= 90 && prev < 99 ? prev + 1 : prev));
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
const onUnityProgress = useCallback(unityProgress => {
|
||||||
|
const mapped = Math.round(Math.min(90, Number(unityProgress) * 90));
|
||||||
|
if (!Number.isFinite(mapped)) return;
|
||||||
|
setProgress(prev => (mapped > prev ? mapped : prev));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onUnityInstanceCreated = useCallback(() => {
|
||||||
|
setProgress(prev => Math.max(prev, 90));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
progress,
|
||||||
|
visible,
|
||||||
|
onUnityProgress,
|
||||||
|
onUnityInstanceCreated
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useGameLoadProgress;
|
||||||
@@ -11,9 +11,12 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
|
|||||||
// const friendToggle = new FriendToggle();
|
// const friendToggle = new FriendToggle();
|
||||||
window.RankBadgeGenerator = RankBadgeGenerator;
|
window.RankBadgeGenerator = RankBadgeGenerator;
|
||||||
window.CONFIG = CONFIG;
|
window.CONFIG = CONFIG;
|
||||||
|
import disableDevtool from 'disable-devtool';
|
||||||
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
||||||
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
||||||
import { debug } from 'scratch-vm/src/util/log.js';
|
import { debug } from 'scratch-vm/src/util/log.js';
|
||||||
|
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
|
||||||
|
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
|
||||||
|
|
||||||
const MAX_LEVELS = 500; // 最大关卡数据数量
|
const MAX_LEVELS = 500; // 最大关卡数据数量
|
||||||
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
||||||
@@ -47,6 +50,16 @@ const UnityComponent = function (props) {
|
|||||||
const [getBlockNum, setBlockNum] = useState(0);
|
const [getBlockNum, setBlockNum] = useState(0);
|
||||||
const [testDal, setTestDal] = useState(-1);
|
const [testDal, setTestDal] = useState(-1);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
const {
|
||||||
|
progress: gameLoadProgress,
|
||||||
|
visible: gameLoadVisible,
|
||||||
|
onUnityProgress,
|
||||||
|
onUnityInstanceCreated
|
||||||
|
} = useGameLoadProgress();
|
||||||
|
const onUnityProgressRef = useRef(onUnityProgress);
|
||||||
|
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
|
||||||
|
onUnityProgressRef.current = onUnityProgress;
|
||||||
|
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
|
||||||
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
||||||
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
||||||
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
||||||
@@ -547,29 +560,39 @@ const UnityComponent = function (props) {
|
|||||||
const [hasAlerted, setHasAlerted] = useState(false);
|
const [hasAlerted, setHasAlerted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initDisableDevtool({
|
if (CONFIG.disabledev) {
|
||||||
ondevtoolopen: () => {
|
disableDevtool({
|
||||||
|
ondevtoolopen: () => {
|
||||||
|
|
||||||
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
||||||
const originalOnBeforeUnload = window.onbeforeunload;
|
const originalOnBeforeUnload = window.onbeforeunload;
|
||||||
window.onbeforeunload = null;
|
window.onbeforeunload = null;
|
||||||
window.cppCodeStopRun();
|
window.cppCodeStopRun();
|
||||||
window.loadCppProject("");
|
window.loadCppProject("");
|
||||||
|
|
||||||
// 销毁 Unity 实例
|
// 销毁游戏实例(Cocos 兼容:Quit 可能不存在)
|
||||||
if (window.unityInstance) {
|
if (window.unityInstance) {
|
||||||
window.unityInstance.Quit().then(() => {
|
const quit = window.unityInstance.Quit;
|
||||||
window.unityInstance = null;
|
if (typeof quit === 'function') {
|
||||||
});
|
Promise.resolve(quit.call(window.unityInstance)).then(() => {
|
||||||
}
|
window.unityInstance = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.unityInstance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 延迟跳转到目标页面,确保销毁
|
// 延迟跳转到目标页面,确保销毁
|
||||||
window.location.href = localStorage.getItem('returnUrl');
|
window.location.href = localStorage.getItem('returnUrl');
|
||||||
// window.location.href = '/matchsum.html';
|
// window.location.href = '/matchsum.html';
|
||||||
// 恢复原始的 onbeforeunload 监听器
|
// 恢复原始的 onbeforeunload 监听器
|
||||||
window.onbeforeunload = originalOnBeforeUnload;
|
window.onbeforeunload = originalOnBeforeUnload;
|
||||||
}
|
},
|
||||||
});
|
interval: 500, // 检测间隔时间
|
||||||
|
clearLog: true, // 清除控制台的日志
|
||||||
|
disableMenu: true, // 禁用右键菜单
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -583,7 +606,7 @@ const UnityComponent = function (props) {
|
|||||||
//计算真实关卡位置
|
//计算真实关卡位置
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -693,7 +716,7 @@ const UnityComponent = function (props) {
|
|||||||
window.iscreate = 1;
|
window.iscreate = 1;
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber, gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -710,7 +733,8 @@ const UnityComponent = function (props) {
|
|||||||
// 延迟 500 毫秒后切换关卡,只执行一次
|
// 延迟 500 毫秒后切换关卡,只执行一次
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
|
||||||
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
} else {
|
} else {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
@@ -769,7 +793,8 @@ const UnityComponent = function (props) {
|
|||||||
|
|
||||||
const savedProject = window.cppCode;
|
const savedProject = window.cppCode;
|
||||||
setIsLockNextButton(true);
|
setIsLockNextButton(true);
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
|
||||||
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
const trialCount = localStorage.getItem('trial_count');
|
const trialCount = localStorage.getItem('trial_count');
|
||||||
|
|
||||||
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
||||||
@@ -1152,8 +1177,9 @@ const UnityComponent = function (props) {
|
|||||||
} else {
|
} else {
|
||||||
setIsNextButtonComplete(false);
|
setIsNextButtonComplete(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
|
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
CONFIG.checkGameStyle(realLvId);
|
CONFIG.checkGameStyle(realLvId);
|
||||||
@@ -1210,7 +1236,8 @@ const UnityComponent = function (props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
|
||||||
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
console.log("Retrying the current level...");
|
console.log("Retrying the current level...");
|
||||||
};
|
};
|
||||||
@@ -1218,46 +1245,87 @@ const UnityComponent = function (props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
let cancelled = false;
|
|
||||||
|
|
||||||
const bootUnity = async () => {
|
// if (canvas && !window.unityInstance) {
|
||||||
const config = await getUnityPlayerConfig();
|
const unityUrl = "/unity";
|
||||||
await loadUnityLoaderScript();
|
const buildUrl = "/unity/Build";
|
||||||
if (cancelled || !canvas) {
|
const cdnRoot = CONFIG.usecdn ? getUnityCdnRoot() : unityUrl;
|
||||||
return;
|
const cdnBuildRoot = CONFIG.usecdn ? getUnityCdnBuildRoot() : buildUrl;
|
||||||
}
|
let loaderUrl = CONFIG.usecdn
|
||||||
|
? `${cdnBuildRoot}/mstest5.loader.js`
|
||||||
|
: `${buildUrl}/mstest5.loader.js`;
|
||||||
|
let config = {
|
||||||
|
dataUrl: buildUrl + "/mstest5.data.br",
|
||||||
|
frameworkUrl: buildUrl + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: buildUrl + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
config = {
|
||||||
|
dataUrl: cdnBuildRoot + "/mstest5.data.br",
|
||||||
|
frameworkUrl: cdnBuildRoot + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: cdnBuildRoot + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: cdnRoot + "/StreamingAssets",
|
||||||
|
// dataUrl: buildUrl + "/Build.data.br",
|
||||||
|
// frameworkUrl: buildUrl + "/Build.framework.js.br",
|
||||||
|
// codeUrl: buildUrl + "/Build.wasm.br",
|
||||||
|
// streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
window.iscreate = 0;
|
window.iscreate = 0;
|
||||||
const unityInstance = await createUnityInstance(canvas, config, () => {});
|
const boot = () => {
|
||||||
if (cancelled) {
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
return;
|
onUnityProgressRef.current(progress);
|
||||||
}
|
}).then((unityInstance) => {
|
||||||
window.unityInstance = unityInstance;
|
onUnityInstanceCreatedRef.current();
|
||||||
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
window.unityInstance = unityInstance;
|
||||||
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
||||||
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
||||||
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
||||||
console.log("Unity 实例创建成功");
|
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
||||||
window.processData = processData;
|
console.log("Unity 实例创建成功");
|
||||||
window.processVehicleData = processVehicleData;
|
window.processData = processData;
|
||||||
window.externalResult = externalResult;
|
window.processVehicleData = processVehicleData;
|
||||||
window.externalLevelInfo = externalLevelInfo;
|
window.externalResult = externalResult;
|
||||||
let realLvId = 0;
|
window.externalLevelInfo = externalLevelInfo;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
//计算真实关卡位置
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
let realLvId = 0;
|
||||||
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
|
} else {
|
||||||
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
|
}
|
||||||
|
CONFIG.checkGameStyle(realLvId);
|
||||||
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
|
}).catch((message) => {
|
||||||
|
console.error(`Unity 实例创建失败: ${message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
prefetchLevelsDatabase(cdnRoot).then(boot).catch((err) => {
|
||||||
|
console.error('[unity-cdn] 关卡库预取失败', err);
|
||||||
|
boot();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
boot();
|
||||||
}
|
}
|
||||||
CONFIG.checkGameStyle(realLvId);
|
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bootUnity().catch((message) => {
|
document.body.appendChild(script);
|
||||||
console.error(`Unity 实例创建失败: ${message}`);
|
// } else {
|
||||||
});
|
// console.log("Unity 实例已经存在,跳过初始化");
|
||||||
|
// }
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 修改 formatDateTime 函数
|
// 修改 formatDateTime 函数
|
||||||
@@ -1284,6 +1352,7 @@ const UnityComponent = function (props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
||||||
|
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -771,40 +771,18 @@ export class CppCodeExecutor {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是函数调用(包括带参数的)
|
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));)
|
||||||
const functionCallMatch = line.match(/(\w+)\s*\(([^)]*)\)\s*;?/);
|
const customCall = this.parseCustomFunctionCall(line);
|
||||||
if (functionCallMatch) {
|
if (customCall) {
|
||||||
const functionName = functionCallMatch[1];
|
statements.push({
|
||||||
const argumentsStr = functionCallMatch[2];
|
type: 'function_call',
|
||||||
|
functionName: customCall.functionName,
|
||||||
// 排除C++关键字和控制结构
|
arguments: customCall.arguments,
|
||||||
const cppKeywords = [
|
lineNumber: isMainFunction ? this.getActualLineNumber(i) : i + 1,
|
||||||
'for', 'while', 'if', 'else', 'switch', 'case', 'return',
|
content: line // 保存原始内容用于行号映射
|
||||||
'int', 'string', 'bool', 'void', 'float', 'double', 'char'
|
});
|
||||||
];
|
i++;
|
||||||
|
continue;
|
||||||
// 检查是否是游戏API函数(这些不是自定义函数)
|
|
||||||
const gameApiFunctions = [
|
|
||||||
'playerMove', 'playerJump', 'turnLeft', 'turnRight',
|
|
||||||
'vehicleMove', 'turnVehicleLeft', 'turnVehicleRight',
|
|
||||||
'player_data', 'player_position', 'vehicle_position'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!cppKeywords.includes(functionName) &&
|
|
||||||
!gameApiFunctions.includes(functionName) &&
|
|
||||||
!line.includes('cout')) {
|
|
||||||
// 这是自定义函数调用
|
|
||||||
const parsedArguments = this.parseArguments(argumentsStr);
|
|
||||||
statements.push({
|
|
||||||
type: 'function_call',
|
|
||||||
functionName: functionName,
|
|
||||||
arguments: parsedArguments,
|
|
||||||
lineNumber: isMainFunction ? this.getActualLineNumber(i) : i + 1,
|
|
||||||
content: line // 保存原始内容用于行号映射
|
|
||||||
});
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析不同类型的语句
|
// 解析不同类型的语句
|
||||||
@@ -821,8 +799,8 @@ export class CppCodeExecutor {
|
|||||||
statements.push(whileLoop);
|
statements.push(whileLoop);
|
||||||
i = whileLoop.endIndex;
|
i = whileLoop.endIndex;
|
||||||
} else {
|
} else {
|
||||||
// 检查是否是break或continue语句
|
// 检查是否是break或continue或return语句
|
||||||
if (line === 'break;' || line === 'continue;') {
|
if (line === 'break;' || line === 'continue;' || line.startsWith('return')) {
|
||||||
statements.push({
|
statements.push({
|
||||||
type: 'control',
|
type: 'control',
|
||||||
subtype: line === 'break;' ? 'break' : 'continue',
|
subtype: line === 'break;' ? 'break' : 'continue',
|
||||||
@@ -866,21 +844,27 @@ export class CppCodeExecutor {
|
|||||||
let braceCount = 0;
|
let braceCount = 0;
|
||||||
let bodyStart = startIndex + 1;
|
let bodyStart = startIndex + 1;
|
||||||
let bodyEnd = startIndex + 1;
|
let bodyEnd = startIndex + 1;
|
||||||
|
let hasBrace = false;
|
||||||
|
|
||||||
for (let i = startIndex; i < lines.length; i++) {
|
for (let i = startIndex; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
debugLog(`🔍 检查行 ${i}: "${line.trim()}" (大括号计数: ${braceCount})`);
|
debugLog(`🔍 检查行 ${i}: "${line.trim()}" (大括号计数: ${braceCount})`);
|
||||||
if (line.includes('{')) braceCount++;
|
if (line.includes('{')) { braceCount++; hasBrace = true; }
|
||||||
if (line.includes('}')) braceCount--;
|
if (line.includes('}')) braceCount--;
|
||||||
if (braceCount === 0 && i > startIndex) {
|
if (hasBrace && braceCount === 0 && i > startIndex) {
|
||||||
bodyEnd = i;
|
bodyEnd = i;
|
||||||
debugLog(`🔍 找到循环体结束位置: ${bodyEnd}`);
|
debugLog(`🔍 找到循环体结束位置: ${bodyEnd}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!hasBrace && i > startIndex && line.trim() && !line.trim().startsWith('//')) {
|
||||||
|
bodyEnd = i + 1;
|
||||||
|
debugLog(`🔍 找到无大括号循环体结束位置: ${bodyEnd}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有找到结束大括号,可能是格式问题
|
// 如果没有找到结束大括号,可能是格式问题
|
||||||
if (bodyEnd === startIndex + 1) {
|
if (hasBrace && bodyEnd === startIndex + 1) {
|
||||||
debugLog(`⚠️ 警告:没有找到循环体结束大括号,检查代码格式`);
|
debugLog(`⚠️ 警告:没有找到循环体结束大括号,检查代码格式`);
|
||||||
debugLog(`⚠️ 当前所有行内容:`);
|
debugLog(`⚠️ 当前所有行内容:`);
|
||||||
lines.forEach((line, index) => {
|
lines.forEach((line, index) => {
|
||||||
@@ -914,7 +898,7 @@ export class CppCodeExecutor {
|
|||||||
increment,
|
increment,
|
||||||
body: bodyStatements,
|
body: bodyStatements,
|
||||||
lineNumber: isMainFunction ? this.getActualLineNumber(startIndex) : startIndex + 1,
|
lineNumber: isMainFunction ? this.getActualLineNumber(startIndex) : startIndex + 1,
|
||||||
endIndex: bodyEnd + 1
|
endIndex: hasBrace ? bodyEnd + 1 : bodyEnd
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,11 +919,26 @@ export class CppCodeExecutor {
|
|||||||
|
|
||||||
const condition = ifMatch[1];
|
const condition = ifMatch[1];
|
||||||
|
|
||||||
|
// 最小化修改:如果if同行包含执行语句,把它拆分拆到下一行
|
||||||
|
const afterIf = ifLine.substring(ifMatch.index + ifMatch[0].length).trim();
|
||||||
|
if (afterIf && afterIf !== '{') {
|
||||||
|
if (afterIf.startsWith('{')) {
|
||||||
|
lines[startIndex] = ifLine.substring(0, ifLine.lastIndexOf(afterIf)) + '{';
|
||||||
|
const afterBrace = afterIf.substring(1).trim();
|
||||||
|
if (afterBrace) lines.splice(startIndex + 1, 0, afterBrace);
|
||||||
|
} else {
|
||||||
|
lines[startIndex] = ifLine.substring(0, ifLine.lastIndexOf(afterIf)).trim();
|
||||||
|
lines.splice(startIndex + 1, 0, afterIf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 找到if语句体
|
// 找到if语句体
|
||||||
let braceCount = 0;
|
let braceCount = 0;
|
||||||
let bodyStart = startIndex + 1;
|
let bodyStart = startIndex + 1;
|
||||||
let bodyEnd = startIndex + 1;
|
let bodyEnd = startIndex + 1;
|
||||||
let ifClosingLine = ''; // 保存if语句的结束行,可能包含else
|
let ifClosingLine = ''; // 保存if语句的结束行,可能包含else
|
||||||
|
let hasBrace = false;
|
||||||
|
|
||||||
for (let i = startIndex; i < lines.length; i++) {
|
for (let i = startIndex; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
@@ -954,15 +953,31 @@ export class CppCodeExecutor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.includes('{')) braceCount++;
|
if (line.includes('{')) { braceCount++; hasBrace = true; }
|
||||||
if (line.includes('}')) braceCount--;
|
if (line.includes('}')) braceCount--;
|
||||||
if (braceCount === 0 && i > startIndex) {
|
|
||||||
|
if (hasBrace && braceCount === 0 && i > startIndex) {
|
||||||
// 普通的}结束
|
// 普通的}结束
|
||||||
bodyEnd = i;
|
bodyEnd = i;
|
||||||
ifClosingLine = trimmedLine;
|
ifClosingLine = trimmedLine;
|
||||||
debugLog(`🔍 if语句结束位置: 行${i}, 内容: "${ifClosingLine}", 是否包含else: ${ifClosingLine.includes('else')}`);
|
debugLog(`🔍 if语句结束位置: 行${i}, 内容: "${ifClosingLine}", 是否包含else: ${ifClosingLine.includes('else')}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasBrace && i > startIndex && trimmedLine && !trimmedLine.startsWith('//')) {
|
||||||
|
bodyEnd = i + 1;
|
||||||
|
// 尝试寻找后续的else
|
||||||
|
for (let j = i + 1; j < lines.length; j++) {
|
||||||
|
const nextLine = lines[j].trim();
|
||||||
|
if (!nextLine || nextLine.startsWith('//')) continue;
|
||||||
|
if (nextLine.startsWith('else')) {
|
||||||
|
ifClosingLine = nextLine;
|
||||||
|
debugLog(`🔍 无大括号单行语句后发现else: 行${j}, 内容: "${ifClosingLine}"`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bodyLines = lines.slice(bodyStart, bodyEnd);
|
let bodyLines = lines.slice(bodyStart, bodyEnd);
|
||||||
@@ -984,7 +999,7 @@ export class CppCodeExecutor {
|
|||||||
// 解析else if 和 else 分支
|
// 解析else if 和 else 分支
|
||||||
const elseIfBranches = [];
|
const elseIfBranches = [];
|
||||||
let elseStatements = [];
|
let elseStatements = [];
|
||||||
let currentIndex = bodyEnd + 1;
|
let currentIndex = hasBrace ? bodyEnd + 1 : bodyEnd;
|
||||||
|
|
||||||
// 首先检查if结束行是否包含else(处理 } else { 的情况)
|
// 首先检查if结束行是否包含else(处理 } else { 的情况)
|
||||||
if (ifClosingLine.includes('else')) {
|
if (ifClosingLine.includes('else')) {
|
||||||
@@ -1110,15 +1125,20 @@ export class CppCodeExecutor {
|
|||||||
let elseIfBraceCount = 0;
|
let elseIfBraceCount = 0;
|
||||||
let elseIfBodyStart = currentIndex + 1;
|
let elseIfBodyStart = currentIndex + 1;
|
||||||
let elseIfBodyEnd = currentIndex + 1;
|
let elseIfBodyEnd = currentIndex + 1;
|
||||||
|
let elseIfHasBrace = false;
|
||||||
|
|
||||||
for (let i = currentIndex; i < lines.length; i++) {
|
for (let i = currentIndex; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
if (line.includes('{')) elseIfBraceCount++;
|
if (line.includes('{')) { elseIfBraceCount++; elseIfHasBrace = true; }
|
||||||
if (line.includes('}')) elseIfBraceCount--;
|
if (line.includes('}')) elseIfBraceCount--;
|
||||||
if (elseIfBraceCount === 0 && i > currentIndex) {
|
if (elseIfHasBrace && elseIfBraceCount === 0 && i > currentIndex) {
|
||||||
elseIfBodyEnd = i;
|
elseIfBodyEnd = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!elseIfHasBrace && i > currentIndex && line.trim() && !line.trim().startsWith('//')) {
|
||||||
|
elseIfBodyEnd = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const elseIfBodyLines = lines.slice(elseIfBodyStart, elseIfBodyEnd);
|
const elseIfBodyLines = lines.slice(elseIfBodyStart, elseIfBodyEnd);
|
||||||
@@ -1131,7 +1151,7 @@ export class CppCodeExecutor {
|
|||||||
|
|
||||||
debugLog(`🔍 解析else if语句体,条件: ${elseIfCondition}, 行数: ${elseIfBodyLines.length}, 语句数: ${elseIfStatements.length}`);
|
debugLog(`🔍 解析else if语句体,条件: ${elseIfCondition}, 行数: ${elseIfBodyLines.length}, 语句数: ${elseIfStatements.length}`);
|
||||||
|
|
||||||
currentIndex = elseIfBodyEnd + 1;
|
currentIndex = elseIfHasBrace ? elseIfBodyEnd + 1 : elseIfBodyEnd;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1143,15 +1163,20 @@ export class CppCodeExecutor {
|
|||||||
let elseBraceCount = 0;
|
let elseBraceCount = 0;
|
||||||
let elseBodyStart = currentIndex + 1;
|
let elseBodyStart = currentIndex + 1;
|
||||||
let elseBodyEnd = currentIndex + 1;
|
let elseBodyEnd = currentIndex + 1;
|
||||||
|
let elseHasBrace = false;
|
||||||
|
|
||||||
for (let i = currentIndex; i < lines.length; i++) {
|
for (let i = currentIndex; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
if (line.includes('{')) elseBraceCount++;
|
if (line.includes('{')) { elseBraceCount++; elseHasBrace = true; }
|
||||||
if (line.includes('}')) elseBraceCount--;
|
if (line.includes('}')) elseBraceCount--;
|
||||||
if (elseBraceCount === 0 && i > currentIndex) {
|
if (elseHasBrace && elseBraceCount === 0 && i > currentIndex) {
|
||||||
elseBodyEnd = i;
|
elseBodyEnd = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!elseHasBrace && i > currentIndex && line.trim() && !line.trim().startsWith('//')) {
|
||||||
|
elseBodyEnd = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let elseBodyLines = lines.slice(elseBodyStart, elseBodyEnd);
|
let elseBodyLines = lines.slice(elseBodyStart, elseBodyEnd);
|
||||||
@@ -1173,7 +1198,7 @@ export class CppCodeExecutor {
|
|||||||
|
|
||||||
debugLog(`🔍 解析else语句体,行数: ${elseBodyLines.length}, 语句数: ${elseStatements.length}`);
|
debugLog(`🔍 解析else语句体,行数: ${elseBodyLines.length}, 语句数: ${elseStatements.length}`);
|
||||||
|
|
||||||
currentIndex = elseBodyEnd + 1;
|
currentIndex = elseHasBrace ? elseBodyEnd + 1 : elseBodyEnd;
|
||||||
break; // else分支是最后一个,不再继续
|
break; // else分支是最后一个,不再继续
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,15 +1233,20 @@ export class CppCodeExecutor {
|
|||||||
let braceCount = 0;
|
let braceCount = 0;
|
||||||
let bodyStart = startIndex + 1;
|
let bodyStart = startIndex + 1;
|
||||||
let bodyEnd = startIndex + 1;
|
let bodyEnd = startIndex + 1;
|
||||||
|
let hasBrace = false;
|
||||||
|
|
||||||
for (let i = startIndex; i < lines.length; i++) {
|
for (let i = startIndex; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
if (line.includes('{')) braceCount++;
|
if (line.includes('{')) { braceCount++; hasBrace = true; }
|
||||||
if (line.includes('}')) braceCount--;
|
if (line.includes('}')) braceCount--;
|
||||||
if (braceCount === 0 && i > startIndex) {
|
if (hasBrace && braceCount === 0 && i > startIndex) {
|
||||||
bodyEnd = i;
|
bodyEnd = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!hasBrace && i > startIndex && line.trim() && !line.trim().startsWith('//')) {
|
||||||
|
bodyEnd = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bodyLines = lines.slice(bodyStart, bodyEnd);
|
let bodyLines = lines.slice(bodyStart, bodyEnd);
|
||||||
@@ -1239,7 +1269,7 @@ export class CppCodeExecutor {
|
|||||||
condition,
|
condition,
|
||||||
body: bodyStatements,
|
body: bodyStatements,
|
||||||
lineNumber: isMainFunction ? this.getActualLineNumber(startIndex) : startIndex + 1,
|
lineNumber: isMainFunction ? this.getActualLineNumber(startIndex) : startIndex + 1,
|
||||||
endIndex: bodyEnd + 1
|
endIndex: hasBrace ? bodyEnd + 1 : bodyEnd
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,7 +1289,7 @@ export class CppCodeExecutor {
|
|||||||
|
|
||||||
// 高亮当前执行行
|
// 高亮当前执行行
|
||||||
if (this.setHighlightedLine) {
|
if (this.setHighlightedLine) {
|
||||||
// this.setHighlightedLine(statement.lineNumber);
|
this.setHighlightedLine(statement.lineNumber);
|
||||||
}
|
}
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
|
|
||||||
@@ -1282,6 +1312,9 @@ export class CppCodeExecutor {
|
|||||||
await this.executeFunctionCall(statement);
|
await this.executeFunctionCall(statement);
|
||||||
break;
|
break;
|
||||||
case 'control':
|
case 'control':
|
||||||
|
if (statement.content.startsWith('return')) {
|
||||||
|
throw new Error('RETURN_INTERRUPT');
|
||||||
|
}
|
||||||
// break和continue在各自的循环中处理,这里只记录
|
// break和continue在各自的循环中处理,这里只记录
|
||||||
debugLog(`🔄 控制语句: ${statement.subtype}`);
|
debugLog(`🔄 控制语句: ${statement.subtype}`);
|
||||||
break;
|
break;
|
||||||
@@ -1291,29 +1324,16 @@ export class CppCodeExecutor {
|
|||||||
async executeSimpleStatement(content) {
|
async executeSimpleStatement(content) {
|
||||||
debugLog(`🔍 执行简单语句: ${content}`);
|
debugLog(`🔍 执行简单语句: ${content}`);
|
||||||
|
|
||||||
// 检查是否是函数调用(包括带参数的)
|
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));)
|
||||||
const functionCallMatch = content.match(/(\w+)\s*\(([^)]*)\)\s*;?/);
|
const customCall = this.parseCustomFunctionCall(content);
|
||||||
if (functionCallMatch) {
|
if (customCall && this.functions.has(customCall.functionName)) {
|
||||||
const functionName = functionCallMatch[1];
|
console.log(`📞 调用自定义函数: ${customCall.functionName}(${customCall.arguments.join(', ')})`);
|
||||||
const argumentsStr = functionCallMatch[2];
|
await this.executeFunctionCall({
|
||||||
|
type: 'function_call',
|
||||||
// 排除C++关键字和控制结构
|
functionName: customCall.functionName,
|
||||||
const cppKeywords = [
|
arguments: customCall.arguments
|
||||||
'for', 'while', 'if', 'else', 'switch', 'case', 'return',
|
});
|
||||||
'int', 'string', 'bool', 'void', 'float', 'double', 'char'
|
return;
|
||||||
];
|
|
||||||
|
|
||||||
// 检查是否是自定义函数(排除关键字)
|
|
||||||
if (!cppKeywords.includes(functionName) && this.functions.has(functionName)) {
|
|
||||||
console.log(`📞 调用自定义函数: ${functionName}(${argumentsStr})`);
|
|
||||||
const parsedArguments = this.parseArguments(argumentsStr);
|
|
||||||
await this.executeFunctionCall({
|
|
||||||
type: 'function_call',
|
|
||||||
functionName: functionName,
|
|
||||||
arguments: parsedArguments
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自增/自减语句 (如 a++; 或 ++a;)
|
// 自增/自减语句 (如 a++; 或 ++a;)
|
||||||
@@ -1792,8 +1812,12 @@ export class CppCodeExecutor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建函数作用域(保存当前变量状态)
|
// 最小改动:防递归数据污染,只备份会被覆盖的同名参数变量
|
||||||
const savedVariables = new Map(this.variables);
|
const savedParams = new Map();
|
||||||
|
for (let i = 0; i < functionInfo.parameters.length; i++) {
|
||||||
|
const pName = functionInfo.parameters[i].name;
|
||||||
|
if (this.variables.has(pName)) savedParams.set(pName, this.variables.get(pName));
|
||||||
|
}
|
||||||
|
|
||||||
// 设置函数参数变量
|
// 设置函数参数变量
|
||||||
for (let i = 0; i < functionInfo.parameters.length; i++) {
|
for (let i = 0; i < functionInfo.parameters.length; i++) {
|
||||||
@@ -1808,32 +1832,42 @@ export class CppCodeExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 执行函数体中的所有语句
|
// 执行函数体中的所有语句
|
||||||
for (const functionStatement of functionInfo.body) {
|
try {
|
||||||
if (this.shouldStop()) break;
|
for (const functionStatement of functionInfo.body) {
|
||||||
|
if (this.shouldStop()) break;
|
||||||
|
|
||||||
// 使用预先计算好的精确行号,如果没有则计算行号
|
// 使用预先计算好的精确行号,如果没有则计算行号
|
||||||
let adjustedStatement;
|
let adjustedStatement;
|
||||||
if (functionStatement.exactLineNumber) {
|
if (functionStatement.exactLineNumber) {
|
||||||
adjustedStatement = {
|
adjustedStatement = {
|
||||||
...functionStatement,
|
...functionStatement,
|
||||||
lineNumber: functionStatement.exactLineNumber
|
lineNumber: functionStatement.exactLineNumber
|
||||||
};
|
};
|
||||||
console.log(`🎯 使用函数内精确行号: "${functionStatement.content}" -> 第${functionStatement.exactLineNumber}行 (函数: ${functionName})`);
|
console.log(`🎯 使用函数内精确行号: "${functionStatement.content}" -> 第${functionStatement.exactLineNumber}行 (函数: ${functionName})`);
|
||||||
} else {
|
} else {
|
||||||
// 需要更精确地计算函数内语句的实际行号
|
// 需要更精确地计算函数内语句的实际行号
|
||||||
const actualLineNumber = this.calculateActualLineNumber(functionName, functionStatement);
|
const actualLineNumber = this.calculateActualLineNumber(functionName, functionStatement);
|
||||||
adjustedStatement = {
|
adjustedStatement = {
|
||||||
...functionStatement,
|
...functionStatement,
|
||||||
lineNumber: actualLineNumber
|
lineNumber: actualLineNumber
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.executeStatement(adjustedStatement);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
await this.executeStatement(adjustedStatement);
|
if (e.message !== 'RETURN_INTERRUPT') throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复函数调用前的变量状态(简单的作用域实现)
|
// 最小改动:仅恢复递归调用前的参数状态,保留全局变量的修改
|
||||||
// 注意:这是简化版本,实际C++的作用域更复杂
|
for (let i = 0; i < functionInfo.parameters.length; i++) {
|
||||||
this.variables = savedVariables;
|
const pName = functionInfo.parameters[i].name;
|
||||||
|
if (savedParams.has(pName)) {
|
||||||
|
this.variables.set(pName, savedParams.get(pName));
|
||||||
|
} else {
|
||||||
|
this.variables.delete(pName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`✅ 函数${functionName}执行完成`);
|
console.log(`✅ 函数${functionName}执行完成`);
|
||||||
|
|
||||||
@@ -2089,7 +2123,46 @@ export class CppCodeExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析函数调用参数
|
// 解析自定义函数调用,正确处理实参里的嵌套括号(如 f((n + 1)))
|
||||||
|
parseCustomFunctionCall(content) {
|
||||||
|
if (!content) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cppKeywords = [
|
||||||
|
'for', 'while', 'if', 'else', 'switch', 'case', 'return',
|
||||||
|
'int', 'string', 'bool', 'void', 'float', 'double', 'char'
|
||||||
|
];
|
||||||
|
const gameApiFunctions = [
|
||||||
|
'playerMove', 'playerJump', 'turnLeft', 'turnRight',
|
||||||
|
'vehicleMove', 'turnVehicleLeft', 'turnVehicleRight',
|
||||||
|
'player_data', 'player_position', 'vehicle_position'
|
||||||
|
];
|
||||||
|
|
||||||
|
const nameMatch = content.match(/(\w+)\s*\(/);
|
||||||
|
if (!nameMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const functionName = nameMatch[1];
|
||||||
|
if (cppKeywords.includes(functionName) ||
|
||||||
|
gameApiFunctions.includes(functionName) ||
|
||||||
|
content.includes('cout')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedArguments = this.extractFunctionArguments(content, functionName);
|
||||||
|
if (parsedArguments === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
functionName,
|
||||||
|
arguments: parsedArguments
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析函数调用参数,按逗号分割时忽略括号内的逗号
|
||||||
parseArguments(argumentsStr) {
|
parseArguments(argumentsStr) {
|
||||||
const argumentsList = [];
|
const argumentsList = [];
|
||||||
|
|
||||||
@@ -2097,14 +2170,29 @@ export class CppCodeExecutor {
|
|||||||
return argumentsList; // 无参数调用
|
return argumentsList; // 无参数调用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分割参数,支持多个参数
|
let currentArg = '';
|
||||||
const argParts = argumentsStr.split(',');
|
let nestedParens = 0;
|
||||||
|
|
||||||
for (const argPart of argParts) {
|
for (let j = 0; j < argumentsStr.length; j++) {
|
||||||
const trimmed = argPart.trim();
|
const char = argumentsStr[j];
|
||||||
if (trimmed) {
|
|
||||||
argumentsList.push(trimmed);
|
if (char === '(') {
|
||||||
|
nestedParens++;
|
||||||
|
} else if (char === ')') {
|
||||||
|
nestedParens--;
|
||||||
|
} else if (char === ',' && nestedParens === 0) {
|
||||||
|
if (currentArg.trim()) {
|
||||||
|
argumentsList.push(currentArg.trim());
|
||||||
|
}
|
||||||
|
currentArg = '';
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentArg += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentArg.trim()) {
|
||||||
|
argumentsList.push(currentArg.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
return argumentsList;
|
return argumentsList;
|
||||||
@@ -2332,28 +2420,10 @@ export class CppCodeExecutor {
|
|||||||
// 处理一元运算符(负号)
|
// 处理一元运算符(负号)
|
||||||
expr = this.handleUnaryOperators(expr);
|
expr = this.handleUnaryOperators(expr);
|
||||||
|
|
||||||
// 按运算优先级解析:乘法、除法和模运算优先
|
// 按正确的运算优先级解析:
|
||||||
if (expr.includes('*') || expr.includes('/') || expr.includes('%')) {
|
// 加法和减法是最低优先级,因此在此处最先切分。
|
||||||
return this.parseMultiplicationDivision(expr);
|
// 切分后的每一项将交给 parseAdditionSubtraction,在其中处理更高级别的乘除法。
|
||||||
}
|
return this.parseAdditionSubtraction(expr);
|
||||||
|
|
||||||
// 然后处理加法和减法
|
|
||||||
if (expr.includes('+') || expr.includes('-')) {
|
|
||||||
return this.parseAdditionSubtraction(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是单个值
|
|
||||||
if (/^-?\d+$/.test(expr)) {
|
|
||||||
return parseInt(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 变量查找
|
|
||||||
if (this.variables.has(expr)) {
|
|
||||||
return this.variables.get(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog(`⚠️ 无法识别的表达式: ${expr}, 返回默认值 0`);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugLog(`⚠️ 表达式解析错误: ${expr}, 错误: ${error.message}`);
|
debugLog(`⚠️ 表达式解析错误: ${expr}, 错误: ${error.message}`);
|
||||||
@@ -2490,20 +2560,49 @@ export class CppCodeExecutor {
|
|||||||
// 计算单个标记的值
|
// 计算单个标记的值
|
||||||
evaluateToken(token) {
|
evaluateToken(token) {
|
||||||
token = token.trim();
|
token = token.trim();
|
||||||
|
if (!token) return 0;
|
||||||
|
|
||||||
|
// 如果包含乘除法或模运算,交给乘除法解析器处理
|
||||||
|
if (token.includes('*') || token.includes('/') || token.includes('%')) {
|
||||||
|
return this.parseMultiplicationDivision(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 布尔常量
|
||||||
|
if (token === 'true') return 1;
|
||||||
|
if (token === 'false') return 0;
|
||||||
|
|
||||||
// 数字
|
// 数字
|
||||||
if (/^-?\d+$/.test(token)) {
|
if (/^-?\d+$/.test(token)) {
|
||||||
return parseInt(token);
|
return parseInt(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 变量
|
// 变量查找
|
||||||
if (this.variables.has(token)) {
|
if (this.variables.has(token)) {
|
||||||
return this.variables.get(token);
|
return this.variables.get(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果包含乘除法或模运算,递归处理
|
// 对象属性访问 (如 pos.x, pos.y, pos.z)
|
||||||
if (token.includes('*') || token.includes('/') || token.includes('%')) {
|
if (token.includes('.')) {
|
||||||
return this.parseMultiplicationDivision(token);
|
const [objName, propName] = token.split('.');
|
||||||
|
if (this.variables.has(objName)) {
|
||||||
|
const obj = this.variables.get(objName);
|
||||||
|
if (obj && typeof obj === 'object' && obj[propName] !== undefined) {
|
||||||
|
return obj[propName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏API调用
|
||||||
|
if (token.includes('player_data(')) {
|
||||||
|
const match = token.match(/player_data\s*\(\s*"(.+?)"\s*\)/);
|
||||||
|
if (match) return window.cppGameAPI.player_data(match[1]);
|
||||||
|
}
|
||||||
|
if (token.includes('player_position(')) {
|
||||||
|
const match = token.match(/player_position\s*\(\s*"(.+?)"\s*\)/);
|
||||||
|
if (match) return window.cppGameAPI.player_position(match[1]);
|
||||||
|
}
|
||||||
|
if (token.includes('vehicle_position()')) {
|
||||||
|
return window.cppGameAPI.vehicle_position();
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(`⚠️ 无法解析标记: ${token}`);
|
debugLog(`⚠️ 无法解析标记: ${token}`);
|
||||||
@@ -2544,35 +2643,8 @@ export class CppCodeExecutor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析参数
|
// 解析参数,按逗号分割时忽略括号内的逗号
|
||||||
if (!argsString.trim()) {
|
const args = this.parseArguments(argsString);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按逗号分割参数,但要考虑括号嵌套
|
|
||||||
const args = [];
|
|
||||||
let currentArg = '';
|
|
||||||
let nestedParens = 0;
|
|
||||||
|
|
||||||
for (let j = 0; j < argsString.length; j++) {
|
|
||||||
const char = argsString[j];
|
|
||||||
|
|
||||||
if (char === '(') {
|
|
||||||
nestedParens++;
|
|
||||||
} else if (char === ')') {
|
|
||||||
nestedParens--;
|
|
||||||
} else if (char === ',' && nestedParens === 0) {
|
|
||||||
args.push(currentArg.trim());
|
|
||||||
currentArg = '';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentArg += char;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentArg.trim()) {
|
|
||||||
args.push(currentArg.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog(`🔍 函数${functionName}提取到的参数:`, args);
|
debugLog(`🔍 函数${functionName}提取到的参数:`, args);
|
||||||
return args;
|
return args;
|
||||||
|
|||||||
@@ -32,11 +32,16 @@ const StageWrapperCppClang = () => {
|
|||||||
const [wasmModule, setWasmModule] = useState(null);
|
const [wasmModule, setWasmModule] = useState(null);
|
||||||
const [syntaxErrorModalVisible, setSyntaxErrorModalVisible] = useState(false);
|
const [syntaxErrorModalVisible, setSyntaxErrorModalVisible] = useState(false);
|
||||||
const [syntaxErrors, setSyntaxErrors] = useState([]);
|
const [syntaxErrors, setSyntaxErrors] = useState([]);
|
||||||
const [overlayActive, setOverlayActive] = useState(false);
|
const [apiSelectedCategory, setApiSelectedCategory] = useState('player');
|
||||||
const [apiSelectedCategory, setApiSelectedCategory] = useState('syntax');
|
|
||||||
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
|
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
|
||||||
|
const [overlayActive, setOverlayActive] = useState(false);
|
||||||
|
const MAX_CODE_LINES = 500;
|
||||||
|
|
||||||
// Unity 加载完成前,API 面板显示加载态并屏蔽点击(5 秒兜底自动解除)
|
// CodeMirror编辑器引用
|
||||||
|
const editorRef = useRef(null);
|
||||||
|
const lastValidCppCodeRef = useRef(cppCode);
|
||||||
|
|
||||||
|
// 游戏引擎加载完成前,API 面板显示加载态并屏蔽点击(5 秒兜底自动解除)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isApiPanelReady) return undefined;
|
if (isApiPanelReady) return undefined;
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@@ -54,11 +59,6 @@ const StageWrapperCppClang = () => {
|
|||||||
clearTimeout(fallback);
|
clearTimeout(fallback);
|
||||||
};
|
};
|
||||||
}, [isApiPanelReady]);
|
}, [isApiPanelReady]);
|
||||||
const MAX_CODE_LINES = 500;
|
|
||||||
|
|
||||||
// CodeMirror编辑器引用
|
|
||||||
const editorRef = useRef(null);
|
|
||||||
const lastValidCppCodeRef = useRef(cppCode);
|
|
||||||
|
|
||||||
// API参考数据
|
// API参考数据
|
||||||
const apiReference = {
|
const apiReference = {
|
||||||
@@ -1112,7 +1112,7 @@ const StageWrapperCppClang = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// API参考面板
|
// API参考面板(与 Python 一样常驻在编辑区左侧)
|
||||||
const ApiDrawer = () => {
|
const ApiDrawer = () => {
|
||||||
const selectedCategory = apiSelectedCategory;
|
const selectedCategory = apiSelectedCategory;
|
||||||
const setSelectedCategory = setApiSelectedCategory;
|
const setSelectedCategory = setApiSelectedCategory;
|
||||||
@@ -1120,12 +1120,10 @@ const StageWrapperCppClang = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.apiDrawer}>
|
<div className={styles.apiDrawer}>
|
||||||
<div className={styles.apiDrawerHeader}>
|
<div className={styles.apiDrawerHeader}>
|
||||||
<h3>🔧 API参考</h3>
|
<h3>🔧 C++ API参考</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 抽屉内容 */}
|
|
||||||
<div className={styles.apiDrawerContent}>
|
<div className={styles.apiDrawerContent}>
|
||||||
{/* 分类导航 */}
|
|
||||||
<div className={styles.apiDrawerCategories}>
|
<div className={styles.apiDrawerCategories}>
|
||||||
{Object.entries(apiReference).map(([key, category]) => (
|
{Object.entries(apiReference).map(([key, category]) => (
|
||||||
<button
|
<button
|
||||||
@@ -1139,7 +1137,6 @@ const StageWrapperCppClang = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* API列表 */}
|
|
||||||
<div className={styles.apiDrawerList}>
|
<div className={styles.apiDrawerList}>
|
||||||
{apiReference[selectedCategory]?.apis.map((api, index) => (
|
{apiReference[selectedCategory]?.apis.map((api, index) => (
|
||||||
<div key={index} className={styles.apiDrawerItem}>
|
<div key={index} className={styles.apiDrawerItem}>
|
||||||
@@ -1163,12 +1160,13 @@ const StageWrapperCppClang = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isApiPanelReady && (
|
<div
|
||||||
<div className={styles.apiDrawerLoading}>
|
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
|
||||||
<div className={styles.apiDrawerLoadingSpinner}></div>
|
aria-hidden={isApiPanelReady}
|
||||||
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中,完成后即可插入代码…</div>
|
>
|
||||||
</div>
|
<div className={styles.apiDrawerLoadingSpinner}></div>
|
||||||
)}
|
<div className={styles.apiDrawerLoadingText}>正在加载中,完成后即可插入代码…</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1189,6 +1187,12 @@ const StageWrapperCppClang = () => {
|
|||||||
// 移除snippet标记,转换为普通文本
|
// 移除snippet标记,转换为普通文本
|
||||||
insertCode = code.replace(/#{([^}]*)}/g, '$1');
|
insertCode = code.replace(/#{([^}]*)}/g, '$1');
|
||||||
}
|
}
|
||||||
|
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
|
||||||
|
insertCode = '\n' + insertCode;
|
||||||
|
}
|
||||||
|
if (!insertCode.endsWith('\n')) {
|
||||||
|
insertCode += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
// 插入代码
|
// 插入代码
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
@@ -1202,7 +1206,6 @@ const StageWrapperCppClang = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 聚焦编辑器
|
|
||||||
view.focus();
|
view.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1213,141 +1216,138 @@ const StageWrapperCppClang = () => {
|
|||||||
<ErrorModal />
|
<ErrorModal />
|
||||||
<CompilationModal />
|
<CompilationModal />
|
||||||
<SyntaxErrorModal />
|
<SyntaxErrorModal />
|
||||||
<div
|
<div className={styles.editorWithApi}>
|
||||||
className={styles.editorWithApi}
|
{ApiDrawer()}
|
||||||
onWheel={(e) => e.stopPropagation()}
|
<div
|
||||||
>
|
className={styles.editorPane}
|
||||||
<ApiDrawer />
|
onWheel={(e) => e.stopPropagation()}
|
||||||
<div className={styles.editorPane}>
|
>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#010101',
|
||||||
|
overflow: 'auto',
|
||||||
|
fontSize: '20px'
|
||||||
|
}}
|
||||||
|
value={cppCode}
|
||||||
|
theme={abcdef}
|
||||||
|
extensions={[
|
||||||
|
cpp(),
|
||||||
|
createCppCompletions(),
|
||||||
|
keymap.of(completionKeymap),
|
||||||
|
keymap.of(snippetKeymap),
|
||||||
|
classname({
|
||||||
|
add: (lineNumber) => {
|
||||||
|
if (lineNumber === highlightedLine) {
|
||||||
|
return 'highlighted-line';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
EditorView.theme({
|
||||||
|
'&': {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
'.cm-editor': {
|
||||||
|
height: '100%',
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
'.cm-scroller': {
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto'
|
||||||
|
},
|
||||||
|
'.cm-content': {
|
||||||
|
minHeight: '100%',
|
||||||
|
padding: '10px'
|
||||||
|
},
|
||||||
|
'.highlighted-line': {
|
||||||
|
backgroundColor: '#ffeb3b80',
|
||||||
|
borderLeft: '4px solid #ffeb3b',
|
||||||
|
borderRight: '2px solid #ffeb3b',
|
||||||
|
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
|
||||||
|
animation: 'pulse 1s ease-in-out'
|
||||||
|
},
|
||||||
|
'@keyframes pulse': {
|
||||||
|
'0%': { backgroundColor: '#ffeb3b80' },
|
||||||
|
'50%': { backgroundColor: '#ffeb3ba0' },
|
||||||
|
'100%': { backgroundColor: '#ffeb3b80' }
|
||||||
|
},
|
||||||
|
fontSize: "20px"
|
||||||
|
})
|
||||||
|
]}
|
||||||
|
basicSetup={{
|
||||||
|
lineNumbers: true,
|
||||||
|
highlightActiveLine: true,
|
||||||
|
highlightSelectionMatches: true,
|
||||||
|
searchKeymap: true,
|
||||||
|
autocompletion: true
|
||||||
|
}}
|
||||||
|
onChange={(value) => {
|
||||||
|
const lines = value.split(/\r\n|\r|\n/);
|
||||||
|
if (lines.length > MAX_CODE_LINES) {
|
||||||
|
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
|
||||||
|
setErrorModalVisible(true);
|
||||||
|
if (editorRef.current && editorRef.current.view) {
|
||||||
|
const view = editorRef.current.view;
|
||||||
|
view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: view.state.doc.length,
|
||||||
|
insert: lastValidCppCodeRef.current
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastValidCppCodeRef.current = value;
|
||||||
|
setCppCode(value);
|
||||||
|
updateBlockCount(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{overlayActive && (
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
position: 'absolute',
|
||||||
height: '100%',
|
|
||||||
position: 'relative',
|
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
backgroundColor: '#010101',
|
right: 0,
|
||||||
fontSize: '20px'
|
bottom: 0,
|
||||||
|
background: 'rgba(0,0,0,0.25)',
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
zIndex: 10,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
userSelect: 'none'
|
||||||
}}
|
}}
|
||||||
value={cppCode}
|
onWheel={(e) => e.preventDefault()}
|
||||||
theme={abcdef}
|
onTouchMove={(e) => e.preventDefault()}
|
||||||
extensions={[
|
onClick={(e) => e.preventDefault()}
|
||||||
cpp(),
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
createCppCompletions(),
|
>
|
||||||
keymap.of(completionKeymap),
|
<div style={{
|
||||||
keymap.of(snippetKeymap),
|
color: '#fff',
|
||||||
classname({
|
fontSize: '28px',
|
||||||
add: (lineNumber) => {
|
fontWeight: 'bold',
|
||||||
if (lineNumber === highlightedLine) {
|
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
|
||||||
return 'highlighted-line';
|
letterSpacing: '4px',
|
||||||
}
|
animation: 'blink 1.5s ease-in-out infinite'
|
||||||
return null;
|
}}>
|
||||||
}
|
⏳ 执行中...
|
||||||
}),
|
|
||||||
EditorView.theme({
|
|
||||||
'&': {
|
|
||||||
height: '100%',
|
|
||||||
width: '100%'
|
|
||||||
},
|
|
||||||
'.cm-editor': {
|
|
||||||
height: '100%',
|
|
||||||
width: '100%'
|
|
||||||
},
|
|
||||||
'.cm-scroller': {
|
|
||||||
height: '100%',
|
|
||||||
overflow: 'auto'
|
|
||||||
},
|
|
||||||
'.cm-content': {
|
|
||||||
minHeight: '100%',
|
|
||||||
padding: '10px'
|
|
||||||
},
|
|
||||||
'.highlighted-line': {
|
|
||||||
backgroundColor: '#ffeb3b80',
|
|
||||||
borderLeft: '4px solid #ffeb3b',
|
|
||||||
borderRight: '2px solid #ffeb3b',
|
|
||||||
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
|
|
||||||
animation: 'pulse 1s ease-in-out'
|
|
||||||
},
|
|
||||||
'@keyframes pulse': {
|
|
||||||
'0%': { backgroundColor: '#ffeb3b80' },
|
|
||||||
'50%': { backgroundColor: '#ffeb3ba0' },
|
|
||||||
'100%': { backgroundColor: '#ffeb3b80' }
|
|
||||||
},
|
|
||||||
fontSize: "20px"
|
|
||||||
})
|
|
||||||
]}
|
|
||||||
basicSetup={{
|
|
||||||
lineNumbers: true,
|
|
||||||
highlightActiveLine: true,
|
|
||||||
highlightSelectionMatches: true,
|
|
||||||
searchKeymap: true,
|
|
||||||
autocompletion: true
|
|
||||||
}}
|
|
||||||
onChange={(value) => {
|
|
||||||
const lines = value.split(/\r\n|\r|\n/);
|
|
||||||
if (lines.length > MAX_CODE_LINES) {
|
|
||||||
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
|
|
||||||
setErrorModalVisible(true);
|
|
||||||
// 使用 CodeMirror dispatch 直接回滚编辑器内容
|
|
||||||
if (editorRef.current && editorRef.current.view) {
|
|
||||||
const view = editorRef.current.view;
|
|
||||||
view.dispatch({
|
|
||||||
changes: {
|
|
||||||
from: 0,
|
|
||||||
to: view.state.doc.length,
|
|
||||||
insert: lastValidCppCodeRef.current
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastValidCppCodeRef.current = value;
|
|
||||||
setCppCode(value);
|
|
||||||
// 计算代码行数并更新window.blockcount
|
|
||||||
updateBlockCount(value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{overlayActive && (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
background: 'rgba(0,0,0,0.25)',
|
|
||||||
cursor: 'not-allowed',
|
|
||||||
zIndex: 10,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
userSelect: 'none'
|
|
||||||
}}
|
|
||||||
onWheel={(e) => e.preventDefault()}
|
|
||||||
onTouchMove={(e) => e.preventDefault()}
|
|
||||||
onClick={(e) => e.preventDefault()}
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<div style={{
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: '28px',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
|
|
||||||
letterSpacing: '4px',
|
|
||||||
animation: 'blink 1.5s ease-in-out infinite'
|
|
||||||
}}>
|
|
||||||
⏳ 执行中...
|
|
||||||
</div>
|
|
||||||
<style>{`
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.4; }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<style>{`
|
||||||
</div>
|
@keyframes blink {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.4; }
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -378,7 +378,7 @@
|
|||||||
color: #f44336;
|
color: #f44336;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* API参考 + 编辑器并排:参考在左,编辑器靠右 */
|
/* API参考 + 编辑器并排:参考在左,编辑器靠右(与 Python 一致) */
|
||||||
.editorWithApi {
|
.editorWithApi {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -392,9 +392,118 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* API抽屉标签样式 */
|
||||||
|
.apiDrawerTab {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 45px;
|
||||||
|
height: 90px;
|
||||||
|
background: hsla(215, 100%, 65%, 1);
|
||||||
|
border: 1px solid hsla(215, 100%, 65%, 1);
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 12px 0 0 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1001;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.25);
|
||||||
|
color: #FFFFFF;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab:hover {
|
||||||
|
background: hsla(215, 100%, 65%, 1);
|
||||||
|
transform: translateY(-50%) translateX(-3px);
|
||||||
|
box-shadow: -6px 0 18px rgba(0, 0, 0, 0.35);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab.apiDrawerTabOpen {
|
||||||
|
background: linear-gradient(135deg, #D35400 0%, #E67E22 100%);
|
||||||
|
border-color: #E67E22;
|
||||||
|
right: 20vw;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab.apiDrawerTabOpen:hover {
|
||||||
|
background: linear-gradient(135deg, #E67E22 0%, #F39C12 100%);
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTabIcon {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
color: #FFFFFF;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab:hover .apiDrawerTabIcon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTabText {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
text-orientation: mixed;
|
||||||
|
line-height: 1;
|
||||||
|
color: #FFFFFF;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab:hover .apiDrawerTabText {
|
||||||
|
opacity: 1;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式抽屉标签 */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.apiDrawerTab.apiDrawerTabOpen {
|
||||||
|
right: 25vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.apiDrawerTab.apiDrawerTabOpen {
|
||||||
|
right: 35vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab {
|
||||||
|
width: 35px;
|
||||||
|
height: 75px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTabIcon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTabText {
|
||||||
|
font-size: 8px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiDrawerTab:hover .apiDrawerTabText {
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* API面板样式 */
|
||||||
.apiDrawer {
|
.apiDrawer {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -422,6 +531,10 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apiDrawerLoadingHidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.apiDrawerLoadingSpinner {
|
.apiDrawerLoadingSpinner {
|
||||||
width: 38px;
|
width: 38px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
@@ -429,6 +542,7 @@
|
|||||||
border-top-color: #ba68c8;
|
border-top-color: #ba68c8;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: apiDrawerLoadingSpin 0.9s linear infinite;
|
animation: apiDrawerLoadingSpin 0.9s linear infinite;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apiDrawerLoadingText {
|
.apiDrawerLoadingText {
|
||||||
@@ -440,6 +554,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes apiDrawerLoadingSpin {
|
@keyframes apiDrawerLoadingSpin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
|
|||||||
// const friendToggle = new FriendToggle();
|
// const friendToggle = new FriendToggle();
|
||||||
window.RankBadgeGenerator = RankBadgeGenerator;
|
window.RankBadgeGenerator = RankBadgeGenerator;
|
||||||
window.CONFIG = CONFIG;
|
window.CONFIG = CONFIG;
|
||||||
|
import disableDevtool from 'disable-devtool';
|
||||||
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
||||||
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
||||||
import { debug } from 'scratch-vm/src/util/log.js';
|
import { debug } from 'scratch-vm/src/util/log.js';
|
||||||
|
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
|
||||||
|
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
|
||||||
|
|
||||||
const MAX_LEVELS = 500; // 最大关卡数据数量
|
const MAX_LEVELS = 500; // 最大关卡数据数量
|
||||||
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
||||||
@@ -47,6 +50,16 @@ const UnityComponent = function (props) {
|
|||||||
const [getBlockNum, setBlockNum] = useState(0);
|
const [getBlockNum, setBlockNum] = useState(0);
|
||||||
const [testDal, setTestDal] = useState(-1);
|
const [testDal, setTestDal] = useState(-1);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
const {
|
||||||
|
progress: gameLoadProgress,
|
||||||
|
visible: gameLoadVisible,
|
||||||
|
onUnityProgress,
|
||||||
|
onUnityInstanceCreated
|
||||||
|
} = useGameLoadProgress();
|
||||||
|
const onUnityProgressRef = useRef(onUnityProgress);
|
||||||
|
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
|
||||||
|
onUnityProgressRef.current = onUnityProgress;
|
||||||
|
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
|
||||||
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
||||||
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
||||||
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
||||||
@@ -547,29 +560,39 @@ const UnityComponent = function (props) {
|
|||||||
const [hasAlerted, setHasAlerted] = useState(false);
|
const [hasAlerted, setHasAlerted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initDisableDevtool({
|
if (CONFIG.disabledev) {
|
||||||
ondevtoolopen: () => {
|
disableDevtool({
|
||||||
|
ondevtoolopen: () => {
|
||||||
|
|
||||||
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
||||||
const originalOnBeforeUnload = window.onbeforeunload;
|
const originalOnBeforeUnload = window.onbeforeunload;
|
||||||
window.onbeforeunload = null;
|
window.onbeforeunload = null;
|
||||||
window.pythonCodeStopRun();
|
window.pythonCodeStopRun();
|
||||||
window.loadPythonProject("");
|
window.loadPythonProject("");
|
||||||
|
|
||||||
// 销毁 Unity 实例
|
// 销毁游戏实例(Cocos 兼容:Quit 可能不存在)
|
||||||
if (window.unityInstance) {
|
if (window.unityInstance) {
|
||||||
window.unityInstance.Quit().then(() => {
|
const quit = window.unityInstance.Quit;
|
||||||
window.unityInstance = null;
|
if (typeof quit === 'function') {
|
||||||
});
|
Promise.resolve(quit.call(window.unityInstance)).then(() => {
|
||||||
}
|
window.unityInstance = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.unityInstance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 延迟跳转到目标页面,确保销毁
|
// 延迟跳转到目标页面,确保销毁
|
||||||
window.location.href = localStorage.getItem('returnUrl');
|
window.location.href = localStorage.getItem('returnUrl');
|
||||||
// window.location.href = '/matchsum.html';
|
// window.location.href = '/matchsum.html';
|
||||||
// 恢复原始的 onbeforeunload 监听器
|
// 恢复原始的 onbeforeunload 监听器
|
||||||
window.onbeforeunload = originalOnBeforeUnload;
|
window.onbeforeunload = originalOnBeforeUnload;
|
||||||
}
|
},
|
||||||
});
|
interval: 500, // 检测间隔时间
|
||||||
|
clearLog: true, // 清除控制台的日志
|
||||||
|
disableMenu: true, // 禁用右键菜单
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -583,7 +606,7 @@ const UnityComponent = function (props) {
|
|||||||
//计算真实关卡位置
|
//计算真实关卡位置
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -693,7 +716,7 @@ const UnityComponent = function (props) {
|
|||||||
window.iscreate = 1;
|
window.iscreate = 1;
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber, gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -710,7 +733,7 @@ const UnityComponent = function (props) {
|
|||||||
// 延迟 500 毫秒后切换关卡,只执行一次
|
// 延迟 500 毫秒后切换关卡,只执行一次
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
} else {
|
} else {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
@@ -767,7 +790,7 @@ const UnityComponent = function (props) {
|
|||||||
const savedProject = window.pythonCode;
|
const savedProject = window.pythonCode;
|
||||||
setIsLockNextButton(true);
|
setIsLockNextButton(true);
|
||||||
|
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
const trialCount = localStorage.getItem('trial_count');
|
const trialCount = localStorage.getItem('trial_count');
|
||||||
|
|
||||||
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
||||||
@@ -1147,7 +1170,7 @@ const UnityComponent = function (props) {
|
|||||||
setIsNextButtonComplete(false);
|
setIsNextButtonComplete(false);
|
||||||
}
|
}
|
||||||
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
|
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
CONFIG.checkGameStyle(realLvId);
|
CONFIG.checkGameStyle(realLvId);
|
||||||
@@ -1199,7 +1222,7 @@ const UnityComponent = function (props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
console.log("Retrying the current level...");
|
console.log("Retrying the current level...");
|
||||||
};
|
};
|
||||||
@@ -1207,46 +1230,87 @@ const UnityComponent = function (props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
let cancelled = false;
|
|
||||||
|
|
||||||
const bootUnity = async () => {
|
// if (canvas && !window.unityInstance) {
|
||||||
const config = await getUnityPlayerConfig();
|
const unityUrl = "/unity";
|
||||||
await loadUnityLoaderScript();
|
const buildUrl = "/unity/Build";
|
||||||
if (cancelled || !canvas) {
|
const cdnRoot = CONFIG.usecdn ? getUnityCdnRoot() : unityUrl;
|
||||||
return;
|
const cdnBuildRoot = CONFIG.usecdn ? getUnityCdnBuildRoot() : buildUrl;
|
||||||
}
|
let loaderUrl = CONFIG.usecdn
|
||||||
|
? `${cdnBuildRoot}/mstest5.loader.js`
|
||||||
|
: `${buildUrl}/mstest5.loader.js`;
|
||||||
|
let config = {
|
||||||
|
dataUrl: buildUrl + "/mstest5.data.br",
|
||||||
|
frameworkUrl: buildUrl + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: buildUrl + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
config = {
|
||||||
|
dataUrl: cdnBuildRoot + "/mstest5.data.br",
|
||||||
|
frameworkUrl: cdnBuildRoot + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: cdnBuildRoot + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: cdnRoot + "/StreamingAssets",
|
||||||
|
// dataUrl: buildUrl + "/Build.data.br",
|
||||||
|
// frameworkUrl: buildUrl + "/Build.framework.js.br",
|
||||||
|
// codeUrl: buildUrl + "/Build.wasm.br",
|
||||||
|
// streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
window.iscreate = 0;
|
window.iscreate = 0;
|
||||||
const unityInstance = await createUnityInstance(canvas, config, () => {});
|
const boot = () => {
|
||||||
if (cancelled) {
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
return;
|
onUnityProgressRef.current(progress);
|
||||||
}
|
}).then((unityInstance) => {
|
||||||
window.unityInstance = unityInstance;
|
onUnityInstanceCreatedRef.current();
|
||||||
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
window.unityInstance = unityInstance;
|
||||||
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
||||||
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
||||||
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
||||||
console.log("Unity 实例创建成功");
|
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
||||||
window.processData = processData;
|
console.log("Unity 实例创建成功");
|
||||||
window.processVehicleData = processVehicleData;
|
window.processData = processData;
|
||||||
window.externalResult = externalResult;
|
window.processVehicleData = processVehicleData;
|
||||||
window.externalLevelInfo = externalLevelInfo;
|
window.externalResult = externalResult;
|
||||||
let realLvId = 0;
|
window.externalLevelInfo = externalLevelInfo;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
//计算真实关卡位置
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
let realLvId = 0;
|
||||||
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
|
} else {
|
||||||
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
|
}
|
||||||
|
CONFIG.checkGameStyle(realLvId);
|
||||||
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
|
}).catch((message) => {
|
||||||
|
console.error(`Unity 实例创建失败: ${message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
prefetchLevelsDatabase(cdnRoot).then(boot).catch((err) => {
|
||||||
|
console.error('[unity-cdn] 关卡库预取失败', err);
|
||||||
|
boot();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
boot();
|
||||||
}
|
}
|
||||||
CONFIG.checkGameStyle(realLvId);
|
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bootUnity().catch((message) => {
|
document.body.appendChild(script);
|
||||||
console.error(`Unity 实例创建失败: ${message}`);
|
// } else {
|
||||||
});
|
// console.log("Unity 实例已经存在,跳过初始化");
|
||||||
|
// }
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 修改 formatDateTime 函数
|
// 修改 formatDateTime 函数
|
||||||
@@ -1273,6 +1337,7 @@ const UnityComponent = function (props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
||||||
|
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,10 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.apiDrawerLoadingHidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.apiDrawerLoadingSpinner {
|
.apiDrawerLoadingSpinner {
|
||||||
width: 38px;
|
width: 38px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
@@ -190,6 +194,7 @@
|
|||||||
border-top-color: #ba68c8;
|
border-top-color: #ba68c8;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: apiDrawerLoadingSpin 0.9s linear infinite;
|
animation: apiDrawerLoadingSpin 0.9s linear infinite;
|
||||||
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.apiDrawerLoadingText {
|
.apiDrawerLoadingText {
|
||||||
@@ -201,6 +206,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes apiDrawerLoadingSpin {
|
@keyframes apiDrawerLoadingSpin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1287,12 +1287,13 @@ sys.settrace(trace_func)`;
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!isApiPanelReady && (
|
<div
|
||||||
<div className={styles.apiDrawerLoading}>
|
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
|
||||||
<div className={styles.apiDrawerLoadingSpinner}></div>
|
aria-hidden={isApiPanelReady}
|
||||||
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中,完成后即可插入代码…</div>
|
>
|
||||||
</div>
|
<div className={styles.apiDrawerLoadingSpinner}></div>
|
||||||
)}
|
<div className={styles.apiDrawerLoadingText}>正在加载中,完成后即可插入代码…</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1305,12 +1306,22 @@ sys.settrace(trace_func)`;
|
|||||||
const { state } = view;
|
const { state } = view;
|
||||||
const { selection } = state;
|
const { selection } = state;
|
||||||
const cursor = selection.main.head;
|
const cursor = selection.main.head;
|
||||||
|
const withSafeNewlines = (text) => {
|
||||||
|
let next = text;
|
||||||
|
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
|
||||||
|
next = '\n' + next;
|
||||||
|
}
|
||||||
|
if (!next.endsWith('\n')) {
|
||||||
|
next += '\n';
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
// 检查代码是否包含snippet占位符
|
// 检查代码是否包含snippet占位符
|
||||||
if (code.includes('#{')) {
|
if (code.includes('#{')) {
|
||||||
// 使用snippet功能插入代码
|
// 使用snippet功能插入代码
|
||||||
try {
|
try {
|
||||||
const snippetTemplate = snippet(code);
|
const snippetTemplate = snippet(withSafeNewlines(code));
|
||||||
const completion = snippetTemplate(view, {
|
const completion = snippetTemplate(view, {
|
||||||
from: cursor,
|
from: cursor,
|
||||||
to: cursor
|
to: cursor
|
||||||
@@ -1320,7 +1331,7 @@ sys.settrace(trace_func)`;
|
|||||||
view.dispatch(completion);
|
view.dispatch(completion);
|
||||||
} else {
|
} else {
|
||||||
// 如果snippet创建失败,使用简化的插入方式
|
// 如果snippet创建失败,使用简化的插入方式
|
||||||
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
|
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: cursor,
|
from: cursor,
|
||||||
@@ -1332,7 +1343,7 @@ sys.settrace(trace_func)`;
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Snippet insertion failed, falling back to plain text:', error);
|
console.warn('Snippet insertion failed, falling back to plain text:', error);
|
||||||
// 回退到普通插入,移除snippet标记
|
// 回退到普通插入,移除snippet标记
|
||||||
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
|
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: cursor,
|
from: cursor,
|
||||||
@@ -1342,15 +1353,15 @@ sys.settrace(trace_func)`;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 普通代码插入
|
const insertText = withSafeNewlines(code);
|
||||||
view.dispatch({
|
view.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: cursor,
|
from: cursor,
|
||||||
to: cursor,
|
to: cursor,
|
||||||
insert: code
|
insert: insertText
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
anchor: cursor + code.length
|
anchor: cursor + insertText.length
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1365,7 +1376,7 @@ sys.settrace(trace_func)`;
|
|||||||
<>
|
<>
|
||||||
<ErrorModal />
|
<ErrorModal />
|
||||||
<div className={styles.editorWithApi}>
|
<div className={styles.editorWithApi}>
|
||||||
<ApiDrawer />
|
{ApiDrawer()}
|
||||||
<div
|
<div
|
||||||
className={styles.editorPane}
|
className={styles.editorPane}
|
||||||
onWheel={(e) => e.stopPropagation()}
|
onWheel={(e) => e.stopPropagation()}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ const CurrentLevelChoose = ({ onCancel }) => {
|
|||||||
}
|
}
|
||||||
// window.handleClickNewProjectWithUnity();
|
// window.handleClickNewProjectWithUnity();
|
||||||
}
|
}
|
||||||
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber);
|
const realLvId_tmp = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode);
|
||||||
const realLvId = realLvId_tmp + cellIndex
|
const realLvId = realLvId_tmp + cellIndex
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
CONFIG.checkGameStyle(realLvId);
|
CONFIG.checkGameStyle(realLvId);
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
|
|||||||
// const friendToggle = new FriendToggle();
|
// const friendToggle = new FriendToggle();
|
||||||
window.RankBadgeGenerator = RankBadgeGenerator;
|
window.RankBadgeGenerator = RankBadgeGenerator;
|
||||||
window.CONFIG = CONFIG;
|
window.CONFIG = CONFIG;
|
||||||
|
import disableDevtool from 'disable-devtool';
|
||||||
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
import { initDisableDevtool } from '../../playground/devtool-control.js';
|
||||||
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
|
||||||
import { debug } from 'scratch-vm/src/util/log.js';
|
import { debug } from 'scratch-vm/src/util/log.js';
|
||||||
|
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
|
||||||
|
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
|
||||||
|
|
||||||
const MAX_LEVELS = 500; // 最大关卡数据数量
|
const MAX_LEVELS = 500; // 最大关卡数据数量
|
||||||
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
|
||||||
@@ -247,6 +250,16 @@ const UnityComponent = function (props) {
|
|||||||
const [getBlockNum, setBlockNum] = useState(0);
|
const [getBlockNum, setBlockNum] = useState(0);
|
||||||
const [testDal, setTestDal] = useState(-1);
|
const [testDal, setTestDal] = useState(-1);
|
||||||
const canvasRef = useRef(null);
|
const canvasRef = useRef(null);
|
||||||
|
const {
|
||||||
|
progress: gameLoadProgress,
|
||||||
|
visible: gameLoadVisible,
|
||||||
|
onUnityProgress,
|
||||||
|
onUnityInstanceCreated
|
||||||
|
} = useGameLoadProgress();
|
||||||
|
const onUnityProgressRef = useRef(onUnityProgress);
|
||||||
|
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
|
||||||
|
onUnityProgressRef.current = onUnityProgress;
|
||||||
|
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
|
||||||
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
|
||||||
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
|
||||||
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
const [isLockNextButton, setIsLockNextButton] = useState(true);
|
||||||
@@ -741,33 +754,43 @@ const UnityComponent = function (props) {
|
|||||||
const [hasAlerted, setHasAlerted] = useState(false);
|
const [hasAlerted, setHasAlerted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initDisableDevtool({
|
if (CONFIG.disabledev) {
|
||||||
ondevtoolopen: () => {
|
disableDevtool({
|
||||||
|
ondevtoolopen: () => {
|
||||||
|
|
||||||
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
|
||||||
const originalOnBeforeUnload = window.onbeforeunload;
|
const originalOnBeforeUnload = window.onbeforeunload;
|
||||||
window.onbeforeunload = null;
|
window.onbeforeunload = null;
|
||||||
|
|
||||||
// 停止 Scratch VM 的所有活动
|
// 停止 Scratch VM 的所有活动
|
||||||
vm.stopAll();
|
vm.stopAll();
|
||||||
|
|
||||||
// 清空积木
|
// 清空积木
|
||||||
vm.editingTarget.blocks.deleteAllBlocks();
|
vm.editingTarget.blocks.deleteAllBlocks();
|
||||||
vm.emitWorkspaceUpdate();
|
vm.emitWorkspaceUpdate();
|
||||||
|
|
||||||
// 销毁 Unity 实例
|
// 销毁游戏实例(Cocos 兼容:Quit 可能不存在)
|
||||||
if (window.unityInstance) {
|
if (window.unityInstance) {
|
||||||
window.unityInstance.Quit().then(() => {
|
const quit = window.unityInstance.Quit;
|
||||||
window.unityInstance = null;
|
if (typeof quit === 'function') {
|
||||||
});
|
Promise.resolve(quit.call(window.unityInstance)).then(() => {
|
||||||
}
|
window.unityInstance = null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.unityInstance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 延迟跳转到目标页面,确保销毁
|
// 延迟跳转到目标页面,确保销毁
|
||||||
window.location.href = localStorage.getItem('returnUrl')
|
window.location.href = localStorage.getItem('returnUrl')
|
||||||
// 恢复原始的 onbeforeunload 监听器
|
// 恢复原始的 onbeforeunload 监听器
|
||||||
window.onbeforeunload = originalOnBeforeUnload;
|
window.onbeforeunload = originalOnBeforeUnload;
|
||||||
}
|
},
|
||||||
});
|
interval: 500, // 检测间隔时间
|
||||||
|
clearLog: true, // 清除控制台的日志
|
||||||
|
disableMenu: true, // 禁用右键菜单
|
||||||
|
});
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -781,7 +804,7 @@ const UnityComponent = function (props) {
|
|||||||
//计算真实关卡位置
|
//计算真实关卡位置
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -905,7 +928,7 @@ const UnityComponent = function (props) {
|
|||||||
window.iscreate = 1;
|
window.iscreate = 1;
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber, gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
realLvId = CONFIG.getRealLvId(gamelvdataJson.gamelv, gamelvdataJson.gameid, gamelvdataJson.ismatch, gamelvdataJson.gameNumber)
|
||||||
}
|
}
|
||||||
@@ -922,7 +945,7 @@ const UnityComponent = function (props) {
|
|||||||
// 延迟 500 毫秒后切换关卡,只执行一次
|
// 延迟 500 毫秒后切换关卡,只执行一次
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
} else {
|
} else {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
@@ -984,7 +1007,7 @@ const UnityComponent = function (props) {
|
|||||||
}
|
}
|
||||||
setIsLockNextButton(true);
|
setIsLockNextButton(true);
|
||||||
|
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
const trialCount = localStorage.getItem('trial_count');
|
const trialCount = localStorage.getItem('trial_count');
|
||||||
|
|
||||||
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
if (localStorage.getItem('permission') === "0" && (realLvId - CONFIG.BEGINNING_REAL_LVID) >= trialCount && currentlv > 9) {
|
||||||
@@ -1403,7 +1426,7 @@ const UnityComponent = function (props) {
|
|||||||
setIsNextButtonComplete(false);
|
setIsNextButtonComplete(false);
|
||||||
}
|
}
|
||||||
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
window.gamelvdata.currentlv = window.gamelvdata.currentlv + 1
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
console.log(window.gamelvdata.currentlv, window.gamelvdataJson.gameNumber)
|
console.log(window.gamelvdata.currentlv, window.gamelvdataJson.gameNumber)
|
||||||
removeGlobalKeyboardShield();
|
removeGlobalKeyboardShield();
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
@@ -1461,7 +1484,7 @@ const UnityComponent = function (props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
const realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
removeGlobalKeyboardShield();
|
removeGlobalKeyboardShield();
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
console.log("Retrying the current level...realLvId...", realLvId);
|
console.log("Retrying the current level...realLvId...", realLvId);
|
||||||
@@ -1470,47 +1493,88 @@ const UnityComponent = function (props) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
let cancelled = false;
|
|
||||||
|
|
||||||
const bootUnity = async () => {
|
// if (canvas && !window.unityInstance) {
|
||||||
const config = await getUnityPlayerConfig();
|
const unityUrl = "/unity";
|
||||||
await loadUnityLoaderScript();
|
const buildUrl = "/unity/Build";
|
||||||
if (cancelled || !canvas) {
|
const cdnRoot = CONFIG.usecdn ? getUnityCdnRoot() : unityUrl;
|
||||||
return;
|
const cdnBuildRoot = CONFIG.usecdn ? getUnityCdnBuildRoot() : buildUrl;
|
||||||
}
|
let loaderUrl = CONFIG.usecdn
|
||||||
|
? `${cdnBuildRoot}/mstest5.loader.js`
|
||||||
|
: `${buildUrl}/mstest5.loader.js`;
|
||||||
|
let config = {
|
||||||
|
dataUrl: buildUrl + "/mstest5.data.br",
|
||||||
|
frameworkUrl: buildUrl + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: buildUrl + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
config = {
|
||||||
|
dataUrl: cdnBuildRoot + "/mstest5.data.br",
|
||||||
|
frameworkUrl: cdnBuildRoot + "/mstest5.framework.js.br",
|
||||||
|
codeUrl: cdnBuildRoot + "/mstest5.wasm.br",
|
||||||
|
streamingAssetsUrl: cdnRoot + "/StreamingAssets",
|
||||||
|
// dataUrl: buildUrl + "/Build.data.br",
|
||||||
|
// frameworkUrl: buildUrl + "/Build.framework.js.br",
|
||||||
|
// codeUrl: buildUrl + "/Build.wasm.br",
|
||||||
|
// streamingAssetsUrl: unityUrl + "/StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "Web A",
|
||||||
|
productVersion: "1.0",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
|
||||||
|
script.onload = () => {
|
||||||
window.iscreate = 0;
|
window.iscreate = 0;
|
||||||
const unityInstance = await createUnityInstance(canvas, config, () => {});
|
const boot = () => {
|
||||||
if (cancelled) {
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
return;
|
onUnityProgressRef.current(progress);
|
||||||
}
|
}).then((unityInstance) => {
|
||||||
window.unityInstance = unityInstance;
|
onUnityInstanceCreatedRef.current();
|
||||||
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
window.unityInstance = unityInstance;
|
||||||
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
window.waitForUnityStepFinish = waitForUnityStepFinish;
|
||||||
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
|
||||||
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
window.sleepForUnityStepFinish = sleepForUnityStepFinish;
|
||||||
console.log("Unity 实例创建成功");
|
window.sleepByStepForUnityStepFinish = sleepByStepForUnityStepFinish;
|
||||||
window.processData = processData;
|
console.log("Unity 实例创建成功");
|
||||||
window.processVehicleData = processVehicleData;
|
window.processData = processData;
|
||||||
window.externalResult = externalResult;
|
window.processVehicleData = processVehicleData;
|
||||||
window.externalLevelInfo = externalLevelInfo;
|
window.externalResult = externalResult;
|
||||||
|
window.externalLevelInfo = externalLevelInfo;
|
||||||
|
//计算真实关卡位置
|
||||||
|
|
||||||
let realLvId = 0;
|
let realLvId = 0;
|
||||||
if (localStorage.getItem('platformMode') === 'normal') {
|
if (localStorage.getItem('platformMode') === 'normal') {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber) + window.gamelvdata.currentlv
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber, window.gamelvdataJson.gamemode) + window.gamelvdata.currentlv
|
||||||
|
} else {
|
||||||
|
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
||||||
|
}
|
||||||
|
CONFIG.checkGameStyle(realLvId);
|
||||||
|
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
||||||
|
}).catch((message) => {
|
||||||
|
console.error(`Unity 实例创建失败: ${message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (CONFIG.usecdn) {
|
||||||
|
prefetchLevelsDatabase(cdnRoot).then(boot).catch((err) => {
|
||||||
|
console.error('[unity-cdn] 关卡库预取失败', err);
|
||||||
|
boot();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
realLvId = CONFIG.getRealLvId(window.gamelvdataJson.gamelv, window.gamelvdataJson.gameid, window.gamelvdataJson.ismatch, window.gamelvdataJson.gameNumber)
|
boot();
|
||||||
}
|
}
|
||||||
CONFIG.checkGameStyle(realLvId);
|
|
||||||
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bootUnity().catch((message) => {
|
document.body.appendChild(script);
|
||||||
console.error(`Unity 实例创建失败: ${message}`);
|
// } else {
|
||||||
});
|
// console.log("Unity 实例已经存在,跳过初始化");
|
||||||
|
// }
|
||||||
return () => {
|
|
||||||
cancelled = true;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const formatDateTime = (dateStr) => {
|
const formatDateTime = (dateStr) => {
|
||||||
@@ -1536,6 +1600,7 @@ const UnityComponent = function (props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
|
||||||
|
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
|
||||||
|
|
||||||
<section
|
<section
|
||||||
className="u-align-center u-black u-clearfix u-container-align-center u-container-style u-dialog-block u-opacity u-opacity-50 u-valign-middle-md u-valign-middle-xl u-dialog-section-8"
|
className="u-align-center u-black u-clearfix u-container-align-center u-container-style u-dialog-block u-opacity u-opacity-50 u-valign-middle-md u-valign-middle-xl u-dialog-section-8"
|
||||||
|
|||||||
0
scratch-gui/src/lib/libraries/decks/steps/add-effects.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-add-sound.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-change-color.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-jump.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-move.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-say-something.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/animate-char-talk.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/change-size.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/chase-game-change-score.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/chase-game-move-randomly.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/chase-game-play-sound.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/chase-game-right-left.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/chase-game-up-down.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/cn-backdrop.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/cn-collect.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/cn-glide.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/cn-say.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/cn-score.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-01-say-something.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-02-animate.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-03-select-different-character.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-04-use-minus-sign.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-05-grow-shrink.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-06-select-another-different-character.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-07-jump.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-08-change-scenes.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-09-glide-around.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-10-change-costumes.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/code-cartoon-11-choose-more-characters.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-choose-character.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-flying-heart.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-keep-score.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-make-interactive.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-move-scenery.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-object-to-collect.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-say-something.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-select-flyer.LTR.png
Normal file → Executable file
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/fly-switch-costume.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
0
scratch-gui/src/lib/libraries/decks/steps/glide-around-back-and-forth.es.png
Normal file → Executable file
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |