XNA 遊戲選單與畫面管理(Game State Management)

  • 7793
  • 0

一般遊戲都會提供選單給玩家做一些設定,像是設定遊戲難度、開關遊戲音樂、選定關卡等,在遊戲的過程中也會做暫停、回到主選單等遊戲畫面切換等動作,今天我們就來實作這些功能。這次的示範主要是利用微軟提供的範例為範本,然後加以修改成我們自己的需求。

一般遊戲都會提供選單給玩家做一些設定,像是設定遊戲難度、開關遊戲音樂、選定關卡等,在遊戲的過程中也會做暫停、回到主選單等遊戲畫面切換等動作,今天我們就來實作這些功能。這次的示範主要是利用微軟提供的範例為範本,然後加以修改成我們自己的需求。

 

我們新建一個專案,在專案中建立ScreenManager跟Screens兩個資料夾,在內容建立fonts跟images兩個資料夾。

image

 

下載以下檔案解壓縮並加入專案對應位置 (GsmFiles下載)

image

 

在專案裡新增兩個參考,Microsoft.Phone跟System.Windows

image

 

在這裡先大概說明一下幾個重要類別

ScreenManager資料夾下:

ScreenManager類別提供管理多個GameScreen物件(遊戲畫面)的功能,負責呼叫GameScreen物件的Update()、Draw()方法,以及將玩家輸入的動作傳到目前的GameScreen物件。

InputState類別負責處理玩家的觸控操作。

GameScreen類別是一個基類,每個遊戲畫面都是繼承這個類別,繼承GameScreen類別的衍生類別可以覆寫(override) LoadContent()、Unloadcontent、Update()、Draw()、HandleInput()等方法,HandleInput()方法是用來處理玩家的輸入操作。

Screens資料夾:

裡面是一些現成的遊戲畫面,大家可以直接使用或是自己寫一個繼承GameScreen類別的遊戲畫面。

Background.vb 遊戲背景畫面

LoadingScreen.vb 載入中的等待畫面

MainMenuScreen.vb 主選單畫面

MenuScreen.vb 選單的基類

PauseScreen.vb 暫停畫面

Button.vb 選單按鈕選項

 

OK,現在繼續我們的步驟

在Game1宣告變數,ScreenManager用來管理遊戲畫面,

Public Class Game1
    Inherits Microsoft.Xna.Framework.Game
 
    Private WithEvents graphics As GraphicsDeviceManager
    Private WithEvents spriteBatch As SpriteBatch
 
    Dim ScreenManager As ScreenManager
 
End Class

 

在New()加入了三個事件,當遊戲Lunching時會呼叫GameLunching(),Actived時會呼叫GameActived,Deactived時會呼叫GameDeactived(),這也是我們一開始加入那兩個參考的原因。

Public Sub New()
        graphics = New GraphicsDeviceManager(Me)
        Content.RootDirectory = "Content"
 
        'Frame rate is 30 fps by default for Windows Phone.
        TargetElapsedTime = TimeSpan.FromTicks(333333)
 
        graphics.IsFullScreen = True '全螢幕
 
        ' Create the screen manager component.
        ScreenManager = New ScreenManager(Me) '產生ScreenManager物件
        Components.Add(ScreenManager)         '將ScreenManager物件加入Components集合
 
        ' Hook events on the PhoneApplicationService so we're notified of the application's life cycle
        AddHandler Microsoft.Phone.Shell.PhoneApplicationService.Current.Launching, AddressOf GameLaunching
        AddHandler Microsoft.Phone.Shell.PhoneApplicationService.Current.Activated, AddressOf GameActivated
        AddHandler Microsoft.Phone.Shell.PhoneApplicationService.Current.Deactivated, AddressOf GameDeactivated
 
        'Extend battery life under lock.
         InactiveSleepTime = TimeSpan.FromSeconds(1)
End Sub

 

在Game1裡加入以下副程式,讓剛剛加入的事件可以呼叫,我們可以看出成是一執行ScreenManager加入了背景跟主選單畫面。

Sub AddInitialScreens()
        ' Activate the first screens.
        ScreenManager.AddScreen(New BackgroundScreen(), Nothing)
        screenManager.AddScreen(New MainMenuScreen(), Nothing)
 
End Sub
 
Sub GameLaunching(ByVal sender As Object, ByVal e As Microsoft.Phone.Shell.LaunchingEventArgs)
        AddInitialScreens()
End Sub
 
Sub GameActivated(ByVal sender As Object, ByVal e As Microsoft.Phone.Shell.ActivatedEventArgs)
        ' Try to deserialize the screen manager
        If Not screenManager.Activate(e.IsApplicationInstancePreserved) Then
            ' If the screen manager fails to deserialize, add the initial screens
            AddInitialScreens()
        End If
End Sub
 
Sub GameDeactivated(ByVal sender As Object, ByVal e As Microsoft.Phone.Shell.DeactivatedEventArgs)
        ' Serialize the screen manager when the game deactivated
        screenManager.Deactivate()
End Sub

 

在Draw()將背景改成黑色,隨個人喜好。

