Skip to content

ControllerFont

Chuck Walbourn edited this page Nov 14, 2018 · 4 revisions

This is a C++ implementation of a helper that will composite Xbox controller artwork into a string inspired by this XNA tutorial.

Controller font test

For details on creating the xboxController.spritefont file, see MakeSpriteFont.

ControllerFont.h

enum class ControllerFont : wchar_t
{
    LeftThumb = L' ',
    DPad = L'!',
    RightThumb = L'\"',
    Back = L'#',
    Guide = L'$',
    Start = L'%',
    XButton = L'&',
    AButton = L'\'',
    YButton = L'(',
    BButton = L')',
    RightShoulder = L'*',
    RightTrigger = L'+',
    LeftTrigger = L',',
    LeftShoulder = L'-',
};

inline void XM_CALLCONV DrawControllerString(DirectX::SpriteBatch* spriteBatch,
    DirectX::SpriteFont* textFont, DirectX::SpriteFont* butnFont,
    wchar_t const* text, DirectX::XMFLOAT2 const& position,
    DirectX::FXMVECTOR color = DirectX::Colors::White, float scale = 1)
{
    using namespace DirectX;

    size_t textLen = wcslen(text);
    if (textLen >= 4096)
    {
        throw std::out_of_range("String is too long");
    }

    float buttonHeight = butnFont->GetLineSpacing();
    float buttonScale = (textFont->GetLineSpacing() * scale) / buttonHeight;
    float offsetY = buttonScale / 2.f;

    size_t j = 0;
    wchar_t strBuffer[4096] = {};

    bool buttonText = false;

    XMFLOAT2 outPos = position;

    for (size_t ch = 0; ch < textLen; ++ch)
    {
        if (buttonText)
        {
            strBuffer[j++] = text[ch];

            if (text[ch] == L']')
            {
                wchar_t button[2] = {};

                if (_wcsicmp(strBuffer, L"[A]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::AButton);
                }
                else if (_wcsicmp(strBuffer, L"[B]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::BButton);
                }
                else if (_wcsicmp(strBuffer, L"[X]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::XButton);
                }
                else if (_wcsicmp(strBuffer, L"[Y]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::YButton);
                }
                else if (_wcsicmp(strBuffer, L"[DPad]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::DPad);
                }
                else if (_wcsicmp(strBuffer, L"[Back]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::Back);
                }
                else if (_wcsicmp(strBuffer, L"[Start]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::Start);
                }
                else if (_wcsicmp(strBuffer, L"[Guide]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::Guide);
                }
                else if (_wcsicmp(strBuffer, L"[RThumb]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::RightThumb);
                }
                else if (_wcsicmp(strBuffer, L"[LThumb]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::LeftThumb);
                }
                else if (_wcsicmp(strBuffer, L"[RB]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::RightShoulder);
                }
                else if (_wcsicmp(strBuffer, L"[LB]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::LeftShoulder);
                }
                else if (_wcsicmp(strBuffer, L"[RT]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::RightTrigger);
                }
                else if (_wcsicmp(strBuffer, L"[LT]") == 0)
                {
                    *button = static_cast<wchar_t>(ControllerFont::LeftTrigger);
                }

                if ( *button )
                {
                    float bsize = XMVectorGetX(butnFont->MeasureString(button));
                    float offsetX = (bsize * buttonScale / 2.f);

                    outPos.x += offsetX;
                    outPos.y -= offsetY;
                    butnFont->DrawString(spriteBatch, button, outPos, Colors::White, 0.f, XMFLOAT2(0.f, 0.f), XMFLOAT2(buttonScale, buttonScale));
                    outPos.x += bsize * buttonScale;
                    outPos.y += offsetY;
                }

                memset(strBuffer, 0, sizeof(strBuffer));
                j = 0;

                buttonText = false;
            }
        }
        else
        {
            switch (text[ch])
            {
            case '\r':
                break;

            case '[':
                if (*strBuffer)
                {
                    textFont->DrawString(spriteBatch, strBuffer, outPos, color, 0.f, XMFLOAT2(0.f, 0.f), XMFLOAT2(scale,scale));
                    outPos.x += XMVectorGetX(textFont->MeasureString(strBuffer)) * scale;
                    memset(strBuffer, 0, sizeof(strBuffer));
                    j = 0;
                }
                buttonText = true;
                *strBuffer = L'[';
                ++j;
                break;

            case '\n':
                if (*strBuffer)
                {
                    textFont->DrawString(spriteBatch, strBuffer, outPos, color, 0.f, XMFLOAT2(0.f, 0.f), XMFLOAT2(scale, scale));
                    memset(strBuffer, 0, sizeof(strBuffer));
                    j = 0;
                }
                outPos.x = position.x;
                outPos.y += textFont->GetLineSpacing() * scale;
                break;

            default:
                strBuffer[j++] = text[ch];
                break;
            }
        }
    }

    if (*strBuffer)
    {
        textFont->DrawString(spriteBatch, strBuffer, outPos, color, 0.f, XMFLOAT2(0.f, 0.f), XMFLOAT2(scale, scale));
    }
}

Example

To use the controller, you need to create and initialize two SpriteFont instances, and a SpriteBatch. Declare the following variables:

std::unique_ptr<DirectX::SpriteBatch> m_batch;
std::unique_ptr<DirectX::SpriteFont> m_font;
std::unique_ptr<DirectX::SpriteFont> m_ctrlFont;

Initialize the instances:

SpriteBatchPipelineStateDescription pd(rtState);
m_batch = std::make_unique<SpriteBatch>(device, upload, pd);

m_font = std::make_unique<SpriteFont>(device, upload,
    L"myfont.spritefont",
    m_resourceDescriptors->GetCpuHandle(Descriptors::TextFont),
    m_resourceDescriptors->GetGpuHandle(Descriptors::TextFont));

m_ctrlFont = std::make_unique<SpriteFont>(device, upload,
    L"xboxController.spritefont",
    m_resourceDescriptors->GetCpuHandle(Descriptors::ControllerFont),
    m_resourceDescriptors->GetGpuHandle(Descriptors::ControllerFont));

In the Render function, you use the helper to draw the composed text:

auto heap = m_resourceDescriptors->Heap();
commandList->SetDescriptorHeaps(1, &heap);

m_batch->Begin(commandList);

DX::DrawControllerString(m_batch.get(), m_font.get(), m_ctrlFont.get(),
    L"Press [A] to select", pos);
pos.y += m_font->GetLineSpacing();

DX::DrawControllerString(m_batch.get(), m_font.get(), m_ctrlFont.get(),
    L"Press [B] to backup", pos);

m_batch->End();

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Xbox One
  • Xbox Series X|S

Architecture

  • x86
  • x64
  • ARM64

For Development

  • Visual Studio 2022
  • Visual Studio 2019 (16.11)
  • clang/LLVM v12 - v18
  • MinGW 12.2, 13.2
  • CMake 3.20

Related Projects

DirectX Tool Kit for DirectX 11

DirectXMesh

DirectXTex

DirectXMath

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

See also

DirectX Landing Page

Clone this wiki locally