Scene management in Blitz Max

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Trust

Betreff: Scene management in Blitz Max

BeitragFr, Nov 03, 2017 11:23
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo Freunde,

hier mal ein kleines Beispiel wie man Szenen in BMax handeln kann.
Mit Szenen sind verschiedene Zustände im Spiel/App gemeint. Zustände können zB. sein: Titelscreen, Hauptmenü, Game Over Screen, Level 1, Level 2, ... und so weiter.

Dazu 2 Klassen, zum Einen "TComposer" welche das Szenenmanagement übernimmt und zum Anderen die Klasse TScene welche eine Szene repräsentiert.

Folgen die 2 Klassen:

Arrow "TComposer.bmx"
BlitzMax: [AUSKLAPPEN]
' Class for manageing scenes
' Required classes: TScene

Type TComposer

Global sceneList:TList = New TList
Field _currentScene:TScene
Field _lastScene:TScene


Method goToScene(sceneName:String)
For Local scene:TScene = EachIn sceneList
' If a scene with name "sceneName" exist set it as current scene to show
If scene.name = sceneName
_lastScene = _currentScene
_currentScene = scene
Exit
EndIf
Next
End Method

Method showCurrentScene()
If _currentScene <> Null
' If lastScene exist -> hide it before showing the new one
If _lastScene <> Null
_lastScene.hide()
EndIf
_currentScene.show()
Else
DrawText("Scene does not exist!", 10, 10)
DrawText("Scenes loaded: " + sceneList.count(), 10, 20)
EndIf
End Method

Method currentScene:String()
Return _currentScene.name
End Method

Method lastScene:String()
Return _currentScene.name
End Method

Method removeScene(sceneName:String)
For Local scene:TScene = EachIn sceneList
' If a scene with name "sceneName" exist then remove it from the list
If scene.name = sceneName
' make sure it's not the current shown scene
If _currentScene = scene
Print(scene.name + " is the current shown scene, so it cant be deleted")
Else
' remove the scene from the list
sceneList.Remove(scene)
' also make sure if it was the last shown scene, set _lastScene to Null
If _lastScene = scene
_lastScene = Null
EndIf
EndIf

Exit
EndIf
Next
End Method

Method removeLastScene()
For Local scene:TScene = EachIn sceneList
' If a scene _lastScene exist then remove it from the list
If scene = _lastScene
' make sure it's not the current shown scene
If _currentScene = scene
Print(scene.name + " is the current shown scene, so it can't be deleted")
Else
' remove the scene from the list
sceneList.Remove(scene)
' also make sure to set _lastScene to Null
If _lastScene = scene
_lastScene = Null
EndIf
EndIf

Exit
EndIf
Next
End Method
End Type



Arrow "TScene.bmx"
BlitzMax: [AUSKLAPPEN]
 ' Class for a scene
' Required classes: TComposer
'
' A scene can be the "title menue", the "game over" screen, different levels in a game etc.
' Each scene must inherit from this class in order to work with the composer class
' Each scene can be created in it's class file after the class definition, however, be aware of memory,
' as it loads all scenes into memory at once at the start of the game
' example:
' Type MainMenu extends TScene
' ...
' ...
' End Type
' MainMenu.Create()
'
' The composer holds the reference to each scene, so there is no need to do it manually

Type TScene
' name must be set and be unique
Field name:String

Method New()
' Register this scene, so the composer knows of it's existence
TComposer.sceneList.Addlast(Self)
End Method

' Code to create the scene goes here (eg. loading images/sounds etc.)
' This function must be called after the inheriting class definition (see example above)
Function Create() Abstract

' Code to show the scene goes here (eg. drawing images, playing sounds, game logic etc.)
Method show() Abstract

' When another scene is going to be shown, this method will automaticly be called by the composer, so this method should NOT be called manually
' Code to prepare the scene to be hidden goes here (eg. stop playing sounds of this scene etc.)
Method hide() Abstract
End Type


Möchte man nun neue Szenen erstellen, müssen diese immer von TScene abgeleitet werden.
Ebenfalls ist es wichtig, der neuen Szene einen Namen zu geben. Dies muss in der Create Funktion der neuen Szene über die TScene.name variable passieren.

Folgend 3 Beispiele neuer Szenen Klassen: titelscreen, mainmenu und level01

Arrow "titlescreen.bmx"
BlitzMax: [AUSKLAPPEN]
' Titlescreen

Type Titlescreen Extends TScene

' Code to create the scene goes here (eg. loading images/sounds etc.)
Function Create()
Local scene:Titlescreen = New Titlescreen
' name must be set and be unique, as the composer identifies each scene by it's name
' because a scene can be called by it's name through the composer.(see TComposer.goToScene)
scene.name = "titlescreen"
End Function

' Code to show the scene goes here (eg. drawing images, playing sounds, game logic etc.)
Method show()
DrawText("Title Screen", 200, 200)
End Method

