From 9edf3a6573e23910e79e2c990c2682b47fe66e8d Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 6 Apr 2026 11:11:36 +0900 Subject: [PATCH] better splash --- src/net/torvald/terrarum/App.java | 111 +--------------- .../torvald/terrarum/CommonResourcePool.kt | 11 -- src/net/torvald/terrarum/SplashScreen.kt | 119 +++++++++++++++--- 3 files changed, 111 insertions(+), 130 deletions(-) diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index cb84ed3ef..a719ef8f1 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -265,10 +265,10 @@ public class App implements ApplicationListener { public static Hq2x hq2x; public static Mesh fullscreenQuad; - private static OrthographicCamera camera; - private static FlippingSpriteBatch logoBatch; + public static OrthographicCamera camera; + public static FlippingSpriteBatch logoBatch; public static TextureRegion splashScreenLogo; - private static TextureRegion splashBackdrop; + public static TextureRegion splashBackdrop; public static AudioDevice audioDevice; public static FlippingSpriteBatch batch; @@ -550,7 +550,7 @@ public class App implements ApplicationListener { } } - private static Color splashTextCol = new Color(0x282828FF); + public static Color splashTextCol = new Color(0x282828FF); @Override public void create() { @@ -752,6 +752,7 @@ public class App implements ApplicationListener { // hand over the scene control to this single class; Terrarum must call // 'AppLoader.getINSTANCE().screen.render(delta)', this is not redundant at all! + SplashScreen.loadingComplete = true; IngameInstance title = ModMgr.INSTANCE.getTitleScreen(batch); if (title != null) { @@ -882,107 +883,7 @@ public class App implements ApplicationListener { } public static void drawSplash() { - setCameraPosition(0f, 0f); - - logoBatch.setColor(Color.WHITE); - logoBatch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA); - - - int drawWidth = Toolkit.INSTANCE.getDrawWidth(); - int safetyTextLen = fontGame.getWidth(Lang.INSTANCE.get("APP_WARNING_HEALTH_AND_SAFETY", true)); - int logoPosX0 = (drawWidth - splashScreenLogo.getRegionWidth() - safetyTextLen) >>> 1; - int logoPosY = Math.round(scr.getHeight() / 15f); - int textY = logoPosY + splashScreenLogo.getRegionHeight() - 16; - - // draw custom backdrop (if exists) - if (splashBackdrop != null) { - logoBatch.setShader(null); - logoBatch.begin(); - - - var size = ((float) Math.max(scr.getWidth(), scr.getHeight())); - var x = 0f; - var y = 0f; - if (scr.getWidth() > scr.getHeight()) { - y = (scr.getHeight() - size) / 2f; - } - else { - x = (scr.getWidth() - size) / 2f; - } - - logoBatch.draw(splashBackdrop, x, y, size, size); - - - logoBatch.end(); - } - - int logoPosX = logoPosX0;//(int)(logoPosX0 + Math.round(100 * Math.sin(GLOBAL_RENDER_TIMER / 50.0))); - - // draw logo reflection - logoBatch.setShader(shaderReflect); - logoBatch.setColor(Color.WHITE); - logoBatch.begin(); - if (getConfigBoolean("showhealthmessageonstartup")) { - logoBatch.draw(splashScreenLogo, logoPosX, logoPosY + splashScreenLogo.getRegionHeight()); - } - else { - logoBatch.draw(splashScreenLogo, (drawWidth - splashScreenLogo.getRegionWidth()) / 2f, - (scr.getHeight() - splashScreenLogo.getRegionHeight() * 2) / 2f + splashScreenLogo.getRegionHeight() - ); - } - logoBatch.end(); - logoBatch.setShader(null); - logoBatch.begin(); - if (getConfigBoolean("showhealthmessageonstartup")) { - logoBatch.draw(splashScreenLogo, logoPosX, logoPosY); - } - else { - logoBatch.draw(splashScreenLogo, (drawWidth - splashScreenLogo.getRegionWidth()) / 2f, - (scr.getHeight() - splashScreenLogo.getRegionHeight() * 2) / 2f - ); - } - - - - logoBatch.end(); - - // draw loading bar and subtitle SplashScreen.render(logoBatch, camera); - - // draw health messages - if (getConfigBoolean("showhealthmessageonstartup")) { - logoBatch.setShader(null); - logoBatch.begin(); - - logoBatch.setColor(splashTextCol); - fontGame.draw(logoBatch, Lang.INSTANCE.get("APP_WARNING_HEALTH_AND_SAFETY", true), - logoPosX + splashScreenLogo.getRegionWidth(), - textY - ); - - // some chinese stuff - if (GAME_LOCALE.contentEquals("zhCN")) { - for (int i = 1; i <= 4; i++) { - String s = Lang.INSTANCE.get("APP_CHINESE_HEALTHY_GAME_MSG_" + i, true); - - fontGame.draw(logoBatch, s, - (drawWidth - fontGame.getWidth(s)) >>> 1, - Math.round(scr.getHeight() * 12f / 15f + fontGame.getLineHeight() * (i - 1)) - ); - } - } - - Texture tex1 = CommonResourcePool.INSTANCE.getAsTexture("title_health1"); - Texture tex2 = CommonResourcePool.INSTANCE.getAsTexture("title_health2"); - int virtualHeight = scr.getHeight() - logoPosY - splashScreenLogo.getRegionHeight() / 4; - int virtualHeightOffset = scr.getHeight() - virtualHeight; - logoBatch.drawFlipped(tex1, (drawWidth - tex1.getWidth()) >>> 1, virtualHeightOffset + (virtualHeight >>> 1) - 16, tex1.getWidth(), -tex1.getHeight()); - logoBatch.drawFlipped(tex2, (drawWidth - tex2.getWidth()) >>> 1, virtualHeightOffset + (virtualHeight >>> 1) + 16 + tex2.getHeight(), tex2.getWidth(), -tex2.getHeight()); - - } - - logoBatch.end(); - batch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA); } /** @@ -1471,7 +1372,7 @@ public class App implements ApplicationListener { } - private static void setCameraPosition(float newX, float newY) { + public static void setCameraPosition(float newX, float newY) { camera.position.set((-newX + scr.getWidth() / 2), (-newY + scr.getHeight() / 2), 0f); // deliberate integer division camera.update(); logoBatch.setProjectionMatrix(camera.combined); diff --git a/src/net/torvald/terrarum/CommonResourcePool.kt b/src/net/torvald/terrarum/CommonResourcePool.kt index 3248ed7ee..d35a0a497 100644 --- a/src/net/torvald/terrarum/CommonResourcePool.kt +++ b/src/net/torvald/terrarum/CommonResourcePool.kt @@ -31,16 +31,6 @@ object CommonResourcePool { private val slowLoadingQueue = ConcurrentLinkedQueue() private val slowLoadingRemaining = AtomicInteger(0) - private val slowLoadingTotal = AtomicInteger(0) - - /** 0.0 = not started yet, 1.0 = all done. Only meaningful during / after [loadAllSlowly]. */ - val loadingProgress: Float - get() { - val total = slowLoadingTotal.get() - if (total == 0) return 0f - val remaining = slowLoadingRemaining.get() - return (total - remaining).toFloat() / total.toFloat() - } fun setGLThread(thread: Thread) { glThread = thread @@ -162,7 +152,6 @@ object CommonResourcePool { val desc = loadingList.removeFirst() slowLoadingQueue.add(desc) slowLoadingRemaining.incrementAndGet() - slowLoadingTotal.incrementAndGet() } // Block until the GL thread has processed all slow items while (slowLoadingRemaining.get() > 0) { diff --git a/src/net/torvald/terrarum/SplashScreen.kt b/src/net/torvald/terrarum/SplashScreen.kt index 23e750d1f..f6b425fc5 100644 --- a/src/net/torvald/terrarum/SplashScreen.kt +++ b/src/net/torvald/terrarum/SplashScreen.kt @@ -1,20 +1,32 @@ package net.torvald.terrarum import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.GL20 import com.badlogic.gdx.graphics.OrthographicCamera +import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarum.ui.UICanvas import net.torvald.terrarum.ui.UIItemHorzSlider +import kotlin.math.max /** - * Draws the loading bar and subtitle on the cold-boot splash screen. - * Called from App.drawSplash() (Java static context) on the GL thread. + * Draws the cold-boot splash screen: backdrop, logo, loading bar, subtitle, and health messages. + * Consolidated from App.drawSplash() so all splash rendering lives here. + * + * Progress is time-based: p = t / (t + HALF_TIME_MS), snapping to 1.0 when + * [loadingComplete] is set to true by App right before transitioning to the title screen. * * Created by minjaesong on 2026-04-06. */ object SplashScreen { + /** Set to true by App when loading completes, to snap the bar to 100%. */ + @JvmField @Volatile var loadingComplete = false + + private const val HALF_TIME_MS = 1500L // progress = t / (t + HALF_TIME_MS) + private val stubParent = object : UICanvas() { override var width = 0 override var height = 0 @@ -25,15 +37,74 @@ object SplashScreen { private var loadingBar: UIItemHorzSlider? = null private var lastBarWidth = 0 + private var startTime = 0L private val subtitleCol = Color(0.75f, 0.75f, 0.75f, 1f) - @JvmStatic - fun render(batch: SpriteBatch, camera: OrthographicCamera) { - val progress = CommonResourcePool.loadingProgress + @JvmStatic fun render(batch: SpriteBatch, camera: OrthographicCamera) { + App.setCameraPosition(0f, 0f) + + val batch = App.logoBatch + val scr = App.scr + val drawWidth = Toolkit.drawWidth + val showHealth = App.getConfigBoolean("showhealthmessageonstartup") + + batch.color = Color.WHITE + batch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA) + + val safetyTextLen = if (showHealth) App.fontGame.getWidth(Lang["APP_WARNING_HEALTH_AND_SAFETY", true]) else 0 + val logoPosX0 = (drawWidth - App.splashScreenLogo.regionWidth - safetyTextLen) ushr 1 + val logoPosY = Math.round(scr.height / 15f) + val textY = logoPosY + App.splashScreenLogo.regionHeight - 16 + + // backdrop + App.splashBackdrop?.let { backdrop -> + batch.setShader(null) + batch.inUse { + val size = max(scr.width, scr.height).toFloat() + val x = if (scr.width > scr.height) 0f else (scr.width - size) / 2f + val y = if (scr.width > scr.height) (scr.height - size) / 2f else 0f + batch.draw(backdrop, x, y, size, size) + } + } + + val logoPosX = logoPosX0 + + // logo reflection + batch.setShader(App.shaderReflect) + batch.color = Color.WHITE + batch.inUse { + if (showHealth) { + batch.draw(App.splashScreenLogo, logoPosX.toFloat(), (logoPosY + App.splashScreenLogo.regionHeight).toFloat()) + } else { + batch.draw(App.splashScreenLogo, + (drawWidth - App.splashScreenLogo.regionWidth) / 2f, + (scr.height - App.splashScreenLogo.regionHeight * 2) / 2f + App.splashScreenLogo.regionHeight) + } + } + + // logo + batch.setShader(null) + batch.inUse { + if (showHealth) { + batch.draw(App.splashScreenLogo, logoPosX.toFloat(), logoPosY.toFloat()) + } else { + batch.draw(App.splashScreenLogo, + (drawWidth - App.splashScreenLogo.regionWidth) / 2f, + (scr.height - App.splashScreenLogo.regionHeight * 2) / 2f) + } + } + + // loading bar+subtitle + if (startTime == 0L) startTime = System.currentTimeMillis() + + val elapsed = System.currentTimeMillis() - startTime + val progress = if (loadingComplete) 1f + else elapsed.toFloat() / (elapsed + HALF_TIME_MS).toFloat() + val barWidth = (Toolkit.drawWidth * 0.4f).toInt().coerceAtLeast(200) val barX = (Toolkit.drawWidth - barWidth) / 2 - val barY = App.scr.height - 48 + val barY = App.scr.height - App.scr.tvSafeGraphicsHeight - 24 if (loadingBar == null || lastBarWidth != barWidth) { lastBarWidth = barWidth @@ -48,16 +119,36 @@ object SplashScreen { bar.posY = barY bar.handleWidth = (progress * barWidth).toInt().coerceIn(12, barWidth) - val subtitle = "${App.GAME_NAME} ${App.getVERSION_STRING()}" + val subtitle = "Reticulating Splines..." val subtitleW = App.fontGame.getWidth(subtitle) val subtitleX = (Toolkit.drawWidth - subtitleW) / 2f - val subtitleY = (barY - 20).toFloat() // leave gap above the bar + val subtitleY = (barY - 32).toFloat() + + batch.inUse { + batch.color = subtitleCol + App.fontGame.draw(batch, subtitle, subtitleX, subtitleY) + batch.color = Color.WHITE + bar.render(0f, batch, camera) + } + + // health messages + if (showHealth) { + batch.setShader(null) + batch.inUse { + batch.color = App.splashTextCol + App.fontGame.draw(batch, Lang["APP_WARNING_HEALTH_AND_SAFETY", true], + (logoPosX + App.splashScreenLogo.regionWidth).toFloat(), textY.toFloat()) + + val tex1 = CommonResourcePool.getAsTexture("title_health1") + val tex2 = CommonResourcePool.getAsTexture("title_health2") + val virtualHeight = scr.height - logoPosY - App.splashScreenLogo.regionHeight / 4 + val virtualHeightOffset = scr.height - virtualHeight + batch.drawFlipped(tex1, ((drawWidth - tex1.width) ushr 1).toFloat(), (virtualHeightOffset + (virtualHeight ushr 1) - 16).toFloat(), tex1.width.toFloat(), (-tex1.height).toFloat()) + batch.drawFlipped(tex2, ((drawWidth - tex2.width) ushr 1).toFloat(), (virtualHeightOffset + (virtualHeight ushr 1) + 16 + tex2.height).toFloat(), tex2.width.toFloat(), (-tex2.height).toFloat()) + } + } + + App.batch.setBlendFunctionSeparate(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA) - batch.begin() - batch.color = subtitleCol - App.fontGame.draw(batch, subtitle, subtitleX, subtitleY) - batch.color = Color.WHITE - bar.render(0f, batch, camera) - batch.end() } }