テトリスを小一時間で作ってみたⅡ【C言語ゲームプログラミング実況】Programming Tetris
HTML-код
- Опубликовано: 19 ноя 2024
- C言語によるコンソールアプリで「テトリス」を0から作りました。
編集無しの一発撮りです。
「小一時間で作ってみた」シリーズが書籍化されました!
【著書】小一時間でゲームをつくる──7つの定番ゲームのプログラミングを体験
amzn.to/3aD2cLS
ソースコードは、メンバーシップ特典として公開しています。
【公開中ソースコード一覧】
www.youtube.co...
【前作との違い】
前回はテトリミノの回転を、ファミコン版を再現する為に予め用意したパターンを切り替えてましたが、今回は計算で行いました。
【前作】「C言語で「テトリス」を小一時間で作ってみた」
• テトリスを小一時間で作ってみた【C言語ゲーム...
【C言語で小一時間で作ってみたシリーズ】
• 小一時間で作ってみた【C言語ゲームプログラミ...
【ゲヱム道館】gamedokan.web.f...
【GPU】gameprogramming...
【ニコニコ】www.nicovideo....
【Twitter】 / gamedokan
私苦手なんですが話しながら入力してくの見てると気持ちいいですね。
移動早くてめちゃ、尊敬です。
地面と同化してるみたいですね、
はい。でもこの動きはどうかしてるということなんですけども、スゥー
とても分かりやすいです
マイクロソフトの高機能なグラフィックソフトがわかりやすいです...。
最近独学でC言語を学び始めているのですがタイトルとゲームオーバーになったらタイトルに戻るみたいなことしたいのですがどうすればいいでしょうか。
タイトル画面の実装はいろいろな方法がありますが、以下、簡単な実装例です。
①変数を宣言し、値が「0」ならタイトル画面、「1」ならゲーム画面とする
②switch文でタイトル画面とゲーム画面の処理を分岐させる
③タイトル画面でスタートを選択したら変数を「1」に、ゲームオーバーになったら変数を「0」にする
具体的な例として、タイトル画面の実装は、拙著「小一時間でゲームをつくる」の第3章で解説しています。本シリーズでは工数を最小限にする為にタイトル画面はほとんどカットするのですが、この章ではゲームモードを選択する為に、タイトル画面とゲーム画面の遷移を実装しています。
小一時間でゲームをつくる──7つの定番ゲームのプログラミングを体験 (WEB+DB PRESS plus)
amzn.to/3xYcmjW
また、上記のプログラムの作成はライブ配信で行いました。どう実装するか考えながら解説しているので、参考になるかもしれません。
オセロを作ってみる【完結】【C言語ゲームプログラミング実況ライブ】
ruclips.net/p/PL8_ASIpg7ciFuJOtL1b0sTxT2BTAjT0iA
@@Gamedokan ありがとうございます!
初コメ失礼します。
18:36のmino.x+x mino.y+yの意味がわかりません。
そもそもmino.x,yというのは何を表しているのでしょうか?
mino.x,yの値の代入ははどこでしているのでしょうか
そして何故+x,yするのでしょうか?
初歩的な質問ですみません。。。
試しにx,yだけにしてやってみたのですが、ちゃんとミノが表示されました。
mino.x,yだけでやると、うまく表示できませんでした。
まだここまでしかやっていないので先のことはわからないんですけども。。。
すみません!
自己解決しました!
mino.x, mino.yはまだ定義しかしてないから初期の0なので左上に表示されるのですね!
その後にmino.xを真ん中にしてましたね。。。
解決しましたが、他に同じことでわからない人のために(いるからわからないですが)一応残しておきます。
テトリス作るのに1年も掛からない1日でできる
簡単なやつなら1時間
18:30 のパターン内のx,yを加える所と1を上書きするところが分かりません…
教えて頂きたいです…
「|」はビット単位の論理演算子で、ビット論理和(OR)演算子です。このシリーズでは使うべきではありませんでしたが、工数を減らして再生時間を短くする為に使ってしまいました。すみません。
ビット演算は2進数の各桁ごとに計算を行いますが、このコードでは0か1しか扱わないので、下1桁の計算だけです。ビット論理和演算子は次のように計算されます。
0 | 0 → 0
0 | 1 → 1
1 | 0 → 1
1 | 1 → 1
「|=」は代入も兼ねた、ビット論理和【代入】演算子です。
このコードは、次のコードと同等です。
screen[mino.y + y][mono.x + x] |= 1;
↓
screen[mino.y + y][mono.x + x] = screen[mino.y + y][mono.x + x] | 1;
つまり、
空白のマスに空白を上書きしても空白のままで、
空白のマスにブロックを上書きすればブロックが残り、
ブロックがあるマスに空白を上書きしてもブロックが残り、
ブロックがあるマスにブロックを上書きしてもブロックが残る、
ということになります。
ビット論理和演算子を使わなければ、次ようになりますが、冗長です。
screen[mino.y + y][mono.x + x] |= 1;
↓
if(screen[mino.y + y][mono.x + x] == 0)
screen[mino.y + y][mono.x + x] = 1;
返信ありがとうございます!
もうひとつ質問してもよろしいでしょうか。
応用として枠外に次に出るミノを表示させたいのですが、ヒントをいただきたいです。
プログラミング独学初心者なのでぜひ教えて頂きたいです。
@@炊き水
ここまで作ってきたプログラムと比べたら、難しくありません。
1. 「現在のミノ」を変数として保持しているのと同様に、「次のミノ」の変数を宣言する
2. 新しいミノが発生するタイミングになったら、「次のミノ」を「現在のミノ」にして、「次のミノ」を新しい値にする
3. 次のミノを枠外に描画する
プログラムを実行して表示する時に行間?が広くてブロックが上手く詰まっている感じに表示されません。プログラムの問題では無いと思うのですがなにか表示の設定とかありますか?
コンソールウィンドウのプロパティ設定で、行間に関するものを確認しましたが、見当たりませんでした。
行間が空いてしまう原因として、2つ考えられます。
1. 改行コード「¥n」を2回出力してしまっている
2. コンソールウィンドウの自動改行
文字を出力して画面バッファーの幅をオーバーすると、自動的に改行されます。
画面バッファーの幅を表示ギリギリにするとよくある事で、「自動改行」+「改行コード」で2回改行されてしまいます。
それでも上手くいきませんでした。おそらく自分のフォントや文字サイズがダメな気もするのですがコンソールウィンドウはどのようなフォントと文字サイズで設定なされていますか?
@@りもコん フォントはおそらくデフォルト(MSゴシック)、文字のサイズは具体的な数値はわかりませんが、動画の画面にギリギリ収まる大きなサイズを設定しています。
ターミナルアプリが、Windows標準のものでなく、「Windows Terminal」などに置き換わっているかもしれません。その場合は、アプリ名と「"行間"」などのキーワードでググると、行間の変更方法がわかるかもしれません。
たびたび申し訳ございません。
全角の記号(四角形)を表示しようとすると上手く表示されず左半分しか映りません。これはなぜでしょうか?どの四角形を使っていますか?初歩的な質問で申し訳ございません。
@@りもコん 左半分しか表示されないというのは、日本語非対応のフォントで文字化けしているかもしれません。フォントを「MSゴシック」など日本語対応のものに変更すれば直ると思います。
動画で表示している四角形は、「しかく」で変換すると候補に出てきます。
地面に着地した場合回転、移動停止する処理をキーボード欄に書き込むとどうなりますか?
キーボードの下キーを押して接地した場合も、テトリミノがフィールドに接地したいという事でしょうか。
この動画のプログラムでは、時間経過による自動落下の時にしか、テトリミノがフィールドに同化せず、キーボード入力で接地しても押し戻されるだけになっています。
キーボード入力で接地した場合もフィールドに同化させたい場合は、キーボードの下キーを押してテトリミノを移動させた直後にフィールドとの当たり判定を行い、当たっていたら同化させればよいのです。
下記コードは、このプログラムのメインループを簡略化し、コメントを付けたものです。【2-d】に、【1】をまるごとコピーすれば、キーボード入力で接地した場合も、テトリミノがフィールドに同化するようになります。
ただし、それだと【1】という長い処理を複数箇所で行うので、【1】は関数化して呼び出すべきです。
これがちゃんと実装されたかどうか確認する為に、自動落下の時は【1-a】〜【1-c】の処理を一旦コメントアウトすれば、キーボード入力でしかテトリミノがフィールドに同化する処理が行われなくなり、動作確認しやすいです。
// メインループ
while (1) {
...
// 1秒経過したら
if (nowClock >= lastClock + INTERVAL) {
...
// テトリミノを自動落下させる
mino.y++;
// 【1】テトリミノがフィールドと重なったら
if (MinoIntersectField()) {
// 【1-a】テトリミノをフィールドに書き込む
// 【1-b】揃った行を探し、揃ったらその行のブロックを消し、その行から上のブロックを1マス下にずらす
// 【1-c】新しいテトリミノを発生させる
InitMino();
}
}
// 【2】キーボード入力処理
if (_kbhit()) {
...
// 【2-a】入力されたキーで分岐
switch (_getch()) {
...
// 【2-b】sキーが押されたら
case 's':
// 【2-c】テトリミノを1マス下に移動させる
mino.y++;
// 【2-d】ここに【1】をコピペする
break;
}
}
y方向+したとき、かつ、インターセクトしたらフィールドと同化、ってした方が良くない?
多分x方向+してインターセクトした場合も同化するようなってるキガスル。
デバッグ画面で、画面バッファーのサイズ、ウィンドウのサイズなどの情報を教えて欲しいです
詳細は覚えていませんが、今動画を見て確認したところ、「画面バッファー」と「ウィンドウのサイズ」の幅と高さは25x22、フォントはおそらく美咲フォント、フォントのサイズは36くらいで、コンソールウィンドウを拡大か縮小して配信画面に表示しています。フォントのサイズは配信画面用の設定なので、ご使用のディスプレイの解像度に合わせてお好みで設定するのが良いと思います。
@@Gamedokan ありがとうございます
何度もすみません、何度調べてもこの動画と同じ"□"が出てこないのですが、なんと調べたら出てきますか?
@@seattles7408 「しかく」で変換候補出てくる「■/□」です。IMEは「Google 日本語入力」で確認しました。
この動画では「美咲フォント」というフリーのレトロゲームフォントを使っているようで、他のフォントの■とは微妙に異なります。
@@Gamedokan すみません、わざわざありがとうございます
ミノがフィールドの両端に接地してしまうのですが何が原因だと思われますか?
MinoIntersectField関数で「ミノのマスのX座標が【0未満】であれば」という判定が【0以下】になっているのではないかと思います。具体的には、下記のところです。
【正常】
(mino.x + x < 0)
【間違い】
(mino.x + x
@@Gamedokan 返信ありがとうございます。ご指摘された関数部分を確認しましたが、動画の通りに記述していました。一つ疑問に感じたのですが、MinoIntersectField関数は壁を超えるキー入力がされた時にtrueを返すため、壁に接地されて新しいミノが降ってきてしまう気がするのですが、壁と床の処理の区別(?)はどこでされているのでしょうか。
重ねての質問失礼しました。
@@tetsu8987 壁と床、さらにフィールド上に配置されたブロックの区別は行っておりません。落下ブロックが降ってくる条件は、一定時間毎の自動落下時に、落下ブロックが壁と重なってしまう場合です。その判定もMinoIntersectField関数で行なっているので、この関数が正しく機能しているか(ミノがあらゆる方向からブロックに突っ込めないようになっているか)を確認すると何かわかるかもしれません。
テトリミノが床やミノ同士で設置すると設置してる部分が消えてしまうのですが、何が原因だと思われますか?
例えばミノを設置したつもりが0の配列を設置してしまって上書きした状態になってるとか
(ミノなしを0と定義)
if(field[0][4]==0){
while(1)//動画の166行あたり
.....
}
else{
system("cls");
printf("GAME OVER");
}
と、書いてゲームオーバーの判定をしようしたのですがうまくいきませんでした。
代わりに
while(1){
.....
if(field[0][4]==1){
break;
}
}
ststem("cls");
printf("GAME OVER");
と、したらうまくいきました。
この二つに違いが生まれた理由を教えてほしいです。
また、もっと良い書き方があれば教えていただきたいです。
前者はゲームオーバー処理に到達することは決してありません。
前者は次のような構造になっています。
if(条件式)
{
処理1
}
else
{
処理2
}
条件式が真(非0)であれば処理1、偽(0)であれば処理2に分岐して終了します。処理1に入ってしまった時点で処理2に移行することはありませんん。
後者はの構造は次のようになっています。
whie(1)
{
}
処理
ループを抜けたら、必ず処理に移行します。
動画では割愛しましたが、ゲームオーバーを入れるなら、ブロックがフィールドに接地して新しいミノが生成された瞬間に、フィールドとの当たり判定をとって、当たっていればゲームオーバーの処理を行います。具体的には下記のとおりです。
int main()
{
...
whike(1){
if(nowClock >= lastClock + INTERVAL) {// 一定時間毎の更新
....
if(MinoIntersectField()) {// ミノが接地したら
....
InitMino():// 新しいミノが生成される
// ↓↓↓追加箇所↓↓↓
if(MinoIntersectField()) {// ミノとフィールドの当たり判定
system("cls");
printf("GAME OVER");
_getch();
Init();// ゲームをリセットする
}
// ↑↑↑追加箇所↑↑↑
}
}
}
}
@@Gamedokan なるほど!丁寧にありがとうございました。
コードはどちらに貼っていますか?
ゲームオーバー判定を知りたいです!
ゲームオーバー判定は、新しいテトリミノが生成されたときに、そのテトリミノとフィールドの当たり判定をして、もし当たっていればゲームオーバーということになります。