' When another scene is going to be shown, this method will automaticly be called by the composer
' So code to prepare the scene to be hidden goes here (eg. stop playing sounds of this scene etc.)
Method hide()
End Method
End Type

' Call the create function to create an instance of this scene
' The composer holds the reference to each scene
' Don't call this in the Class file if memory is a problem,
' because calling it here will load all scenes at once into memory when the game starts
TitleScreen .Create()


Arrow "mainmenu.bmx"
BlitzMax: [AUSKLAPPEN]
' Main menue

Type MainMenu Extends TScene

' Code to create the scene goes here (eg. loading images/sounds etc.)
Function Create()
Local scene:MainMenu = New MainMenu
' name must be set and unique, as the composer identifies each scene by it's name
' because a scene can be called by it's name through the TComposer.(see TComposer.goToScene)
scene.name = "mainmenu"
End Function

' Code to show the scene goes here (eg. drawing images, playing sounds, game logic etc.)
Method show()
DrawText("Main Menue", 200, 200)
End Method

' When another scene is going to be shown, this method will automaticly be called by the composer
' So code to prepare the scene to be hidden goes here (eg. stop playing sounds of this scene etc.)
Method hide()
End Method
End Type

' Call the create function to create an instance of this scene
' The composer holds the reference to each scene
' Don't call this in the Class file if memory is a problem,
' because calling it here will load all scenes at once into memory when the game starts
MainMenu.Create()]


Arrow "level01.bmx"
BlitzMax: [AUSKLAPPEN]
' Level 01

Type Level01 Extends TScene

' Code to create the scene goes here (eg. loading images/sounds etc.)
Function Create()
Local scene:Level01 = New Level01
' name must be set and unique, as the composer identifies each scene by it's name
' because a scene can be called by it's name through the TComposer.(see TComposer.goToScene)
scene.name = "level01"
End Function

' Code to show the scene goes here (eg. drawing images, playing sounds, game logic etc.)
Method show()
DrawText("Level 01", 200, 200)
End Method

' When another scene is going to be shown, this method will automaticly be called by the composer
' So code to prepare the scene to be hidden goes here (eg. stop playing sounds of this scene etc.)
Method hide()
End Method
End Type

' Call the create function to create an instance of this scene
' The composer holds the reference to each scene
' Don't call this in the Class file if memory is a problem,
' because calling it here will load all scenes at once into memory when the game starts
Level01.Create()



Wie man in jedem Beispiel sehen kann, wird jeder Szene einen Namen gegeben:
BlitzMax: [AUSKLAPPEN]
Function Create()
Local scene:Level01 = New Level01
' Each scene must have a unique name
scene.name = "level01" '<---
End Function


Dies ist wie schon oben erwähnt wichtig, damit man diese Scene über den Composer aufrufen kann:
BlitzMax: [AUSKLAPPEN]
composer.goToScene("level01")


Desweiteren kann man sehen, dass in jeder Klassendatei gleich nach der Klassendefinition eine Instanz der Klasse erstellt wird. (dies gleich nach der Klassendefinition zu tun ist übersichtlich und bei kleinen Projekten deshalb anzuraten. Allerdings muss auf den Arbeitsspeicher geachtet werden, da alle Szenen auf einmal in den Arbeitsspeicher geladen werden - dazu mehr unter Tipps weiter unten):
BlitzMax: [AUSKLAPPEN]
' Call the create function to create an instance of this scene
' The composer holds the reference to each scene
' Don't call this in the Class file if memory is a problem,
' because calling it here will load all scenes at once into memory when the game starts
Level01.Create()


Folgend noch ein kleines Beispiel "main.bmx", welches alle oben genannten bmx-Dateien verwendet:

Arrow "main.bmx"
BlitzMax: [AUSKLAPPEN]
Strict

' Including the composer and scene class
Include "TComposer.bmx"
Include "TScene.bmx"

' Including scenes
Include "titlescreen.bmx"
Include "mainmenu.bmx"
Include "level01.bmx"

' Set the graphics mode
Graphics 1024, 768


' Create a new composer instance
Local composer:TComposer = New TComposer

' set the first scene
composer.goToScene("titlescreen")


While Not AppTerminate()

' determine what seen has to be shown
If KeyHit(KEY_1)
composer.goToScene("titlescreen")
ElseIf KeyHit(KEY_2)
composer.goToScene("mainmenu")
ElseIf KeyHit(KEY_3)
composer.goToScene("level01")
EndIf

' Remove a scene from memory
' "composer.removeLastScene()" is equivalent to the below statement, except it automatlically removes the last loaded scene
If KeyHit(KEY_SPACE)
composer.removeScene("level01")
EndIf

' show the scene
composer.showCurrentScene()

Flip();Cls()
Wend



Idea Tipps


Code-übersichtlichkeit:


