An example game engine

Advanced game related topics
coco2
Enthusiast
Enthusiast
Posts: 368
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

An example game engine

Post by coco2 »

Hi everyone I just wanted to share my work over the last week or so, I present the beginnings of a game engine. I've never seen it done before in PB so I thought I'd make my own to explore how it would work. I found a lot of limitations with regards to Ogre system so I am going to keep developing my own 3D subsystem if I ever get that far. It doesn't do much visually, but it has quite a few low level functions useful for most games. The keys can be found in the keyboard handler, but here is a list if you want to just run it and see what it does:

ESC / Ctrl+Shft+Q / Alt+F4 / Close = quit the game engine
Ctrl+Shft+R = restart :)
F3 = display FPS
F11 = switch to full screen or back to window
F12 = screen capture

Please make any suggestions for how I am using PB and especially variables etc

Updates:
2017-01-05: fixed up FPS calculation and removed keyboard and mouse detection variables
2017-01-06: lots of fixes and added menu controls
2017-01-09: going to make this my last update, added windowed full screen and sprites. Quake 3 engine is ~64,000 lines of code, so I'm not going to keep updating this. It's a good framework for getting the game started.
2017-01-10: some bug fixes
2017-12-09: minor update

Code: Select all

; Universal Game Engine

EnableExplicit

; Do compiler checks
CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
  CompilerCase #PB_OS_Linux
  CompilerCase #PB_OS_MacOS
  CompilerDefault
    CompilerError "OS not supported"
CompilerEndSelect

;- Enumerations

; Layer 1 - Operating System and universal controls

Enumeration Render_Engine3D
  #Render_Engine3D_None
  #Render_Engine3D_Builtin ; custom built 3D engine
EndEnumeration

Enumeration Game_Window
  #Game_Window_Main
  #Game_Window_Full_Screen_Minimised ; this is special window opened when classic full screen mode is switched back to the OS
EndEnumeration

Enumeration Full_Screen_Type
  #Full_Screen_Classic
  #Full_Screen_Windowed ; recommended, allows multiple monitor support
EndEnumeration

Enumeration Data_Source
  #Data_Source_None
  #Data_Source_Internal_Memory
  #Data_Source_File
  #Data_Source_Database
EndEnumeration

Enumeration Simulated_Resolution_Stretch_Type
  ; Used for simulating a low resolution game
  #Simulated_Resolution_Stretch_Smallest ; recommended
  #Simulated_Resolution_Stretch_H
  #Simulated_Resolution_Stretch_V
  #Simulated_Resolution_Stretch_Both
EndEnumeration

; Layer 2 - Menus and controls

Enumeration Control_Hardware
  #Control_Hardware_Keyboard
  #Control_Hardware_Mouse
EndEnumeration 

Enumeration Menu_System ; specifies how the menu will be navigated
  #Menu_System_Menuless ; uses buttons only to start and reset the game
  #Menu_System_Simple   ; like most console and some PC games (uses arrow keys/controller to navigate)
  #Menu_System_Pointer  ; like most PC games
EndEnumeration

Enumeration Menu_Action
  ; specifies different actions a menu will take
  #Menu_Action_None
  
  ; menuless
  #Menu_Action_Start  ; starts the game
  #Menu_Action_Select ; selects which mode of the game to play
  #Menu_Action_Reset  ; resets the game
  
  ; simple
  #Menu_Action_Confirm
  #Menu_Action_Back
  #Menu_Action_Up
  #Menu_Action_Down
  #Menu_Action_Left
  #Menu_Action_Right
  
  ; pointer based menu controls
  #Menu_Action_Click
EndEnumeration

Enumeration Menu_Background
  #Menu_Background_None ; displays a solid colour
  #Menu_Background_Vector
  #Menu_Background_Image
EndEnumeration


;- Globals
Global Restart.i=0 ; restarts the game engine

;- Constants
; System
#Max_Monitors_Supported = 3
#Max_Sprite_Resources = 256 ; total amount of individual sprites supported

; Menu
#Max_Menu_Controls = 12

; Game
#Max_Sprite_Instances = 2048 ; all sprites used by the game


;- Structures
; System
Structure Desktop_Structure ; structure to store parametres for each available display
  Name.s
  Width.i
  Height.i
  Depth.i
  Frequency.i
EndStructure

Structure Sprite_Resource_Structure
  ; Source collection of sprites
  Width.i
  Height.i
  Mode.i ; #PB_Sprite_PixelCollision and #PB_Sprite_AlphaBlending
  Transparent.i ; set if the sprite uses transparency
  Data_Source.i ; See Data_Source enumeration
  Memory_Location.i ; use one of these
  File_Location.s
  Database_Location.i
EndStructure

; Menu
Structure Menu_Control_Structure
  Menu_Control_Type.i ; specifies the type of the menu control system, see Enumeration Menu_System
  Menu_Control_Action.i ; this is the action the control will take, see Enumeration Menu_Control_Actions
  Menu_Control_Hardware_Type.i   ; see Enumeration Control_Hardware
  Menu_Control_ID.i              ; this is the ID of the actual control, for example a keyboard key
EndStructure

; Game
Structure Sprite_Instance_Structure
  Sprite_Resource.i
  X.i
  Y.i
  Intensity.i ; only works for transparent sprites
  Colour.i    ; only works for transparent sprites, not yet implemented
  Layer.i     ; used to sort the sprite instance array for drawing order, 0 is background
  Visible.i
EndStructure

;- Parametres

