├── .gitignore ├── assets ├── Page.fire ├── Page.fire.meta ├── resources.meta ├── resources │ ├── book.jpg │ ├── book.jpg.meta │ ├── page1.png │ ├── page1.png.meta │ ├── page2.png │ └── page2.png.meta ├── scripts.meta ├── scripts │ ├── bezier-assembler.ts │ ├── bezier-assembler.ts.meta │ ├── bezier-render.ts │ ├── bezier-render.ts.meta │ ├── page-effect-assembler-base.ts │ ├── page-effect-assembler-base.ts.meta │ ├── page.ts │ ├── page.ts.meta │ ├── verlet-assembler.ts │ ├── verlet-assembler.ts.meta │ ├── verlet-render.ts │ └── verlet-render.ts.meta ├── shader.meta └── shader │ ├── page-effect.effect │ ├── page-effect.effect.meta │ ├── page-material.mtl │ └── page-material.mtl.meta ├── creator.d.ts ├── jsconfig.json ├── project.json └── settings ├── project.json └── services.json /.gitignore: -------------------------------------------------------------------------------- 1 | #///////////////////////////////////////////////////////////////////////////// 2 | # Fireball Projects 3 | #///////////////////////////////////////////////////////////////////////////// 4 | 5 | /library/ 6 | /temp/ 7 | /local/ 8 | /build/ 9 | 10 | #///////////////////////////////////////////////////////////////////////////// 11 | # npm files 12 | #///////////////////////////////////////////////////////////////////////////// 13 | 14 | npm-debug.log 15 | node_modules/ 16 | 17 | #///////////////////////////////////////////////////////////////////////////// 18 | # Logs and databases 19 | #///////////////////////////////////////////////////////////////////////////// 20 | 21 | *.log 22 | *.sql 23 | *.sqlite 24 | 25 | #///////////////////////////////////////////////////////////////////////////// 26 | # files for debugger 27 | #///////////////////////////////////////////////////////////////////////////// 28 | 29 | *.sln 30 | *.csproj 31 | *.pidb 32 | *.unityproj 33 | *.suo 34 | 35 | #///////////////////////////////////////////////////////////////////////////// 36 | # OS generated files 37 | #///////////////////////////////////////////////////////////////////////////// 38 | 39 | .DS_Store 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | #///////////////////////////////////////////////////////////////////////////// 44 | # WebStorm files 45 | #///////////////////////////////////////////////////////////////////////////// 46 | 47 | .idea/ 48 | 49 | #////////////////////////// 50 | # VS Code files 51 | #////////////////////////// 52 | 53 | .vscode/ 54 | -------------------------------------------------------------------------------- /assets/Page.fire: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "__type__": "cc.SceneAsset", 4 | "_name": "", 5 | "_objFlags": 0, 6 | "_native": "", 7 | "scene": { 8 | "__id__": 1 9 | } 10 | }, 11 | { 12 | "__type__": "cc.Scene", 13 | "_objFlags": 0, 14 | "_parent": null, 15 | "_children": [ 16 | { 17 | "__id__": 2 18 | } 19 | ], 20 | "_active": true, 21 | "_components": [], 22 | "_prefab": null, 23 | "_opacity": 255, 24 | "_color": { 25 | "__type__": "cc.Color", 26 | "r": 255, 27 | "g": 255, 28 | "b": 255, 29 | "a": 255 30 | }, 31 | "_contentSize": { 32 | "__type__": "cc.Size", 33 | "width": 0, 34 | "height": 0 35 | }, 36 | "_anchorPoint": { 37 | "__type__": "cc.Vec2", 38 | "x": 0, 39 | "y": 0 40 | }, 41 | "_trs": { 42 | "__type__": "TypedArray", 43 | "ctor": "Float64Array", 44 | "array": [ 45 | 0, 46 | 0, 47 | 0, 48 | 0, 49 | 0, 50 | 0, 51 | 1, 52 | 1, 53 | 1, 54 | 1 55 | ] 56 | }, 57 | "_is3DNode": true, 58 | "_groupIndex": 0, 59 | "groupIndex": 0, 60 | "autoReleaseAssets": false, 61 | "_id": "c57b5ede-ddff-4548-8f3c-a053962cf066" 62 | }, 63 | { 64 | "__type__": "cc.Node", 65 | "_name": "Canvas", 66 | "_objFlags": 0, 67 | "_parent": { 68 | "__id__": 1 69 | }, 70 | "_children": [ 71 | { 72 | "__id__": 3 73 | }, 74 | { 75 | "__id__": 5 76 | }, 77 | { 78 | "__id__": 15 79 | }, 80 | { 81 | "__id__": 25 82 | }, 83 | { 84 | "__id__": 27 85 | } 86 | ], 87 | "_active": true, 88 | "_components": [ 89 | { 90 | "__id__": 29 91 | }, 92 | { 93 | "__id__": 30 94 | }, 95 | { 96 | "__id__": 31 97 | } 98 | ], 99 | "_prefab": null, 100 | "_opacity": 255, 101 | "_color": { 102 | "__type__": "cc.Color", 103 | "r": 255, 104 | "g": 255, 105 | "b": 255, 106 | "a": 255 107 | }, 108 | "_contentSize": { 109 | "__type__": "cc.Size", 110 | "width": 640, 111 | "height": 960 112 | }, 113 | "_anchorPoint": { 114 | "__type__": "cc.Vec2", 115 | "x": 0.5, 116 | "y": 0.5 117 | }, 118 | "_trs": { 119 | "__type__": "TypedArray", 120 | "ctor": "Float64Array", 121 | "array": [ 122 | 320, 123 | 480, 124 | 0, 125 | 0, 126 | 0, 127 | 0, 128 | 1, 129 | 1, 130 | 1, 131 | 1 132 | ] 133 | }, 134 | "_eulerAngles": { 135 | "__type__": "cc.Vec3", 136 | "x": 0, 137 | "y": 0, 138 | "z": 0 139 | }, 140 | "_skewX": 0, 141 | "_skewY": 0, 142 | "_is3DNode": false, 143 | "_groupIndex": 0, 144 | "groupIndex": 0, 145 | "_id": "f9AZ5wT8dBoY2Ava4CRUKc" 146 | }, 147 | { 148 | "__type__": "cc.Node", 149 | "_name": "Main Camera", 150 | "_objFlags": 0, 151 | "_parent": { 152 | "__id__": 2 153 | }, 154 | "_children": [], 155 | "_active": true, 156 | "_components": [ 157 | { 158 | "__id__": 4 159 | } 160 | ], 161 | "_prefab": null, 162 | "_opacity": 255, 163 | "_color": { 164 | "__type__": "cc.Color", 165 | "r": 255, 166 | "g": 255, 167 | "b": 255, 168 | "a": 255 169 | }, 170 | "_contentSize": { 171 | "__type__": "cc.Size", 172 | "width": 0, 173 | "height": 0 174 | }, 175 | "_anchorPoint": { 176 | "__type__": "cc.Vec2", 177 | "x": 0.5, 178 | "y": 0.5 179 | }, 180 | "_trs": { 181 | "__type__": "TypedArray", 182 | "ctor": "Float64Array", 183 | "array": [ 184 | 0, 185 | 0, 186 | 359.4005425705421, 187 | 0, 188 | 0, 189 | 0, 190 | 1, 191 | 1, 192 | 1, 193 | 1 194 | ] 195 | }, 196 | "_eulerAngles": { 197 | "__type__": "cc.Vec3", 198 | "x": 0, 199 | "y": 0, 200 | "z": 0 201 | }, 202 | "_skewX": 0, 203 | "_skewY": 0, 204 | "_is3DNode": false, 205 | "_groupIndex": 0, 206 | "groupIndex": 0, 207 | "_id": "75IRNK7WFK0qKEBIt8IAPJ" 208 | }, 209 | { 210 | "__type__": "cc.Camera", 211 | "_name": "", 212 | "_objFlags": 0, 213 | "node": { 214 | "__id__": 3 215 | }, 216 | "_enabled": true, 217 | "_cullingMask": 4294967295, 218 | "_clearFlags": 7, 219 | "_backgroundColor": { 220 | "__type__": "cc.Color", 221 | "r": 0, 222 | "g": 0, 223 | "b": 0, 224 | "a": 255 225 | }, 226 | "_depth": -1, 227 | "_zoomRatio": 1, 228 | "_targetTexture": null, 229 | "_fov": 60, 230 | "_orthoSize": 10, 231 | "_nearClip": 1, 232 | "_farClip": 4096, 233 | "_ortho": true, 234 | "_rect": { 235 | "__type__": "cc.Rect", 236 | "x": 0, 237 | "y": 0, 238 | "width": 1, 239 | "height": 1 240 | }, 241 | "_renderStages": 1, 242 | "_alignWithScreen": true, 243 | "_id": "01kC2R/kBBEZvfmexO1BRv" 244 | }, 245 | { 246 | "__type__": "cc.Node", 247 | "_name": "bezier", 248 | "_objFlags": 0, 249 | "_parent": { 250 | "__id__": 2 251 | }, 252 | "_children": [ 253 | { 254 | "__id__": 6 255 | }, 256 | { 257 | "__id__": 8 258 | }, 259 | { 260 | "__id__": 13 261 | } 262 | ], 263 | "_active": true, 264 | "_components": [], 265 | "_prefab": null, 266 | "_opacity": 255, 267 | "_color": { 268 | "__type__": "cc.Color", 269 | "r": 255, 270 | "g": 255, 271 | "b": 255, 272 | "a": 255 273 | }, 274 | "_contentSize": { 275 | "__type__": "cc.Size", 276 | "width": 0, 277 | "height": 0 278 | }, 279 | "_anchorPoint": { 280 | "__type__": "cc.Vec2", 281 | "x": 0.5, 282 | "y": 0.5 283 | }, 284 | "_trs": { 285 | "__type__": "TypedArray", 286 | "ctor": "Float64Array", 287 | "array": [ 288 | 0, 289 | 120, 290 | 0, 291 | 0, 292 | 0, 293 | 0, 294 | 1, 295 | 0.7, 296 | 0.7, 297 | 1 298 | ] 299 | }, 300 | "_eulerAngles": { 301 | "__type__": "cc.Vec3", 302 | "x": 0, 303 | "y": 0, 304 | "z": 0 305 | }, 306 | "_skewX": 0, 307 | "_skewY": 0, 308 | "_is3DNode": false, 309 | "_groupIndex": 0, 310 | "groupIndex": 0, 311 | "_id": "f4bLru1dFGIr2NeNJOTqnO" 312 | }, 313 | { 314 | "__type__": "cc.Node", 315 | "_name": "bg", 316 | "_objFlags": 0, 317 | "_parent": { 318 | "__id__": 5 319 | }, 320 | "_children": [], 321 | "_active": true, 322 | "_components": [ 323 | { 324 | "__id__": 7 325 | } 326 | ], 327 | "_prefab": null, 328 | "_opacity": 255, 329 | "_color": { 330 | "__type__": "cc.Color", 331 | "r": 255, 332 | "g": 255, 333 | "b": 255, 334 | "a": 255 335 | }, 336 | "_contentSize": { 337 | "__type__": "cc.Size", 338 | "width": 580, 339 | "height": 465 340 | }, 341 | "_anchorPoint": { 342 | "__type__": "cc.Vec2", 343 | "x": 0.5, 344 | "y": 0.5 345 | }, 346 | "_trs": { 347 | "__type__": "TypedArray", 348 | "ctor": "Float64Array", 349 | "array": [ 350 | 0, 351 | 0, 352 | 0, 353 | 0, 354 | 0, 355 | 0, 356 | 1, 357 | 1, 358 | 1, 359 | 1 360 | ] 361 | }, 362 | "_eulerAngles": { 363 | "__type__": "cc.Vec3", 364 | "x": 0, 365 | "y": 0, 366 | "z": 0 367 | }, 368 | "_skewX": 0, 369 | "_skewY": 0, 370 | "_is3DNode": false, 371 | "_groupIndex": 0, 372 | "groupIndex": 0, 373 | "_id": "c3+RMGGkxGy77eCydTtG7i" 374 | }, 375 | { 376 | "__type__": "cc.Sprite", 377 | "_name": "", 378 | "_objFlags": 0, 379 | "node": { 380 | "__id__": 6 381 | }, 382 | "_enabled": true, 383 | "_materials": [ 384 | { 385 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 386 | } 387 | ], 388 | "_srcBlendFactor": 770, 389 | "_dstBlendFactor": 771, 390 | "_spriteFrame": { 391 | "__uuid__": "edb45e70-de1f-4bad-9332-1f61c61e881b" 392 | }, 393 | "_type": 0, 394 | "_sizeMode": 0, 395 | "_fillType": 0, 396 | "_fillCenter": { 397 | "__type__": "cc.Vec2", 398 | "x": 0, 399 | "y": 0 400 | }, 401 | "_fillStart": 0, 402 | "_fillRange": 0, 403 | "_isTrimmedMode": true, 404 | "_atlas": null, 405 | "_id": "9cUmgEaeZG8YgKldONVJvF" 406 | }, 407 | { 408 | "__type__": "cc.Node", 409 | "_name": "defaultPage", 410 | "_objFlags": 0, 411 | "_parent": { 412 | "__id__": 5 413 | }, 414 | "_children": [ 415 | { 416 | "__id__": 9 417 | }, 418 | { 419 | "__id__": 11 420 | } 421 | ], 422 | "_active": true, 423 | "_components": [], 424 | "_prefab": null, 425 | "_opacity": 255, 426 | "_color": { 427 | "__type__": "cc.Color", 428 | "r": 255, 429 | "g": 255, 430 | "b": 255, 431 | "a": 255 432 | }, 433 | "_contentSize": { 434 | "__type__": "cc.Size", 435 | "width": 0, 436 | "height": 0 437 | }, 438 | "_anchorPoint": { 439 | "__type__": "cc.Vec2", 440 | "x": 0.5, 441 | "y": 0.5 442 | }, 443 | "_trs": { 444 | "__type__": "TypedArray", 445 | "ctor": "Float64Array", 446 | "array": [ 447 | 0, 448 | -7, 449 | 0, 450 | 0, 451 | 0, 452 | 0, 453 | 1, 454 | 1, 455 | 1, 456 | 1 457 | ] 458 | }, 459 | "_eulerAngles": { 460 | "__type__": "cc.Vec3", 461 | "x": 0, 462 | "y": 0, 463 | "z": 0 464 | }, 465 | "_skewX": 0, 466 | "_skewY": 0, 467 | "_is3DNode": false, 468 | "_groupIndex": 0, 469 | "groupIndex": 0, 470 | "_id": "48wb8mfChAMKqXSUGKVoWO" 471 | }, 472 | { 473 | "__type__": "cc.Node", 474 | "_name": "page2", 475 | "_objFlags": 0, 476 | "_parent": { 477 | "__id__": 8 478 | }, 479 | "_children": [], 480 | "_active": true, 481 | "_components": [ 482 | { 483 | "__id__": 10 484 | } 485 | ], 486 | "_prefab": null, 487 | "_opacity": 255, 488 | "_color": { 489 | "__type__": "cc.Color", 490 | "r": 255, 491 | "g": 255, 492 | "b": 255, 493 | "a": 255 494 | }, 495 | "_contentSize": { 496 | "__type__": "cc.Size", 497 | "width": 230, 498 | "height": 360 499 | }, 500 | "_anchorPoint": { 501 | "__type__": "cc.Vec2", 502 | "x": 0.5, 503 | "y": 0.5 504 | }, 505 | "_trs": { 506 | "__type__": "TypedArray", 507 | "ctor": "Float64Array", 508 | "array": [ 509 | 115, 510 | 0, 511 | 0, 512 | 0, 513 | 0, 514 | 0, 515 | 1, 516 | 1, 517 | 1, 518 | 1 519 | ] 520 | }, 521 | "_eulerAngles": { 522 | "__type__": "cc.Vec3", 523 | "x": 0, 524 | "y": 0, 525 | "z": 0 526 | }, 527 | "_skewX": 0, 528 | "_skewY": 0, 529 | "_is3DNode": false, 530 | "_groupIndex": 0, 531 | "groupIndex": 0, 532 | "_id": "67Th/ahQZFhbU3Bs2N6ZGm" 533 | }, 534 | { 535 | "__type__": "cc.Sprite", 536 | "_name": "", 537 | "_objFlags": 0, 538 | "node": { 539 | "__id__": 9 540 | }, 541 | "_enabled": true, 542 | "_materials": [ 543 | { 544 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 545 | } 546 | ], 547 | "_srcBlendFactor": 770, 548 | "_dstBlendFactor": 771, 549 | "_spriteFrame": { 550 | "__uuid__": "f97d68d5-63b4-42a8-b3ef-d999045744ee" 551 | }, 552 | "_type": 0, 553 | "_sizeMode": 0, 554 | "_fillType": 0, 555 | "_fillCenter": { 556 | "__type__": "cc.Vec2", 557 | "x": 0, 558 | "y": 0 559 | }, 560 | "_fillStart": 0, 561 | "_fillRange": 0, 562 | "_isTrimmedMode": true, 563 | "_atlas": null, 564 | "_id": "a9Fsa4SGVJ1oC/BROe/8CA" 565 | }, 566 | { 567 | "__type__": "cc.Node", 568 | "_name": "page1", 569 | "_objFlags": 0, 570 | "_parent": { 571 | "__id__": 8 572 | }, 573 | "_children": [], 574 | "_active": true, 575 | "_components": [ 576 | { 577 | "__id__": 12 578 | } 579 | ], 580 | "_prefab": null, 581 | "_opacity": 255, 582 | "_color": { 583 | "__type__": "cc.Color", 584 | "r": 255, 585 | "g": 255, 586 | "b": 255, 587 | "a": 255 588 | }, 589 | "_contentSize": { 590 | "__type__": "cc.Size", 591 | "width": 230, 592 | "height": 360 593 | }, 594 | "_anchorPoint": { 595 | "__type__": "cc.Vec2", 596 | "x": 0.5, 597 | "y": 0.5 598 | }, 599 | "_trs": { 600 | "__type__": "TypedArray", 601 | "ctor": "Float64Array", 602 | "array": [ 603 | -115, 604 | 0, 605 | 0, 606 | 0, 607 | 0, 608 | 0, 609 | 1, 610 | -1, 611 | 1, 612 | 1 613 | ] 614 | }, 615 | "_eulerAngles": { 616 | "__type__": "cc.Vec3", 617 | "x": 0, 618 | "y": 0, 619 | "z": 0 620 | }, 621 | "_skewX": 0, 622 | "_skewY": 0, 623 | "_is3DNode": false, 624 | "_groupIndex": 0, 625 | "groupIndex": 0, 626 | "_id": "da6gbKOABKg4VXWM+pIlCq" 627 | }, 628 | { 629 | "__type__": "cc.Sprite", 630 | "_name": "", 631 | "_objFlags": 0, 632 | "node": { 633 | "__id__": 11 634 | }, 635 | "_enabled": true, 636 | "_materials": [ 637 | { 638 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 639 | } 640 | ], 641 | "_srcBlendFactor": 770, 642 | "_dstBlendFactor": 771, 643 | "_spriteFrame": { 644 | "__uuid__": "a7fbea6d-8c87-41bb-b1a8-bed8e6530ecf" 645 | }, 646 | "_type": 0, 647 | "_sizeMode": 0, 648 | "_fillType": 0, 649 | "_fillCenter": { 650 | "__type__": "cc.Vec2", 651 | "x": 0, 652 | "y": 0 653 | }, 654 | "_fillStart": 0, 655 | "_fillRange": 0, 656 | "_isTrimmedMode": true, 657 | "_atlas": null, 658 | "_id": "89svQeQN1B5LYS4o+gGoSd" 659 | }, 660 | { 661 | "__type__": "cc.Node", 662 | "_name": "page", 663 | "_objFlags": 0, 664 | "_parent": { 665 | "__id__": 5 666 | }, 667 | "_children": [], 668 | "_active": true, 669 | "_components": [ 670 | { 671 | "__id__": 14 672 | } 673 | ], 674 | "_prefab": null, 675 | "_opacity": 255, 676 | "_color": { 677 | "__type__": "cc.Color", 678 | "r": 255, 679 | "g": 255, 680 | "b": 255, 681 | "a": 255 682 | }, 683 | "_contentSize": { 684 | "__type__": "cc.Size", 685 | "width": 230, 686 | "height": 360 687 | }, 688 | "_anchorPoint": { 689 | "__type__": "cc.Vec2", 690 | "x": 0.5, 691 | "y": 0.5 692 | }, 693 | "_trs": { 694 | "__type__": "TypedArray", 695 | "ctor": "Float64Array", 696 | "array": [ 697 | 115, 698 | -7, 699 | 0, 700 | 0, 701 | 0, 702 | 0, 703 | 1, 704 | 1, 705 | 1, 706 | 1 707 | ] 708 | }, 709 | "_eulerAngles": { 710 | "__type__": "cc.Vec3", 711 | "x": 0, 712 | "y": 0, 713 | "z": 0 714 | }, 715 | "_skewX": 0, 716 | "_skewY": 0, 717 | "_is3DNode": false, 718 | "_groupIndex": 0, 719 | "groupIndex": 0, 720 | "_id": "30b8iTOU1J946Z+ialXiFT" 721 | }, 722 | { 723 | "__type__": "33e45xqc9pJ/agYa1E+jVYa", 724 | "_name": "", 725 | "_objFlags": 0, 726 | "node": { 727 | "__id__": 13 728 | }, 729 | "_enabled": true, 730 | "_materials": [ 731 | { 732 | "__uuid__": "4f7c0520-945f-4c7c-9b51-9c012fca0299" 733 | } 734 | ], 735 | "textureList": [ 736 | { 737 | "__uuid__": "f427f6fd-dac7-425d-9db4-26b1184a2489" 738 | }, 739 | { 740 | "__uuid__": "84ab79a5-db40-4e18-9ca3-eb6e7a83c8e8" 741 | } 742 | ], 743 | "pointsCount": 5, 744 | "_id": "61gQy70eZNFJ5gSylJa5iG" 745 | }, 746 | { 747 | "__type__": "cc.Node", 748 | "_name": "verlet", 749 | "_objFlags": 0, 750 | "_parent": { 751 | "__id__": 2 752 | }, 753 | "_children": [ 754 | { 755 | "__id__": 16 756 | }, 757 | { 758 | "__id__": 18 759 | }, 760 | { 761 | "__id__": 23 762 | } 763 | ], 764 | "_active": true, 765 | "_components": [], 766 | "_prefab": null, 767 | "_opacity": 255, 768 | "_color": { 769 | "__type__": "cc.Color", 770 | "r": 255, 771 | "g": 255, 772 | "b": 255, 773 | "a": 255 774 | }, 775 | "_contentSize": { 776 | "__type__": "cc.Size", 777 | "width": 0, 778 | "height": 0 779 | }, 780 | "_anchorPoint": { 781 | "__type__": "cc.Vec2", 782 | "x": 0.5, 783 | "y": 0.5 784 | }, 785 | "_trs": { 786 | "__type__": "TypedArray", 787 | "ctor": "Float64Array", 788 | "array": [ 789 | 0, 790 | -200, 791 | 0, 792 | 0, 793 | 0, 794 | 0, 795 | 1, 796 | 0.7, 797 | 0.7, 798 | 1 799 | ] 800 | }, 801 | "_eulerAngles": { 802 | "__type__": "cc.Vec3", 803 | "x": 0, 804 | "y": 0, 805 | "z": 0 806 | }, 807 | "_skewX": 0, 808 | "_skewY": 0, 809 | "_is3DNode": false, 810 | "_groupIndex": 0, 811 | "groupIndex": 0, 812 | "_id": "01C+GfiZZBc7vi/xBnEcUE" 813 | }, 814 | { 815 | "__type__": "cc.Node", 816 | "_name": "bg", 817 | "_objFlags": 0, 818 | "_parent": { 819 | "__id__": 15 820 | }, 821 | "_children": [], 822 | "_active": true, 823 | "_components": [ 824 | { 825 | "__id__": 17 826 | } 827 | ], 828 | "_prefab": null, 829 | "_opacity": 255, 830 | "_color": { 831 | "__type__": "cc.Color", 832 | "r": 255, 833 | "g": 255, 834 | "b": 255, 835 | "a": 255 836 | }, 837 | "_contentSize": { 838 | "__type__": "cc.Size", 839 | "width": 580, 840 | "height": 465 841 | }, 842 | "_anchorPoint": { 843 | "__type__": "cc.Vec2", 844 | "x": 0.5, 845 | "y": 0.5 846 | }, 847 | "_trs": { 848 | "__type__": "TypedArray", 849 | "ctor": "Float64Array", 850 | "array": [ 851 | 0, 852 | 0, 853 | 0, 854 | 0, 855 | 0, 856 | 0, 857 | 1, 858 | 1, 859 | 1, 860 | 1 861 | ] 862 | }, 863 | "_eulerAngles": { 864 | "__type__": "cc.Vec3", 865 | "x": 0, 866 | "y": 0, 867 | "z": 0 868 | }, 869 | "_skewX": 0, 870 | "_skewY": 0, 871 | "_is3DNode": false, 872 | "_groupIndex": 0, 873 | "groupIndex": 0, 874 | "_id": "22iNUqB3ROZoOpd7vjo/rZ" 875 | }, 876 | { 877 | "__type__": "cc.Sprite", 878 | "_name": "", 879 | "_objFlags": 0, 880 | "node": { 881 | "__id__": 16 882 | }, 883 | "_enabled": true, 884 | "_materials": [ 885 | { 886 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 887 | } 888 | ], 889 | "_srcBlendFactor": 770, 890 | "_dstBlendFactor": 771, 891 | "_spriteFrame": { 892 | "__uuid__": "edb45e70-de1f-4bad-9332-1f61c61e881b" 893 | }, 894 | "_type": 0, 895 | "_sizeMode": 0, 896 | "_fillType": 0, 897 | "_fillCenter": { 898 | "__type__": "cc.Vec2", 899 | "x": 0, 900 | "y": 0 901 | }, 902 | "_fillStart": 0, 903 | "_fillRange": 0, 904 | "_isTrimmedMode": true, 905 | "_atlas": null, 906 | "_id": "53hqCGhABG2Kk4rRDhuYsy" 907 | }, 908 | { 909 | "__type__": "cc.Node", 910 | "_name": "defaultPage", 911 | "_objFlags": 0, 912 | "_parent": { 913 | "__id__": 15 914 | }, 915 | "_children": [ 916 | { 917 | "__id__": 19 918 | }, 919 | { 920 | "__id__": 21 921 | } 922 | ], 923 | "_active": true, 924 | "_components": [], 925 | "_prefab": null, 926 | "_opacity": 255, 927 | "_color": { 928 | "__type__": "cc.Color", 929 | "r": 255, 930 | "g": 255, 931 | "b": 255, 932 | "a": 255 933 | }, 934 | "_contentSize": { 935 | "__type__": "cc.Size", 936 | "width": 0, 937 | "height": 0 938 | }, 939 | "_anchorPoint": { 940 | "__type__": "cc.Vec2", 941 | "x": 0.5, 942 | "y": 0.5 943 | }, 944 | "_trs": { 945 | "__type__": "TypedArray", 946 | "ctor": "Float64Array", 947 | "array": [ 948 | 0, 949 | -7, 950 | 0, 951 | 0, 952 | 0, 953 | 0, 954 | 1, 955 | 1, 956 | 1, 957 | 1 958 | ] 959 | }, 960 | "_eulerAngles": { 961 | "__type__": "cc.Vec3", 962 | "x": 0, 963 | "y": 0, 964 | "z": 0 965 | }, 966 | "_skewX": 0, 967 | "_skewY": 0, 968 | "_is3DNode": false, 969 | "_groupIndex": 0, 970 | "groupIndex": 0, 971 | "_id": "62xLOqqshGBY5JWK5X1sw7" 972 | }, 973 | { 974 | "__type__": "cc.Node", 975 | "_name": "page2", 976 | "_objFlags": 0, 977 | "_parent": { 978 | "__id__": 18 979 | }, 980 | "_children": [], 981 | "_active": true, 982 | "_components": [ 983 | { 984 | "__id__": 20 985 | } 986 | ], 987 | "_prefab": null, 988 | "_opacity": 255, 989 | "_color": { 990 | "__type__": "cc.Color", 991 | "r": 255, 992 | "g": 255, 993 | "b": 255, 994 | "a": 255 995 | }, 996 | "_contentSize": { 997 | "__type__": "cc.Size", 998 | "width": 230, 999 | "height": 360 1000 | }, 1001 | "_anchorPoint": { 1002 | "__type__": "cc.Vec2", 1003 | "x": 0.5, 1004 | "y": 0.5 1005 | }, 1006 | "_trs": { 1007 | "__type__": "TypedArray", 1008 | "ctor": "Float64Array", 1009 | "array": [ 1010 | 115, 1011 | 0, 1012 | 0, 1013 | 0, 1014 | 0, 1015 | 0, 1016 | 1, 1017 | 1, 1018 | 1, 1019 | 1 1020 | ] 1021 | }, 1022 | "_eulerAngles": { 1023 | "__type__": "cc.Vec3", 1024 | "x": 0, 1025 | "y": 0, 1026 | "z": 0 1027 | }, 1028 | "_skewX": 0, 1029 | "_skewY": 0, 1030 | "_is3DNode": false, 1031 | "_groupIndex": 0, 1032 | "groupIndex": 0, 1033 | "_id": "73Ux4Df3dBIK3MkeAqBEM0" 1034 | }, 1035 | { 1036 | "__type__": "cc.Sprite", 1037 | "_name": "", 1038 | "_objFlags": 0, 1039 | "node": { 1040 | "__id__": 19 1041 | }, 1042 | "_enabled": true, 1043 | "_materials": [ 1044 | { 1045 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 1046 | } 1047 | ], 1048 | "_srcBlendFactor": 770, 1049 | "_dstBlendFactor": 771, 1050 | "_spriteFrame": { 1051 | "__uuid__": "f97d68d5-63b4-42a8-b3ef-d999045744ee" 1052 | }, 1053 | "_type": 0, 1054 | "_sizeMode": 0, 1055 | "_fillType": 0, 1056 | "_fillCenter": { 1057 | "__type__": "cc.Vec2", 1058 | "x": 0, 1059 | "y": 0 1060 | }, 1061 | "_fillStart": 0, 1062 | "_fillRange": 0, 1063 | "_isTrimmedMode": true, 1064 | "_atlas": null, 1065 | "_id": "c8l5A18oRHnJgkC5HBHrsl" 1066 | }, 1067 | { 1068 | "__type__": "cc.Node", 1069 | "_name": "page1", 1070 | "_objFlags": 0, 1071 | "_parent": { 1072 | "__id__": 18 1073 | }, 1074 | "_children": [], 1075 | "_active": true, 1076 | "_components": [ 1077 | { 1078 | "__id__": 22 1079 | } 1080 | ], 1081 | "_prefab": null, 1082 | "_opacity": 255, 1083 | "_color": { 1084 | "__type__": "cc.Color", 1085 | "r": 255, 1086 | "g": 255, 1087 | "b": 255, 1088 | "a": 255 1089 | }, 1090 | "_contentSize": { 1091 | "__type__": "cc.Size", 1092 | "width": 230, 1093 | "height": 360 1094 | }, 1095 | "_anchorPoint": { 1096 | "__type__": "cc.Vec2", 1097 | "x": 0.5, 1098 | "y": 0.5 1099 | }, 1100 | "_trs": { 1101 | "__type__": "TypedArray", 1102 | "ctor": "Float64Array", 1103 | "array": [ 1104 | -115, 1105 | 0, 1106 | 0, 1107 | 0, 1108 | 0, 1109 | 0, 1110 | 1, 1111 | -1, 1112 | 1, 1113 | 1 1114 | ] 1115 | }, 1116 | "_eulerAngles": { 1117 | "__type__": "cc.Vec3", 1118 | "x": 0, 1119 | "y": 0, 1120 | "z": 0 1121 | }, 1122 | "_skewX": 0, 1123 | "_skewY": 0, 1124 | "_is3DNode": false, 1125 | "_groupIndex": 0, 1126 | "groupIndex": 0, 1127 | "_id": "1b7j8YQ05KqL505Mddk+TB" 1128 | }, 1129 | { 1130 | "__type__": "cc.Sprite", 1131 | "_name": "", 1132 | "_objFlags": 0, 1133 | "node": { 1134 | "__id__": 21 1135 | }, 1136 | "_enabled": true, 1137 | "_materials": [ 1138 | { 1139 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 1140 | } 1141 | ], 1142 | "_srcBlendFactor": 770, 1143 | "_dstBlendFactor": 771, 1144 | "_spriteFrame": { 1145 | "__uuid__": "a7fbea6d-8c87-41bb-b1a8-bed8e6530ecf" 1146 | }, 1147 | "_type": 0, 1148 | "_sizeMode": 0, 1149 | "_fillType": 0, 1150 | "_fillCenter": { 1151 | "__type__": "cc.Vec2", 1152 | "x": 0, 1153 | "y": 0 1154 | }, 1155 | "_fillStart": 0, 1156 | "_fillRange": 0, 1157 | "_isTrimmedMode": true, 1158 | "_atlas": null, 1159 | "_id": "90UiXhcwdJhptV1Xo1syEW" 1160 | }, 1161 | { 1162 | "__type__": "cc.Node", 1163 | "_name": "page", 1164 | "_objFlags": 0, 1165 | "_parent": { 1166 | "__id__": 15 1167 | }, 1168 | "_children": [], 1169 | "_active": true, 1170 | "_components": [ 1171 | { 1172 | "__id__": 24 1173 | } 1174 | ], 1175 | "_prefab": null, 1176 | "_opacity": 255, 1177 | "_color": { 1178 | "__type__": "cc.Color", 1179 | "r": 255, 1180 | "g": 255, 1181 | "b": 255, 1182 | "a": 255 1183 | }, 1184 | "_contentSize": { 1185 | "__type__": "cc.Size", 1186 | "width": 230, 1187 | "height": 360 1188 | }, 1189 | "_anchorPoint": { 1190 | "__type__": "cc.Vec2", 1191 | "x": 0.5, 1192 | "y": 0.5 1193 | }, 1194 | "_trs": { 1195 | "__type__": "TypedArray", 1196 | "ctor": "Float64Array", 1197 | "array": [ 1198 | 115, 1199 | -7, 1200 | 0, 1201 | 0, 1202 | 0, 1203 | 0, 1204 | 1, 1205 | 1, 1206 | 1, 1207 | 1 1208 | ] 1209 | }, 1210 | "_eulerAngles": { 1211 | "__type__": "cc.Vec3", 1212 | "x": 0, 1213 | "y": 0, 1214 | "z": 0 1215 | }, 1216 | "_skewX": 0, 1217 | "_skewY": 0, 1218 | "_is3DNode": false, 1219 | "_groupIndex": 0, 1220 | "groupIndex": 0, 1221 | "_id": "83O4MPAmNNv7fBpa6y87AZ" 1222 | }, 1223 | { 1224 | "__type__": "50dbcjXYhpDxpAmkQwp0/H6", 1225 | "_name": "", 1226 | "_objFlags": 0, 1227 | "node": { 1228 | "__id__": 23 1229 | }, 1230 | "_enabled": true, 1231 | "_materials": [ 1232 | { 1233 | "__uuid__": "4f7c0520-945f-4c7c-9b51-9c012fca0299" 1234 | } 1235 | ], 1236 | "textureList": [ 1237 | { 1238 | "__uuid__": "f427f6fd-dac7-425d-9db4-26b1184a2489" 1239 | }, 1240 | { 1241 | "__uuid__": "84ab79a5-db40-4e18-9ca3-eb6e7a83c8e8" 1242 | } 1243 | ], 1244 | "pointsCount": 20, 1245 | "constraintTimes": 100, 1246 | "damping": 0.1, 1247 | "gravity": 0, 1248 | "_id": "93KhUk1aVEfp44h3ujc9je" 1249 | }, 1250 | { 1251 | "__type__": "cc.Node", 1252 | "_name": "label", 1253 | "_objFlags": 0, 1254 | "_parent": { 1255 | "__id__": 2 1256 | }, 1257 | "_children": [], 1258 | "_active": true, 1259 | "_components": [ 1260 | { 1261 | "__id__": 26 1262 | } 1263 | ], 1264 | "_prefab": null, 1265 | "_opacity": 255, 1266 | "_color": { 1267 | "__type__": "cc.Color", 1268 | "r": 255, 1269 | "g": 255, 1270 | "b": 255, 1271 | "a": 255 1272 | }, 1273 | "_contentSize": { 1274 | "__type__": "cc.Size", 1275 | "width": 200, 1276 | "height": 50.4 1277 | }, 1278 | "_anchorPoint": { 1279 | "__type__": "cc.Vec2", 1280 | "x": 0.5, 1281 | "y": 0.5 1282 | }, 1283 | "_trs": { 1284 | "__type__": "TypedArray", 1285 | "ctor": "Float64Array", 1286 | "array": [ 1287 | 2.164, 1288 | 264.031, 1289 | 0, 1290 | 0, 1291 | 0, 1292 | 0, 1293 | 1, 1294 | 1, 1295 | 1, 1296 | 1 1297 | ] 1298 | }, 1299 | "_eulerAngles": { 1300 | "__type__": "cc.Vec3", 1301 | "x": 0, 1302 | "y": 0, 1303 | "z": 0 1304 | }, 1305 | "_skewX": 0, 1306 | "_skewY": 0, 1307 | "_is3DNode": false, 1308 | "_groupIndex": 0, 1309 | "groupIndex": 0, 1310 | "_id": "4fvfL85NtKHbw6n1V5EN/G" 1311 | }, 1312 | { 1313 | "__type__": "cc.Label", 1314 | "_name": "", 1315 | "_objFlags": 0, 1316 | "node": { 1317 | "__id__": 25 1318 | }, 1319 | "_enabled": true, 1320 | "_materials": [ 1321 | { 1322 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 1323 | } 1324 | ], 1325 | "_useOriginalSize": false, 1326 | "_string": "贝塞尔曲线", 1327 | "_N$string": "贝塞尔曲线", 1328 | "_fontSize": 40, 1329 | "_lineHeight": 40, 1330 | "_enableWrapText": true, 1331 | "_N$file": null, 1332 | "_isSystemFontUsed": true, 1333 | "_spacingX": 0, 1334 | "_batchAsBitmap": false, 1335 | "_styleFlags": 0, 1336 | "_underlineHeight": 0, 1337 | "_N$horizontalAlign": 1, 1338 | "_N$verticalAlign": 1, 1339 | "_N$fontFamily": "Arial", 1340 | "_N$overflow": 0, 1341 | "_N$cacheMode": 0, 1342 | "_id": "97A4PXkVNA6YPTzY2niWoX" 1343 | }, 1344 | { 1345 | "__type__": "cc.Node", 1346 | "_name": "label", 1347 | "_objFlags": 0, 1348 | "_parent": { 1349 | "__id__": 2 1350 | }, 1351 | "_children": [], 1352 | "_active": true, 1353 | "_components": [ 1354 | { 1355 | "__id__": 28 1356 | } 1357 | ], 1358 | "_prefab": null, 1359 | "_opacity": 255, 1360 | "_color": { 1361 | "__type__": "cc.Color", 1362 | "r": 255, 1363 | "g": 255, 1364 | "b": 255, 1365 | "a": 255 1366 | }, 1367 | "_contentSize": { 1368 | "__type__": "cc.Size", 1369 | "width": 177.81, 1370 | "height": 50.4 1371 | }, 1372 | "_anchorPoint": { 1373 | "__type__": "cc.Vec2", 1374 | "x": 0.5, 1375 | "y": 0.5 1376 | }, 1377 | "_trs": { 1378 | "__type__": "TypedArray", 1379 | "ctor": "Float64Array", 1380 | "array": [ 1381 | 0, 1382 | -45.03, 1383 | 0, 1384 | 0, 1385 | 0, 1386 | 0, 1387 | 1, 1388 | 1, 1389 | 1, 1390 | 1 1391 | ] 1392 | }, 1393 | "_eulerAngles": { 1394 | "__type__": "cc.Vec3", 1395 | "x": 0, 1396 | "y": 0, 1397 | "z": 0 1398 | }, 1399 | "_skewX": 0, 1400 | "_skewY": 0, 1401 | "_is3DNode": false, 1402 | "_groupIndex": 0, 1403 | "groupIndex": 0, 1404 | "_id": "0a9sKaaolOm7gyWafdJ4K0" 1405 | }, 1406 | { 1407 | "__type__": "cc.Label", 1408 | "_name": "", 1409 | "_objFlags": 0, 1410 | "node": { 1411 | "__id__": 27 1412 | }, 1413 | "_enabled": true, 1414 | "_materials": [ 1415 | { 1416 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 1417 | } 1418 | ], 1419 | "_useOriginalSize": false, 1420 | "_string": "verlet积分", 1421 | "_N$string": "verlet积分", 1422 | "_fontSize": 40, 1423 | "_lineHeight": 40, 1424 | "_enableWrapText": true, 1425 | "_N$file": null, 1426 | "_isSystemFontUsed": true, 1427 | "_spacingX": 0, 1428 | "_batchAsBitmap": false, 1429 | "_styleFlags": 0, 1430 | "_underlineHeight": 0, 1431 | "_N$horizontalAlign": 1, 1432 | "_N$verticalAlign": 1, 1433 | "_N$fontFamily": "Arial", 1434 | "_N$overflow": 0, 1435 | "_N$cacheMode": 0, 1436 | "_id": "d7b4uF1oxIPJJBumq57W32" 1437 | }, 1438 | { 1439 | "__type__": "cc.Canvas", 1440 | "_name": "", 1441 | "_objFlags": 0, 1442 | "node": { 1443 | "__id__": 2 1444 | }, 1445 | "_enabled": true, 1446 | "_designResolution": { 1447 | "__type__": "cc.Size", 1448 | "width": 640, 1449 | "height": 960 1450 | }, 1451 | "_fitWidth": false, 1452 | "_fitHeight": true, 1453 | "_id": "a4k+2SSlNL6qzH86Cl0r6S" 1454 | }, 1455 | { 1456 | "__type__": "cc.Widget", 1457 | "_name": "", 1458 | "_objFlags": 0, 1459 | "node": { 1460 | "__id__": 2 1461 | }, 1462 | "_enabled": true, 1463 | "alignMode": 1, 1464 | "_target": null, 1465 | "_alignFlags": 45, 1466 | "_left": 0, 1467 | "_right": 0, 1468 | "_top": 0, 1469 | "_bottom": 0, 1470 | "_verticalCenter": 0, 1471 | "_horizontalCenter": 0, 1472 | "_isAbsLeft": true, 1473 | "_isAbsRight": true, 1474 | "_isAbsTop": true, 1475 | "_isAbsBottom": true, 1476 | "_isAbsHorizontalCenter": true, 1477 | "_isAbsVerticalCenter": true, 1478 | "_originalWidth": 0, 1479 | "_originalHeight": 0, 1480 | "_id": "d0OxeJDsdJbY/qHHhfKisG" 1481 | }, 1482 | { 1483 | "__type__": "8f20ffFFn1GyZqIezO01S4H", 1484 | "_name": "", 1485 | "_objFlags": 0, 1486 | "node": { 1487 | "__id__": 2 1488 | }, 1489 | "_enabled": true, 1490 | "bezierRender": { 1491 | "__id__": 14 1492 | }, 1493 | "verletRender": { 1494 | "__id__": 24 1495 | }, 1496 | "_id": "b5p7f0qs9GQIjYH3Ubl/+p" 1497 | } 1498 | ] -------------------------------------------------------------------------------- /assets/Page.fire.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.2.6", 3 | "uuid": "c57b5ede-ddff-4548-8f3c-a053962cf066", 4 | "asyncLoadAssets": false, 5 | "autoReleaseAssets": false, 6 | "subMetas": {} 7 | } -------------------------------------------------------------------------------- /assets/resources.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "37ec42dd-a6ff-421a-a559-6a62a9786048", 4 | "isSubpackage": false, 5 | "subpackageName": "", 6 | "subMetas": {} 7 | } -------------------------------------------------------------------------------- /assets/resources/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlubluBlue7/Page/d03b0094db062eaf26dfee7a6d074ddd2e56fd3c/assets/resources/book.jpg -------------------------------------------------------------------------------- /assets/resources/book.jpg.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "2.3.4", 3 | "uuid": "1f56744b-313d-4ff0-9a47-dc9b74f61a9f", 4 | "type": "sprite", 5 | "wrapMode": "clamp", 6 | "filterMode": "bilinear", 7 | "premultiplyAlpha": false, 8 | "genMipmaps": false, 9 | "packable": true, 10 | "width": 1024, 11 | "height": 699, 12 | "platformSettings": {}, 13 | "subMetas": { 14 | "book": { 15 | "ver": "1.0.4", 16 | "uuid": "edb45e70-de1f-4bad-9332-1f61c61e881b", 17 | "rawTextureUuid": "1f56744b-313d-4ff0-9a47-dc9b74f61a9f", 18 | "trimType": "auto", 19 | "trimThreshold": 1, 20 | "rotated": false, 21 | "offsetX": 0, 22 | "offsetY": 0, 23 | "trimX": 0, 24 | "trimY": 0, 25 | "width": 1024, 26 | "height": 699, 27 | "rawWidth": 1024, 28 | "rawHeight": 699, 29 | "borderTop": 0, 30 | "borderBottom": 0, 31 | "borderLeft": 0, 32 | "borderRight": 0, 33 | "subMetas": {} 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /assets/resources/page1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlubluBlue7/Page/d03b0094db062eaf26dfee7a6d074ddd2e56fd3c/assets/resources/page1.png -------------------------------------------------------------------------------- /assets/resources/page1.png.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "2.3.4", 3 | "uuid": "f427f6fd-dac7-425d-9db4-26b1184a2489", 4 | "type": "sprite", 5 | "wrapMode": "clamp", 6 | "filterMode": "bilinear", 7 | "premultiplyAlpha": false, 8 | "genMipmaps": false, 9 | "packable": true, 10 | "width": 300, 11 | "height": 430, 12 | "platformSettings": {}, 13 | "subMetas": { 14 | "page1": { 15 | "ver": "1.0.4", 16 | "uuid": "a7fbea6d-8c87-41bb-b1a8-bed8e6530ecf", 17 | "rawTextureUuid": "f427f6fd-dac7-425d-9db4-26b1184a2489", 18 | "trimType": "auto", 19 | "trimThreshold": 1, 20 | "rotated": false, 21 | "offsetX": 0, 22 | "offsetY": 0, 23 | "trimX": 0, 24 | "trimY": 0, 25 | "width": 300, 26 | "height": 430, 27 | "rawWidth": 300, 28 | "rawHeight": 430, 29 | "borderTop": 0, 30 | "borderBottom": 0, 31 | "borderLeft": 0, 32 | "borderRight": 0, 33 | "subMetas": {} 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /assets/resources/page2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlubluBlue7/Page/d03b0094db062eaf26dfee7a6d074ddd2e56fd3c/assets/resources/page2.png -------------------------------------------------------------------------------- /assets/resources/page2.png.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "2.3.4", 3 | "uuid": "84ab79a5-db40-4e18-9ca3-eb6e7a83c8e8", 4 | "type": "sprite", 5 | "wrapMode": "clamp", 6 | "filterMode": "bilinear", 7 | "premultiplyAlpha": false, 8 | "genMipmaps": false, 9 | "packable": true, 10 | "width": 300, 11 | "height": 430, 12 | "platformSettings": {}, 13 | "subMetas": { 14 | "page2": { 15 | "ver": "1.0.4", 16 | "uuid": "f97d68d5-63b4-42a8-b3ef-d999045744ee", 17 | "rawTextureUuid": "84ab79a5-db40-4e18-9ca3-eb6e7a83c8e8", 18 | "trimType": "auto", 19 | "trimThreshold": 1, 20 | "rotated": false, 21 | "offsetX": 0, 22 | "offsetY": 0, 23 | "trimX": 0, 24 | "trimY": 0, 25 | "width": 300, 26 | "height": 430, 27 | "rawWidth": 300, 28 | "rawHeight": 430, 29 | "borderTop": 0, 30 | "borderBottom": 0, 31 | "borderLeft": 0, 32 | "borderRight": 0, 33 | "subMetas": {} 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /assets/scripts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "ed15b729-dc1c-4d6e-88a9-9967ff3b3428", 4 | "isSubpackage": false, 5 | "subpackageName": "", 6 | "subMetas": {} 7 | } -------------------------------------------------------------------------------- /assets/scripts/bezier-assembler.ts: -------------------------------------------------------------------------------- 1 | import PageEffectAssemblerBase from "./page-effect-assembler-base"; 2 | 3 | const gfx = cc.gfx 4 | 5 | let vfmtPosUvColorFront = new gfx.VertexFormat([ 6 | { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 }, 7 | { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 }, 8 | { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, 9 | { name: "a_isFront", type: gfx.ATTR_TYPE_FLOAT32, num: 1}, 10 | ]); 11 | 12 | export default class BezierAssembler extends PageEffectAssemblerBase { 13 | protected angle:number = 0 14 | public updateRenderData (comp: any) { 15 | if (comp) { 16 | let pointNum: number = comp.getPointCount() 17 | if (pointNum < 2) { 18 | return 19 | } 20 | 21 | let node = comp.node 22 | let height = node.height 23 | let width = node.width 24 | // 左下角的坐标 25 | let posX = - width * node.anchorX 26 | let posY = - height * node.anchorY 27 | // 根据角度获得控制点的位置 28 | let ctrlPosData = this._getCtrlPosByAngle(width) 29 | let startPos = ctrlPosData.startPos 30 | let endPos = ctrlPosData.endPos 31 | let ctrlPos1 = ctrlPosData.ctrlPos1 32 | let ctrlPos2 = ctrlPosData.ctrlPos2 33 | // 记录各个顶点的位置 34 | let bezierPosList: cc.Vec2[] = [] 35 | bezierPosList[0] = startPos 36 | // 当前所有顶点连线的总长 37 | let realWidth = 0 38 | // 上一个点的纹理坐标 39 | let lastU = 0 40 | // 下一个点的纹理坐标 41 | let nextU = 0 42 | 43 | let floatsPerVert = this.floatsPerVert; 44 | let verts = this.renderData.vDatas[0]; 45 | // 写verts时的下标 46 | let dstOffset = 0; 47 | for (let i = 1; i < pointNum; i++) { 48 | let isTail = i === pointNum - 1 49 | let lastBezierPos = bezierPosList[i - 1] 50 | let nextBezierPos = this._getBezierPos(i / (pointNum - 1) , startPos, endPos, ctrlPos1, ctrlPos2) 51 | let fixedData = this._fixWidth(lastBezierPos, nextBezierPos, width, realWidth, isTail) 52 | let gapWidth = fixedData.gapWidth 53 | nextBezierPos = fixedData.nextBezierPos 54 | realWidth += gapWidth 55 | bezierPosList[i] = nextBezierPos 56 | // 根据当前小矩形的宽度占总长度的比例来计算纹理坐标的间隔 57 | let gapU = gapWidth / width 58 | nextU = lastU + gapU 59 | /* 60 | 分别计算小矩形四个顶点的坐标和纹理坐标 61 | 各顶点的坐标计算方法为在左下角坐标的基础上加上顶点在贝塞尔曲线上的坐标,如果是书页顶部的顶点则还要加上书页的高度 62 | */ 63 | 64 | // 将4个顶点数据写入verts 65 | dstOffset = floatsPerVert * (i-1) * 4; 66 | verts[dstOffset] = posX + lastBezierPos.x; 67 | verts[dstOffset + 1] = posY + lastBezierPos.y; 68 | verts[dstOffset + 2] = lastU; 69 | verts[dstOffset + 3] = 1; 70 | dstOffset += floatsPerVert; 71 | 72 | verts[dstOffset] = posX + nextBezierPos.x; 73 | verts[dstOffset + 1] = posY + nextBezierPos.y; 74 | verts[dstOffset + 2] = nextU; 75 | verts[dstOffset + 3] = 1; 76 | dstOffset += floatsPerVert; 77 | 78 | verts[dstOffset] = posX + lastBezierPos.x; 79 | verts[dstOffset + 1] = posY + height + lastBezierPos.y; 80 | verts[dstOffset + 2] = lastU; 81 | verts[dstOffset + 3] = 0; 82 | dstOffset += floatsPerVert; 83 | 84 | verts[dstOffset] = posX + nextBezierPos.x; 85 | verts[dstOffset + 1] = posY + height + nextBezierPos.y; 86 | verts[dstOffset + 2] = nextU; 87 | verts[dstOffset + 3] = 0; 88 | 89 | lastU = nextU 90 | } 91 | 92 | this.updateColor(comp, null); 93 | this.updateIsFront(comp, 5); 94 | } 95 | } 96 | 97 | init(comp: cc.RenderComponent) { 98 | super.init(comp); 99 | 100 | //@ts-ignore 101 | let segmentCount = comp.getPointCount() - 1; 102 | this.verticesCount = 4 * segmentCount; 103 | this.indicesCount = 6 * segmentCount; 104 | this.floatsPerVert = 6; 105 | 106 | this.initData(); 107 | } 108 | 109 | getVfmt() { 110 | return vfmtPosUvColorFront; 111 | } 112 | 113 | private _getCtrlPosByAngle(width: number): {startPos: cc.Vec2, endPos: cc.Vec2, ctrlPos1: cc.Vec2, ctrlPos2: cc.Vec2} { 114 | let startPos = new cc.Vec2(0, 0) 115 | let endPos = null 116 | let ctrlPos1 = null 117 | let ctrlPos2 = null 118 | let rad = this.angle * Math.PI / 180 119 | let per = rad * 2 / Math.PI 120 | if(this.angle <= 90) { 121 | // 终点的x坐标变换 width => 0,速度先慢后快,使用InCubic缓动函数 122 | let endPosX = width * (1 - Math.pow(per, 3)) 123 | // InCubic 124 | // 终点的y坐标变换 0 => width / 4, 速度先快后慢,使用OutQuart缓动函数 125 | let endPosY = width / 4 * (1 - Math.pow(1 - per, 4)) 126 | endPos = new cc.Vec2(endPosX, endPosY) 127 | 128 | // 中间两个控制点坐标匀速变换 129 | // x坐标 width => width * 3 / 4 130 | let ctrlPosX = width * (1 - 1 / 4 * per) 131 | // 控制点1y坐标 0 => width / 16 132 | let ctrlPos1Y = width * 1 / 16 * per 133 | // 控制点2y坐标 0 => width * 3 / 16 134 | let ctrlPos2Y = width * 3 / 16 * per 135 | ctrlPos1 = new cc.Vec2(ctrlPosX, ctrlPos1Y) 136 | ctrlPos2 = new cc.Vec2(ctrlPosX, ctrlPos2Y) 137 | } else { 138 | per = per - 1 139 | // 终点的x坐标变换 0 => width,速度先快后慢,使用OutCubic缓动函数 140 | let endPosX = - width * (1 - Math.pow(1 - per, 3)) 141 | // 终点的y坐标变换 width / 4 => 0, 速度先慢后快,使用InQuart缓动函数 142 | let endPosY = width / 4 * (1 - Math.pow(per, 4)) 143 | endPos = new cc.Vec2(endPosX, endPosY) 144 | 145 | // 控制点1x坐标 width * 3 / 4 => 0 146 | let ctrlPos1X = width * 3 / 4 * (1 - per) 147 | // 控制点2x坐标 width * 3 / 4 => 0 148 | let ctrlPos2X = width * 3 / 4 * Math.pow(1 - per, 3) 149 | // 控制点1y坐标 width / 16 => 0 150 | let ctrlPos1Y = width * 1 / 16 * (1 - per) 151 | // 控制点2y坐标 width * 3 / 16 => 0 152 | let ctrlPos2Y = width * 3 / 16 * (1 - Math.pow(per, 4)) 153 | ctrlPos1 = new cc.Vec2(ctrlPos1X, ctrlPos1Y) 154 | ctrlPos2 = new cc.Vec2(ctrlPos2X, ctrlPos2Y) 155 | } 156 | 157 | return { 158 | startPos: startPos, 159 | endPos: endPos, 160 | ctrlPos1: ctrlPos1, 161 | ctrlPos2: ctrlPos2 162 | } 163 | } 164 | 165 | // 修正宽度 166 | private _fixWidth(lastBezierPos: cc.Vec2, nextBezierPos: cc.Vec2, width: number, realWidth: number, isTail: boolean) { 167 | let deltaVector = nextBezierPos.sub(lastBezierPos) 168 | // 两个顶点的间距 169 | let gapWidth = deltaVector.mag() 170 | // 当前的总长 171 | let curWidth = realWidth + gapWidth 172 | if(isTail) { 173 | // 如果是最后一个顶点则将总长度修正至书页的真实宽度 174 | gapWidth = width - realWidth 175 | let direction = deltaVector.normalize() 176 | nextBezierPos = lastBezierPos.add(direction.mul(gapWidth)) 177 | } else if(curWidth >= width) { 178 | // 如果当前总长超过了书页的真实宽度,就衰减超过部分的1.1倍 179 | let delta = curWidth - width 180 | gapWidth = gapWidth - delta * 1.1 181 | gapWidth = Math.max(0, gapWidth) 182 | let direction = deltaVector.normalize() 183 | nextBezierPos = lastBezierPos.add(direction.mul(gapWidth)) 184 | } 185 | 186 | return { 187 | gapWidth: gapWidth, 188 | nextBezierPos: nextBezierPos, 189 | } 190 | } 191 | 192 | // 贝塞尔曲线公式 193 | private _getBezierPos(t: number, startPos: cc.Vec2, endPos: cc.Vec2, ctrlPos1: cc.Vec2, ctrlPos2: cc.Vec2): cc.Vec2 { 194 | startPos = startPos.mul(Math.pow(1 - t, 3)) 195 | ctrlPos1 = ctrlPos1.mul(3 * t * Math.pow(1 - t, 2)) 196 | ctrlPos2 = ctrlPos2.mul(3 * (1 - t) * Math.pow(t, 2)) 197 | endPos = endPos.mul(Math.pow(t, 3)) 198 | return startPos.add(ctrlPos1.add(ctrlPos2.add(endPos))) 199 | } 200 | } -------------------------------------------------------------------------------- /assets/scripts/bezier-assembler.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "127a13e5-f07d-4087-a1d1-ede5e6688ec4", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/scripts/bezier-render.ts: -------------------------------------------------------------------------------- 1 | import BezierAssembler from "./bezier-assembler"; 2 | 3 | const {ccclass, property} = cc._decorator; 4 | 5 | 6 | @ccclass 7 | export default class BezierRender extends cc.RenderComponent { 8 | @property({type: [cc.Texture2D], displayName: "纹理"}) 9 | public textureList: cc.Texture2D[] = [] 10 | 11 | @property({displayName: "每条边上的顶点数量"}) 12 | public pointsCount: number = 10 13 | 14 | protected _initedMaterial: boolean = false 15 | 16 | onEnable () { 17 | super.onEnable(); 18 | this.init(); 19 | } 20 | 21 | public init() { 22 | if (!this._initedMaterial) { 23 | this.updateMaterial() 24 | } 25 | 26 | this.setVertsDirty() 27 | } 28 | 29 | public _resetAssembler() { 30 | let assembler = this._assembler = new BezierAssembler() 31 | assembler.init(this) 32 | } 33 | 34 | protected _updateMaterial() { 35 | let material = this.getMaterial(0) 36 | if (material) { 37 | material.define('CC_USE_MODEL', 1); 38 | if (this.textureList.length === 2) { 39 | material.setProperty('texture0', this.textureList[0]); 40 | material.setProperty('texture1', this.textureList[1]); 41 | } 42 | } 43 | } 44 | 45 | protected updateMaterial () { 46 | if (this.textureList.length === 2) { 47 | this._updateMaterial() 48 | this._initedMaterial = true 49 | return 50 | } 51 | } 52 | 53 | public getPointCount() { 54 | return this.pointsCount 55 | } 56 | 57 | public updateAngle(angle: number) { 58 | if(!this._assembler) { 59 | return 60 | } 61 | 62 | this._assembler.angle = angle; 63 | this._assembler.updateRenderData(this); 64 | } 65 | } -------------------------------------------------------------------------------- /assets/scripts/bezier-render.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "33e45c6a-73da-49fd-a818-6b513e8d561a", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/scripts/page-effect-assembler-base.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class PageEffectAssemblerBase extends cc.Assembler { 3 | // 普通四边形的属性,根据实际的顶点格式、数量调整 4 | protected verticesCount = 4; 5 | protected indicesCount = 6; 6 | protected floatsPerVert = 5; 7 | 8 | protected colorOffset = 4; 9 | protected renderData: cc.RenderData = null; 10 | 11 | get verticesFloats() { 12 | return this.verticesCount * this.floatsPerVert; 13 | } 14 | 15 | getBuffer() { 16 | //@ts-ignore 17 | return cc.renderer._handle.getBuffer("mesh", this.getVfmt()); 18 | } 19 | 20 | getVfmt() { 21 | // to be overwrite 22 | return null; 23 | } 24 | 25 | updateColor(comp, color) { 26 | let uintVerts = this.renderData.uintVDatas[0]; 27 | if (!uintVerts) return; 28 | color = color != null ? color : comp.node.color._val; 29 | let floatsPerVert = this.floatsPerVert; 30 | let colorOffset = this.colorOffset; 31 | for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) { 32 | uintVerts[i] = color; 33 | } 34 | } 35 | 36 | updateIsFront(comp, dataOffset) { 37 | let verts = this.renderData.vDatas[0]; 38 | let index = 0; 39 | let floatsPerVert = this.floatsPerVert; 40 | for (let i = 0, n = this.verticesCount; i < n; ++i) { 41 | index = i * floatsPerVert; 42 | let isFirstVert = i % 2 === 0; 43 | let firstVertX = isFirstVert ? verts[index] : verts[index - floatsPerVert]; 44 | let secondVertX = isFirstVert ? verts[index + floatsPerVert] : verts[index]; 45 | let isFront = firstVertX < secondVertX ? 1.0 : 0.0; 46 | verts[index + dataOffset] = isFront; 47 | } 48 | } 49 | 50 | initData() { 51 | //@ts-ignore 52 | this.renderData = new cc.RenderData(); 53 | this.renderData.init(this); 54 | 55 | let data = this.renderData; 56 | // createFlexData支持创建指定格式的renderData 57 | data.createFlexData(0, this.verticesCount, this.indicesCount, this.getVfmt()); 58 | 59 | // 顶点数固定的情况下索引不变化 60 | let indices = data.iDatas[0]; 61 | let count = indices.length / 6; 62 | for (let i = 0, idx = 0; i < count; i++) { 63 | let vertextID = i * 4; 64 | indices[idx++] = vertextID; 65 | indices[idx++] = vertextID+1; 66 | indices[idx++] = vertextID+2; 67 | indices[idx++] = vertextID+1; 68 | indices[idx++] = vertextID+3; 69 | indices[idx++] = vertextID+2; 70 | } 71 | } 72 | 73 | fillBuffers(comp, renderer) { 74 | let renderData = this.renderData; 75 | let vData = renderData.vDatas[0]; 76 | let iData = renderData.iDatas[0]; 77 | 78 | let buffer = this.getBuffer(); 79 | let offsetInfo = buffer.request(this.verticesCount, this.indicesCount); 80 | 81 | let vertexOffset = offsetInfo.byteOffset >> 2, 82 | vbuf = buffer._vData; 83 | 84 | if (vData.length + vertexOffset > vbuf.length) { 85 | vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset); 86 | } else { 87 | vbuf.set(vData, vertexOffset); 88 | } 89 | 90 | let ibuf = buffer._iData, 91 | indiceOffset = offsetInfo.indiceOffset, 92 | vertexId = offsetInfo.vertexOffset; 93 | for (let i = 0, l = iData.length; i < l; i++) { 94 | ibuf[indiceOffset++] = vertexId + iData[i]; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /assets/scripts/page-effect-assembler-base.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "ad29885c-a051-4f8e-a7a8-f460b4b6c081", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/scripts/page.ts: -------------------------------------------------------------------------------- 1 | import BezierRender from "./bezier-render"; 2 | import VerletRender from "./verlet-render"; 3 | 4 | const {ccclass, property} = cc._decorator; 5 | 6 | @ccclass 7 | export default class Page extends cc.Component { 8 | @property({type: BezierRender, displayName:"贝塞尔曲线"}) 9 | public bezierRender: BezierRender = null 10 | 11 | @property({type: VerletRender, displayName:"Verlet积分算法"}) 12 | public verletRender: VerletRender = null 13 | 14 | private _angle: number = 0 15 | private _rightToLeft = true 16 | private _waitTime = 0 17 | start () { 18 | } 19 | 20 | update(dt: number) { 21 | let anglePerDt = 180 22 | if(this._rightToLeft) { 23 | this._angle += dt * anglePerDt 24 | if(this._angle > 180) { 25 | this._angle = 180 26 | if(this._waitTime++ > 100) { 27 | this._rightToLeft = false 28 | this._waitTime = 0 29 | } 30 | } 31 | } else { 32 | this._angle -= dt * anglePerDt 33 | if(this._angle < 0) { 34 | this._angle = 0 35 | if(this._waitTime++ > 100) { 36 | this._rightToLeft = true 37 | this._waitTime = 0 38 | } 39 | } 40 | } 41 | 42 | this.bezierRender.updateAngle(this._angle) 43 | this.verletRender.updateAngle(this._angle) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assets/scripts/page.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "8f20f7c5-167d-46c9-9a88-7b33b4d52e07", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/scripts/verlet-assembler.ts: -------------------------------------------------------------------------------- 1 | import PageEffectAssemblerBase from "./page-effect-assembler-base"; 2 | 3 | const gfx = cc.gfx 4 | 5 | let vfmtPosUvColorFront = new gfx.VertexFormat([ 6 | { name: gfx.ATTR_POSITION, type: gfx.ATTR_TYPE_FLOAT32, num: 2 }, 7 | { name: gfx.ATTR_UV0, type: gfx.ATTR_TYPE_FLOAT32, num: 2 }, 8 | { name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, 9 | { name: "a_isFront", type: gfx.ATTR_TYPE_FLOAT32, num: 1}, 10 | ]); 11 | 12 | export default class VerletAssembler extends PageEffectAssemblerBase { 13 | 14 | init(comp: cc.RenderComponent) { 15 | super.init(comp); 16 | 17 | //@ts-ignore 18 | let segmentCount = comp.pointsCount - 1; 19 | this.verticesCount = 4 * segmentCount; 20 | this.indicesCount = 6 * segmentCount; 21 | this.floatsPerVert = 6; 22 | 23 | this.initData(); 24 | } 25 | 26 | getVfmt() { 27 | return vfmtPosUvColorFront; 28 | } 29 | 30 | public updateRenderData (comp: any) { 31 | if (comp) { 32 | let pointList: cc.Vec2[] = comp.getPointList() 33 | let pointNum: number = pointList.length 34 | if (pointNum < 2) { 35 | return 36 | } 37 | 38 | let node = comp.node 39 | let height = node.height 40 | let width = node.width 41 | let posX = - width * node.anchorX 42 | let posY = - height * node.anchorY 43 | 44 | let gapU = 1 / (pointNum - 1) 45 | let lastU = 0 46 | let nextU = 0 47 | 48 | let floatsPerVert = this.floatsPerVert; 49 | let verts = this.renderData.vDatas[0]; 50 | // 写verts时的下标 51 | let dstOffset = 0; 52 | for (let i = 1; i < pointNum; i++) { 53 | let lastPoint = pointList[i - 1] 54 | let nextPoint = pointList[i] 55 | nextU = lastU + gapU 56 | 57 | // 顶点和质点一一对应 58 | // 顶点数据写入verts 59 | dstOffset = floatsPerVert * (i-1) * 4; 60 | verts[dstOffset] = posX + lastPoint.x; 61 | verts[dstOffset + 1] = posY + lastPoint.y; 62 | verts[dstOffset + 2] = lastU; 63 | verts[dstOffset + 3] = 1; 64 | dstOffset += floatsPerVert; 65 | 66 | verts[dstOffset] = posX + nextPoint.x; 67 | verts[dstOffset + 1] = posY + nextPoint.y; 68 | verts[dstOffset + 2] = nextU; 69 | verts[dstOffset + 3] = 1; 70 | dstOffset += floatsPerVert; 71 | 72 | verts[dstOffset] = posX + lastPoint.x; 73 | verts[dstOffset + 1] = posY + height + lastPoint.y; 74 | verts[dstOffset + 2] = lastU; 75 | verts[dstOffset + 3] = 0; 76 | dstOffset += floatsPerVert; 77 | 78 | verts[dstOffset] = posX + nextPoint.x; 79 | verts[dstOffset + 1] = posY + height + nextPoint.y; 80 | verts[dstOffset + 2] = nextU; 81 | verts[dstOffset + 3] = 0; 82 | 83 | lastU = nextU 84 | } 85 | 86 | this.updateColor(comp, null); 87 | this.updateIsFront(comp, 5); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /assets/scripts/verlet-assembler.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "1836a06a-0703-494b-806a-aacf961f0baa", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/scripts/verlet-render.ts: -------------------------------------------------------------------------------- 1 | import VerletAssembler from "./verlet-assembler"; 2 | 3 | const {ccclass, property} = cc._decorator; 4 | 5 | class PagePoint { 6 | public oldPos:cc.Vec2 7 | public newPos:cc.Vec2 8 | 9 | constructor(x:number, y:number){ 10 | this.oldPos = this.newPos = cc.v2(x, y) 11 | } 12 | } 13 | 14 | @ccclass 15 | export default class VerletRender extends cc.RenderComponent { 16 | @property({type: [cc.Texture2D], displayName:"纹理"}) 17 | public textureList: cc.Texture2D[] = [] 18 | 19 | @property({displayName:"每条边上的顶点数量"}) 20 | public pointsCount:number = 30 21 | 22 | @property({displayName:"纠正次数"}) 23 | public constraintTimes: number = 100 24 | 25 | @property({displayName:"速度衰减系数"}) 26 | public damping: number = 0.1 27 | 28 | @property({displayName:"重力"}) 29 | public gravity: number = 0 30 | 31 | protected _initedMaterial:boolean = false 32 | private _pointList: PagePoint[] = [] 33 | private _angle: number = 0 34 | onEnable () { 35 | super.onEnable(); 36 | this.initPointList(); 37 | this.draw(); 38 | } 39 | 40 | public _resetAssembler() { 41 | let assembler = this._assembler = new VerletAssembler() 42 | assembler.init(this) 43 | } 44 | 45 | protected _updateMaterial() { 46 | let material = this.getMaterial(0) 47 | if (material) { 48 | material.define('CC_USE_MODEL', 1); 49 | if (this.textureList.length === 2) { 50 | material.setProperty('texture0', this.textureList[0]); 51 | material.setProperty('texture1', this.textureList[1]); 52 | } 53 | } 54 | } 55 | 56 | protected updateMaterial () { 57 | if (this.textureList.length === 2) { 58 | this._updateMaterial() 59 | this._initedMaterial = true 60 | return 61 | } 62 | } 63 | 64 | public updateAngle(angle: number) { 65 | this._angle = angle 66 | } 67 | 68 | public getPointList() { 69 | let pointList: cc.Vec2[] = [] 70 | for(let point of this._pointList) { 71 | pointList.push(new cc.Vec2(point.newPos.x, point.newPos.y)) 72 | } 73 | 74 | return pointList 75 | } 76 | 77 | // 初始化质点 78 | public initPointList() { 79 | for(let i = 0; i < this.pointsCount; ++i) { 80 | let posX = i / (this.pointsCount - 1) * this.node.width 81 | this._pointList.push(new PagePoint(posX, 0)) 82 | } 83 | } 84 | 85 | public update() { 86 | this.simulate() 87 | this.applyConstraint() 88 | this.draw() 89 | } 90 | 91 | // 使用verlet积分更新位置 92 | public simulate() { 93 | let gravity = cc.v2(0, this.gravity) 94 | for (let i = this.pointsCount - 1; i >= 1; i--) { 95 | let point = this._pointList[i] 96 | // 速度等于当前位置与上一个位置的差乘上衰减系数 97 | let velocity: cc.Vec2 = point.newPos.sub(point.oldPos).mul(this.damping) 98 | // 模拟一个水平放置的绳子,当y小于等于0时,将不再受重力影响 99 | if(point.newPos.y <= 0) { 100 | gravity.y = Math.max(0, gravity.y) 101 | } 102 | point.oldPos = point.newPos 103 | point.newPos = point.newPos.add(velocity) 104 | point.newPos = point.newPos.add(gravity) 105 | } 106 | } 107 | 108 | private _updateEndPos(endPos: cc.Vec2) { 109 | let tailPoint = this._pointList[this.pointsCount - 1] 110 | tailPoint.newPos = new cc.Vec2(endPos.x, endPos.y) 111 | } 112 | 113 | private _getEndPos(): cc.Vec2 { 114 | let endPos = new cc.Vec2(0, 0) 115 | let width = this.node.width 116 | let rad = this._angle * Math.PI / 180 117 | 118 | // 与贝塞尔曲线使用相同的运动轨迹 119 | let per = rad * 2 / Math.PI 120 | if(this._angle <= 90) { 121 | let endPosX = width * (1 - Math.pow(per, 3)) 122 | let endPosY = width * 1 / 4 * (1 - Math.pow(1 - per, 4)) 123 | endPos = new cc.Vec2(endPosX, endPosY) 124 | } else { 125 | per = per - 1 126 | let endPosX = - width * (1 - Math.pow(1 - per, 3)) 127 | let endPosY = width * 1 / 4 * (1 - Math.pow(per, 4)) 128 | endPos = new cc.Vec2(endPosX, endPosY) 129 | } 130 | 131 | 132 | return endPos 133 | } 134 | // 约束纠正 135 | public applyConstraint() { 136 | // 两个质点之间的固定距离 137 | let normalDistance = this.node.width / (this.pointsCount - 1) 138 | let endPos = this._getEndPos() 139 | for (let t = 0; t < this.constraintTimes; t++) { 140 | this._updateEndPos(endPos) 141 | //由最后一个质点开始依次纠正 142 | for (let i = this.pointsCount - 1; i >= 1; i--) { 143 | let firstPoint = this._pointList[i - 1] 144 | let secondPoint = this._pointList[i] 145 | let delatPos = secondPoint.newPos.sub(firstPoint.newPos) 146 | let distance = delatPos.mag() 147 | let fixDirection :cc.Vec2 = null 148 | if (distance < normalDistance) { 149 | fixDirection = delatPos.normalize().negate() 150 | } else if (distance > normalDistance) { 151 | fixDirection = delatPos.normalize() 152 | } else { 153 | continue 154 | } 155 | 156 | let fixLen = Math.abs(distance - normalDistance) 157 | if (i == 1) { 158 | // 由于第一个质点是固定的,所以只对第二个质点做纠正 159 | let fixVector = fixDirection.mul(fixLen) 160 | secondPoint.newPos.subSelf(fixVector) 161 | } else { 162 | // 将两个质点之间的距离纠正为固定长度 163 | let fixHalfVector = fixDirection.mul(fixLen * 0.5) 164 | firstPoint.newPos.addSelf(fixHalfVector) 165 | secondPoint.newPos.subSelf(fixHalfVector) 166 | } 167 | } 168 | } 169 | } 170 | 171 | public draw() { 172 | if (!this._initedMaterial) { 173 | this.updateMaterial() 174 | } 175 | 176 | this.setVertsDirty() 177 | } 178 | } -------------------------------------------------------------------------------- /assets/scripts/verlet-render.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "50dbc8d7-621a-43c6-9026-910c29d3f1fa", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/shader.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "ec3e97af-aac1-45ae-a177-90181898e9b8", 4 | "isSubpackage": false, 5 | "subpackageName": "", 6 | "subMetas": {} 7 | } -------------------------------------------------------------------------------- /assets/shader/page-effect.effect: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. 2 | 3 | CCEffect %{ 4 | techniques: 5 | - passes: 6 | - vert: vs 7 | frag: fs 8 | blendState: 9 | targets: 10 | - blend: true 11 | rasterizerState: 12 | cullMode: none 13 | properties: 14 | texture0: { value: white } 15 | texture1: { value: white } 16 | alphaThreshold: { value: 0.5 } 17 | }% 18 | 19 | 20 | CCProgram vs %{ 21 | precision highp float; 22 | 23 | #include 24 | #include 25 | 26 | in vec3 a_position; 27 | in vec4 a_color; 28 | out vec4 v_color; 29 | 30 | #if USE_TEXTURE 31 | in vec2 a_uv0; 32 | out vec2 v_uv0; 33 | in float a_isFront; 34 | out float v_isFront; 35 | #endif 36 | 37 | void main () { 38 | vec4 pos = vec4(a_position, 1); 39 | 40 | #if CC_USE_MODEL 41 | pos = cc_matViewProj * cc_matWorld * pos; 42 | #else 43 | pos = cc_matViewProj * pos; 44 | #endif 45 | 46 | #if USE_TEXTURE 47 | v_uv0 = a_uv0; 48 | #endif 49 | 50 | v_color = a_color; 51 | v_isFront = a_isFront; 52 | gl_Position = pos; 53 | } 54 | }% 55 | 56 | 57 | CCProgram fs %{ 58 | precision highp float; 59 | 60 | #include 61 | 62 | in vec4 v_color; 63 | 64 | #if USE_TEXTURE 65 | in vec2 v_uv0; 66 | in float v_isFront; 67 | uniform sampler2D texture0; 68 | uniform sampler2D texture1; 69 | #endif 70 | 71 | void main () { 72 | vec4 o = vec4(1, 1, 1, 1); 73 | 74 | #if USE_TEXTURE 75 | if(v_isFront == 1.0) { 76 | o *= texture(texture0, v_uv0); 77 | } else { 78 | o *= texture(texture1, v_uv0); 79 | } 80 | #if CC_USE_ALPHA_ATLAS_TEXTURE 81 | o.a *= texture2D(texture, v_uv0 + vec2(0, 0.5)).r; 82 | #endif 83 | #endif 84 | 85 | o *= v_color; 86 | ALPHA_TEST(o); 87 | gl_FragColor = o; 88 | } 89 | }% 90 | -------------------------------------------------------------------------------- /assets/shader/page-effect.effect.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.25", 3 | "uuid": "71f0ba7f-9abb-4754-9100-100e47c782bc", 4 | "compiledShaders": [ 5 | { 6 | "glsl1": { 7 | "vert": "\nprecision highp float;\nuniform mediump mat4 cc_matViewProj;\nuniform mat4 cc_matWorld;\nattribute vec3 a_position;\nattribute vec4 a_color;\nvarying vec4 v_color;\n#if USE_TEXTURE\nattribute vec2 a_uv0;\nvarying vec2 v_uv0;\nattribute float a_isFront;\nvarying float v_isFront;\n#endif\nvoid main () {\n vec4 pos = vec4(a_position, 1);\n #if CC_USE_MODEL\n pos = cc_matViewProj * cc_matWorld * pos;\n #else\n pos = cc_matViewProj * pos;\n #endif\n #if USE_TEXTURE\n v_uv0 = a_uv0;\n #endif\n v_color = a_color;\n v_isFront = a_isFront;\n gl_Position = pos;\n}", 8 | "frag": "\nprecision highp float;\n#if USE_ALPHA_TEST\n uniform float alphaThreshold;\n#endif\nvoid ALPHA_TEST (in vec4 color) {\n #if USE_ALPHA_TEST\n if (color.a < alphaThreshold) discard;\n #endif\n}\nvoid ALPHA_TEST (in float alpha) {\n #if USE_ALPHA_TEST\n if (alpha < alphaThreshold) discard;\n #endif\n}\nvarying vec4 v_color;\n#if USE_TEXTURE\nvarying vec2 v_uv0;\nvarying float v_isFront;\nuniform sampler2D texture0;\nuniform sampler2D texture1;\n#endif\nvoid main () {\n vec4 o = vec4(1, 1, 1, 1);\n #if USE_TEXTURE\n if(v_isFront == 1.0) {\n o *= texture2D(texture0, v_uv0);\n } else {\n o *= texture2D(texture1, v_uv0);\n }\n #if CC_USE_ALPHA_ATLAS_TEXTURE\n o.a *= texture2D(texture, v_uv0 + vec2(0, 0.5)).r;\n #endif\n #endif\n o *= v_color;\n ALPHA_TEST(o);\n gl_FragColor = o;\n}" 9 | }, 10 | "glsl3": { 11 | "vert": "\nprecision highp float;\nuniform CCGlobal {\n highp vec4 cc_time;\n mediump vec4 cc_screenSize;\n mediump vec4 cc_screenScale;\n mediump vec4 cc_nativeSize;\n highp mat4 cc_matView;\n mediump mat4 cc_matViewInv;\n mediump mat4 cc_matProj;\n mediump mat4 cc_matProjInv;\n mediump mat4 cc_matViewProj;\n mediump mat4 cc_matViewProjInv;\n mediump vec4 cc_cameraPos;\n};\nuniform CCLocal {\n mat4 cc_matWorld;\n mat4 cc_matWorldIT;\n};\nin vec3 a_position;\nin vec4 a_color;\nout vec4 v_color;\n#if USE_TEXTURE\nin vec2 a_uv0;\nout vec2 v_uv0;\nin float a_isFront;\nout float v_isFront;\n#endif\nvoid main () {\n vec4 pos = vec4(a_position, 1);\n #if CC_USE_MODEL\n pos = cc_matViewProj * cc_matWorld * pos;\n #else\n pos = cc_matViewProj * pos;\n #endif\n #if USE_TEXTURE\n v_uv0 = a_uv0;\n #endif\n v_color = a_color;\n v_isFront = a_isFront;\n gl_Position = pos;\n}", 12 | "frag": "\nprecision highp float;\n#if USE_ALPHA_TEST\n uniform ALPHA_TEST {\n float alphaThreshold;\n };\n#endif\nvoid ALPHA_TEST (in vec4 color) {\n #if USE_ALPHA_TEST\n if (color.a < alphaThreshold) discard;\n #endif\n}\nvoid ALPHA_TEST (in float alpha) {\n #if USE_ALPHA_TEST\n if (alpha < alphaThreshold) discard;\n #endif\n}\nin vec4 v_color;\n#if USE_TEXTURE\nin vec2 v_uv0;\nin float v_isFront;\nuniform sampler2D texture0;\nuniform sampler2D texture1;\n#endif\nvoid main () {\n vec4 o = vec4(1, 1, 1, 1);\n #if USE_TEXTURE\n if(v_isFront == 1.0) {\n o *= texture(texture0, v_uv0);\n } else {\n o *= texture(texture1, v_uv0);\n }\n #if CC_USE_ALPHA_ATLAS_TEXTURE\n o.a *= texture2D(texture, v_uv0 + vec2(0, 0.5)).r;\n #endif\n #endif\n o *= v_color;\n ALPHA_TEST(o);\n gl_FragColor = o;\n}" 13 | } 14 | } 15 | ], 16 | "subMetas": {} 17 | } -------------------------------------------------------------------------------- /assets/shader/page-material.mtl: -------------------------------------------------------------------------------- 1 | { 2 | "__type__": "cc.Material", 3 | "_name": "page-material", 4 | "_objFlags": 0, 5 | "_native": "", 6 | "_effectAsset": { 7 | "__uuid__": "71f0ba7f-9abb-4754-9100-100e47c782bc" 8 | }, 9 | "_techniqueIndex": 0, 10 | "_techniqueData": { 11 | "0": { 12 | "defines": { 13 | "USE_TEXTURE": true 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /assets/shader/page-material.mtl.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.3", 3 | "uuid": "4f7c0520-945f-4c7c-9b51-9c012fca0299", 4 | "dataAsSubAsset": null, 5 | "subMetas": {} 6 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "experimentalDecorators": true 6 | }, 7 | "exclude": [ 8 | "node_modules", 9 | ".vscode", 10 | "library", 11 | "local", 12 | "settings", 13 | "temp" 14 | ] 15 | } -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "engine": "cocos-creator-js", 3 | "packages": "packages", 4 | "version": "2.3.1", 5 | "id": "26a8dfa4-7252-496d-ac0a-b822738f3337" 6 | } -------------------------------------------------------------------------------- /settings/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "start-scene": "current", 3 | "group-list": [ 4 | "default" 5 | ], 6 | "collision-matrix": [ 7 | [ 8 | true 9 | ] 10 | ], 11 | "excluded-modules": [ 12 | "3D Physics/Builtin" 13 | ], 14 | "last-module-event-record-time": 0, 15 | "design-resolution-width": 960, 16 | "design-resolution-height": 640, 17 | "fit-width": false, 18 | "fit-height": true, 19 | "use-project-simulator-setting": false, 20 | "simulator-orientation": false, 21 | "use-customize-simulator": false, 22 | "simulator-resolution": { 23 | "width": 960, 24 | "height": 640 25 | }, 26 | "assets-sort-type": "name", 27 | "facebook": { 28 | "enable": false, 29 | "appID": "", 30 | "live": { 31 | "enable": false 32 | }, 33 | "audience": { 34 | "enable": false 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /settings/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "game": { 3 | "name": "未知游戏", 4 | "appid": "UNKNOW" 5 | } 6 | } --------------------------------------------------------------------------------