今日のプログラミング

このコーナーは、主(奥海清瀧)が自由にプログラミングについて書いていくページです。休みの日に2時間ほどプログラミングをやっていきます。

2025/12/7(日)

はじまり

さて、はじまりましたこのコーナー。主が休みの日に2時間ほど使って自由にプログラミングしていくコーナーです。説明しながらやっていくので、あまり内容を書けないし、いろいろ端折ったりすると思いますが、そこはご愛敬でお願いいたします。何をやっていくのかというと、自分が思うようにプログラミングしていき、その説明をするだけです。筆者は基本的にC++のDirect2Dか、JavaScriptのCanvasを使ってグラフィックスプログラミングを主にやっていきます。やはり見た目でわかりやすいほうが楽しめると思いませんか?

今日やるのはC++です。プログラミングをするといったらC++が真っ先に思い浮かびます。C++はJavaScriptやPythonよりも圧倒的に高速で動作し、静的型付き言語なので、プログラミングをしている感が強いものです。ただ、Direct2Dで描画しようと思うと、最初のコードが結構長くなってしまいます。慣れれば高速で書いて15分ほどで書けるかと思われます。ただ、コードを書いているだけでもプログラミングした気になれるので、ただプログラミングがしたいといった場合にはDirect2Dで描画しても良いでしょう。てなわけで、Direct2Dで円を描画します。


基本

Visual Studioを開いたら、[新しいプロジェクトの作成]→[Windowsデスクトップウィザード]を選択し、プロジェクト名を入れ、[作成]を押し、アプリケーションの種類を[デスクトップアプリケーション]、追加のオプションで[空のプロジェクト]にチェックを入れ、[作成]を押します。

プロジェクトを作成したら、ソリューションエクスプローラーのソースファイルを右クリックして、新しいファイルを追加しましょう。ここでは「main.cpp」とすることにします。コードを書く部分に、ウィンドウ作成のコードを書いていきましょう。次のコードを入力します。

main.cpp
#include <windows.h>

const int WIDTH = 640;
const int HEIGHT = 480;
const wchar_t TITLE[] = L"タイトル";

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

HWND m_hwnd;

int WINAPI wWinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrev, _In_ PWSTR pCmd, _In_ int nCmd)
{
    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInst;
    wc.lpszClassName = L"Window Class";
    RegisterClass(&wc);

    RECT rc = { 0, 0, WIDTH, HEIGHT };
    AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
    m_hwnd = CreateWindowEx(0, wc.lpszClassName, TITLE,
        WS_OVERLAPPEDWINDOW ^ WS_SIZEBOX ^ WS_MAXIMIZEBOX,
        CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top,
        NULL, NULL, hInst, NULL);
    if (m_hwnd == NULL) return 0;

    ShowWindow(m_hwnd, nCmd);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

実行するとウィンドウが表示されるはずです。大事なのは、WIDTHにウィンドウの幅、HEIGHTに高さ、TITLEにウィンドウのタイトルを入れていることと、wWinMainが最初に実行される関数で、WindowProcがイベントを処理する関数となっていることです。ウィンドウの作成は定型として覚えるのが無難です。

次に、Direct2Dを使って背景を塗ります。次のコードを書きましょう。

main.cpp
#include <windows.h>
#include <d2d1.h>
#pragma comment(lib, "d2d1")

#include <atlbase.h>

using D2D1::ColorF;

const int WIDTH = 640;
const int HEIGHT = 480;
const wchar_t TITLE[] = L"タイトル";

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

HRESULT CreateResources();
void OnPaint();

HWND m_hwnd;

CComPtr<ID2D1Factory> factory;
CComPtr<ID2D1HwndRenderTarget> rt;
CComPtr<ID2D1SolidColorBrush> brush;

≀省略

LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
        if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory)))
        {
            return -1;
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        OnPaint();
        return 0;
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

HRESULT CreateResources()
{
    HRESULT hr = S_OK;
    if (rt == NULL)
    {
        D2D1_SIZE_U size = D2D1::SizeU(WIDTH, HEIGHT);
        hr = factory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(m_hwnd, size), &rt);
        if (SUCCEEDED(hr))
        {
            hr = rt->CreateSolidColorBrush(ColorF(ColorF::Black), &brush);
        }
    }
    return hr;
}

void OnPaint()
{
    HRESULT hr = CreateResources();
    if (SUCCEEDED(hr))
    {
        PAINTSTRUCT ps;
        BeginPaint(m_hwnd, &ps);
        rt->BeginDraw();

        rt->Clear(ColorF(ColorF::Ivory));

        rt->EndDraw();
        EndPaint(m_hwnd, &ps);
        InvalidateRect(m_hwnd, NULL, FALSE);
    }
}

実行したら、ウィンドウの背景が薄い黄色で塗られているはずです。ここで大事なのは、OnPaint関数のrt->Clear()で背景を薄い黄色にしている点です。最後のInvalidateRect関数で画面を更新しています。


円を描く

どうやってページを書くか考えていたら長くなってしまったので、今回は円を描いて終わりにしましょう。また、一つのファイルにウィンドウの作成とDirect2Dの基本コード、円の描画まで書くと見にくくなってきたので、次回からはちゃんとファイル分割して見やすくしていきます。とりあえず、円を描く関数を作り、円を描いて終わりましょう。OnPaint関数の上にDrawCircle関数を作り、OnPaintの中で呼び出します。次のコードを書きましょう。

main.cpp
≀
    
void DrawCircle(float x, float y, float r, ColorF col)
{
	D2D1_ELLIPSE ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), r, r);
	brush->SetColor(col);
	rt->FillEllipse(ellipse, brush);
}

void OnPaint()
{
	HRESULT hr = CreateResources();
	if (SUCCEEDED(hr))
	{
		≀

		rt->Clear(ColorF(ColorF::Ivory));

		DrawCircle(WIDTH / 2, HEIGHT / 2, 30, ColorF(ColorF::Blue));

		≀
	}
}

実行結果は次のようになります。

画像

今回はちゃんと説明できていなかったのですが、次回、来週の休みの日からはなるべくコードの説明をして、C++自体のことも書いていきたいです。プログラミングは継続なり。