コミュ障だから明日が僕らをよんだって返事もろくにしなかった

何かを創る人に憧れたからブログをはじめたんだと思うよ

懺・これからはじめるC++

競技プログラミングのためにC++をやるといったな。あれは嘘だ!

僕が購読したりしなかったりしている方がゲーム制作などをしていまして、そこで以下のようなサイトを紹介していたんでそこで紹介されているやつをやってみることにしました。そう、ということで今日はUnityをば……。やらずにC++でPong作ります。

参考サイト
noobtuts - Tutorials


てなわけでこちらのサイトの C++ チュートリアルにあるPongを作っていきます。このブログを読んでいる聡明な読者の皆様はこのようなレトロゲームぐらい余裕で嗜んでいると思うので解説する必要ないのですがPongはこんなやつです。

『ポン』(PONG)は、ビデオ画面上に再現された卓球ゲームである。類似ゲームはそれ以前から制作されていたが、本稿では1972年11月にアタリより発表され、一般に広く知れ渡った最初のビデオゲームを扱う。類似ゲームは『Tennis for Two』と『オデッセイ』を参照。

PONGつくるよ

C++ Pongチュートリアル
noobtuts - C++ 2D Pong Game

さて、チュートリアルとかそういったやつはバージョンや実行環境をそろえることが重要とか言われていますが僕はそんなの知らない。動かなくなってから対応を考えるとして、とりあえず以下の環境で動かします。
Visual Studio 2017
・FreeGLUT 2.8.1
C++14

まずはFreeGLUTを用意します。
公式 FreeGLUT
The freeglut Project :: About

FreeGLUTは、OpenGL Utility Toolkit(GLUT)ライブラリの代わりにオープンソースのものです。

まあ、何なのかと言うと画面周りの処理機能を良い感じにしてくれるやつです。OpenGLでごりごりやるのが辛い人向け用のやつです。

最新版が3.0なんですけど、3.0系から導入方法が面倒くさくなっていたんで、2.8.1で確認していきます。チュートリアルの方で用意されたやつあるんですけど、なんか落としてくるの怖かったんで公式から落とします。割と準備が面倒くさい詰みポイントですが頑張ってfreeglut.dllファイルとfreeglut.libファイルを手に入れましょう。手に入れたらGLフォルダと先ほどのファイルをプロジェクトにねじ込めばとりあえずfreeglutが使えるようになります。

んで、こうコードを書きます……。

#include "stdafx.h"
#include <string>
#include <windows.h>
#include <iostream>
#include <conio.h>
#include <sstream> 
#include <math.h> 
#include <gl\gl.h>
#include <gl\glu.h>
#include "GL/freeglut.h"
#pragma comment(lib, "OpenGL32.lib")

// キーボード割当 W,S
#define VK_W 0x57
#define VK_S 0x53

// ウィンドウズサイズ (60 fps)
int width = 500;
int height = 200;
int interval = 1000 / 60;

// スコア
int score_left = 0;
int score_right = 0;

// ラケット
int racket_width = 10;
int racket_height = 80;
int racket_speed = 3;

// 左ラケット位置
float racket_left_x = 10.0f;
float racket_left_y = 50.0f;

// 右ラケット位置
float racket_right_x = width - racket_width - 10.0f;
float racket_right_y = 50.0f;

// ボール
float ball_pos_x = width / 2.0f;
float ball_pos_y = height / 2.0f;
float ball_dir_x = -1.0f;
float ball_dir_y = 0.0f;
int ball_size = 8;
int ball_speed = 2;

// 文字描画
void drawText(float x, float y, std::string text) {
	glRasterPos2f(x, y);
	glutBitmapString(GLUT_BITMAP_8_BY_13, (const unsigned char*)text.c_str());
}

// ラケット描画
void drawRect(float x, float y, float width, float height) {
	glBegin(GL_QUADS);
	glVertex2f(x, y);
	glVertex2f(x + width, y);
	glVertex2f(x + width, y + height);
	glVertex2f(x, y + height);
	glEnd();
}

// 描画
void draw() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	// ラケット描画
	drawRect(racket_left_x, racket_left_y, (float)racket_width, (float)racket_height);
	drawRect(racket_right_x, racket_right_y, (float)racket_width, (float)racket_height);
	// ボール描画
	drawRect(ball_pos_x - (float)ball_size / 2.0f, ball_pos_y - (float)ball_size / 2.0f, (float)ball_size, (float)ball_size);

	// 文字描画(スコア)
	drawText((float)width / 2.0f - 10.0f, (float)height - 15.0f,
		std::to_string(score_left) + ":" + std::to_string(score_right));

	glutSwapBuffers();
}

