C++ Win32 FPS 和 DeltaTime 实现
Posted
技术标签:
【中文标题】C++ Win32 FPS 和 DeltaTime 实现【英文标题】:C++ Win32 FPS and DeltaTime Implementation 【发布时间】:2015-09-26 12:38:28 【问题描述】:我正在尝试为我的程序 (C++ Win32) 实现 FPS 和 DeltaTime。以下是我拥有的当前代码。 FPS 和 DeltaTime 应该以正确的方式实现。如果没有,请告诉我如何解决我的问题。
我目前面临的问题是我应该如何使用 DeltaTime。那就是更新和渲染。是的,我确实使用了包装类。 在我实现这个之前,我使用 WindowProcedure 来处理我的消息,我对此没有任何问题。但现在,试图实现这一点让我倾斜。因此,在我使用后台缓冲区和 WM_PAINT 进行绘制之前,我必须使用 hwnd 才能进行绘制。更新是通过来自 WindowProcedure 的输入,该输入必须接受 LPARAM 和 WPARAM 等参数,但在阅读了有关该主题的文章和论坛之后。需要更新和渲染,但他们不必为渲染方法采用 hwnd。至于更新,他们不必接受。
所以基本上我只想知道如何编写更新和渲染方法?
bool BaseWindow::HandleMessages()
// Counts Per Second
INT64 counts_per_sec = 0;
QueryPerformanceFrequency( ( LARGE_INTEGER* ) &counts_per_sec );
// Seconds Per Count
float sec_per_count = 1.0f / ( float ) counts_per_sec;
// Pervious Time
INT64 prev_time = 0;
QueryPerformanceCounter( ( LARGE_INTEGER* ) &prev_time );
MSG message = 0 ;
if ( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ))
TranslateMessage( &message );
DispatchMessage( &message );
if ( message.message == WM_QUIT )
OnDestroy();
return false;
else
// Get current count
INT64 current_time = 0;
QueryPerformanceCounter( ( LARGE_INTEGER* ) ¤t_time );
// DeltaTime
float delta_time = ( current_time - prev_time ) * sec_per_count;
// Update
// Render
return true;
更新 Main.cpp
#include "BaseWindow.h"
#include "ChildWindow.h"
int APIENTRY WinMain( HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR lp_cmd_line, int n_cmd_show )
ChildWindow child_window( h_instance, TEXT( "Child Window" ) );
BaseWindow base_window( TEXT( "Base Window" ), child_window.ClassName() );
while ( base_window.HandleMessages() );
return 0;
AbstractWindow.h
#ifndef __ABSTRACTWINDOW_H__
#define __ABSTRACTWINDOW_H__
#pragma once
#include <Windows.h>
class AbstractWindow
#pragma region Methods
public:
AbstractWindow();
~AbstractWindow();
virtual bool Create();
static LRESULT CALLBACK MessageHandler( HWND, UINT, WPARAM, LPARAM );
protected:
virtual LRESULT CALLBACK WindowProcedure( HWND, UINT, WPARAM, LPARAM ) = 0;
#pragma endregion
#pragma region Variables
protected:
HWND hwnd_;
DWORD style_ex_;
LPCTSTR class_name_;
LPCTSTR window_name_;
DWORD style_;
int x_;
int y_;
int width_;
int height_;
HWND parent_;
HMENU menu_;
HINSTANCE instance_;
#pragma endregion
;
#endif // !__ABSTRACTWINDOW_H__
AbstractWindow.cpp
#include "AbstractWindow.h"
AbstractWindow::AbstractWindow()
AbstractWindow::~AbstractWindow()
bool AbstractWindow::Create()
// Default Create Method
hwnd_ = CreateWindowEx(
style_ex_,
class_name_,
window_name_,
style_,
x_,
y_,
width_,
height_,
parent_,
menu_,
instance_,
this
);
return ( hwnd_ ? true : false );
LRESULT CALLBACK AbstractWindow::MessageHandler( HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param )
AbstractWindow* abstract_window = 0;
if ( message == WM_NCCREATE )
abstract_window = ( AbstractWindow* ) ( ( LPCREATESTRUCT( l_param ) )->lpCreateParams );
SetWindowLong( hwnd, GWL_USERDATA, long( abstract_window ) );
abstract_window = ( AbstractWindow * ) ( GetWindowLong( hwnd, GWL_USERDATA ) );
if ( abstract_window )
return abstract_window->WindowProcedure( hwnd, message, w_param, l_param );
else
return DefWindowProc( hwnd, message, w_param, l_param );
BaseWindow.h
#ifndef __BASEWINDOW_H__
#define __BASEWINDOW_H__
#pragma once
#include "AbstractWindow.h"
#include "ChildWindow.h"
class BaseWindow : public AbstractWindow
#pragma region Test Methods
private:
void Update();
void Render();
#pragma endregion
#pragma region Methods
public:
BaseWindow();
~BaseWindow();
bool HandleMessages();
BaseWindow( const TCHAR*, const TCHAR* );
void Show();
virtual LRESULT CALLBACK WindowProcedure( HWND, UINT, WPARAM, LPARAM );
#pragma region Handles
private:
bool CreateBackBuffer( HWND );
bool OnPaint( HWND );
bool PaintManager();
bool OnDestroy();
#pragma endregion
#pragma endregion
#pragma region Variables
private:
RECT window_rect_; // Structure for window width and height
int client_width_;
int client_height_;
POINT mouse_pos_;
#pragma region Back Buffer
HDC hdc_; // Handle to Device Context
HDC back_buffer_; // Back Buffer
HBITMAP bitmap_; // Current bitmap
#pragma endregion
#pragma endregion
;
#endif // !__BASEWINDOW_H__
BaseWindow.cpp
#include "BaseWindow.h"
BaseWindow::BaseWindow()
BaseWindow::~BaseWindow()
BaseWindow::BaseWindow( const TCHAR* window_name, const TCHAR* class_name )
style_ex_ = NULL;
class_name_ = class_name;
window_name_ = window_name;
style_ = WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
x_ = CW_USEDEFAULT;
y_ = CW_USEDEFAULT; CW_USEDEFAULT;
width_ = CW_USEDEFAULT;
height_ = CW_USEDEFAULT;
parent_ = NULL;
menu_ = NULL;
instance_ = GetModuleHandle( NULL );
Create();
Show();
void BaseWindow::Show()
ShowWindow( hwnd_, SW_SHOW );
UpdateWindow( hwnd_ );
LRESULT CALLBACK BaseWindow::WindowProcedure( HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param )
switch ( message )
case WM_CREATE:
CreateBackBuffer(hwnd);
return true;
case WM_ERASEBKGND:
return true;
case WM_DESTROY:
return OnDestroy();
default:
return DefWindowProc( hwnd, message, w_param, l_param );
bool BaseWindow::HandleMessages()
// Counts Per Second
INT64 counts_per_sec = 0;
QueryPerformanceFrequency( ( LARGE_INTEGER* ) &counts_per_sec );
// Seconds Per Count
float sec_per_count = 1.0f / ( float ) counts_per_sec;
// Pervious Time
INT64 prev_time = 0;
QueryPerformanceCounter( ( LARGE_INTEGER* ) &prev_time );
MSG message = 0 ;
if ( PeekMessage( &message, NULL, 0, 0, PM_REMOVE ) )
TranslateMessage( &message );
DispatchMessage( &message );
if ( message.message == WM_QUIT )
OnDestroy();
return false;
else
// Get current count
INT64 current_time = 0;
QueryPerformanceCounter( ( LARGE_INTEGER* ) ¤t_time );
// DeltaTime
float delta_time = ( current_time - prev_time ) * sec_per_count;
// Update
// Render
// I need to call for OnPaint()
// I need to take in hwnd
// But how ?
// Or do i not take in hwnd ?
// But hoe ?
return true;
#pragma region Handles
bool BaseWindow::CreateBackBuffer( HWND hwnd )
GetClientRect( hwnd, &window_rect_ );
client_width_ = window_rect_.right;
client_height_ = window_rect_.bottom;
back_buffer_ = CreateCompatibleDC( NULL ); // Create Back Buffer
hdc_ = GetDC( hwnd ); // Get the Device Context
bitmap_ = CreateCompatibleBitmap( hdc_, client_width_, client_height_ ); // Create Bitmap
SelectObject( back_buffer_, bitmap_ ); // Select Bitmap
ReleaseDC( hwnd, hdc_ ); // Release
return true;
bool BaseWindow::OnPaint( HWND hwnd )
PAINTSTRUCT paint_struct;
hdc_ = BeginPaint( hwnd_, &paint_struct ); // Get the Device Context
BitBlt( back_buffer_, 0, 0, client_width_, client_height_, NULL, NULL, NULL, WHITENESS );
// Paint
PaintManager();
BitBlt( hdc_, 0, 0, client_width_, client_height_, back_buffer_, 0, 0, SRCCOPY ); // Display the back buff
InvalidateRect( hwnd, NULL, true ); // Repaint the screen
EndPaint( hwnd, &paint_struct );
return true;
bool BaseWindow::PaintManager()
HBRUSH brush = ( HBRUSH ) ( GetStockObject( WHITE_BRUSH ) );
SelectObject( back_buffer_, brush ); // Select Brush
Rectangle( back_buffer_, 200, 200, 500, 500 );
DeleteObject( brush );
return true;
bool BaseWindow::OnDestroy()
PostQuitMessage( 0 );
return true;
#pragma endregion
ChildWindow.h
#ifndef __CHILDWINDOW_H__
#define __CHILDWINDOW_H__
#pragma once
#include "AbstractWindow.h"
#include "BaseWindow.h"
class ChildWindow : protected WNDCLASSEX
#pragma region Methods
public:
ChildWindow();
~ChildWindow();
ChildWindow( HINSTANCE, const TCHAR* );
bool Register();
const TCHAR* ClassName() const;
#pragma endregion
;
#endif // !__CHILDWINDOW_H__
ChildWindow.cpp
#include "ChildWindow.h"
ChildWindow::ChildWindow()
ChildWindow::~ChildWindow()
ChildWindow::ChildWindow( HINSTANCE h_instance, const TCHAR* class_name )
cbSize = sizeof( WNDCLASSEX );
style = NULL;
lpfnWndProc = AbstractWindow::MessageHandler;
cbClsExtra = NULL;
cbWndExtra = NULL;
hInstance = h_instance;
hIcon = LoadIcon( NULL, IDI_APPLICATION );
hCursor = LoadCursor( NULL, IDC_ARROW );
//hbrBackground = ( HBRUSH ) ( GetStockObject( DKGRAY_BRUSH ) );
hbrBackground = ( HBRUSH ) NULL;
lpszMenuName = NULL;
lpszClassName = class_name;
hIconSm = LoadIcon( NULL, IDI_APPLICATION );
Register();
bool ChildWindow::Register()
return ( ( RegisterClassEx( this ) ) ? true : false );
const TCHAR* ChildWindow::ClassName() const
return lpszClassName;
【问题讨论】:
【参考方案1】:我之前已经回答过这个问题,但你忽略了我的 cmets。你不应该从WM_PAINT
调用InvalidateRect
。您必须从 OnPaint
中删除 InvalidateRect
。
您的 OnPaint
函数甚至没有被调用。您必须添加这一行:
case WM_PAINT:
OnPaint();
return 0;
你在主消息循环中做了一些奇怪的事情。这是非常危险的。只需完全删除HandleMessages
。用一个简单的消息循环替换它:
int APIENTRY WinMain(HINSTANCE h_instance, HINSTANCE, LPSTR, int)
ChildWindow child_window(h_instance, TEXT("Child Window"));
BaseWindow base_window(TEXT("Base Window"), child_window.ClassName());
//main message loop
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
TranslateMessage(&msg);
DispatchMessage(&msg);
return 0;
不理会主消息循环。您可以稍后在消息循环中处理加速器,但仅此而已。
现在假设您要添加动画。首先,让我们从一些简单的动画开始。声明一个全局变量int g_counter = 0;
然后像这样改变PaintManager
:
bool BaseWindow::PaintManager()
HBRUSH brush = (HBRUSH)(GetStockObject(WHITE_BRUSH));
SelectObject(back_buffer_, brush); // Select Brush
Rectangle(back_buffer_, 200, 200, 500, 500);
DeleteObject(brush);
TCHAR buf[100];
wsprintf(buf, TEXT("%d"), g_counter);
TextOut(back_buffer_, 0, 0, buf, strlen(buf));
return true;
添加一些动画信号:
case WM_LBUTTONDOWN:
animate();
return 0;
制作动画功能:
void BaseWindow::animate()
for (int i = 0; i < 10000; i++)
g_counter++;
InvalidateRect(hwnd_, 0, FALSE);
//this code allows window to refresh itself, use it as is
//don't mess around with it
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
if (msg.message == WM_QUIT)
PostQuitMessage(0);
return;
TranslateMessage(&msg);
DispatchMessage(&msg);
您可以在 for
循环中放置一个计时器以减慢它的速度...如果您不需要高精度计时器,也可以查看 WM_TIMER
。
【讨论】:
以上是关于C++ Win32 FPS 和 DeltaTime 实现的主要内容,如果未能解决你的问题,请参考以下文章