主頁 > 後端開發 > SMFL 筆記

SMFL 筆記

2022-05-18 00:20:37 後端開發

本文大部分來自官方教程的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 沒有明確管理多個監視器,因此,您將無法選擇視窗出現在哪個監視器上,并且您將無法創建多個全屏視窗,這應該在未來的版本中得到改進,

必須在視窗的執行緒中輪詢事件

這是大多數作業系統的一個重要限制:事件回圈(更準確地說是pollEventorwaitEvent函式)必須在創建視窗的同一執行緒中呼叫,這意味著如果你想為事件處理創建一個專用執行緒,你必須確保視窗也是在這個執行緒中創建的,如果您真的想在執行緒之間拆分事物,將事件處理保留在主執行緒中并將其余部分(渲染、物理、邏輯......)移到單獨的執行緒中會更方便,這種配置也將與下面描述的其他限制兼容,

在 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::Eventunion型別是導致沒有經驗的程式員許多bug的原因,

好的,現在我們可以看到 SFML 支持哪些事件,它們的含義以及如何正確使用它們,

關閉的事件

sf::Event::Closed當用戶想要關閉視窗時,通過視窗管理器提供的任何可能的方法(“關閉”按鈕、鍵盤快捷鍵等)觸發 該事件,該事件僅代表關閉請求,收到事件時視窗尚未關閉,

典型的代碼只會呼叫window.close()這個事件來實際關閉視窗,但是,您可能還想先做其他事情,例如保存當前應用程式狀態或詢問用戶要做什么,如果您不執行任何操作,視窗將保持打開狀態,

sf::Eventunion 中沒有與此事件相關的成員,

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::Eventunion中沒有與這些事件相關的成員,

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::Eventunion中沒有與這些事件相關的成員,

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);

請注意,這三個時間都是相等的,

同樣,sf::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::FileInputStreamsf::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添加高級功能以幫助您輕松繪制事物,在本教程中,我們將關注其中兩個函式:cleardraw. 它們就像它們的名字所暗示的那樣簡單: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(700, 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、...)啟動程式時,有時可能會將作業目錄設定為專案目錄,這通常可以在專案設定中很容易地更改,(在Clion中是從可執行檔案為起點的)

您還可以從記憶體 ( 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);

重復vs不重復

這僅在您的精靈配置為顯示大于紋理的矩形時才有效,否則此屬性無效,

好的,我現在可以擁有我的精靈了嗎?

是的,你現在可以創建你的精靈了,

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);

小總結程式

在根目錄下access/pi'c中放了一張圖片loading,png 程式將不停的旋轉pic該圖片

loading
#include <SFML/Graphics.hpp>
//#include <GLFW/glfw3.h>
#include <bits/stdc++.h>

int main() {
    sf::RenderWindow window(sf::VideoMode(700, 500), "title");
    window.setVerticalSyncEnabled(true);
    sf::Texture texture;
    if (!texture.loadFromFile("../access/pic/loading.png")) {
        std::cerr << "load texture failed!" << std::endl;
    }
    sf::Sprite sprite;
    sprite.setTexture(texture);
    sprite.scale((float) window.getSize().x / (float) texture.getSize().x,
                 (float) window.getSize().y / (float) texture.getSize().y);
    sprite.setOrigin((float) window.getSize().x / 2.f,
                     (float) window.getSize().y / 2.f);
    sprite.setPosition((float) window.getSize().x / 2.f,
                       (float) window.getSize().y / 2.f);

    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();
        sprite.rotate(1.f);
        window.draw(sprite);

        window.display();
    }
    return 0;
}

離屏繪圖

SFML 還提供了一種繪制到紋理而不是直接繪制到視窗的方法,為此,請使用 sf::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

從執行緒中繪制*(此處用std::thread更好)

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 會在需要時自動為您完成,

請記住始終在主執行緒中創建視窗并處理其事件,以獲得最大的可移植性,

文字和字體

加載字體

在繪制任何文本之前,您需要有一個可用的字體,就像任何其他列印文本的程式一樣,字體封裝在sf::Font 該類中,該類提供三個主要功能:加載字體、從中獲取字形(即視覺字符)以及讀取其屬性,在一個典型的程式中,你只需要使用第一個特性,加載字體,所以讓我們首先關注它,

加載字體最常見的方法是從磁盤上的檔案中加載,這是通過loadFromFile函式完成的,

sf::Font font;
if (!font.loadFromFile("arial.ttf"))
{
    // error...
}

請注意,SFML 不會自動加載您的系統字體,即font.loadFromFile("Courier New")不會作業,首先,因為 SFML 需要檔案名,而不是字體名稱,其次,因為 SFML沒有對系統字體檔案夾的神奇訪問權限,如果要加載字體,則需要在應用程式中包含字體檔案,就像所有其他資源(影像、聲音等)一樣,

在windows下 C:\Windows\Fonts 中就有字體檔案 可以將其復制到 access/font

推薦自然是 consola 字體啦

sf::Font font;
    if (!font.loadFromFile("../access/font/consola.ttf")) {
        std::cerr << "load texture failed!" << std::endl;
    }

