ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MonoGame 跨平台游戏开发:30 分钟写出一个能动的方块

MonoGame 跨平台游戏开发:30 分钟写出一个能动的方块

MonoGame 跨平台游戏开发:30 分钟写出一个能动的方块

【免费下载链接】MonoGameOne framework for creating powerful cross-platform games.项目地址: https://gitcode.com/gh_mirrors/mo/MonoGame

你手上有一个很小的创意:一个方块在窗口里移动,按方向键换方向。听起来半小时能写完,但"写出来"和"在 Windows、Mac、Linux、手机上都能跑"之间,还隔着环境、渲染和平台差异这些琐事。MonoGame 跨平台游戏开发把这部分收进了框架:你用 C# 写游戏逻辑,它负责窗口、图形后端和输入。本文以"让方块动起来"这条最小任务为主线,把环境搭建、游戏循环、渲染和模型加载串成一条能跟着做的路径。

🛠️ 搭建 MonoGame 开发环境:两样工具加一个模板

先装 .NET SDK(6.0 及以上)和 Git。框架源码可以拉下来备用,看实现、调后端时会用到:

git clone https://gitcode.com/gh_mirrors/mo/MonoGame

游戏项目用模板生成,一条命令得到主类、内容管道和配置文件:

dotnet new monogame -o MyFirstGame cd MyFirstGame

生成后只需要认识三个东西:Game1.cs是游戏主类,Content/放纹理、模型、音频等资源,Content.mgcb是内容管道配置,决定每个资源如何被处理。IDE 选 Visual Studio、Rider 或 VS Code 都行,差异不在框架侧。

跨平台不是最后再做的事,而是从第一个项目起就成立的:同一套 C# 代码靠各平台自己的 csproj 分别编译。当前支持范围和图形后端大致如下:

目标平台图形后端说明
Windows 10+OpenGL / DirectX 10DX12 为预览实现
LinuxOpenGLVulkan 为实验性实现
macOS 13+OpenGL—
Android 6+ / iOS 12.2+OpenGL移动端统一走 GL
PS4/PS5、Xbox、Switch各平台 SDK需注册开发者

🔁 读懂游戏循环:Initialize、Update、Draw 各管一件事

所有玩法代码都挂在Game基类的三个回调上,框架按固定节奏调用它们:

  • Initialize():只执行一次,加载内容、注册组件都放这里;
  • Update(GameTime):每帧跑一次逻辑,处理输入、改状态;
  • Draw(GameTime):每帧渲染一次,所有画画面的代码放这里。

GameTime里最常用的是ElapsedGameTime——上一帧到这一帧隔了多久。移动角色时乘上它,而不是"每帧走固定像素",方块在 30 帧的旧设备和 144 帧的显示器上才会一样快:

protected override void Update(GameTime gameTime) { float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; position.X += moveDir * speed * dt; }

默认是 60 帧定步长(IsFixedTimeStep为 true),逻辑节奏稳定,这是它比手写while(true)循环省事的地方。

🎮 实现最小 demo:读输入、画方块

输入侧,Keyboard.GetState()返回"这一帧每个键的状态",直接判断即可。鼠标和游戏手柄是同构的 API(Mouse.GetState()、GamePad.GetState(PlayerIndex.One)),将来移植到手机上换成触摸 API 就行:

var kb = Keyboard.GetState(); moveDir = kb.IsKeyDown(Keys.Right) ? 1 : kb.IsKeyDown(Keys.Left) ? -1 : 0;

窗口大小由GraphicsDeviceManager控制,PreferredBackBufferWidth/Height决定初始分辨率,顺手把AllowUserResizing打开,避免用户在桌面上拖窗口时崩溃。

2D 渲染用SpriteBatch:它在Begin()和End()之间把多次Draw合批成一次 GPU 调用,所以一整屏的精灵都该放进同一个批次:

spriteBatch.Begin(); spriteBatch.Draw(block, position, Color.White); spriteBatch.End();

到这里 demo 已经完整:窗口开了、方向键能动、方块每帧被画出来。下一步只是把"方块"换成带纹理的物体和 3D 模型。

🧊 把方块换成 3D:贴纹理、加载模型

把纹理贴到 3D 模型上

3D 部分不必从顶点缓冲写起。最简路径是:用内容管道把图片处理成Texture2D,赋给BasicEffect的Texture属性并打开TextureEnabled,一个带贴图的光照平面就出来了:

加载并旋转一个模型

复杂模型走Content.Load<Model>,遍历Meshes把每个 part 的Effect换成自己的材质,然后一行model.Draw(world, view, projection)。每帧让world矩阵随时间旋转,模型就"动"起来了:

🕳️ 排查新手最容易卡住的三个地方

内容管道不认识新资源。运行时加载的.xnb是内容管道的产物,不是原始图片。加了资源忘了重新构建内容,Content.Load就会报"找不到"——这是新手第一个坑,先确认管道构建过了再查代码。处理器的实现在 MonoGame.Framework.Content.Pipeline/。

窗口和分辨率对不上。只改了PreferredBackBufferWidth/Height却没同步视口,或设计分辨率与窗口宽高比不一致,画面就会拉伸。改窗口尺寸前先看GraphicsDeviceManager现有的几个属性,别自己另算。

平台差异没提前留口子。键盘逻辑写死的话,手机端没法操作。把输入判断集中到一个方法里,桌面用键盘、移动用触摸,用条件编译分流。3D 场景里东西被挡住或矩阵错乱时,把光栅化状态临时切到线框(RasterizerState.WireFrame),问题通常一眼可见:

🚀 跑通之后:把它推进成一个小游戏

demo 跑通后,自然的延伸方向有四个:给模型接骨骼动画、加物理(Farseer 或 Box2D)、用SoundEffect播音效、性能上把精灵合批并让纹理进图集。

先别急着上引擎,今晚就做一件事:给方块加跳跃——按空格给一个向上的初速度,在Update里用ElapsedGameTime做重力积分,落地归零。你会顺手把 dt 用法和地面/空中状态切换都练到。想对照实现细节时,核心代码在 MonoGame.Framework/,官方测试项目和参考截图在 Tests/,改之前先跑一遍GameTest系列用例,确认你对每个回调行为的预期和框架一致。

【免费下载链接】MonoGameOne framework for creating powerful cross-platform games.项目地址: https://gitcode.com/gh_mirrors/mo/MonoGame

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进