在mac环境下用c#进行SDL2游戏开发


虽然笔者是在Rider下实现的,不过VS下也是可以用的

最近为了折腾这SDL2,可是废了点时间,尝试用C/C++/Go/C#进行开发,结果发现:

  • C/C++环境好设置,但是cmake配置麻烦(对于没用过的我来说),而且我受够了引入文件总要include
  • Go最省心,go mod包管理永远的神!sdl2库的api封装也是用起来最舒服的
  • C#在windows下很方便,但是切到mac就各种毛病,奈何C#做游戏太香了,写好了核心类库,以后port到unity也方便。

C#在mac下用SDL2开发游戏,在油管和各种论坛上有好多帖子,但是没有一篇能让我真正运行起来,看到窗口的,着实有点劝退...

几番探索下,我终于成功了,这里给大家分享下,希望至少能省下下大伙半天时间...

话不多说,开始吧!

创建项目

咱们先不依赖任何编译器,这样好多参数也就不用选了,方便大家有统一的环境。

咱们用命令来创建项目

创建项目目录

项目名称SDL2_TEST

$ mkdir SDL2_TEST
$ cd SDL2_TEST

用命令创建项目

$ dotnet new console

结果发现报错,该命令不存在,可是咱们可是安装了dotnet的,咋回事?

原来这个命令躲在其他地方,我们要链接下

$ ln -s /usr/local/share/dotnet/dotnet /usr/local/bin/

再次运行,发现可以了。

$ dotnet new console

利用homebrew安装sdl2

其实我另一篇文章提到了怎么做,一客不烦二主,直接一篇文章搞定吧!

依次执行下面的命令就好了

  • 安装国内镜像的Homebrew,一路往下YES就行
$ /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
  • 安装sdl2
$ brew install sdl2 sdl2_image sdl2_mixer sdl2_net sdl2_ttf
  • 查看下sdl2库安装情况
$ brew info sdl2

根据安装的版本不同,可能得到不同的信息,下面是我的

sdl2: stable 2.0.16 (bottled), HEAD
Low-level access to audio, keyboard, mouse, joystick, and graphics
https://www.libsdl.org/
/usr/local/Cellar/sdl2/2.0.16 (91 files, 5.4MB) *
  Poured from bottle on 2021-08-27 at 05:51:01
From: https://mirrors.ustc.edu.cn/homebrew-core.git/Formula/sdl2.rb
License: Zlib
==> Options
--HEAD
	Install HEAD version
==> Analytics
install: 107,393 (30 days), 219,217 (90 days), 957,010 (365 days)
install-on-request: 10,041 (30 days), 19,802 (90 days), 91,778 (365 days)
build-error: 0 (30 days)

下载SDL2-CS库

上面装的是sdl2库本尊,下面我们需要安装调用其功能的C#库。

SDL2-CS是一个c#中SDL2库的一个封装,直接用nuget下载这个库是可行的,不过咱们这里直接引入源文件:

接着上文,我们现在处于项目根目录中

$ mkdir sdl
$ cd sdl

用wget下载文件

这里你直接把SDL2-CS项目中的src目录下文件下载到我们根目录也是可以的

$ wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/SDL2.cs
$ wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/SDL2_image.cs
$ wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/SDL2_mixer.cs
$ wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/SDL2_ttf.cs
$ wget https://github.com/flibitijibibo/SDL2-CS/raw/master/src/LPUtf8StrMarshaler.cs

回到项目根目录,尝试着运行一下:

$ cd ../
$ dotnet run

收到报错不安全代码只会在使用 /unsafe 编译的情况下出现

启用unsafe

上面的报错,咱们启用unsafe就好了。

编辑SDL2.csproj文件,增加下面的文本

SDL2.csproj这个文件,你用rider默认看不到的,简单点就用vscode或者任意编译器打开项目,就能看到这个文件了

    true

这样以来文件内容就变成了



  
    Exe
    net5.0
    true
  


项目转为dotcore

我个人习惯用dotcore开发,所以可以修改上面的SDL2.csproj文件



  
    Exe
    netcoreapp3.1
    true
  


编辑Program.cs

内容修改为

using System;
using SDL2;

