Page 2 of 3 FirstFirst 123 LastLast
Results 11 to 20 of 26

Thread: State machine - requesting advice

  1. #11
    I have 1 rule of thumb for all applications i ever make: Never add application main unit to uses list of other units. It is really unmodular and bad habit. Only when it's really really necessary for 2 forms needing alot of communication between eachother, then i might consider cross-using.

    Instead you can make for example gameunit.pas or gametypes.pas, and introduce all global types and variables there. This unit would use no other units in itself, but could be included up to all your other units. Kind of like this:
    https://code.google.com/p/nxpascal/s...rc/nxTypes.pas
    nxTypes is included throughout the whole engine, and all of the units have access to nxSetError() for consistent error reporting, types and other little things.

    And on topic, if you would have to make a whole class implementation for each different state, then at least i would consider it more complicated than the case-procedure way. It might be smarter, but it might take a whole lot more code lines

  2. #12
    Well, it might take more lines, but in the end I'll get nice clean and extensible code. And if I declare my state variable in e.g. Unit global, it'll modify same area of memory when accessed from unit A (which includes it) and unit B? Just want to be sure.

    Also thanks for being so helpful. I'll be sure to re-read this thread when I'll do actual state machine implementation (for now I need to get done more important stuff like chunk rendering and tileset handling).

  3. #13
    i've used it in few projects but i can't say i invented it
    http://gamedevgeek.com/tutorials/man...e-states-in-c/
    check out the 'stack of states' thing - really usefull for menus

  4. #14
    Laggyluk, interesting read, thanks.

    Now that I've started actually implementing this, I'll post any problems I'll have with this in this thread.

  5. #15
    Oops... My design seems to not working. Nothing past initialization messages which are part of main file is displayed as if it isn't even executed.

    Here is project file code sans legacy, commented out code:
    Code:
    program alletest;
    
    {$mode objfpc}{$H+}
    {$apptype gui}
    uses
      {$IFDEF UNIX}{$IFDEF UseCThreads}
      cthreads,
      {$ENDIF}{$ENDIF}
      Classes, allegro, sprites, PerlinNoiseUnit, WorldGen, boolUtils,
      Tilesets, ChunkUtils, Globals, states;
    
      procedure quitfunc();CDECL;
      begin
        quit:=true;
      end;
    
      procedure update();CDECL;
      begin
        //frame update
        CurrentState.Update;
      end;
    
    
    begin
      Randomize;
      //initializing Allegro
      if al_init then
      begin
          //setting up window
    
          al_set_color_depth(al_desktop_color_depth);
          al_set_gfx_mode(AL_GFX_AUTODETECT_WINDOWED,SCREENW,SCREENH,SCREENW,SCREENH);
          al_set_window_title('Super Heli Land');
          al_set_close_button_callback(@quitfunc);
          //installing keyboard
          al_textout_ex(al_screen,al_font,'Installing keyboard... ',0,0,al_makeacol_depth(al_desktop_color_depth,128,128,128,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
          al_install_keyboard;
          al_textout_ex(al_screen,al_font,'OK',600,0,al_makeacol_depth(al_desktop_color_depth,0,255,0,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
    
          //installing timers
          al_textout_ex(al_screen,al_font,'Installing timers... ',0,20,al_makeacol_depth(al_desktop_color_depth,128,128,128,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
          al_install_timer;
          al_textout_ex(al_screen,al_font,'OK',600,20,al_makeacol_depth(al_desktop_color_depth,0,255,0,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
    
          //installing update timer
          al_textout_ex(al_screen,al_font,'Initializing update routine... ',0,40,al_makeacol_depth(al_desktop_color_depth,128,128,128,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
          al_install_int_ex(@Update,AL_BPS_TO_TIMER(60));
          al_textout_ex(al_screen,al_font,'OK',600,40,al_makeacol_depth(al_desktop_color_depth,0,255,0,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
          //loading marioland font
          MarioFont:=al_load_font('marioland.pcx',nil,nil);
    
          al_textout_ex(al_screen,al_font,'Creating main menu... ',0,120,al_makeacol_depth(al_desktop_color_depth,128,128,128,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
          MainMenuState:=TMainMenuState.Create;
          CurrentState:=MainMenuState;
          al_textout_ex(al_screen,al_font,'OK',600,120,al_makeacol_depth(al_desktop_color_depth,0,255,0,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
    
          al_textout_ex(al_screen,al_font,'Starting main loop... ',0,140,al_makeacol_depth(al_desktop_color_depth,128,128,128,255),al_makeacol_depth(al_desktop_color_depth,0,0,0,255));
    
          //main loop
          repeat
            CurrentState.BeforeDraw;
            CurrentState.Draw;
            CurrentState.Main;
    
          until quit;
          //destroying font, so we won't have any leaks
          al_destroy_font(MarioFont);
          if CurrentState<>nil then CurrentState.Destroy;
          if MainMenuState<>nil then MainMenuState.Destroy;
      end;
    end.
    And here is States unit which defines states:
    Code:
    unit States;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils,sprites,allegro;
    
    type
    
      { TState }
    
      TState = class
        public
          constructor Create;
          destructor Destroy;override;
          procedure Update;
          procedure BeforeDraw;
          procedure Draw;
          procedure Main;
        private
    
      end;
    
      { TMainMenuState }
    
      TMainMenuState = class(TState)
        public
          constructor Create;
          destructor Destroy;override;
          procedure Update;
          procedure BeforeDraw;
          procedure Draw;
          procedure Main;
        private
          BGSprite:TSprite;
          SelectionShroomSprite:TSprite;
          Buffer:AL_BITMAPptr;
          StartItemSprite:TSprite;
          OptionItemSprite:TSprite;
          ExitItemSprite:TSprite;
          MenuIndex:Integer;
          destroying:boolean;
    
      end;
      function keyreleased(idx:Integer):Boolean;
    implementation
    uses globals;
    var
      last_al_key : AL_KEY_LIST;
    
    
    { TMainMenuState }
    
    constructor TMainMenuState.Create;
    var bmp:AL_BITMAPptr;
    begin
      inherited;
      //creating title image
      bmp:=al_load_pcx('title.pcx',@al_default_palette);
      BGSprite:=TSprite.Create(bmp,bmp);
      BGSprite.ScaleFactor:=4;
      BGSprite.UpdateMask; //need to be called.
      //loading selection mushroom
      bmp:=al_load_pcx('mushroom.pcx',@al_default_palette);
      SelectionShroomSprite:=TSprite.Create(bmp,bmp);
      SelectionShroomSprite.ScaleFactor:=4;
      SelectionShroomSprite.UpdateMask; //need to be called.
      SelectionShroomSprite.x:=32*4;
      SelectionShroomSprite.y:=96*4;
      SelectionShroomSprite.magentatransparent:=true;
      //creating Start option sprite
      bmp:=al_create_bitmap(5*8,8);
      al_clear_to_color(bmp,al_makecol(255,0,255));
      al_textout_ex(bmp,MarioFont,'Start',0,0,al_makecol(0,0,0),-1);
      StartItemSprite:=TSprite.Create(bmp,bmp);
      StartItemSprite.ScaleFactor:=4;
      StartItemSprite.x:=40*4;
      StartItemSprite.y:=96*4;
      StartItemSprite.magentatransparent:=true;
      //creating "Option" option sprite
      bmp:=al_create_bitmap(6*8,8);
      al_clear_to_color(bmp,al_makecol(255,0,255));
      al_textout_ex(bmp,MarioFont,'Option',0,0,al_makecol(0,0,0),-1);
      OptionItemSprite:=TSprite.Create(bmp,bmp);
      OptionItemSprite.ScaleFactor:=4;
      OptionItemSprite.x:=40*4;
      OptionItemSprite.y:=104*4;
      OptionItemSprite.magentatransparent:=true;
      //creating Exit option sprite
      bmp:=al_create_bitmap(4*8,8);
      al_clear_to_color(bmp,al_makecol(255,0,255));
      al_textout_ex(bmp,MarioFont,'Exit',0,0,al_makecol(0,0,0),-1);
      ExitItemSprite:=TSprite.Create(bmp,bmp);
      ExitItemSprite.ScaleFactor:=4;
      ExitItemSprite.x:=40*4;
      ExitItemSprite.y:=112*4;
      ExitItemSprite.magentatransparent:=true;
      //setting menu index to 0
      MenuIndex:=0;
      //creating drawing buffer so it won't blink
      Buffer:=al_create_bitmap(SCREENW,SCREENH);
      //telling we aren't destroying state just yet
      destroying:=false;
    end;
    
    destructor TMainMenuState.Destroy;
    begin
      inherited Destroy;
      //we need to set that to avoid potential segfaults related to drawing
      destroying:=true;
      //removing buffer
      al_destroy_bitmap(Buffer);
      //removing exit option sprite
      ExitItemSprite.Destroy();
      //removing "option" option sprite
      ExitItemSprite.Destroy();
      //removing start option sprite
      StartItemSprite.Destroy();
      //removing selection mushroom
      SelectionShroomSprite.Destroy();
      //removing title card
      BGSprite.Destroy();
    end;
    
    procedure TMainMenuState.Update;
    const LastMenuItem=2;
    begin
      inherited;
      if not destroying then
      begin //making sure we won't get any accidental segfaults
    
        //handling keyboard
        if keyreleased(AL_KEY_UP) then Dec(MenuIndex);
        if keyreleased(AL_KEY_DOWN) then Inc(MenuIndex);
        if MenuIndex>LastMenuItem then MenuIndex:=0;
        if MenuIndex<0 then MenuIndex:=LastMenuItem;
        case MenuIndex of
          0 : begin SelectionShroomSprite.y:=96*4; end; //start
          1 : begin SelectionShroomSprite.y:=104*4; end;//option
          2 : begin SelectionShroomSprite.y:=112*4; end;//exit
        end;
        if (keyreleased(AL_KEY_ENTER) or keyreleased(AL_KEY_ENTER_PAD)) then
        begin
          case MenuIndex of
          0 : begin  end; //start
          1 : begin  end;//option
          2 : begin quit:=true; self.Destroy; end;//exit
        end;
        end;
      end;
    end;
    
    procedure TMainMenuState.BeforeDraw;
    begin
      inherited;
    end;
    
    procedure TMainMenuState.Draw;
    begin
      inherited;
      if not destroying then
      begin
        //we embed it that way, just to avoid potential segfaults.
        BGSprite.Draw(Buffer);
        StartItemSprite.Draw(Buffer);
        OptionItemSprite.Draw(Buffer);
        ExitItemSprite.Draw(Buffer);
        SelectionShroomSprite.Draw(Buffer);
        al_blit(buffer,al_screen,0,0,0,0,SCREENW,SCREENH);
      end;
    end;
    
    procedure TMainMenuState.Main;
    begin
      inherited;
    end;
    
    function keyreleased(idx: Integer): Boolean;
    
    begin
      Result := ((al_key[idx]=0) and (last_al_key[idx]<>0));
      last_al_key:=al_key;
    end;
    
    { TState }
    
    constructor TState.Create;
    begin
      inherited;
      //Here state is initialized. Every state needs to keep their own buffer for
      //double/triple buffering. All resource loading should be done here.
    end;
    
    destructor TState.Destroy;
    begin
      inherited;
      //You should dispose of any resources made in Create if you don't want to cause
      //memory leak
    end;
    
    procedure TState.Update;
    begin
      //Update is called every frame (60 times per second).
      //It is designed to update objects (collision check, movement)
      //You shouldn't, however, do any write to variables (change values)
      //that aren't primitives (writing to integers, strings, etc. is fine)
      //or you'll get memory leak
    end;
    
    procedure TState.BeforeDraw;
    begin
      //Like Main, this is called in main loop and usage is similar.
      //It is, however called before drawing.
    end;
    
    procedure TState.Draw;
    begin
      //Drawing is done here. It is called in main loop (as fast as it can).
    end;
    
    procedure TState.Main;
    begin
      //This method is also called in main loop. It is designed to do things
      //that aren't drawing and needs to be called in main loop, for example
      //rebuilding map, updating frame of TAnimatedSprite, etc.
    end;
    
    end.
    //edit: Globals unit which may also be part of issue:
    Code:
    unit Globals;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils,states,allegro;
    var
      CurrentState:TState;
      MainMenuState:TMainMenuState;
      Quit:Boolean;
      MarioFont:AL_FONTptr;
    const
      SCREENW=640;
      SCREENH=576;
    implementation
    
    end.
    //edit #2: Also MainMenuState.Destroy gives me segfault in some function of ntdll, wait a minute... ntdll!LdrWx86FormatVirtualImage (1584). When I comment it out, it closes fine, but then I have mem leak..
    Last edited by Darkhog; 29-06-2013 at 12:27 AM.

  6. #16
    Code:
          //if CurrentState<>nil then CurrentState.Destroy; // This you can't call.
          //CurrentState = MainMenuState sometimes, and calling Destroy on already Destroyed object brings trouble...
          if MainMenuState<>nil then MainMenuState.Destroy;
    Not entirely sure, but i think you need to override them all. CurrentState is type TState, so calling CurrentState.Update etc must refer to object it was created as. Hence override.
    Code:
    TMainMenuState = class(TState)
        public
          constructor Create; override;
          destructor Destroy; override;
          procedure Update; override;
          procedure BeforeDraw; override;
          procedure Draw; override;
          procedure Main; override;

  7. #17
    The reason why you are getting segfault on MainMenuState.Destroy is becouse you are calling inherited on the start of the destructor method.
    In constructor (SomeObject.Create) you always call inherited on start of the method to make sure that constructor code from parent class is executed first.
    But in destructor (SomeObject.Destroy) you should always call inherited last othervise destructor code from parent class is executed too fast and can lead to memory leaks due to only freeing part of your objects memory (only memory of parent class is being freed up).

    As for nothing except initialization showing shouldn't you be calling the stuff from your Main Loop within the OnTimer events of your timers that you prepared in initialization part of your game?

  8. #18
    Well, yes, but some things are needed to be called in main loop, like drawing. As for your suggestion regarding segfault (calling inherited in destroy last), it didn't fix the issue.

    @User137: Segfault is even there if only I'm destroying only MainMenuState, without touching CurrentState.

    As for overriding, let the compiler speak for itself:
    Quote Originally Posted by FPC
    states.pas(32,17) Error: There is no method in an ancestor class to be overridden: "TMainMenuState.Update;"
    states.pas(33,17) Error: There is no method in an ancestor class to be overridden: "TMainMenuState.BeforeDraw;"
    states.pas(34,17) Error: There is no method in an ancestor class to be overridden: "TMainMenuState.Draw;"
    states.pas(35,17) Error: There is no method in an ancestor class to be overridden: "TMainMenuState.Main;"
    states.pas(48,1) Fatal: There were 4 errors compiling module, stopping
    And those methods are in ancestor (TState)!

  9. #19
    Yes, but you need to mark a method as "virtual" in your ancestor to be able to override it.

  10. #20
    It works, thanks! Now if only I could figure out that pesky segfault...

Page 2 of 3 FirstFirst 123 LastLast

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •