diff --git a/Core/GDCore/Extensions/Builtin/SceneExtension.cpp b/Core/GDCore/Extensions/Builtin/SceneExtension.cpp index 27df8fa861ff..d90076de393b 100644 --- a/Core/GDCore/Extensions/Builtin/SceneExtension.cpp +++ b/Core/GDCore/Extensions/Builtin/SceneExtension.cpp @@ -171,8 +171,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( _("Preload a scene resources as soon as possible in background."), _("Preload scene _PARAM1_ in background"), "", - "res/actions/replaceScene24.png", - "res/actions/replaceScene.png") + "res/actions/hourglass_black.svg", + "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("sceneName", _("Name of the new scene")) @@ -184,7 +184,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( _("The progress of resources loading in background for a scene (between 0 and 1)."), _("_PARAM0_ loading progress"), _(""), - "res/actions/replaceScene24.png") + "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("sceneName", _("Scene name")) @@ -197,8 +197,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension( _("Check if scene resources have finished to load in background."), _("Scene _PARAM1_ was preloaded in background"), "", - "res/actions/replaceScene24.png", - "res/actions/replaceScene.png") + "res/actions/hourglass_black.svg", + "res/actions/hourglass_black.svg") .SetHelpPath("/all-features/resources-loading") .AddCodeOnlyParameter("currentScene", "") .AddParameter("sceneName", _("Scene name")) diff --git a/Core/GDCore/Project/LoadingScreen.cpp b/Core/GDCore/Project/LoadingScreen.cpp index 6e62cdc054e6..65ced60c15d4 100644 --- a/Core/GDCore/Project/LoadingScreen.cpp +++ b/Core/GDCore/Project/LoadingScreen.cpp @@ -17,7 +17,7 @@ LoadingScreen::LoadingScreen() backgroundFadeInDuration(0.2), minDuration(1.5), logoAndProgressFadeInDuration(0.2), - logoAndProgressLogoFadeInDelay(0.2), + logoAndProgressLogoFadeInDelay(0), showProgressBar(true), progressBarMinWidth(40), progressBarMaxWidth(200), diff --git a/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts b/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts index 10731fe6abbe..ec3849199f2a 100644 --- a/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts +++ b/GDJS/Runtime/howler-sound-manager/howler-sound-manager.ts @@ -820,7 +820,15 @@ namespace gdjs { 'There was an error while preloading an audio file: ' + error ); } - } else if (resource.preloadInCache) { + } else if ( + resource.preloadInCache || + // Force downloading of sounds. + // TODO Decide if sounds should be allowed to be downloaded after the scene starts. + // - they should be requested automatically at the end of the scene loading + // - they will be downloaded while the scene is playing + // - other scenes will be pre-loaded only when all the sounds for the current scene are in cache + !resource.preloadAsMusic + ) { // preloading as sound already does a XHR request, hence "else if" try { await new Promise((resolve, reject) => { diff --git a/GDJS/Runtime/pixi-renderers/loadingscreen-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/loadingscreen-pixi-renderer.ts index f04f26909a95..8f6c532f5a47 100644 --- a/GDJS/Runtime/pixi-renderers/loadingscreen-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/loadingscreen-pixi-renderer.ts @@ -6,7 +6,7 @@ namespace gdjs { } const fadeIn = ( - object: PIXI.DisplayObject | null, + object: { alpha: number } | null, duration: float, deltaTimeInMs: float ) => { @@ -54,14 +54,12 @@ namespace gdjs { return; } this._pixiRenderer.background.color = this._loadingScreenData.backgroundColor; + this._pixiRenderer.background.alpha = 0; const backgroundTexture = imageManager.getOrLoadPIXITexture( loadingScreenData.backgroundImageResourceName ); - if ( - backgroundTexture !== imageManager.getInvalidPIXITexture() && - isFirstScene - ) { + if (backgroundTexture !== imageManager.getInvalidPIXITexture()) { this._backgroundSprite = PIXI.Sprite.from(backgroundTexture); this._backgroundSprite.alpha = 0; this._backgroundSprite.anchor.x = 0.5; @@ -173,6 +171,15 @@ namespace gdjs { } else if (this._state == LoadingScreenState.STARTED) { const backgroundFadeInDuration = this._loadingScreenData .backgroundFadeInDuration; + + this._pixiRenderer.clear(); + if (!this._backgroundSprite) { + fadeIn( + this._pixiRenderer.background, + backgroundFadeInDuration, + deltaTimeInMs + ); + } fadeIn(this._backgroundSprite, backgroundFadeInDuration, deltaTimeInMs); if (hasFadedIn(this._backgroundSprite)) { @@ -245,6 +252,13 @@ namespace gdjs { ); this._progressBarGraphics.endFill(); } + } else if (this._state === LoadingScreenState.FINISHED) { + // Display a black screen to avoid a stretched image of the loading + // screen to appear. + this._pixiRenderer.background.color = 'black'; + this._pixiRenderer.background.alpha = 1; + this._pixiRenderer.clear(); + this._loadingScreenContainer.removeChildren(); } this._pixiRenderer.render(this._loadingScreenContainer); diff --git a/GDJS/Runtime/runtimegame.ts b/GDJS/Runtime/runtimegame.ts index 1014344faff4..beae9eb5c6b6 100644 --- a/GDJS/Runtime/runtimegame.ts +++ b/GDJS/Runtime/runtimegame.ts @@ -650,25 +650,36 @@ namespace gdjs { progressCallback?: (progress: float) => void ): Promise { try { - await this._loadAssetsWithLoadingScreen( - /* isFirstScene = */ true, - async (onProgress) => { - // TODO Is a setting needed? - if (false) { - await this._resourcesLoader.loadAllResources(onProgress); - } else { - await this._resourcesLoader.loadGlobalAndFirstSceneResources( - firstSceneName, - onProgress - ); - // Don't await as it must not block the first scene from starting. - this._resourcesLoader.loadAllSceneInBackground(); - } - }, - progressCallback - ); - // TODO This is probably not necessary in case of hot reload. - await gdjs.getAllAsynchronouslyLoadingLibraryPromise(); + // Download the loading screen background image first to be able to + // display the loading screen as soon as possible. + const backgroundImageResourceName = this._data.properties.loadingScreen + .backgroundImageResourceName; + if (backgroundImageResourceName) { + await this._resourcesLoader + .getImageManager() + .loadResource(backgroundImageResourceName); + } + await Promise.all([ + this._loadAssetsWithLoadingScreen( + /* isFirstScene = */ true, + async (onProgress) => { + // TODO Is a setting needed? + if (false) { + await this._resourcesLoader.loadAllResources(onProgress); + } else { + await this._resourcesLoader.loadGlobalAndFirstSceneResources( + firstSceneName, + onProgress + ); + // Don't await as it must not block the first scene from starting. + this._resourcesLoader.loadAllSceneInBackground(); + } + }, + progressCallback + ), + // TODO This is probably not necessary in case of hot reload. + gdjs.getAllAsynchronouslyLoadingLibraryPromise(), + ]); } catch (e) { if (this._debuggerClient) this._debuggerClient.onUncaughtException(e); diff --git a/newIDE/app/public/res/actions/hourglass_black.svg b/newIDE/app/public/res/actions/hourglass_black.svg new file mode 100644 index 000000000000..b79fe5bd45f9 --- /dev/null +++ b/newIDE/app/public/res/actions/hourglass_black.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/newIDE/app/src/Profile/Subscription/SubscriptionChecker.js b/newIDE/app/src/Profile/Subscription/SubscriptionChecker.js index 617b6777460c..10e1e1c0cfe7 100644 --- a/newIDE/app/src/Profile/Subscription/SubscriptionChecker.js +++ b/newIDE/app/src/Profile/Subscription/SubscriptionChecker.js @@ -19,6 +19,7 @@ import { isNativeMobileApp } from '../../Utils/Platform'; export type SubscriptionCheckerInterface = {| checkUserHasSubscription: () => boolean, + hasUserSubscription: () => boolean, |}; type Props = {| @@ -68,7 +69,14 @@ const SubscriptionChecker = React.forwardRef< return false; }; - React.useImperativeHandle(ref, () => ({ checkUserHasSubscription })); + const hasUserSubscription = () => { + return hasValidSubscriptionPlan(authenticatedUser.subscription); + }; + + React.useImperativeHandle(ref, () => ({ + checkUserHasSubscription, + hasUserSubscription, + })); return ( ({ + minDuration: loadingScreen.getMinDuration(), + logoAndProgressFadeInDuration: loadingScreen.getLogoAndProgressFadeInDuration(), + logoAndProgressLogoFadeInDelay: loadingScreen.getLogoAndProgressLogoFadeInDelay(), + }); + return ( {({ i18n }) => ( @@ -150,6 +169,42 @@ export const LoadingScreenEditor = ({ return; } watermark.showGDevelopWatermark(checked); + if (checked) { + loadingScreen.setMinDuration( + timeSettings.current.minDuration + ); + loadingScreen.setLogoAndProgressFadeInDuration( + timeSettings.current.logoAndProgressFadeInDuration + ); + loadingScreen.setLogoAndProgressLogoFadeInDelay( + timeSettings.current.logoAndProgressLogoFadeInDelay + ); + } else if ( + subscriptionChecker.current && + !subscriptionChecker.current.hasUserSubscription() + ) { + if ( + loadingScreen.getMinDuration() < forcedLogo.minDuration + ) { + loadingScreen.setMinDuration(forcedLogo.minDuration); + } + if ( + loadingScreen.getLogoAndProgressFadeInDuration() > + forcedLogo.logoAndProgressFadeInDuration + ) { + loadingScreen.setLogoAndProgressFadeInDuration( + forcedLogo.logoAndProgressFadeInDuration + ); + } + if ( + loadingScreen.getLogoAndProgressLogoFadeInDelay() > + forcedLogo.logoAndProgressLogoFadeInDelay + ) { + loadingScreen.setLogoAndProgressLogoFadeInDelay( + forcedLogo.logoAndProgressLogoFadeInDelay + ); + } + } onUpdate(); }} /> @@ -359,6 +414,41 @@ export const LoadingScreenEditor = ({ }} /> + + Duration + + Minimum duration of the screen (in seconds) + } + step={0.1} + fullWidth + type="number" + value={'' + loadingScreen.getMinDuration()} + onChange={newValue => { + const newMinDuration = Math.max(0, parseFloat(newValue)); + if ( + newMinDuration < forcedLogo.minDuration && + !watermark.isGDevelopWatermarkShown() && + subscriptionChecker.current && + !subscriptionChecker.current.checkUserHasSubscription() + ) { + // If users want to reduce GDevelop splash screen although + // watermark is hidden, we don't allow it if they have no subscription. + return; + } + const currentMinDuration = loadingScreen.getMinDuration(); + if (currentMinDuration === newMinDuration) { + return; + } + loadingScreen.setMinDuration(newMinDuration); + timeSettings.current.minDuration = newMinDuration; + onUpdate(); + }} + helperMarkdownText={i18n._( + t`When previewing the game in the editor, this duration is ignored (the game preview starts as soon as possible).` + )} + /> { - const currentLogoAndProgressLogoFadeInDelay = loadingScreen.getLogoAndProgressLogoFadeInDelay(); const newLogoAndProgressLogoFadeInDelay = Math.max( 0, parseFloat(newValue) ); + if ( + newLogoAndProgressLogoFadeInDelay > + forcedLogo.logoAndProgressLogoFadeInDelay && + !watermark.isGDevelopWatermarkShown() && + subscriptionChecker.current && + !subscriptionChecker.current.checkUserHasSubscription() + ) { + // If users want to reduce GDevelop splash screen although + // watermark is hidden, we don't allow it if they have no subscription. + return; + } + const currentLogoAndProgressLogoFadeInDelay = loadingScreen.getLogoAndProgressLogoFadeInDelay(); if ( currentLogoAndProgressLogoFadeInDelay === newLogoAndProgressLogoFadeInDelay @@ -386,6 +487,7 @@ export const LoadingScreenEditor = ({ loadingScreen.setLogoAndProgressLogoFadeInDelay( newLogoAndProgressLogoFadeInDelay ); + timeSettings.current.logoAndProgressLogoFadeInDelay = newLogoAndProgressLogoFadeInDelay; onUpdate(); }} /> @@ -402,11 +504,22 @@ export const LoadingScreenEditor = ({ type="number" value={'' + loadingScreen.getLogoAndProgressFadeInDuration()} onChange={newValue => { - const currentLogoAndProgressFadeInDuration = loadingScreen.getLogoAndProgressFadeInDuration(); const newLogoAndProgressFadeInDuration = Math.max( 0, parseFloat(newValue) ); + if ( + newLogoAndProgressFadeInDuration > + forcedLogo.logoAndProgressFadeInDuration && + !watermark.isGDevelopWatermarkShown() && + subscriptionChecker.current && + !subscriptionChecker.current.checkUserHasSubscription() + ) { + // If users want to reduce GDevelop splash screen although + // watermark is hidden, we don't allow it if they have no subscription. + return; + } + const currentLogoAndProgressFadeInDuration = loadingScreen.getLogoAndProgressFadeInDuration(); if ( currentLogoAndProgressFadeInDuration === newLogoAndProgressFadeInDuration @@ -415,6 +528,7 @@ export const LoadingScreenEditor = ({ loadingScreen.setLogoAndProgressFadeInDuration( newLogoAndProgressFadeInDuration ); + timeSettings.current.logoAndProgressFadeInDuration = newLogoAndProgressFadeInDuration; onUpdate(); }} /> @@ -427,30 +541,6 @@ export const LoadingScreenEditor = ({ ) : null} - - Duration - - Minimum duration of the screen (in seconds) - } - step={0.1} - fullWidth - type="number" - value={'' + loadingScreen.getMinDuration()} - onChange={newValue => { - const currentMinDuration = loadingScreen.getMinDuration(); - const newMinDuration = Math.max(0, parseFloat(newValue)); - if (currentMinDuration === newMinDuration) { - return; - } - loadingScreen.setMinDuration(newMinDuration); - onUpdate(); - }} - helperMarkdownText={i18n._( - t`When previewing the game in the editor, this duration is ignored (the game preview starts as soon as possible).` - )} - />