From 2b0440d7f734c5cecfdc51735f47ba7f20dc2bb2 Mon Sep 17 00:00:00 2001 From: Hanxing Yang Date: Fri, 9 Aug 2024 13:58:18 +0800 Subject: [PATCH] keep order of sprites & sounds (#720) --- spx-gui/src/models/project/index.test.ts | 40 +++++++++++++++++++++++- spx-gui/src/models/project/index.ts | 32 ++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/spx-gui/src/models/project/index.test.ts b/spx-gui/src/models/project/index.test.ts index 4605e3942..209d088a5 100644 --- a/spx-gui/src/models/project/index.test.ts +++ b/spx-gui/src/models/project/index.test.ts @@ -3,7 +3,7 @@ import { Sprite } from '../sprite' import { Animation } from '../animation' import { Sound } from '../sound' import { Costume } from '../costume' -import { fromText } from '../common/file' +import { fromText, type Files } from '../common/file' import { Project } from '.' function mockFile(name = 'mocked') { @@ -35,4 +35,42 @@ describe('Project', () => { await project.loadGameFiles(files) expect(project.sprites[0].animations[0].sound).toBe(project.sounds[0].name) }) + + it('should preserve order for sprites & sounds however files are sorted', async () => { + const project = makeProject() + + project.sprites[0].setName('Sprite1') + const sprite3 = new Sprite('Sprite3') + project.addSprite(sprite3) + const sprite2 = new Sprite('Sprite2') + project.addSprite(sprite2) + + project.sounds[0].setName('sound1') + const sound3 = new Sound('sound3', mockFile()) + project.addSound(sound3) + const sound2 = new Sound('sound2', mockFile()) + project.addSound(sound2) + + const files = project.exportGameFiles() + + const reversedFiles = Object.keys(files) + .reverse() + .reduce((acc, key) => { + acc[key] = files[key] + return acc + }, {}) + await project.loadGameFiles(reversedFiles) + expect(project.sprites.map((s) => s.name)).toEqual(['Sprite1', 'Sprite3', 'Sprite2']) + expect(project.sounds.map((s) => s.name)).toEqual(['sound1', 'sound3', 'sound2']) + + const shuffledFiles = Object.keys(files) + .sort(() => Math.random() - 0.5) + .reduce((acc, key) => { + acc[key] = files[key] + return acc + }, {}) + await project.loadGameFiles(shuffledFiles) + expect(project.sprites.map((s) => s.name)).toEqual(['Sprite1', 'Sprite3', 'Sprite2']) + expect(project.sounds.map((s) => s.name)).toEqual(['sound1', 'sound3', 'sound2']) + }) }) diff --git a/spx-gui/src/models/project/index.ts b/spx-gui/src/models/project/index.ts index 776b57e60..acb88248b 100644 --- a/spx-gui/src/models/project/index.ts +++ b/spx-gui/src/models/project/index.ts @@ -74,6 +74,16 @@ type RawProjectConfig = RawStageConfig & { // TODO: support other types in zorder zorder?: string[] run?: RunConfig + /** + * Sprite order info, used by Go+ Builder to determine the order of sprites. + * `builderSpriteOrder` is [builder-only data](https://github.com/goplus/builder/issues/714#issuecomment-2274863055), whose name should be prefixed with `builder_` as a convention. + */ + builder_spriteOrder?: string[] + /** + * Sound order info, used by Go+ Builder to determine the order of sounds. + * `builderSoundOrder` is [builder-only data](https://github.com/goplus/builder/issues/714#issuecomment-2274863055), whose name should be prefixed with `builder_` as a convention. + */ + builder_soundOrder?: string[] // TODO: camera } @@ -264,7 +274,12 @@ export class Project extends Disposable { if (configFile != null) { Object.assign(config, await toConfig(configFile)) } - const { zorder, ...stageConfig } = config + const { + zorder, + builder_spriteOrder: spriteOrder, + builder_soundOrder: soundOrder, + ...stageConfig + } = config const [stage, sounds, sprites] = await Promise.all([ Stage.load(stageConfig, files), Sound.loadAll(files), @@ -272,9 +287,9 @@ export class Project extends Disposable { ]) this.stage = stage this.sprites.splice(0).forEach((s) => s.dispose()) - sprites.forEach((s) => this.addSprite(s)) + orderBy(sprites, spriteOrder).forEach((s) => this.addSprite(s)) this.sounds.splice(0).forEach((s) => s.dispose()) - sounds.forEach((s) => this.addSound(s)) + orderBy(sounds, soundOrder).forEach((s) => this.addSound(s)) this.zorder = zorder ?? [] this.autoSelect() } @@ -291,7 +306,9 @@ export class Project extends Disposable { width: stageConfig.map?.width, height: stageConfig.map?.height }, - zorder: this.zorder + zorder: this.zorder, + builder_spriteOrder: this.sprites.map((s) => s.name), + builder_soundOrder: this.sounds.map((s) => s.name) } files[projectConfigFilePath] = fromConfig(projectConfigFileName, config) Object.assign(files, stageFiles) @@ -487,3 +504,10 @@ export class Project extends Disposable { export function fullName(owner: string, name: string) { return `${owner}/${name}` } + +function orderBy(list: T[], order: string[] | undefined) { + if (order == null) return list + return list.slice().sort((a, b) => { + return order.indexOf(a.name) - order.indexOf(b.name) + }) +}