Initial Cocos Creator port of main-site Unity WebGL game.
Includes core gameplay, 600 exported levels, visual assets, web bridge, and bootstrap scene. Co-authored-by: Cursor <cursoragent@cursor.com>
14
assets/resources.meta
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "e4dc033d-0f14-4dfc-8475-f3f23d26d6dd",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"isBundle": true,
|
||||
"bundleConfigID": "default",
|
||||
"bundleName": "resources",
|
||||
"priority": 8
|
||||
}
|
||||
}
|
||||
9
assets/resources/textures.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "d81d86b4-23de-4bc2-a3a5-82add6f6e534",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/resources/textures/silu.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "cd9ff0f5-a276-4990-9d03-407d44cf21e9",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
BIN
assets/resources/textures/silu/Baseblock.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
42
assets/resources/textures/silu/Baseblock.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "5625da25-9915-416f-be60-c6decb355672",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "5625da25-9915-416f-be60-c6decb355672@6c48a",
|
||||
"displayName": "Baseblock",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "5625da25-9915-416f-be60-c6decb355672",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "5625da25-9915-416f-be60-c6decb355672@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/textures/silu/player_B.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
42
assets/resources/textures/silu/player_B.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "fa305708-508c-4b5d-8ddc-f834daf89cfd",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "fa305708-508c-4b5d-8ddc-f834daf89cfd@6c48a",
|
||||
"displayName": "player_B",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "fa305708-508c-4b5d-8ddc-f834daf89cfd",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "fa305708-508c-4b5d-8ddc-f834daf89cfd@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/textures/silu/player_F.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
42
assets/resources/textures/silu/player_F.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "369d2601-41a6-41a4-8804-c59fb1ab1eef",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "369d2601-41a6-41a4-8804-c59fb1ab1eef@6c48a",
|
||||
"displayName": "player_F",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "369d2601-41a6-41a4-8804-c59fb1ab1eef",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "369d2601-41a6-41a4-8804-c59fb1ab1eef@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/textures/silu/ship_B.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
42
assets/resources/textures/silu/ship_B.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "6d61e1e0-435a-490e-af33-addbe85ee32d",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "6d61e1e0-435a-490e-af33-addbe85ee32d@6c48a",
|
||||
"displayName": "ship_B",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "6d61e1e0-435a-490e-af33-addbe85ee32d",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "6d61e1e0-435a-490e-af33-addbe85ee32d@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/textures/silu/ship_F.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
42
assets/resources/textures/silu/ship_F.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "6d5f9230-ee4e-46c2-aaef-12077b6936fc",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "6d5f9230-ee4e-46c2-aaef-12077b6936fc@6c48a",
|
||||
"displayName": "ship_F",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "6d5f9230-ee4e-46c2-aaef-12077b6936fc",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "6d5f9230-ee4e-46c2-aaef-12077b6936fc@6c48a"
|
||||
}
|
||||
}
|
||||
9
assets/resources/textures/ui.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "9310a763-9c47-4200-8140-97ff97d09e11",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
BIN
assets/resources/textures/ui/bg.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
42
assets/resources/textures/ui/bg.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "2b1adaab-2489-4ff0-8125-665717c6275a",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "2b1adaab-2489-4ff0-8125-665717c6275a@6c48a",
|
||||
"displayName": "bg",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "2b1adaab-2489-4ff0-8125-665717c6275a",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "2b1adaab-2489-4ff0-8125-665717c6275a@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/textures/ui/coin.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
42
assets/resources/textures/ui/coin.png.meta
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "f2192a39-42df-419b-9ef0-06b4a35fb223",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "f2192a39-42df-419b-9ef0-06b4a35fb223@6c48a",
|
||||
"displayName": "coin",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "repeat",
|
||||
"wrapModeT": "repeat",
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0,
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "f2192a39-42df-419b-9ef0-06b4a35fb223",
|
||||
"visible": false
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "texture",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "f2192a39-42df-419b-9ef0-06b4a35fb223@6c48a"
|
||||
}
|
||||
}
|
||||
9
assets/scenes.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "e990322a-3266-4055-842e-e674f66f6fa3",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
494
assets/scenes/main.scene
Normal file
@@ -0,0 +1,494 @@
|
||||
[
|
||||
{
|
||||
"__type__": "cc.SceneAsset",
|
||||
"_name": "main",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"scene": {
|
||||
"__id__": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Scene",
|
||||
"_name": "main",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [
|
||||
{
|
||||
"__id__": 2
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 7
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_id": "d071e7d3-2dc4-4815-8cb8-c258c4b7c515"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Main Light",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 3
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": -0.06397656665577071,
|
||||
"y": -0.44608233363525845,
|
||||
"z": -0.8239028751062036,
|
||||
"w": -0.3436591377065261
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -117.894,
|
||||
"y": -194.909,
|
||||
"z": 38.562
|
||||
},
|
||||
"_id": "d381mi5H1Nyq4+wgo0zLCX"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.DirectionalLight",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 255,
|
||||
"g": 250,
|
||||
"b": 240,
|
||||
"a": 255
|
||||
},
|
||||
"_useColorTemperature": false,
|
||||
"_colorTemperature": 6550,
|
||||
"_staticSettings": {
|
||||
"__id__": 4
|
||||
},
|
||||
"_visibility": -325058561,
|
||||
"_illuminanceHDR": 65000,
|
||||
"_illuminance": 65000,
|
||||
"_illuminanceLDR": 1.6927083333333335,
|
||||
"_shadowEnabled": false,
|
||||
"_shadowPcf": 0,
|
||||
"_shadowBias": 0.00001,
|
||||
"_shadowNormalBias": 0,
|
||||
"_shadowSaturation": 1,
|
||||
"_shadowDistance": 50,
|
||||
"_shadowInvisibleOcclusionRange": 200,
|
||||
"_csmLevel": 4,
|
||||
"_csmLayerLambda": 0.75,
|
||||
"_csmOptimizationMode": 2,
|
||||
"_csmAdvancedOptions": false,
|
||||
"_csmLayersTransition": false,
|
||||
"_csmTransitionRange": 0.05,
|
||||
"_shadowFixedArea": false,
|
||||
"_shadowNear": 0.1,
|
||||
"_shadowFar": 10,
|
||||
"_shadowOrthoSize": 5,
|
||||
"_id": "b4ITpe9cBLQ7QkVb+TkXWc"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.StaticLightSettings",
|
||||
"_baked": false,
|
||||
"_editorOnly": false,
|
||||
"_castShadow": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "Main Camera",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 6
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -10,
|
||||
"y": 10,
|
||||
"z": 10
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": -0.27781593346944056,
|
||||
"y": -0.36497167621709875,
|
||||
"z": -0.11507512748638377,
|
||||
"w": 0.8811195706053617
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -35,
|
||||
"y": -45,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "45wksGP0VK44RZQ6l+OECN"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Camera",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 5
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_projection": 1,
|
||||
"_priority": 0,
|
||||
"_fov": 45,
|
||||
"_fovAxis": 0,
|
||||
"_orthoHeight": 10,
|
||||
"_near": 1,
|
||||
"_far": 1000,
|
||||
"_color": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 51,
|
||||
"g": 51,
|
||||
"b": 51,
|
||||
"a": 255
|
||||
},
|
||||
"_depth": 1,
|
||||
"_stencil": 0,
|
||||
"_clearFlags": 14,
|
||||
"_rect": {
|
||||
"__type__": "cc.Rect",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1,
|
||||
"height": 1
|
||||
},
|
||||
"_aperture": 19,
|
||||
"_shutter": 7,
|
||||
"_iso": 0,
|
||||
"_screenScale": 1,
|
||||
"_visibility": 1822425087,
|
||||
"_targetTexture": null,
|
||||
"_postProcess": null,
|
||||
"_usePostProcess": false,
|
||||
"_cameraType": -1,
|
||||
"_trackingType": 0,
|
||||
"_id": "80VFXpZJ9E8qFokxruq7p4"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "AppRoot",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "4fu9hupTlJ84UrhzTAt969"
|
||||
},
|
||||
{
|
||||
"__type__": "c0468XFPX1InLN14gtZ5PLf",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"_id": "eeKeOJWZBOzI20RcHfalSe"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 10
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 11
|
||||
},
|
||||
"_skybox": {
|
||||
"__id__": 12
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 13
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 14
|
||||
},
|
||||
"skin": {
|
||||
"__id__": 15
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"__id__": 16
|
||||
},
|
||||
"postSettings": {
|
||||
"__id__": 17
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AmbientInfo",
|
||||
"_skyColorHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 0.520833125
|
||||
},
|
||||
"_skyColor": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 0.520833125
|
||||
},
|
||||
"_skyIllumHDR": 20000,
|
||||
"_skyIllum": 20000,
|
||||
"_groundAlbedoHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
},
|
||||
"_groundAlbedo": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
},
|
||||
"_skyColorLDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.452588,
|
||||
"y": 0.607642,
|
||||
"z": 0.755699,
|
||||
"w": 0
|
||||
},
|
||||
"_skyIllumLDR": 0.8,
|
||||
"_groundAlbedoLDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.618555,
|
||||
"y": 0.577848,
|
||||
"z": 0.544564,
|
||||
"w": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.ShadowsInfo",
|
||||
"_enabled": false,
|
||||
"_type": 0,
|
||||
"_normal": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"z": 0
|
||||
},
|
||||
"_distance": 0,
|
||||
"_planeBias": 1,
|
||||
"_shadowColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 76,
|
||||
"g": 76,
|
||||
"b": 76,
|
||||
"a": 255
|
||||
},
|
||||
"_maxReceived": 4,
|
||||
"_size": {
|
||||
"__type__": "cc.Vec2",
|
||||
"x": 1024,
|
||||
"y": 1024
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SkyboxInfo",
|
||||
"_envLightingType": 0,
|
||||
"_envmapHDR": {
|
||||
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
|
||||
"__expectedType__": "cc.TextureCube"
|
||||
},
|
||||
"_envmap": {
|
||||
"__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0",
|
||||
"__expectedType__": "cc.TextureCube"
|
||||
},
|
||||
"_envmapLDR": {
|
||||
"__uuid__": "6f01cf7f-81bf-4a7e-bd5d-0afc19696480@b47c0",
|
||||
"__expectedType__": "cc.TextureCube"
|
||||
},
|
||||
"_diffuseMapHDR": null,
|
||||
"_diffuseMapLDR": null,
|
||||
"_enabled": true,
|
||||
"_useHDR": true,
|
||||
"_editableMaterial": null,
|
||||
"_reflectionHDR": null,
|
||||
"_reflectionLDR": null,
|
||||
"_rotationAngle": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.FogInfo",
|
||||
"_type": 0,
|
||||
"_fogColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200,
|
||||
"a": 255
|
||||
},
|
||||
"_enabled": false,
|
||||
"_fogDensity": 0.3,
|
||||
"_fogStart": 0.5,
|
||||
"_fogEnd": 300,
|
||||
"_fogAtten": 5,
|
||||
"_fogTop": 1.5,
|
||||
"_fogRange": 1.2,
|
||||
"_accurate": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.OctreeInfo",
|
||||
"_enabled": false,
|
||||
"_minPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1024,
|
||||
"y": -1024,
|
||||
"z": -1024
|
||||
},
|
||||
"_maxPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1024,
|
||||
"y": 1024,
|
||||
"z": 1024
|
||||
},
|
||||
"_depth": 8
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SkinInfo",
|
||||
"_enabled": true,
|
||||
"_blurRadius": 0.01,
|
||||
"_sssIntensity": 3
|
||||
},
|
||||
{
|
||||
"__type__": "cc.LightProbeInfo",
|
||||
"_giScale": 1,
|
||||
"_giSamples": 1024,
|
||||
"_bounces": 2,
|
||||
"_reduceRinging": 0,
|
||||
"_showProbe": true,
|
||||
"_showWireframe": true,
|
||||
"_showConvex": false,
|
||||
"_data": null,
|
||||
"_lightProbeSphereVolume": 1
|
||||
},
|
||||
{
|
||||
"__type__": "cc.PostSettingsInfo",
|
||||
"_toneMappingType": 0
|
||||
}
|
||||
]
|
||||
11
assets/scenes/main.scene.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "scene",
|
||||
"imported": true,
|
||||
"uuid": "d071e7d3-2dc4-4815-8cb8-c258c4b7c515",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
8
assets/scenes/场景搭建说明.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
在 Cocos Creator 3.8.8 中:
|
||||
|
||||
1. 新建场景 → 保存为 main.scene(与本目录同级)
|
||||
2. 创建空节点 AppRoot,添加组件 AppBootstrap(脚本路径 assets/scripts/AppBootstrap.ts)
|
||||
3. 项目设置中将 main 设为启动场景
|
||||
4. 点击播放即可
|
||||
|
||||
无需手动创建 GameController / Player,AppBootstrap 会自动搭建。
|
||||
11
assets/scenes/场景搭建说明.txt.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "2e31b18a-7901-449d-b1c1-d2a5c09dd773",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "8254b8f8-57eb-4bc8-ab6d-062a44e75e22",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
117
assets/scripts/AppBootstrap.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
_decorator, Component, Node, Canvas, Camera, UITransform, view, Color,
|
||||
director, Label, find,
|
||||
} from 'cc';
|
||||
import { GameController } from './GameController';
|
||||
import { GameManager } from './manager/GameManager';
|
||||
import { VisualAssets } from './visual/VisualAssets';
|
||||
|
||||
const { ccclass, executionOrder } = _decorator;
|
||||
|
||||
/** 每格像素尺寸(UI 坐标) */
|
||||
export const CELL_PIXEL = 56;
|
||||
|
||||
@ccclass('AppBootstrap')
|
||||
@executionOrder(-100)
|
||||
export class AppBootstrap extends Component {
|
||||
async onLoad() {
|
||||
try {
|
||||
await this.bootstrap();
|
||||
} catch (e) {
|
||||
console.error('[AppBootstrap] 初始化失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
private async bootstrap() {
|
||||
console.log('[AppBootstrap] 开始初始化…');
|
||||
await VisualAssets.preload();
|
||||
|
||||
const scene = director.getScene()!;
|
||||
let mainCam = find('Main Camera', scene)?.getComponent(Camera) ?? null;
|
||||
if (!mainCam) {
|
||||
const camNode = new Node('Main Camera');
|
||||
camNode.parent = scene;
|
||||
mainCam = camNode.addComponent(Camera);
|
||||
}
|
||||
this.setupCamera(mainCam);
|
||||
|
||||
const light = find('Main Light', scene);
|
||||
if (light) light.active = false;
|
||||
|
||||
let canvasNode = find('Canvas', scene);
|
||||
if (!canvasNode) {
|
||||
canvasNode = new Node('Canvas');
|
||||
canvasNode.parent = scene;
|
||||
canvasNode.addComponent(Canvas);
|
||||
}
|
||||
const canvas = canvasNode.getComponent(Canvas)!;
|
||||
canvas.cameraComponent = mainCam;
|
||||
let canvasUi = canvasNode.getComponent(UITransform);
|
||||
if (!canvasUi) canvasUi = canvasNode.addComponent(UITransform);
|
||||
const size = view.getVisibleSize();
|
||||
canvasUi.setContentSize(size.width, size.height);
|
||||
|
||||
let gameRoot = canvasNode.getChildByName('GameRoot');
|
||||
if (!gameRoot) {
|
||||
gameRoot = new Node('GameRoot');
|
||||
gameRoot.parent = canvasNode;
|
||||
const grUi = gameRoot.addComponent(UITransform);
|
||||
grUi.setContentSize(size.width, size.height);
|
||||
}
|
||||
|
||||
let gcNode = scene.getChildByName('GameController');
|
||||
if (!gcNode) {
|
||||
gcNode = new Node('GameController');
|
||||
gcNode.parent = scene;
|
||||
gcNode.addComponent(GameManager);
|
||||
gcNode.addComponent(GameController);
|
||||
}
|
||||
|
||||
const gctl = gcNode.getComponent(GameController);
|
||||
const gm = gcNode.getComponent(GameManager)!;
|
||||
if (gctl) {
|
||||
if (gctl.mainLevelEntrance) gm.mainLevelEntrance = gctl.mainLevelEntrance;
|
||||
gm.initialLevelID = gctl.initialLevelID;
|
||||
gm.playerSkin = gctl.playerSkin;
|
||||
}
|
||||
let entrance = gameRoot.getChildByName('MainLevelEntrance');
|
||||
if (!entrance) {
|
||||
entrance = new Node('MainLevelEntrance');
|
||||
entrance.parent = gameRoot;
|
||||
const eUi = entrance.addComponent(UITransform);
|
||||
eUi.setContentSize(size.width, size.height);
|
||||
}
|
||||
gm.mainLevelEntrance = entrance;
|
||||
gm.initialLevelID = 1;
|
||||
|
||||
this.ensureHint(canvasNode);
|
||||
|
||||
await gm.createNewLevel(gm.initialLevelID);
|
||||
console.log('[AppBootstrap] 关卡已加载');
|
||||
}
|
||||
|
||||
private setupCamera(cam: Camera) {
|
||||
const camNode = cam.node;
|
||||
camNode.setPosition(0, 0, 1000);
|
||||
camNode.setRotationFromEuler(0, 0, 0);
|
||||
cam.projection = Camera.ProjectionType.ORTHO;
|
||||
cam.orthoHeight = 360;
|
||||
cam.near = 1;
|
||||
cam.far = 2000;
|
||||
cam.clearFlags = Camera.ClearFlag.SOLID_COLOR;
|
||||
cam.clearColor = new Color(30, 40, 60, 255);
|
||||
}
|
||||
|
||||
private ensureHint(canvasNode: Node) {
|
||||
if (canvasNode.getChildByName('Hint')) return;
|
||||
const hint = new Node('Hint');
|
||||
hint.parent = canvasNode;
|
||||
const ui = hint.addComponent(UITransform);
|
||||
ui.setContentSize(400, 40);
|
||||
hint.setPosition(0, 280, 0);
|
||||
const label = hint.addComponent(Label);
|
||||
label.string = '主站 Cocos · 关卡运行中';
|
||||
label.fontSize = 22;
|
||||
label.color = new Color(200, 220, 255);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/AppBootstrap.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c04685c5-3d7d-489c-b375-e20b59e4f2df",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
275
assets/scripts/GameController.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import {
|
||||
_decorator, Component, Node, Enum, CCString, CCInteger, CCBoolean, game,
|
||||
} from 'cc';
|
||||
import { GameManager } from './manager/GameManager';
|
||||
import { Skin } from './core/Define';
|
||||
import { VisualAssets } from './visual/VisualAssets';
|
||||
import { getLevelCount } from './level/LevelRegistry';
|
||||
|
||||
const { ccclass, property, executionOrder } = _decorator;
|
||||
|
||||
/** UI 主题(与 Unity ChangeUIStyle 参数一致) */
|
||||
export enum UIStyleType {
|
||||
default = 0,
|
||||
chinese = 1,
|
||||
redArmy = 2,
|
||||
numMan = 3,
|
||||
snow = 4,
|
||||
sanxing = 5,
|
||||
}
|
||||
const UIStyleNames = ['default', 'chinese', 'redArmy', 'numMan', 'snow', 'sanxing'];
|
||||
|
||||
/** 多人角色 */
|
||||
export enum MultRole {
|
||||
PlayerA1 = 0,
|
||||
PlayerA2 = 1,
|
||||
PlayerA3 = 2,
|
||||
PlayerB1 = 3,
|
||||
PlayerB2 = 4,
|
||||
PlayerB3 = 5,
|
||||
}
|
||||
const MultRoleNames = ['PlayerA1', 'PlayerA2', 'PlayerA3', 'PlayerB1', 'PlayerB2', 'PlayerB3'];
|
||||
|
||||
/**
|
||||
* 对应 Unity 场景 GameController + Inspector 调试面板(TestGame2)
|
||||
* 挂在 GameController 节点,与 GameManager 同级或同节点
|
||||
*/
|
||||
@ccclass('GameController')
|
||||
@executionOrder(-50)
|
||||
export class GameController extends Component {
|
||||
@property({ group: { name: '场景', id: '1' }, type: Node, displayName: 'Main Level Entrance' })
|
||||
mainLevelEntrance: Node | null = null;
|
||||
|
||||
@property({ group: { name: '场景', id: '1' }, type: Node, displayName: 'Cur Level', readonly: true })
|
||||
curLevelNode: Node | null = null;
|
||||
|
||||
@property({ group: { name: '关卡', id: '2' }, type: CCInteger, displayName: 'Initial Level ID' })
|
||||
initialLevelID = 1;
|
||||
|
||||
@property({ group: { name: '关卡', id: '2' }, type: CCInteger, displayName: 'Cur Level ID', readonly: true })
|
||||
curLevelID = 1;
|
||||
|
||||
@property({ group: { name: '关卡', id: '2' }, displayName: '已注册关卡数', readonly: true })
|
||||
registeredLevelCount = 0;
|
||||
|
||||
@property({ group: { name: '角色', id: '3' }, type: Enum(Skin), displayName: 'Player Skin' })
|
||||
playerSkin: Skin = Skin.Silu;
|
||||
|
||||
@property({ group: { name: '多人', id: '4' }, displayName: 'Mult Mode' })
|
||||
multMode = false;
|
||||
|
||||
@property({ group: { name: '多人', id: '4' }, type: Enum(MultRole), displayName: 'Mult Player Role' })
|
||||
multPlayerRoleEnum: MultRole = MultRole.PlayerA1;
|
||||
|
||||
@property({ group: { name: '主题', id: '5' }, type: Enum(UIStyleType), displayName: 'UI Style' })
|
||||
uiStyleEnum: UIStyleType = UIStyleType.default;
|
||||
|
||||
// --- 与 Unity Inspector 输入框一致 ---
|
||||
@property({ group: { name: '调试输入', id: '6' }, displayName: 'inputLevel' })
|
||||
inputLevel = '1';
|
||||
|
||||
@property({ group: { name: '调试输入', id: '6' }, displayName: 'inputStyle' })
|
||||
inputStyle = 'default';
|
||||
|
||||
@property({ group: { name: '调试输入', id: '6' }, multiline: true, displayName: 'coinStr' })
|
||||
coinStr = '["-2,0","-2,2","-1,2"]';
|
||||
|
||||
// --- 播放时勾选即执行(等同 Unity 按钮) ---
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ SwitchLevel' })
|
||||
execSwitchLevel = false;
|
||||
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ EndInput' })
|
||||
execEndInput = false;
|
||||
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ ChangeStyle' })
|
||||
execChangeStyle = false;
|
||||
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ StartMultPlay' })
|
||||
execStartMultPlay = false;
|
||||
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ Mute' })
|
||||
execMute = false;
|
||||
|
||||
@property({ group: { name: '调试操作(播放时勾选)', id: '7' }, displayName: '▶ Unmute' })
|
||||
execUnmute = false;
|
||||
|
||||
private gm!: GameManager;
|
||||
|
||||
onLoad() {
|
||||
this.gm = this.getComponent(GameManager) || this.node.getComponentInChildren(GameManager)!;
|
||||
if (!this.gm) {
|
||||
this.gm = this.node.addComponent(GameManager);
|
||||
}
|
||||
this.syncToManager();
|
||||
this.registerWebApi();
|
||||
this.registeredLevelCount = getLevelCount();
|
||||
}
|
||||
|
||||
start() {
|
||||
this.refreshReadonly();
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!game.isRunning()) return;
|
||||
this.refreshReadonly();
|
||||
this.pollInspectorActions();
|
||||
}
|
||||
|
||||
private refreshReadonly() {
|
||||
this.curLevelID = this.gm?.curLevelID ?? this.curLevelID;
|
||||
if (this.gm?.mainLevelEntrance) {
|
||||
this.mainLevelEntrance = this.gm.mainLevelEntrance;
|
||||
const lv = this.gm.mainLevelEntrance.children.find((c) => c.name.startsWith('Level_'));
|
||||
this.curLevelNode = lv ?? null;
|
||||
}
|
||||
this.multMode = this.gm?.multMode ?? false;
|
||||
}
|
||||
|
||||
private syncToManager() {
|
||||
if (!this.gm) return;
|
||||
if (this.mainLevelEntrance) this.gm.mainLevelEntrance = this.mainLevelEntrance;
|
||||
this.gm.initialLevelID = this.initialLevelID;
|
||||
this.gm.playerSkin = this.playerSkin;
|
||||
this.gm.multMode = this.multMode;
|
||||
this.gm.multPlayerRole = MultRoleNames[this.multPlayerRoleEnum] ?? 'PlayerA1';
|
||||
this.gm.uiStyle = UIStyleNames[this.uiStyleEnum] ?? 'default';
|
||||
this.gm.curLevelID = this.initialLevelID;
|
||||
}
|
||||
|
||||
private pollInspectorActions() {
|
||||
if (this.execSwitchLevel) {
|
||||
this.execSwitchLevel = false;
|
||||
this.onInspectorSwitchLevel();
|
||||
}
|
||||
if (this.execEndInput) {
|
||||
this.execEndInput = false;
|
||||
this.callSetIsInputEnd(1);
|
||||
}
|
||||
if (this.execChangeStyle) {
|
||||
this.execChangeStyle = false;
|
||||
this.onInspectorChangeStyle();
|
||||
}
|
||||
if (this.execStartMultPlay) {
|
||||
this.execStartMultPlay = false;
|
||||
this.onInspectorStartMultPlay();
|
||||
}
|
||||
if (this.execMute) {
|
||||
this.execMute = false;
|
||||
this.callMute();
|
||||
}
|
||||
if (this.execUnmute) {
|
||||
this.execUnmute = false;
|
||||
this.callUnmute();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inspector / SendMessage 共用 ---
|
||||
onInspectorSwitchLevel() {
|
||||
const id = parseInt(this.inputLevel, 10);
|
||||
if (Number.isNaN(id)) {
|
||||
console.warn('[GameController] inputLevel 无效:', this.inputLevel);
|
||||
return;
|
||||
}
|
||||
this.syncToManager();
|
||||
this.gm.switchLevel(id);
|
||||
this.curLevelID = id;
|
||||
console.log('[GameController] SwitchLevel', id);
|
||||
}
|
||||
|
||||
onInspectorChangeStyle() {
|
||||
const style = this.inputStyle.trim() || UIStyleNames[this.uiStyleEnum];
|
||||
this.gm.changeUIStyle(style);
|
||||
this.uiStyleEnum = UIStyleNames.indexOf(style) as UIStyleType;
|
||||
if (this.uiStyleEnum < 0) this.uiStyleEnum = UIStyleType.default;
|
||||
console.log('[GameController] ChangeUIStyle', style);
|
||||
}
|
||||
|
||||
onInspectorStartMultPlay() {
|
||||
const role = MultRoleNames[this.multPlayerRoleEnum] ?? 'PlayerA1';
|
||||
this.gm.startMultPlay(role, this.coinStr);
|
||||
this.multMode = true;
|
||||
console.log('[GameController] StartMultPlay', role, this.coinStr);
|
||||
}
|
||||
|
||||
applyPlayerSkinFromInspector() {
|
||||
const player = this.gm.findNodeByName('Player')
|
||||
?? this.gm.findNodeByName(this.gm.multPlayerRole);
|
||||
const pc = player?.getComponent('PlayerController') as { callChangeSkin?: (n: number) => void };
|
||||
pc?.callChangeSkin?.(this.playerSkin);
|
||||
this.gm.playerSkin = this.playerSkin;
|
||||
}
|
||||
|
||||
// --- JS Bridge (SendMessage) ---
|
||||
private registerWebApi() {
|
||||
const api = {
|
||||
SendMessage: (objectName: string, methodName: string, param?: string | number) => {
|
||||
this.sendMessage(objectName, methodName, param);
|
||||
},
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as unknown as { cocosIns?: typeof api }).cocosIns = api;
|
||||
(window as unknown as { unityInstance?: typeof api }).unityInstance = api;
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(objectName: string, methodName: string, param?: string | number) {
|
||||
if (objectName === 'GameController') {
|
||||
this.invoke(this, methodName, param);
|
||||
return;
|
||||
}
|
||||
if (objectName === 'UIMain') {
|
||||
const n = this.gm.findNodeByName('UIMain');
|
||||
const c = n?.getComponent('UIMain');
|
||||
if (c) this.invoke(c, methodName, param);
|
||||
return;
|
||||
}
|
||||
const node = this.gm.findNodeByName(objectName);
|
||||
if (!node) {
|
||||
console.warn(`SendMessage: 未找到 ${objectName}`);
|
||||
return;
|
||||
}
|
||||
for (const comp of node.getComponents(Component)) {
|
||||
if (typeof (comp as Record<string, unknown>)[methodName] === 'function') {
|
||||
this.invoke(comp, methodName, param);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.warn(`SendMessage: ${objectName}.${methodName} 无此方法`);
|
||||
}
|
||||
|
||||
private invoke(target: object, methodName: string, param?: string | number) {
|
||||
const fn = (target as Record<string, unknown>)[methodName];
|
||||
if (typeof fn !== 'function') return;
|
||||
if (param === undefined) (fn as () => void).call(target);
|
||||
else (fn as (p: string | number) => void).call(target, param);
|
||||
}
|
||||
|
||||
switchLevel(levelID: number) {
|
||||
this.inputLevel = String(levelID);
|
||||
this.onInspectorSwitchLevel();
|
||||
}
|
||||
|
||||
callSetIsInputEnd(v: number) {
|
||||
this.gm.callSetIsInputEnd(v);
|
||||
}
|
||||
|
||||
changeUIStyle(style: string) {
|
||||
this.inputStyle = style;
|
||||
this.onInspectorChangeStyle();
|
||||
}
|
||||
|
||||
startMultPlay(role: string, coins: string) {
|
||||
this.coinStr = coins;
|
||||
const idx = MultRoleNames.indexOf(role);
|
||||
if (idx >= 0) this.multPlayerRoleEnum = idx as MultRole;
|
||||
this.onInspectorStartMultPlay();
|
||||
}
|
||||
|
||||
callMute() {
|
||||
this.gm.callMute();
|
||||
}
|
||||
|
||||
callUnmute() {
|
||||
this.gm.callUnmute();
|
||||
}
|
||||
}
|
||||
9
assets/scripts/GameController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8940a3d2-2898-4628-a201-13fa8a9c59bb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/bridge.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "ba5d5aa4-760d-4aae-aef0-83f80c1f2cba",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
28
assets/scripts/bridge/JsBridge.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 对应 Unity Application.ExternalCall
|
||||
* Web 环境下调用 window 上的全局回调
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
processData?: (json: string) => void;
|
||||
processVehicleData?: (json: string) => void;
|
||||
externalResult?: (json: string) => void;
|
||||
externalLevelInfo?: (json: string) => void;
|
||||
coinsData?: (json: string) => void;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export class JsBridge {
|
||||
static call(callbackName: string, jsonData: string) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const fn = window[callbackName];
|
||||
if (typeof fn === 'function') {
|
||||
(fn as (json: string) => void)(jsonData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log(`[JsBridge] ${callbackName}`, jsonData);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/bridge/JsBridge.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d34fa121-5277-49b2-9938-ed0886a31fc4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/controller.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "6fcf8603-44fa-444f-900d-118529aca8dc",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
213
assets/scripts/controller/PlayerController.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { _decorator, Vec3 } from 'cc';
|
||||
import { Direction, GameState, GridType, Skin } from '../core/Define';
|
||||
import { EventManager, EventType } from '../core/EventManager';
|
||||
import { JsBridge } from '../bridge/JsBridge';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { Movement } from '../gameplay/Movement';
|
||||
import { VehicleController } from './VehicleController';
|
||||
import { VisualAssets } from '../visual/VisualAssets';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
export interface ExternalData {
|
||||
position: { x: number; y: number; z: number };
|
||||
gridType: number;
|
||||
direction: string;
|
||||
}
|
||||
|
||||
export interface ExternalDataList {
|
||||
direction: number;
|
||||
externalDatas: ExternalData[];
|
||||
}
|
||||
|
||||
export interface ExternalResult {
|
||||
isWin: boolean;
|
||||
stepNum: number;
|
||||
direction: number;
|
||||
isInputEnd: boolean;
|
||||
}
|
||||
|
||||
@ccclass('PlayerController')
|
||||
export class PlayerController extends Movement {
|
||||
coins = 0;
|
||||
private vehicle: VehicleController | null = null;
|
||||
private sendFinally = false;
|
||||
|
||||
onLoad() {
|
||||
this.moverRole = 'player';
|
||||
EventManager.register(EventType.LevelInit, this.onLevelInit);
|
||||
EventManager.register(EventType.InputEnd, this.onInputEnd);
|
||||
this.coins = 0;
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
EventManager.remove(EventType.LevelInit, this.onLevelInit);
|
||||
EventManager.remove(EventType.InputEnd, this.onInputEnd);
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
VisualAssets.applyPlayerSprite(this.node, this.direction);
|
||||
if (this.node.name === 'Player' && GameManager.instance) {
|
||||
this.callChangeSkin(GameManager.instance.playerSkin);
|
||||
}
|
||||
}
|
||||
|
||||
override setDirection(dir: Direction) {
|
||||
super.setDirection(dir);
|
||||
VisualAssets.applyPlayerSprite(this.node, dir);
|
||||
}
|
||||
|
||||
private onLevelInit = () => {
|
||||
this.checkRide();
|
||||
};
|
||||
|
||||
private onInputEnd = () => {
|
||||
this.externalCallResult(false);
|
||||
};
|
||||
|
||||
protected onMoveNextSet(isJump: boolean) {
|
||||
if (isJump && this.targetGridType === GridType.Jump) {
|
||||
const p = this.node.worldPosition.clone();
|
||||
p.y += 0.15;
|
||||
this.targetPosition.set(p);
|
||||
}
|
||||
}
|
||||
|
||||
onMoving() {
|
||||
if (this.targetGridType === GridType.None && this.vehicle && !Movement.callEach) {
|
||||
this.vehicle.setPosition(this.node.worldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
protected onMoveFail(isJump: boolean) {
|
||||
this.externalCallResult(false);
|
||||
const gm = GameManager.instance!;
|
||||
if (gm.multMode) {
|
||||
const other = gm.findNodeByName(this.node.name === 'Player' ? 'Enemy' : 'Player');
|
||||
other?.getComponent(PlayerController)?.externalCallResult(true);
|
||||
}
|
||||
console.log(`${this.node.name} 无法移动`, isJump);
|
||||
}
|
||||
|
||||
protected onMoveToTarget() {
|
||||
if (this.curGrid === GridType.Ride) {
|
||||
const obj = GameManager.instance!.getGameObject(this.node.worldPosition);
|
||||
if (obj) {
|
||||
this.vehicle = obj.getComponent(VehicleController);
|
||||
this.vehicle?.setPlayer(this);
|
||||
if (this.vehicle) this.vehicle.setDirection(this.direction);
|
||||
}
|
||||
} else if (this.curGrid !== GridType.None && this.vehicle) {
|
||||
this.vehicle.setPlayer(null);
|
||||
this.vehicle = null;
|
||||
}
|
||||
if (this.vehicle) {
|
||||
this.vehicle.setPosition(this.node.worldPosition);
|
||||
GameManager.instance!.removeObj(this.lastPosition);
|
||||
GameManager.instance!.addObj(this.node.worldPosition, GridType.Ride, this.vehicle.node);
|
||||
}
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
callChangeSkin(n: number) {
|
||||
if (n < 0 || n > Skin.sanxing) return;
|
||||
if (GameManager.instance) GameManager.instance.playerSkin = n as Skin;
|
||||
}
|
||||
|
||||
callPlayerInfo() {
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this.vehicle?.setName(name);
|
||||
}
|
||||
|
||||
getCoinsNum() {
|
||||
JsBridge.call('coinsData', JSON.stringify({ coinsNum: this.coins, gameObjectName: this.node.name }));
|
||||
}
|
||||
|
||||
addCoins() {
|
||||
this.coins++;
|
||||
}
|
||||
|
||||
setPosition(pos: Vec3) {
|
||||
this.node.setPosition(pos);
|
||||
}
|
||||
|
||||
/** 供 VehicleController 同步 */
|
||||
syncFromVehicle(targetGridType: GridType, isJump: boolean) {
|
||||
this.targetGridType = targetGridType;
|
||||
this.onMoveNextSet(isJump);
|
||||
}
|
||||
|
||||
syncMoveToTargetFromVehicle() {
|
||||
this.onMoveToTarget();
|
||||
}
|
||||
|
||||
externalCall() {
|
||||
const gm = GameManager.instance!;
|
||||
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
|
||||
const self = gm.worldToCell(this.node.worldPosition);
|
||||
list.externalDatas.push({
|
||||
position: { x: self.x, y: self.y, z: 0 },
|
||||
gridType: this.curGrid,
|
||||
direction: 'self',
|
||||
});
|
||||
for (let d = Direction.North; d <= Direction.West; d++) {
|
||||
const wp = gm.nextGridPosition(this.node.worldPosition, d);
|
||||
const cell = gm.worldToCell(wp);
|
||||
list.externalDatas.push({
|
||||
position: { x: cell.x, y: cell.y, z: 0 },
|
||||
gridType: gm.calculateNextGridType(this.node.worldPosition, d),
|
||||
direction: gm.getRelativePosition(this.direction, d),
|
||||
});
|
||||
}
|
||||
const json = JSON.stringify(list);
|
||||
if (gm.multMode) JsBridge.call(`process${this.node.name}`, json);
|
||||
else JsBridge.call('processData', json);
|
||||
}
|
||||
|
||||
externalCallResult(isWin: boolean) {
|
||||
const gm = GameManager.instance!;
|
||||
if (isWin && (this.node.name === 'Player' || this.node.name === gm.multPlayerRole)) {
|
||||
/* success audio */
|
||||
} else if (!isWin && (this.node.name === 'Player' || this.node.name === gm.multPlayerRole)) {
|
||||
/* fail audio */
|
||||
}
|
||||
let myStep = gm.stepNum;
|
||||
if (gm.multMode) {
|
||||
switch (gm.multPlayerRole) {
|
||||
case 'PlayerA1': myStep = gm.stepA1Num; break;
|
||||
case 'PlayerA2': myStep = gm.stepA2Num; break;
|
||||
case 'PlayerA3': myStep = gm.stepA3Num; break;
|
||||
case 'PlayerB1': myStep = gm.stepB1Num; break;
|
||||
case 'PlayerB2': myStep = gm.stepB2Num; break;
|
||||
case 'PlayerB3': myStep = gm.stepB3Num; break;
|
||||
}
|
||||
}
|
||||
if ((this.node.name === 'Player' || this.node.name === gm.multPlayerRole) && !this.sendFinally) {
|
||||
const payload: ExternalResult = {
|
||||
isWin,
|
||||
stepNum: myStep,
|
||||
direction: this.direction,
|
||||
isInputEnd: gm.isInputEnd,
|
||||
};
|
||||
JsBridge.call('externalResult', JSON.stringify(payload));
|
||||
this.sendFinally = true;
|
||||
}
|
||||
if (gm.gameState !== GameState.Run) return;
|
||||
gm.setGameState(isWin ? GameState.ResultWin : GameState.ResultFail);
|
||||
}
|
||||
|
||||
private checkRide() {
|
||||
if (this.curGrid === GridType.Ride) {
|
||||
const obj = GameManager.instance!.getGameObject(this.node.worldPosition);
|
||||
if (obj) {
|
||||
this.vehicle = obj.getComponent(VehicleController);
|
||||
this.vehicle?.setDirection(this.direction);
|
||||
this.vehicle?.setPlayer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/PlayerController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "93a110e7-a99e-4719-b6c2-d2c2d8469d7f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
59
assets/scripts/controller/PropController.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { _decorator, Component, Vec3 } from 'cc';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { PlayerController } from './PlayerController';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@ccclass('PropController')
|
||||
export class PropController extends Component {
|
||||
private collected = false;
|
||||
|
||||
update() {
|
||||
if (this.collected || !GameManager.instance) return;
|
||||
const gm = GameManager.instance;
|
||||
const players = gm.curLevel?.children.filter((c) => c.name.includes('Player')) ?? [];
|
||||
const propCell = gm.worldToCell(this.node.worldPosition);
|
||||
for (const p of players) {
|
||||
const pc = p.getComponent(PlayerController);
|
||||
if (!pc) continue;
|
||||
const pcCell = gm.worldToCell(p.worldPosition);
|
||||
if (pcCell.x !== propCell.x || pcCell.y !== propCell.y) continue;
|
||||
this.onCollected(pc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onCollected(player: PlayerController) {
|
||||
if (this.collected) return;
|
||||
this.collected = true;
|
||||
const gm = GameManager.instance!;
|
||||
player.addCoins();
|
||||
gm.removeProp(this.node.worldPosition);
|
||||
const remaining = (gm.curLevel?.children.filter((c) => c.name.includes('Prop') && c !== this.node).length ?? 0);
|
||||
this.node.destroy();
|
||||
|
||||
if (remaining === 0) {
|
||||
if (gm.multMode) this.resolveMultWin(gm);
|
||||
else player.externalCallResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMultWin(gm: GameManager) {
|
||||
const sum = (names: string[]) =>
|
||||
names.reduce((t, n) => t + (gm.findNodeByName(n)?.getComponent(PlayerController)?.coins ?? 0), 0);
|
||||
const totalA = sum(['PlayerA1', 'PlayerA2', 'PlayerA3']);
|
||||
const totalB = sum(['PlayerB1', 'PlayerB2', 'PlayerB3']);
|
||||
const winA = totalA > totalB || (totalA === totalB && gm.stepA1Num + gm.stepA2Num + gm.stepA3Num <
|
||||
gm.stepB1Num + gm.stepB2Num + gm.stepB3Num);
|
||||
const set = (names: string[], win: boolean) => {
|
||||
for (const n of names) gm.findNodeByName(n)?.getComponent(PlayerController)?.externalCallResult(win);
|
||||
};
|
||||
if (winA) {
|
||||
set(['PlayerA1', 'PlayerA2', 'PlayerA3'], true);
|
||||
set(['PlayerB1', 'PlayerB2', 'PlayerB3'], false);
|
||||
} else {
|
||||
set(['PlayerA1', 'PlayerA2', 'PlayerA3'], false);
|
||||
set(['PlayerB1', 'PlayerB2', 'PlayerB3'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/PropController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "7b8d3aef-659e-486a-8e93-a08689bed871",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
114
assets/scripts/controller/VehicleController.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { _decorator, Vec3 } from 'cc';
|
||||
import { Direction, GridType } from '../core/Define';
|
||||
import { EventManager, EventType } from '../core/EventManager';
|
||||
import { JsBridge } from '../bridge/JsBridge';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
import { Movement } from '../gameplay/Movement';
|
||||
import { PlayerController, ExternalDataList } from './PlayerController';
|
||||
import { VisualAssets } from '../visual/VisualAssets';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
@ccclass('VehicleController')
|
||||
export class VehicleController extends Movement {
|
||||
private player: PlayerController | null = null;
|
||||
|
||||
onLoad() {
|
||||
this.moverRole = 'vehicle';
|
||||
this.moveState = 0;
|
||||
}
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
this.setIcon();
|
||||
}
|
||||
|
||||
setPlayer(p: PlayerController | null) {
|
||||
this.player = p;
|
||||
}
|
||||
|
||||
setPosition(pos: Vec3) {
|
||||
this.node.setPosition(pos);
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
/* 可挂 Label 显示名称 */
|
||||
}
|
||||
|
||||
override setDirection(dir: Direction) {
|
||||
super.setDirection(dir);
|
||||
this.setIcon();
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
this.player?.setDirection(dir);
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
setIcon() {
|
||||
const style = GameManager.instance?.uiStyle ?? 'default';
|
||||
VisualAssets.applyVehicleSprite(this.node, this.direction, style);
|
||||
}
|
||||
|
||||
protected onMoveNextSet(isJump: boolean) {
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
this.player?.syncFromVehicle(this.targetGridType, isJump);
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
protected onMoving() {
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
if (this.player) {
|
||||
this.player.onMoving();
|
||||
this.player.setPosition(this.node.worldPosition);
|
||||
}
|
||||
Movement.callEach = false;
|
||||
}
|
||||
|
||||
protected onMoveToTarget() {
|
||||
GameManager.instance!.removeObj(this.lastPosition);
|
||||
GameManager.instance!.addObj(this.node.worldPosition, GridType.Ride, this.node);
|
||||
if (Movement.callEach) return;
|
||||
Movement.callEach = true;
|
||||
if (this.player) {
|
||||
this.player.setPosition(this.node.worldPosition);
|
||||
this.player.syncMoveToTargetFromVehicle();
|
||||
}
|
||||
Movement.callEach = false;
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
protected onMoveFail(isJump: boolean) {
|
||||
if (!GameManager.instance!.multMode) {
|
||||
EventManager.dispatch(EventType.InputEnd, GameManager.instance!.gameState);
|
||||
}
|
||||
}
|
||||
|
||||
callVehicleInfo() {
|
||||
this.externalCall();
|
||||
}
|
||||
|
||||
externalCall() {
|
||||
const gm = GameManager.instance!;
|
||||
const list: ExternalDataList = { direction: this.direction, externalDatas: [] };
|
||||
const self = gm.worldToCell(this.node.worldPosition);
|
||||
list.externalDatas.push({
|
||||
position: { x: self.x, y: self.y, z: 0 },
|
||||
gridType: this.curGrid,
|
||||
direction: 'self',
|
||||
});
|
||||
for (let d = Direction.North; d <= Direction.West; d++) {
|
||||
const wp = gm.nextGridPosition(this.node.worldPosition, d);
|
||||
const cell = gm.worldToCell(wp);
|
||||
list.externalDatas.push({
|
||||
position: { x: cell.x, y: cell.y, z: 0 },
|
||||
gridType: gm.calculateNextGridType(this.node.worldPosition, d),
|
||||
direction: gm.getRelativePosition(this.direction, d),
|
||||
});
|
||||
}
|
||||
const json = JSON.stringify(list);
|
||||
if (gm.multMode) JsBridge.call(`process${this.node.name}`, json);
|
||||
else JsBridge.call('processVehicleData', json);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/controller/VehicleController.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "aa054a89-5920-470a-aac7-9af275a2d789",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/core.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "f398f642-6b7c-47cf-9e92-19d6cf61c05c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
152
assets/scripts/core/Define.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/** 与 Unity Platformer.Core.Define 对齐 */
|
||||
|
||||
export enum Direction {
|
||||
North = 0,
|
||||
East = 1,
|
||||
South = 2,
|
||||
West = 3,
|
||||
}
|
||||
|
||||
export enum MoveState {
|
||||
Idle = 0,
|
||||
Moving = 1,
|
||||
}
|
||||
|
||||
export enum GridType {
|
||||
Across = 0,
|
||||
Jump = 1,
|
||||
Block = 2,
|
||||
Ride = 3,
|
||||
None = 4,
|
||||
Boundary = 5,
|
||||
}
|
||||
|
||||
export enum Skin {
|
||||
Silu = 0,
|
||||
Panda = 1,
|
||||
RedArmy = 2,
|
||||
numMan = 3,
|
||||
snow = 4,
|
||||
sanxing = 5,
|
||||
}
|
||||
|
||||
export enum GameState {
|
||||
Run = 0,
|
||||
ResultWin = 1,
|
||||
ResultFail = 2,
|
||||
}
|
||||
|
||||
export type MoverRole = 'player' | 'vehicle';
|
||||
|
||||
export const CELL_SIZE = 1;
|
||||
|
||||
export const CommonDefine = {
|
||||
TilemapGround: 'Ground',
|
||||
TilemapBorder: 'Border',
|
||||
Prop: 'Prop',
|
||||
Vehicle: 'Vehicle',
|
||||
BlockBase: 'Baseblock',
|
||||
BlockJump: 'JumpBlock',
|
||||
};
|
||||
|
||||
export function addDirection(dir: Direction, delta: number): Direction {
|
||||
const n = 4;
|
||||
return (((dir + delta) % n) + n) % n as Direction;
|
||||
}
|
||||
|
||||
type MoveKey = string;
|
||||
|
||||
function mk(role: MoverRole, cur: GridType, next: GridType, jump: boolean): MoveKey {
|
||||
return `${role}|${cur}|${next}|${jump ? 1 : 0}`;
|
||||
}
|
||||
|
||||
function buildMoveTable(entries: [MoverRole, GridType, GridType, boolean, number][]): Map<MoveKey, number> {
|
||||
const m = new Map<MoveKey, number>();
|
||||
for (const [role, cur, next, jump, v] of entries) {
|
||||
m.set(mk(role, cur, next, jump), v);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/** 单人 moveCondition */
|
||||
export const moveCondition = buildMoveTable([
|
||||
['player', GridType.Across, GridType.Across, false, 1],
|
||||
['player', GridType.Jump, GridType.Across, false, 1],
|
||||
['player', GridType.Jump, GridType.Across, true, 1],
|
||||
['player', GridType.Ride, GridType.Across, false, 1],
|
||||
['player', GridType.Ride, GridType.Across, true, 1],
|
||||
['player', GridType.Across, GridType.Jump, true, 1],
|
||||
['player', GridType.Across, GridType.Ride, false, 1],
|
||||
['player', GridType.Across, GridType.Ride, true, 1],
|
||||
['player', GridType.Jump, GridType.Ride, false, 1],
|
||||
['player', GridType.Jump, GridType.Ride, true, 1],
|
||||
['player', GridType.Jump, GridType.Jump, false, 1],
|
||||
['player', GridType.Jump, GridType.Jump, true, 1],
|
||||
['player', GridType.Ride, GridType.None, false, 1],
|
||||
['player', GridType.Ride, GridType.None, true, 1],
|
||||
['player', GridType.None, GridType.None, false, 1],
|
||||
['player', GridType.None, GridType.None, true, 1],
|
||||
['player', GridType.None, GridType.Across, false, 1],
|
||||
['player', GridType.None, GridType.Across, true, 1],
|
||||
['vehicle', GridType.None, GridType.None, false, 1],
|
||||
['vehicle', GridType.None, GridType.None, true, 1],
|
||||
['vehicle', GridType.Ride, GridType.None, false, 1],
|
||||
]);
|
||||
|
||||
/** 多人 moveConditionMult(与 Unity Define.moveConditionMult 一致) */
|
||||
const multEntries: [MoverRole, GridType, GridType, boolean, number][] = [
|
||||
['player', GridType.Across, GridType.Across, false, 1],
|
||||
['player', GridType.Across, GridType.Across, true, 1],
|
||||
['player', GridType.Jump, GridType.Across, false, 1],
|
||||
['player', GridType.Jump, GridType.Across, true, 1],
|
||||
['player', GridType.Ride, GridType.Across, false, 1],
|
||||
['player', GridType.Ride, GridType.Across, true, 1],
|
||||
['player', GridType.Across, GridType.Jump, true, 1],
|
||||
['player', GridType.Across, GridType.Jump, false, 1],
|
||||
['player', GridType.Across, GridType.Ride, false, 1],
|
||||
['player', GridType.Across, GridType.Ride, true, 1],
|
||||
['player', GridType.Jump, GridType.Ride, false, 1],
|
||||
['player', GridType.Jump, GridType.Ride, true, 1],
|
||||
['player', GridType.Jump, GridType.Jump, false, 1],
|
||||
['player', GridType.Jump, GridType.Jump, true, 1],
|
||||
['player', GridType.Ride, GridType.None, false, 1],
|
||||
['player', GridType.Ride, GridType.None, true, 1],
|
||||
['player', GridType.None, GridType.None, false, 1],
|
||||
['player', GridType.None, GridType.None, true, 1],
|
||||
['player', GridType.None, GridType.Across, false, 1],
|
||||
['player', GridType.None, GridType.Across, true, 1],
|
||||
['player', GridType.Across, GridType.Block, false, 0],
|
||||
['player', GridType.Across, GridType.Block, true, 0],
|
||||
['vehicle', GridType.Ride, GridType.Block, false, 0],
|
||||
['vehicle', GridType.Ride, GridType.Block, true, 0],
|
||||
['vehicle', GridType.Ride, GridType.Across, false, 0],
|
||||
['vehicle', GridType.Ride, GridType.Across, true, 0],
|
||||
['vehicle', GridType.None, GridType.None, false, 1],
|
||||
['vehicle', GridType.None, GridType.None, true, 1],
|
||||
['vehicle', GridType.Ride, GridType.None, false, 1],
|
||||
['vehicle', GridType.Ride, GridType.None, true, 1],
|
||||
];
|
||||
export const moveConditionMultMap = buildMoveTable(multEntries);
|
||||
|
||||
export const skinPath: Record<Skin, { unlockLevel: number }> = {
|
||||
[Skin.Silu]: { unlockLevel: 0 },
|
||||
[Skin.Panda]: { unlockLevel: 400 },
|
||||
[Skin.RedArmy]: { unlockLevel: 800 },
|
||||
[Skin.numMan]: { unlockLevel: 1200 },
|
||||
[Skin.snow]: { unlockLevel: 1600 },
|
||||
[Skin.sanxing]: { unlockLevel: 1600 },
|
||||
};
|
||||
|
||||
export function getMoveCondition(mult: boolean): Map<MoveKey, number> {
|
||||
return mult ? moveConditionMultMap : moveCondition;
|
||||
}
|
||||
|
||||
export function lookupMove(
|
||||
table: Map<MoveKey, number>,
|
||||
role: MoverRole,
|
||||
cur: GridType,
|
||||
next: GridType,
|
||||
jump: boolean,
|
||||
): number | undefined {
|
||||
return table.get(mk(role, cur, next, jump));
|
||||
}
|
||||
9
assets/scripts/core/Define.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e5d9eb64-9856-42f8-b14c-ab18ad6ff50e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
27
assets/scripts/core/EventManager.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export enum EventType {
|
||||
LevelInit = 'LevelInit',
|
||||
InputEnd = 'InputEnd',
|
||||
}
|
||||
|
||||
type Handler = (...args: unknown[]) => void;
|
||||
|
||||
const listeners = new Map<EventType, Handler[]>();
|
||||
|
||||
export const EventManager = {
|
||||
register(type: EventType, handler: Handler) {
|
||||
if (!listeners.has(type)) listeners.set(type, []);
|
||||
const list = listeners.get(type)!;
|
||||
if (!list.includes(handler)) list.push(handler);
|
||||
},
|
||||
remove(type: EventType, handler: Handler) {
|
||||
const list = listeners.get(type);
|
||||
if (!list) return;
|
||||
const i = list.indexOf(handler);
|
||||
if (i >= 0) list.splice(i, 1);
|
||||
},
|
||||
dispatch(type: EventType, ...args: unknown[]) {
|
||||
const list = listeners.get(type);
|
||||
if (!list) return;
|
||||
for (const h of [...list]) h(...args);
|
||||
},
|
||||
};
|
||||
9
assets/scripts/core/EventManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d101e574-7e0a-4ccf-9ead-12d3872afe82",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/gameplay.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "189e768a-9029-4554-9763-dd1099995a8d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
178
assets/scripts/gameplay/Movement.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { _decorator, Component, Vec3 } from 'cc';
|
||||
import {
|
||||
Direction, GameState, GridType, MoveState, MoverRole,
|
||||
addDirection, getMoveCondition, lookupMove,
|
||||
} from '../core/Define';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('Movement')
|
||||
export class Movement extends Component {
|
||||
@property
|
||||
moveSpeed = 4;
|
||||
|
||||
direction: Direction = Direction.North;
|
||||
moveState: MoveState = MoveState.Idle;
|
||||
moverRole: MoverRole = 'player';
|
||||
|
||||
protected targetPosition = new Vec3();
|
||||
protected targetGridType: GridType = GridType.None;
|
||||
protected lastPosition = new Vec3();
|
||||
protected step = 0;
|
||||
|
||||
private moveWait = false;
|
||||
private queue: Promise<void> = Promise.resolve();
|
||||
static callEach = false;
|
||||
|
||||
get curGrid(): GridType {
|
||||
return GameManager.instance!.calculateGridType(this.node.position);
|
||||
}
|
||||
get nextGrid(): GridType {
|
||||
return GameManager.instance!.calculateNextGridType(this.node.position, this.direction);
|
||||
}
|
||||
get lastGrid(): GridType {
|
||||
return GameManager.instance!.calculateLastGridType(this.node.position, this.direction);
|
||||
}
|
||||
get isFront(): boolean {
|
||||
return this.direction === Direction.South || this.direction === Direction.East;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.setDirection(this.direction);
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
if (this.moveState !== MoveState.Moving) return;
|
||||
const pos = this.node.position;
|
||||
const next = new Vec3();
|
||||
Vec3.moveTowards(next, pos, this.targetPosition, this.moveSpeed * dt);
|
||||
this.node.setPosition(next);
|
||||
this.onMoving();
|
||||
if (Vec3.distance(next, this.targetPosition) < 0.01) {
|
||||
this.node.setPosition(this.targetPosition);
|
||||
this.moveState = MoveState.Idle;
|
||||
this.moveWait = false;
|
||||
this.onMoveToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
setDirection(dir: Direction) {
|
||||
this.direction = dir;
|
||||
}
|
||||
|
||||
protected onMoving() {}
|
||||
protected onMoveToTarget() {}
|
||||
protected onMoveNextSet(_isJump: boolean) {}
|
||||
protected onMoveFail(_isJump: boolean) {}
|
||||
protected playMoveAnim() {}
|
||||
|
||||
private moveNextCheck(isJump: boolean, toFront: boolean): number {
|
||||
const gm = GameManager.instance!;
|
||||
const dir = toFront ? this.direction : addDirection(this.direction, 2);
|
||||
const targetTmp = gm.nextGridPosition(this.node.position, dir);
|
||||
const targetType = gm.calculateGridType(targetTmp);
|
||||
const table = getMoveCondition(gm.multMode);
|
||||
const nextG = toFront ? this.nextGrid : this.lastGrid;
|
||||
const v = lookupMove(table, this.moverRole, this.curGrid, nextG, isJump);
|
||||
if (v !== undefined) {
|
||||
if (v === 1) {
|
||||
if (gm.multMode) {
|
||||
const nextCell = gm.worldToCell(targetTmp);
|
||||
for (const n of ['PlayerA1', 'PlayerA2', 'PlayerA3', 'PlayerB1', 'PlayerB2', 'PlayerB3']) {
|
||||
const p = gm.findNodeByName(n);
|
||||
if (p) {
|
||||
const c = gm.worldToCell(p.worldPosition);
|
||||
if (c.x === nextCell.x && c.y === nextCell.y) return 0;
|
||||
}
|
||||
}
|
||||
if (nextG === GridType.Ride) {
|
||||
const ride = gm.getGameObject(targetTmp);
|
||||
const expect = this.node.name.replace('Player', 'Vehicle');
|
||||
if (ride && ride.name !== expect) return 0;
|
||||
}
|
||||
}
|
||||
this.targetPosition.set(targetTmp);
|
||||
this.targetGridType = targetType;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
return gm.multMode ? 0 : -1;
|
||||
}
|
||||
|
||||
private enqueue(fn: () => Promise<void>) {
|
||||
this.queue = this.queue.then(fn).catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
private waitUntil(cond: () => boolean): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const tick = () => {
|
||||
if (cond()) resolve();
|
||||
else requestAnimationFrame(tick);
|
||||
};
|
||||
tick();
|
||||
});
|
||||
}
|
||||
|
||||
private async moveCoroutine(n: number, isJump: boolean) {
|
||||
await this.waitUntil(() => !this.moveWait);
|
||||
const toFront = n > 0;
|
||||
this.step = Math.abs(n);
|
||||
const gm = GameManager.instance!;
|
||||
while (this.step > 0 && gm.gameState === GameState.Run) {
|
||||
this.step--;
|
||||
const r = this.moveNextCheck(isJump, toFront);
|
||||
if (r === -1) {
|
||||
this.onMoveFail(isJump);
|
||||
return;
|
||||
}
|
||||
if (r === 1) {
|
||||
this.moveWait = true;
|
||||
this.lastPosition.set(this.node.position);
|
||||
this.moveState = MoveState.Moving;
|
||||
this.onMoveNextSet(isJump);
|
||||
await this.waitUntil(() => !this.moveWait);
|
||||
} else {
|
||||
this.playMoveAnim();
|
||||
this.moveState = MoveState.Moving;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async rotateCoroutine(n: number) {
|
||||
await this.waitUntil(() => !this.moveWait);
|
||||
this.moveWait = true;
|
||||
this.setDirection(addDirection(this.direction, n));
|
||||
this.moveWait = false;
|
||||
this.onMoveToTarget();
|
||||
}
|
||||
|
||||
protected jsCallCheck(n: number): boolean {
|
||||
const gm = GameManager.instance!;
|
||||
const name = this.node.name;
|
||||
if (name === 'Player' || name === 'Vehicle') return gm.jsCallCheck(n);
|
||||
if (gm.multMode) return gm.jsCallCheckMultMode(n, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
callMove(n: number) {
|
||||
if (!this.jsCallCheck(n)) return;
|
||||
this.enqueue(() => this.moveCoroutine(n, false));
|
||||
}
|
||||
|
||||
callRotateLeft(n: number) {
|
||||
if (!this.jsCallCheck(n)) return;
|
||||
this.enqueue(() => this.rotateCoroutine(-n));
|
||||
}
|
||||
|
||||
callRotateRight(n: number) {
|
||||
if (!this.jsCallCheck(n)) return;
|
||||
this.enqueue(() => this.rotateCoroutine(n));
|
||||
}
|
||||
|
||||
callJump() {
|
||||
if (this.moverRole !== 'player') return;
|
||||
if (!this.jsCallCheck(1)) return;
|
||||
this.enqueue(() => this.moveCoroutine(1, true));
|
||||
}
|
||||
}
|
||||
9
assets/scripts/gameplay/Movement.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "5fdb4109-5d6a-4002-90f9-a7568122ac93",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/level.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "95df233f-f174-4e12-a4f4-cc94d163a21d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
47
assets/scripts/level/LevelRegistry.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Direction } from '../core/Define';
|
||||
import { LevelConfig } from './LevelTypes';
|
||||
import { LEVELS_600 } from './levels-600.generated';
|
||||
|
||||
/** 额外关卡(多人等) */
|
||||
const EXTRA_LEVELS: Record<number, LevelConfig> = {
|
||||
601: {
|
||||
levelID: 601,
|
||||
boundary: { x: 20, y: 20 },
|
||||
spawns: [
|
||||
{ x: 0, y: 0, kind: 'player', playerDirection: Direction.North },
|
||||
{ x: 6, y: 6, kind: 'prop' },
|
||||
{ x: -1, y: 2, kind: 'prop' },
|
||||
],
|
||||
},
|
||||
999001: {
|
||||
levelID: 999001,
|
||||
boundary: { x: 999, y: 999 },
|
||||
spawns: [
|
||||
{ x: -9, y: -9, kind: 'player', playerDirection: Direction.South },
|
||||
{ x: 9, y: 9, kind: 'player', playerDirection: Direction.North },
|
||||
{ x: -9, y: -10, kind: 'vehicle', vehicleDirection: Direction.North },
|
||||
{ x: 9, y: 10, kind: 'vehicle', vehicleDirection: Direction.South },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const allLevels: Record<number, LevelConfig> = {
|
||||
...LEVELS_600,
|
||||
...EXTRA_LEVELS,
|
||||
};
|
||||
|
||||
export function getLevelConfig(levelID: number): LevelConfig | null {
|
||||
return allLevels[levelID] ?? null;
|
||||
}
|
||||
|
||||
export function hasLevel(levelID: number): boolean {
|
||||
return levelID in allLevels;
|
||||
}
|
||||
|
||||
export function registerLevel(config: LevelConfig) {
|
||||
allLevels[config.levelID] = config;
|
||||
}
|
||||
|
||||
export function getLevelCount(): number {
|
||||
return Object.keys(allLevels).length;
|
||||
}
|
||||
9
assets/scripts/level/LevelRegistry.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "05e4b37c-f71a-42c0-b8f6-88fda70b655d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
22
assets/scripts/level/LevelTypes.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Direction } from '../core/Define';
|
||||
|
||||
export type SpawnKind = 'player' | 'vehicle' | 'prop' | 'prop_decor' | 'enemy';
|
||||
|
||||
export interface SpawnConfig {
|
||||
x: number;
|
||||
y: number;
|
||||
kind: SpawnKind;
|
||||
playerDirection?: Direction;
|
||||
vehicleDirection?: Direction;
|
||||
}
|
||||
|
||||
/** 稀疏地块:key 为 "x,y" */
|
||||
export interface LevelConfig {
|
||||
levelID: number;
|
||||
boundary: { x: number; y: number };
|
||||
spawns: SpawnConfig[];
|
||||
/** Ground 层 tile 名 */
|
||||
ground?: Record<string, string>;
|
||||
/** Border 阻挡格 */
|
||||
border?: Record<string, boolean>;
|
||||
}
|
||||
9
assets/scripts/level/LevelTypes.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "9fd69354-fd48-4ecf-9bd3-2cb056258612",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
2022
assets/scripts/level/border-cache.json
Normal file
11
assets/scripts/level/border-cache.json.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "2.0.1",
|
||||
"importer": "json",
|
||||
"imported": true,
|
||||
"uuid": "c5bd9bd8-738e-4bbc-8f04-8b1c77279929",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
7858
assets/scripts/level/levels-600.generated.ts
Normal file
9
assets/scripts/level/levels-600.generated.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "507007a3-8bd2-41d5-b962-44c38a653bbb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/manager.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "f289d7c6-e9b4-45e2-9068-532f08608094",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
388
assets/scripts/manager/GameManager.ts
Normal file
@@ -0,0 +1,388 @@
|
||||
import { _decorator, Component, Node, Vec3, Color, Graphics, UITransform, director } from 'cc';
|
||||
import { CELL_PIXEL } from '../AppBootstrap';
|
||||
import {
|
||||
CELL_SIZE, CommonDefine, Direction, GameState, GridType, Skin, addDirection,
|
||||
} from '../core/Define';
|
||||
import { EventManager, EventType } from '../core/EventManager';
|
||||
import { JsBridge } from '../bridge/JsBridge';
|
||||
import { getLevelConfig, hasLevel, registerLevel } from '../level/LevelRegistry';
|
||||
import { LevelConfig, SpawnConfig } from '../level/LevelTypes';
|
||||
import { PlayerController } from '../controller/PlayerController';
|
||||
import { VehicleController } from '../controller/VehicleController';
|
||||
import { PropController } from '../controller/PropController';
|
||||
import { VisualAssets } from '../visual/VisualAssets';
|
||||
import { SpawnKind } from '../level/LevelTypes';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
interface GridEntry {
|
||||
type: GridType;
|
||||
node: Node;
|
||||
}
|
||||
|
||||
@ccclass('GameManager')
|
||||
export class GameManager extends Component {
|
||||
static instance: GameManager | null = null;
|
||||
|
||||
@property(Node)
|
||||
mainLevelEntrance: Node | null = null;
|
||||
|
||||
@property
|
||||
initialLevelID = 1;
|
||||
|
||||
playerSkin: Skin = Skin.Silu;
|
||||
multMode = false;
|
||||
multPlayerRole = '';
|
||||
gameState: GameState = GameState.Run;
|
||||
isInputEnd = false;
|
||||
uiStyle = 'default';
|
||||
curLevelID = 1;
|
||||
|
||||
stepNum = 0;
|
||||
stepA1Num = 0;
|
||||
stepA2Num = 0;
|
||||
stepA3Num = 0;
|
||||
stepB1Num = 0;
|
||||
stepB2Num = 0;
|
||||
stepB3Num = 0;
|
||||
|
||||
private creating = false;
|
||||
private curLevel: Node | null = null;
|
||||
private curConfig: LevelConfig | null = null;
|
||||
private gridTypes = new Map<string, GridEntry>();
|
||||
private gridTypesForProps = new Map<string, GridEntry>();
|
||||
private groundCells = new Map<string, string>();
|
||||
private borderCells = new Set<string>();
|
||||
|
||||
onLoad() {
|
||||
if (GameManager.instance && GameManager.instance !== this) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
GameManager.instance = this;
|
||||
}
|
||||
|
||||
start() {
|
||||
/* 关卡由 AppBootstrap 在就绪后加载 */
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
if (GameManager.instance === this) GameManager.instance = null;
|
||||
}
|
||||
|
||||
// --- 状态 ---
|
||||
setGameState(s: GameState) { this.gameState = s; }
|
||||
|
||||
initData() {
|
||||
this.setGameState(GameState.Run);
|
||||
this.stepNum = 0;
|
||||
this.stepA1Num = this.stepA2Num = this.stepA3Num = 0;
|
||||
this.stepB1Num = this.stepB2Num = this.stepB3Num = 0;
|
||||
this.callSetIsInputEnd(0);
|
||||
}
|
||||
|
||||
jsCallCheck(n: number): boolean {
|
||||
if (this.isInputEnd || this.gameState !== GameState.Run) return false;
|
||||
this.stepNum += Math.abs(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
jsCallCheckMultMode(n: number, name: string): boolean {
|
||||
if (this.isInputEnd || this.gameState !== GameState.Run) return false;
|
||||
const a = Math.abs(n);
|
||||
switch (name) {
|
||||
case 'PlayerA1':
|
||||
case 'VehicleA1': this.stepA1Num += a; break;
|
||||
case 'PlayerA2':
|
||||
case 'VehicleA2': this.stepA2Num += a; break;
|
||||
case 'PlayerA3':
|
||||
case 'VehicleA3': this.stepA3Num += a; break;
|
||||
case 'PlayerB1':
|
||||
case 'VehicleB1': this.stepB1Num += a; break;
|
||||
case 'PlayerB2':
|
||||
case 'VehicleB2': this.stepB2Num += a; break;
|
||||
case 'PlayerB3':
|
||||
case 'VehicleB3': this.stepB3Num += a; break;
|
||||
default: break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
callSetIsInputEnd(v: number) {
|
||||
this.isInputEnd = v !== 0;
|
||||
if (this.isInputEnd) EventManager.dispatch(EventType.InputEnd, this.gameState);
|
||||
}
|
||||
|
||||
getRelativePosition(given: Direction, input: Direction): string {
|
||||
const rel = (((input - given) % 4) + 4) % 4;
|
||||
return ['front', 'right', 'back', 'left'][rel];
|
||||
}
|
||||
|
||||
// --- 坐标 ---
|
||||
cellKey(x: number, y: number) { return `${x},${y}`; }
|
||||
|
||||
cellToWorld(cell: Vec3): Vec3 {
|
||||
return new Vec3(cell.x * CELL_PIXEL, cell.y * CELL_PIXEL, 0);
|
||||
}
|
||||
|
||||
worldToCell(world: Vec3): Vec3 {
|
||||
return new Vec3(Math.round(world.x / CELL_PIXEL), Math.round(world.y / CELL_PIXEL), 0);
|
||||
}
|
||||
|
||||
nextGridPosition(pos: Vec3, dir: Direction): Vec3 {
|
||||
const c = this.worldToCell(pos);
|
||||
switch (dir) {
|
||||
case Direction.North: c.x += 1; break;
|
||||
case Direction.South: c.x -= 1; break;
|
||||
case Direction.East: c.y -= 1; break;
|
||||
case Direction.West: c.y += 1; break;
|
||||
}
|
||||
return this.cellToWorld(c);
|
||||
}
|
||||
|
||||
calculateGridType(pos: Vec3): GridType {
|
||||
const cell = this.worldToCell(pos);
|
||||
const key = this.cellKey(cell.x, cell.y);
|
||||
const dyn = this.gridTypes.get(key);
|
||||
if (dyn) return dyn.type;
|
||||
if (this.borderCells.has(key)) return GridType.Block;
|
||||
const g = this.groundCells.get(key);
|
||||
if (g === CommonDefine.BlockBase) return GridType.Across;
|
||||
if (g === CommonDefine.BlockJump) return GridType.Jump;
|
||||
const b = this.curConfig?.boundary;
|
||||
if (b && (Math.abs(cell.x) >= b.x || Math.abs(cell.y) >= b.y)) return GridType.Boundary;
|
||||
return GridType.None;
|
||||
}
|
||||
|
||||
calculateNextGridType(pos: Vec3, dir: Direction): GridType {
|
||||
return this.calculateGridType(this.nextGridPosition(pos, dir));
|
||||
}
|
||||
|
||||
calculateLastGridType(pos: Vec3, dir: Direction): GridType {
|
||||
return this.calculateGridType(this.nextGridPosition(pos, addDirection(dir, 2)));
|
||||
}
|
||||
|
||||
getGameObject(pos: Vec3): Node | null {
|
||||
const key = this.cellKey(this.worldToCell(pos).x, this.worldToCell(pos).y);
|
||||
return this.gridTypes.get(key)?.node ?? null;
|
||||
}
|
||||
|
||||
addObj(pos: Vec3, type: GridType, node: Node) {
|
||||
const c = this.worldToCell(pos);
|
||||
this.gridTypes.set(this.cellKey(c.x, c.y), { type, node });
|
||||
}
|
||||
|
||||
removeObj(pos: Vec3) {
|
||||
const c = this.worldToCell(pos);
|
||||
this.gridTypes.delete(this.cellKey(c.x, c.y));
|
||||
}
|
||||
|
||||
removeProp(pos: Vec3) {
|
||||
const c = this.worldToCell(pos);
|
||||
this.gridTypesForProps.delete(this.cellKey(c.x, c.y));
|
||||
}
|
||||
|
||||
countProp(): number {
|
||||
if (!this.curLevel) return 0;
|
||||
return this.curLevel.children.filter((c) => c.isValid && c.getComponent(PropController)).length;
|
||||
}
|
||||
|
||||
getCurLevel() { return this.curConfig; }
|
||||
|
||||
findNodeByName(name: string): Node | null {
|
||||
const scene = director.getScene();
|
||||
if (!scene) return null;
|
||||
return this.findInTree(scene, name);
|
||||
}
|
||||
|
||||
private findInTree(root: Node, name: string): Node | null {
|
||||
if (root.name === name) return root;
|
||||
for (const ch of root.children) {
|
||||
const f = this.findInTree(ch, name);
|
||||
if (f) return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- 关卡 ---
|
||||
destroyCurLevel() {
|
||||
if (this.curLevel?.isValid) this.curLevel.destroy();
|
||||
this.curLevel = null;
|
||||
this.gridTypes.clear();
|
||||
this.gridTypesForProps.clear();
|
||||
this.groundCells.clear();
|
||||
this.borderCells.clear();
|
||||
}
|
||||
|
||||
switchLevel(levelID: number) {
|
||||
if (!hasLevel(levelID) || this.creating) return;
|
||||
this.multMode = levelID >= 999000;
|
||||
this.destroyCurLevel();
|
||||
this.createNewLevel(levelID);
|
||||
}
|
||||
|
||||
async createNewLevel(levelID: number) {
|
||||
if (this.creating) return;
|
||||
this.creating = true;
|
||||
const config = getLevelConfig(levelID);
|
||||
if (!config || !this.mainLevelEntrance) {
|
||||
this.creating = false;
|
||||
return;
|
||||
}
|
||||
this.curLevelID = levelID;
|
||||
this.curConfig = config;
|
||||
|
||||
const levelRoot = new Node(`Level_${levelID}`);
|
||||
levelRoot.parent = this.mainLevelEntrance;
|
||||
this.curLevel = levelRoot;
|
||||
|
||||
if (config.ground) {
|
||||
for (const [k, v] of Object.entries(config.ground)) this.groundCells.set(k, v);
|
||||
}
|
||||
if (config.border) {
|
||||
for (const k of Object.keys(config.border)) this.borderCells.add(k);
|
||||
}
|
||||
|
||||
this.drawGridDebug(levelRoot, config);
|
||||
|
||||
const spawned: Node[] = [];
|
||||
for (const s of config.spawns) {
|
||||
const node = this.spawnEntity(levelRoot, s);
|
||||
if (node) spawned.push(node);
|
||||
}
|
||||
|
||||
this.initGridTypes();
|
||||
this.initData();
|
||||
EventManager.dispatch(EventType.LevelInit);
|
||||
this.externalCallLevelInfo(spawned);
|
||||
this.creating = false;
|
||||
}
|
||||
|
||||
resetLevel() {
|
||||
this.destroyCurLevel();
|
||||
this.createNewLevel(this.curLevelID);
|
||||
}
|
||||
|
||||
startMultPlay(role: string, coinsJson: string) {
|
||||
this.multMode = true;
|
||||
this.multPlayerRole = role;
|
||||
let coins: string[] = [];
|
||||
try {
|
||||
coins = JSON.parse(coinsJson) as string[];
|
||||
} catch (e) {
|
||||
console.error('StartMultPlay coins parse', e);
|
||||
}
|
||||
const base = getLevelConfig(999001);
|
||||
if (!base) return;
|
||||
const extra = coins.map((s) => {
|
||||
const [xs, ys] = s.split(',');
|
||||
return { x: parseInt(xs, 10), y: parseInt(ys, 10), kind: 'prop' as const };
|
||||
});
|
||||
registerLevel({ ...base, spawns: [...base.spawns, ...extra] });
|
||||
this.switchLevel(999001);
|
||||
}
|
||||
|
||||
changeUIStyle(style: string) {
|
||||
this.uiStyle = style;
|
||||
}
|
||||
|
||||
callMute() { /* 可由 UIMain 实现 */ }
|
||||
callUnmute() { /* 可由 UIMain 实现 */ }
|
||||
|
||||
private initGridTypes() {
|
||||
this.gridTypes.clear();
|
||||
this.gridTypesForProps.clear();
|
||||
if (!this.curLevel) return;
|
||||
const vehicles = this.curLevel.children.filter((c) => c.name.includes('Vehicle'));
|
||||
for (const v of vehicles) {
|
||||
const c = this.worldToCell(v.worldPosition);
|
||||
this.gridTypes.set(this.cellKey(c.x, c.y), { type: GridType.Ride, node: v });
|
||||
}
|
||||
const props = this.curLevel.children.filter((c) => c.getComponent(PropController));
|
||||
for (const p of props) {
|
||||
const c = this.worldToCell(p.worldPosition);
|
||||
this.gridTypesForProps.set(this.cellKey(c.x, c.y), {
|
||||
type: this.calculateGridType(p.worldPosition),
|
||||
node: p,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private spawnEntity(parent: Node, s: SpawnConfig): Node | null {
|
||||
const pos = this.cellToWorld(new Vec3(s.x, s.y, 0));
|
||||
let node: Node;
|
||||
if (s.kind === 'player') {
|
||||
node = new Node(s.x === -9 ? 'PlayerA1' : s.x === 9 ? 'PlayerB1' : 'Player');
|
||||
node.addComponent(PlayerController);
|
||||
const pc = node.getComponent(PlayerController)!;
|
||||
pc.direction = s.playerDirection ?? Direction.South;
|
||||
} else if (s.kind === 'vehicle') {
|
||||
node = new Node(s.x < 0 ? 'VehicleA1' : 'VehicleB1');
|
||||
node.addComponent(VehicleController);
|
||||
const vc = node.getComponent(VehicleController)!;
|
||||
vc.direction = s.vehicleDirection ?? Direction.North;
|
||||
} else if (s.kind === 'prop') {
|
||||
node = new Node('Prop');
|
||||
node.addComponent(PropController);
|
||||
} else if (s.kind === 'prop_decor') {
|
||||
node = new Node('PropDecor');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
this.attachVisual(node, s.kind, s.playerDirection, s.vehicleDirection);
|
||||
node.setPosition(pos);
|
||||
const ui = node.getComponent(UITransform) || node.addComponent(UITransform);
|
||||
ui.setContentSize(CELL_PIXEL * 0.9, CELL_PIXEL * 0.9);
|
||||
node.parent = parent;
|
||||
return node;
|
||||
}
|
||||
|
||||
private attachVisual(
|
||||
node: Node,
|
||||
kind: SpawnKind,
|
||||
playerDir?: Direction,
|
||||
vehicleDir?: Direction,
|
||||
) {
|
||||
const dir = kind === 'player' ? playerDir : kind === 'vehicle' ? vehicleDir : undefined;
|
||||
VisualAssets.setupEntityVisual(node, kind, dir);
|
||||
}
|
||||
|
||||
private drawGridDebug(root: Node, config: LevelConfig) {
|
||||
const tiles = new Node('Ground');
|
||||
tiles.parent = root;
|
||||
const bx = config.boundary.x;
|
||||
const by = config.boundary.y;
|
||||
const half = CELL_PIXEL * 0.45;
|
||||
for (let x = -bx + 1; x < bx; x++) {
|
||||
for (let y = -by + 1; y < by; y++) {
|
||||
const key = this.cellKey(x, y);
|
||||
if (this.borderCells.has(key)) continue;
|
||||
const p = this.cellToWorld(new Vec3(x, y, 0));
|
||||
const cell = new Node(`cell_${x}_${y}`);
|
||||
cell.parent = tiles;
|
||||
cell.setPosition(p);
|
||||
const cui = cell.addComponent(UITransform);
|
||||
cui.setContentSize(CELL_PIXEL * 0.9, CELL_PIXEL * 0.9);
|
||||
VisualAssets.applySprite(cell, 'tile', false, 1, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
externalCallLevelInfo(objects: Node[]) {
|
||||
const info: { LevelID: number; PlayerName: string; VehicleName: string } = {
|
||||
LevelID: this.curLevelID,
|
||||
PlayerName: '',
|
||||
VehicleName: '',
|
||||
};
|
||||
for (const obj of objects) {
|
||||
if (this.multMode) {
|
||||
if (obj.name === this.multPlayerRole) info.PlayerName = obj.name;
|
||||
else if (obj.name === this.multPlayerRole.replace('Player', 'Vehicle')) info.VehicleName = obj.name;
|
||||
} else {
|
||||
if (obj.name.includes('Player')) info.PlayerName = obj.name;
|
||||
if (obj.name.includes('Vehicle')) info.VehicleName = obj.name;
|
||||
}
|
||||
}
|
||||
JsBridge.call('externalLevelInfo', JSON.stringify(info));
|
||||
}
|
||||
}
|
||||
9
assets/scripts/manager/GameManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "bfc2e3c7-1217-4813-b019-2c0014cb1579",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/ui.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "e5583f3f-b1d5-4acb-b0ff-33493e54c023",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
20
assets/scripts/ui/UIMain.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { _decorator, Component } from 'cc';
|
||||
import { GameManager } from '../manager/GameManager';
|
||||
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
/** 对应 Unity UIMain,JS 可 SendMessage("UIMain", "SetText", ...) */
|
||||
@ccclass('UIMain')
|
||||
export class UIMain extends Component {
|
||||
private textVisible = false;
|
||||
private textContent = '';
|
||||
|
||||
setText(str: string) {
|
||||
this.textContent = str;
|
||||
console.log('[UIMain]', str);
|
||||
}
|
||||
|
||||
setTextActive(active: string) {
|
||||
this.textVisible = active === 'true';
|
||||
}
|
||||
}
|
||||
9
assets/scripts/ui/UIMain.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0585296d-ea8c-427b-8cc0-8d89a56719bb",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/scripts/visual.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "66712587-a8af-4a45-8c43-129ae3bc8ae7",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
147
assets/scripts/visual/VisualAssets.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
Node, Sprite, SpriteFrame, UITransform, resources, Color, Graphics,
|
||||
ImageAsset, Texture2D,
|
||||
} from 'cc';
|
||||
import { Direction } from '../core/Define';
|
||||
import { SpawnKind } from '../level/LevelTypes';
|
||||
|
||||
type SpriteKey = 'player_F' | 'player_B' | 'ship_F' | 'ship_B' | 'coin' | 'tile';
|
||||
|
||||
const PATHS: Record<SpriteKey, string> = {
|
||||
player_F: 'textures/silu/player_F',
|
||||
player_B: 'textures/silu/player_B',
|
||||
ship_F: 'textures/silu/ship_F',
|
||||
ship_B: 'textures/silu/ship_B',
|
||||
coin: 'textures/ui/coin',
|
||||
tile: 'textures/silu/Baseblock',
|
||||
};
|
||||
|
||||
export class VisualAssets {
|
||||
private static frames = new Map<SpriteKey, SpriteFrame>();
|
||||
private static loading: Promise<void> | null = null;
|
||||
|
||||
static async preload(): Promise<void> {
|
||||
if (this.loading) return this.loading;
|
||||
this.loading = (async () => {
|
||||
const keys = Object.keys(PATHS) as SpriteKey[];
|
||||
const results = await Promise.all(keys.map((k) => this.loadOne(k)));
|
||||
const ok = results.filter(Boolean).length;
|
||||
console.log(`[VisualAssets] 贴图加载 ${ok}/${keys.length}`);
|
||||
if (ok === 0) {
|
||||
console.warn('[VisualAssets] 未加载到贴图,将使用色块。请确认 assets/resources/textures 已导入');
|
||||
}
|
||||
})().catch((e) => {
|
||||
console.error('[VisualAssets] preload failed', e);
|
||||
this.loading = null;
|
||||
});
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
private static loadOne(key: SpriteKey): Promise<boolean> {
|
||||
if (this.frames.has(key)) return Promise.resolve(true);
|
||||
const base = PATHS[key];
|
||||
return new Promise((resolve) => {
|
||||
resources.load(`${base}/spriteFrame`, SpriteFrame, (err, sf) => {
|
||||
if (!err && sf) {
|
||||
this.frames.set(key, sf);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resources.load(base, SpriteFrame, (err2, sf2) => {
|
||||
if (!err2 && sf2) {
|
||||
this.frames.set(key, sf2);
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
resources.load(base, ImageAsset, (err3, img) => {
|
||||
if (!err3 && img) {
|
||||
const tex = new Texture2D();
|
||||
tex.image = img;
|
||||
const frame = new SpriteFrame();
|
||||
frame.texture = tex;
|
||||
this.frames.set(key, frame);
|
||||
resolve(true);
|
||||
} else {
|
||||
console.warn(`[VisualAssets] 加载失败: ${base}`, err3 || err2 || err);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static getFrame(key: SpriteKey): SpriteFrame | null {
|
||||
return this.frames.get(key) ?? null;
|
||||
}
|
||||
|
||||
static applyPlayerSprite(node: Node, direction: Direction) {
|
||||
const isFront = direction === Direction.South || direction === Direction.East;
|
||||
const flipX = direction === Direction.West || direction === Direction.East;
|
||||
const key: SpriteKey = isFront ? 'player_F' : 'player_B';
|
||||
this.applySprite(node, key, flipX);
|
||||
}
|
||||
|
||||
static applyVehicleSprite(node: Node, direction: Direction, uiStyle = 'default') {
|
||||
void uiStyle;
|
||||
const isFront = direction === Direction.South || direction === Direction.East;
|
||||
const flipX = direction === Direction.West || direction === Direction.East;
|
||||
const key: SpriteKey = isFront ? 'ship_F' : 'ship_B';
|
||||
this.applySprite(node, key, flipX);
|
||||
}
|
||||
|
||||
static setupEntityVisual(node: Node, kind: SpawnKind, direction?: Direction) {
|
||||
if (kind === 'player') {
|
||||
this.applyPlayerSprite(node, direction ?? Direction.South);
|
||||
return;
|
||||
}
|
||||
if (kind === 'vehicle') {
|
||||
this.applyVehicleSprite(node, direction ?? Direction.North);
|
||||
return;
|
||||
}
|
||||
if (kind === 'prop') {
|
||||
this.applySprite(node, 'coin', false, 0.85);
|
||||
return;
|
||||
}
|
||||
if (kind === 'prop_decor') {
|
||||
this.applySprite(node, 'coin', false, 0.6, 120);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sprite 与 Graphics 不能共存,二选一 */
|
||||
static applySprite(node: Node, key: SpriteKey, flipX: boolean, scale = 1, alpha = 255) {
|
||||
let ui = node.getComponent(UITransform);
|
||||
if (!ui) {
|
||||
ui = node.addComponent(UITransform);
|
||||
ui.setContentSize(48, 48);
|
||||
}
|
||||
|
||||
const sf = this.getFrame(key);
|
||||
const spr = node.getComponent(Sprite);
|
||||
const g = node.getComponent(Graphics);
|
||||
|
||||
if (sf) {
|
||||
if (g) g.destroy();
|
||||
const sprite = spr || node.addComponent(Sprite);
|
||||
sprite.spriteFrame = sf;
|
||||
sprite.sizeMode = Sprite.SizeMode.CUSTOM;
|
||||
const w = ui.contentSize.width * scale;
|
||||
const h = ui.contentSize.height * scale;
|
||||
ui.setContentSize(w, h);
|
||||
sprite.color = new Color(255, 255, 255, alpha);
|
||||
} else {
|
||||
if (spr) spr.destroy();
|
||||
const graphics = g || node.addComponent(Graphics);
|
||||
graphics.fillColor = key === 'coin'
|
||||
? new Color(255, 220, 0, alpha)
|
||||
: new Color(80, 160, 255, alpha);
|
||||
const w = ui.contentSize.width * 0.45 * scale;
|
||||
graphics.clear();
|
||||
graphics.rect(-w, -w, w * 2, w * 2);
|
||||
graphics.fill();
|
||||
}
|
||||
|
||||
const sx = flipX ? -Math.abs(scale) : Math.abs(scale);
|
||||
node.setScale(sx, Math.abs(scale), 1);
|
||||
}
|
||||
}
|
||||
9
assets/scripts/visual/VisualAssets.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c813fc15-e977-4bb5-9d75-abc460539690",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||