シューティングゲームを作りながらプログラミングを学ぼう!
高校生対象・プログラミング入門 (C言語編)


●プログラミングとは

 簡単に言うと,コンピュータで動くソフトウェアを作ることです。実際の内容は,データと
それを処理する一連の命令を書くことです。

●プログラミングは大変楽しいもので,趣味としても面白いものです

 音楽・絵・小説などと同じ様に,自分で想像した作品を実際に作り上げるのは自己表現として
も大変楽しいものです。音楽・絵・小説などと同様に,理論や技術の上に作り上げるアートとも
言えます。

●自分の作品を作りながら身につけるのが一番

 理論や技術と言っても,ゲーム作品でも何でも良いので自分の好きな作品を作ろうとチャレン
ジする内に自然と身につきます。おすすめは,学校で基本を習ったら,自分のオリジナルソフト
作品を作ってみることです。実力を身につける為にかかるコスト(金銭・時間)パフォーマンスは,
非常に高く,数冊の参考書を読んで実際にソフトを作って見ることを繰り返せば基本は身につき
ます。スポーツや音楽,絵画よりは習熟するのが簡単です。
 さらに,小品であっても良いソフトウェアを作ることが出来たらシェアウェアなどとして売る
ことも出来ます。(Apple社のスマートフォン・iPhone用のソフトは個人作成の作品でも簡単に
世界中で売ることができ,その作品の売り上げの70%が作者に収入として渡されますので,数
百万円の収入を得た日本の大学生も居ます。)
 情報系の学生のうち,実際に自分のオリジナル作品を作った経験のある人は10%以下と思わ
れますので,その10%の中に入れれば,ソフトウェア作成に近い現場の職業への就職には非常
に有利になるのははっきりしています。就職面接でも話題造りに役に立ったと言う本学学生の証
言もあります(本学の環境情報学科ソフトウェアコンテストの受賞者)。

東京情報大学主催・高校生ソフトウェアコンテストも是非利用してください。豪華賞品有り!!
 好きな作品を作って,実力を付け,賞まで貰って将来の進学や就職に役立つ実力が身につくと
いう,一石何鳥にもなるイベントです。

●参考:東京情報大学 環境情報学科ソフトウェアコンテスト

●この講義では,シューティングゲームを作りながらC言語というプログラミングのための言語と
 その書き方を紹介していきます。

●シューティングゲームで遊んでみよう
 (1)TuisDXGame2.zip をダウンロード 。
 (2)ダウンロードした を展開し,できた「TuisDXGame2」フォルダの中から「TuisDXGame2.vcproj」という
   ファイルを探してダブルクリック。→ VisualStudioが起動する。
 (3)教員の指示に従って,VisualStudioからのゲームを起動する。
 (4)矢印キーで自機の操作が可能。10秒経過後からスペースキーで弾を撃てます。最短クリアスコアは誰かな?

ソースプログラム

#include "DxLib.h" /* DXライブラリという便利な機能を使うため */
#include <math.h>  /* C言語に用意されているsin()とcos()を使うため */
 