Structure Game_Parametres_Structure
  ;***************************************************
  ; System
  ;***************************************************
  
  Allow_AltF4_Full_Screen.i ; allows full screen to be quit using Alt+F4
  Allow_Restart.i           ; allows the game engine to be restarted
  Allow_Screen_Capture.i    ; allows a screenshot to be taken
  Allow_Switch_to_Window.i  ; to allow switching between window and full screen
  Allow_Window.i            ; prevents window mode completely
  Allow_Window_Resize.i     ; to allow the game window to be resized or maximised (doesn't apply to full screen mode)
  Background_Colour.i       ; background colour used when clearing the screen. This is set depending on what layer the system is running in
  Config_File.i             ; set to 1 if config file exists, used for when there's no config file
  Config_Loaded.i           ; set to 1 once the config is loaded. Cannot save until loaded  
  Current_Directory.s
  Desktop.Desktop_Structure[#Max_Monitors_Supported] ; Array to hold information about all monitors
  
  Fatal_Error_Message.s  ; when the game encounters a fatal error this is the error which will be displayed immediately and the program ended with an error code
  Flip_Mode.i            ; Video flip mode: 0 = no sync, 1 = sync, 2 = smart sync
  FPS.i                  ; current FPS
  FPS_Frame_Count.i      ; counts how many frames have been drawn within one second
  FPS_Last_Time.i        ; stores the last time in milliseconds that FPS will be calculated from
  FPS_Limit.i            ; maximum FPS for unsynced mode
  Full_Screen.i          ; 0 = windowed, 1 = full screen
  Full_Screen_Inactive.i ; set when the user switches from the full screen to the desktop
  Full_Screen_Type.i     ; see enumeration Full_Screen_Types, classic or windowed
  Game_Config_File.s     ; filename of the confg file
  Game_Database_Location.s ; location of the database
  Game_Loop_Start_Time.i   ; records the time in milliseconds when the main loop started
  Game_Resource_Location.s ; location of files
  Game_Title.s             ; Name of the game
  Initialisation_Error.i   ; will be set when there's an error. Helps track down the first error causing an issue
  Initialise_Error_Message.s ; special string for giving an initialisation error message. Only set this using SetInitialiseError()
  Initialised.i
  Last_Screen_Capture_File.s ; last file used by screen capture
  Last_Screen_Capture_Number.i ; used for capturing more than one frame per second  
  Main_Loop_Time.i             ; time in milliseconds for a game loop based on maximum FPS setting
  Min_Window_Width.i           ; minimum width you can set a window to
  Min_Window_Height.i
  Mouse_Button_Left.i ; gives the actual mouse button state, only works in ExamineMouse() mode
  Mouse_Button_Middle.i
  Mouse_Button_Right.i
  Mouse_Control.i     ; mouse is controlling the player
  Mouse_Left_Click.i  ; set when the mouse is clicked while in window mode
  Mouse_Right_Click.i
  Mouse_Offset_X.i ; offset of the mouse sprite displayed
  Mouse_Offset_Y.i ; usually 0 
  Mouse_Save_X.i   ; saves the position of the mouse when switching back to desktop (alt+tab)
  Mouse_Save_Y.i
  Mouse_Sensitivity_X.f
  Mouse_Sensitivity_Y.f
  Mouse_Sprite_Index.i ; index of the sprite to use for the mouse
  Mouse_Wheel_Movement.i ; movement of the mouse wheel since the last ExamineMouse()
  Mouse_X.f              ; location of the mouse pointer when it is over the window
  Mouse_Y.f
  MutexError.i ; 
  MutexID.i    ; used to check if more than one instance of the game is running
  Num_Monitors.i         ; number of monitors detected
  Quit.i                 ; flag to quit game 1 = quit. To restart the game use the global Restart variable
  Render_Engine3D.i      ; select which 3D rendering engine to use, select none for 2D only
  Reset_Window.i         ; triggers a move back to the main monitor, useful if a monitor is unplugged
  Screen_Active.i        ; set when the screen is active (including the windowed screen)
  Screen_Open.i          ; flag set when screen successfuly open (window or full screen)
  Screen_W.i             ; size of the full screen mode
  Screen_H.i
  Show_Debug_Info.i      ; when set this will show things like FPS etc on screen
  Show_Loading_Screen.i  ; will show the loading screen during LoadConfig
  Show_Mouse.i           ; shows the mouse
  Simulate_Res.i         ; zooms sprites to simulate a low res game
  Simulate_Res_W.i       ; width of simulated resolution
  Simulate_Res_H.i
  Simulate_Res_Stretch.i ; how to stretch the simulated resolution (see enumeration Simulated_Resolution_Stretch_Types)
  Sprite_List_Data_Source.i ; The source for the sprite resource list (see enumeration Data_Source)
  Sprite_Resource_Count.i   ; number of sprite resources loaded
  Sprite_Resource.Sprite_Resource_Structure[#Max_Sprite_Resources] ; hold all sprite resources (the actual sprite image data)
  
  Sprite_Zoom_X.i ; zoom factor for displaying sprites to the current screen
  Sprite_Zoom_Y.i
  Take_Screen_Capture.i ; flag to take a screen capture
  Total_Desktop_Width.i ; used for checking if the window is displayed off screen
  Window_Flags.i        ; holds flags for game window
  Window_Maximised.i    ; set when window is maximised 
  Window_Minimised.i    ; set when window is minimised
  Window_Moved.i        ; triggers whenever the window in normal mode moves
  Window_Open.i         ; flag is set when window successfully open
  Window_W.i            ; size of the window when in window mode
  Window_H.i
  Window_X.i    ; position of window
  Window_Y.i
  Window_Ratio.f ; ratio of Window_W and Window_H
  Window_Ratio_Enable.i ; set to enforce keeping the ratio when resizing the window
  
  
  ;***********************************************
  ; Menu
  ;***********************************************
  
  Menu_Action.i           ; which menu action to take
  Menu_Active.i           ; when true means that the menu system is active and has control
  Menu_Background.i       ; see enumeration Menu_Background
  Menu_Background_Colour.i; background colour for the menu
  Menu_Background_Data_Source.i   ; see enumeration Data_Source
  Menu_Control.Menu_Control_Structure[#Max_Menu_Controls]   ; array that holds menu controls
  
  Menu_Controls_Count.i ; total number of menu controls loaded
  Menu_System_Type.i    ; the type of menu system
  
  ;***********************************************
  ; Game
  ;***********************************************
  ; The game layer includes all the game mechanics as well as the display mechanism for the levels
  
  Game_Active.i ; game is active
  Sprite_Instance.Sprite_Instance_Structure[#Max_Sprite_Instances]
  Sprite_Instance_Count.i  
  
  
  
EndStructure

Procedure SetDefaults(*P.Game_Parametres_Structure)
  ; System
  *P\Allow_AltF4_Full_Screen = 0
  *P\Allow_Restart = 1
  *P\Allow_Screen_Capture = 1
  *P\Allow_Switch_to_Window = 1
  *P\Allow_Window_Resize = 1
  *P\Background_Colour = #Black ; this is the background for the system
  *P\Config_File = 0            ; assume no config file
  *P\Config_Loaded = 0          ; config not yet loaded
  *P\Current_Directory = GetCurrentDirectory()
  *P\Fatal_Error_Message = "none"
  *P\Flip_Mode = #PB_Screen_WaitSynchronization
  *P\FPS = 0
  *P\FPS_Frame_Count = 1
  *P\FPS_Last_Time = ElapsedMilliseconds() ; start the timer for calculating the FPS
  *P\FPS_Limit = 60                        ; used for unsynced mode
  *P\Full_Screen = 0
  *P\Full_Screen_Type = #Full_Screen_Windowed
  *P\Full_Screen_Inactive = 0
  *P\Game_Config_File = "settings.cfg"
  *P\Game_Database_Location = ""
  *P\Game_Resource_Location = "data"
  *P\Game_Title = "Universal Game Engine"
  *P\Initialisation_Error = 0
  *P\Initialise_Error_Message = "none"
  *P\Initialised = 0
  *P\Last_Screen_Capture_File = ""
  *P\Last_Screen_Capture_Number = 0 ; reset the screen capture counter for more than one per second
  *P\Main_Loop_Time = 1000 / *P\FPS_Limit ; the time it should take for the main loop to complete
  *P\Min_Window_Width = 320  
  *P\Min_Window_Height = 200
  *P\Mouse_Control = 0 ; set when the mouse is controlling the player
  *P\Mouse_Offset_X = 0
  *P\Mouse_Offset_Y = 0
  *P\Mouse_Sprite_Index = 1
  *P\Mouse_Sensitivity_X = 1
  *P\Mouse_Sensitivity_Y = 1
  *P\Mouse_Wheel_Movement = 0
  *P\Num_Monitors = 0
  *P\Quit = 0
  *P\Render_Engine3D = #Render_Engine3D_Builtin
  *P\Reset_Window = 0
  *P\Screen_Active = 0
  *P\Screen_Open = 0
  *P\Show_Debug_Info = 0
  *P\Show_Loading_Screen = 1
  *P\Show_Mouse = 1
  *P\Simulate_Res = 1 ; simulate a low resolution game
  *P\Simulate_Res_W = 320
  *P\Simulate_Res_H = 200
  *P\Simulate_Res_Stretch = #Simulated_Resolution_Stretch_Both
  *P\Sprite_List_Data_Source = #Data_Source_Internal_Memory
  *P\Sprite_Resource_Count = 0
  *P\Take_Screen_Capture = 0
  *P\Total_Desktop_Width = 0
  *P\Window_Minimised = 0
  *P\Window_Moved = 0
  *P\Window_W = 640
  *P\Window_H = 400
  *P\Window_Ratio = *P\Window_H / *P\Window_W
  *P\Window_Ratio_Enable = 1
  
  
  ; Menu
  *P\Menu_Active = 0 ; game starts in system mode, when initialise finishes the menu becomes active
  *P\Menu_Action = #Menu_Action_None ; make sure nothing has been pressed for the menu controls
  *P\Menu_Background = #Menu_Background_None
  *P\Menu_Background_Colour = #Black ; this will be changed when the menu is loaded
  *P\Menu_Background_Data_Source = #Data_Source_Internal_Memory
  *P\Menu_System_Type = #Menu_System_Menuless
  
EndProcedure

;- Error Handler

Procedure Fatal_Error(*P.Game_Parametres_Structure)
  Debug "FATAL ERROR: " + *P\Fatal_Error_Message
  CloseScreen() ; drop back to desktop
  MessageRequester (*P\Game_Title + " Fatal Error", "FATAL ERROR!" + #CRLF$ + *P\Fatal_Error_Message, #PB_MessageRequester_Error)
  End 1 ; 1 means there was an error
EndProcedure

;- Graphics

Procedure InitDesktop(*P.Game_Parametres_Structure)
  ; used to check which monitors are connected and how to display the game by default
  Protected c.i, t.i
  *P\Num_Monitors = ExamineDesktops()
  Debug "InitDesktop: " + *P\Num_Monitors + " monitors detected"
  If *P\Num_Monitors > 0
    t = *P\Num_Monitors
    If t > #Max_Monitors_Supported : t = #Max_Monitors_Supported : EndIf
    *P\Total_Desktop_Width = 0
    For c = 0 To *P\Num_Monitors - 1
      *P\Desktop[c]\Name = DesktopName(c)
      *P\Desktop[c]\Width = DesktopWidth(c)
      *P\Desktop[c]\Height = DesktopHeight(c)
      *P\Desktop[c]\Depth = DesktopDepth(c)
      *P\Desktop[c]\Frequency = DesktopFrequency(c)
      *P\Total_Desktop_Width = *P\Total_Desktop_Width + *P\Desktop[c]\Width
    Next
    ; Full screen mode is always on monitor 0, this is a limitation of PureBasic
    *P\Screen_W = DesktopWidth(0)
    *P\Screen_H = DesktopHeight(0) 
  Else
    Debug "InitDesktop: could not initialise desktop"
    ProcedureReturn 0
  EndIf
  ProcedureReturn 1
EndProcedure

Procedure SetWindowFlags(*P.Game_Parametres_Structure)
  ; used to set the flags for how the windowed application will be displayed
  If *P\Full_Screen
    ; Set window flags for windowed full screen
    *P\Window_Flags = #PB_Window_Maximize | #PB_Window_BorderLess
  Else
    *P\Window_Flags = #PB_Window_MinimizeGadget
    If *P\Allow_Window_Resize
      *P\Window_Flags = *P\Window_Flags | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget
    EndIf
    If *P\Reset_Window ; windows has been reset back to main display, centre it
      *P\Window_Flags = *P\Window_Flags | #PB_Window_ScreenCentered
    EndIf  
    If *P\Window_Maximised
      *P\Window_Flags = *P\Window_Flags | #PB_Window_Maximize
    EndIf
  EndIf
EndProcedure

Procedure GetScreenWidth(*P.Game_Parametres_Structure)
  ; Returns the screen width whether it's a window or full screen
  If *P\Full_Screen
    ProcedureReturn *P\Screen_W
  Else
    ProcedureReturn WindowWidth(#Game_Window_Main)
  EndIf
EndProcedure

Procedure GetScreenHeight(*P.Game_Parametres_Structure)
  If *P\Full_Screen
    ProcedureReturn *P\Screen_H
  Else
    ProcedureReturn WindowHeight(#Game_Window_Main)
  EndIf
EndProcedure

Procedure LoadSpriteResources(*P.Game_Parametres_Structure)
  ; Can be called anytime as it handles freeing all existing sprites
  Protected c.i, f.s
  Protected x.i, y.i, col.l
  Protected r.i ; this is used as a variable for reading data to be discarded
  Debug "LoadSprites: loading sprite resource list"
  Select *P\Sprite_List_Data_Source
    Case #Data_Source_None
      *P\Sprite_Resource_Count = 0
      Debug "LoadSprites: no sprite resources to load"
    Case #Data_Source_Internal_Memory
      Restore Sprite_Resource_Data
      Read *P\Sprite_Resource_Count
      If *P\Sprite_Resource_Count > #Max_Sprite_Resources
        *P\Fatal_Error_Message = "#Max_Sprite_Resources too small to load all sprite resources"
        Fatal_Error(*P)       
      EndIf
      ; Read complete sprite resource list first
      ; That way sprites can be loaded from the DataSection  
      For c = 0 To *P\Sprite_Resource_Count - 1
        Read.i *P\Sprite_Resource[c]\Width
        Read.i *P\Sprite_Resource[c]\Height
        Read.i *P\Sprite_Resource[c]\Mode
        Read.i *P\Sprite_Resource[c]\Transparent
        Read.i *P\Sprite_Resource[c]\Data_Source
        Select *P\Sprite_Resource[c]\Data_Source
            ; Have to specify which variable we read the next data in to
          Case #Data_Source_None
            Read.i r
          Case #Data_Source_Internal_Memory
            Read.i *P\Sprite_Resource[c]\Memory_Location
          Case #Data_Source_File
            Read.s *P\Sprite_Resource[c]\File_Location
          Case #Data_Source_Database   
            Read.i *P\Sprite_Resource[c]\Database_Location
        EndSelect
      Next
    Case #Data_Source_File
    Case #Data_Source_Database
  EndSelect
  ; Now that the resource list has been stored, load the sprites.
  ; This is necessary so that sprites can be stored in the DataSection
  If *P\Sprite_Resource_Count > 0
    Debug "LoadSprites: loading sprites"
    Restore Sprite_Data
    For c = 0 To *P\Sprite_Resource_Count - 1
      Select *P\Sprite_Resource[c]\Data_Source
        Case #Data_Source_None
          ; Nothing to do, empty sprite            
        Case #Data_Source_Internal_Memory
          If Not CreateSprite(c, *P\Sprite_Resource[c]\Width, *P\Sprite_Resource[c]\Height, *P\Sprite_Resource[c]\Mode)
            *P\Fatal_Error_Message = "Unable to create sprite " + c
            Fatal_Error(*P) 
          EndIf
          StartDrawing(SpriteOutput(c))
          DrawingMode(#PB_2DDrawing_AllChannels)
          For y = 0 To *P\Sprite_Resource[c]\Height - 1
            For x = 0 To *P\Sprite_Resource[c]\Width - 1
              Read.l col
              Plot(x,y,col)
            Next
          Next
          StopDrawing()
        Case #Data_Source_File
          f = *P\Game_Resource_Location + "\" + *P\Sprite_Resource[c]\File_Location
          If Not LoadSprite(c, f, *P\Sprite_Resource[c]\Mode)
            *P\Fatal_Error_Message = "Unable to load sprite " + f
            Fatal_Error(*P)
          EndIf
        Case #Data_Source_Database
          *P\Fatal_Error_Message = "Loading sprites from a database not yet supported"
          Fatal_Error(*P)              
      EndSelect
    Next
  EndIf
  Debug "LoadSprites: " + *P\Sprite_Resource_Count + " sprite resource(s) loaded"
EndProcedure

Procedure SetWindowScreen(*P.Game_Parametres_Structure)
  ; Sets the window screen closing the old one if necessary
  If *P\Screen_Open
    CloseScreen()
    *P\Screen_Open = 0
  EndIf
  If OpenWindowedScreen(WindowID(#Game_Window_Main), 0, 0, WindowWidth(#Game_Window_Main), WindowHeight(#Game_Window_Main), #False, 0, 0, *P\Flip_Mode)
    *P\Screen_Open = 1
    *P\Screen_Active = 1
    Debug "SetWindowScreen: set new window screen dimensions: " + WindowWidth(#Game_Window_Main) + " x " + WindowHeight(#Game_Window_Main)
  Else
    Debug "SetWindowScreen: could not initialise windowed screen"
    ProcedureReturn 0
  EndIf 
  ProcedureReturn 1
EndProcedure

Procedure SetScreen(*P.Game_Parametres_Structure)
  ; Sets (or resets) the screen. In fullscreen mode it opens a fullscreen.
  ; In window mode it opens a window then opens a window screen inside it.
  ; When used mid game-loop check if it failed and generate a fatal error.
  Protected Result.i
  If *P\Screen_Open
    Debug "SetScreen: closing screen"
    CloseScreen()
    *P\Screen_Open = 0
  EndIf
  If *P\Window_Open
    ; Only used if switching to full screen from a window
    Debug "SetScreen: closing window"
    CloseWindow(0)
    *P\Window_Open = 0
  EndIf
  If *P\Full_Screen And *P\Full_Screen_Type = #Full_Screen_Classic
    Debug "SetScreen: opening full screen"
    If OpenScreen(*P\Screen_W, *P\Screen_H, 32, *P\Game_Title, *P\Flip_Mode)
      *P\Screen_Open = 1
      *P\Screen_Active = 1
    Else
      Debug "SetScreen: could not intialise full screen"
      ProcedureReturn 0
    EndIf 
  Else
    ; check the window is visible, useful if you unplug a monitor
    If *P\Window_X > *P\Total_Desktop_Width And Not *P\Full_Screen
      ; Only reset the window if it's not in windowed full screen mode
      Debug "SetScreen: window is not visible"
      *P\Reset_Window = 1
      *P\Window_X = 0
      *P\Window_Y = 0
      *P\Window_Maximised = 0 ; need to demaximise window so it can get new coordinates
    EndIf
    SetWindowFlags(*P)
    Debug "SetScreen: opening window"
    If OpenWindow(#Game_Window_Main, *P\Window_X, *P\Window_Y, *P\Window_W, *P\Window_H, *P\Game_Title, *P\Window_Flags)
      *P\Window_Open = 1
      WindowBounds(#Game_Window_Main, *P\Min_Window_Width, *P\Min_Window_Height, #PB_Default, #PB_Default)
      ; Sets the limit of how small a window can be resized
      If Not *P\Config_File Or *P\Reset_Window ; update window coordinates because there was no config file
        *P\Window_X = WindowX(#Game_Window_Main)
        *P\Window_Y = WindowY(#Game_Window_Main)
        *P\Window_W = WindowWidth(#Game_Window_Main)
        *P\Window_H = WindowHeight(#Game_Window_Main)
        *P\Reset_Window = 0
      EndIf      
    Else
      Debug "SetScreen: could not initialise window"
      ProcedureReturn 0
    EndIf
    Debug "SetScreen: opening window screen"
    If OpenWindowedScreen(WindowID(#Game_Window_Main), 0, 0, WindowWidth(#Game_Window_Main), WindowHeight(#Game_Window_Main), #False, 0, 0, *P\Flip_Mode)
      *P\Screen_Open = 1
      *P\Screen_Active = 1
    Else
      Debug "SetScreen: could not initialise windowed screen"
      ProcedureReturn 0
    EndIf
  EndIf
  ProcedureReturn 1
EndProcedure 

Procedure SwitchFullScreen(*P.Game_Parametres_Structure)
  ; Handles reloading of resources
  *P\Full_Screen = 1 - *P\Full_Screen ; toggle full screen
  If Not SetScreen(*P)
    *P\Fatal_Error_Message = "SetScreen failed"
    Fatal_Error(*P)
  EndIf 
  LoadSpriteResources(*P) ; need to reload sprites anytime SetScreen is called
EndProcedure

Procedure SaveScreen(*P.Game_Parametres_Structure)
  Protected s.i, d.s, n.s, f.s
  s = GrabSprite(#PB_Any, 0, 0, ScreenWidth(), ScreenHeight())
  d = FormatDate("%yyyy%mm%dd-%hh%ii%ss", Date())
  n = ""
  If *P\Last_Screen_Capture_File = d
    ; add counter to end of date
    *P\Last_Screen_Capture_Number = *P\Last_Screen_Capture_Number + 1
    n = "-" + Str(*P\Last_Screen_Capture_Number)
  Else
    ; reset the counter because the time has incremented to the next second
    *P\Last_Screen_Capture_Number = 0
  EndIf
  f = *P\Current_Directory + "screen_capture_" + d + n + ".png"
  *P\Last_Screen_Capture_File = d
  If SaveSprite(s, f, #PB_ImagePlugin_PNG)
    Debug "SaveScreen: saved screen_capture_" + d + n + ".png"
  Else
    Debug "SaveScreen: failed"
  EndIf
  FreeSprite(s)
EndProcedure

Procedure DoClearScreen(*P.Game_Parametres_Structure)
  If Not *P\Full_Screen_Inactive
    ClearScreen(*P\Background_Colour)
  EndIf  
EndProcedure

Procedure DoFlipBuffer(*P.Game_Parametres_Structure)
  If Not *P\Full_Screen_Inactive
    ; don't try and flip an inactive full screen
    FlipBuffers()
  EndIf
EndProcedure

Procedure Draw3DWorld(*P.Game_Parametres_Structure)
  If *P\Render_Engine3D <> #Render_Engine3D_None
    Select *P\Render_Engine3D
      Case #Render_Engine3D_Builtin
        ; not written yet
    EndSelect
  EndIf
EndProcedure

Procedure DisplaySpriteInstance(*P.Game_Parametres_Structure, i.i)
  ; Sprite instances are all the copies of sprites displayed in a level
  ; The only limit to how big a level can be is available memory
  If *P\Sprite_Resource[*P\Sprite_Instance[i]\Sprite_Resource]\Transparent
    DisplayTransparentSprite(*P\Sprite_Instance[i]\Sprite_Resource, *P\Sprite_Instance[i]\X, *P\Sprite_Instance[i]\Y, *P\Sprite_Instance[i]\Intensity)
  Else
    DisplaySprite(*P\Sprite_Instance[i]\Sprite_Resource, *P\Sprite_Instance[i]\X, *P\Sprite_Instance[i]\Y)
  EndIf
EndProcedure

Procedure DisplaySpriteResource(*P.Game_Parametres_Structure, s.i, x.i, y.i, Intensity.i=255)
  ; Used for manually displaying a sprite
  If *P\Sprite_Resource[s]\Transparent
    DisplayTransparentSprite(s, x, y, Intensity)
  Else
    DisplaySprite(s, x, y)
  EndIf 
EndProcedure

Procedure DrawDebugInfo(*P.Game_Parametres_Structure)
  Protected f.s
  If *P\Show_Debug_Info
    f = "FPS: " + Str(*P\FPS)
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(0, 0, f)
  EndIf
EndProcedure

Procedure Draw2DGraphics(*P.Game_Parametres_Structure)
  Protected c.i
  If Not *P\Full_Screen_Inactive ; don't draw anything if full screen has been alt+tabbed
    
    
    ; *************************************
    ; menu
    ; *************************************   
    
    StartDrawing(ScreenOutput())
    
    If *P\Menu_Active
      ; Only draw background and menus when active
      ; Draw menu background
      Select *P\Menu_Background
        Case #Menu_Background_None
          ; nothing to do
        Case #Menu_Background_Vector
        Case #Menu_Background_Image
      EndSelect
    EndIf
    
    StopDrawing()
    
    ; *************************************
    ; system
    ; *************************************  
    
    
    
    
  EndIf
EndProcedure


Procedure DrawSprites(*P.Game_Parametres_Structure)
  
  If *P\Sprite_Resource_Count > 0
    ; Display sprites
    StartDrawing(ScreenOutput())
    
    StopDrawing()
  EndIf
EndProcedure

Procedure DrawMouse(*P.Game_Parametres_Structure)
  If *P\Show_Mouse
    If *P\Mouse_Control
      ; dont show mouse when it's in control (ie first person mouse control)
    Else
      If *P\Full_Screen
        ; show the mouse sprite
        ; no need to show the mouse sprite in window mode when it's not in control of the player
        DisplaySpriteResource(*P, *P\Mouse_Sprite_Index, *P\Mouse_X - *P\Mouse_Offset_X, *P\Mouse_Y - *P\Mouse_Offset_Y)
      EndIf
    EndIf
  EndIf
EndProcedure

Procedure DoPostProcessing(*P.Game_Parametres_Structure)
  If *P\Take_Screen_Capture
    SaveScreen(*P)
    *P\Take_Screen_Capture = 0
  EndIf
EndProcedure

Procedure CheckFullScreen(*P.Game_Parametres_Structure)
  ; Manages the full screen in case the user switches back
  ; to the operating system (ie using alt+tab)
  Protected e.i
  If *P\Full_Screen_Type = #Full_Screen_Classic
    If *P\Full_Screen_Inactive
      ; process window events for the minimised window
      e = WaitWindowEvent(10)
      If e = #PB_Event_ActivateWindow
        ; User has switched back to full screen
        Debug "CheckFullScreen: returned to full screen"
        CloseWindow(#Game_Window_Full_Screen_Minimised)
        ; this closes the dummy minimised window that gets
        ; created to hide the full screen
        If Not SetScreen(*P)
          *P\Fatal_Error_Message = "SetScreen failed"
          Fatal_Error(*P)
        EndIf
        MouseLocate(*P\Mouse_Save_X, *P\Mouse_Save_Y) ; move the mouse back to the saved location
        LoadSpriteResources(*P)
        *P\Full_Screen_Inactive = 0
      EndIf      
    Else
      ; Check if the screen has gone inactive
      *P\Screen_Active = IsScreenActive()
      If Not *P\Screen_Active And *P\Full_Screen And *P\Full_Screen_Type = #Full_Screen_Classic
        ; only process IsScreenActive if in full screen
        *P\Mouse_Save_X = *P\Mouse_X ; Save the mouse position
        *P\Mouse_Save_Y = *P\Mouse_Y
        ReleaseMouse(1) ; release the mouse to the OS
        CloseScreen()
        *P\Screen_Open = 0
        *P\Full_Screen_Inactive = 1
        OpenWindow(#Game_Window_Full_Screen_Minimised, 1, 1, 1, 1, *P\Game_Title, #PB_Window_Minimize)
        Debug "CheckFullScreen: screen inactive, waiting for user to switch back"
      EndIf
    EndIf
  EndIf
EndProcedure

;- Input


Procedure ProcessKeyboard(*P.Game_Parametres_Structure)
  Protected c.i
  If Not *P\Full_Screen_Inactive
    ; Disable keyboard when classic full screen inactive
    ExamineKeyboard()
    
    ; Always process CTRL, SHIFT and ALT pushed commands first
    ; Process alt commands
    If KeyboardPushed(#PB_Key_LeftAlt) Or KeyboardPushed(#PB_Key_RightAlt)
      If KeyboardReleased(#PB_Key_Return)
        SwitchFullScreen(*P)         
      EndIf
    EndIf
    ; Process control commands
    If KeyboardPushed(#PB_Key_LeftControl) Or KeyboardPushed(#PB_Key_RightControl)
      ; Process control+shift commands
      If KeyboardPushed(#PB_Key_LeftShift) Or KeyboardPushed(#PB_Key_RightShift)
        If KeyboardReleased(#PB_Key_R)
          If *P\Allow_Restart
            Debug "ProcessKeyboard: restarting"
            Restart = 1
          Else
            Debug "ProcessKeyboard: not allowed to restart"
          EndIf
        EndIf
        If KeyboardReleased(#PB_Key_Q)
          Debug "ProcessKeyboard: quit"
          *P\Quit = 1
        EndIf
      EndIf
    EndIf    
    
    ; *************************************
    ; menu
    ; *************************************
    
    If *P\Menu_Active
      ; only process menu controls when the menu is active
      *P\Menu_Action = #Menu_Action_None
      For c = 0 To #Max_Menu_Controls - 1
        If *P\Menu_Control[c]\Menu_Control_Hardware_Type = #Control_Hardware_Keyboard
          ; only check keyboard controls since this is the keyboard handler
          If KeyboardReleased(*P\Menu_Control[c]\Menu_Control_ID)
            Debug "ProcessKeyboard: menu control " + *P\Menu_Control[c]\Menu_Control_ID + " pressed"
            *P\Menu_Action = *P\Menu_Control[c]\Menu_Control_Action
          EndIf
        EndIf
        If *P\Menu_Action <> #Menu_Action_None : Break : EndIf 
      Next
    EndIf    
    
    ; *************************************
    ; system
    ; *************************************
    ; Process full screen only key commands
    ; *************************************
    If *P\Full_Screen
      If KeyboardPushed(#PB_Key_LeftAlt) Or KeyboardPushed(#PB_Key_RightAlt)
        If KeyboardReleased(#PB_Key_F4)
          If *P\Allow_AltF4_Full_Screen
            Debug "ProcessKeyboard: quit by Alt+F4 in fullscreen"
            *P\Quit = 1
          Else
            Debug "ProcessKeyboard: not allowed to quit by Alt+F4 in fullscreen"
          EndIf
        EndIf
      EndIf
    Else
      ; Remove F10 and Alt
      CompilerIf #PB_Compiler_OS = #PB_OS_Windows
        If KeyboardPushed(#PB_Key_F10) Or KeyboardPushed(#PB_Key_LeftAlt) Or KeyboardPushed(#PB_Key_RightAlt)
          keybd_event_(#PB_Key_F9, 0, #KEYEVENTF_KEYUP, 0)
          Debug "ProcessKeyboard: F10/Alt key pushed while in Window mode"
        EndIf
      CompilerEndIf 
    EndIf
    ; ************************************************
    ; Process both full screen and window key commands
    ; ************************************************ 
    
    If KeyboardReleased(#PB_Key_F11)
      If *P\Allow_Switch_to_Window
        ; switch between window or full screen
        SwitchFullScreen(*P)
      Else
        Debug "ProcessKeyboard: not allowed to switch between full screen and window"
      EndIf
    EndIf
    If KeyboardReleased(#PB_Key_F12)
      If *P\Allow_Screen_Capture
        *P\Take_Screen_Capture = 1
      Else
        Debug "ProcessKeyboard: not allowed to screen capture"
      EndIf
    EndIf    
    If KeyboardReleased(#PB_Key_Escape)
      Debug "ProcessKeyboard: quit"
      *P\Quit = 1
    EndIf
    
  EndIf
EndProcedure

Procedure ProcessMouse(*P.Game_Parametres_Structure)
  Protected c.i
  If Not *P\Full_Screen_Inactive
    ; only process is there has been a change
    If *P\Full_Screen Or *P\Mouse_Control
      ; Mouse is contained within the screen
      ExamineMouse()
      *P\Mouse_X = MouseX() * *P\Mouse_Sensitivity_X
      *P\Mouse_Y = MouseY() * *P\Mouse_Sensitivity_Y
      *P\Mouse_Button_Left = MouseButton(#PB_MouseButton_Left)
      *P\Mouse_Button_Middle = MouseButton(#PB_MouseButton_Middle)
      *P\Mouse_Button_Right = MouseButton(#PB_MouseButton_Right)
      *P\Mouse_Wheel_Movement = MouseWheel()
    Else
      *P\Mouse_X = WindowMouseX(#Game_Window_Main)
      *P\Mouse_Y = WindowMouseY(#Game_Window_Main)
      ; Window mouse clicks are handled by ProcessWindowEvents
    EndIf 
    
  EndIf
EndProcedure

;- Events

Procedure ProcessWindowEvents(*P.Game_Parametres_Structure)
  Protected Event.i
  If Not *P\Full_Screen Or *P\Full_Screen_Type = #Full_Screen_Windowed
    ; only process window events in a window mode               
    *P\Mouse_Left_Click = 0 ; Reset the mouse clicks
    *P\Mouse_Right_Click = 0
    Repeat              ; process all events
      Event = WindowEvent()
      Select Event
        Case #PB_Event_Menu
          Debug "ProcessWindowEvents: menu event"
        Case #PB_Event_DeactivateWindow
          Debug "ProcessWindowEvents: window deactivated"
        Case #PB_Event_ActivateWindow
          Debug "ProcessWindowEvents: window activated"          
        Case #PB_Event_CloseWindow
          *P\Quit = 1
        Case #PB_Event_SizeWindow
          If GetWindowState(#Game_Window_Main) = #PB_Window_Normal
            ; ****************************************************
            ; Only update window variables if it's a normal window
            ; This is needed for maximise etc to work
            If *P\Window_W <> WindowWidth(#Game_Window_Main) Or *P\Window_H <> WindowHeight(#Game_Window_Main)
              ; Window has actually been resized and not just minimised
              *P\Window_W = WindowWidth(#Game_Window_Main)
              *P\Window_H = WindowHeight(#Game_Window_Main)
              If *P\Window_Ratio_Enable
                ; Calculate the window height based on the width
                *P\Window_H = Round(*P\Window_W * *P\Window_Ratio, #PB_Round_Up)
                ResizeWindow(#Game_Window_Main, *P\Window_X, *P\Window_Y, *P\Window_W, *P\Window_H)
              EndIf
              Debug "ProcessWindowEvents: window resized to " + *P\Window_W + " x " + *P\Window_H
              *P\Window_Moved = 1
              ; Don't close the window and reopen (SetScreen), just reset the window screen
              SetWindowScreen(*P)
              LoadSpriteResources(*P)    ; have to reload sprites after setting a new Window Screen
            EndIf 
          EndIf
        Case #PB_Event_MoveWindow
          If GetWindowState(#Game_Window_Main) = #PB_Window_Normal ; only change the coordinates if it's a normal window
            Debug "ProcessWindowEvents: window moved"
            *P\Window_Moved = 1
            *P\Window_X = WindowX(#Game_Window_Main)
            *P\Window_Y = WindowY(#Game_Window_Main)
          EndIf
        Case #PB_Event_MaximizeWindow
          Debug "ProcessWindowEvents: window maximised"
          *P\Window_Maximised = 1
          *P\Window_Minimised = 0
          SetWindowScreen(*P)
          LoadSpriteResources(*P) ; have to reload sprites after setting a new Window Screen
        Case #PB_Event_RestoreWindow
          *P\Window_Maximised = 0
          *P\Window_Minimised = 0
        Case #PB_Event_MinimizeWindow
          *P\Window_Maximised = 0
          *P\Window_Minimised = 1
        Case #PB_Event_LeftClick
          Debug "ProcessWindowEvents: primary mouse button clicked"
          *P\Mouse_Left_Click = 1
        Case #PB_Event_RightClick
          Debug "ProcessWindowEvents: secondary mouse button clicked"
          *P\Mouse_Right_Click = 1
      EndSelect
    Until Event = 0
  EndIf  
EndProcedure

;- System

Procedure DoCPUWait(*P.Game_Parametres_Structure)
  Protected t.i, d.i
  If *P\Flip_Mode = #PB_Screen_NoSynchronization
    ; Only no sync mode needs CPU wait time
    t = ElapsedMilliseconds() - *P\Game_Loop_Start_Time ; length of time the main loop took
    d = *P\Main_Loop_Time - t                           ; Main loop time is usually a fixed frame rate like 60
    If d>=0 : Delay(d) : EndIf
  EndIf
EndProcedure


Procedure ProcessFPS(*P.Game_Parametres_Structure)
  Protected t.i
  t = ElapsedMilliseconds() - *P\FPS_Last_Time
  If t >= 1000
    *P\FPS = *P\FPS_Frame_Count
    ; reset the timer
    *P\FPS_Frame_Count = 0
    *P\FPS_Last_Time = ElapsedMilliseconds()
  Else
    *P\FPS_Frame_Count = *P\FPS_Frame_Count + 1
  EndIf
EndProcedure

Procedure ProcessSystem(*P.Game_Parametres_Structure)
  *P\Game_Loop_Start_Time = ElapsedMilliseconds()
  ProcessFPS(*P)
EndProcedure

Procedure SaveConfig(*P.Game_Parametres_Structure, Level.i=1)
  ; Never call SaveConfig before LoadConfig
  ; Save levels: 1 - window settings, 2 - game settings
  Protected f.s
  f = GetCurrentDirectory() + *P\Game_Config_File
  If *P\Config_Loaded ; only save if the config has been loaded
    If Level = 1 Or Level = 2
      ; Create or open the config file
      If Not *P\Config_File
        ; Need to create a new file
        Debug "SaveConfig: creating new config file"
        If Not CreatePreferences(f)
          Debug "SaveConfig: ERROR - unable to create config file"
          ProcedureReturn 0
        EndIf
        *P\Reset_Window = 1 ; reset the window so that it's centred
        *P\Config_File = 1  ; config file exists now
      Else
        ; Open the existing config file
        If Not OpenPreferences(f)
          Debug "SaveConfig: ERROR - unable to open existing config file"
          ProcedureReturn 0
        EndIf
      EndIf
      ; Window settings are automatically saved when closing the game
      ; or changing the window (while in window mode)
      Debug "SaveConfig: writing window preferences"
      PreferenceGroup("Window")
      WritePreferenceInteger("Full_Screen", *P\Full_Screen)
      WritePreferenceInteger("Window_X", *P\Window_X)
      WritePreferenceInteger("Window_Y", *P\Window_Y)
      WritePreferenceInteger("Window_W", *P\Window_W)
      WritePreferenceInteger("Window_H", *P\Window_H)
      WritePreferenceInteger("Window_Maximised", *P\Window_Maximised)
      If Level = 2
        ; write the level 2 settings
        ; this is usually only run in-game
        Debug "SaveConfig: writing graphics preferences"
        PreferenceGroup("Graphics")
        ; WritePreferenceInteger("Flip_Mode", *P\Flip_Mode)
      EndIf
      ClosePreferences()
      ProcedureReturn 1
    Else ; invalid config level specified
      Debug "SaveConfig: ERROR - invalid config level specified: " + Level
      ProcedureReturn 0
    EndIf
  Else
    Debug "SaveConfig: ERROR - cannot save config if it hasn't been loaded yet"
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure LoadConfig(*P.Game_Parametres_Structure)
  ; Loads configuration if available and sets defaults when no config is available
  ; If there is no config file then SaveConfig will be called
  Protected f.s
  ; Layer 1
  f = *P\Game_Config_File
  If FileSize(f)>0
    *P\Config_File = 1 ; config file found
  EndIf
  Debug "LoadConfig: opening " + *P\Game_Config_File
  OpenPreferences(f)
  Debug "LoadConfig: loading window preferences from disk storage"
  PreferenceGroup("Window")
  *P\Full_Screen = ReadPreferenceInteger("Full_Screen", *P\Full_Screen)
  *P\Window_X = ReadPreferenceInteger("Window_X", 0)
  *P\Window_Y = ReadPreferenceInteger("Window_Y", 0)  
  *P\Window_W = ReadPreferenceInteger("Window_W", *P\Window_W)
  *P\Window_H = ReadPreferenceInteger("Window_H", *P\Window_H)
  *P\Window_Maximised = ReadPreferenceInteger("Window_Maximised", 0)
  PreferenceGroup("Grapics")
  ; *P\Flip_Mode = ReadPreferenceInteger("Flip_Mode", #Default_Flip_Mode)
  *P\Config_Loaded = 1 ; set this so that SaveConfig can run. It's important to load config before saving
  ClosePreferences()
  ProcedureReturn 1
EndProcedure

Procedure SetInitialiseError(*P.Game_Parametres_Structure, Message.s)
  ; Sets a special error message on the first initialisation error encountered, to help troubleshooting
  If Not *P\Initialisation_Error
    ; Only set the error message if there has been no error yet, this helps with troubleshooting so you can see the first error
    *P\Initialisation_Error = 1
    *P\Initialise_Error_Message = Message
  EndIf
EndProcedure

Procedure Initialise(*P.Game_Parametres_Structure)
  ; Initialises the environment
  Protected o.i, Result.i, c.i
  Debug "Initialise: starting"
  
  SetDefaults(*P)
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    ; Check if game is already running
    *P\MutexID = CreateMutex_(0, 1, *P\Game_Title)
    *P\MutexError = GetLastError_()
    If *P\MutexID = 0 Or *P\MutexError <> 0
      ReleaseMutex_(*P\MutexID)
      CloseHandle_(*P\MutexID)
      Debug "Initialise: " + *P\Game_Title + " is already running, cannot run more than one instance"
      SetInitialiseError(*P, *P\Game_Title + " is already running, cannot run more than one instance")
      ProcedureReturn 0
    EndIf
  CompilerEndIf
  
  InitKeyboard()
  InitMouse()
  UsePNGImageEncoder() ; enable PNG encoding for saving screen captures
  UsePNGImageDecoder()
  
  If Not InitDesktop(*P)
    Debug "Initialise: could not initialise desktop"
    SetInitialiseError(*P, "could not initialise desktop (the operating system graphical display)")
    ProcedureReturn 0
  EndIf
  
  If *P\Desktop[0]\Depth <> 32
    Debug "Initialise: only 32 bit desktop colour depth is supported)"
    SetInitialiseError(*P, "only 32 bit desktop colour depth is supported")
    ProcedureReturn 0    
  EndIf
  
  If Not InitSprite()
    Debug "Initialise: could not initialise sprite environment"
    SetInitialiseError(*P, "could not initialise sprite environment (usually this is a DirectX problem)")
    ProcedureReturn 0    
  EndIf
  
  If Not LoadConfig(*P)
    Debug "Initialise: could not load config"
    SetInitialiseError(*P, "could not load config")
    ProcedureReturn 0
  EndIf    
  
  SaveConfig(*P, 2) ; always save config on start incase of corrupt file
  
  If Not SetScreen(*P)
    Debug "Initialise: could not set screen"
    SetInitialiseError(*P, "could not set screen")
    ProcedureReturn 0
  EndIf
  
  
  ;*****************************************
  ; Loading screen begin
  ;*****************************************
  
  ClearScreen(*P\Background_Colour)
  FlipBuffers()
  
  If *P\Show_Loading_Screen
    ClearScreen(*P\Background_Colour)
    StartDrawing(ScreenOutput())
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(0, 0, "Loading...")
    StopDrawing()
    FlipBuffers()
  EndIf
  
  If *P\Render_Engine3D <> #Render_Engine3D_None
    ; Don't initialise 3D if it is not enabled
    Debug "Initialise: 3D engine"
    Select *P\Render_Engine3D ; Initialise render engine
      Case #Render_Engine3D_Builtin
        ; not written yet
    EndSelect
  EndIf
  
  Debug "Initialise: loading menu controls"
  
  Select *P\Menu_System_Type
    Case #Menu_System_Menuless
      Restore Menu_Controls_MenuLess_Data
    Case #Menu_System_Simple
      Restore Menu_Controls_Simple_Data
    Case #Menu_System_Pointer
      Restore Menu_Controls_Pointer_Data
  EndSelect
  Read.i *P\Menu_Controls_Count
  If *P\Menu_Controls_Count > #Max_Menu_Controls
    *P\Fatal_Error_Message = "#Max_Menu_Controls too small to load all menu controls"
    Fatal_Error(*P)       
  EndIf
  For c = 0 To *P\Menu_Controls_Count - 1
    Read.i *P\Menu_Control[c]\Menu_Control_Type
    Read.i *P\Menu_Control[c]\Menu_Control_Action
    Read.i *P\Menu_Control[c]\Menu_Control_Hardware_Type
    Read.i *P\Menu_Control[c]\Menu_Control_ID
  Next
  
  LoadSpriteResources(*P)
  
  ; Load Menu Background
  Select *P\Menu_Background_Data_Source
    Case #Data_Source_Internal_Memory
      Restore Menu_Background_None_Data
      Read.i *P\Menu_Background_Colour
  EndSelect
  
  
  ; Elevate control to layer 2 (menu)
  *P\Background_Colour = *P\Menu_Background_Colour
  *P\Menu_Active = 1
  
  Debug "Initialise: completed"
  *P\Initialised = 1
  ProcedureReturn 1 ; Initialise successful
EndProcedure

Procedure Shutdown(*P.Game_Parametres_Structure)
  If *P\Screen_Open
    CloseScreen()
    *P\Screen_Open = 0
  EndIf
  If *P\Window_Open
    CloseWindow(0)
    *P\Window_Open = 0
  EndIf 
  SaveConfig(*P) ; only need to save screen/window variables
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows : CloseHandle_(*P\MutexID) : CompilerEndIf
  FreeMemory(*P)
EndProcedure

;- Main loop

Repeat ; used for restarting the game
  If Restart : Debug "System: restarting..." : EndIf
  Restart = 0 ; game has started so don't restart again
  Define *P.Game_Parametres_Structure
  *P = AllocateMemory(SizeOf(Game_Parametres_Structure)) ; Allocate game memory
  If *P And Initialise(*P)
    ; Game memory allocated and game initialised
    Debug "System: starting main loop"
    Repeat
      ; main game loop
      ProcessSystem(*P) ; must be first in the main loop
      ProcessWindowEvents(*P)
      ProcessMouse(*P)
      ProcessKeyboard(*P)
      DoFlipBuffer(*P)
      DoClearScreen(*P)
      Draw3DWorld(*P)
      DrawSprites(*P) ; draws all sprites
      Draw2DGraphics(*P)
      DrawMouse(*P)
      DoPostProcessing(*P) ; eg screen capture
      CheckFullScreen(*P)  ; check for switching back to main window system (alt+tab), must be after FlipBuffers      
      DoCPUWait(*P)        ; limit the CPU usage
    Until *P\Quit Or Restart
    Debug "System: shutting down..."
    Shutdown(*P)
  Else
    MessageRequester ("Unable to start " + *P\Game_Title, "Could not initialise." + #CRLF$ + *P\Initialise_Error_Message, #PB_MessageRequester_Error)
  EndIf
Until Not Restart

Debug "System: game ended"
End 0

;- Data section
DataSection
  Menu_Controls_Data:
  ; First record is the number of records (make sure #Max_Menu_Controls is higher than the largest)
  ; Data is in the format of the Structure Menu_Control_Type
  ; menuless system
  Menu_Controls_Menuless_Data:
  ; This menu control system is like the Atari 2600 and is only included for making very simple games
  Data.i 3
  Data.i #Menu_System_Menuless, #Menu_Action_Start, #Control_Hardware_Keyboard, #PB_Key_Space
  Data.i #Menu_System_Menuless, #Menu_Action_Select, #Control_Hardware_Keyboard, #PB_Key_F1
  Data.i #Menu_System_Menuless, #Menu_Action_Reset, #Control_Hardware_Keyboard, #PB_Key_F2
  ; simple menu system
  Menu_Controls_Simple_Data:
  ; This menu system is for making console type games where the menu is controlled by up/down/left/right etc
  Data.i 6
  Data.i #Menu_System_Simple, #Menu_Action_Confirm, #Control_Hardware_Keyboard, #PB_Key_Return
  Data.i #Menu_System_Simple, #Menu_Action_Back, #Control_Hardware_Keyboard, #PB_Key_Escape
  Data.i #Menu_System_Simple, #Menu_Action_Up, #Control_Hardware_Keyboard, #PB_Key_Up
  Data.i #Menu_System_Simple, #Menu_Action_Down, #Control_Hardware_Keyboard, #PB_Key_Down
  Data.i #Menu_System_Simple, #Menu_Action_Left, #Control_Hardware_Keyboard, #PB_Key_Left
  Data.i #Menu_System_Simple, #Menu_Action_Right, #Control_Hardware_Keyboard, #PB_Key_Right
  ; pointer menu system
  Menu_Controls_Pointer_Data:
  ; This menu system is the most common for PC games
  Data.i 3
  Data.i #Menu_System_Pointer, #Menu_Action_Click, #Control_Hardware_Keyboard, #PB_Key_Return
  Data.i #Menu_System_Pointer, #Menu_Action_Click, #Control_Hardware_Keyboard, #PB_Key_Space
  Data.i #Menu_System_Pointer, #Menu_Action_Click, #Control_Hardware_Mouse, #PB_MouseButton_Left
  
  Image_Data:
  ; These are all the 2D images loaded by the system available to the game
  Data.i 0 ; Number of records
  
  Sprite_Resource_Data:
  ; Provides a list of sprite resources to be loaded
  ; Examples:
  ; Loading a sprite from internal memory is not yet supported
  ; Format: Width, Height, Mode, Transparent, Source, Index/file
  ; From DataSection: Data.i 30, 30, #PB_Sprite_AlphaBlending, #Data_Source_Internal_Memory, 0
  ; From a file: Data.i 30, 30, 0, #Data_Source_File : Data.s "test.bmp"
  ; From a database: Data.i 30, 30, 0, #Data_Source_Database, 100
  Data.i 2
  ; Number of records
  ;Data.i 12, 19, #PB_Sprite_AlphaBlending, #True, #Data_Source_File : Data.s "mouse.png"
  Data.i 8, 8, #PB_Sprite_AlphaBlending, #True, #Data_Source_None, 0
  ; This is the scratch sprite, used for zooming 2D drawing operations when simulating low resolution
  Data.i 12, 19, #PB_Sprite_AlphaBlending, #True, #Data_Source_Internal_Memory, 0
  ; Mouse sprite
  
  Sprite_Data:
  ; Provides actual sprites in the DataSection
  ; This can be ignored if necessary but leave the label above as the code needs it to work
  Data.l $FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000,$0000F2FF
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$FF000000,$FF000000,$FF000000,$FF000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FFFFFFFF,$FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000
  Data.l $FF000000,$FFFFFFFF,$FF000000,$00000000,$00000000,$FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$00000000,$00000000
  Data.l $FF000000,$FF000000,$00000000,$00000000,$00000000,$00000000,$FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$0000F2FF
  Data.l $00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$FF000000,$FFFFFFFF,$FFFFFFFF,$FF000000,$00000000,$0000F2FF
  Data.l $00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$00000000,$FF000000,$FF000000,$00000000,$00000000,$0000F2FF
  
  Menu_Background_Data:
  ; Data for displaying the menu background
  Menu_Background_None_Data:
  Data.i 8723235 ; colour to be displayed as background
  Menu_Background_Vector_Data:
  Menu_Background_Image_Data:
  
EndDataSection
Last edited by coco2 on Sat Dec 09, 2017 7:10 am, edited 9 times in total.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: An example game engine

Post by Keya »

sooo many demos posted here which use 3D simply crash on my XP (its just not set up for gaming etc) due to assuming success instead of checking the return codes from those init functions... (just to be clear yes my system is the fault - there's no way yours or any game will run on it so its no fault of your code there!)

So I'm happy to report that your error-checking looks good from this end and it gracefully quit with a msgbox alerting me to the problem being detected :) (I dont have a good pc for testing your game sorry but it is good for testing as a failed system lol)
Debug output:

Code: Select all

SaveConfig: writing window preferences
SaveConfig: writing graphics preferences
SetScreen: Could not initialise windowed screen
Init: Could not set screen/window
with a graceful messagebox "Could not initialise game"
coco2
Enthusiast
Enthusiast
Posts: 368
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: An example game engine

Post by coco2 »

Thanks for the feedback :) yes I have gracefully surprised myself with nice error messages when something is totally wrong with it. What do you mean you don't have a decent PC I would love to help you get one if possible. Anyway, you got me interested in this, I might try it on my Windows XP VMWare PC to see why it's crashing.
Jeromyal
Enthusiast
Enthusiast
Posts: 204
Joined: Wed Jul 17, 2013 8:49 am

Re: An example game engine

Post by Jeromyal »

Was there any more progress on this?
User avatar
Caronte3D
Addict
Addict
Posts: 1027
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: An example game engine

Post by Caronte3D »

6 years later, I doubt it :?
coco2
Enthusiast
Enthusiast
Posts: 368
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: An example game engine

Post by coco2 »

I ran it in Windows XP Home 32 bit (in VirtualBox) and didn't get the error you got, however it seems to have trouble drawing sprites which might be related to VirtualBox.
Post Reply