メインコンテンツへスキップ
見出し画像

GameMakerでノベルパートを実装する方法

    こんにちは。私は現在GameMakerというゲームエンジンをつかって音ゲーを作っているのですが、その中でノベルパートを実装しようと思い立ちました。やり方はネットで調べればいいやと思ったのですが、いざ探してみると案外見つからず、結果、手探りでの実装になりました。

    初心者ながら試行錯誤を重ねなんとか形になったので、記録として残しておきます。同じようにノベル・アドベンチャーパートを実装したい方の参考になれば幸いです。

    実装する要素

    以下の要素を実装します:
    ・画面クリックでテキスト送り
    ・人物の登場・退場
    ・立ち絵の変更
    ・SE(効果音)の再生
    【2025年2月02日追記】
    ・テキストの自動改行
    ・メッセージが表示されきっていない時、画面クリックで全文表示
    以下に、サンプル動画を示します:

    Gamemakerで短いノベルパートを作成してみました!需要があるかはわかりませんが、やり方の詳細をnoteとかに残しておこうかなと考えています。 pic.twitter.com/j2FevaYHUq

    — キャンディペンシル (@candyenpitsu) January 2, 2025

    上記の動画の使用素材
    背景:みんちりえ様https://min-chi.material.jp/
    立ち絵: 坂本アヒル様https://www.pixiv.net/artworks/92641351
    SE素材: 効果音ラボ様https://soundeffect-lab.info/

    フォントの設定

    GameMakerのデフォルトフォントは英語専用で、日本語をサポートしていないため、日本語を表示するには日本語対応フォント(游明朝とか)を設定する必要があります。また、GameMakerは必要な文字だけを事前に読み込んでおく仕組みなので、「どの文字を使うか」を指定する必要があります。
    今回は以下の図のように游明朝にひらがな・カタカナと一部漢字を登録し、fnt_minchoというフォントを設定しました。

    画像

    オブジェクト構成と配置

    このノベルゲームパート用に、以下の4つのオブジェクトをroomに配置しています。

    画像

    obj_click_area
    役割: 画面クリックを検知し、表示するメッセージ番号の更新や立ち絵の切り替えフラグを立てる。

    obj_girl
    役割: 立ち絵関係のフラグに応じて立ち絵の登場や退場、変更を行う。

    obj_message_window
    役割: メッセージウィンドウを表示する。特別な機能はなし。

    obj_message
    役割: メッセージ番号に応じたメッセージを表示する。

    コードの記述

    obj_message

    ///// Createイベント
    message_array = [
    "",
    "1画面クリックでメッセージ送り。メッセージが長い場合はこのサンプルのように自動改行されます。",
    "2キャラクター登場",
    "3立ち絵変更",
    "4SE再生+立ち絵変更",
    "5キャラクター退場",
    "6終わり",
    "",
    ];
    
    depth = 0;//オブジェクト配置の奥行
    
    message_index = 0; // 表示するメッセージの番号
    current_message = ""; // 初期表示のメッセージ
    
    // メッセージを一文字ずつ表示するための変数
    num_letter = 0; // 表示する文字数
    message_timer = 0; // 表示タイミング用のタイマー
    message_speed = 2; // メッセージ表示速度(フレーム数)
    
    /// 指定した座標(x, y)に、指定した幅(max_width)で折り返しながらtextを描画する
    function draw_wrapped_text(_x, _y, _text, _max_width) {
        var _lines = [];
        var _current_line = "";
        var _current_width = 0;
        var _char, _char_width;
        
        var _len = string_length(_text);
        for (var i = 1; i <= _len; i++) {
            _char = string_char_at(_text, i);
            _char_width = string_width(_char);
            
            if (_char == "\n") { // 改行があれば強制的に改行
                _lines[array_length(_lines)] = _current_line;
                _current_line = "";
                _current_width = 0;
            } else if (_current_width + _char_width > _max_width) {
                // 最大幅を超えたら改行
                _lines[array_length(_lines)] = _current_line;
                _current_line = _char;
                _current_width = _char_width;
            } else {
                _current_line += _char;
                _current_width += _char_width;
            }
        }
        // 最後の行を追加
        if (_current_line != "") {
            _lines[array_length(_lines)] = _current_line;
        }
    
        // 描画処理
        var _line_height = string_height("あ"); // 日本語フォントの高さを取得
        for (var j = 0; j < array_length(_lines); j++) {
            draw_text(_x, _y + j * _line_height, _lines[j]);
        }
    }
    
    
    
    ///// Stepイベント
    // メッセージを一文字ずつ表示するための処理
        message_timer++;
        if (message_timer >= message_speed) {
            num_letter++;
            message_timer = 0;
    		if(num_letter > 1000){
    			num_letter = 1000;
    		}
        }
    
    //表示するメッセージを更新する処理
    if (message_index >= 0 && message_index < array_length(message_array)) {
        current_message = message_array[message_index];
    } else {
        // 範囲外の場合の処理
        current_message = ""; // 例: 空文字を設定
    }
    
    
    
    ///// Drawイベント
    draw_set_font(fnt_mincho); // フォントを設定
    draw_set_color(c_white); // 色を白に設定
    var _x = 80;//テキスト位置x
    var _y = 550;//テキスト位置y
    var _max_width = 1080;//テキスト折り返し位置
    
    // テキストを折り返して描画
    draw_wrapped_text(_x, _y, string_copy(current_message, 1, num_letter), _max_width);

    obj_message_window

    /////Createイベント
    image_alpha = 0.7;//半透明にする
    depth = 1;//メッセージより奥になるように奥行を調整

    obj_girl

    /////Createイベント
    depth = 2;
    
    image_alpha = 0;         // 初期状態は透明
    start_fade_in = false;   // フェードインの開始フラグ
    start_fade_out = false;   // フェードアウトの開始フラグ
    
    
    
    
    /////Stepイベント
    //フェードインで登場
    if (start_fade_in) {
        if (image_alpha < 1) {
            image_alpha += 0.03; // フェードインの速度
            if (image_alpha > 1) {
                image_alpha = 1; // 1を超えないように調整
                start_fade_in = false; // フェードイン終了後フラグを無効化
            }
        }
    }
    
    //フェードアウトで退場
    if (start_fade_out) {
        if (image_alpha > 0) {
            image_alpha -= 0.03; // フェードアウトの速度
            if (image_alpha < 0) {
                image_alpha = 0; // マイナスにならないように調整
                start_fade_out = false; // フェードアウト終了後フラグを無効化
            }
        }
    }

    obj_click_area

    /////Createイベント
    image_alpha = 0;//透明化
    depth=0;//最も手前になるように奥行を調整
    
    
    
    
    /////Left mouse pressイベント
    // 現在のメッセージの全文の長さを取得
    var full_length = string_length(obj_message.current_message);
    
    // 全文が表示されていないなら、全文表示に切り替える
    if (obj_message.num_letter < full_length) {
        obj_message.num_letter = full_length;
    	} else {
        // 既に全文表示されている場合は、次のメッセージへ進む処理
        obj_message.message_index++;
        obj_message.num_letter = 0;
        obj_message.message_timer = 0;
    }
    
    // 立ち絵切り替えを行うメッセージ番号を指定
    var normal_sprite = [0];
    var sad_sprite = [4];
    var smile_sprite = [3];
    
    //サウンド再生を行うメッセージ番号を指定
    var sound_shock = [4];
    
    //キャラクター登場を行うメッセージ番号を指定
    var chara_show = [2];
    
    //キャラクター退場を行うメッセージ番号を指定
    var chara_hide = [5];
    
    // メッセージ番号に応じてスプライトを変更
    if (array_contains(normal_sprite, obj_message.message_index)) {
        obj_girl.sprite_index = spr_piano_girl_normal;
    } else if (array_contains(sad_sprite, obj_message.message_index)) {
        obj_girl.sprite_index = spr_piano_girl_sad;
    } else if (array_contains(smile_sprite, obj_message.message_index)) {
        obj_girl.sprite_index = spr_piano_girl_smile;
    }
    
    // メッセージ番号に応じてサウンドを再生
    if (array_contains(sound_shock, obj_message.message_index)) {
        audio_play_sound(Shock, 1, false); 
    }
    
    // メッセージ番号に応じてキャラクター登場
    if (array_contains(chara_show, obj_message.message_index)) {
         with (obj_girl) {
                start_fade_in = true; 
         }
    }
    
    // メッセージ番号に応じてキャラクター退場
    if (array_contains(chara_hide, obj_message.message_index)) {
         with (obj_girl) {
                start_fade_out = true; 
         }
    }	
    

    終わり

    以上でノベルパートで最低限必要な要素は実装できるかなと思います。
    まだまだ改善の余地はあると思いますが、この記事が少しでも皆さんの参考になれば嬉しいです。

    あなたへのおすすめ