你是否在开发游戏或复杂应用时,被各种界面状态切换搞得头疼不已?菜单、游戏中、暂停、设置...每增加一个状态,代码就变得更加混乱,if-else满天飞,维护起来简直是噩梦。
今天就来分享一个经典的解决方案——状态机模式,它能让你的界面管理变得井井有条。我们将基于SKiaSharp构建一个完整的WinForms游戏状态管理系统,不仅代码优雅,视觉效果也相当出色!
在传统的WinForms开发中,我们通常这样处理状态切换:
c#// 传统做法:意大利面条式代码
private void ButtonClick(object sender, EventArgs e)
{
if (currentState == "menu")
{
if (button == startButton)
{
// 隐藏菜单控件
// 显示游戏控件
// 初始化游戏
currentState = "playing";
}
}
else if (currentState == "playing")
{
if (button == pauseButton)
{
// 暂停游戏
// 显示暂停菜单
currentState = "paused";
}
}
// 更多嵌套的if-else...
}
这种方式的问题显而易见:
状态机模式将每个状态封装为独立的类,让状态管理变得清晰可控。让我们看看如何实现:

首先定义状态接口和枚举:
c#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public enum GameState
{
Menu,
Playing,
Paused
}
}
c#using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public interface IGameState
{
void Enter();
void Update();
void Draw(SKCanvas canvas);
void Exit();
}
}
核心思想: 每个状态都是一个独立的"小世界",有自己的生命周期和职责。
c#using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public class GameStateManager
{
private Dictionary<GameState, IGameState> _states;
private GameState _currentState;
private GameState _previousState;
public event Action<GameState, GameState> StateChanged;
public GameState CurrentState => _currentState;
public GameState PreviousState => _previousState;
public GameStateManager()
{
_states = new Dictionary<GameState, IGameState>();
}
public void RegisterState(GameState state, IGameState stateImplementation)
{
_states[state] = stateImplementation;
}
public void ChangeState(GameState newState)
{
if (!_states.ContainsKey(newState))
throw new ArgumentException($"State {newState} is not registered");
if (_currentState != GameState.Menu)
{
_states[_currentState]?.Exit();
}
_previousState = _currentState;
_currentState = newState;
_states[_currentState].Enter();
StateChanged?.Invoke(_previousState, _currentState);
}
public void Update()
{
_states[_currentState]?.Update();
}
public void Draw(SKCanvas canvas)
{
_states[_currentState]?.Draw(canvas);
}
}
}
关键特性:
c#using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public class MenuState : IGameState
{
private readonly Action<GameState> _changeState;
private SKPaint _titlePaint;
private SKPaint _buttonPaint;
private SKPaint _textPaint;
public MenuState(Action<GameState> changeStateCallback)
{
_changeState = changeStateCallback;
InitializePaints();
}
private void InitializePaints()
{
_titlePaint = new SKPaint
{
Color = SKColors.White,
TextSize = 48,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei", SKFontStyle.Bold)
};
_buttonPaint = new SKPaint
{
Color = SKColors.DarkBlue,
IsAntialias = true
};
_textPaint = new SKPaint
{
Color = SKColors.White,
TextSize = 24,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei")
};
}
public void Enter()
{
// 菜单状态初始化
}
public void Update()
{
// 菜单更新逻辑
}
public void Draw(SKCanvas canvas)
{
// 绘制渐变背景
using var gradient = SKShader.CreateLinearGradient(
new SKPoint(0, 0),
new SKPoint(800, 600),
new[] { SKColors.DarkBlue, SKColors.Navy },
SKShaderTileMode.Clamp);
using var bgPaint = new SKPaint { Shader = gradient };
canvas.DrawRect(0, 0, 800, 600, bgPaint);
// 绘制标题
var titleText = "游戏主菜单";
var titleBounds = new SKRect();
_titlePaint.MeasureText(titleText, ref titleBounds);
canvas.DrawText(titleText,
400 - titleBounds.Width / 2,
150,
_titlePaint);
// 绘制按钮
DrawButton(canvas, "开始游戏", 300, 250);
DrawButton(canvas, "设置", 300, 320);
DrawButton(canvas, "退出", 300, 390);
}
private void DrawButton(SKCanvas canvas, string text, float x, float y)
{
var buttonRect = new SKRect(x, y, x + 200, y + 50);
// 绘制按钮背景
using var buttonGradient = SKShader.CreateLinearGradient(
new SKPoint(x, y),
new SKPoint(x, y + 50),
new[] { SKColors.LightBlue, SKColors.DarkBlue },
SKShaderTileMode.Clamp);
using var paint = new SKPaint { Shader = buttonGradient };
canvas.DrawRoundRect(buttonRect, 10, 10, paint);
// 绘制按钮边框
using var borderPaint = new SKPaint
{
Color = SKColors.White,
Style = SKPaintStyle.Stroke,
StrokeWidth = 2,
IsAntialias = true
};
canvas.DrawRoundRect(buttonRect, 10, 10, borderPaint);
// 绘制按钮文字
var textBounds = new SKRect();
_textPaint.MeasureText(text, ref textBounds);
canvas.DrawText(text,
x + (200 - textBounds.Width) / 2,
y + 25 + textBounds.Height / 2,
_textPaint);
}
public void Exit()
{
// 清理菜单状态
}
public void StartGame()
{
_changeState(GameState.Playing);
}
}
}
设计亮点:
c#using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public class PlayingState : IGameState
{
private readonly Action<GameState> _changeState;
private SKPaint _backgroundPaint;
private SKPaint _playerPaint;
private SKPaint _uiPaint;
private float _playerX = 100;
private float _playerY = 300;
private int _score = 0;
private Random _random;
public PlayingState(Action<GameState> changeStateCallback)
{
_changeState = changeStateCallback;
_random = new Random();
InitializePaints();
}
private void InitializePaints()
{
_backgroundPaint = new SKPaint
{
Color = SKColors.DarkGreen,
IsAntialias = true
};
_playerPaint = new SKPaint
{
Color = SKColors.Yellow,
IsAntialias = true
};
_uiPaint = new SKPaint
{
Color = SKColors.White,
TextSize = 20,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei")
};
}
public void Enter()
{
_playerX = 100;
_playerY = 300;
_score = 0;
}
public void Update()
{
_score++;
_playerY += (float)Math.Sin(Environment.TickCount * 0.01) * 2;
}
public void Draw(SKCanvas canvas)
{
canvas.DrawRect(0, 0, 800, 600, _backgroundPaint);
for (int i = 0; i < 50; i++)
{
var x = (_random.Next(800) + Environment.TickCount * 0.1) % 800;
var y = _random.Next(600);
canvas.DrawCircle((float)x, y, 1, new SKPaint { Color = SKColors.White });
}
canvas.DrawCircle(_playerX, _playerY, 20, _playerPaint);
canvas.DrawText($"分数: {_score}", 20, 30, _uiPaint);
canvas.DrawText("按ESC暂停", 20, 560, _uiPaint);
using var borderPaint = new SKPaint
{
Color = SKColors.Lime,
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
IsAntialias = true
};
canvas.DrawRect(10, 10, 780, 580, borderPaint);
}
public void Exit()
{
}
public void PauseGame()
{
_changeState(GameState.Paused);
}
public void BackToMenu()
{
_changeState(GameState.Menu);
}
}
}
c#using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppGameStateManager
{
public class PausedState : IGameState
{
private readonly Action<GameState> _changeState;
private SKPaint _overlayPaint;
private SKPaint _textPaint;
private SKPaint _buttonPaint;
public PausedState(Action<GameState> changeStateCallback)
{
_changeState = changeStateCallback;
InitializePaints();
}
private void InitializePaints()
{
_overlayPaint = new SKPaint
{
Color = SKColors.Black.WithAlpha(128),
IsAntialias = true
};
_textPaint = new SKPaint
{
Color = SKColors.White,
TextSize = 36,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei", SKFontStyle.Bold)
};
_buttonPaint = new SKPaint
{
Color = SKColors.DarkRed,
IsAntialias = true
};
}
public void Enter()
{
// 暂停状态初始化
}
public void Update()
{
}
public void Draw(SKCanvas canvas)
{
// 绘制半透明遮罩
canvas.DrawRect(0, 0, 800, 600, _overlayPaint);
// 绘制暂停面板
var panelRect = new SKRect(250, 200, 550, 400);
using var panelPaint = new SKPaint
{
Color = SKColors.DarkBlue,
IsAntialias = true
};
canvas.DrawRoundRect(panelRect, 20, 20, panelPaint);
// 绘制面板边框
using var borderPaint = new SKPaint
{
Color = SKColors.White,
Style = SKPaintStyle.Stroke,
StrokeWidth = 3,
IsAntialias = true
};
canvas.DrawRoundRect(panelRect, 20, 20, borderPaint);
var pauseText = "游戏暂停";
var textBounds = new SKRect();
_textPaint.MeasureText(pauseText, ref textBounds);
canvas.DrawText(pauseText,
400 - textBounds.Width / 2,
250,
_textPaint);
using var buttonTextPaint = new SKPaint
{
Color = SKColors.White,
TextSize = 20,
IsAntialias = true,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei")
};
DrawPauseButton(canvas, "继续游戏", 300, 280, buttonTextPaint);
DrawPauseButton(canvas, "返回菜单", 300, 340, buttonTextPaint);
}
private void DrawPauseButton(SKCanvas canvas, string text, float x, float y, SKPaint textPaint)
{
var buttonRect = new SKRect(x, y, x + 200, y + 40);
using var buttonGradient = SKShader.CreateLinearGradient(
new SKPoint(x, y),
new SKPoint(x, y + 40),
new[] { SKColors.Red, SKColors.DarkRed },
SKShaderTileMode.Clamp);
using var paint = new SKPaint { Shader = buttonGradient };
canvas.DrawRoundRect(buttonRect, 8, 8, paint);
var textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
canvas.DrawText(text,
x + (200 - textBounds.Width) / 2,
y + 20 + textBounds.Height / 2,
textPaint);
}
public void Exit()
{
}
public void ResumeGame()
{
_changeState(GameState.Playing);
}
public void BackToMenu()
{
_changeState(GameState.Menu);
}
}
}
c#private void InitializeComponent()
{
// SKControl:高性能渲染画布
this.skControl = new SkiaSharp.Views.Desktop.SKControl();
this.skControl.BackColor = System.Drawing.Color.Black;
this.skControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.skControl.VSync = true; // 垂直同步,避免撕裂
// 控制面板:扁平化设计
this.pnlControls.BackColor = System.Drawing.Color.FromArgb(45, 45, 48);
// 现代化按钮样式
this.btnStart.BackColor = System.Drawing.Color.FromArgb(0, 122, 204);
this.btnStart.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnStart.FlatAppearance.BorderSize = 0;
}
c#public partial class FrmGameMain : Form
{
private GameStateManager _stateManager;
private Timer _gameTimer;
private void InitializeGame()
{
_stateManager = new GameStateManager();
// 注册所有状态
_stateManager.RegisterState(GameState.Menu, new MenuState(ChangeState));
_stateManager.RegisterState(GameState.Playing, new PlayingState(ChangeState));
_stateManager.RegisterState(GameState.Paused, new PausedState(ChangeState));
// 🔄 状态变化监听:UI自动更新
_stateManager.StateChanged += OnStateChanged;
_stateManager.ChangeState(GameState.Menu);
}
private void OnStateChanged(GameState oldState, GameState newState)
{
// 自动更新UI状态
lblStatus.Text = $"状态: {GetStateDisplayName(newState)}";
UpdateButtonStates(newState);
}
}



