前言
最近灵感枯竭,精力不再,想什么都想不出来,于是就想随便学点什么东西。回想起这段时间真的是被游戏程序框架搞郁闷了,因为看不到Godot的全部(其实看得到,但是懒得看,看了也动不了),然后加上自己才疏识浅,想破脑袋也想不出能很好适配Godot的程序框架。
于是动了不用引擎做游戏的念头。其实我也早就这么干过了,不过当时是用C++写的,写得一坨,后面当然还是老老实实用游戏引擎开发。直到我玩了蔚蓝Celeste,在游戏初次启动时,我不经意间瞟到Steam正在帮我下载XNA的一些支持,这玩意也就是蔚蓝使用的游戏框架。
这就是我第一次遇见它,然后感兴趣搜了一下发现是个做游戏的玩意,但是已经很老了,而且停止维护了。
再后来,也就是最近几天,我一直在网上高强度搜索游戏程序框架企图收集“灵感”。结果就在知乎的某个评论里看到了XNA这个字眼,然后更关键的是它的“孩子”——FNA。
FNA很有意思,我看着像是一个孤傲的开发者捍卫自己的爱好而诞生的产物,具体一点可以直接去FNA的官网看看。总而言之FNA尽可能保留了和XNA相同的API,但是底层做了不少重写,可以很好帮助XNA项目移植。
其实XNA还有另一个“孩子”,MonoGame。而且似乎比FNA要更出名,虽然FNA更XNA名字接近,但是内在改了不少,主打一个叛逆,尤其是在XNA原本那个被FNA作者怒喷的ContentPipline上面,这玩意简单来讲就是用来管理游戏资源的。MonoGame的选择是继承并发扬XNA的衣钵,而FNA则是尽力摆脱ContentPipline,转向支持原生的文件格式。我也是因为这个在MonoGame和FNA两者之间选择了后者。
为什么是FNA?首先因为用C#习惯了,不想用C++,其次是引擎用烦了,想找罪受。好吧,其实本质目的是想看看自己的一些想法能不能实现,包括但不限于一些游戏程序设计理念,所以不想受引擎限制,转而尝试使用轻量级的游戏框架。
FNA基操
1.获取
FNA的创始人非常有个性,不愿意用Nuget包,所以大家需要自行去Github上扒下来。这个过程还是比较简单的,略了。
值得一提的是,FNA建议大家不要build库,直接甩到解决方案里面跟着一起build。我个人也是觉得最好直接把项目复制(克隆)一份到自己项目的文件夹下,不仅因为可以看源码,还能更好的调整各个csproj之间的引用,这一点很重要,因为跟其他库的使用有关,比如后面会介绍的FontStashSharp库。
2.创建项目
我用的是VSCode开发,没有用到VS,不过如果连VSCode都能配置好,那VS肯定更简单好吧。
首先,新建文件夹。
然后按照个人喜欢准备好项目结构。我个人是喜欢这样子:这是一个VSCode工作区,我把FNA一整个项目,我的实际项目,还有引用FNA的其他项目,放在一同级目录。
然后在一个解决方案中,添加这些项目。如果你不知道解决方案到底有什么用,那么恭喜你,我也不知道。我只知道它是用来方便项目管理的,就像我们这种有多个项目的情况。
总之先把这些个项目都加到解决方案里面吧。 可以直接控制台:
dotnet sln add <csproj_path>
也可以在VSCode中手动添加引用,就像在VS里一样(等等,你应该不会没装.NET的拓展吧?!)。
需要注意的是,现在.NET8.0时代我们需要添加的FNA项目是FNA.Core.csproj。其他的是用于低版本或者VS的。
最后记得在你的游戏项目中引用 FNA.Core.csproj:
可以在.csproj里加上几个项(Item):
<ItemGroup>
<ProjectReference Include = <FNA.Core.csproj相对于该项目的路径> />
</ItemGroup>
当然同样可以可视化操作:
然后项目就配置好了!试着写几行代码:
using Microsoft.Xna.Framework;
namespace MyGame
{
public class MyGame : Game
{
private GraphicsDeviceManager gdm;
public MyGame()
{
gdm = new GraphicsDeviceManager(this);
gdm.PreferredBackBufferWidth = 1280;
gdm.PreferredBackBufferHeight = 720;
Content.RootDirectory = "res";
}
[STAThread]
public static void Main()
{
using MyGame game = new();
game.Run();
}
}
}
启动!
然后就崩了。
因为你还没有添加FNA用到的Bin:比如x64用到的是下面这几位。
你可以自己准备,也可以很逆天地从创始人的个人网页上扒下来。
具体操作只要跟着FNA的官方文档进行就好了,我这里只做简述。
在把所需的bin放在输出目录后,再次运行不出意外的话应该没什么问题了,吧。
3.运行与调试
VSCode的调试比较麻烦,如果它提示你没有加载C#项目的话,可以用下面这个指令:
生成一下用于调试的配置。
当然你可以直接Build!甚至publish!
dotnet build
dotnet publish
更复杂的指令请自己去参考文档吧,因为我实在记不清了!
FNA使用体验
1.非常不推荐
以下人群适合使用FNA开发:
1.经验丰富的高技术开发者;
2.喜欢《从零开始的游戏世界生活》的开发者(轮子人)。
3.自讨苦吃的白痴;
4.具有SM倾向的受虐狂;
我是第3个。
为什么这么说呢?因为FNA真的很难上手,尤其新手开发者。首先一个很重要的问题:
FNA没有官方API文档,是的,它只有官方文档,没有API文档。或者说没有“FNA的文档”,
因为FNA是XNA的延续,我们可以参考XNA的文档:
Writing Game Code | Microsoft Learn
这只是其中一个,因为实在是太太太难找到了,XNA是十几年前的东西了,所以FNA的使用者在FNA官方看来都是一些老手了,它们在FNA文档里也提到让新手开发者放弃使用FNA,所以大家见仁见智,如果是新手的话还是得听劝,节省时间。
2.清晰又折磨
FNA的框架很清晰,没什么难用的,再配合上C#的简洁语法。在某种程度上也是有开发优势的。前提是你能忍受没有可视化编辑,没有调试,没有UI控件,没有字体支持......
真的是给了多大的自由就有多大的限制。
好就好在,这回的游戏逻辑可就真正的由你做主了,每一帧怎么渲染,物理怎么更新都得自己设计好,没有了游戏引擎的便利同时失去了它们的冗杂,这大概就是轻量级的魅力吧。
我这里并不打算多讲怎么写游戏逻辑,FNA已经讲清楚了,能用上FNA的人应该都有自己的想法。
3.开发优势
因为FNA的引用方式是直接COPY到项目文件夹里,所以我们可以很方便的查看,修改源代码,这点对于我这种控制狂来讲是福音。
虽然平时不喜欢改源码,不过想到这只是个几十兆的小项目,随便复制随便改,大不了就再从Github上再扒下来一次。所以可以畅快的乱改源码,再加上C#编译优势,应该是可以比C++编译好受些,不会太慢的。
所以FNA开发优势最主要的就是一览无余,自由自在,同样也是最大劣势。
FNA使用技巧
1.FontStashSharp
这是一个C#封装的库,主要用于字体渲染。为什么我要提一嘴这个呢,因为我在使用FNA是发现FNA不支持ttf字体加载啊啊啊,所以找了半天,找到了这个库,做游戏没有字体实属难受。
这个库不是专门用于FNA的,但是它里面有专门的一个csproj可以拓展FNA。再具体一点可以自己去GitHub上看看。
具体的使用方法就是和FNA一样把整个文件夹扔到游戏项目文件夹里,跟FNA不同,因为需要拓展的是FNA的内容,所以这个csproj必须持有对FNA的引用,比如这里它需要引用FNA.Core.csproj,才能正常运行,不然就等着一顿报错吧。
一定一定要注意设置好正确的引用!!!
可以直接点开它们的csproj看看它们到底有没有引用到FNA,如果没有,可以直接修改,和上面说的一样,反正都是COPY过来的,改了也不心疼。
所以其实怎么拜访这些文件夹都无所谓,只要确定好引用关系就好了。
2.资源管理
FNA开发需要指定一个文件夹作为资源文件夹,导出(这里应该说build或publish)后的exe也需要能找到这个文件夹。所以我们需要一个好的手段来协助我们导出资源,当然你也可以每次build完后复制粘贴。
这里我推荐的是直接配置项目文件:
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
<ProjectReference Include="..\FontStashSharp-main\src\XNA\FontStashSharp.FNA.Core.csproj" />
<Content Include = "*.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Update = "res\**">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
前两个Item是项目引用,Content和None都是MSbuild预留Item,重点是里面的内容,Include = " *.dll' ",意味着包含所有.dll文件,然后 <CopyToOutputDirectory> 故名思意就是复制到输出目录。
具体的含义请参考官方文档。总而言之,我们可以通过这种手段协助导出项目时资源跟依赖一起导出,就像游戏引擎一样。
3.导出配置
.NET的导出还是挺有说法的,下面是我认为比较简单的一种适合游戏应用的导出属性配置:
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
<PublishAot>true</PublishAot>
<DebugType>None</DebugType>
<DebugSymbols>false</DebugSymbols>
<Optimize>true</Optimize>
</PropertyGroup>
上面的OutType,如果是控制台程序Exe,发布后打开游戏仍会打开控制台,如过你像保留到导出后DEBUG用,那可以给PropertyGroup加条件:
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
然后构建时会根据条件变化。
这里我还用到了AOT,也是非常适合C#游戏程序,也是借助.NET的优势了。
4.修改图标
如果你也有Godot开发经历,肯定会对Rcedit有印象,这个东西是一个开源工具,主要用于修改Windows的PE等。
Godot就是用它来修改游戏的图标的,哦对了,还有一个工具ImageMagick,可以用来生成.ico文件。两者结合起来就可以像Godot一样修改我们exe的图标了。
可以试着写一个小程序规范化这个过程,就像Godot为我们做的那样。
结语
FNA是真的新手劝退,我之前没有死磕FNA现在看来相当明知,只有在了解足够的游戏引擎之后才会想着自己实现一些东西,所以适合对自己游戏需求比较清晰,对游戏引擎理解比较全面的开发者。
反正不是我。