Der Übersichthalber ist es zu empfehlen, der Szene den gleichen Namen wie der Klassendatei zu geben:
BlitzMax: [AUSKLAPPEN]
...
...
' Including scenes
Include "titlescreen.bmx"
...
...
' set the first scene
composer.goToScene("titlescreen")

Dies hat den Vorteil, dass man auf einen Blick erkennt, zu welcher Szene man dort wechselt.



Projekt-übersichtlichkeit:

In dem obigen Beispiel befinden sich alle Dateien im Rootverzeichnis des Projekts, so auch alle Szenen, das ist natürlich nicht sehr übersichtlich.
Empfehlenswert wäre hier, als Beispiel, einen Ordner namens "scenes" zu erstellen und dort für jede Szene einen eigenen Unterordner:
Code: [AUSKLAPPEN]
-MyGame
   main.bmx
   TComposer.bmx
   TScene.bmx
   -scenes
      -titlescreen
         titlescreen.bmx
      -mainmenu
         mainmenu.bmx
      -level01
         level01.bmx

Und in jedem Szenenordner auch alle Assets wie Bilder und Sounds zu dieser Szene speichern. Dies könnte man ebenfalls wieder in einem Unterordner zB. "assets" tun.

Jedenfalls müssen dann aber natürlich die Pfade angepasst werden:
BlitzMax: [AUSKLAPPEN]
...
...
' Including the composer and scene class
Include "TComposer.bmx"
Include "TScene.bmx"

' Including scenes
Include "scenes/titlescreen/titlescreen.bmx" '<--
Include "scenes/mainmenu/mainmenu.bmx" '<--
Include "scenes/level01/level01.bmx" '<--

' Set the graphics mode
Graphics 1024, 768
...
...




Arbeitsspeicher management:

Im obigen Beispiel wurde die Szene gleich nach der Klassendefinition erstellt.
Dies ist übersichtlich und kann man bei kleineren Projekten mit kleinen Szenen gut machen, allerdings wenn man größere Projekte mit tausenden Szenen hat, welche mehrere megabytes groß sind kann es eng mit dem Arbeitsspeicher werden, da alle Szenen beim Start des Spiels in den Arbeitsspeicher geladen werden.

Um dies bei größeren Projekten zu verhindern, kann die Szene nach Bedarf erstellt werden, jedoch muss man darauf achten, dass man diese Szene dann nicht vorher schon über den Composer aufruft, da diese Szene zu diesem Zeitpunkt noch nicht existiert.
Zudem kann man die Szene, insofern man diese wirklich nicht mehr braucht (Level abgeschlossen etc.) dann über den Composer aus dem Arbeitsspeicher entfernen. Hier ist dann auch wieder darauf zu achten, dass diese Szene dann nicht mehr aufgerufen wird.

Folgend ein Beispiel wie man Szenen dann nach Bedarf erstellt und ggf. wieder aus dem Arbeitsspeicher entfernt:

Als erstes muss der Aufruf der Create-Funktion aus der Szenenklassendatei entfernt werden:
BlitzMax: [AUSKLAPPEN]
' Call the create function to create an instance of this scene
' The composer holds the reference to each scene
' Don't call this in the Class file if memory is a problem,
' because calling it here will load all scenes at once into memory when the game starts
Level01.Create() '<--


Nun wird die Szene nicht mehr über die Klassendatei erstellt.


Nun erstelle die Szene dann nach Bedarf (in diesem Beispiel wird sie in der main.bmx erstellt, kann aber zu jeder Zeit erstellt werden. Wichtig ist nur, dass sie vorher nicht Aufgerufen wird mit "composer.goToScene()"
BlitzMax: [AUSKLAPPEN]
...
...
...

' Create a new composer instance
Local composer:TComposer = New TComposer

' manually create a scene on demand
Level01.Create() ' <-- this call must happen before calling the scene through the composer

' set the first scene
composer.goToScene("titlescreen")


While Not AppTerminate()

' determine what seen has to be shown
If KeyHit(KEY_1)
...
...
...



Jede Szene kann über einen der beiden Befehle "composer.removeScene("sceneName")" und "composer.removeLastScene()" aus dem Arbeitspeicher gelöscht werden.

Der Unterschied zwischen den Beiden ist, dass "composer.removeScene("sceneName")" die Szene mit angegebenen Namen ("sceneName") löscht, und "composer.removeLastScene()" die zuletzt geladene, aber nicht aktuelle Szene löscht.
Das heist, vom obigen Beispiel ausgehend:
Wenn Szene "titlescreen" als erstes aufgerufen wird und danach die Szene "mainmenu", wäre die zuletzt geladene, nicht aktuelle Szene, die Szene "titlescreen". Somit würde diese dann mit "composer.removeLastScene()" aus dem Arbeitsspeicher gelöscht werden.


Ich hoffe das es verständlich und hilfreich ist Smile

LG
Trust

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group