Thursday, June 15, 2017

SDL2 + C# The Quick Start Guide

The primary goal of this Tutorial is to introduce C# developers to SDL2 development. The secondary goal is to provide existing SDL and SDL2 developers with a SDL2+C# quick start guide. I recognize that this is pretty niche, which is why this might be the only tutorial on the subject that doesn’t require some fat middleware framework.

What does this tutorial demonstrate?

Using SDL2 with C# obviously, but, when it’s all over, you will have a window that can display images, play audio and accept input, and this is a great combination for game development.

What development tools am I using?

I am using Windows 7 with Visual Studio 2013, because that’s what I use, but don’t worry, my projects run on MonoDevelop and the mono runtime without modification.

What is SDL2?
“Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.”

SDL2 supports deployment on the Windows, Mac, Linux, IOS and Android platforms.

There are bindings for a lot of languages, but for this tutorial’s purposes, I’m only discussing C#.

Get Ready:

First, you need the SDL2-CS c# bindings.
https://github.com/flibitijibibo/SDL2-CS

Second, you need SDL2 runtime binaries. I’m using the 32bit runtime binaries.
https://www.libsdl.org/download-2.0.php

You don’t need the SDL2 development libraries or source.

Third, you need SDL_image runtime binaries.
https://www.libsdl.org/projects/SDL_image/

Fourth, you ned the SDL_mixer binaries.
https://www.libsdl.org/projects/SDL_mixer/

Get started:

1. Create your new project, I recommend using a console application for your first project, but windows application will also work, and other things might to.

2. Copy the C# *.cs files contained within the SLD2-CS-master.zip to a folder within your project, mine is called “SDL BINDINGS”.

3. Edit your project properties, under the Build tab, enable “Allow Unsafe Code”, you need to do this, because you are using native DLL libraries. Now Build your project.

4. Copy the SDL2.dll, libpng16-16.dll, SDL2_image.dll, SDL2_mixer.dll, finally the zlib1.dll into your bin directory.

*Important:
Make sure that you force your application to run either 32bit or 64bit depending on the dll packages you download, otherwise you could get the following error: "System.BadImageFormatException".

Get Coding:

Once I do this, I don’t want to ever do it again, so I’ll be making an object that manages everything for me.

1. Create and new class called CORE.

2. Add the following at the top.
 using SDL2;  

3. Make some public variables.

 //go!  
 public bool bRunning = true;  
 public IntPtr win;  
 public IntPtr ren;  

4. Time to initialize SDL2 and open an SDL2 window. I’m going to skip error reporting, that isn’t the goal here. Inside core, you’re going to need some stuff. Inside core’s constructor, add the following.

 //Initialize SDL2  
 public CORE()  
 {  
      SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO);  
      win = SDL.SDL_CreateWindow("SDL2 Sharp", 50, 50, 1280, 720, SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);  
      ren = SDL.SDL_CreateRenderer(win, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED | SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);  
 }  

5. Now create another variable.

 SDL.SDL_Event systemEvent;  

6. Create the variable’s companion function.

 //This is important, and must be run every frame.  
 public void Events()  
 {  
      while(SDL.SDL_PollEvent(out systemEvent) != 0)  
      {  
           //This is an SDL2 escape event  
           if(systemEvent.type == SDL.SDL_EventType.SDL_QUIT)  
           {  
                bRunning = false;  
           }  
      }  
 }  

7. You also need a present function.

 //This must be called after you finish drawing, otherwise you won’t see anything.  
 public void Present()  
 {  
      SDL.SDL_RenderPresent(ren);  
 }  

8. Now if you were to run your application, it would start and quickly exit, which isn’t what we want. You need to create an SDL window and a system loop.

We need to add somethings to Program.cs.

Update Main to look like the following.

 static void Main(string[] args)  
 {  
      CORE myCore = new CORE();  
      while(myCore.bRunning)  
      {  
           //give windows some time as well as capture SDL events.  
           myCore.Events();  
           //Your code goes here.  
           //Display what you have drawn.  
           myCore.Present();  
      }  
 }  

9. Now that you have an SDL window and a system loop, you’re ready to start drawing pictures, or playing sound, or whatever. Let’s quickly cover making a simple drawing tool.

Back inside your CORE class. Create a LoadTexture function.

 public IntPtr LoadTexture(string filepath)  
 {  
      return SDL_image.IMG_LoadTexture(ren, filepath);  
 }  