int main()
{
    
    const int windowWidth  = 640;  /* 画面の横幅 */
    const int windowHeight = 480;  /* 画面の高さ */
    
    const int numOfMyShot    = 10; /* 自機の弾数 */
    const int numOfEnemyShot =  8; /* 敵機の弾数 */
 
    enum shotStatus { /* 弾の状態を表す列挙型定数の定義 */
      kUnShot,   /* 0 : 未発射状態(弾は画面内に無い) */
      kShot      /* 1 : 既発射状態(弾は画面内に有る) */
    };
 
    struct Ship {    /* 自機や敵機など「機体」を表す構造体 */
      int x, y;    /* 座標を表すフィールド x, y */
      int life;    /* ライフを表すint型のフィールド life */
      int graphic; /* グラフィック番号を表すint型のフィールド graphic */
    };
 
    struct Ship myShip, eShip;
    
    /* 最初の描画位置を画面の真ん中にセット(画面サイズはwindowHeight x windowWidth) */
    myShip.x =  windowWidth/2 - 32/2;
    myShip.y = windowHeight/2 - 32/2;
    myShip.life = 5;
    myShip.graphic = 0;
 
    int keyInput;             /* キー入力の結果を保存しておくための変数 */
 
    int bg     = 0; /* 背景グラフィックのグラフィック番号を記憶しておく変数 */
    int i; /* カウントダウンを表示するfor文用の変数 */
 
    int shots_Life[numOfMyShot]; /* 自機の弾が未発射(kUnShot)か発射済み(kShot)かを表す変数numOfMyShot個分の配列 */
    int shots_x[numOfMyShot];    /* 自機の弾のx座標を表す変数numOfMyShot個分の配列 */
    int shots_y[numOfMyShot];    /* 自機の弾のy座標を表す変数numOfMyShot個分の配列 */
    int j; /* 自機の弾を表している配列をfor文で処理するためのカウンタ変数 */
    int myShot = 0; /* 自機弾グラフィックのグラフィック番号を記憶しておく変数 */
 
    /* 敵機の最初の描画位置を画面の真ん中上部にセット */
    eShip.x = windowWidth/2 - 32/2;
    eShip.y = 0;
    eShip.life = 30;
    eShip.graphic = 0;
 
    int e_shots_Life[numOfEnemyShot]; /* 敵機の弾が未発射(kUnShot)か発射済み(kShot)かを表す変数numOfEnemyShot個分の配列 */
    int e_shots_x[numOfEnemyShot];    /* 敵機の弾のx座標を表す変数numOfEnemyShot個分の配列 */
    int e_shots_y[numOfEnemyShot];    /* 敵機の弾のy座標を表す変数numOfEnemyShot個分の配列 */
    int eShot    = 0;    /* 敵機弾グラフィックのグラフィック番号を記憶しておく変数 */
    int eShot_Go = 0;    /* 敵機が弾を発射できるかどうか(0のとき発射不可能、1の時発射可能) */
 
    int bg_scroll_y = 0; /* 背景グラフィックのスクロール位置 */
 
    int bgm_sound   = 0; /* BGMの音声データ番号を記憶しておく変数 */
    int shot_sound  = 0; /* 弾の発射音声データ番号を記憶しておく変数 */
    int eshot_sound = 0; /* 敵の弾の発射音性データ番号を記憶しておく変数 */
    int crash_sound = 0; /* 爆発の音声データ番号を記憶しておく変数 */
    int hit_sound   = 0; /* 弾のヒット音声データ番号を記憶しておく変数 */
    int win_sound   = 0; /* 勝利BGMの音声データ番号を記憶しておく変数 */
 
    int time = GetNowCount(); /* Windowsが起動してからの時間をミリ秒の単位で得る */
 
    for( i = 0; i < numOfMyShot; i++ ) {
        shots_Life[ i ] = kUnShot; shots_x[ i ] = 0; shots_y[ i ] = 0;
    }
    for( i = 0; i < numOfEnemyShot; i++ ) {
        e_shots_Life[ i ] = kUnShot; e_shots_x[ i ] = 0; e_shots_y[ i ] = 0;
    }
 
    SetWindowSizeChangeEnableFlag( TRUE );
    SetGraphMode( windowWidth, windowHeight, 32  );
 
    eShip.graphic = LoadGraph(  "EnemyShip32.bmp" ); /* 敵機グラフィック(32pixels × 32pixels)の読み込み */
    eShot = LoadGraph( "Chars16_blue.bmp" ); /* 敵機弾グラフィック(16pixels × 16pixels)の読み込み */
 
    myShot = LoadGraph( "Chars16_orange.bmp" ); /* 自機弾グラフィック(16pixels×16pixels)の読み込み */
 
    bgm_sound   = LoadSoundMem(  "ForGame008.mp3" ); /* BGM音声データの読み込み */
    shot_sound  = LoadSoundMem(        "shot.mp3" ); /* 弾の発射音声データの読み込み */
    eshot_sound = LoadSoundMem(       "eshot.mp3" ); /* 敵の弾の発射音声データの読み込み */
    crash_sound = LoadSoundMem(       "crash.mp3" ); /* 爆発の音声データの読み込み */
    hit_sound   = LoadSoundMem(         "hit.mp3" ); /* 弾のヒット音声データの読み込み */
    win_sound   = LoadSoundMem(         "win.mp3" ); /* 勝利BGMの音音声データの読み込み */
 
    SetFontSize( 30 ); /* 字体(フォント)のサイズを30に設定 */
    for( i = 3; i >= 0; i-- ) {
        ClsDrawScreen(); /* 描画先画面をきれいに消去する */
        /* 数値iを画面のだいたい中央あたり(windowWidth/2, windowHeight/2)に青色で表示 */
        DrawFormatString( windowWidth/2, windowHeight/2, GetColor( 50, 50, 255 ), "%d", i ); 
        WaitTimer( 700 ); /* 700ミリ秒動作をとめる */
    }
 
    PlaySoundMem( bgm_sound, DX_PLAYTYPE_LOOP, TRUE ); /* BGMを繰り返し再生開始 */
 
    bg     = LoadGraph( "bg.jpg" );       /* 背景グラフィック(windowWidthpixels × 600pixels)の読み込み */
    myShip.graphic = LoadGraph( "MyShip32.bmp" ); /* 自機グラフィック(32pixels × 32pixels)の読み込み */
 
    SetDrawScreen( DX_SCREEN_BACK );  /* 描画先を裏画面に設定する */
 
    while( 1 ){ /* 無限に繰り返す(無限ループ) */
    
        ClsDrawScreen(); /* 描画先画面をきれいに消去する */
        
        /* 背景描写( スクロール対応版 ) */
        if( bg_scroll_y  < 0 ) bg_scroll_y = bg_scroll_y + 600;
        if( (bg_scroll_y + windowHeight) > 600 ) DrawRectGraph( 0, 600 - bg_scroll_y, 0, 0, windowWidth, bg_scroll_y + windowHeight - 600, bg, TRUE, FALSE );
        DrawRectGraph( 0, 0, 0, bg_scroll_y, windowWidth, 600 - bg_scroll_y, bg, TRUE, FALSE );
        bg_scroll_y = bg_scroll_y - 2;
 
        keyInput = GetJoypadInputState( DX_INPUT_KEY_PAD1 ); /* キーボードからの入力を変数keyInputへ保存 */
        if( keyInput == PAD_INPUT_RIGHT ) { /* 右矢印キーを押し続けても画面の右側に自機が出ないようにする */
            if( (myShip.x + 5) >= ((windowWidth - 1)-31) ) {
                myShip.x = ((windowWidth - 1)-31);
            }
            else {
                myShip.x = myShip.x + 5;
            }
        }
        if( keyInput == PAD_INPUT_LEFT  ) { /* 左矢印キーを押し続けても画面の左側に自機が出ないようにする */
            if( (myShip.x - 5) <= 0 ) {
                myShip.x = 0;
            }
            else {
                myShip.x = myShip.x - 5;
            }
        }
        if( keyInput == PAD_INPUT_DOWN  ) {
            if( (myShip.y + 5) >= ((windowHeight - 1) - 31) ) {
                myShip.y = ((windowHeight - 1) - 31);
            }
            else {
                myShip.y = myShip.y + 5;
            }
        }
        if( keyInput == PAD_INPUT_UP    ) {
            if( (myShip.y + 5) <= 0 ) {
                myShip.y = 0;
            }
            else {
                myShip.y = myShip.y - 5;
            }
        }
        
        /* キーボードから入力された文字がスペースなら自機が弾を撃つ */
        if( GetInputChar( TRUE ) == ' ' && (GetNowCount() - time)>= 10000 ) { /* GetInputChar(TRUE)はDXライブラリの機能でキーボードで押した文字を入力する */
            for( j = 0; j < numOfMyShot; j++ ) {
                if( shots_Life[j] == kUnShot ) { /* 未発射の弾を見つけたら... */
                    shots_Life[j] = kShot;     /* 発射済みにして */
                    shots_x[j] = myShip.x + (32/2) - (16/2); /* 弾のx座標を自機の鼻先に設定する */
                    shots_y[j] = myShip.y;                   /* 弾のy座標を自機の鼻先に設定する */
                    PlaySoundMem( shot_sound, DX_PLAYTYPE_BACK, TRUE ); /* 弾の発射音性を再生 */
                    break; /* 発射する弾は1個だけでいいので、繰り返しを強制的に終了させる */
                }
            }
        }
 
        for( j = 0; j < numOfMyShot; j++ ) { /* 自機の弾numOfMyShot個に対して次の処理を行う */
            if( shots_y[j]+16 < 0 ) shots_Life[j] = kUnShot; /* 画面から飛び出した弾は未発射状態にする */
            if( shots_Life[j] != kUnShot ) {              /* もし自機のj番目の弾が発射済みなら... */
                DrawGraph( shots_x[j], shots_y[j], myShot, TRUE ); /* 自機のj番目の弾を画面に描画する */
                shots_y[j] = shots_y[j] - 8;        /* 次の描画時のためにj番目の弾の位置を前に進める */
            }
        }
 
        eShot_Go = 1; /* とりあえず発射可能状態にしておく */
        for( j = 0; j < numOfEnemyShot; j++ ) {
            if( e_shots_x[j]    < 0   ) e_shots_Life[j] = kUnShot; /* 画面から飛び出した弾は未発射状態にする */
            if( e_shots_x[j]+16 > (windowWidth - 1) ) e_shots_Life[j] = kUnShot; /* 画面から飛び出した弾は未発射状態にする */
            if( e_shots_y[j]+16 < 0   ) e_shots_Life[j] = kUnShot; /* 画面から飛び出した弾は未発射状態にする */
            if( e_shots_y[j]    > (windowHeight - 1) ) e_shots_Life[j] = kUnShot; /* 画面から飛び出した弾は未発射状態にする */
            if( e_shots_Life[j] != kUnShot ) eShot_Go = 0; /* numOfEnemyShot発の弾のうち1個でも発射済なら発射不可能とする */
        }
        if( eShot_Go == 1 ) { /* 敵機の弾が発射可能なら全ての弾を発射済みにして位置をセット */
            for( j = 0; j < numOfEnemyShot; j++ ) {
                e_shots_Life[j] = kShot;
                e_shots_x[j] = (eShip.x + 16 - 8); e_shots_y[j] = (eShip.y + 16 - 8);
            }
            PlaySoundMem( eshot_sound, DX_PLAYTYPE_BACK, TRUE ); /* 敵の弾の発射音性を再生 */
        }
 
        for( j = 0; j < numOfEnemyShot; j++ ) { /* 敵機の弾のうち、発射済みの弾を描画する。 */
            if( e_shots_Life[j] != kUnShot ) {
                DrawGraph( e_shots_x[j], e_shots_y[j], eShot, TRUE ); /* 敵機弾のグラフィックを描画する */
            }
            /* 次の描画時のためにj番目の弾の位置を前に進める */
            e_shots_x[j] = e_shots_x[j] + 6 * cos( j * ((2.0/numOfEnemyShot) * 3.1415926) );
            e_shots_y[j] = e_shots_y[j] + 6 * sin( j * ((2.0/numOfEnemyShot) * 3.1415926) );
        }
 
        DrawGraph( eShip.x, eShip.y, eShip.graphic, TRUE ); /* 座標ex,eyに敵機のグラフィックを描画する */
        /* 敵機の座標をランダムに自機に近づける。なお、GetRand(n)は、0からnまでのでたらめな値を返す */
        eShip.x = eShip.x + (myShip.x - eShip.x)/( GetRand(200) + 15 );  eShip.y = eShip.y + (myShip.y - eShip.y)/( GetRand(200) + 15 );
 
        DrawGraph( myShip.x, myShip.y, myShip.graphic, TRUE ); /* 座標x,yに自機のグラフィックを描画する */
 
        /* 衝突判定を行う */
        /* 衝突判定1.「自機と敵機」 */
        if( (( myShip.x <= eShip.x) && ( eShip.x <= (myShip.x + 31))) && (( myShip.y <= eShip.y) && ( eShip.y <= (myShip.y + 31))) ) {
            PlaySoundMem( crash_sound, DX_PLAYTYPE_BACK, TRUE ); /* 爆発音を再生 */
            myShip.life = 0; /* 自機のライフを0に設定 */
        }
 
        /* 衝突判定2.「自機の弾と敵機」 */
        for( i = 0; i < numOfMyShot; i++ ) {
            if( shots_Life[i] != kUnShot ) { /* 発射状態の弾に関してだけ敵機との衝突判定をする */
                if(
                    ( (eShip.x <= shots_x[i]) && (shots_x[i] <= (eShip.x + 31) ) && 
                    ( (eShip.y <= shots_y[i]) && (shots_y[i] <= (eShip.y + 31) ) ))
                  ) {
                    shots_Life[i] = kUnShot; /* 当たった弾を未発射状態にする */
                    PlaySoundMem( hit_sound, DX_PLAYTYPE_BACK, TRUE ); /* 弾のヒット音を再生 */
                    eShip.life = eShip.life - 1; /* 敵機のライフを1減らす */
                }
            }
        }
 
        /* 衝突判定3.「敵機の弾と自機」 */
        for( i = 0; i < numOfEnemyShot; i++ ) {
            if( e_shots_Life[i] != kUnShot ) { /* 発射状態の弾に関してだけ自機との衝突判定をする */
                if(
                    ( (myShip.x <= e_shots_x[i]) && (e_shots_x[i] <= (myShip.x + 31) ) && 
                    ( (myShip.y <= e_shots_y[i]) && (e_shots_y[i] <= (myShip.y + 31) ) ))
                  ) {
                    e_shots_Life[i] = kUnShot; /* 当たった弾を未発射状態にする */
                    PlaySoundMem( hit_sound, DX_PLAYTYPE_BACK, TRUE ); /* 弾のヒット音を再生 */
                    myShip.life = myShip.life - 1; /* 自機のライフを1減らす */
                }
            }
        }
 
        /* ライフと経過時間の表示 */
        DrawFormatString( 0, 0, GetColor( 20, 20, 255 ), "%02d", myShip.life );
        DrawFormatString( windowWidth - GetDrawFormatStringWidth( "%02d", eShip.life ), 0, GetColor( 255, 0, 0 ), "%02d", eShip.life );
        DrawFormatString( windowWidth/2 - ( GetDrawFormatStringWidth( "%03.2f", (GetNowCount() - time) / 1000.0 ) ) / 2, 
                         2 , GetColor( 255, 255, 0 ), "%03.2f", (GetNowCount() - time) / 1000.0 );
 
        /* 勝負が決まったら無限ループから抜け出す */
        if( eShip.life <= 0 || myShip.life <= 0 ) break;
 
        ScreenFlip(); /* 裏画面に描画したものを表画面に転写する */
 
        if( CheckHitKey(KEY_INPUT_ESCAPE) ) break; /* エスケープキーが押されたら繰り返し処理から出る */
    } /* while()の閉じ中括弧 */
 
    SetDrawScreen( DX_SCREEN_FRONT ); /* 描画先を表画面にする */
 
    /* プレイヤーが勝った場合 */
    if( eShip.life <= 0 ) {
        PlaySoundMem( crash_sound, DX_PLAYTYPE_BACK, TRUE ); /* 爆発音を再生 */
        StopSoundMem( bgm_sound ); /* BGM再生を停止 */
        PlaySoundMem( win_sound, DX_PLAYTYPE_LOOP, TRUE ); /* 勝利BGMをループ再生 */
        SetFontSize( 60 ); /* フォントサイズを60に設定 */
        DrawFormatString( windowWidth/2 - (GetDrawFormatStringWidth( "You Win!!" ) / 2 ), 
                          windowHeight/2 - 20, GetColor( 20, 20, 255 ), "You Win!!" );
    }
 
    /* プレイヤーが負けた場合 */
    if( myShip.life <= 0 ) {
        PlaySoundMem( crash_sound, DX_PLAYTYPE_BACK, TRUE ); /* 爆発音を再生 */
        SetFontSize( 60 ); /* フォントサイズを60に設定 */
        DrawFormatString( windowWidth/2 - (GetDrawFormatStringWidth( "You Lose!!" ) / 2 ), 
                          windowHeight/2 - 20, GetColor( 255, 100, 100 ), "You Lose!!" );
    }
 
    while( CheckHitKey( KEY_INPUT_ESCAPE ) == 0 ) {} /* エスケープキーが押されるのを待つ */
 
    return 0;
}

 


