状态模式
状态模式是设计模式中的一种行为设计模式,对很多人来说,这个模式平时可能用不到。但是如果你做游戏开发的话,我相信你应该对这个模式有一个很深刻的理解。状态模式在游戏中开发中还是比较常见的。状态模式将状态的行为封装在独立的状态类中,使得状态转换变得更加清晰和易于管理。这样的话,对象只负责状态的切换,不负责具体的行为。
比如射击类游戏:玩家模式是原地站立状态,当用户按下前进的时候,玩家的状态切换为前进状态,当按下后退的时候,状态切换为后退状态。当按下鼠标左键时,玩家进入射击状态。对于同一个玩家对象来说,不同的状态下,都是由不同的行为逻辑。这些逻辑,如果全部都在玩家这个对象类中实现的话,将会非常复杂。状态模式,就是用来解决这个问题的。
状态模式的主要组成部分包括:
(1)、上下文(Context):维护一个具体状态的实例,这个实例定义了当前的状态。
(2)、状态接口(State):定义了所有具体状态类的公共接口。
(3)、 具体状态类(Concrete States):实现状态接口,并根据上下文的状态改变其行为。
下面用C#简单的写一个状态模式的代码,来实现上面的玩家类。
1、定义状态接口IPlayerState表示玩家状态,Handle用于处理不同状态下玩家的具体行为- public interface IPlayerState
- {
- void Handle(PlayerContext context);
- }
复制代码 2、定义具体状态类:默认站立状态类:DefaultState 、前进状态:MoveForwardState 、后退状态:MoveBackwardState 、射击状态:DesignState 、死亡状态:DeadState- public class DefaultState : IPlayerState
- {
- public void Handle(PlayerContext context)
- {
- Console.WriteLine("玩家处于默认站立状态.");
- // 状态转换逻辑
- }
- }
- public class MoveForwardState : IPlayerState
- {
- public void Handle(PlayerContext context)
- {
- Console.WriteLine("玩家正在前进....");
- // 状态转换逻辑 这里可以实时渲染玩家位置
- }
- }
- public class MoveBackwardState : IPlayerState
- {
- public void Handle(PlayerContext context)
- {
- Console.WriteLine("玩家正在后退...");
- // 状态转换逻辑 这里可以实时渲染玩家位置
- }
- }
- public class DesignState : IPlayerState
- {
- public void Handle(PlayerContext context)
- {
- Console.WriteLine("玩家正在射击...");
- // 状态转换逻辑 这里可以判断玩家是否击中对方,更新对方血条和自己血条
- }
- }
- public class DeadState : IPlayerState
- {
- public void Handle(PlayerContext context)
- {
- Console.WriteLine("玩家死亡...");
- // 状态转换逻辑 这里可以对本局游戏进行玩家分数结算
- }
- }
复制代码 从上面可以看到,不同的状态,玩家的逻辑被清晰的描述出来。这样的话,我们就不用在玩家类Player中,通过if-else来来回回切换状态,实现业务逻辑,结构也会非常清晰。
3、定义上下文类:上下文类中存储了玩家的当前状态,并且可以通过上下文切换玩家状态。这是状态类中最基本的元素,当然还可以包含其他的状态数据,比如玩家的实时坐标,玩家的血条信息等等。- public class PlayerContext
- {
- private IPlayerState _state;
- public PlayerContext()
- {
- _state = new DefaultState(); // 初始状态为默认状态
- }
- public void SetState(IPlayerState state)
- {
- _state = state;
- }
- public void Request()
- {
- _state.Handle(this);
- }
- }
复制代码 4、在对象中,切换状态,调用对应的状态类逻辑。当然我们只是在这里手动切换状态 ,实际开发中,我们一般是监听鼠标键盘事件或者鼠标事件之后切换玩家状态。- class Program
- {
- static void Main(string[] args)
- {
- PlayerContext context = new PlayerContext();
- // 初始状态为默认状态
- context.Request();
- // 切换到前进状态
- context.SetState(new MoveForwardState());
- context.Request();
- // 切换到后退状态
- context.SetState(new MoveBackwardState());
- context.Request();
- // 切换到设计状态
- context.SetState(new DesignState());
- context.Request();
- // 切换到死亡状态
- context.SetState(new DeadState());
- context.Request();
- }
- }
复制代码 基本原理
介绍完状态模式的基本远离之后,接下来介绍截图功能的基本原理:通过捕获屏幕上的特定区域并将其保存为图像文件,在WPF中,我们可以使用System.Drawing命名空间中的类来实现这一功能。具体步骤如下:
1. 捕获屏幕区域:使用Graphics.CopyFromScreen方法从屏幕上复制指定区域的像素。
2. 保存图像:将捕获的像素数据保存为位图(Bitmap)格式。
3. 显示或处理图像:将位图转换为WPF可以处理的BitmapSource格式,以便在界面上显示或进一步处理。
我们先来看下如何保存图像:- public static System.Drawing.Bitmap Snapshot(int x, int y, int width, int height)
- {
- System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
- using (System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bitmap))
- {
- graphics.CopyFromScreen(x, y, 0, 0, new System.Drawing.Size(width, height), System.Drawing.CopyPixelOperation.SourceCopy);
- }
- return bitmap;
- }
复制代码 在截图过程中,我们使用了状态模式来管理截图过程中的不同状态。
当我们按下快捷键的时候, 处于默认状态,具体行为是:将一个WPF窗体背景设置为透明,宽高设置为和屏幕大小一致。
当我们按下鼠标左键的时候,处理开始截图状态(鼠标左键单击事件),具体行为是:记录截图的开始坐标,也就是截图矩形的左上角坐标。
当按下鼠标开始移动端时候,处于截图中状态(鼠标移动事件),具体行为是:不断记录截图的实时坐标,作为截图区域的右下角坐标,同时用户选中的区域,背景色要设置成亮色,可以清晰可见。
当放开鼠标的时候,处于截图结束状态(鼠标左键抬起事件),具体行为是:记录截图的终止坐标,也就是截图举行的右下角坐标。
当下鼠标移动的时候,处于移动中状态,已经选好的截图区域是可以移动的,具体行为是:记录鼠标移动 偏差,动态设置选中区域的背景色。
当然还有其他的状态类,就不一一描述了。
首先定义我们的状态类接口IScreentState :表示截图状态类- public interface IScreentState
- {
- void ProcessState(StateContext context);
- }
复制代码 其次定义具体的状态实现类:StartState、SelectState、SelectingState、SelectedState、MoveState、MovingState、MovedState、EndState总共有8个状态了,这里我只实现了截图和移动功能,并没有实现拖拽功能,如果需要拖拽修改截图区域大小功能,大家可以自行实现。
[code]public class StartState : IScreentState{ public void ProcessState(StateContext context) { SnapShotWindow win = context._window; win.clipRect.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#8000")); win.Cursor = Cursors.Arrow; win.leftPanel.Width = 0; win.topPanel.Height = 0; win.rightPanel.Width = 0; win.bottomPanel.Height = 0; }}public class SelectState : IScreentState{ public void ProcessState(StateContext context) { context._startPoint = context._args.GetPosition(context._window); }}public class SelectingState : IScreentState{ public void ProcessState(StateContext context) { SnapShotWindow win = context._window; win.clipRect.Background = Brushes.Transparent; context._endPoint = context._args.GetPosition(win); win.leftPanel.Width = context._startPoint.X; win.topPanel.Height = context._startPoint.Y; win.rightPanel.Width = win.ActualWidth - context._endPoint.X; win.bottomPanel.Height = win.ActualHeight - context._endPoint.Y; win.snapShotInfo.Text = context.GetText(); }}public class SelectedState : IScreentState{ public void ProcessState(StateContext context) { context._endPoint = context._args.GetPosition(context._window); }}public class MoveState : IScreentState{ public void ProcessState(StateContext context) { SnapShotWindow win = context._window; context._mouseDownPosition = context._args.GetPosition(win); context._mouseDownMargin = new Thickness(win.leftPanel.ActualWidth, win.topPanel.ActualHeight, win.rightPanel.ActualWidth, win.bottomPanel.Height); ; var relativePosition = context._args.GetPosition(win); if (relativePosition.X >= 0 && relativePosition.X = 0 && relativePosition.Y = 0 && relativePosition.X = 0 && relativePosition.Y |