loadFromFile功能有時會在沒有明顯原因的情況下失敗,首先,檢查 SFML 列印到標準輸出的錯誤訊息(檢查控制臺),如果訊息無法打開檔案,請確保作業目錄(任何檔案路徑都將被解釋為相對的目錄)是您認為的:當您從桌面環境運行應用程式時,作業目錄是可執行檔案夾,但是,當您從 IDE啟動程式時,有時可能會將作業目錄設定為專案目錄,這通常可以在專案設定中很容易地更改,

您還可以從記憶體 ( loadFromMemory) 或自定義輸入流( loadFromStream) 加載字體檔案,

SFML 支持最常見的字體格式,API 檔案中提供了完整串列,

這就是你需要做的,加載字體后,您可以開始繪制文本,

繪圖文本

要繪制文本,您將使用sf::Text該類,使用非常簡單:

sf::Text text;

// select the font
text.setFont(font); // font is a sf::Font

// set the string to display
text.setString("Hello world");

// set the character size
text.setCharacterSize(24); // in pixels, not points!

// set the color
text.setFillColor(sf::Color::Red);

// set the text style
text.setStyle(sf::Text::Bold | sf::Text::Underlined);

...

// inside the main loop, between window.clear() and window.display()
window.draw(text);

繪圖文本

文本也可以轉換:它們具有位置、方向和比例,涉及的功能與 sf::Sprite類和其他 SFML 物體的功能相同,它們在 轉換物體教程中進行了解釋,

小總結程式

在圖片的基礎上加了一行loading...

#include <SFML/Graphics.hpp>
//#include <GLFW/glfw3.h>
#include <bits/stdc++.h>

int main() {
    sf::RenderWindow window(sf::VideoMode(700, 500), "title");
    window.setVerticalSyncEnabled(true);
    sf::Texture texture;
    if (!texture.loadFromFile("../access/pic/loading.png")) {
        std::cerr << "load texture failed!" << std::endl;
    }
    sf::Sprite sprite;
    sprite.setTexture(texture);
    sprite.scale((float) window.getSize().x / (float) texture.getSize().x,
                 (float) window.getSize().y / (float) texture.getSize().y);
    sprite.setOrigin((float) window.getSize().x / 2.f,
                     (float) window.getSize().y / 2.f);
    sprite.setPosition((float) window.getSize().x / 2.f,
                       (float) window.getSize().y / 2.f);

    sf::Font font;
    if (!font.loadFromFile("../access/font/consola.ttf")) {
        std::cerr << "load texture failed!" << std::endl;
    }
    sf::Text text;
    text.setFont(font);
    text.setString("loading...");
    text.setCharacterSize(50);
    text.setFillColor(sf::Color::Blue);
    text.setPosition((float) window.getSize().x / 2.f,
                     (float) window.getSize().y / 2.f);


    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();
        sprite.rotate(1.f);
        window.draw(sprite);
        window.draw(text);

        window.display();
    }
    return 0;
}

如何避免非 ASCII 字符的問題?

正確處理非 ASCII 字符(例如重音歐洲字符、阿拉伯字符或中文字符)可能很棘手,它需要對解釋和繪制文本程序中涉及的各種編碼有很好的理解,為了避免這些編碼的困擾,有一個簡單的解決方案:使用寬文本字串

text.setString(L"????");

正是字串前面的這個簡單的“L”前綴通過告訴編譯器生成一個寬字串來使其作業,寬字串在 C++ 中是一種奇怪的野獸:標準沒有說明它們的大小(16 位?32 位?),也沒有說明它們使用的編碼(UTF-16?UTF-32?),但是我們知道,在大多數平臺上(如果不是全部),它們都會生成 Unicode 字串,并且 SFML 知道如何正確處理它們,

請注意,C++11 標準支持新的字符型別和前綴來構建 UTF-8、UTF-16 和 UTF-32 字串文字,但 SFML 還不支持它們,

這似乎很明顯,但您還必須確保您使用的字體包含您要繪制的字符,實際上,字體并不包含所有可能字符的字形(Unicode 標準中有超過 100000 個字形!),例如,阿拉伯字體將無法顯示日文文本,

制作自己的文本類

Ifsf::Text太有限,或者如果你想用預渲染的字形做其他事情,sf::Font提供你需要的一切,

您可以檢索包含特定大小的所有預渲染字形的紋理:

const sf::Texture& texture = font.getTexture(characterSize);

需要注意的是,字形會在請求時添加到紋理中,字符太多(記住,超過 100000 個),加載字體時無法全部生成,相反,它們會在您呼叫getGlyph函式時即時呈現(見下文),

要對字體紋理做一些有意義的事情,您必須獲取其中包含的字形的紋理坐標:

sf::Glyph glyph = font.getGlyph(character, characterSize, bold);

character是要獲取其字形的字符的 UTF-32 代碼,您還必須指定字符大小,以及是否需要粗體或常規版本的字形,

sf::Glyph結構包含三個成員:

  • textureRect包含紋理內字形的紋理坐標
  • bounds包含字形的邊界矩形,這有助于相對于文本的基線定位它
  • advance是用于獲取文本中下一個字形的起始位置的水平偏移量

您還可以獲得一些字體的其他指標,例如兩個字符之間的字距或行間距(總是針對特定字符大小):

int lineSpacing = font.getLineSpacing(characterSize);

int kerning = font.getKerning(character1, character2, characterSize);

持續更新中,,,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/476461.html

標籤:C++

上一篇:構建專案時,單元測驗是僅手動執行還是自動執行?

下一篇:第十五章 面向物件程式設計

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more