diff --git a/README.md b/README.md index cb4f96276..8255ba5c1 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ such as `character.player` or `env.ground`. "object:TankConfiguration::CombinedTank/TankBase": "vehicle.tank.base" }, "effects": { - "effect:Game Scene//SkyBox": { "frontFaceResourceName": "sky.day.front" } + "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": ["scene:Game Scene/Camera"] } @@ -66,8 +66,9 @@ such as `character.player` or `env.ground`. An object is referred to as `scene:/`, `global/`, or `object:::/` for the children of a custom object. A 3D model maps to one model slot. A 3D cube maps either to one texture -slot for all its faces, or to one slot per face. A skybox effect maps each of -its texture parameters. +slot for all its faces, or to one slot per face. A skybox effect, referred to +as `effect://`, maps to one skybox slot: the theme's +skybox replaces its six faces. **When you add a 3D starter, add its `theme-slots.json`.** The build refuses to publish a 3D starter without one, or one where a 3D model or cube is neither diff --git a/examples/starting-3D-platformer/theme-slots.json b/examples/starting-3D-platformer/theme-slots.json index c788044f7..1b6aeb8d6 100644 --- a/examples/starting-3D-platformer/theme-slots.json +++ b/examples/starting-3D-platformer/theme-slots.json @@ -26,13 +26,6 @@ } }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" } -} \ No newline at end of file +} diff --git a/examples/starting-3d-car-racing/theme-slots.json b/examples/starting-3d-car-racing/theme-slots.json index a56a7542a..83259ff28 100644 --- a/examples/starting-3d-car-racing/theme-slots.json +++ b/examples/starting-3d-car-racing/theme-slots.json @@ -15,13 +15,6 @@ "scene:Game Scene/Walls_3D": "env.wall" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" } -} \ No newline at end of file +} diff --git a/examples/starting-3d-driving/theme-slots.json b/examples/starting-3d-driving/theme-slots.json index af6a0aa79..ed37222a2 100644 --- a/examples/starting-3d-driving/theme-slots.json +++ b/examples/starting-3d-driving/theme-slots.json @@ -8,13 +8,6 @@ "scene:Game Scene/Walls_3D": "env.wall" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" } -} \ No newline at end of file +} diff --git a/examples/starting-3d-flight-sim/theme-slots.json b/examples/starting-3d-flight-sim/theme-slots.json index dc6b2a517..7e376460d 100644 --- a/examples/starting-3d-flight-sim/theme-slots.json +++ b/examples/starting-3d-flight-sim/theme-slots.json @@ -7,13 +7,6 @@ "scene:Game Scene/PlayerAircraft": "vehicle.aircraft.player" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" } -} \ No newline at end of file +} diff --git a/examples/starting-3d-tank/theme-slots.json b/examples/starting-3d-tank/theme-slots.json index 422896d5c..136d5f06b 100644 --- a/examples/starting-3d-tank/theme-slots.json +++ b/examples/starting-3d-tank/theme-slots.json @@ -11,13 +11,6 @@ "object:TankConfiguration::TankTop/TankCanon": "vehicle.tank.cannon" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" } -} \ No newline at end of file +} diff --git a/examples/starting-first-person-farming/theme-slots.json b/examples/starting-first-person-farming/theme-slots.json index 362317849..92d6fc8a2 100644 --- a/examples/starting-first-person-farming/theme-slots.json +++ b/examples/starting-first-person-farming/theme-slots.json @@ -26,14 +26,7 @@ } }, "effects": { - "effect:Game Scene//Effect2": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//Effect2": "sky.day" }, "ignoredObjects": [ "scene:Game Scene/Player" diff --git a/examples/starting-first-person-horror/theme-slots.json b/examples/starting-first-person-horror/theme-slots.json index 924eb36f6..b0cb3b77c 100644 --- a/examples/starting-first-person-horror/theme-slots.json +++ b/examples/starting-first-person-horror/theme-slots.json @@ -19,14 +19,7 @@ "scene:Game Scene/Monster": "character.monster" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.dark.back", - "bottomFaceResourceName": "sky.dark.bottom", - "frontFaceResourceName": "sky.dark.front", - "leftFaceResourceName": "sky.dark.left", - "rightFaceResourceName": "sky.dark.right", - "topFaceResourceName": "sky.dark.top" - } + "effect:Game Scene//SkyBox": "sky.dark" }, "ignoredObjects": [ "scene:Game Scene/PathfindingObstacle", diff --git a/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json b/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json index d4db8fbe3..545b5e236 100644 --- a/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json +++ b/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json @@ -876,7 +876,7 @@ "locked": true, "name": "Gun", "persistentUuid": "84abff0d-54b1-41a2-b557-c1442e215f5a", - "rotationX": 0, + "rotationX": 180, "rotationY": 90, "width": 0, "x": 1156, @@ -1671,8 +1671,8 @@ "modelResourceName": "assets\\StartingGun_orange.glb", "originLocation": "ModelOrigin", "rotationX": 0, - "rotationY": 180, - "rotationZ": 0, + "rotationY": 0, + "rotationZ": 180, "width": 2252.6, "animations": [] } diff --git a/examples/starting-first-person-shooter-horror/theme-slots.json b/examples/starting-first-person-shooter-horror/theme-slots.json index fe2281f83..e592de59b 100644 --- a/examples/starting-first-person-shooter-horror/theme-slots.json +++ b/examples/starting-first-person-shooter-horror/theme-slots.json @@ -20,14 +20,7 @@ "scene:Game Scene/Gun": "weapon.handheld" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.dark.back", - "bottomFaceResourceName": "sky.dark.bottom", - "frontFaceResourceName": "sky.dark.front", - "leftFaceResourceName": "sky.dark.left", - "rightFaceResourceName": "sky.dark.right", - "topFaceResourceName": "sky.dark.top" - } + "effect:Game Scene//SkyBox": "sky.dark" }, "ignoredObjects": [ "scene:Game Scene/PathfindingObstacle", diff --git a/examples/starting-first-person-shooter/starting-first-person-shooter.json b/examples/starting-first-person-shooter/starting-first-person-shooter.json index 5e082decc..631ce52e0 100644 --- a/examples/starting-first-person-shooter/starting-first-person-shooter.json +++ b/examples/starting-first-person-shooter/starting-first-person-shooter.json @@ -555,7 +555,7 @@ "locked": true, "name": "Gun", "persistentUuid": "701527b6-1157-47a2-bd12-4e6ccb24835e", - "rotationX": 0, + "rotationX": 180, "rotationY": 90, "width": 0, "x": 1156, @@ -1406,8 +1406,8 @@ "modelResourceName": "assets\\StartingGun_orange.glb", "originLocation": "ModelOrigin", "rotationX": 0, - "rotationY": 180, - "rotationZ": 0, + "rotationY": 0, + "rotationZ": 180, "width": 2252.64, "animations": [] } diff --git a/examples/starting-first-person-shooter/theme-slots.json b/examples/starting-first-person-shooter/theme-slots.json index d4f52dd2d..cef9a0271 100644 --- a/examples/starting-first-person-shooter/theme-slots.json +++ b/examples/starting-first-person-shooter/theme-slots.json @@ -20,14 +20,7 @@ "scene:Game Scene/Target": "character.player" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ "scene:Game Scene/Player" diff --git a/examples/starting-first-person-survival-crafting/theme-slots.json b/examples/starting-first-person-survival-crafting/theme-slots.json index 216e28b1a..95d95e90e 100644 --- a/examples/starting-first-person-survival-crafting/theme-slots.json +++ b/examples/starting-first-person-survival-crafting/theme-slots.json @@ -21,14 +21,7 @@ "scene:Game Scene/Harvest_Tree": "nature.tree" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ "scene:Game Scene/Player" diff --git a/examples/starting-first-person/theme-slots.json b/examples/starting-first-person/theme-slots.json index ba95df2b7..fa1a9ca91 100644 --- a/examples/starting-first-person/theme-slots.json +++ b/examples/starting-first-person/theme-slots.json @@ -18,14 +18,7 @@ "scene:Game Scene/PushableBox": "env.crate" }, "effects": { - "effect:Game Scene//SkyBox": { - "backFaceResourceName": "sky.day.back", - "bottomFaceResourceName": "sky.day.bottom", - "frontFaceResourceName": "sky.day.front", - "leftFaceResourceName": "sky.day.left", - "rightFaceResourceName": "sky.day.right", - "topFaceResourceName": "sky.day.top" - } + "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ "scene:Game Scene/Player" diff --git a/scripts/lib/StarterThemer.js b/scripts/lib/StarterThemer.js index c79806746..11d1c40fc 100644 --- a/scripts/lib/StarterThemer.js +++ b/scripts/lib/StarterThemer.js @@ -4,14 +4,22 @@ /** * A theme, as published by the assets repository: for each slot, the file to - * use and, for a 3D model, the serialized content of the asset's object. + * use and, for a 3D model, the serialized content of the asset's object. A + * skybox slot has one file per face instead. * @typedef {{ - * kind: 'model' | 'texture', * file: string, * resourceName: string, + * origin?: {name: string, identifier: string}, + * }} ThemeFile + * + * @typedef {ThemeFile & { + * kind: 'model' | 'texture', * objectContent?: any, * assetStoreId?: string, - * origin?: {name: string, identifier: string}, + * } | { + * kind: 'skybox', + * faces: Object., + * assetStoreId?: string, * }} ThemeSlot * * @typedef {{ @@ -147,8 +155,8 @@ const getSizeRatio = (objectContent, themeContent) => { * The starter's theme-slots.json says which of its objects play which slot. * Each such object gets the theme's asset for that slot: a 3D model takes the * theme model's dimensions, rotation, material and animations while keeping - * its name, behaviors, variables, instances, origin and center; a cube face or - * a skybox face points at the theme image. + * its name, behaviors, variables, instances, origin and center; a cube face + * points at the theme image; a skybox effect points at the six theme images. * * Each theme file becomes a resource named like in the asset store, and the * resources of the starter that nothing uses anymore are removed. @@ -179,39 +187,36 @@ const applyThemeToStarter = (projectObject, starterThemeSlots, theme) => { let changedObjectsCount = 0; /** - * Add the resource of a slot to the project, configured like the resource it - * replaces, and return its name. + * Add a file of a slot to the project as a resource configured like the + * resource it replaces, and return its name. * @param {string} replacedResourceName * @param {string} slotId + * @param {ThemeFile} themeFile * @returns {string | null} */ - const useThemeResource = (replacedResourceName, slotId) => { - const slot = theme.slots[slotId]; - if (!slot) { - missingSlots.add(slotId); - return null; - } + const useThemeResource = (replacedResourceName, slotId, themeFile) => { const replacedResource = resourceByName.get(replacedResourceName); if (!replacedResource) return null; // A resource of the starter can already have that name: number the new one. - let name = slot.resourceName; + let name = themeFile.resourceName; for ( let index = 2; - resourceByName.has(name) && resourceByName.get(name).file !== slot.file; + resourceByName.has(name) && + resourceByName.get(name).file !== themeFile.file; index++ ) { - name = `${slot.resourceName} ${index}`; + name = `${themeFile.resourceName} ${index}`; } if (!resourceByName.has(name)) { const themeResource = { ...replacedResource, name, - file: slot.file, - origin: slot.origin || { + file: themeFile.file, + origin: themeFile.origin || { name: 'gdevelop-asset-store', - identifier: slot.file, + identifier: themeFile.file, }, }; resources.push(themeResource); @@ -239,7 +244,8 @@ const applyThemeToStarter = (projectObject, starterThemeSlots, theme) => { } const modelResourceName = useThemeResource( objectContent.modelResourceName, - mapping + mapping, + slot ); if (!modelResourceName) return; @@ -272,7 +278,16 @@ const applyThemeToStarter = (projectObject, starterThemeSlots, theme) => { const faceProperty = cubeFaceProperties[face]; const resourceName = objectContent[faceProperty]; if (!slotId || !resourceName) return; - const themeResourceName = useThemeResource(resourceName, slotId); + const slot = theme.slots[slotId]; + if (!slot || slot.kind !== 'texture') { + missingSlots.add(slotId); + return; + } + const themeResourceName = useThemeResource( + resourceName, + slotId, + slot + ); if (!themeResourceName) return; objectContent[faceProperty] = themeResourceName; changed = true; @@ -283,18 +298,25 @@ const applyThemeToStarter = (projectObject, starterThemeSlots, theme) => { ); Object.entries(starterThemeSlots.effects || {}).forEach( - ([effectPath, parameters]) => { + ([effectPath, slotId]) => { const effect = effectsByPath.get(effectPath); if (!effect || !effect.stringParameters) return; - Object.entries(parameters).forEach(([parameter, slotId]) => { - const resourceName = effect.stringParameters[parameter]; - if (!resourceName) return; + const slot = theme.slots[slotId]; + if (!slot || slot.kind !== 'skybox') { + missingSlots.add(slotId); + return; + } + Object.entries(cubeFaceProperties).forEach(([face, faceProperty]) => { + const resourceName = effect.stringParameters[faceProperty]; + const themeFile = slot.faces[face]; + if (!resourceName || !themeFile) return; const themeResourceName = useThemeResource( String(resourceName), - slotId + slotId, + themeFile ); if (themeResourceName) { - effect.stringParameters[parameter] = themeResourceName; + effect.stringParameters[faceProperty] = themeResourceName; } }); } diff --git a/scripts/lib/ThemeSlots.js b/scripts/lib/ThemeSlots.js index c424778a5..c6f179d05 100644 --- a/scripts/lib/ThemeSlots.js +++ b/scripts/lib/ThemeSlots.js @@ -6,8 +6,10 @@ const fs = require('fs').promises; * The vocabulary of slots a theme can fill, shared by every starter. * @typedef {{ * version: number, - * slots: Array<{id: string, kind: 'model' | 'texture', label: string}>, + * slots: Array<{id: string, kind: ThemeSlotKind, label: string}>, * }} ThemeSlotsVocabulary + * + * @typedef {'model' | 'texture' | 'skybox'} ThemeSlotKind */ /** @@ -15,10 +17,11 @@ const fs = require('fs').promises; * re-skins, and as what. Object paths are `scene:/`, * `global/` or `object:::/`. A cube * maps either to one texture slot for all six faces, or to one slot per face. + * A skybox effect (`effect://`) maps to one skybox slot. * @typedef {{ * version: number, * objects: Object.>, - * effects?: Object.>, + * effects?: Object., * ignoredObjects?: Array, * }} StarterThemeSlots */ @@ -133,7 +136,7 @@ const checkStarterThemeSlots = ( const ignored = new Set(starterThemeSlots.ignoredObjects || []); const mappedEffects = starterThemeSlots.effects || {}; - /** @param {string} where @param {string} slotId @param {'model'|'texture'} kind */ + /** @param {string} where @param {string} slotId @param {ThemeSlotKind} kind */ const checkSlot = (where, slotId, kind) => { const declaredKind = slotKindById.get(slotId); if (!declaredKind) { @@ -207,7 +210,7 @@ const checkStarterThemeSlots = ( ); } }); - Object.entries(mappedEffects).forEach(([effectPath, faces]) => { + Object.entries(mappedEffects).forEach(([effectPath, slotId]) => { if (!effectPaths.has(effectPath)) { errors.push( new Error( @@ -216,13 +219,15 @@ const checkStarterThemeSlots = ( ); return; } - Object.entries(faces).forEach(([parameter, slotId]) => - checkSlot( - `skybox "${effectPath}" parameter "${parameter}"`, - slotId, - 'texture' - ) - ); + if (typeof slotId !== 'string') { + errors.push( + new Error( + `Starter "${slug}": the skybox "${effectPath}" must map to a single skybox slot.` + ) + ); + return; + } + checkSlot(`skybox "${effectPath}"`, slotId, 'skybox'); }); return errors; diff --git a/scripts/lib/__tests__/StarterThemer.spec.js b/scripts/lib/__tests__/StarterThemer.spec.js index ea0747391..e0f58ccd5 100644 --- a/scripts/lib/__tests__/StarterThemer.spec.js +++ b/scripts/lib/__tests__/StarterThemer.spec.js @@ -18,7 +18,7 @@ const makeStarterThemeSlots = () => ({ 'object:TankConfiguration::CombinedTank/TankBase': 'character.player', }, effects: { - 'effect:Game Scene//SkyBox': { frontFaceResourceName: 'sky.day.front' }, + 'effect:Game Scene//SkyBox': 'sky.day', }, ignoredObjects: ['scene:Game Scene/Camera'], }); @@ -61,11 +61,23 @@ const makeStarterTheme = () => ({ file: 'https://asset-resources.gdevelop.io/public-resources/Planks.png', resourceName: 'Planks.png', }, - 'sky.day.front': { - kind: 'texture', - file: 'https://asset-resources.gdevelop.io/public-resources/Tropical.png', - resourceName: 'Tropical.png', - origin: { name: 'gdevelop-asset-store', identifier: 'tropical-front' }, + 'sky.day': { + kind: 'skybox', + assetStoreId: 'sky123', + faces: { + front: { + file: 'https://asset-resources.gdevelop.io/public-resources/Tropical_Front.png', + resourceName: 'Tropical_Front.png', + origin: { + name: 'gdevelop-asset-store', + identifier: 'tropical-front', + }, + }, + top: { + file: 'https://asset-resources.gdevelop.io/public-resources/Tropical_Top.png', + resourceName: 'Tropical_Top.png', + }, + }, }, }, }); @@ -99,6 +111,7 @@ const makeProjectContent = () => ({ }, { name: 'Wall.png', file: 'assets/Wall.png', kind: 'image' }, { name: 'Sky_Front.png', file: 'assets/Sky_Front.png', kind: 'image' }, + { name: 'Sky_Top.png', file: 'assets/Sky_Top.png', kind: 'image' }, { name: 'Camera.png', file: 'assets/Camera.png', kind: 'image' }, ], }, @@ -112,7 +125,10 @@ const makeProjectContent = () => ({ { name: 'SkyBox', effectType: 'Scene3D::Skybox', - stringParameters: { frontFaceResourceName: 'Sky_Front.png' }, + stringParameters: { + frontFaceResourceName: 'Sky_Front.png', + topFaceResourceName: 'Sky_Top.png', + }, }, ], }, @@ -208,7 +224,7 @@ describe('applyThemeToStarter', () => { 'https://asset-resources.gdevelop.io/public-resources/Henry.glb', }, }); - expect(getResource(projectContent, 'Tropical.png').origin).toEqual({ + expect(getResource(projectContent, 'Tropical_Front.png').origin).toEqual({ name: 'gdevelop-asset-store', identifier: 'tropical-front', }); @@ -230,8 +246,12 @@ describe('applyThemeToStarter', () => { expect(ground.content.frontFaceResourceName).toBe('Sand.png'); expect( projectContent.layouts[0].layers[0].effects[0].stringParameters - .frontFaceResourceName - ).toBe('Tropical.png'); + ).toEqual({ + frontFaceResourceName: 'Tropical_Front.png', + topFaceResourceName: 'Tropical_Top.png', + }); + expect(getResource(projectContent, 'Sky_Front.png')).toBeUndefined(); + expect(getResource(projectContent, 'Sky_Top.png')).toBeUndefined(); }); it('removes the resources that were replaced', () => { @@ -265,7 +285,7 @@ describe('applyThemeToStarter', () => { it('numbers a theme resource named like another resource of the starter', () => { const projectContent = makeProjectContent(); - projectContent.resources.resources[5].name = 'Sand.png'; + projectContent.resources.resources[6].name = 'Sand.png'; projectContent.layouts[0].objects[4].content.frontFaceResourceName = 'Sand.png'; applyPirateTheme(projectContent); diff --git a/scripts/lib/__tests__/ThemeSlots.spec.js b/scripts/lib/__tests__/ThemeSlots.spec.js index aa87165f3..52cada748 100644 --- a/scripts/lib/__tests__/ThemeSlots.spec.js +++ b/scripts/lib/__tests__/ThemeSlots.spec.js @@ -14,7 +14,7 @@ const vocabulary = { { id: 'character.player', kind: 'model', label: 'Player character' }, { id: 'env.ground', kind: 'texture', label: 'Ground' }, { id: 'env.wall', kind: 'texture', label: 'Wall' }, - { id: 'sky.day.front', kind: 'texture', label: 'Day skybox, front' }, + { id: 'sky.day', kind: 'skybox', label: 'Daytime sky' }, ], }; @@ -58,7 +58,7 @@ const createFakeStarterThemeSlots = () => ({ 'object:TankConfiguration::CombinedTank/TankBase': 'character.player', }, effects: { - 'effect:Game Scene//SkyBox': { frontFaceResourceName: 'sky.day.front' }, + 'effect:Game Scene//SkyBox': 'sky.day', }, ignoredObjects: ['scene:Game Scene/Camera'], }); @@ -194,9 +194,7 @@ describe('checkStarterThemeSlots', () => { it('reports a skybox mapping to an effect the starter does not have', () => { const starterThemeSlots = createFakeStarterThemeSlots(); - starterThemeSlots.effects = { - 'effect:Game Scene//Gone': { frontFaceResourceName: 'sky.day.front' }, - }; + starterThemeSlots.effects = { 'effect:Game Scene//Gone': 'sky.day' }; const errors = checkStarterThemeSlots( vocabulary, @@ -208,4 +206,21 @@ describe('checkStarterThemeSlots', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toContain('"effect:Game Scene//Gone"'); }); + + it('requires a skybox to map to a skybox slot', () => { + const starterThemeSlots = createFakeStarterThemeSlots(); + starterThemeSlots.effects = { 'effect:Game Scene//SkyBox': 'env.wall' }; + + const errors = checkStarterThemeSlots( + vocabulary, + 'starting-3d-test', + createFakeProjectObject(), + starterThemeSlots + ); + + expect(errors).toHaveLength(1); + expect(errors[0].message).toContain( + 'a texture slot, but needs a skybox slot' + ); + }); }); diff --git a/theme-slots.json b/theme-slots.json index cba69eb29..cf70338d5 100644 --- a/theme-slots.json +++ b/theme-slots.json @@ -172,64 +172,14 @@ "label": "Farm plot or diggable ground" }, { - "id": "sky.day.front", - "kind": "texture", - "label": "Day skybox, front face" - }, - { - "id": "sky.day.back", - "kind": "texture", - "label": "Day skybox, back face" - }, - { - "id": "sky.day.left", - "kind": "texture", - "label": "Day skybox, left face" - }, - { - "id": "sky.day.right", - "kind": "texture", - "label": "Day skybox, right face" - }, - { - "id": "sky.day.top", - "kind": "texture", - "label": "Day skybox, top face" - }, - { - "id": "sky.day.bottom", - "kind": "texture", - "label": "Day skybox, bottom face" + "id": "sky.day", + "kind": "skybox", + "label": "Daytime sky" }, { - "id": "sky.dark.front", - "kind": "texture", - "label": "Dark skybox, front face" - }, - { - "id": "sky.dark.back", - "kind": "texture", - "label": "Dark skybox, back face" - }, - { - "id": "sky.dark.left", - "kind": "texture", - "label": "Dark skybox, left face" - }, - { - "id": "sky.dark.right", - "kind": "texture", - "label": "Dark skybox, right face" - }, - { - "id": "sky.dark.top", - "kind": "texture", - "label": "Dark skybox, top face" - }, - { - "id": "sky.dark.bottom", - "kind": "texture", - "label": "Dark skybox, bottom face" + "id": "sky.dark", + "kind": "skybox", + "label": "Night or gloomy sky" } ] } \ No newline at end of file