Now create another function called DrawSprite.

 public void DrawSprite(IntPtr myTexture, SDL.SDL_Rect sourceRect, SDL.SDL_Rect destinationRect)  
 {  
      SDL.SDL_RenderCopy(ren, myTexture, ref sourceRect, ref destinationRect);  
 }  

10. Great, that’s all over with, now lets draw an image.

Add an image to your projects bin folder for your application to load. I’ll be loading a 128x128 Mulletman Icon.

If, for whatever reason, you don't have an image or a sound, I included both of the files I am using in the project zip linked at the bottom.

After you initialize the CORE object, add the following.

 IntPtr myTexture = myCore.LoadTexture("mmIcon.png");  

Inside your loop, add the following before your myCore.Present() call.

 myCore.DrawSprite(  
      myTexture,  
      new SDL2.SDL.SDL_Rect() { x = 0, y = 0, w = 128, h =128},  
      new SDL2.SDL.SDL_Rect() { x = 0, y = 0, w = 128, h = 128});  

Now run your application, you should see your image being rendered in the top left corner. If not and there aren’t any errors, there are some causes, but most likely you made a typo in your image loading filepath.

11. What about audio? Lets do that right now.

Add the following to your CORE constructor after SDL_CreateRenderer

 SDL_mixer.Mix_OpenAudio(  
      SDL_mixer.MIX_DEFAULT_FREQUENCY,  
      SDL_mixer.MIX_DEFAULT_FORMAT,  
      2,  
      128);  

Run your application, weird crash, something about vs host. Weird.
Go to your bin directory and run the compiled exe. Works fine. Weirder.
To fix this, go to your project settings, go to the Debug tab, and select “Enable native code debugging”, now your project will launch from the editor without error.

12. We need to add a way to load and play sounds. Cool, I know.

Inside your CORE class, add the following functions.

 public IntPtr LoadSound(string filepath)  
 {  
      return SDL_mixer.Mix_LoadWAV(filepath);  
 }  
 public void PlaySound(IntPtr mySound)  
 {  
      SDL_mixer.Mix_PlayChannel(-1, mySound, 0);  
 }  

13. We need a sound to play. I’ll be playing a sound sampled from one of my games. Go to your project’s bin/debug directory and add a *.wav

Go back to where you loaded an image file, but before the loop and add the following.

 //Load the sound.  
 IntPtr mySound = myCore.LoadSound("PlayerJump.wav");  
 //Play the new sound!  
 myCore.PlaySound(mySound);  

Now when your application starts, you will hear your sound play once.

14. Now we have images and sound effects, but we need a way to input instructions. This can be done a lot of good and bad ways. We will do it the simplest way, well, sort of, it’s the flattest way. Writing a complete input system is way out of the scope of this tutorial, so I’ll demo simple gamepad input.

Inside of your core class, after you initialize Mix_OpenAudio, add the following.

 SDL.SDL_InitSubSystem(SDL.SDL_INIT_GAMECONTROLLER);  

NOTE* For those of you who have used SDL2before, I’m initializing the game controller as a subsystem rather than inside of the SDL_Init arguments, because adding the game controller mysteriously crashes SDL with the C# bindings and I’m not super interested in figuring out way, I’m satisfied with the work around.

Back inside your Program.cs’s main function.

After you loaded the sound, lets set the controller with the following.

 IntPtr gc = SDL2.SDL.SDL_GameControllerOpen(0);  

This function will use whatever controller you have at index 0, in my case, index 0 is the only controller I have plugged in.

Now, inside your loop, lets do something obnoxious to test gamepad input. Add the following.

 if(SDL2.SDL.SDL_GameControllerGetButton(gc, SDL2.SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) == 1)  
 {  
      myCore.PlaySound(mySound);  
 }  

If everything worked correctly, every time you press A, your ears are blasted with noise. Way to go.

-----

In conclusion, you now have a very compact C# SDL2 system capable of displaying images, playing audio and capturing gamepad input. If there are any problems, please feel free to post a comment.

... And if you scrolled to the bottom because you don’t like to read. I have attached a zipped project file. And, if that’s not enough, I have also attached a my LudumDare 38 Compo entry which would have seriously benefited from this tutorial, because for some crazy reason I decided to learn how to do all of this during a 48 hour game jam.

The Quick Start Guide's Project File
https://www.dropbox.com/s/7q7hb42rza591yv/SDL2SharpTutorial.zip?dl=0

Project File for My Ludum Dare 38 Compo Entry
https://www.dropbox.com/s/lq4ltbbtqy1ls6l/Mulletman%20and%20the%20Molemen%20-%20Source.zip?dl=0


No comments: