Pascal遊戲開發入門(五):遊戲狀態管理與有限狀態機

Pascal遊戲開發入門(五): 遊戲狀態管理與有限狀態機

前言

啓動畫面顯示開發商和發行商,然後顯示加載中,後臺進行初始化。成功後顯示菜單頁面,其中可以選擇開始遊戲和遊戲設置。點擊開始則進入到各個遊戲場景,遊戲中可以暫停和恢復遊戲, 角色死亡顯示GameOver畫面。

不同的狀態下需要渲染的遊戲對象是不一樣的。都放到Game類中管理不太合適。一旦TGameObject對象變化,維護起來非常容易出錯。

解決思路就是每個狀態管理自己的TGameObject列表, 爲了協調不同狀態之間的切換和過度。新增一個狀態管理類。

於是就有了本文的有限狀態機

抽象狀態基類

  TGameState = class
  public
    stateId: string;
    procedure Update; virtual;
    procedure Render(); virtual;
    function OnExit: boolean; virtual;
    function OnEnter: boolean; virtual;
  protected
    objects: TGameObjectList
  end;

每個狀態負責自己的TGameObject列表,並管理其渲染,更新

狀態機

  TGameStateMachine = class
  public
    constructor Create;
    procedure PushState(state: TGameState);
    procedure ChangeState(state: TGameState);
    procedure PopState();
    procedure PopAndChangeState(state: TGameState);
    procedure Update;
    procedure Render();
  private
    states: TGameStateList;
  end;

狀態機負責管理各種狀態以及它們之間的過渡轉換, Update和Render接口用於調用各個狀態的Update和Render。並提供給TGame使用

實現狀態

實現菜單狀態前先要解決菜單項的繪製以及事件響應

  TButtonClickEvent = procedure of object;

  TMenuButton = class(TGameObject)
  public
    isReleased: boolean;
    OnClick: TButtonClickEvent;
    procedure Update; override;
  end;

在Update函數中判斷鼠標的位置是否在矩形中以及左鍵是否按下來觸發點擊事件

procedure TMenuButton.Update;
var
  mp: TVector2;
begin
  currentFrame := Ord(MOUSE_OUT);
  mp := (TInputHandle.Instance()).GetMousePosition();
  if (mp.x < position.x + w) and (mp.x > position.x) and
  (mp.y < position.y + h) and (mp.y > position.y) then
  begin
    if not (TInputHandle.Instance()).isMouseButtonDown(Ord(MOUSE_LEFT)) then
    begin
      isReleased := True;
      currentFrame := Ord(MOUSE_OVER);
    end
    else
      if isReleased then
      begin
        writeln('mouse click');
        currentFrame := Ord(MOUSE_CLICKED);
        if Assigned(OnClick) then
          OnClick();

        isReleased := False;
      end;
  end
end;

接下來先實現剛進入時顯示菜單的狀態

  TMenuState = class(TGameState)
  public
    constructor Create(r: PSDL_Renderer);
    function OnEnter: boolean; override;
  private
    procedure PlayClick;
    procedure ExitClick;
    procedure CreateBackground;
  end;

OnEnter 用於創建TGameObjectList並綁定點擊事件

function TMenuState.OnEnter: boolean;
var
  btn: TMenuButton;
begin
  (TTextureManager.Instance()).Load(pr, 'play_btn', 'assets/play_button.png');
  (TTextureManager.Instance()).Load(pr, 'exit_btn', 'assets/exit_button.png');

  CreateBackground();

  btn := TMenuButton.Create;
  btn.Load(100, 100, 400, 100, 'play_btn');
  btn.OnClick := @self.PlayClick;
  objects.Add(btn);

  btn := TMenuButton.Create;
  btn.Load(100, 300, 400, 100, 'exit_btn');
  btn.OnClick := @self.ExitClick;
  objects.Add(btn);
  Result := True;
end;

在每個點擊事件代碼中去創建目的狀態

procedure TMenuState.PlayClick;
var
  state: TGameState;
begin
  readyState := TPlayState.Create(self.pr);
end;

並在基類的Update中處理狀態的過渡

procedure TGameState.Update;
var
  i: integer;
begin
  writelnlog(stateId);
  if Assigned(readyState) then
  begin
    writelnlog(readyState.stateId);
    (TGameStateMachine.Instance()).changeState(readyState);
    exit;
  end;
  for i := 0 to objects.Count - 1 do
    objects[i].Update;
end;

最後就是修改TGame類,去除TGameObjectList的管理

procedure TGame.Update();
begin
  if not isRunning then
    exit;  
  (TGameStateMachine.Instance()).Update;
end;
procedure TGame.Render();
begin
  if not isRunning then
    exit;
  SDL_RenderClear(pr);
  (TGameStateMachine.Instance()).Render();
  SDL_RenderPresent(pr);
end;
procedure TGame.Init(title: string; x, y, h, w, flags: integer);
var state:TGameState;
begin
  .....
  state := TMenuState.Create(pr);
  (TGameStateMachine.Instance()).pushState(state);
  ....
end;

至於PlayState,PauseState依葫蘆畫瓢實現就可以了

完整代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章