c#private void GameTimer_Tick(object sender, EventArgs e)
{
_stateManager.Update();
skControl.Invalidate(); // 只重绘必要区域
}
// 💡 技巧:缓存Paint对象,避免频繁创建
private readonly Dictionary<string, SKPaint> _paintCache = new();
private SKPaint GetCachedPaint(string key, Func<SKPaint> factory)
{
if (!_paintCache.ContainsKey(key))
_paintCache[key] = factory();
return _paintCache[key];
}
c#protected override void OnFormClosed(FormClosedEventArgs e)
{
_gameTimer?.Stop();
_gameTimer?.Dispose();
// 清理Paint对象
foreach (var paint in _paintCache.Values)
paint?.Dispose();
base.OnFormClosed(e);
}
这套状态机不仅适用于游戏开发,还可以用于:
1. 架构清晰:状态机模式让复杂的状态管理变得井然有序,每个状态职责明确,易于维护和扩展。
2. 性能出色:SKiaSharp的硬件加速渲染配合合理的资源管理,确保应用运行流畅,视觉效果专业。
3. 实用性强:这套解决方案不仅适用于游戏开发,更可以广泛应用于各种需要状态管理的桌面应用场景。
通过状态机模式,我们将原本混乱的状态切换逻辑,转变为清晰可控的架构设计。配合现代化的UI设计和高性能渲染,让你的WinForms应用焕然一新!
觉得这套方案有用吗? 你在项目中是如何处理复杂状态管理的?欢迎在评论区分享你的经验和遇到的挑战!
如果这篇文章对你有帮助,请转发给更多需要的同行! 让我们一起提升C#开发的技术水平! 🚀
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!