Results 1 to 10 of 26

Thread: State machine - requesting advice

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Still, seems like hacky solution to me. I JUST DON'T WANT TO FORGET MEANING OF MY CODE AFTER NOT LOOKING INTO IT FZOR 3 MONTHS OR MORE ANYMORE!!!

    Erm... Sorry for that little burst. Anyway, I think there may be a problem with setting states from within other states.

    Problem is that I won't be able to access global variable declared in main program from within other units (each state would get its own unit for clarity sake). Any ideas how to solve it?

  2. #2
    You can always add main unit in uses clause. And for avoiding ciclick dependancies you do it in implementation part of your state units.
    So for instance you have UMain unit as general game unit and several other units each for its own state.
    For you to be able to create theese state objects you need to add theese units into uses section of your UMain file.
    But if you wanna acces objects and variables declared which are in UMain file from theese state units you add UMain file into uses section which you declare in implementation section:
    Code:
    ...
    implementation
    uses UMain //Main game unit
    ...
    Do not add UMain unit into uses section on top of your states units othevise you will cause cicklic redundancies (compiler won't be able to figure out which units need which) and you won't be able to compile your project.

  3. #3
    Quote Originally Posted by Darkhog View Post
    Still, seems like hacky solution to me.
    It is not so teribly hacky. Until now I have always been using such approach. But I still think that your idea is actually better.
    Anywhay in the end it all depends on how good you implement it. You can have great idea but if your implementation of this idea is terible so will be result.

  4. #4
    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

  5. #5
    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).

  6. #6
    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

  7. #7
    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.

  8. #8
    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.

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
  •