#include #include #include #include #include #include #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dcompiler.lib") using namespace DirectX; // Global variables ID3D11Device* g_pd3dDevice = nullptr; ID3D11DeviceContext* g_pImmediateContext = nullptr; IDXGISwapChain* g_pSwapChain = nullptr; ID3D11RenderTargetView* g_pRenderTargetView = nullptr; ID3D11DepthStencilView* g_pDepthStencilView = nullptr; // Shaders ID3D11VertexShader* g_pVertexShader = nullptr; ID3D11PixelShader* g_pPixelShader = nullptr; ID3D11InputLayout* g_pLayout = nullptr; ID3D11Buffer* g_pVertexBuffer = nullptr; ID3D11Buffer* g_pIndexBuffer = nullptr; ID3D11Buffer* g_pCBChangesEveryFrame = nullptr; ID3D11RasterizerState* g_pRasterizerState = nullptr; ID3D11SamplerState* g_pSamplerState = nullptr; // Matrices XMMATRIX g_viewMatrix; XMMATRIX g_projMatrix; // Game objects struct Paddle { float x, y, width, height; bool active; }; struct Ball { float x, y, radius; float dx, dy; bool active; }; struct Brick { float x, y, width, height; bool active; int color; // 0-5 for different colors }; Paddle g_paddle; Ball g_ball; std::vector g_bricks; // Vertex structure struct Vertex { XMFLOAT3 pos; XMFLOAT2 texCoord; }; // Constant buffer structure struct CBChangesEveryFrame { XMMATRIX mWorld; XMMATRIX mWorldViewProj; }; // Function prototypes LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); bool InitD3D(HWND hwnd); void CleanupD3D(); void InitializeGameObjects(); void Update(float deltaTime); void HandleInput(); void Render(); // Main function int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { // Register window class WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = NULL; wcex.lpszClassName = L"BreakoutGame"; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, L"Failed to register window class!", L"Error", MB_OK); return 1; } // Create window HWND hwnd = CreateWindow( L"BreakoutGame", L"Breakout Game - DirectX 11", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, hInstance, NULL ); if (!hwnd) { MessageBox(NULL, L"Failed to create window!", L"Error", MB_OK); return 1; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Initialize DirectX if (!InitD3D(hwnd)) { MessageBox(NULL, L"Failed to initialize DirectX!", L"Error", MB_OK); return 1; } // Initialize game objects InitializeGameObjects(); // Main message loop MSG msg = {0}; DWORD lastTime = GetTickCount(); while (WM_QUIT != msg.message) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { // Calculate delta time DWORD currentTime = GetTickCount(); float deltaTime = (currentTime - lastTime) / 1000.0f; lastTime = currentTime; // Update game state Update(deltaTime); // Render frame Render(); } } CleanupD3D(); return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } bool InitD3D(HWND hwnd) { // Get screen dimensions RECT rc; GetClientRect(hwnd, &rc); UINT width = rc.right - rc.left; UINT height = rc.bottom - rc.top; // Create DXGI factory IDXGIFactory* pFactory = nullptr; HRESULT hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pFactory); if (FAILED(hr)) return false; // Create swap chain description DXGI_SWAP_CHAIN_DESC scd; ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC)); scd.BufferCount = 1; scd.BufferDesc.Width = width; scd.BufferDesc.Height = height; scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; scd.BufferDesc.RefreshRate.Numerator = 60; scd.BufferDesc.RefreshRate.Denominator = 1; scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.OutputWindow = hwnd; scd.SampleDesc.Count = 1; scd.SampleDesc.Quality = 0; scd.Windowed = FALSE; // FULLSCREEN MODE // Create device and swap chain hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_DEBUG, NULL, 0, D3D11_SDK_VERSION, &scd, &g_pSwapChain, &g_pd3dDevice, NULL, &g_pImmediateContext ); if (FAILED(hr)) { pFactory->Release(); return false; } // Create render target view ID3D11Texture2D* pBackBuffer = nullptr; hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); if (FAILED(hr)) { pFactory->Release(); return false; } hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView); pBackBuffer->Release(); if (FAILED(hr)) { pFactory->Release(); return false; } // Create depth stencil buffer D3D11_TEXTURE2D_DESC depthStencilDesc; depthStencilDesc.Width = width; depthStencilDesc.Height = height; depthStencilDesc.MipLevels = 1; depthStencilDesc.ArraySize = 1; depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilDesc.SampleDesc.Count = 1; depthStencilDesc.SampleDesc.Quality = 0; depthStencilDesc.Usage = D3D11_USAGE_DEFAULT; depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthStencilDesc.CPUAccessFlags = 0; depthStencilDesc.MiscFlags = 0; ID3D11Texture2D* pDepthStencilBuffer = nullptr; hr = g_pd3dDevice->CreateTexture2D(&depthStencilDesc, NULL, &pDepthStencilBuffer); if (FAILED(hr)) { pFactory->Release(); return false; } // Create depth stencil view D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc; depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; depthStencilViewDesc.Texture2D.MipSlice = 0; depthStencilViewDesc.Flags = 0; hr = g_pd3dDevice->CreateDepthStencilView(pDepthStencilBuffer, &depthStencilViewDesc, &g_pDepthStencilView); pDepthStencilBuffer->Release(); if (FAILED(hr)) { pFactory->Release(); return false; } // Set render target and depth stencil g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, g_pDepthStencilView); // Create viewport D3D11_VIEWPORT vp; vp.Width = (FLOAT)width; vp.Height = (FLOAT)height; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; g_pImmediateContext->RSSetViewports(1, &vp); // Create vertex shader const char* vsSource = "cbuffer cbNeverChanges : register(b0) {" " matrix mWorldViewProj;" "};" "struct VS_INPUT {" " float4 pos : POSITION;" " float2 texCoord : TEXCOORD0;" "};" "struct VS_OUTPUT {" " float4 pos : SV_POSITION;" " float2 texCoord : TEXCOORD0;" "};" "VS_OUTPUT main(VS_INPUT input) {" " VS_OUTPUT output;" " output.pos = mul(input.pos, mWorldViewProj);" " output.texCoord = input.texCoord;" " return output;" "}"; ID3DBlob* pVertexShaderBlob = nullptr; ID3DBlob* pErrorBlob = nullptr; hr = D3DCompile(vsSource, strlen(vsSource), NULL, NULL, NULL, "main", "vs_5_0", 0, 0, &pVertexShaderBlob, &pErrorBlob); if (FAILED(hr)) { if (pErrorBlob) { OutputDebugStringA((char*)pErrorBlob->GetBufferPointer()); pErrorBlob->Release(); } return false; } hr = g_pd3dDevice->CreateVertexShader(pVertexShaderBlob->GetBufferPointer(), pVertexShaderBlob->GetBufferSize(), NULL, &g_pVertexShader); if (FAILED(hr)) { pVertexShaderBlob->Release(); return false; } // Create pixel shader const char* psSource = "struct PS_INPUT {" " float4 pos : SV_POSITION;" " float2 texCoord : TEXCOORD0;" "};" "float4 main(PS_INPUT input) : SV_TARGET {" " return float4(1.0f, 1.0f, 1.0f, 1.0f); // White color for all objects" "}"; ID3DBlob* pPixelShaderBlob = nullptr; hr = D3DCompile(psSource, strlen(psSource), NULL, NULL, NULL, "main", "ps_5_0", 0, 0, &pPixelShaderBlob, &pErrorBlob); if (FAILED(hr)) { if (pErrorBlob) { OutputDebugStringA((char*)pErrorBlob->GetBufferPointer()); pErrorBlob->Release(); } return false; } hr = g_pd3dDevice->CreatePixelShader(pPixelShaderBlob->GetBufferPointer(), pPixelShaderBlob->GetBufferSize(), NULL, &g_pPixelShader); if (FAILED(hr)) { pPixelShaderBlob->Release(); return false; } // Create input layout D3D11_INPUT_ELEMENT_DESC ied[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0} }; hr = g_pd3dDevice->CreateInputLayout(ied, 2, pVertexShaderBlob->GetBufferPointer(), pVertexShaderBlob->GetBufferSize(), &g_pLayout); if (FAILED(hr)) { pVertexShaderBlob->Release(); pPixelShaderBlob->Release(); return false; } pVertexShaderBlob->Release(); pPixelShaderBlob->Release(); // Create vertex buffer Vertex vertices[] = { {XMFLOAT3(-0.5f, -0.5f, 0.0f), XMFLOAT2(0.0f, 1.0f)}, {XMFLOAT3(-0.5f, 0.5f, 0.0f), XMFLOAT2(0.0f, 0.0f)}, {XMFLOAT3(0.5f, 0.5f, 0.0f), XMFLOAT2(1.0f, 0.0f)}, {XMFLOAT3(0.5f, -0.5f, 0.0f), XMFLOAT2(1.0f, 1.0f)} }; D3D11_BUFFER_DESC bd = { 0 }; bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(Vertex) * 4; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA initData = { 0 }; initData.pSysMem = vertices; hr = g_pd3dDevice->CreateBuffer(&bd, &initData, &g_pVertexBuffer); if (FAILED(hr)) return false; // Create index buffer WORD indices[] = { 0, 1, 2, 0, 2, 3 }; bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(WORD) * 6; bd.BindFlags = D3D11_BIND_INDEX_BUFFER; bd.CPUAccessFlags = 0; initData.pSysMem = indices; hr = g_pd3dDevice->CreateBuffer(&bd, &initData, &g_pIndexBuffer); if (FAILED(hr)) return false; // Create constant buffer bd.Usage = D3D11_USAGE_DYNAMIC; bd.ByteWidth = sizeof(CBChangesEveryFrame); bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; hr = g_pd3dDevice->CreateBuffer(&bd, NULL, &g_pCBChangesEveryFrame); if (FAILED(hr)) return false; // Create rasterizer state D3D11_RASTERIZER_DESC rd; rd.FillMode = D3D11_FILL_SOLID; rd.CullMode = D3D11_CULL_BACK; rd.FrontCounterClockwise = FALSE; rd.DepthBias = 0; rd.SlopeScaledDepthBias = 0.0f; rd.DepthBiasClamp = 0.0f; rd.DepthClipEnable = TRUE; rd.ScissorEnable = FALSE; rd.MultisampleEnable = FALSE; rd.AntialiasedLineEnable = FALSE; hr = g_pd3dDevice->CreateRasterizerState(&rd, &g_pRasterizerState); if (FAILED(hr)) return false; g_pImmediateContext->RSSetState(g_pRasterizerState); // Create sampler state D3D11_SAMPLER_DESC sd; sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; sd.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; sd.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; sd.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; sd.MipLODBias = 0.0f; sd.MaxAnisotropy = 1; sd.ComparisonFunc = D3D11_COMPARISON_ALWAYS; sd.MinLOD = 0; sd.MaxLOD = D3D11_FLOAT32_MAX; hr = g_pd3dDevice->CreateSamplerState(&sd, &g_pSamplerState); if (FAILED(hr)) return false; // Set up view and projection matrices XMMATRIX view = XMMatrixLookAtLH( XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f), XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f), XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f) ); XMMATRIX proj = XMMatrixOrthographicLH( (float)WINDOW_WIDTH, (float)WINDOW_HEIGHT, 0.1f, 100.0f ); g_viewMatrix = view; g_projMatrix = proj; return true; } void CleanupD3D() { if (g_pSwapChain) g_pSwapChain->Release(); if (g_pRenderTargetView) g_pRenderTargetView->Release(); if (g_pDepthStencilView) g_pDepthStencilView->Release(); if (g_pVertexShader) g_pVertexShader->Release(); if (g_pPixelShader) g_pPixelShader->Release(); if (g_pLayout) g_pLayout->Release(); if (g_pVertexBuffer) g_pVertexBuffer->Release(); if (g_pIndexBuffer) g_pIndexBuffer->Release(); if (g_pCBChangesEveryFrame) g_pCBChangesEveryFrame->Release(); if (g_pRasterizerState) g_pRasterizerState->Release(); if (g_pSamplerState) g_pSamplerState->Release(); if (g_pImmediateContext) g_pImmediateContext->Release(); if (g_pd3dDevice) g_pd3dDevice->Release(); } void InitializeGameObjects() { // Initialize paddle g_paddle.x = WINDOW_WIDTH / 2.0f - 50.0f; g_paddle.y = WINDOW_HEIGHT - 30.0f; g_paddle.width = 100.0f; g_paddle.height = 20.0f; g_paddle.active = true; // Initialize ball g_ball.x = WINDOW_WIDTH / 2.0f; g_ball.y = WINDOW_HEIGHT / 2.0f; g_ball.radius = 10.0f; g_ball.dx = 200.0f; g_ball.dy = -200.0f; g_ball.active = true; // Initialize bricks g_bricks.clear(); float brickWidth = 75.0f; float brickHeight = 20.0f; float padding = 5.0f; float offsetX = (WINDOW_WIDTH - (10 * (brickWidth + padding))) / 2.0f; for (int row = 0; row < 6; row++) { for (int col = 0; col < 10; col++) { Brick brick; brick.x = offsetX + col * (brickWidth + padding); brick.y = 50.0f + row * (brickHeight + padding); brick.width = brickWidth; brick.height = brickHeight; brick.active = true; brick.color = row % 6; g_bricks.push_back(brick); } } } void Update(float deltaTime) { if (!g_paddle.active || !g_ball.active) return; // Move paddle with mouse POINT pt; GetCursorPos(&pt); ScreenToClient(GetActiveWindow(), &pt); g_paddle.x = (float)pt.x - g_paddle.width / 2.0f; // Keep paddle within window bounds if (g_paddle.x < 0) g_paddle.x = 0; if (g_paddle.x + g_paddle.width > WINDOW_WIDTH) { g_paddle.x = WINDOW_WIDTH - g_paddle.width; } // Move ball g_ball.x += g_ball.dx * deltaTime; g_ball.y += g_ball.dy * deltaTime; // Ball collision with walls if (g.ball.x - g.ball.radius < 0 || g.ball.x + g.ball.radius > WINDOW_WIDTH) { g.ball.dx = -g.ball.dx; } if (g.ball.y - g.ball.radius < 0) { g.ball.dy = -g.ball.dy; } // Ball collision with paddle if (g.ball.y + g.ball.radius > g.paddle.y && g.ball.x > g.paddle.x && g.ball.x < g.paddle.x + g.paddle.width) { // Calculate bounce angle based on where ball hits paddle float hitPos = (g.ball.x - g.paddle.x) / g.paddle.width; float angle = (hitPos - 0.5f) * 1.5f; // -0.75 to 0.75 g.ball.dy = -abs(g.ball.dy); g.ball.dx = angle * 200.0f; } // Ball collision with bricks for (auto& brick : g_bricks) { if (!brick.active) continue; if (g.ball.x + g.ball.radius > brick.x && g.ball.x - g.ball.radius < brick.x + brick.width && g.ball.y + g.ball.radius > brick.y && g.ball.y - g.ball.radius < brick.y + brick.height) { // Determine which side was hit float ballLeft = g.ball.x - g.ball.radius; float ballRight = g.ball.x + g.ball.radius; float ballTop = g.ball.y - g.ball.radius; float ballBottom = g.ball.y + g.ball.radius; float brickLeft = brick.x; float brickRight = brick.x + brick.width; float brickTop = brick.y; float brickBottom = brick.y + brick.height; // Calculate overlap on each side float leftOverlap = ballRight - brickLeft; float rightOverlap = brickRight - ballLeft; float topOverlap = ballBottom - brickTop; float bottomOverlap = brickBottom - ballTop; // Find minimum overlap float minOverlap = min(leftOverlap, min(rightOverlap, min(topOverlap, bottomOverlap))); // Resolve collision based on minimum overlap if (minOverlap == leftOverlap || minOverlap == rightOverlap) { g.ball.dx = -g.ball.dx; } else { g.ball.dy = -g.ball.dy; } brick.active = false; break; // Only one brick hit per frame } } // Check if ball is out of bounds (lose condition) if (g.ball.y > WINDOW_HEIGHT) { g.ball.active = false; } // Check if all bricks are destroyed (win condition) bool allBricksDestroyed = true; for (const auto& brick : g_bricks) { if (brick.active) { allBricksDestroyed = false; break; } } if (allBricksDestroyed) { // Reset ball position for next game g.ball.x = WINDOW_WIDTH / 2.0f; g.ball.y = WINDOW_HEIGHT / 2.0f; g.ball.dx = 200.0f; g.ball.dy = -200.0f; g.ball.active = true; // Reset bricks InitializeGameObjects(); } } void Render() { // Clear back buffer float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, clearColor); g_pImmediateContext->ClearDepthStencilView(g_pDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0); // Set shaders and input layout g_pImmediateContext->VSSetShader(g_pVertexShader, NULL, 0); g_pImmediateContext->PSSetShader(g_pPixelShader, NULL, 0); g_pImmediateContext->IASetInputLayout(g_pLayout); // Set vertex and index buffers UINT stride = sizeof(Vertex); UINT offset = 0; g_pImmediateContext->IASetVertexBuffers(0, 1, &g_pVertexBuffer, &stride, &offset); g_pImmediateContext->IASetIndexBuffer(g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0); g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // Render paddle if (g_paddle.active) { XMMATRIX world = XMMatrixTranslation(g_paddle.x, g_paddle.y, 0.0f); XMMATRIX wvp = world * g_viewMatrix * g_projMatrix; CBChangesEveryFrame cb; cb.mWorld = XMMatrixTranspose(world); cb.mWorldViewProj = XMMatrixTranspose(wvp); g_pImmediateContext->UpdateSubresource(g_pCBChangesEveryFrame, 0, NULL, &cb, 0, 0); g_pImmediateContext->VSSetConstantBuffers(0, 1, &g_pCBChangesEveryFrame); g_pImmediateContext->DrawIndexed(6, 0, 0); } // Render ball if (g_ball.active) { XMMATRIX world = XMMatrixTranslation(g_ball.x, g_ball.y, 0.0f); XMMATRIX wvp = world * g_viewMatrix * g_projMatrix; CBChangesEveryFrame cb; cb.mWorld = XMMatrixTranspose(world); cb.mWorldViewProj = XMMatrixTranspose(wvp); g_pImmediateContext->UpdateSubresource(g_pCBChangesEveryFrame, 0, NULL, &cb, 0, 0); g_pImmediateContext->VSSetConstantBuffers(0, 1, &g_pCBChangesEveryFrame); g_pImmediateContext->DrawIndexed(6, 0, 0); } // Render bricks for (const auto& brick : g_bricks) { if (!brick.active) continue; XMMATRIX world = XMMatrixTranslation(brick.x, brick.y, 0.0f); XMMATRIX wvp = world * g_viewMatrix * g_projMatrix; CBChangesEveryFrame cb; cb.mWorld = XMMatrixTranspose(world); cb.mWorldViewProj = XMMatrixTranspose(wvp); g_pImmediateContext->UpdateSubresource(g_pCBChangesEveryFrame, 0, NULL, &cb, 0, 0); g_pImmediateContext->VSSetConstantBuffers(0, 1, &g_pCBChangesEveryFrame); g_pImmediateContext->DrawIndexed(6, 0, 0); } // Present the back buffer g_pSwapChain->Present(0, 0); }