2012年度『プログラミング入門』授業内容

タイトル (共通資料へのリンク)
共通
資料
ゲーム作成コーナー
( シューティングゲーム作成例)
第1回
(4/12)


●クラス分けアンケート (10分間)

●完成品を動かしてみよう★ (前期最終版)

★ゲーム大会!!
 誰が最速クリアを達成するかな?

第2回 ●プログラムが動く仕組み
●基本学習用プロジェクトの作り方とコンパイルの方法)
●ゲーム用プロジェクトのための設定
(及び,「DXライブラリについて」)
第3回

● 書法とデータ型,変数とデータの表示方法
 
第4回
変数とデータの入力方法 ゲームの基本部分を動かしてみよう(解説編)
第5回 計算式,関数(命令)の利用方法 ゲーム用の命令を紹介します★
第6回

条件判定と条件分岐処理1(if文)

if文を使ってキャラクタを動かしてみよう★
第7回

条件判定と条件分岐処理2(if-else文)

グラフィックデータを読み込んで表示してみよう★

キャラクタが画面の外に出て行くのを防ごう★
第8回 中間試験
 
第9回 繰り返し処理1(while文基本編) ゲームのメインループについて★
第10回 繰り返し処理2(while文応用編)  
第11回

繰り返し処理3(for文)

カウントダウンを表示させてみよう★
第12回

配列1(基礎編)

弾を撃てるようにしてみよう!★
山崎先生作成の課題問題

第13回 配列2(応用編) 敵が弾を発射するようにしてみよう★
第14回 総合演習(条件分岐・繰り返し・配列の組み合わせ)   衝突判定とエンディングを入れてみよう★
第15回 まとめ

ゲームとして仕上げてみよう!★
背景スクロールを実現(実装)してみよう
経過時間とライフを表示してみよう(前期最終版の完成)
(後期の『ゲーム・アニメーション基礎演習』
で更に「良いプログラム」に書き直していきま す)



2011年度『ゲーム・アニメーション基礎演習』C言語クラス 授業内容

タイトル (共通資料へのリンク)
共通
資料
ゲーム作成コーナー
( シューティングゲーム作成例)
第1回
(9/29)

●前期の復習

弾を撃てるようにしてみよう!★ まで復習
山崎先生作成の課題問題

第2回
(10/5)
●関数   敵が弾を発射するようにしてみよう★ まで復習
山崎先生作成の課題問題(関数編)
第3回 ポインタ   衝突判定とエンディングを入れてみよう★ まで復習