MonoGame游戏开发框架日记 -07
第七章:输入管理
我又回来了家人们!虽迟但到,这几天除了继续我的项目开发,但是效率不高,不知道最近啥原因老爱刷视频看手机,所以我打算把B站给删了,玩游戏就算了毕竟本身就是游戏开发者整整这个其实并不是全是坏处,这几天摸了几天鱼赶紧跑来更新文章
输入管理
我们知道玩游戏的时候我们需要进行玩家操控主机端的话呢无非就是以下这些控制:键盘,鼠标控制,控制器控制(也就是我们俗称的游戏手柄),很多电脑玩家的话对于这个取决于你开发的是哪种类型的游戏,比如说FPS游戏的话就非常不适合开发手柄适配,因为FPS本身第一人称的话相对于手柄来说鼠标控制会更加合适,但是我们今天的教程会带大家创建一个输入管理类用于控制游戏,好了别的也不说多我们正式开始。
第一步:了解输入
MonoGame游戏框架已经为我们写好了游戏的输入输出的具体操作,但是这个还有一些缺点如果我们不再加以封装的话我们之后将会非常难受,那么我们直接开始今天的第一个就是如何处理输入,看下面这段代码
// 取得当前键盘输入状态
KeyboardState keyboardState = Keyboard.GetState();// 检查当前状态是否按下空格键
if (keyboardState.IsKeyDown(Keys.Space))
{// 执行按下空格键时的逻辑
}
上面这段代码是最简单的检测输入的方法时MonoGame原生就可以使用的,但是当我们创建一个游戏框架的时候,我们必须要为所有的东西封装好,不仅仅是为了以后自己看得懂代码,且以后如果有幸你的游戏有很多支持者,那么就免不了玩家为爱发电为你的游戏开发开发MOD那么这时如果你的代码阅读起来很困难那么MOD开发就不会那么顺畅,OK了我也不啰嗦了,直接下一步。
第二步:创建键盘输入检测
首先我们和之前一样在项目的根目录创建文件夹“Input”当然了这个你可以自己取名字,接着我们在创建一个cs文件“KeyBoardInfo.cs”
然后我们声明一些关键的变量,并进行构造,看下面的代码
using Microsoft.Xna.Framework.Input;namespace SlimeGameLibrary.Input;public class KeyBoardInfo
{/// <summary>/// 之前的按键状态/// </summary> public KeyboardState PreviousState { get; private set; }/// <summary>/// 当前的按键状态/// </summary>public KeyboardState CurrentState { get; private set; }/// <summary>/// 无参构造/// </summary> public KeyBoardInfo(){PreviousState = new KeyboardState();CurrentState = Keyboard.GetState();}/// <summary>/// 不断更新键盘状态/// </summary> public void Update(){PreviousState = CurrentState;CurrentState = Keyboard.GetState();}
}
接下来我们还得编写一些关键的输入处理包括按下抬起按钮,以及一些其他的七七八八的按钮
/// <summary>/// 检测当前按钮是否按下 (只要按住指定的键,就会返回 true)/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否按下</returns>public bool IsKeyDown(Keys key){return CurrentState.IsKeyDown(key);}/// <summary>/// 检测当前按钮是否未被按下 (只要未按下指定的键,则返回 true)/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否Up</returns> public bool IsKeyUp(Keys key){return CurrentState.IsKeyUp(key);}/// <summary>/// 仅当指定的键从上到下更改时,仅在帧上返回 true/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否按下</returns>public bool WasKeyJustPressed(Keys key){return CurrentState.IsKeyDown(key) && PreviousState.IsKeyUp(key);} /// <summary>/// 仅当指定的键从 down-up 更改为 up 时,仅在帧上返回 true/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否Up</returns>public bool WasKeyJustReleased(Keys key){return CurrentState.IsKeyUp(key) && PreviousState.IsKeyDown(key);}
很简单,对吧,没错输入逻辑这部分的核心逻辑无非就是if else其实无论你是小白,还是大佬都用的是if else
这个的本质是不会变化的,需要学习的主要是这些API的用法,大家学习一个新的技术栈,只要你会这个语言,那么其实你要做的仅仅只是了解这个API接口的工作原理以及如何使用,包括这个开发人员给出的文档都是好东西,那么我们接下来直接给出完整的逻辑代码
using Microsoft.Xna.Framework.Input;namespace SlimeGameLibrary.Input;public class KeyBoardInfo
{/// <summary>/// 之前的按键状态/// </summary> public KeyboardState PreviousState { get; private set; }/// <summary>/// 当前的按键状态/// </summary>public KeyboardState CurrentState { get; private set; }/// <summary>/// 无参构造/// </summary> public KeyBoardInfo(){PreviousState = new KeyboardState();CurrentState = Keyboard.GetState();}/// <summary>/// 不断更新键盘状态/// </summary> public void Update(){PreviousState = CurrentState;CurrentState = Keyboard.GetState();}/// <summary>/// 检测当前按钮是否按下 (只要按住指定的键,就会返回 true)/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否按下</returns>public bool IsKeyDown(Keys key){return CurrentState.IsKeyDown(key);}/// <summary>/// 检测当前按钮是否未被按下 (只要未按下指定的键,则返回 true)/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否Up</returns> public bool IsKeyUp(Keys key){return CurrentState.IsKeyUp(key);}/// <summary>/// 仅当指定的键从上到下更改时,仅在帧上返回 true/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否按下</returns>public bool WasKeyJustPressed(Keys key){return CurrentState.IsKeyDown(key) && PreviousState.IsKeyUp(key);} /// <summary>/// 仅当指定的键从 down-up 更改为 up 时,仅在帧上返回 true/// </summary>/// <param name="key">检测按键</param>/// <returns>返回是否Up</returns>public bool WasKeyJustReleased(Keys key){return CurrentState.IsKeyUp(key) && PreviousState.IsKeyDown(key);}
}
第三步:创建鼠标输入类检测
大部分的个人电脑都是配有鼠标的,那么我们都知道鼠标一共有五个按钮,有的鼠标甚至更多,但是大部分的鼠标都是五个,分别是:鼠标右键,鼠标左键,鼠标滚轮(中键),鼠标侧键_1,鼠标侧键_2,那么我们如果要新建一个鼠标的输入状态的话那么我们必须先声明一个枚举类型,不会没关系,这个其实就是个菜单一样的东西
那么我们往下看代码
namespace SlimeGame.Input;public enum MouseButton
{Left,Middle,Right,XButton1,XButton2
}
好了我们声明完了这个组件,之后和上面键盘输入检测一样都需要写重复的内容,那么我们继续给MouseInfo的上按钮按下,且我们要确定鼠标的位置,通过MonoGame的API进行开发,我接下来直接展示代码,因为前面部分都是换汤不换药那么鼠标位置在代码的尾部
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;namespace SlimeGame.Input;public class MouseInfo
{/// <summary>/// 鼠标上一帧的状态/// </summary>public MouseState PreviousState { get; private set; }/// <summary>/// 鼠标当前状态/// </summary> public MouseState CurrentState { get; private set; }/// <summary>/// 将光标位置获取/设置为 Point/// </summary>public Point Position{get => CurrentState.Position;set => SetPosition(value.X, value.Y);}/// <summary>/// 仅获取/设置水平位置/// </summary>public int X{get => CurrentState.X;set => SetPosition(value, CurrentState.Y);}/// <summary>/// 仅获取/设置垂直位置/// </summary> public int Y{get => CurrentState.Y;set => SetPosition(CurrentState.X, value);}/// <summary>/// 获取光标作为 Point 在帧之间移动的量/// </summary>public Point PositionDelta => CurrentState.Position - PreviousState.Position;/// <summary>/// 获取光标在帧之间水平移动的量/// </summary> public int XDelta => CurrentState.X - PreviousState.X;/// <summary>/// 获取光标在帧之间垂直移动的量/// </summary>public int YDelta => CurrentState.Y - PreviousState.Y;/// <summary>/// 指示光标是否在帧之间移动/// </summary> public bool WasMoved => PositionDelta != Point.Zero;/// <summary>/// 获取自游戏开始以来的总累积滚动值/// </summary>public int ScrollWheel => CurrentState.ScrollWheelValue;/// <summary>/// 获取此帧中滚动值的变化/// </summary>public int ScrollWheelDelta => CurrentState.ScrollWheelValue - PreviousState.ScrollWheelValue;/// <summary>/// 无参构造/// </summary> public MouseInfo(){PreviousState = new MouseState();CurrentState = Mouse.GetState();}/// <summary>/// 更新鼠标状态/// </summary>public void Update(){PreviousState = CurrentState;CurrentState = Mouse.GetState();}/// <summary>/// 只要按住指定的按钮,就返回 true/// </summary>/// <param name="button">按键</param>/// <returns></returns>public bool IsButtonDown(MouseButton button){switch (button){case MouseButton.Left:return CurrentState.LeftButton == ButtonState.Pressed;case MouseButton.Middle:return CurrentState.MiddleButton == ButtonState.Pressed;case MouseButton.Right:return CurrentState.RightButton == ButtonState.Pressed;case MouseButton.XButton1:return CurrentState.XButton1 == ButtonState.Pressed;case MouseButton.XButton2:return CurrentState.XButton2 == ButtonState.Pressed;default:return false;}}/// <summary>/// 只要未按下指定的按钮,则返回 true/// </summary>/// <param name="button">按键</param>/// <returns></returns>public bool IsButtonUp(MouseButton button){switch (button){case MouseButton.Left:return CurrentState.LeftButton == ButtonState.Released;case MouseButton.Middle:return CurrentState.MiddleButton == ButtonState.Released;case MouseButton.Right:return CurrentState.RightButton == ButtonState.Released;case MouseButton.XButton1:return CurrentState.XButton1 == ButtonState.Released;case MouseButton.XButton2:return CurrentState.XButton2 == ButtonState.Released;default:return false;}}/// <summary>/// 仅当指定的按钮从上到下更改时,在帧上返回 true/// </summary>/// <param name="button">按键</param>/// <returns></returns>public bool WasButtonJustPressed(MouseButton button){switch (button){case MouseButton.Left:return CurrentState.LeftButton == ButtonState.Pressed && PreviousState.LeftButton == ButtonState.Released;case MouseButton.Middle:return CurrentState.MiddleButton == ButtonState.Pressed && PreviousState.MiddleButton == ButtonState.Released;case MouseButton.Right:return CurrentState.RightButton == ButtonState.Pressed && PreviousState.RightButton == ButtonState.Released;case MouseButton.XButton1:return CurrentState.XButton1 == ButtonState.Pressed && PreviousState.XButton1 == ButtonState.Released;case MouseButton.XButton2:return CurrentState.XButton2 == ButtonState.Pressed && PreviousState.XButton2 == ButtonState.Released;default:return false;}}/// <summary>/// 仅当指定的按钮从 down-up 变为 up 时,仅在帧上返回 true/// </summary>/// <param name="button">按键</param>/// <returns></returns> public bool WasButtonJustReleased(MouseButton button){switch (button){case MouseButton.Left:return CurrentState.LeftButton == ButtonState.Released && PreviousState.LeftButton == ButtonState.Pressed;case MouseButton.Middle:return CurrentState.MiddleButton == ButtonState.Released && PreviousState.MiddleButton == ButtonState.Pressed;case MouseButton.Right:return CurrentState.RightButton == ButtonState.Released && PreviousState.RightButton == ButtonState.Pressed;case MouseButton.XButton1:return CurrentState.XButton1 == ButtonState.Released && PreviousState.XButton1 == ButtonState.Pressed;case MouseButton.XButton2:return CurrentState.XButton2 == ButtonState.Released && PreviousState.XButton2 == ButtonState.Pressed;default:return false;}}/// <summary>/// 设置光标位置/// </summary>/// <param name="x">X坐标值</param>/// <param name="y">Y坐标值</param>public void SetPosition(int x, int y){Mouse.SetPosition(x, y);CurrentState = new MouseState(x,y,CurrentState.ScrollWheelValue,CurrentState.LeftButton,CurrentState.MiddleButton,CurrentState.RightButton,CurrentState.XButton1,CurrentState.XButton2);}
}
第四步:创建手柄输入
手柄无疑是游戏的象征,很多图标都是通过游戏手柄的的图标来表示游戏的意思,我们大家看到手柄的第一反应应该都是游戏吧,手柄部分我们和代码部分一样先创建文件,然后再编写代码
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;namespace SlimeGameLibrary.Input;public class GamePadInfo
{private TimeSpan _vibrationTimeRemaining = TimeSpan.Zero;/// <summary>/// 获取此游戏手柄所针对的玩家的索引./// </summary>public PlayerIndex PlayerIndex { get; }/// <summary>/// 获取此游戏手柄在上一个更新周期内的输入状态./// </summary>public GamePadState PreviousState { get; private set; }/// <summary>/// 获取此游戏手柄在当前更新周期内的输入状态./// </summary>public GamePadState CurrentState { get; private set; }/// <summary>/// 获取一个值,该值指示此游戏手柄当前是否已连接./// </summary>public bool IsConnected => CurrentState.IsConnected;/// <summary>/// 获取此游戏手柄的左控制杆的值./// </summary>public Vector2 LeftThumbStick => CurrentState.ThumbSticks.Left;/// <summary>/// 获取此游戏手柄的右控制杆的值./// </summary>public Vector2 RightThumbStick => CurrentState.ThumbSticks.Right;/// <summary>/// 获取此游戏手柄的左触发器的值./// </summary>public float LeftTrigger => CurrentState.Triggers.Left;/// <summary>/// 获取此游戏手柄的右触发器的值./// </summary>public float RightTrigger => CurrentState.Triggers.Right;/// <summary>/// 为在指定玩家索引处连接的游戏手柄创建新的 GamePadInfo./// </summary>/// <param name="playerIndex">此游戏手柄的玩家索引.</param>public GamePadInfo(PlayerIndex playerIndex){PlayerIndex = playerIndex;PreviousState = new GamePadState();CurrentState = GamePad.GetState(playerIndex);}/// <summary>/// 更新此游戏手柄输入的状态信息./// </summary>/// <param name="gameTime"></param>public void Update(GameTime gameTime){PreviousState = CurrentState;CurrentState = GamePad.GetState(PlayerIndex);if (_vibrationTimeRemaining > TimeSpan.Zero){_vibrationTimeRemaining -= gameTime.ElapsedGameTime;if (_vibrationTimeRemaining <= TimeSpan.Zero){StopVibration();}}}}
现在大部分手柄都有震动功能,这个震动也是手柄玩家的一部分感受,随着我们游戏的开发,那么用户体验也是至关重要的一环,手柄的开发意味着我们我们需要实现震动功能以及一部分我们的操纵杆功能那么这里我只展示一部分输入环节
/// <summary>/// 返回一个值,该值指示指定的游戏手柄按钮是否当前关闭./// </summary>/// <param name="button">要检查的游戏手柄按钮.</param>/// <returns>如果指定的游戏手柄按钮当前处于关闭状态,则为 true;否则为 false.</returns>public bool IsButtonDown(Buttons button){return CurrentState.IsButtonDown(button);}/// <summary>/// 返回一个值,该值指示指定的游戏手柄按钮当前是否处于打开状态./// </summary>/// <param name="button">要检查的游戏手柄按钮.</param>/// <returns>如果指定的游戏手柄按钮当前处于打开状态,则为 true;否则为 false.</returns>public bool IsButtonUp(Buttons button){return CurrentState.IsButtonUp(button);}/// <summary>/// 返回一个值,该值指示是否刚刚在当前帧上按下了指定的游戏手柄按钮./// </summary>/// <param name="button">要检查的游戏手柄按钮.</param>/// <returns>如果仅在当前帧上按下指定的游戏手柄按钮,则为 true;否则为 false.</returns>public bool WasButtonJustPressed(Buttons button){return CurrentState.IsButtonDown(button) && PreviousState.IsButtonUp(button);}/// <summary>/// 返回一个值,该值指示是否刚刚在当前帧上释放了指定的游戏手柄按钮./// </summary>/// <param name="button">要检查的游戏手柄按钮</param>/// <returns>如果指定的 Gamepad 按钮刚刚在当前帧上释放,则为 true;否则为 false.</returns>public bool WasButtonJustReleased(Buttons button){return CurrentState.IsButtonUp(button) && PreviousState.IsButtonDown(button);}/// <summary>/// 设置此游戏手柄的所有电机的振动./// </summary>/// <param name="strength">振动强度从 0.0f(无)到 1.0f(全).</param>/// <param name="time">振动应发生的时间量.</param>public void SetVibration(float strength, TimeSpan time){_vibrationTimeRemaining = time;GamePad.SetVibration(PlayerIndex, strength, strength);}/// <summary>/// 停止此游戏手柄的所有电机的振动./// </summary>public void StopVibration(){GamePad.SetVibration(PlayerIndex, 0.0f, 0.0f);}
OK合并两者代码,我们的代码就算是成功创建了
第五步:InputManager 输入管理
这个是今天的总结代码,前面的内容都只是在原本的基础上初步封装了部分代码,那么我们需要的的是将所有的输入管理封装成输入管理器,后面我们进行游戏开发的时候才比较轻松,那么我们直接开始编写代码吧!
using Microsoft.Xna.Framework;namespace SlimeGameLibrary.Input;public class InputManager
{/// <summary>/// 获取键盘按键信息/// </summary>public KeyBoardInfo Keyboard { get; private set; }/// <summary>/// 获取鼠标按键信息/// </summary>public MouseInfo Mouse { get; private set; }/// <summary>/// 获取手柄按键信息/// </summary>public GamePadInfo[] GamePads { get; private set; }/// <summary>/// 无参构造/// </summary>/// <param name="game">此 input manager 所属的游戏.</param>public InputManager(){Keyboard = new KeyBoardInfo();Mouse = new MouseInfo();GamePads = new GamePadInfo[4];for (int i = 0; i < 4; i++){GamePads[i] = new GamePadInfo((PlayerIndex)i);}}/// <summary>/// 更新按键信息/// </summary>/// <param name="gameTime">游戏时钟</param> public void Update(GameTime gameTime){Keyboard.Update();Mouse.Update();for (int i = 0; i < 4; i++){GamePads[i].Update(gameTime);}}
}
当然了很简单,我们今天敲的所有代码都不是很难稍微理解以下都是可以的,接下来的内容都需要创建一些其他的必须的游戏框架代码我们今天就到这里结束了!
结语
好久没更新博客了,最近不仅要考驾照,而且最近就是整个人老是想看手机,我写完这个博客打算把手机上的B站给删了天天接触这个脑子都乱了,包括自己项目都没咋整我的任务还很艰巨,我要立一个Flag:我要在暑假期结束之前把我的类星露谷游戏开发完成,这个是我对我自己下的一个挑战,我将星露谷解包到Unity我会用Unity实现一个大体相同的Demo(当然仅供学习使用)