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