namespace SDL2_TEST
{
    class Program
    {
        static void Main(string[] args)
        {
            SDL.SDL_Init(SDL.SDL_INIT_VIDEO);
            SDL.SDL_Quit();
        }
    }
}

运行一下

$ dotnet run

看到报错,说是不存在SDL2.dll

Unhandled exception. System.DllNotFoundException: Unable to load shared library 'SDL2.dll' 

下载dll

于是咱们从官网下载了SDL2.dll,并且放到了根目录,还把属性设置为始终复制,然而,还是没用。

于是求助了万能的谷歌,到官网找到了这样的一个帖子how-to-setup-sdl-with-c-bindings-in-visual-studio-for-mac,
文字内容很多,咱们挑简要的说,意思就是 :

  • 咱们在mac下用的netcore 3.1项目用的dll不应该是SDL2.dll,应该是SDL2.dylib

那么SDL2.dylib在哪儿呢?

寻找SDL2.dylib

这个文件是存在的,只不过不叫这个名字。

我们去这里找:

$ cd /usr/local/lib
$ open .

找到libSDL2.dylib, 右键,显示原身,这文件就是我们要的。

复制到根目录

我们直接在原位置修改名字不合适,我们可以复制一份到项目根目录再修改:

  • 将上面的文件复制到项目的根目录,然后设置属性为始终复制

再次尝试运行,发现还是失败?!

$ dotnet run

同时寻找dll和dylib

这次不卖官子了,直接查看源代码sdl/SDL2.cs发现这么一行

		private const string nativeLibName = "SDL2.dll";

写死了寻找dll文件,我们可以去掉这个dll后缀,这样既可以寻找dll文件,也可以寻找dylib了。

我们改成

		private const string nativeLibName = "SDL2";

再次运行,成了!

$ dotnet run

但是什么窗口都没看到,不行,必须看到窗口才算结束!

我们要看到窗口!

别说了,把Program.cs内容换成这个,再次运行就看到窗口了

using System;
using SDL2;

namespace SDL2_TEST
{
    class Program
    {
        static void Main(string[] args)
        {

            SDL.SDL_Init(SDL.SDL_INIT_VIDEO);

            IntPtr window = SDL.SDL_CreateWindow("测试窗口",
                SDL.SDL_WINDOWPOS_CENTERED,
                SDL.SDL_WINDOWPOS_CENTERED,
                640,
                320,
                SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE);

            IntPtr renderer = SDL.SDL_CreateRenderer(window, -1, 0);

            SDL.SDL_RenderSetScale(renderer, 10.0f, 10.0f); // Render a pixels 10 times bigger than they actually actually are

            SDL.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
            SDL.SDL_RenderClear(renderer);
            SDL.SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
            for (int x = 0; x < 64; x++)
            {
                SDL.SDL_RenderDrawPoint(renderer, x, x / 2);
            }
            SDL.SDL_RenderPresent(renderer);


            // "Game Loop"
            SDL.SDL_Event e;
            bool quit = false;
            while (!quit)
            {
                while (SDL.SDL_PollEvent(out e) != 0)
                {
                    switch (e.type)
                    {
                        case SDL.SDL_EventType.SDL_QUIT:
                            quit = true;
                            break;
                        case SDL.SDL_EventType.SDL_KEYDOWN:
                            switch (e.key.keysym.sym)
                            {
                                case SDL.SDL_Keycode.SDLK_q:
                                    quit = true;
                                    break;
                            }
                            break;
                    }
                }
            }

            SDL.SDL_DestroyRenderer(renderer);
            SDL.SDL_DestroyWindow(window);
            SDL.SDL_Quit();
        }
    }
}

这次是真的成了!

最后一点优化

把所有类库放在根目录着实有点不雅,我们要做优雅的程序员,要学会目录管理,于是,我们新建目录lib,把相关的dlldylib都放到里面去。

项目一样可以运行!目录还合理一些了,何乐而不为?

最后项目目录在Rider中如下所示:

  • 最上面的SDL名字可以无视,不影响运行的,如果按照我的步骤做,你可能叫做SDL2_TEST
  • 我多了个README.md文件,这是我自己建的,因为我凡事喜欢做笔记...

留个尾巴,自我拓展

这个例子只用到了sdl2核心库,但是sdl2_img这些扩展库还没用到,到时候再遇到报错,相信你也可以举一反三,一一解决吧?