本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識 轉載請註明 原文鏈接 :https://www.cnblogs.com/Multya/p/16273753.html 官方教程: https://www.sfml-dev.org/tutorials/2.5/ 持續更新 ...
本文大部分來自官方教程的Google翻譯 但是加了一點點個人的理解和其他相關知識
轉載請註明 原文鏈接 :https://www.cnblogs.com/Multya/p/16273753.html
官方教程: https://www.sfml-dev.org/tutorials/2.5/
持續更新中
配置cmake
這裡使用了預編譯的二進位庫文件 不嫌麻煩可以自行編譯
cmake文件需額外添加以下 其中Gm為項目名稱 SFML在根目錄下 dependence/SFML
源文件在根目錄下 src/
#提供查找包的路徑
set(SFML_DIR "dependence/SFML/lib/cmake/SFML")
#查找SFMLConfig.cmake文件並通過預置查找包工具查找
find_package(SFML REQUIRED COMPONENTS audio network graphics window system)
#頭文件目錄綁定
include_directories(${SFML_INCLUDE_DIR})
#添加編譯為可執行文件
add_executable(Gm src/main.cpp)
#鏈接二進位庫文件
target_link_libraries(Gm sfml-audio sfml-network sfml-graphics sfml-window sfml-system)
視窗
打開一個視窗
SFML 中的視窗由 sf::Window
類定義。可以在構建時直接創建和打開視窗:
#include <SFML/Window.hpp>
int main()
{
sf::Window window(sf::VideoMode(800, 600), "My window");
...
return 0;
}
第一個參數video mode定義了視窗的大小(內部大小,沒有標題欄和邊框)。在這裡,我們創建一個大小為 800x600 像素的視窗。
該類sf::VideoMode
有一些有趣的靜態函數來獲取桌面解析度,或全屏模式的有效視頻模式列表。不要猶豫,看看它的文檔。
第二個參數只是視窗的標題。
此構造函數接受第三個可選參數:樣式,它允許您選擇所需的裝飾和功能。您可以使用以下樣式的任意組合:
sf::Style::None |
完全沒有裝飾(例如,對於啟動畫面很有用);這種風格不能與其他風格結合 |
---|---|
sf::Style::Titlebar |
視窗有一個標題欄 |
sf::Style::Resize |
視窗可以調整大小並有一個最大化按鈕 |
sf::Style::Close |
視窗有一個關閉按鈕 |
sf::Style::Fullscreen |
視窗以全屏模式顯示;此樣式不能與其他樣式組合,並且需要有效的視頻模式 |
sf::Style::Default |
預設樣式,它是`Titlebar |
還有第四個可選參數,它定義了 OpenGL 特定的選項,這些選項在 專門的 OpenGL 教程中進行瞭解釋。
如果您想在實例構建後sf::Window
創建視窗,或者用不同的視頻模式或標題重新創建它,您可以使用該create
函數來代替。它採用與構造函數完全相同的參數。
#include <SFML/Window.hpp>
int main()
{
sf::Window window;
window.create(sf::VideoMode(800, 600), "My window");
...
return 0;
}
讓視窗煥發生機
如果您嘗試執行上面的代碼而不用任何東西代替“...”,您將幾乎看不到任何東西。首先,因為程式立即結束。其次,因為沒有事件處理——所以即使你在這段代碼中添加了一個無限迴圈,你也會看到一個死視窗,無法移動、調整大小或關閉。
讓我們添加一些代碼讓這個程式更有趣:
#include <SFML/Window.hpp>
#include <bits/stdc++.h>
int main() {
sf::Window window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen()) {
// check all the window's events that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event)) {
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
}
return 0;
}
上面的代碼將打開一個視窗,併在用戶關閉它時終止。讓我們詳細看看它是如何工作的。
首先,我們添加了一個迴圈,以確保應用程式將被刷新/更新,直到視窗關閉。大多數(如果不是全部)SFML 程式都會有這種迴圈,有時稱為主迴圈或游戲迴圈。
然後,我們想要在游戲迴圈中做的第一件事是檢查發生的任何事件。請註意,我們使用while
迴圈,以便在有多個事件的情況下處理所有未決事件。如果事件未決,則該pollEvent
函數返回 true,如果沒有,則返回 false。
每當我們得到一個事件時,我們必須檢查它的類型(視窗關閉?按鍵被按下?滑鼠移動?操縱桿連接?...),如果我們對它感興趣,就做出相應的反應。在這種情況下,我們只關心Event::Closed
當用戶想要關閉視窗時觸發的事件。此時,視窗仍處於打開狀態,我們必須使用close
函數顯式關閉它。這使您可以在視窗關閉之前執行某些操作,例如保存應用程式的當前狀態或顯示消息。
人們經常犯的一個錯誤是忘記事件迴圈,僅僅是因為他們還不關心處理事件(他們使用實時輸入代替)。如果沒有事件迴圈,視窗將變得無響應。值得註意的是,事件迴圈有兩個角色:除了向用戶提供事件之外,它還讓視窗有機會處理其內部事件,這是必需的,以便它可以對移動或調整用戶操作做出反應。
視窗關閉後,主迴圈退出,程式終止。
在這一點上,您可能註意到我們還沒有討論在視窗上繪製一些東西。如介紹中所述,這不是 sfml-window 模塊的工作,如果您想繪製精靈、文本或形狀等內容,則必須跳轉到 sfml-graphics 教程。
要繪製東西,您也可以直接使用 OpenGL,完全忽略 sfml-graphics 模塊。sf::Window
在內部創建一個 OpenGL 上下文並準備好接受您的 OpenGL 調用。您可以在相應的教程中瞭解更多相關信息。
不要期望在此視窗中看到有趣的東西:您可能會看到統一的顏色(黑色或白色),或者之前使用 OpenGL 的應用程式的最後內容,或者......其他的東西。
玩視窗
當然,SFML 允許您稍微玩一下自己的視窗。支持更改大小、位置、標題或圖標等基本視窗操作,但與專用 GUI 庫(Qt、wxWidgets)不同,SFML 不提供高級功能。SFML 視窗僅用於為 OpenGL 或 SFML 繪圖提供環境。
// change the position of the window (relatively to the desktop)
window.setPosition(sf::Vector2i(10, 50));
// change the size of the window
window.setSize(sf::Vector2u(640, 480));
// change the title of the window
window.setTitle("SFML window");
// get the size of the window
sf::Vector2u size = window.getSize();
unsigned int width = size.x;
unsigned int height = size.y;
// check whether the window has the focus
bool focus = window.hasFocus();
...
您可以參考 API 文檔以獲取sf::Window
函數的完整列表。
如果您確實需要視窗的高級功能,您可以使用另一個庫創建一個(甚至是完整的 GUI),並將 SFML 嵌入其中。為此,您可以使用其他構造函數或create
函數,sf::Window
它採用現有視窗的特定於操作系統的句柄。在這種情況下,SFML 將在給定視窗內創建一個繪圖上下文,併在不幹擾父視窗管理的情況下捕獲其所有事件。
sf::WindowHandle handle = /* specific to what you're doing and the library you're using */;
sf::Window window(handle);
如果您只想要一個額外的、非常具體的功能,您也可以反過來做:創建一個 SFML 視窗並獲取其特定於操作系統的句柄來實現 SFML 本身不支持的東西。
sf::Window window(sf::VideoMode(800, 600), "SFML window");
sf::WindowHandle handle = window.getSystemHandle();
// you can now use the handle with OS specific functions
將 SFML 與其他庫集成需要一些工作,此處不再贅述,但您可以參考專門的教程、示例或論壇帖子。
控制幀率
有時,當您的應用程式快速運行時,您可能會註意到諸如撕裂之類的視覺偽影。原因是您的應用程式的刷新率與顯示器的垂直頻率不同步,因此前一幀的底部與下一幀的頂部混合在一起。
解決這個問題的方法是激活垂直同步。由顯卡自動處理,可通過以下setVerticalSyncEnabled
功能輕鬆開啟和關閉:
window.setVerticalSyncEnabled(true); // call it once, after creating the window
在此調用之後,您的應用程式將以與顯示器刷新率相同的頻率運行。
有時setVerticalSyncEnabled
沒有效果:這很可能是因為垂直同步在您的圖形驅動程式設置中被強制“關閉”。它應該設置為“由應用程式控制”。
在其他情況下,您可能還希望應用程式以給定的幀速率運行,而不是監視器的頻率。這可以通過調用來完成 setFramerateLimit
:
window.setFramerateLimit(60); // call it once, after creating the window
與 不同setVerticalSyncEnabled
的是,此功能由 SFML 本身實現,使用sf::Clock
和的組合sf::sleep
。一個重要的後果是它不是 100% 可靠的,尤其是對於高幀率:sf::sleep
的解析度取決於底層操作系統和硬體,可能高達 10 或 15 毫秒。不要依賴此功能來實現精確計時。
切勿同時使用setVerticalSyncEnabled
兩者setFramerateLimit
!他們會嚴重混合併使事情變得更糟。
關於視窗要知道的事
下麵簡要列出了您可以使用 SFML 視窗做什麼和不能做什麼。
您可以創建多個視窗
SFML 允許您創建多個視窗,併在主線程中處理它們,或者在其自己的線程中處理它們(但...見下文)。在這種情況下,不要忘記為每個視窗設置一個事件迴圈。
尚未正確支持多台顯示器
SFML 沒有明確管理多個監視器。因此,您將無法選擇視窗出現在哪個監視器上,並且您將無法創建多個全屏視窗。這應該在未來的版本中得到改進。
必須在視窗的線程中輪詢事件
這是大多數操作系統的一個重要限制:事件迴圈(更準確地說是pollEvent
orwaitEvent
函數)必須在創建視窗的同一線程中調用。這意味著如果你想為事件處理創建一個專用線程,你必須確保視窗也是在這個線程中創建的。如果您真的想線上程之間拆分事物,將事件處理保留在主線程中並將其餘部分(渲染、物理、邏輯......)移到單獨的線程中會更方便。這種配置也將與下麵描述的其他限制相容。
在 macOS 上,視窗和事件必須在主線程中進行管理
是的,這是真的;如果您嘗試在主線程以外的線程中創建視窗或處理事件,macOS 將不會同意。
在 Windows 上,大於桌面的視窗將無法正常運行
出於某種原因,Windows 不喜歡比桌面更大的視窗。這包括使用 VideoMode::getDesktopMode()
: 添加的視窗裝飾(邊框和標題欄)創建的視窗,您最終會得到一個比桌面稍大的視窗。
獲取事件
sf::Event 類型
在處理事件之前,重要的是要瞭解sf::Event
類型是什麼,以及如何正確使用它。 sf::Event
是一個union,這意味著一次只有一個成員是有效的(記住你的 C++ 課程:一個 union 的所有成員共用相同的記憶體空間)。有效成員是與事件類型匹配的成員,例如event.key
事件 KeyPressed
。嘗試讀取任何其他union將導致未定義的行為(很可能:隨機或無效值)。永遠不要嘗試使用與其類型不匹配的事件成員,這一點很重要。
//Event 內部實現
union
{
SizeEvent size; ///< Size event parameters (Event::Resized)
KeyEvent key; ///< Key event parameters (Event::KeyPressed, Event::KeyReleased)
TextEvent text; ///< Text event parameters (Event::TextEntered)
MouseMoveEvent mouseMove; ///< Mouse move event parameters (Event::MouseMoved)
MouseButtonEvent mouseButton; ///< Mouse button event parameters (Event::MouseButtonPressed, Event::MouseButtonReleased)
MouseWheelEvent mouseWheel; ///< Mouse wheel event parameters (Event::MouseWheelMoved) (deprecated)
MouseWheelScrollEvent mouseWheelScroll; ///< Mouse wheel event parameters (Event::MouseWheelScrolled)
JoystickMoveEvent joystickMove; ///< Joystick move event parameters (Event::JoystickMoved)
JoystickButtonEvent joystickButton; ///< Joystick button event parameters (Event::JoystickButtonPressed, Event::JoystickButtonReleased)
JoystickConnectEvent joystickConnect; ///< Joystick (dis)connect event parameters (Event::JoystickConnected, Event::JoystickDisconnected)
TouchEvent touch; ///< Touch events parameters (Event::TouchBegan, Event::TouchMoved, Event::TouchEnded)
SensorEvent sensor; ///< Sensor event parameters (Event::SensorChanged)
};
sf::Event
實例由類的pollEvent
(或waitEvent
)函數填充sf::Window
。只有這兩個函數可以產生有效事件,任何嘗試使用sf::Event
未通過成功調用 pollEvent
(or waitEvent
) 返回的都會導致與上面提到的相同的未定義行為。
需要明確的是,典型的事件迴圈如下所示:
sf::Event event;
// while there are pending events...
while (window.pollEvent(event))
{
// check the type of the event...
switch (event.type)
{
// window closed
case sf::Event::Closed:
window.close();
break;
// key pressed
case sf::Event::KeyPressed:
...
break;
// we don't process other types of events
default:
break;
}
}
再次閱讀上面的段落並確保您完全理解它,sf::Event
union類型是導致沒有經驗的程式員許多bug的原因。
好的,現在我們可以看到 SFML 支持哪些事件,它們的含義以及如何正確使用它們。
關閉的事件
sf::Event::Closed
當用戶想要關閉視窗時,通過視窗管理器提供的任何可能的方法(“關閉”按鈕、鍵盤快捷鍵等)觸發 該事件。該事件僅代表關閉請求,收到事件時視窗尚未關閉。
典型的代碼只會調用window.close()
這個事件來實際關閉視窗。但是,您可能還想先做其他事情,例如保存當前應用程式狀態或詢問用戶要做什麼。如果您不執行任何操作,視窗將保持打開狀態。
sf::Event
union 中沒有與此事件相關的成員。
if (event.type == sf::Event::Closed)
window.close();
調整大小的事件
sf::Event::Resized
當通過用戶操作或通過調用以編程方式調整視窗大小時觸發 該事件window.setSize
。
您可以使用此事件來調整渲染設置:如果您直接使用 OpenGL,則為viewport,如果您使用 sfml-graphics,則為current view。
與此事件關聯的成員是event.size
,它包含視窗的新大小。
if (event.type == sf::Event::Resized)
{
std::cout << "new width: " << event.size.width << std::endl;
std::cout << "new height: " << event.size.height << std::endl;
}
LostFocus 和 GainedFocus 事件
sf::Event::LostFocus
和事件 在sf::Event::GainedFocus
視窗失去/獲得焦點時觸發,這發生在用戶切換當前活動視窗時。當視窗失焦時,它不會接收鍵盤事件。
例如,如果您想在視窗不活動時暫停游戲,可以使用此事件。
sf::Event
union中沒有與這些事件相關的成員。
if (event.type == sf::Event::LostFocus)
myGame.pause();
if (event.type == sf::Event::GainedFocus)
myGame.resume();
文本輸入事件
sf::Event::TextEntered
輸入字元時觸發 該事件。這不能與KeyPressed
事件混淆:TextEntered
解釋用戶輸入並產生適當的可列印字元。例如,在法語鍵盤上按“^”然後按“e”將產生兩個KeyPressed
事件,但一個TextEntered
事件包含“ê”字元。它適用於操作系統提供的所有輸入法,即使是最具體或最複雜的輸入法。
此事件通常用於捕獲文本欄位中的用戶輸入。
與此事件關聯的成員是event.text
,它包含輸入字元的 Unicode 值。您可以將其直接放在 a 中sf::String
,也可以char
在確保它在 ASCII 範圍內 (0 - 127) 後將其轉換為 a 。
if (event.type == sf::Event::TextEntered)
{
if (event.text.unicode < 128)
std::cout << "ASCII character typed: " << static_cast<char>(event.text.unicode) << std::endl;
}
請註意,由於它們是 Unicode 標準的一部分,因此此事件會生成 一些不可列印的字元,例如退格。在大多數情況下,您需要將它們過濾掉。
許多程式員使用該KeyPressed
事件來獲取用戶輸入,並開始實施瘋狂的演算法,試圖解釋所有可能的鍵組合以產生正確的字元。不要那樣做!
KeyPressed 和 KeyReleased 事件
sf::Event::KeyPressed
和事件 在sf::Event::KeyReleased
按下/釋放鍵盤鍵時觸發。
如果按住某個鍵,KeyPressed
則會在預設的操作系統延遲下生成多個事件(即,與您在文本編輯器中按住字母時應用的延遲相同)。要禁用重覆KeyPressed
事件,您可以調用window.setKeyRepeatEnabled(false)
. 另一方面,很明顯,KeyReleased
事件永遠不會重演。
如果您想在按下或釋放某個鍵時僅觸發一次動作,例如使角色隨空格跳躍,或通過轉義退出某事,則可以使用此事件。
有時,人們試圖KeyPressed
直接對事件做出反應以實現平穩的運動。這樣做不會產生預期的效果,因為當你按住一個鍵時,你只會得到幾個事件(記住,重覆延遲)。要實現事件的平滑移動,您必須使用設置為 onKeyPressed
和 clear on的布爾值KeyReleased
;只要設置了布爾值,您就可以移動(獨立於事件)。
產生平滑移動的另一個(更簡單)解決方案是使用實時鍵盤輸入sf::Keyboard
(參見 專用教程)。
與這些事件關聯的成員是event.key
,它包含按下/釋放鍵的代碼,以及修飾鍵的當前狀態(alt、control、shift、system)。
if (event.type == sf::Event::KeyPressed)
{
if (event.key.code == sf::Keyboard::Escape)
{
std::cout << "the escape key was pressed" << std::endl;
std::cout << "control:" << event.key.control << std::endl;
std::cout << "alt:" << event.key.alt << std::endl;
std::cout << "shift:" << event.key.shift << std::endl;
std::cout << "system:" << event.key.system << std::endl;
}
}
請註意,某些鍵對操作系統具有特殊含義,會導致意外行為。例如,Windows 上的 F10 鍵“竊取”焦點,或者使用 Visual Studio 時啟動調試器的 F12 鍵。這可能會在 SFML 的未來版本中得到解決。
MouseWheelMoved 事件
sf::Event::MouseWheelMoved
事件自 SFML 2.3 起已棄用,請改用 MouseWheelScrolled 事件。
MouseWheelScrolled 事件
sf::Event::MouseWheelScrolled
當滑鼠滾輪向上或向下移動時觸發 該事件,如果滑鼠支持它,也會橫向觸發。
與此事件關聯的成員是event.mouseWheelScroll
,它包含滾輪移動的刻度數、滾輪的方向以及滑鼠游標的當前位置。
if (event.type == sf::Event::MouseWheelScrolled)
{
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
std::cout << "wheel type: vertical" << std::endl;
else if (event.mouseWheelScroll.wheel == sf::Mouse::HorizontalWheel)
std::cout << "wheel type: horizontal" << std::endl;
else
std::cout << "wheel type: unknown" << std::endl;
std::cout << "wheel movement: " << event.mouseWheelScroll.delta << std::endl;
std::cout << "mouse x: " << event.mouseWheelScroll.x << std::endl;
std::cout << "mouse y: " << event.mouseWheelScroll.y << std::endl;
}
MouseButtonPressed 和 MouseButtonReleased 事件
sf::Event::MouseButtonPressed
和事件 在sf::Event::MouseButtonReleased
按下/釋放滑鼠按鈕時觸發。
SFML 支持 5 個滑鼠按鈕:左鍵、右鍵、中鍵(滾輪)、額外的 #1 和額外的 #2(側鍵)。
與這些事件關聯的成員是event.mouseButton
,它包含按下/釋放按鈕的代碼,以及滑鼠游標的當前位置。
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.mouseButton.button == sf::Mouse::Right)
{
std::cout << "the right button was pressed" << std::endl;
std::cout << "mouse x: " << event.mouseButton.x << std::endl;
std::cout << "mouse y: " << event.mouseButton.y << std::endl;
}
}
滑鼠移動事件
sf::Event::MouseMoved
當滑鼠在視窗內移動時觸發 該事件。
即使視窗沒有聚焦,也會觸發此事件。但是,只有當滑鼠在視窗的內部區域內移動時才會觸發它,而不是在它移動到標題欄或邊框上時觸發。
與此事件關聯的成員是event.mouseMove
,它包含滑鼠游標相對於視窗的當前位置。
if (event.type == sf::Event::MouseMoved)
{
std::cout << "new mouse x: " << event.mouseMove.x << std::endl;
std::cout << "new mouse y: " << event.mouseMove.y << std::endl;
}
MouseEntered 和 MouseLeft 事件
sf::Event::MouseEntered
和事件 在sf::Event::MouseLeft
滑鼠游標進入/離開視窗時觸發。
sf::Event
union中沒有與這些事件相關的成員。
if (event.type == sf::Event::MouseEntered)
std::cout << "the mouse cursor has entered the window" << std::endl;
if (event.type == sf::Event::MouseLeft)
std::cout << "the mouse cursor has left the window" << std::endl;
鍵鼠
本部分解釋瞭如何訪問全局輸入設備:鍵盤、滑鼠和操縱桿。這不能與事件混淆。實時輸入讓您可以隨時查詢鍵盤、滑鼠和操縱桿的全局狀態(“當前是否按下此按鈕? ”、“當前滑鼠在哪裡? ”)而事件發生時通知您(“此按鈕被按下“,”滑鼠已移動“)。
鍵盤
提供對鍵盤狀態的訪問的類是sf::Keyboard
. 它只包含一個函數 ,isKeyPressed
它檢查一個鍵的當前狀態(按下或釋放)。它是一個靜態函數,因此您無需實例化sf::Keyboard
即可使用它。
此函數直接讀取鍵盤狀態,忽略視窗的焦點狀態。這意味著isKeyPressed
即使您的視窗處於非活動狀態,它也可能返回 true。(因此可能需要適當的判斷防止意外情況的發生)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
// left key is pressed: move our character
character.move(1.f, 0.f);
}
key codes在sf::Keyboard::Key
枚舉中定義。
class SFML_WINDOW_API Keyboard
{
public:
////////////////////////////////////////////////////////////
/// Key codes
////////////////////////////////////////////////////////////
enum Key
{
Unknown = -1, ///< Unhandled key
A = 0, ///< The A key
B, ///< The B key
C, ///< The C key
D, ///< The D key
...
KeyCount, ///< Keep last -- the total number of keyboard keys
// Deprecated values:
Dash = Hyphen, ///< Use Hyphen instead
BackSpace = Backspace, ///< Use Backspace instead
BackSlash = Backslash, ///< Use Backslash instead
SemiColon = Semicolon, ///< Use Semicolon instead
Return = Enter ///< Use Enter instead
}
...
}
根據您的操作系統和鍵盤佈局,某些鍵代碼可能會丟失或解釋不正確。這將在 SFML 的未來版本中得到改進。
滑鼠
提供對滑鼠狀態的訪問的類是sf::Mouse
. 和它的朋友sf::Keyboard
一樣, sf::Mouse
只包含靜態函數,並不打算實例化(SFML 暫時只處理單個滑鼠)。
您可以檢查按鈕是否被按下:
if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
// left mouse button is pressed: shoot
gun.fire();
}
滑鼠按鈕代碼在sf::Mouse::Button
枚舉中定義。SFML 最多支持 5 個按鈕:左、右、中間(滾輪)和兩個附加按鈕,無論它們是什麼。
您還可以獲取和設置滑鼠相對於桌面或視窗的當前位置:
// get the global mouse position (relative to the desktop)
sf::Vector2i globalPosition = sf::Mouse::getPosition();
// get the local mouse position (relative to a window)
sf::Vector2i localPosition = sf::Mouse::getPosition(window); // window is a sf::Window
// set the mouse position globally (relative to the desktop)
sf::Mouse::setPosition(sf::Vector2i(10, 50));
// set the mouse position locally (relative to a window)
sf::Mouse::setPosition(sf::Vector2i(10, 50), window); // window is a sf::Window
沒有讀取滑鼠滾輪當前狀態的功能。由於輪子只能相對移動,所以沒有可以查詢的絕對狀態。通過查看一個鍵,您可以判斷它是按下還是釋放。通過查看滑鼠游標,您可以知道它在屏幕上的位置。但是,查看滑鼠滾輪並不能告訴您它在哪個“刻度”上。所以只能在它移動(MouseWheelScrolled
事件)時收到通知。
小總結程式
#include <SFML/Graphics.hpp>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
sf::CircleShape shape(100.f);
shape.setFillColor(sf::Color::Green);
sf::Mouse::setPosition(sf::Vector2i(10, 50), window);
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "good byyyye" << std::endl;
window.close();
break;
case sf::Event::Resized:
std::cout << "height:" << event.size.height << std::endl;
std::cout << "weight:" << event.size.width << std::endl;
break;
case sf::Event::LostFocus:
std::cout << "hei! what are you doing!\n";
break;
case sf::Event::GainedFocus:
std::cout << "ok.." << std::endl;
break;
case sf::Event::KeyPressed:
std::cout << event.key.code << std::endl;
// std::boolalpha(std::cout);
// std::cout << "the escape key was pressed" << std::endl;
// std::cout << "control:" << event.key.control << std::endl;
// std::cout << "alt:" << event.key.alt << std::endl;
// std::cout << "shift:" << event.key.shift << std::endl;
// std::cout << "system:" << event.key.system << std::endl;
break;
case sf::Event::TextEntered:
if (event.text.unicode < 128)
std::cout << "ASCII character typed :" << static_cast<char>(event.text.unicode) << std::endl;
break;
case sf::Event::MouseWheelScrolled:
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
std::cout << "wheel type: vertical" << std::endl;
else if (event.mouseWheelScroll.wheel == sf::Mouse::HorizontalWheel)
std::cout << "wheel type: Horizontal" << std::endl;
else
std::cout << "while type: unknown" << std::endl;
std::cout << "wheel delta:" << event.mouseWheelScroll.delta << std::endl;
std::cout << "wheel x:" << event.mouseWheelScroll.x << std::endl;
std::cout << "wheel y:" << event.mouseWheelScroll.y << std::endl;
break;
case sf::Event::MouseButtonPressed:
if (event.mouseButton.button == sf::Mouse::Right)
std::cout << "right button pressed" << std::endl;
else if (event.mouseButton.button == sf::Mouse::Left)
std::cout << "left button pressed" << std::endl;
else if(event.mouseButton.button == sf::Mouse::Middle)
std::cout << "middle button pressed" << std::endl;
std::cout << "mouse x:" << event.mouseButton.x << std::endl;
std::cout << "mouse y:" << event.mouseButton.y << std::endl;
break;
case sf::Event::MouseButtonReleased:
if (event.mouseButton.button == sf::Mouse::Right)
std::cout << "right button Released" << std::endl;
else if (event.mouseButton.button == sf::Mouse::Left)
std::cout << "left button Released" << std::endl;
else if(event.mouseButton.button == sf::Mouse::Middle)
std::cout << "middle button Released" << std::endl;
std::cout << "mouse x:" << event.mouseButton.x << std::endl;
std::cout << "mouse y:" << event.mouseButton.y << std::endl;
break;
case sf::Event::MouseMoved:
// std::cout << "new mouse x " << event.mouseMove.x << std::endl;
// std::cout << "new mouse y " << event.mouseMove.y << std::endl;
default:
break;
}
}
window.clear();
window.draw(shape);
window.display();
}
return 0;
}
處理時間
SFML 中的時間
與許多其他時間是 uint32 毫秒數或浮點數秒數的庫不同,SFML 沒有為時間值強加任何特定的單位或類型。相反,它通過一個靈活的類將這個選擇留給用戶:sf::Time
. 所有處理時間值的 SFML 類和函數都使用這個類。
sf::Time
表示一個時間段(換句話說,兩個事件之間經過的時間)。它不是將當前年/月/日/小時/分鐘/秒錶示為時間戳的日期時間類,它只是表示一定時間量的值,如何解釋它取決於上下文的使用。
轉換時間
sf::Time
可以從不同的單位構造一個值:秒、毫秒和微秒。有一個(非成員)函數可以將它們中的每一個變成sf::Time
:
sf::Time t1 = sf::microseconds(10000);
sf::Time t2 = sf::milliseconds(10);
sf::Time t3 = sf::seconds(0.01f);
請註意,這三個時間都是相等的。
同樣,asf::Time
可以轉換回秒、毫秒或微秒:
sf::Time time = ...;
sf::Int64 usec = time.asMicroseconds();
sf::Int32 msec = time.asMilliseconds();
float sec = time.asSeconds();
計算時間
sf::Time
只是一個時間量,所以它支持算術運算,如加法、減法、比較等。時間也可以是負數。
sf::Time t1 = ...;
sf::Time t2 = t1 * 2;
sf::Time t3 = t1 + t2;
sf::Time t4 = -t3;
bool b1 = (t1 == t2);
bool b2 = (t3 > t4);
測量時間
現在我們已經瞭解瞭如何使用 SFML 操作時間值,讓我們看看如何做幾乎每個程式都需要的事情:測量經過的時間。
SFML 有一個非常簡單的時間測量類:sf::Clock
. 它只有兩個功能:getElapsedTime
, 檢索自時鐘啟動以來經過的時間,以及restart
, 重新啟動時鐘。
sf::Clock clock; // starts the clock
...
sf::Time elapsed1 = clock.getElapsedTime();
std::cout << elapsed1.asSeconds() << std::endl;
clock.restart();
...
sf::Time elapsed2 = clock.getElapsedTime();
std::cout << elapsed2.asSeconds() << std::endl;
請註意,調用restart
還會返回經過的時間,這樣您就可以避免 getElapsedTime
之前必須顯式調用時存在的微小間隙restart
。
這是一個使用游戲迴圈的每次迭代所經過的時間來更新游戲邏輯的示例:
sf::Clock clock;
while (window.isOpen())
{
sf::Time elapsed = clock.restart();
updateGame(elapsed);
...
}
用戶數據流
介紹
SFML 有幾個資源類:圖像、字體、聲音等。在大多數程式中,這些資源將藉助它們的 loadFromFile
功能從文件中載入。在其他一些情況下,資源將直接打包到可執行文件或大數據文件中,並使用loadFromMemory
. 這些功能幾乎涵蓋了所有可能的用例——但不是全部。
有時您想從不尋常的地方載入文件,例如壓縮/加密存檔或遠程網路位置。針對這些特殊情況,SFML 提供了第三種載入函數:loadFromStream
. 此函數使用抽象 sf::InputStream
介面讀取數據,它允許您提供自己的與 SFML 一起使用的流類的實現。
在本教程中,您將學習如何編寫和使用您自己的派生輸入流。
And標準流?
像許多其他語言一樣,C++ 已經有一個用於輸入數據流的類:std::istream
. 實際上它有兩個:std::istream
只是前端,自定義數據的抽象介面是std::streambuf
.
不幸的是,這些類對用戶不是很友好,如果你想實現一些重要的東西,它們會變得非常複雜。Boost.Iostreams庫試圖為標準流提供更簡單的介面,但 Boost 是一個很大的依賴項,SFML 不能依賴它。
這就是為什麼 SFML 提供了自己的流介面,希望它更加簡單和快速。
輸入流
該類sf::InputStream
聲明瞭四個虛函數:
class InputStream
{
public :
virtual ~InputStream() {}
virtual Int64 read(void* data, Int64 size) = 0;
virtual Int64 seek(Int64 position) = 0;
virtual Int64 tell() = 0;
virtual Int64 getSize() = 0;
};
read 必須從流中提取size個位元組的數據,並將它們複製到提供的數據地址。它返回讀取的位元組數,錯誤時返回 -1。
**seek **必須更改流中的當前讀取位置。它的位置參數是要跳轉到的絕對位元組偏移量(因此它是相對於數據的開頭,而不是相對於當前位置)。它返回新位置,或錯誤時返回 -1。
**tell **必須返迴流中的當前讀取位置(以位元組為單位),如果出錯則返回 -1。
**getSize **必須返回包含在流中的數據的總大小(以位元組為單位),如果出錯則返回 -1。
要創建自己的工作流,您必鬚根據他們的要求實現這四個功能中的每一個。
FileInputStream 和 MemoryInputStream
從 SFML 2.3 開始,創建了兩個新類來為新的內部音頻管理提供流。sf::FileInputStream
提供文件的只讀數據流,同時sf::MemoryInputStream
提供來自記憶體的只讀流。兩者都源自sf::InputStream
多態,因此可以使用多態。
使用輸入流
使用自定義流類很簡單:實例化它,並將其傳遞給要載入的對象 的loadFromStream
(或openFromStream
)函數。
sf::FileStream stream;
stream.open("image.png");
sf::Texture texture;
texture.loadFromStream(stream);
例子
如果您需要一個演示來幫助您專註於代碼的工作原理,而不是迷失在實現細節上,您可以查看sf::FileInputStream
或sf::MemoryInputStream
的實現。
不要忘記查看論壇和維基。很有可能另一個用戶已經編寫了一個sf::InputStream
適合您需要的類。如果您寫了一篇新文章,並且覺得它對其他人也有用,請不要猶豫分享!
常見錯誤
某些資源類在loadFromStream
被調用後沒有完全載入。相反,只要它們被使用,它們就會繼續從它們的數據源中讀取。sf::Music
在播放音頻樣本時流式傳輸音頻樣本,而對於 sf::Font
,它根據顯示的文本動態載入字形。
因此,您用於載入音樂或字體的流實例及其數據源必須在資源使用它時保持活動狀態。如果它在仍在使用時被銷毀,則會導致未定義的行為(可能是崩潰、損壞的數據或不可見)。
另一個常見的錯誤是返回內部函數直接返回的任何內容,但有時它與 SFML 所期望的不匹配。例如,在sf::FileInputStream
代碼中,可能會想將seek
函數編寫如下:
sf::Int64 FileInputStream::seek(sf::Int64 position)
{
return std::fseek(m_file, position, SEEK_SET);
}
此代碼是錯誤的,因為std::fseek
成功時返回零,而 SFML 期望返回新位置。
繪製 2D
介紹
正如在前面的教程中所瞭解的,SFML 的視窗模塊提供了一種簡單的方法來打開 OpenGL 視窗並處理其事件,但是在繪製某些東西時它並沒有幫助。留給您的唯一選擇是使用功能強大但複雜且低級的 OpenGL API。
幸運的是,SFML 提供了一個圖形模塊,它可以幫助您以比 OpenGL 更簡單的方式繪製 2D 實體。
繪圖視窗
要繪製圖形模塊提供的實體,您必須使用專門的視窗類:sf::RenderWindow
. 該類派生自sf::Window
,並繼承其所有功能。您所瞭解的所有內容sf::Window
(創建、事件處理、控制幀率、與 OpenGL 混合等)也適用sf::RenderWindow
。
最重要的是,sf::RenderWindow
添加高級功能以幫助您輕鬆繪製事物。在本教程中,我們將關註其中兩個函數:clear
和draw
. 它們就像它們的名字所暗示的那樣簡單:clear
用選定的顏色清除整個視窗,並draw
繪製你傳遞給它的任何對象。
這是帶有渲染視窗的典型主迴圈的樣子:
#include <SFML/Graphics.hpp>
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen())
{
// 檢查自上次迴圈迭代以來觸發的所有視窗事件
sf::Event event;
while (window.pollEvent(event))
{
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
// clear the window with black color
window.clear(sf::Color::Black);
// draw everything here...
// window.draw(...);
// end the current frame
window.display();
}
return 0;
}
在繪製任何內容之前調用clear
是強制性的,否則之前幀的內容將出現在您繪製的任何內容後面。唯一的例外是當您用繪製的內容覆蓋整個視窗時,不會繪製任何像素。在這種情況下,您可以避免調用clear
(儘管它不會對性能產生明顯影響)。
調用display
也是強制性的,它獲取自上次調用以來繪製的內容display
並將其顯示在視窗上。確實,事物不是直接繪製到視窗,而是繪製到隱藏的緩衝區。然後在您調用時將此緩衝區複製到視窗display
- 這稱為雙緩衝。
這種清除/繪製/顯示迴圈是繪製事物的唯一好方法。不要嘗試其他策略,例如保留前一幀的像素,“擦除”像素,或繪製一次並多次調用 display。由於雙緩衝,你會得到奇怪的結果。
現代圖形硬體和 API確實是為重覆的清除/繪製/顯示迴圈而設計的,在主迴圈的每次迭代中,所有內容都會完全刷新。不要害怕每秒繪製 1000 個精靈 60 次,你遠遠低於電腦可以處理的數百萬個三角形。
我現在可以畫什麼?
現在您已經準備好繪製一個主迴圈,讓我們看看您可以在那裡實際繪製什麼以及如何繪製。
SFML 提供了四種可繪製實體:其中三種可供使用(精靈、文本和形狀),最後一種是幫助您創建自己的可繪製實體(頂點數組)的構建塊。
儘管它們具有一些共同的屬性,但這些實體中的每一個都有自己的細微差別,因此在專門的教程中進行瞭解釋:
準備程式
#include <SFML/Graphics.hpp>
#include <bits/stdc++.h>
int main() {
sf::RenderWindow window(sf::VideoMode(500, 500), "title");
while (window.isOpen()) {
sf::Event event{};
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
std::cout << "success exit" << std::endl;
window.close();
break;
default:
break;
}
}
window.clear();
window.display();
}
return 0;
}
精靈圖和紋理
什麼是精靈圖
CSS Sprites通常被稱為css精靈圖, 在國內也被意譯為css圖片整合和css貼圖定位,也有人稱他為雪碧圖。 就是將導航的背景圖,按鈕的背景圖等有規則的合併成一張背景圖,即多張圖合併為一張整圖, 然後再利用background-position進行背景圖定位的一種技術
(下麵都翻譯成精靈)
辭彙
大多數人(如果不是全部)已經熟悉這兩個非常常見的對象,所以讓我們非常簡要地定義它們。
紋理是圖像。但我們稱其為“紋理”,因為它具有非常特殊的作用:被映射到 2D 實體。
精靈只不過是一個帶紋理的矩形。
好的,這很簡短,但如果您真的不瞭解精靈和紋理是什麼,那麼您會在 Wikipedia 上找到更好的描述。
載入紋理
在創建任何精靈之前,我們需要一個有效的紋理。令人驚訝的是,在 SFML 中封裝紋理的類是sf::Texture
. 由於紋理的唯一作用是載入和映射到圖形實體,因此幾乎所有的功能都是關於載入和更新它。
載入紋理的最常見方法是從磁碟上的圖像文件,這是通過loadFromFile
函數完成的。
sf::Texture texture;
if (!texture.loadFromFile("image.png"))
{
// error...
}
該loadFromFile
功能有時會在沒有明顯原因的情況下失敗。首先,檢查 SFML 列印到標準輸出的錯誤消息(檢查控制台)。如果消息無法打開文件,請確保工作目錄(任何文件路徑都將被解釋為相對的目錄)是您認為的:當您從桌面環境運行應用程式時,工作目錄是可執行文件夾。但是,當您從 IDE(Visual Studio、Code::Blocks、...)啟動程式時,有時可能會將工作目錄設置為項目目錄。這通常可以在項目設置中很容易地更改。
您還可以從記憶體 ( loadFromMemory
)、自 定義輸入流( loadFromStream
) 或已載入的圖像( ) 載入圖像文件loadFromImage
。後者從 載入紋理sf::Image
,這是一個實用程式類,可幫助存儲和操作圖像數據(修改像素,創建透明度通道等)。留在系統記憶體中的像素sf::Image
,確保對它們的操作將儘可能快,與駐留在視頻記憶體中的紋理像素形成對比,因此檢索或更新緩慢但繪製速度非常快。
SFML 支持最常見的圖像文件格式。API 文檔中提供了完整列表。
所有這些載入函數都有一個可選參數,如果你想載入圖像的一小部分,可以使用它。
// load a 32x32 rectangle that starts at (10, 10)
if (!texture.loadFromFile("image.png", sf::IntRect(10, 10, 32, 32)))
{
// error...
}
該類sf::IntRect
是一個表示矩形的簡單實用程式類型。它的構造函數獲取左上角的坐標和矩形的大小。
如果您不想從圖像載入紋理,而是想直接從像素數組更新它,您可以將其創建為空並稍後更新:
// create an empty 200x200 texture
if (!texture.create(200, 200))
{
// error...
}
請註意,此時紋理的內容是未定義的。
要更新現有紋理的像素,您必須使用該update
函數。它具有多種數據源的重載:
// update a texture from an array of pixels
sf::Uint8* pixels = new sf::Uint8[width * height * 4]; // * 4 because pixels have 4 components (RGBA)
...
texture.update(pixels);
// update a texture from a sf::Image
sf::Image image;
...
texture.update(image);
// update the texture from the current contents of the window
sf::RenderWindow window;
...
texture.update(window);
這些示例都假設源與紋理大小相同。如果不是這種情況,即如果您只想更新紋理的一部分,您可以指定要更新的子矩形的坐標。您可以參考文檔以獲取更多詳細信息。
此外,紋理有兩個屬性可以改變它的渲染方式。
第一個屬性允許平滑紋理。平滑紋理使像素邊界不那麼明顯(但圖像更模糊),如果放大它可能是可取的。
texture.setSmooth(true);
由於對紋理中相鄰像素的採樣也進行了平滑處理,因此可能會導致不希望的副作用,即在選定紋理區域之外考慮像素。當您的精靈位於非整數坐標時,可能會發生這種情況。
第二個屬性允許紋理在單個精靈中重覆平鋪。
texture.setRepeated(true);
這僅在您的精靈配置為顯示大於紋理的矩形時才有效,否則此屬性無效。
好的,我現在可以擁有我的精靈了嗎?
是的,你現在可以創建你的精靈了。
sf::Sprite sprite;
sprite.setTexture(texture);
……最後畫出來。
// inside the main loop, between window.clear() and window.display()
window.draw(sprite);
如果你不想讓你的精靈使用整個紋理,你可以設置它的紋理矩形。
sprite.setTextureRect(sf::IntRect(10, 10, 32, 32));
您還可以更改精靈的顏色。您設置的顏色會隨著精靈的紋理進行調製(相乘)。這也可以用來改變精靈的全局透明度(alpha)。
sprite.setColor(sf::Color(0, 255, 0)); // green
sprite.setColor(sf::Color(255, 255, 255, 128)); // half transparent
這些精靈都使用相同的紋理,但顏色不同:
精靈也可以變換:它們有一個位置、一個方向和一個比例。
// position
sprite.setPosition(sf::Vector2f(10.f, 50.f)); // absolute position
sprite.move(sf::Vector2f(5.f, 10.f)); // offset relative to the current position
// rotation
sprite.setRotation(90.f); // absolute angle
sprite.rotate(15.f); // offset relative to the current angle
// scale
sprite.setScale(sf::Vector2f(0.5f, 2.f)); // absolute scale factor
sprite.scale(sf::Vector2f(1.5f, 3.f)); // factor relative to the current scale
預設情況下,這三個變換的原點是精靈的左上角。如果您想將原點設置為不同的點(例如精靈的中心,或另一個角),您可以使用該setOrigin
功能。
sprite.setOrigin(sf::Vector2f(25.f, 25.f));
由於轉換函數對所有 SFML 實體都是通用的,因此在單獨的教程中對它們進行了說明: 轉換實體。
白方塊問題
您成功載入了紋理,正確構建了精靈,並且……您現在在屏幕上看到的只是一個白色方塊。發生了什麼?
這是一個常見的錯誤。當您設置精靈的紋理時,它在內部所做的只是存儲指向紋理實例的指針。因此,如果紋理被破壞或移動到記憶體中的其他位置,則精靈最終會得到一個無效的紋理指針。
編寫此類函數時會出現此問題:
sf::Sprite loadSprite(std::string filename)
{
sf::Texture texture;
texture.loadFromFile(filename);
return sf::Sprite(texture);
} // error: the texture is destroyed here
您必須正確管理紋理的生命周期,並確保它們在被任何精靈使用時一直存在。
使用儘可能少的紋理的重要性
使用儘可能少的紋理是一個好策略,原因很簡單:更改當前紋理對於顯卡來說是一項昂貴的操作。繪製許多使用相同紋理的精靈將產生最佳性能。
此外,使用單個紋理允許您將靜態幾何體組合到單個實體中(每次調用只能使用一個紋理draw
),這將比一組許多實體更快地繪製。批處理靜態幾何體涉及其他類,因此超出了本教程的範圍,有關更多詳細信息,請參閱頂點數組教程。
在創建動畫表或圖塊集時請記住這一點:儘可能少地使用紋理。
在 OpenGL 代碼中使用 sf::Texture
如果您使用的是 OpenGL 而不是 SFML 的圖形實體,您仍然可以將sf::Texture
其用作 OpenGL 紋理對象的包裝器,並將其與其餘的 OpenGL 代碼一起使用。
要綁定sf::Texture
繪圖(基本上glBindTexture
),您調用bind
靜態函數:
sf::Texture texture;
...
// bind the texture
sf::Texture::bind(&texture);
// draw your textured OpenGL entity here...
// bind no texture
sf::Texture::bind(NULL);
離屏繪圖
SFML 還提供了一種繪製到紋理而不是直接繪製到視窗的方法。為此,請使用 asf::RenderTexture
而不是 sf::RenderWindow
。它具有相同的繪圖功能,繼承自它們的共同基礎:sf::RenderTarget
.
// create a 500x500 render-texture
sf::RenderTexture renderTexture;
if (!renderTexture.create(500, 500))
{
// error...
}
// drawing uses the same functions
renderTexture.clear();
renderTexture.draw(sprite); // or any other drawable
renderTexture.display();
// get the target texture (where the stuff has been drawn)
const sf::Texture& texture = renderTexture.getTexture();
// draw it to the window
sf::Sprite sprite(texture);
window.draw(sprite);
該getTexture
函數返回一個只讀紋理,這意味著您只能使用它,不能修改它。如果您需要在使用前對其進行修改,您可以將其複製到您自己的sf::Texture
實例中併進行修改。
sf::RenderTexture
還具有與處理視圖和 OpenGL 相同的功能sf::RenderWindow
(有關詳細信息,請參閱相應的教程)。如果您使用 OpenGL 繪製到渲染紋理,您可以使用函數的第三個可選參數請求創建深度緩衝區create
。
renderTexture.create(500, 500, true); // enable depth buffer
從線程中繪製
SFML 支持多線程繪圖,你甚至不需要做任何事情來讓它工作。唯一要記住的是在另一個線程中使用它之前停用一個視窗。這是因為一個視窗(更準確地說是它的 OpenGL 上下文)不能同時在多個線程中處於活動狀態。
void renderingThread(sf::RenderWindow* window)
{
// activate the window's context
window->setActive(true);
// the rendering loop
while (window->isOpen())
{
// draw...
// end the current frame
window->display();
}
}
int main()
{
// create the window (remember: it's safer to create it in the main thread due to OS limitations)
sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL");
// deactivate its OpenGL context
window.setActive(false);
// launch the rendering thread
sf::Thread thread(&renderingThread, &window);
thread.launch();
// the event/logic/whatever loop
while (window.isOpen())
{
...
}
return 0;
}
如您所見,您甚至不需要在渲染線程中激活視窗,SFML 會在需要時自動為您完成。
請記住始終在主線程中創建視窗並處理其事件,以獲得最大的可移植性。
持續更新中。。。