Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware.
This project provides a lightweight wrapper around SDL2 which makes its C-based API easy and intiutive to use from Swift.
Libraries currently supported are:
- SDL2
- Image
- Mixer
- TTF
The SwiftSDL.xcodeproj
provides two working examples of how to use the wrapper:
DemoSDL2
: includes a game-loop, a rudimentary scene graph, and animating sprite textues;CreateWindowDemo
: includes the bare minumum for displaying (and rendering) a native window.
Use Homebrew
to install the SDL2
dependencies:
# Install Deps
$ brew install sdl2 sdl2_image
# Pull Repo
$ git clone https://github.com/KevinVitale/SwiftSDL.git
# Check your SDL2 paths and update 'shim.h' files if Xcode complains 👀
$ sdl2-config --libs --cflags
# Run DemoSDL2
$ cd SwiftSDL
$ make && make run
Note: by default,
make run
will run the DemoSDL2 demo.
As an additional bonus, SwiftSDL provides SDL.Run { engine in /*...*/ }
,
which gets you a game loop quickly. Here's an example:
import Foundation
import CSDL2
import SwiftSDL2
try SDL.Run { engine in
// Start engine ------------------------------------------------------------
try engine.start(subsystems: .video)
// Create renderer ---------------------------------------------------------
let (window, renderer) = try engine.addWindow(width: 640, height: 480)
// Handle input ------------------------------------------------------------
engine.handleInput = { [weak engine] in
var event = SDL_Event()
while(SDL_PollEvent(&event) != 0) {
if event.stype == SDL_QUIT.rawValue {
engine?.removeWindow(window)
engine?.stop()
}
}
}
// Render ------------------------------------------------------------------
engine.render = {
renderer.result(of: SDL_SetRenderDrawColor, 255, 0, 0, 255)
renderer.result(of: SDL_RenderClear)
/* Draw your stuff */
renderer.pass(to: SDL_RenderPresent)
}
}
SDL objects are subclasses of SDLPointer<SDLType>
. These include (but are not limited to):
SDLWindow
SDLRenderer
SDLTexture
SDLSurface
SDLType
is a protocol which describes how the underlying SDL object is freed. Here is an example of SDLWindow
:
public final class SDLWindow: SDLPointer<SDLWindow>, SDLType {
public static func destroy(pointer: OpaquePointer) {
SDL_DestroyWindow(pointer)
}
}
SDLPointer<SDLType>
has just two functions:
result(of:)
is used forSDL2
functions which return errors; and,pass(to:)
is used forSDL2
functions that don't error-out.
You're able to call them on Optional
instances of SDLPointer<SDLType>
, foregoing the need to constantly be checking for nil
.
Some SDL function calls can return error codes. In these cases, use result(of:)
. For example, when setting the renderer's draw color:
// Create renderer ---------------------------------------------------------
let (window, renderer) = try engine.addWindow(width: 640, height: 480)
// Render ------------------------------------------------------------------
engine.render = {
do {
try renderer.result(of: SDL_SetRenderDrawColor, 255, 255, 255, 255).get()
try renderer.result(of: SDL_RenderClear).get()
/* Draw your stuff */
renderer.pass(to: SDL_RenderPresent)
} catch {
print(error)
}
}
You have total freedom for deciding when it is necessary for your application to handle potential errors; result(of:)
has a Void
as its Success
type. You may decide to ignore the Result
being returned, or call try...get()
if you're interested in handling potential errors.
Let's say you wanted to use IMG_LoadTexture
to create a new texture object. Here is the interface for this function:
func IMG_LoadTexture(_ renderer: OpaquePointer!, _ file: UnsafePointer<Int8>!) -> OpaquePointer!
Like nearly all SDL functions which get exposed to Swift, we need to pass an OpaquePointer!
as the first argument. Following that, a C-string'd file
path is needed as the second argument.
Let's look at how pass(to:)
is used to help us with this when loading a texture from a file:
let texturePathURL = Bundle.main.resourceURL!.appendingPathComponent("block.png")
let texture = renderer
.pass(to: IMG_LoadTexture, texturePathURL.path)
.map(SDLTexture.init)
In just a few lines of code, we've called IMG_LoadTexture
and created new SDLTexture
instance from the pointer returned by IMG_LoadTexture
.
Let's see more of SDLPointer<SDLType>
in action! 🎉
For example, to read the width
and height
of a renderer's output size:
let renderer: SDLRenderer? = /* returned elsewhere */
// Get logical renderer size ---------------------------------------------------
do {
var width: Int32 = .zero, height: Int32 = .zero
try renderer?.result(of: SDL_GetRendererOutputSize, &width, &height).get()
print("\(width) x \(height)")
} catch {
print(error)
}
Copyright (c) 2019 Kevin J. Vitale
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.