Skip to content

'Seamless' Singleplayer level transitions#2404

Open
DarthTealc wants to merge 2 commits intoFacepunch:masterfrom
DarthTealc:seamless-trigger_changelevel
Open

'Seamless' Singleplayer level transitions#2404
DarthTealc wants to merge 2 commits intoFacepunch:masterfrom
DarthTealc:seamless-trigger_changelevel

Conversation

@DarthTealc
Copy link
Contributor

@DarthTealc DarthTealc commented Dec 8, 2025

Suggestion

'Seamless' level transitions for singleplayer's Source Engine level transitions, showing the freeze frame just like in the original games.

Demos

trainstation.mp4
testchmb.mp4

HL2's d1_trainstation_02 to d1_trainstation_03 & Portal's testchmb_a_01 to testchmb_a_02

Required for functionality

The PR code would need these before merging.

  • The loading panel needs its :OnActivate() function to be given a parameter :OnActivate( transition ), which is true when the player's singleplayer game experiences a changelevel2/trigger_changelevel. This is similar to https://wiki.facepunch.com/gmod/game.MapLoadType
  • Menu-state needs a function engine.GetLastFrame(), which returns a material of the final frame rendered prior to loading, excluding the hud/vgui/etc.
How the demo gameplay works without that functionality
  • To work around :OnActivate( transition ) not currently being provided that flag - transition = true was added.
  • To work around engine.GetLastFrame() not currently existing - A specific texture is updated every PreDrawHUD clientside, which stops getting updated when rendering stops and is therefore frozen at the final frame, and in menustate a dummy GetLastFrame() returns a material of that texture.

@robotboy655 robotboy655 added Enhancement The pull request enhances current functionality. Requires engine changes The pull request requires changes to the internal source code and cannot yet be merged. labels Dec 8, 2025
@Xenthio
Copy link

Xenthio commented Dec 10, 2025

you can draw a level transition LOADING panel near identical to vanilla hl2 with

        local panelH = h * 0.0667
        local panelW = panelH * 2.625
        local panelX = (w - panelW) / 2
        local panelY = (h - panelH) / 2
        local cornerRadius = math.max( 8, math.floor( panelH * 0.15 ) )
        
        -- Panel background with rounded corners
        draw.RoundedBox( cornerRadius, panelX, panelY, panelW, panelH, Color( 0, 0, 0, 76 ) )
        
        -- Loading text
        surface.SetFont( "HudDefault" )
        local text = "LOADING"
        local tw, th = surface.GetTextSize( text )
        surface.SetTextColor( 255, 255, 255, 255 )
        surface.SetTextPos( panelX + (panelW - tw) / 2, panelY + (panelH - th) / 2 )
        surface.DrawText( text )

you can have localised text with the already existing #GameUI_LoadingGame

@DarthTealc
Copy link
Contributor Author

Thanks @Xenthio - added and confirmed it matches HL2's dimensions/position

This PR in Gmod @ 1920x1080
image

Half Life 2 anniversary edition @ 1920x1080
image

@Xenthio
Copy link

Xenthio commented Dec 11, 2025

Thanks @Xenthio - added and confirmed it matches HL2's dimensions/position

This PR in Gmod @ 1920x1080 image

Half Life 2 anniversary edition @ 1920x1080 image

the text should appear identical to pre anniversary edition half-life 2

@DarthTealc
Copy link
Contributor Author

DarthTealc commented Dec 12, 2025

Confirmed, the text matches pre-anniversary perfectly. via "steam_legacy" branch.

(Animated) alternating between HL2 and Gmod

Half Life 2 pre-Anniversary "steam_legacy" @ 1920x1080
HL2 screenshot

@Grocel
Copy link
Contributor

Grocel commented Dec 14, 2025

Maybe you might want to separate the loading bubble to avoid this PR becoming stuck. I am not sure if Rubat would like this given that loading bubble had been removed from GMod a long time ago. I like that bubble change, though.

@DarthTealc
Copy link
Contributor Author

The bubble commit can be reverted during merge without impacting the rest of the PR. Rubat can decide. If kept, it only shows on singleplayer's Source level transitions, doesn't show on any other loading type.

DarthTealc and others added 2 commits December 20, 2025 17:47
OnActivate() would receive a 'transition' boolean if the load is happening due to a Source Engine level transition (trigger_changelevel / changelevel2 etc), similar to in player_manager.OnPlayerSpawn. Multiplayer will always end up being false as Source Engine level transitions are only supported in singleplayer.

If OnActivate 'transition' is true, it sets the loading page to "about:blank" and draws the panel with the last rendered frame prior to load, provided by engine.GetLastFrame()

engine.GetLastFrame() would only exist in menu state, and would return a material of the last game frame that was rendered prior to load, excluding hud/vgui/etc.

The result of this is when a singleplayer game initiates a level transition, instead of seeing the default Gmod loading screen, they see a freeze frame of their game with the "loading" source panel in the bottom-right corner, similar to in HL2.
Thanks to Xenthio for HL2-style "LOADING" panel

Co-Authored-By: Xenthio <[email protected]>
@DarthTealc DarthTealc force-pushed the seamless-trigger_changelevel branch from 5991f06 to 24a7e80 Compare December 20, 2025 06:47
@DarthTealc

This comment was marked as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement The pull request enhances current functionality. Requires engine changes The pull request requires changes to the internal source code and cannot yet be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants