mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 09:46:02 +08:00
[ts] More sequence fixes, see #1956
Also fixed runtime exports script, must copy all dragon atlas pages.
This commit is contained in:
parent
a0f1ed1b9b
commit
b48e2c33b8
@ -197,7 +197,7 @@ cp -f ../goblins/export/goblins-pma.png "$ROOT/spine-ts/spine-webgl/example/asse
|
|||||||
cp -f ../dragon/export/dragon-ess.json "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../dragon/export/dragon-ess.json "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
cp -f ../dragon/export/dragon-ess.skel "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../dragon/export/dragon-ess.skel "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
cp -f ../dragon/export/dragon-pma.atlas "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../dragon/export/dragon-pma.atlas "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
cp -f ../dragon/export/dragon-pma.png "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../dragon/export/dragon-pma*.png "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
|
|
||||||
cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
cp -f ../raptor/export/raptor-pro.skel "$ROOT/spine-ts/spine-webgl/example/assets/"
|
cp -f ../raptor/export/raptor-pro.skel "$ROOT/spine-ts/spine-webgl/example/assets/"
|
||||||
|
|||||||
7
spine-ts/.vscode/launch.json
vendored
7
spine-ts/.vscode/launch.json
vendored
@ -17,6 +17,13 @@
|
|||||||
"name": "drag-and-drop",
|
"name": "drag-and-drop",
|
||||||
"url": "http://localhost:8080/spine-webgl/example/drag-and-drop.html",
|
"url": "http://localhost:8080/spine-webgl/example/drag-and-drop.html",
|
||||||
"webRoot": "${workspaceFolder}"
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pwa-chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "barebones-dragon",
|
||||||
|
"url": "http://localhost:8080/spine-webgl/example/barebones-dragon.html",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -2210,7 +2210,7 @@ export class SequenceTimeline extends Timeline implements SlotTimeline {
|
|||||||
let index = modeAndIndex >> 4, count = this.attachment.sequence.regions.length;
|
let index = modeAndIndex >> 4, count = this.attachment.sequence.regions.length;
|
||||||
let mode = SequenceModeValues[modeAndIndex & 0xf];
|
let mode = SequenceModeValues[modeAndIndex & 0xf];
|
||||||
if (mode != SequenceMode.hold) {
|
if (mode != SequenceMode.hold) {
|
||||||
index += (time - before) / delay + 0.00001;
|
index += (((time - before) / delay + 0.00001) | 0);
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case SequenceMode.once:
|
case SequenceMode.once:
|
||||||
index = Math.min(count - 1, index);
|
index = Math.min(count - 1, index);
|
||||||
|
|||||||
@ -59,11 +59,11 @@ export class SkeletonBinary {
|
|||||||
attachmentLoader: AttachmentLoader;
|
attachmentLoader: AttachmentLoader;
|
||||||
private linkedMeshes = new Array<LinkedMesh>();
|
private linkedMeshes = new Array<LinkedMesh>();
|
||||||
|
|
||||||
constructor(attachmentLoader: AttachmentLoader) {
|
constructor (attachmentLoader: AttachmentLoader) {
|
||||||
this.attachmentLoader = attachmentLoader;
|
this.attachmentLoader = attachmentLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
readSkeletonData(binary: Uint8Array): SkeletonData {
|
readSkeletonData (binary: Uint8Array): SkeletonData {
|
||||||
let scale = this.scale;
|
let scale = this.scale;
|
||||||
|
|
||||||
let skeletonData = new SkeletonData();
|
let skeletonData = new SkeletonData();
|
||||||
@ -249,7 +249,7 @@ export class SkeletonBinary {
|
|||||||
return skeletonData;
|
return skeletonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readSkin(input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin {
|
private readSkin (input: BinaryInput, skeletonData: SkeletonData, defaultSkin: boolean, nonessential: boolean): Skin {
|
||||||
let skin = null;
|
let skin = null;
|
||||||
let slotCount = 0;
|
let slotCount = 0;
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ export class SkeletonBinary {
|
|||||||
return skin;
|
return skin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readAttachment(input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
|
private readAttachment (input: BinaryInput, skeletonData: SkeletonData, skin: Skin, slotIndex: number, attachmentName: string, nonessential: boolean): Attachment {
|
||||||
let scale = this.scale;
|
let scale = this.scale;
|
||||||
|
|
||||||
let name = input.readStringRef();
|
let name = input.readStringRef();
|
||||||
@ -449,7 +449,7 @@ export class SkeletonBinary {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readSequence(input: BinaryInput) {
|
private readSequence (input: BinaryInput) {
|
||||||
if (!input.readBoolean()) return null;
|
if (!input.readBoolean()) return null;
|
||||||
let sequence = new Sequence(input.readInt(true));
|
let sequence = new Sequence(input.readInt(true));
|
||||||
sequence.start = input.readInt(true);
|
sequence.start = input.readInt(true);
|
||||||
@ -458,7 +458,7 @@ export class SkeletonBinary {
|
|||||||
return sequence;
|
return sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readVertices(input: BinaryInput, vertexCount: number): Vertices {
|
private readVertices (input: BinaryInput, vertexCount: number): Vertices {
|
||||||
let scale = this.scale;
|
let scale = this.scale;
|
||||||
let verticesLength = vertexCount << 1;
|
let verticesLength = vertexCount << 1;
|
||||||
let vertices = new Vertices();
|
let vertices = new Vertices();
|
||||||
@ -483,7 +483,7 @@ export class SkeletonBinary {
|
|||||||
return vertices;
|
return vertices;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readFloatArray(input: BinaryInput, n: number, scale: number): number[] {
|
private readFloatArray (input: BinaryInput, n: number, scale: number): number[] {
|
||||||
let array = new Array<number>(n);
|
let array = new Array<number>(n);
|
||||||
if (scale == 1) {
|
if (scale == 1) {
|
||||||
for (let i = 0; i < n; i++)
|
for (let i = 0; i < n; i++)
|
||||||
@ -495,7 +495,7 @@ export class SkeletonBinary {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readShortArray(input: BinaryInput): number[] {
|
private readShortArray (input: BinaryInput): number[] {
|
||||||
let n = input.readInt(true);
|
let n = input.readInt(true);
|
||||||
let array = new Array<number>(n);
|
let array = new Array<number>(n);
|
||||||
for (let i = 0; i < n; i++)
|
for (let i = 0; i < n; i++)
|
||||||
@ -503,7 +503,7 @@ export class SkeletonBinary {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readAnimation(input: BinaryInput, name: string, skeletonData: SkeletonData): Animation {
|
private readAnimation (input: BinaryInput, name: string, skeletonData: SkeletonData): Animation {
|
||||||
input.readInt(true); // Number of timelines.
|
input.readInt(true); // Number of timelines.
|
||||||
let timelines = new Array<Timeline>();
|
let timelines = new Array<Timeline>();
|
||||||
let scale = this.scale;
|
let scale = this.scale;
|
||||||
@ -996,30 +996,30 @@ export class SkeletonBinary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BinaryInput {
|
export class BinaryInput {
|
||||||
constructor(data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) {
|
constructor (data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
readByte(): number {
|
readByte (): number {
|
||||||
return this.buffer.getInt8(this.index++);
|
return this.buffer.getInt8(this.index++);
|
||||||
}
|
}
|
||||||
|
|
||||||
readUnsignedByte(): number {
|
readUnsignedByte (): number {
|
||||||
return this.buffer.getUint8(this.index++);
|
return this.buffer.getUint8(this.index++);
|
||||||
}
|
}
|
||||||
|
|
||||||
readShort(): number {
|
readShort (): number {
|
||||||
let value = this.buffer.getInt16(this.index);
|
let value = this.buffer.getInt16(this.index);
|
||||||
this.index += 2;
|
this.index += 2;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
readInt32(): number {
|
readInt32 (): number {
|
||||||
let value = this.buffer.getInt32(this.index)
|
let value = this.buffer.getInt32(this.index)
|
||||||
this.index += 4;
|
this.index += 4;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
readInt(optimizePositive: boolean) {
|
readInt (optimizePositive: boolean) {
|
||||||
let b = this.readByte();
|
let b = this.readByte();
|
||||||
let result = b & 0x7F;
|
let result = b & 0x7F;
|
||||||
if ((b & 0x80) != 0) {
|
if ((b & 0x80) != 0) {
|
||||||
@ -1041,12 +1041,12 @@ export class BinaryInput {
|
|||||||
return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));
|
return optimizePositive ? result : ((result >>> 1) ^ -(result & 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
readStringRef(): string {
|
readStringRef (): string {
|
||||||
let index = this.readInt(true);
|
let index = this.readInt(true);
|
||||||
return index == 0 ? null : this.strings[index - 1];
|
return index == 0 ? null : this.strings[index - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
readString(): string {
|
readString (): string {
|
||||||
let byteCount = this.readInt(true);
|
let byteCount = this.readInt(true);
|
||||||
switch (byteCount) {
|
switch (byteCount) {
|
||||||
case 0:
|
case 0:
|
||||||
@ -1077,13 +1077,13 @@ export class BinaryInput {
|
|||||||
return chars;
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
readFloat(): number {
|
readFloat (): number {
|
||||||
let value = this.buffer.getFloat32(this.index);
|
let value = this.buffer.getFloat32(this.index);
|
||||||
this.index += 4;
|
this.index += 4;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
readBoolean(): boolean {
|
readBoolean (): boolean {
|
||||||
return this.readByte() != 0;
|
return this.readByte() != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1094,7 +1094,7 @@ class LinkedMesh {
|
|||||||
mesh: MeshAttachment;
|
mesh: MeshAttachment;
|
||||||
inheritTimeline: boolean;
|
inheritTimeline: boolean;
|
||||||
|
|
||||||
constructor(mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
|
constructor (mesh: MeshAttachment, skin: string, slotIndex: number, parent: string, inheritDeform: boolean) {
|
||||||
this.mesh = mesh;
|
this.mesh = mesh;
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
this.slotIndex = slotIndex;
|
this.slotIndex = slotIndex;
|
||||||
@ -1104,12 +1104,12 @@ class LinkedMesh {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Vertices {
|
class Vertices {
|
||||||
constructor(public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
|
constructor (public bones: Array<number> = null, public vertices: Array<number> | Float32Array = null) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping }
|
enum AttachmentType { Region, BoundingBox, Mesh, LinkedMesh, Path, Point, Clipping }
|
||||||
|
|
||||||
function readTimeline1(input: BinaryInput, timeline: CurveTimeline1, scale: number): CurveTimeline1 {
|
function readTimeline1 (input: BinaryInput, timeline: CurveTimeline1, scale: number): CurveTimeline1 {
|
||||||
let time = input.readFloat(), value = input.readFloat() * scale;
|
let time = input.readFloat(), value = input.readFloat() * scale;
|
||||||
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
|
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
|
||||||
timeline.setFrame(frame, time, value);
|
timeline.setFrame(frame, time, value);
|
||||||
@ -1128,7 +1128,7 @@ function readTimeline1(input: BinaryInput, timeline: CurveTimeline1, scale: numb
|
|||||||
return timeline;
|
return timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readTimeline2(input: BinaryInput, timeline: CurveTimeline2, scale: number): CurveTimeline2 {
|
function readTimeline2 (input: BinaryInput, timeline: CurveTimeline2, scale: number): CurveTimeline2 {
|
||||||
let time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale;
|
let time = input.readFloat(), value1 = input.readFloat() * scale, value2 = input.readFloat() * scale;
|
||||||
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
|
for (let frame = 0, bezier = 0, frameLast = timeline.getFrameCount() - 1; ; frame++) {
|
||||||
timeline.setFrame(frame, time, value1, value2);
|
timeline.setFrame(frame, time, value1, value2);
|
||||||
@ -1149,7 +1149,7 @@ function readTimeline2(input: BinaryInput, timeline: CurveTimeline2, scale: numb
|
|||||||
return timeline;
|
return timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBezier(input: BinaryInput, timeline: CurveTimeline, bezier: number, frame: number, value: number,
|
function setBezier (input: BinaryInput, timeline: CurveTimeline, bezier: number, frame: number, value: number,
|
||||||
time1: number, time2: number, value1: number, value2: number, scale: number) {
|
time1: number, time2: number, value1: number, value2: number, scale: number) {
|
||||||
timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(), input.readFloat() * scale, time2, value2);
|
timeline.setBezier(bezier, frame, value, time1, value1, input.readFloat(), input.readFloat() * scale, input.readFloat(), input.readFloat() * scale, time2, value2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,19 +32,25 @@ import { Color } from "../Utils"
|
|||||||
import { Sequence } from "./Sequence"
|
import { Sequence } from "./Sequence"
|
||||||
|
|
||||||
export interface HasTextureRegion {
|
export interface HasTextureRegion {
|
||||||
/** The name used to find the {@link #region()}. */
|
/** The name used to find the {@link #getRegion()}. */
|
||||||
path: string;
|
getPath (): string;
|
||||||
|
|
||||||
/** The region used to draw the attachment. After setting the region or if the region's properties are changed,
|
setPath (path: string): void;
|
||||||
|
|
||||||
|
getRegion (): TextureRegion;
|
||||||
|
|
||||||
|
/** Sets the region used to draw the attachment. After setting the region or if the region's properties are changed,
|
||||||
* {@link #updateRegion()} must be called. */
|
* {@link #updateRegion()} must be called. */
|
||||||
region: TextureRegion;
|
setRegion (region: TextureRegion): void;
|
||||||
|
|
||||||
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
|
/** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the
|
||||||
* {@link #getRegion()} or if the region's properties are changed. */
|
* {@link #getRegion()} or if the region's properties are changed. */
|
||||||
updateRegion (): void;
|
updateRegion (): void;
|
||||||
|
|
||||||
/** The color to tint the attachment. */
|
/** The color to tint the attachment. */
|
||||||
color: Color;
|
getColor (): Color;
|
||||||
|
|
||||||
sequence: Sequence;
|
getSequence (): Sequence;
|
||||||
|
|
||||||
|
setSequence (sequence: Sequence): void;
|
||||||
}
|
}
|
||||||
@ -145,7 +145,8 @@ export class RegionAttachment extends Attachment implements HasTextureRegion {
|
|||||||
* @param offset The <code>worldVertices</code> index to begin writing values.
|
* @param offset The <code>worldVertices</code> index to begin writing values.
|
||||||
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
|
* @param stride The number of <code>worldVertices</code> entries between the value pairs written. */
|
||||||
computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
computeWorldVertices (slot: Slot, worldVertices: NumberArrayLike, offset: number, stride: number) {
|
||||||
if (this.sequence != null) this.sequence.apply(slot, this);
|
if (this.sequence != null)
|
||||||
|
this.sequence.apply(slot, this);
|
||||||
|
|
||||||
let bone = slot.bone;
|
let bone = slot.bone;
|
||||||
let vertexOffset = this.offset;
|
let vertexOffset = this.offset;
|
||||||
|
|||||||
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_2.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_3.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_4.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_5.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_6.png
Normal file
BIN
spine-ts/spine-webgl/example/assets/dragon-pma_6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
81
spine-ts/spine-webgl/example/barebones-dragon.html
Normal file
81
spine-ts/spine-webgl/example/barebones-dragon.html
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<html>
|
||||||
|
<script src="../dist/iife/spine-webgl.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas" style="position: absolute; width: 100%; height: 100%;"></canvas>
|
||||||
|
<script>
|
||||||
|
class App {
|
||||||
|
constructor() {
|
||||||
|
this.skeleton = null;
|
||||||
|
this.animationState = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAssets(canvas) {
|
||||||
|
// Load the skeleton file.
|
||||||
|
canvas.assetManager.loadBinary("assets/dragon-ess.skel");
|
||||||
|
// Load the atlas and its pages.
|
||||||
|
canvas.assetManager.loadTextureAtlas("assets/dragon-pma.atlas");
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(canvas) {
|
||||||
|
let assetManager = canvas.assetManager;
|
||||||
|
|
||||||
|
// Create the texture atlas.
|
||||||
|
var atlas = assetManager.require("assets/dragon-pma.atlas");
|
||||||
|
|
||||||
|
// Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
|
||||||
|
var atlasLoader = new spine.AtlasAttachmentLoader(atlas);
|
||||||
|
|
||||||
|
// Create a SkeletonBinary instance for parsing the .skel file.
|
||||||
|
var skeletonBinary = new spine.SkeletonBinary(atlasLoader);
|
||||||
|
|
||||||
|
// Set the scale to apply during parsing, parse the file, and create a new skeleton.
|
||||||
|
skeletonBinary.scale = 1;
|
||||||
|
var skeletonData = skeletonBinary.readSkeletonData(assetManager.require("assets/dragon-ess.skel"));
|
||||||
|
this.skeleton = new spine.Skeleton(skeletonData);
|
||||||
|
|
||||||
|
// Create an AnimationState, and set the "run" animation in looping mode.
|
||||||
|
var animationStateData = new spine.AnimationStateData(skeletonData);
|
||||||
|
this.animationState = new spine.AnimationState(animationStateData);
|
||||||
|
this.animationState.setAnimation(0, "flying", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(canvas, delta) {
|
||||||
|
// Update the animation state using the delta time.
|
||||||
|
this.animationState.update(delta);
|
||||||
|
// Apply the animation state to the skeleton.
|
||||||
|
this.animationState.apply(this.skeleton);
|
||||||
|
// Let the skeleton update the transforms of its bones.
|
||||||
|
this.skeleton.updateWorldTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(canvas) {
|
||||||
|
let renderer = canvas.renderer;
|
||||||
|
// Resize the viewport to the full canvas.
|
||||||
|
renderer.resize(spine.ResizeMode.Expand);
|
||||||
|
|
||||||
|
// Clear the canvas with a light gray color.
|
||||||
|
canvas.clear(0.2, 0.2, 0.2, 1);
|
||||||
|
|
||||||
|
// Begin rendering.
|
||||||
|
renderer.begin();
|
||||||
|
// Draw the skeleton
|
||||||
|
renderer.drawSkeleton(this.skeleton, true);
|
||||||
|
// Complete rendering.
|
||||||
|
renderer.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new spine.SpineCanvas(document.getElementById("canvas"), {
|
||||||
|
app: new App()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user