开发环境配置
http://tjumyk.github.io/sdl-tutorial-cn/contents.html
VS 新建项目选择控制台程序,链接属性中选择子系统“/SUBSYSTEM:WINDOWS”。
在屏幕上显示一张图片
#include <SDL/SDL.h>
int main(int argc, char *args[])
{
SDL_Surface *hello = nullptr;
SDL_Surface *screen = nullptr;
SDL_Init(SDL_INIT_EVERYTHING);
// 设置窗口
screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
// 加载图像
hello = SDL_LoadBMP("hello.bmp");
// 将图像应用到窗口上
SDL_BlitSurface(hello, nullptr, screen, nullptr);
// 更新窗口
SDL_Flip(screen);
SDL_Delay(2000);
// 释放资源
SDL_FreeSurface(hello);
SDL_Quit();
return 0;
}
优化表面的加载和 Blit
#include <SDL/SDL.h>
#include <string>
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;
SDL_Surface *message = nullptr;
SDL_Surface *background = nullptr;
SDL_Surface *screen = nullptr;
SDL_Surface* load_image(const std::string &filename)
{
SDL_Surface *loadedImage = nullptr;
SDL_Surface *optimizedImage = nullptr;
// 这个位图是24位的,而窗口是32位的,将一个表面blit到另一个不同表面上时,
// SDL会在每次blit时做一次临时性的格式转换,这会导致程序运行效率降低。
loadedImage = SDL_LoadBMP(filename.c_str());
if (loadedImage != nullptr) {
// 创建一个与窗口同样格式的图像
optimizedImage = SDL_DisplayFormat(loadedImage);
// 释放资源
SDL_FreeSurface(loadedImage);
}
return optimizedImage;
}
void apply_surface(int x, int y, SDL_Surface *source, SDL_Surface *destination)
{
SDL_Rect offset;
offset.x = x;
offset.y = y;
SDL_BlitSurface(source, nullptr, destination, &offset);
}
int main(int argc, char *args[])
{
if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
return 1;
}
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
if (screen == nullptr) {
return 1;
}
// 设置窗口标题
SDL_WM_SetCaption("Hello World", nullptr);
// 加载图片
message = load_image("hello.bmp");
background = load_image("background.bmp");
// 将图片应用到窗口上
// SDL的坐标系左上角是原点
apply_surface(0, 0, background, screen);
apply_surface(320, 0, background, screen);
apply_surface(0, 240, background, screen);
apply_surface(320, 240, background, screen);
apply_surface(180, 140, message, screen);
if (SDL_FLip(screen) != -1) {
return 1;
}
SDL_Delay(2000);
SDL_FreeSurface(message);
SDL_FreeSurface(background);
SDL_Quit();
return 0;
}
SDL仅原生地支持bmp格式的图片文件,但是通过使用SDL_image
这个扩展库,你将可以加载BMP, PNM, XPM, LBM, PCX, GIF, JPEG, TGA 和 PNG 格式的图片文件。
//加载图像
loadedImage = IMG_Load(filename.c_str());
事件驱动编程
bool quit(false);
SDL_Event event;
while (quit == false) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
quit = true;
}
}
}
其他相关函数:SDL_WaitEvent()
SDL_PeepEvents()
。
错误处理相关函数:SDL_GetError()
IMG_GetError()
。
指定颜色为透明
// 将所有颜色为(R 0, G 0xFF, B 0xFF)的像素设为透明
Uint32 colorKey = SDL_MapRGB(optimizedImage->format, 0, 0xFF, 0xFF);
SDL_SetColorKey(optimizedImage, SDL_SRCCOLORKEY, colorKey);
SDL_SRCCOLORKEY标志保证了关键色仅当blit这个表面到另一个表面上时才被使用。
对于带alpha通道的PNG图片,IMG_Load()函数会自动地为他们处理透明色。在一个已经具有透明背景色的图片上设置关键色会导致糟糕的结果。另外,如果你使用SDL_DisplayFormat(),而不是SDL_DisplayFormatAlpha(),你也会丢失Alpha透明色。要保持PNG中的透明色,请不要设置关键色。IMG_Load()也会处理TGA图像的Alpha透明色。你可以在SDL的文档里得到更详细的有关关键色的信息。
局部Blit和精灵图
精灵图是一系列保存在同一个图像文件中的图像。当你有数量庞大的图像,但不想处理那么多的图像文件时,精灵图就派上用场了。
//用白色填充窗口
SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0xFF, 0xFF, 0xFF));
SDL_Rect clip[4];
//左上角的剪切区域
clip[0].x = 0;
clip[0].y = 0;
clip[0].w = 100;
clip[0].h = 100;
//右上角的剪切区域
clip[1].x = 100;
clip[1].y = 0;
clip[1].w = 100;
clip[1].h = 100;
//左下角的剪切区域
clip[2].x = 0;
clip[2].y = 100;
clip[2].w = 100;
clip[2].h = 100;
//右下角的剪切区域
clip[3].x = 100;
clip[3].y = 100;
clip[3].w = 100;
clip[3].h = 100;
//将图像应用到窗口中
apply_surface( 0, 0, dots, screen, &clip[0] );
apply_surface( 540, 0, dots, screen, &clip[1] );
apply_surface( 0, 380, dots, screen, &clip[2] );
apply_surface( 540, 380, dots, screen, &clip[3] );
现在,当你想要使用很多图片时,你不必保存成千上万个图片文件。你可以将一个子图集合放入一个单独的图片文件中,并blit你想要使用的部分。
True Type 字体
SDL 本身不原生地支持 TTF 文件,所以你需要使用 SDL_ttf
扩展库。SDL_ttf 是一个能从 True Type 字体中生成表面的的扩展库。
TTF_Font *font = nullptr;
SDL_Color textColor = {255, 255, 255};
TTF_Init();
font = TTF_OpenFont("lazy.ttf", 28);
SDL_Surface *message = TTF_RenderText_Solid(font, "The quick brown fox jumps over the lazy dog", textColor);
// 清理
TTF_CloseFont(font);
TTF_Quit();
处理键盘事件
if (SDL_PollEvent(&event)) {
if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_UP:
// ...
break;
case SDLK_DOWN:
// ...
break;
}
}
}
处理鼠标事件
if(event.type == SDL_MOUSEBUTTONDOWN) {
if(event.button.button == SDL_BUTTON_LEFT) {
// ...
}
}
按键状态
在不使用事件的情况下通过按键状态检查一个按键是否被按下。
//获取按键状态
Uint8 *keystates = SDL_GetKeyState(nullptr);
if(keystates[SDLK_UP]) {
// ...
}
其他相关函数:SDL_GetModState()
SDL_GetMouseState()
SDL_JoystickGetAxis()
。
播放声音
播放声音是游戏编程的又一个关键概念。SDL原生的声音函数可能会让人很纠结。所以,你将学习使用SDL_mixer扩展库来播放声效和音乐。SDL_mixer扩展库将使用声音变得非常容易。
对于那些使用OGG, MOD或者其他非WAV声音格式的人,请使用Mix_Init()来初始化解码器,并使用Mix_Quit()来关闭解码器。
//将要播放的音乐
Mix_Music *music = NULL;
//将要使用的声效
Mix_Chunk *scratch = NULL;
Mix_Chunk *high = NULL;
Mix_Chunk *med = NULL;
Mix_Chunk *low = NULL;
//初始化SDL_mixer
Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096);
//加载音乐
music = Mix_LoadMUS( "beat.wav" );
//加载声效
scratch = Mix_LoadWAV( "scratch.wav" );
high = Mix_LoadWAV( "high.wav" );
med = Mix_LoadWAV( "medium.wav" );
low = Mix_LoadWAV( "low.wav" );
Mix_PlayMusic( music, -1 );
//Mix_PauseMusic();
//Mix_ResumeMusic();
Mix_PlayChannel( -1, scratch, 0 );
// 释放资源
//释放声效
Mix_FreeChunk( scratch );
Mix_FreeChunk( high );
Mix_FreeChunk( med );
Mix_FreeChunk( low );
//释放音乐
Mix_FreeMusic( music );
//退出SDL_mixer
Mix_CloseAudio();
计时
Uint32 start = SDL_GetTicks();
// ...
std::cout << SDL_GetTicks() - start << std::endl;
其他笔记
SDL Game Development.pdf
坐标系
原点:左上角
SDL扩展
SDL_image
支持多种格式图片加载:BMP GIF PNG TGA PCX ...
SDL_net
跨平台网络库
SDL_mixer
audio mixer library. 支持MP3 MIDI OGG
SDL_ttf
支持TrueType字体
SDL_rtf
support the rendering of the Rich Text Format (RTF).
SDL_Init()
初始化标识
SDL_INIT_HAPTIC Force feedback subsystem 力反馈
SDL_INIT_AUDIO Audio subsystem
SDL_INIT_VIDEO Video subsystem
SDL_INIT_TIMER Timer subsystem
SDL_INIT_JOYSTICK Joystick subsystem
SDL_INIT_EVERYTHING All subsystems
SDL_INIT_noparachute Don't catch fatal signals
查看一个子系统是否已被初始化:
if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
cout << "video was initialized";
SDL_CreateRenderer()
SDL_RENDERER_SOFTWARE Use software rendering
SDL_RENDERER_ACCELERATED Use hardware acceleration
SDL_RENDERER_PRESENTVSYNC Synchronize renderer update with screen's refresh rate
SDL_RENDERER_TARGETTEXTURE Supports render to texture
游戏程序结构
初始化
游戏循环:获取输入 物理运算 渲染
退出
window flags
SDL_WINDOW_FULLSCREEN Make the window fullscreen
SDL_WINDOW_OPENGL Window can be used with as an OpenGL context
SDL_WINDOW_SHOWN The window is visible
SDL_WINDOW_HIDDEN Hide the window
SDL_WINDOW_BORDERLESS No border on the window
SDL_WINDOW_RESIZABLE Enable resizing of the window
SDL_WINDOW_MINIMIZED Minimize the window
SDL_WINDOW_MAXIMIZED Maximize the window
SDL_WINDOW_INPUT_GRABBED Window has grabbed input focus
SDL_WINDOW_INPUT_FOCUS Window has input focus
SDL_WINDOW_MOUSE_FOCUS Window has mouse focus
SDL_WINDOW_FOREIGN The window was not created using SDL
程序基本结构
#include <SDL.h>
SDL_Window* g_pWindow = 0;
SDL_Renderer* g_pRenderer = 0;
int main(int argc, char* argv[])
{
// initialize SDL
if (SDL_Init(SDL_INIT_EVERYTHING) >= 0)
{
// if succeeded create our window
g_pWindow = SDL_CreateWindow("Chapter 1: Setting up SDL",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480,
SDL_WINDOW_SHOWN);
// if the window creation succeeded create our renderer
if (g_pWindow != 0)
{
g_pRenderer = SDL_CreateRenderer(g_pWindow, -1, 0);
}
}
else
return 1; // sdl could not initialize
// everything succeeded lets draw the window
bool bQuit = false;
while (true)
{
SDL_Event event;
if (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
bQuit = true;
break;
}
}
if (bQuit)
break;
// set to black
// This function expects Red, Green, Blue and Alpha as color values
SDL_SetRenderDrawColor(g_pRenderer, 0, 0, 0, 255);
// clear the window to black
SDL_RenderClear(g_pRenderer);
// show the window
SDL_RenderPresent(g_pRenderer);
}
// set a delay before quitting
//SDL_Delay(5000);
// clean up SDL
SDL_Quit();
return 0;
}
绘制图片过程
- 加载图片获得SDL_Surface
- 根据Surface获得SDL_Texture
- 获得纹理尺寸:SDL_QueryTexture
- 渲染:SDL_RenderCopy/SDL_RenderCopyEx 需要Renderer参数
SDL使用两种数据结构渲染到屏幕
SDL_Surface 像素集 使用软件渲染(not GPU)
SDL_Texture 使用硬件加速
SDL_Texture* m_pTexture
SDL_Rect m_sourceRectangle;
SDL_Rect m_destinationRectangle;
// 根据图片创建SDL_Texture
SDL_Surface* pTempSurface = SDL_LoadBMP("asserts/rider.bmp");
m_pTexture = SDL_CreateTextureFromSurface(m_pRenderer, pTempSurface);
SDL_FreeSurface(pTempSurface);
// 获得图片的大小
SDL_QueryTexture(m_pTexture, NULL, NULL,
&m_sourceRectangle.w, &m_sourceRectangle.h);
SDL_RenderClear();
SDL_RenderCopy(m_pRenderer, m_pTexture,
&m_sourceRectangle, &m_destinationRectangle);
SDL_RenderPresent();
源矩形 目标矩形 参数传入0 将整个纹理渲染到整个窗口。
SDL_GetTicks() 毫秒
SDL_RenderCopyEx() 支持旋转和翻转Flip
SDL_image
sdl2_image.lib
#include <sdl_image.h>
SDL_Surface* pTempSurface = IMG_Load("animate.png");
Fixed frames per second (FPS) is
not necessarily always a good option, especially when your game includes more
advanced physics. It is worth bearing this in mind when you move on from this
book and start developing your own games. Fixed FPS will, however, be fine for
the small 2D games, which we will work towards in this book.
固定帧频
const int FPS = 60;
const int DELAY_TIME = 1000.0f / FPS; // 1000毫秒
while (...)
{
frameStart = SDL_GetTicks(); // 毫秒
...
frameTime = SDL_GetTicks() - frameStart;
if (frameTime < DELAY_TIME)
SDL_Delay((int)(DELAY_TIME - frameTime));
}
SDL joystick event
SDL_JoyAxisEvent Axis motion information
SDL_JoyButtonEvent Button press and release information
SDL_JoyBallEvent Trackball event motion information
SDL_JoyHatEvent Joystick hat position change
SDL joystick event Type value
SDL_JoyAxisEvent SDL_JOYAXISMOTION
SDL_JoyButtonEvent SDL_JOYBUTTONDOWN or
SDL_JOYBUTTONUP
SDL_JoyBallEvent SDL_JOYBALLMOTION
SDL_JoyHatEvent SDL_JOYHATMOTION
不同的游戏控制器 按钮和轴可能有不同的值 比如Xbox360 controller, PS3 controller
Xbox360 controller:
Two analog sticks
Analog sticks press as buttons
Start and Select buttons
Four face buttons: A, B, X, and Y
Four triggers: two digital and two analog
A digital directional pad
if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0)
SDL_InitSubSystem(SDL_INIT_JOYSTICK);
if (SDL_NumJoysticks() > 0)
{
for (int i=0; i<SDL_NumJoysticks(); ++i)
{
SDL_Joystick* joy = SDL_JoystickOpen(i);
if (SDL_JoystickOpened(i) == 1)
m_joysticks.push_back(joy);
}
SDL_JoystickEventState(SDL_ENABLE);
}
SDL_JoystickClose(joy);
分辨是哪个控制器的事件
if (event.type == SDL_JOYAXISMOTION)
int whichOne = event.jaxis.which;
控制器按钮
SDL_JoystickNumButtons
event.jbutton.button // 按钮ID
鼠标事件
SDL Mouse Event Purpose
SDL_MouseButtonEvent A button on the mouse has been pressed or released
SDL_MouseMotionEvent The mouse has been moved
SDL_MouseWheelEvent The mouse wheel has moved
SDL Mouse Event Type Value
SDL_MouseButtonEvent SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP
SDL_MouseMotionEvent SDL_MOUSEMOTION
SDL_MouseWheelEvent SDL_MOUSEWHEEL
// 鼠标按钮
// SDL numbers these as 0 for left, 1 for middle, and 2 for right.
if (event.type == SDL_MOUSEBUTTONDOWN)
if (event.button.button == SDL_BUTTON_LEFT)
event.type SDL_MOUSEMOTION
event.motion.x
event.motion.y
// 键盘
1 表示按下 0 表示没有按下
SDL_GetKeyboardState(int* numkeys)
Uint8* m_keystates;
m_keystates = SDL_GetKeyboardState(0);
SDL_Scancode key;
if (m_keysttes[key] == 1)
有限状态机
需要能够处理以下情况:
Removing one state and adding another
Adding one state without removing the previous state
Removing one state without adding another
Game
GameObject
TextureManager // 负责加载图片文件,负责绘制,纹理ID
InputHandler
GameState
GameStateMachine
GameObjectFactory
StateParser
Distributed Factory
class GameObjectFactory
std::map<std::string, BaseCreator*> m_creators;
registerType(std::string typeID, BaseCreator* pCreator);
https://github.com/ReneNyffenegger/development_misc/tree/master/base64.
The base64.h and base64.cpp files can be added directly to the project.
SDL_Mixer
Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)
Mix_OpenAudio(22050, AUDIO_S16, 2, 4096);
std::map<std::string, Mix_Chunk*> mSfxs;
std::map<std::string, Mix_Chunk*> mMusic;
Mix_Music* music = Mix_LoadMUS(filename); // ogg
Mix_Chunk* chunk = Mix_LoadWAV(filename); // wav
Mix_PlayMusic() // Mix_Music
Mix_PlayChannel() // Mix_Chunk
Mix_CloseAudio();
SDL_SetTextureAlphaMod()