启动画面显示开发商和发行商,然后显示加载中,后台进行初始化。成功后显示菜单页面,其中可以选择开始游戏和游戏设置。点击开始则进入到各个游戏场景,游戏中可以暂停和恢复游戏, 角色死亡显示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依葫芦画瓢实现就可以了
完整代码