commit 755b579d01e541e37ce2ea3ae2743add50422e5a Author: 諏訪子 Date: Tue Nov 25 20:11:42 2025 +0900 コミット diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0e3832 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +NAME=brickbreaker +# Linux、Haiku、かIllumos = /usr、FreeBSDかOpenBSD = /usr/local、NetBSD = /usr/pkg +PREFIX=/usr/local +CC=clang++ +CFLAGS=-I${PREFIX}/include -L${PREFIX}/lib +LDFLAGS=-lSDL2 -lSDL2_mixer -lSDL2_ttf +FILES=main.cc src/ball.cc src/brick.cc src/render.cc + +all: + ${CC} ${CFLAGS} -o ${NAME} ${FILES} ${LDFLAGS} + +clean: + rm -f ${NAME} + +.PHONY: all clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c730e8 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# ブリックブレイカー + +7つレベルがあり、レベルを上がるとより難しくなります。\ +「YOU WIN」と表示されたら、ハイスコアを教えて下さいね。(´・ω・`) + +## 音楽 +[Stas Gavrikさんより](https://youtube.owacon.moe/playlist?list=PLyc7oozhSJMytXF1eWMUAU2zl84W0Nkqg) + +## 勉強点 +* BGM +* オブジェクト指向プログラミング +* オブジェクトを非表示にする事 + +## ソフト +* SDL2 +* SDL2_mixer +* SDL2_ttf + +## Linux +```sh +make +``` + +## \*BSD +```sh +gmake +``` + +## Illumos +```sh +gmake PREFIX=/usr +``` diff --git a/ass/1.ogg b/ass/1.ogg new file mode 100644 index 0000000..c505c21 Binary files /dev/null and b/ass/1.ogg differ diff --git a/ass/2.ogg b/ass/2.ogg new file mode 100644 index 0000000..aa9054c Binary files /dev/null and b/ass/2.ogg differ diff --git a/ass/3.ogg b/ass/3.ogg new file mode 100644 index 0000000..50c0854 Binary files /dev/null and b/ass/3.ogg differ diff --git a/ass/4.ogg b/ass/4.ogg new file mode 100644 index 0000000..19b6d66 Binary files /dev/null and b/ass/4.ogg differ diff --git a/ass/5.ogg b/ass/5.ogg new file mode 100644 index 0000000..b23a90b Binary files /dev/null and b/ass/5.ogg differ diff --git a/ass/6.ogg b/ass/6.ogg new file mode 100644 index 0000000..62d2a6c Binary files /dev/null and b/ass/6.ogg differ diff --git a/ass/7.ogg b/ass/7.ogg new file mode 100644 index 0000000..39315d3 Binary files /dev/null and b/ass/7.ogg differ diff --git a/font.ttf b/font.ttf new file mode 100644 index 0000000..b070b9b Binary files /dev/null and b/font.ttf differ diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..f0d71a2 --- /dev/null +++ b/main.cc @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include "src/render.hh" +#include "src/ball.hh" +#include "src/player.hh" +#include "src/brick.hh" + +int main(void) { + Render r; + Player p(r); + Ball bl(r, p); + Brick br(r); + + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + std::cout << "SDL_Init()に失敗" << '\n'; + return -1; + } + + if (SDL_CreateWindowAndRenderer(r.width, r.height, 0, &r.window, &r.renderer) < 0) { + std::cout << "SDL_CreateWindowAndRenderer()に失敗" << '\n'; + SDL_Quit(); + return -1; + } + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { + std::cout << "Mix_OpenAudio()に失敗" << '\n'; + SDL_DestroyRenderer(r.renderer); + SDL_DestroyWindow(r.window); + SDL_Quit(); + return -1; + } + + SDL_SetWindowTitle(r.window, "ブリックブレイカー"); + + TTF_Init(); + r.font = TTF_OpenFont("font.ttf", r.fontsize); + if (!r.font) { + std::cout << "フォントを読込に失敗" << '\n'; + SDL_DestroyRenderer(r.renderer); + SDL_DestroyWindow(r.window); + Mix_CloseAudio(); + SDL_Quit(); + return -1; + } + + static int lastTime = 0; + br.resetBricks(r, p, bl); + + while (r.running) { + r.lastFrame = SDL_GetTicks(); + if (r.lastFrame >= lastTime + 1000) { + lastTime = r.lastFrame; + r.fps = r.frameCount; + r.frameCount = 0; + } + + if (!r.gameover) { + r.update(r, p, bl, br); + } + r.input(p); + r.render(p, bl, br); + } + + TTF_CloseFont(r.font); + SDL_DestroyRenderer(r.renderer); + SDL_DestroyWindow(r.window); + SDL_Quit(); + Mix_FreeMusic(r.music); + Mix_CloseAudio(); + TTF_Quit(); + + return 0; +} diff --git a/src/ball.cc b/src/ball.cc new file mode 100644 index 0000000..d7fa4c3 --- /dev/null +++ b/src/ball.cc @@ -0,0 +1,9 @@ +#include "ball.hh" + +void Ball::resetBall(Render &r, Player &p) { + p.player.x = (r.width / 2) - (p.player.w / 2); + Ball::ball.y = p.player.y - (p.player.h * 4); + Ball::yvelocity = Ball::ballspeed / 2; + Ball::xvelocity = 0; + Ball::ball.x = r.width / 2 - (Ball::size / 2); +} diff --git a/src/ball.hh b/src/ball.hh new file mode 100644 index 0000000..9f03519 --- /dev/null +++ b/src/ball.hh @@ -0,0 +1,30 @@ +#ifndef BALL_H +#define BALL_H + +#include +#include + +#include "player.hh" + +class Ball { + public: + float ballspeed = 8.0f; + int size = 16; + + float xvelocity; + float yvelocity; + + SDL_Rect ball; + + void resetBall(Render &r, Player &p); + + Ball(Render& r, Player& p) { + ball.y = p.player.y - (p.player.h / 2); + yvelocity = ballspeed / 2; + xvelocity = ball.x = 0; + ball.w = ball.h = size; + ball.x = r.width / 2 - (size / 2); + } +}; + +#endif diff --git a/src/brick.cc b/src/brick.cc new file mode 100644 index 0000000..f28c4e4 --- /dev/null +++ b/src/brick.cc @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include "render.hh" +#include "player.hh" +#include "ball.hh" +#include "brick.hh" + +void Brick::resetBricks(Render &r, Player &p, Ball &bl) { + srand(static_cast(time(nullptr))); + r.c1r = rand() % 256; + r.c1g = rand() % 256; + r.c1b = rand() % 256; + r.c2r = rand() % 256; + r.c2g = rand() % 256; + r.c2b = rand() % 256; + r.c3r = rand() % 256; + r.c3g = rand() % 256; + r.c3b = rand() % 256; + r.c4r = rand() % 256; + r.c4g = rand() % 256; + r.c4b = rand() % 256; + r.c5r = rand() % 256; + r.c5g = rand() % 256; + r.c5b = rand() % 256; + r.c6r = rand() % 256; + r.c6g = rand() % 256; + r.c6b = rand() % 256; + + + if (r.level > 1) { + Mix_HaltMusic(); + } + + std::string music = "ass/" + std::to_string(r.level) + ".ogg"; + r.music = Mix_LoadMUS(music.c_str()); + if (Mix_PlayMusic(r.music, -1) < 0) { + std::cout << "OGGファイルのエラー:" << Mix_GetError() << '\n'; + r.running = false; + TTF_CloseFont(r.font); + SDL_DestroyRenderer(r.renderer); + SDL_DestroyWindow(r.window); + SDL_Quit(); + Mix_CloseAudio(); + TTF_Quit(); + } + + for (int i = 0; i < Brick::col * Brick::row; i++) { + Brick::bricks[i] = 1; + } + + bl.size -= 2; + bl.ballspeed += 1.4f; + p.player.w -= 1.4f; + p.player.x = (r.width / 2) - (p.player.w / 2); + bl.ball.y = p.player.y - (p.player.h * 4); + bl.yvelocity = bl.ballspeed / 2; + bl.xvelocity = 0; + bl.ball.x = r.width / 2 - (bl.size / 2); +} + +void Brick::setBricks(int i) { + Brick::brick.x = (((i%Brick::col) + 1) * Brick::gaps) + ((i%Brick::col) * Brick::brick.w) - (Brick::gaps/2); + Brick::brick.y = Brick::brick.h * 3 + (((i%Brick::row) + 1) * Brick::gaps) + ((i%Brick::row) * Brick::brick.h) - (Brick::gaps/2); +} diff --git a/src/brick.hh b/src/brick.hh new file mode 100644 index 0000000..61f9125 --- /dev/null +++ b/src/brick.hh @@ -0,0 +1,31 @@ +#ifndef BRICK_H +#define BRICK_H + +#include +#include + +#include "render.hh" +#include "player.hh" +#include "ball.hh" + +class Brick { + public: + int col = 7; + int row = 5; + int gaps = 16; + + bool bricks[7*5]; + + SDL_Color color = {255, 255, 255}; + SDL_Rect brick; + + void resetBricks(Render &r, Player &p, Ball &bl); + void setBricks(int i); + + Brick(Render& r) { + brick.w = (r.width - (gaps * col)) / col; + brick.h = 22; + } +}; + +#endif diff --git a/src/player.hh b/src/player.hh new file mode 100644 index 0000000..3e2cd05 --- /dev/null +++ b/src/player.hh @@ -0,0 +1,25 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include + +#include "render.hh" + +class Player { + public: + SDL_Rect player, lives; + + int score = 0; + int scoreMultiplier = 1; + int livesCount = 3; + + Player(Render& r) { + player.h = 12; + player.y = r.height - player.h - 32; + player.w = r.width / 4; + player.x = (r.width / 2) - (player.w / 2); + } +}; + +#endif diff --git a/src/render.cc b/src/render.cc new file mode 100644 index 0000000..ca999c7 --- /dev/null +++ b/src/render.cc @@ -0,0 +1,170 @@ +#include "render.hh" +#include "ball.hh" +#include "player.hh" +#include "brick.hh" + +#define PI 3.14 + +void Render::gameOver(Player &p, Brick &br, bool winner) { + if (winner) { + Render::winner = true; + } else { + Render::winner = false; + } + + Render::gameover = true; + Mix_HaltMusic(); +} + +void Render::write(Player &p, Brick &br, std::string text, int x, int y) { + SDL_Surface *surface; + SDL_Texture *texture; + const char* t = text.c_str(); + surface = TTF_RenderText_Solid(Render::font, t, br.color); + texture = SDL_CreateTextureFromSurface(Render::renderer, surface); + + p.lives.w = surface->w; + p.lives.h = surface->h; + p.lives.x = x - p.lives.w; + p.lives.y = y - p.lives.h; + + SDL_FreeSurface(surface); + SDL_RenderCopy(Render::renderer, texture, NULL, &p.lives); + SDL_DestroyTexture(texture); +} + +void Render::update(Render &r, Player &p, Ball &bl, Brick &br) { + if (p.livesCount <= 0) { + gameOver(p, br, false); + } + + if (SDL_HasIntersection(&bl.ball, &p.player)) { + double rel = (p.player.x + ((float)p.player.w/2)) - ((float)bl.ball.x + ((float)bl.size/2)); + double norm = rel / ((float)p.player.w/2); + double bounce = norm * (5*PI/12); + + bl.yvelocity = -bl.ballspeed * cos(bounce); + bl.xvelocity = bl.ballspeed * -sin(bounce); + p.scoreMultiplier = 1; + } + + if (bl.ball.y <= 0) { + bl.yvelocity = -bl.yvelocity; + } + if (bl.ball.y + bl.size >= Render::height) { + bl.yvelocity = -bl.yvelocity; + p.livesCount--; + bl.resetBall(r, p); + } + if (bl.ball.x <= 0 || bl.ball.x + bl.size >= Render::width) { + bl.xvelocity = -bl.xvelocity; + } + bl.ball.x += bl.xvelocity; + bl.ball.y += bl.yvelocity; + if (p.player.x < 0) { + p.player.x = 0; + } + if (p.player.x + p.player.w > Render::width) { + p.player.x = Render::width - p.player.w; + } + + bool reset = true; + for (int i = 0; i < br.col*br.row; i++) { + br.setBricks(i); + + if (SDL_HasIntersection(&bl.ball, &br.brick) && br.bricks[i]) { + br.bricks[i] = 0; + p.score += 100 * p.scoreMultiplier; + p.scoreMultiplier++; + + if (bl.ball.x >= br.brick.x) { + bl.xvelocity = -bl.xvelocity; + bl.ball.x -= 1; + } + if (bl.ball.x <= br.brick.x) { + bl.xvelocity = -bl.xvelocity; + bl.ball.x += 1; + } + if (bl.ball.y <= br.brick.y) { + bl.yvelocity = -bl.yvelocity; + bl.xvelocity = -bl.xvelocity; + bl.ball.y -= 1; + } + if (bl.ball.y >= br.brick.y) { + bl.yvelocity = -bl.yvelocity; + bl.xvelocity = -bl.xvelocity; + bl.ball.y += 1; + } + } + + if (br.bricks[i]) { + reset = false; + } + } + + if (reset) { + Render::level++; + if (r.level > 7) { + gameOver(p, br, true); + } else { + br.resetBricks(r, p, bl); + } + } +} + +void Render::input(Player &p) { + SDL_Event e; + const Uint8 *keystates = SDL_GetKeyboardState(NULL); + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + Render::running = false; + } + } + + if (keystates[SDL_SCANCODE_Q]) Render::running = false; + if (!Render::gameover) { + if (keystates[SDL_SCANCODE_LEFT]) p.player.x -= Render::xpos; + if (keystates[SDL_SCANCODE_RIGHT]) p.player.x += Render::xpos; + } +} + +void Render::render(Player &p, Ball &bl, Brick &br) { + SDL_SetRenderDrawColor(Render::renderer, 0x00, 0x00, 0x00, 255); + SDL_RenderClear(Render::renderer); + + Render::frameCount++; + Render::timerFPS = SDL_GetTicks() - Render::lastFrame; + + if (Render::timerFPS < (1000/60)) { + SDL_Delay((1000/60) - Render::timerFPS); + } + + SDL_SetRenderDrawColor(Render::renderer, 255, 255, 255, 255); + SDL_RenderFillRect(Render::renderer, &p.player); + SDL_RenderFillRect(Render::renderer, &bl.ball); + write(p, br, "SCORE: " + std::to_string(p.score), Render::width, Render::fontsize*1.5f); + write(p, br, "LIVES: " + std::to_string(p.livesCount), 130, Render::fontsize*1.5f); + write(p, br, "LEVEL: " + std::to_string(Render::level), 130, Render::fontsize*3.5f); + + if (Render::gameover) { + if (Render::winner) write(p, br, "YOU WIN", Render::width/2+Render::fontsize, Render::height/2); + else write(p, br, "YOU LOSS", Render::width/2+Render::fontsize, Render::height/2); + } + + for (int i = 0; i < br.col*br.row; i++) { + SDL_SetRenderDrawColor(Render::renderer, c1r, c1g, c1b, 255); + if (i%2 == 0) SDL_SetRenderDrawColor(Render::renderer, c2r, c2g, c2b, 255); + if (i%3 == 0) SDL_SetRenderDrawColor(Render::renderer, c3r, c3g, c3b, 255); + if (i%4 == 0) SDL_SetRenderDrawColor(Render::renderer, c4r, c4g, c4b, 255); + if (i%5 == 0) SDL_SetRenderDrawColor(Render::renderer, c5r, c5g, c5b, 255); + if (i%6 == 0) SDL_SetRenderDrawColor(Render::renderer, c6r, c6g, c6b, 255); + + if (br.bricks[i]) { + br.setBricks(i); + SDL_RenderFillRect(Render::renderer, &br.brick); + } + } + + SDL_RenderPresent(Render::renderer); +} diff --git a/src/render.hh b/src/render.hh new file mode 100644 index 0000000..a929a82 --- /dev/null +++ b/src/render.hh @@ -0,0 +1,52 @@ +#ifndef RENDER_H +#define RENDER_H + +#include +#include +#include +#include + +class Player; +class Ball; +class Brick; + +class Render { + public: + bool running, gameover, winner; + int frameCount, timerFPS, lastFrame, fps = 0; + + int width = 620; + int height = 720; + int fontsize = 16; + int xpos = 9; + int level = 1; + + // 色 + int c1r, c1g, c1b; + int c2r, c2g, c2b; + int c3r, c3g, c3b; + int c4r, c4g, c4b; + int c5r, c5g, c5b; + int c6r, c6g, c6b; + + SDL_Window *window; + SDL_Renderer *renderer; + TTF_Font* font; + Mix_Music* music; + + void update(Render &r, Player &p, Ball &bl, Brick &br); + void input(Player &p); + void render(Player &p, Ball &bl, Brick &br); + + Render() { + winner = false; + gameover = false; + running = true; + } + + private: + void gameOver(Player &p, Brick &br, bool winner); + void write(Player &p, Brick &br, std::string text, int x, int y); +}; + +#endif