// キーボード操作
void keyboard() {
	// 左ラケット
	if (GetAsyncKeyState(VK_W)) racket_left_y += racket_speed;
	if (GetAsyncKeyState(VK_S)) racket_left_y -= racket_speed;

	// 右ラケット
	if (GetAsyncKeyState(VK_UP)) racket_right_y += racket_speed;
	if (GetAsyncKeyState(VK_DOWN)) racket_right_y -= racket_speed;
}

// ベクトル正規化
void vec2_norm(float &x, float &y) {
	float length = sqrt((x * x) + (y * y));
	if (length != 0.0f) {
		length = 1.0f / length;
		x *= length;
		y *= length;
	}
}

// ボールの更新
void updateBall() {
	// ボールの移動
	ball_pos_x += ball_dir_x * ball_speed;
	ball_pos_y += ball_dir_y * ball_speed;

	// 左:ラケット接触判定
	if (ball_pos_x < racket_left_x + racket_width &&
		ball_pos_x > racket_left_x &&
		ball_pos_y < racket_left_y + racket_height &&
		ball_pos_y > racket_left_y) {

		float t = ((ball_pos_y - racket_left_y) / racket_height) - 0.5f;
		ball_dir_x = fabs(ball_dir_x); 
		ball_dir_y = t;
	}

	// 右:ラケット接触判定
	if (ball_pos_x > racket_right_x &&
		ball_pos_x < racket_right_x + racket_width &&
		ball_pos_y < racket_right_y + racket_height &&
		ball_pos_y > racket_right_y) {

		float t = ((ball_pos_y - racket_right_y) / racket_height) - 0.5f;
		ball_dir_x = -fabs(ball_dir_x); 
		ball_dir_y = t;
	}

	// 左:終了判定
	if (ball_pos_x < 0) {
		++score_right;
		ball_pos_x = (float)width / 2.0f;
		ball_pos_y = (float)height / 2.0f;
		ball_dir_x = fabs(ball_dir_x);
		ball_dir_y = 0.0f;
	}

	// 右:終了判定
	if (ball_pos_x > width) {
		++score_left;
		ball_pos_x = (float)width / 2.0f;
		ball_pos_y = (float)height / 2.0f;
		ball_dir_x = -fabs(ball_dir_x);
		ball_dir_y = 0;
	}

	// 上部壁判定
	if (ball_pos_y > height) {
		ball_dir_y = -fabs(ball_dir_y);
	}

	// 下部壁判定
	if (ball_pos_y < 0) {
		ball_dir_y = fabs(ball_dir_y); 
	}

	vec2_norm(ball_dir_x, ball_dir_y);
}

// 更新処理
void update(int value) {
	keyboard();
	updateBall();

	glutTimerFunc(interval, update, 0);

	glutPostRedisplay();
}

// 2D表示
void enable2D(int width, int height) {
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0f, width, 0.0f, height, 0.0f, 1.0f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// メイン関数
int main(int argc, char** argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(width, height);
	glutCreateWindow("Pong");

	glutDisplayFunc(draw);
	glutTimerFunc(interval, update, 0);

	enable2D(width, height);
	glColor3f(1.0f, 1.0f, 1.0f);

	glutMainLoop();
	return 0;
}

まあ、独自にいらん機能とか書き換えたりとかしてますがほぼ写しです。やってることは描画処理とキー入力がほとんどだしね……。考えるところがあるとしたらベクトル正規化で速度を一定化しているところぐらい……?でも、これを使用しなくてもあまり動きに変化がみられない……。


ちなみに、実行するとこんな感じです。
f:id:andron:20180815034903g:plain


完走した感想

今更、C++でFreeGLUTを触るのもどうなのって感じなんですけど、C++アルゴリズム分かっていざ本格的な実装しようと思ったらこういうの(描画周りのお作法など)で躓くから、まあかじる程度に触るのは悪くないと思ってる。


ということで、最後に締めの言葉を書いて、この記事を終わりにしたいと思います。


今日の教訓

こんな記事ばっか書いているからアルゴリズム弱いままなんだぞ。



はい。