编辑
2026-02-24
C#
00

目录

🤔 传统方式的痛点分析
💡 状态机模式:优雅的解决方案
🏗️ 核心架构设计
🎮 状态管理器:指挥官角色
🎨 实战代码:打造酷炫的游戏界面
🌟 菜单状态:第一印象很重要
🎯 游戏状态:核心业务逻辑
⏸️ 暂停状态:用户体验的细节
🔧 WinForms集成:无缝连接
📐 Designer.cs:现代化UI设计
🎯 主窗体逻辑:协调指挥
⚡ 性能优化技巧
🏃‍♂️ 渲染优化
🔄 内存管理
🚨 常见坑点提醒
🎯 扩展应用场景
💎 总结:三个核心要点

你是否在开发游戏或复杂应用时,被各种界面状态切换搞得头疼不已?菜单、游戏中、暂停、设置...每增加一个状态,代码就变得更加混乱,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... }

这种方式的问题显而易见:

  • 状态逻辑散落在各处,难以维护
  • 新增状态需要修改多个地方
  • 状态间的依赖关系不清晰
  • 测试困难,容易出现状态不一致的bug

💡 状态机模式:优雅的解决方案

状态机模式将每个状态封装为独立的类,让状态管理变得清晰可控。让我们看看如何实现:

🏗️ 核心架构设计

image.png

首先定义状态接口和枚举:

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); } } }

关键特性:

  • 集中式状态管理,职责单一
  • 事件通知机制,UI可以及时响应状态变化
  • 生命周期管理,确保状态切换的完整性

🎨 实战代码:打造酷炫的游戏界面

🌟 菜单状态:第一印象很重要

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); } } }

设计亮点:

  • SKiaSharp提供的硬件加速渲染,性能远超传统GDI+
  • 渐变效果和抗锯齿,让界面更加专业
  • 响应式布局,适应不同屏幕尺寸

🎯 游戏状态:核心业务逻辑

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); } } }

🔧 WinForms集成:无缝连接

📐 Designer.cs:现代化UI设计

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); } }

image.png

image.png

image.png

⚡ 性能优化技巧

🏃‍♂️ 渲染优化

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. Paint对象泄露:SKPaint实现了IDisposable,务必及时释放
  2. 状态切换时机:在Update中改变状态可能导致异常,建议使用事件队列
  3. 渲染性能:避免在Draw方法中进行复杂计算

🎯 扩展应用场景

这套状态机不仅适用于游戏开发,还可以用于:

  • 企业应用:工作流状态管理
  • 多媒体软件:播放、暂停、停止状态
  • 数据可视化:不同图表模式切换
  • 向导程序:步骤化流程控制

💎 总结:三个核心要点

1. 架构清晰:状态机模式让复杂的状态管理变得井然有序,每个状态职责明确,易于维护和扩展。

2. 性能出色:SKiaSharp的硬件加速渲染配合合理的资源管理,确保应用运行流畅,视觉效果专业。

3. 实用性强:这套解决方案不仅适用于游戏开发,更可以广泛应用于各种需要状态管理的桌面应用场景。

通过状态机模式,我们将原本混乱的状态切换逻辑,转变为清晰可控的架构设计。配合现代化的UI设计和高性能渲染,让你的WinForms应用焕然一新!

觉得这套方案有用吗? 你在项目中是如何处理复杂状态管理的?欢迎在评论区分享你的经验和遇到的挑战!

如果这篇文章对你有帮助,请转发给更多需要的同行! 让我们一起提升C#开发的技术水平! 🚀

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!