Protected Overrides Sub Draw(ByVal gameTime As GameTime)
        GraphicsDevice.Clear(Color.Black)
 
        ' TODO: Add your drawing code here
        MyBase.Draw(gameTime)
End Sub

 

將Initialize()、LoadContent()、Unloadcontent()、Update()都刪除,遊戲主程式碼都改在GameplayScreen,所以就不需要了。

接下來我們在GameplayScreen.vb寫一些程式碼測試一下。

 

在Gameplayer類別裡宣告兩個變數,一個文字資源、一個timeclock用來累加時間

Friend Class GameplayScreen
    Inherits GameScreen
 
    Private content As ContentManager
    Private pauseAlpha As Single
    Private pauseAction As InputAction
 
    Dim gameFont As SpriteFont    <-----------
    Dim timeclock As Double       <-----------
End Class

 

原本要在LoadContent()載入資源的,現在要改在這裡載入資源

 

Public Overrides Sub Activate(ByVal instancePreserved As Boolean)
        If Not instancePreserved Then
            If content Is Nothing Then
                content = New ContentManager(ScreenManager.Game.Services, "Content")
            End If
 
            gameFont = content.Load(Of SpriteFont)("fonts/gamefont")     <-----------------------
 
            ' A real game would probably have more content than this sample, so
            ' it would take longer to load. We simulate that by delaying for a
            ' while, giving you a chance to admire the beautiful loading screen.
            Thread.Sleep(1000)
 
            ' once the load has finished, we use ResetElapsedTime to tell the game's
            ' timing mechanism that we have just finished a very long frame, and that
            ' it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime()
        End If
End Sub

 

在Update()撰寫程式碼,累加timeclock時間

Public Overrides Sub Update(ByVal gameTime As GameTime, ByVal otherScreenHasFocus As Boolean, ByVal coveredByOtherScreen As Boolean)
        MyBase.Update(gameTime, otherScreenHasFocus, False)
 
        ' Gradually fade in or out depending on whether we are covered by the pause screen.
        If coveredByOtherScreen Then
            pauseAlpha = Math.Min(pauseAlpha + 1.0F / 32, 1)
        Else
            pauseAlpha = Math.Max(pauseAlpha - 1.0F / 32, 0)
        End If
 
        If IsActive Then
            ' TODO: this game isn't very fun! You could probably improve
            ' it by inserting something more interesting in this space :-)
            timeclock += gameTime.ElapsedGameTime.TotalSeconds      <------------------------
        End If
 End Sub

 

這裡是撰寫處理觸控操作程式碼的地方

Public Overrides Sub HandleInput(ByVal gameTime As GameTime, ByVal input As InputState)
        If input Is Nothing Then
            Throw New ArgumentNullException("input")
        End If
 
        ' Look up inputs for the active player profile.
        Dim playerIndex As Integer = CInt(ControllingPlayer.Value)
 
        Dim keyboardState As KeyboardState = input.CurrentKeyboardStates(playerIndex)
        Dim gamePadState As GamePadState = input.CurrentGamePadStates(playerIndex)
 
        ' The game pauses either if the user presses the pause button, or if
        ' they unplug the active gamepad. This requires us to keep track of
        ' whether a gamepad was ever plugged in, because we don't want to pause
        ' on PC if they are playing with a keyboard and have no gamepad at all!
        Dim gamePadDisconnected As Boolean = (Not gamePadState.IsConnected) AndAlso input.GamePadWasConnected(playerIndex)
 
        Dim player As PlayerIndex
        If pauseAction.Evaluate(input, ControllingPlayer, player) OrElse gamePadDisconnected Then
            ScreenManager.AddScreen(New PauseScreen, ControllingPlayer)
        Else
            ' Otherwise move the player position.
                                              <------------寫在這裡
        End If
 End Sub

 

在Draw()繪製出我們的文字

Public Overrides Sub Draw(ByVal gameTime As GameTime)
        ' This game has a blue background. Why? Because!
        ScreenManager.GraphicsDevice.Clear(ClearOptions.Target, Color.CornflowerBlue, 0, 0)
 
        ' Our player and enemy are both actually just text strings.
        Dim spriteBatch As SpriteBatch = ScreenManager.SpriteBatch
 
        spriteBatch.Begin()
 
        spriteBatch.DrawString(gameFont, timeclock.ToString, Vector2.Zero, Color.Red)
 
        spriteBatch.End()
 
        ' If the game is transitioning on or off, fade it out to black.
        If TransitionPosition > 0 OrElse pauseAlpha > 0 Then
            Dim alpha As Single = MathHelper.Lerp(1.0F - TransitionAlpha, 1.0F, pauseAlpha / 2)
 
            ScreenManager.FadeBackBufferToBlack(alpha)
        End If
 End Sub

 

程式執行結果如下 (範例下載)

微軟提供的範例非常好用,讓我們把心思可以放在遊戲開發上,而這些選單及轉場畫面可以直接修改套用,節省非常多的時間。

screenshot_11-8-2011_21.29.44.386