【SDL游戏编程入门第十九卷】获取DeltaTime并控制帧率

一、前言

在 SDL2 中,常用的有两种方式获取时间和控制帧率

1. SDL_GetTicks64()

2. SDL_GetPerformanceCounter() 和 SDL_GetPerformanceFrequency()

二、使用 SDL_GetPerformanceCounter 示例代码

#include <SDL.h>

#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")

class Game
{
private:
	SDL_Window* m_Window;
	SDL_Renderer* m_Renderer;
	SDL_Event m_SDLEvent;
	bool m_BGameRunning;

	// deltatime
	float m_DeltaTime;
	Uint64 m_StartTime;
	Uint64 m_PreFrameTime;

	void InitWindow()
	{
		m_Window = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

		m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	}

public:
	Game()
		:
		m_BGameRunning(true),
		m_DeltaTime(0)
	{
		InitWindow();

		// deltaTime
		m_StartTime = SDL_GetPerformanceCounter();
		m_PreFrameTime = m_StartTime;
	}

	~Game()
	{
		SDL_DestroyRenderer(m_Renderer);
		SDL_DestroyWindow(m_Window);
		SDL_Quit();
	}

	void Run()
	{
		while (m_BGameRunning)
		{
			UpdateDeltaTime();
			Update();
			Render();
		}
	}

	void Update()
	{
		UpdateSDLEvent();
	}

	void Render()
	{
		SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
		SDL_RenderClear(m_Renderer);

		// Draw items

		SDL_RenderPresent(m_Renderer);
	}
	
	void UpdateDeltaTime()
	{
		/* Updates the deltaTime with the time it takes to update and render one frame. */
		Uint64 currTime = SDL_GetPerformanceCounter();
		m_DeltaTime = (currTime - m_PreFrameTime) / (float)SDL_GetPerformanceFrequency();
		m_PreFrameTime = currTime;
		float totalTime = (currTime - m_StartTime) / (float)SDL_GetPerformanceFrequency();
		float FPS = 1.0f / m_DeltaTime;
		SDL_LogInfo(0, "totalTime: %f\tdt: %f\tFPS: %f\n", totalTime, m_DeltaTime, FPS);

		// 限制帧率
		int MaxFPS = 60;
		if (m_DeltaTime < 1.0f / MaxFPS * 1000.0f)
			SDL_Delay(Uint32(floor(1.0 / MaxFPS * 1000.0 - m_DeltaTime)));
	}

	void UpdateSDLEvent()
	{
		while (SDL_PollEvent(&m_SDLEvent))
		{
			switch (m_SDLEvent.type)
			{
			case SDL_QUIT:
				m_BGameRunning = false;
				break;
			}
		}
	}
};

int main(int argc, char* argv[])
{
	Game game;

	game.Run();

	return 0;
}

下面第二种方式,只做了一点点改动,在精度上会低一点

三、使用 SDL_GetTicks64() 示例代码

#include <SDL.h>

#pragma comment(lib, "SDL2.lib")
#pragma comment(lib, "SDL2main.lib")

class Game
{
private:
	SDL_Window* m_Window;
	SDL_Renderer* m_Renderer;
	SDL_Event m_SDLEvent;
	bool m_BGameRunning;

	// deltatime
	float m_DeltaTime;
	Uint64 m_StartTime;
	Uint64 m_PreFrameTime;

	void InitWindow()
	{
		m_Window = SDL_CreateWindow("HelloSDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

		m_Renderer = SDL_CreateRenderer(m_Window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
	}

public:
	Game()
		:
		m_BGameRunning(true),
		m_DeltaTime(0)
	{
		InitWindow();

		// deltaTime
		m_StartTime = SDL_GetTicks64();
		m_PreFrameTime = m_StartTime;
	}

	~Game()
	{
		SDL_DestroyRenderer(m_Renderer);
		SDL_DestroyWindow(m_Window);
		SDL_Quit();
	}

	void Run()
	{
		while (m_BGameRunning)
		{
			UpdateDeltaTime();
			Update();
			Render();
		}
	}

	void Update()
	{
		UpdateSDLEvent();
	}

	void Render()
	{
		SDL_SetRenderDrawColor(m_Renderer, 0, 0, 0, 255);
		SDL_RenderClear(m_Renderer);

		// Draw items

		SDL_RenderPresent(m_Renderer);
	}
	
	void UpdateDeltaTime()
	{
		/* Updates the deltaTime with the time it takes to update and render one frame. */
		Uint64 currTime = SDL_GetTicks64();
		m_DeltaTime = (currTime - m_PreFrameTime) / 1000.0f;
		m_PreFrameTime = currTime;
		float totalTime = (currTime - m_StartTime) / 1000.0f;
		float FPS = 1.0f / m_DeltaTime;
		SDL_LogInfo(0, "totalTime: %f\tdt: %f\tFPS: %f\n", totalTime, m_DeltaTime, FPS);

		// 限制帧率
		int MaxFPS = 60;
		if (m_DeltaTime < 1.0f / MaxFPS * 1000.0f)
			SDL_Delay(Uint32(floor(1.0 / MaxFPS * 1000.0 - m_DeltaTime)));
	}

	void UpdateSDLEvent()
	{
		while (SDL_PollEvent(&m_SDLEvent))
		{
			switch (m_SDLEvent.type)
			{
			case SDL_QUIT:
				m_BGameRunning = false;
				break;
			}
		}
	}
};

int main(int argc, char* argv[])
{
	Game game;

	game.Run();

	return 0;
}

四、最后

SDL_GetPerformanceCounter 精度要高一些,推荐使用

在控制帧率的时候,精度都不太高,还没想到好的办法同时兼顾到跨平台和高精度

1. 本卷演示

最新更新

还有很多优化空间,这里提供控制标题更改不太快的方法

void Game::UpdateDeltaTime()  // 如果在窗口未激活状态,应该暂停,这里没处理事件...
{
	/* Updates the deltaTime with the time it takes to update and render one frame. */
	Uint64 currTime = SDL_GetPerformanceCounter();
	m_DeltaTime = (currTime - m_PreFrameTime) / (float)SDL_GetPerformanceFrequency();
	m_PreFrameTime = currTime;
	
	// 限制最大帧率
	int MaxFPS = 120;
	if (m_DeltaTime < 1.0f / MaxFPS * 1000.0f)
		SDL_Delay(Uint32(floor(1.0 / MaxFPS * 1000.0 - m_DeltaTime)));

	// 游戏从开始到目前所消耗的时间
	float totalTime = (currTime - m_StartTime) / (float)SDL_GetPerformanceFrequency();

	static int frameCount = 0;
	static float timeElapsed = 0.0f;
	frameCount++;
	// 每秒更改一次标题,避免更新太快
	if ((totalTime - timeElapsed) > 1.0f)
	{
		float mspf = 1000.0f / frameCount;
		std::ostringstream outs;
		outs << "C++ SDL2 RPG" << "    " <<
			"FPS: " << frameCount << "    " <<
			"Frame Time: " << mspf << " (ms)";
		// 更改窗口标题
		SDL_SetWindowTitle(m_Window, outs.str().c_str());

		frameCount = 0;
		timeElapsed += 1.0f;
	}
}



本节内容就到这里了,下卷会继续分享 SDL 的基本使用

The End.

posted @ 2022-12-16 19:48  AnnihilateSword  阅读(230)  评论(0编辑  收藏  举报