
ティラノビルダーでLive2Dモデルの上にふきだしアイコンを表示する

今回の機能は、マップ上などに表示するフキダシアイコンの機能です。
イベントクリアをすると「…」が「♥」になります。
speech_bubble_config.ks
ふきだしカスタマイズ用
..\data\scenario\systemに入れます
;===============================================
; フキダシシステム カスタマイズ設定
; このファイルで見た目や動作をカスタマイズできます
;===============================================
[iscript]
// フキダシカスタマイズ設定
window.SpeechBubbleConfig = {
// =====================
// サイズ設定
// =====================
size: {
width: 60, // フキダシの幅(ピクセル)
height: 60, // フキダシの高さ(ピクセル)
tailWidth: 24, // しっぽの幅(ピクセル)※三角形
tailHeight: 20, // しっぽの高さ(ピクセル)※三角形
tailOffset: 25 // しっぽの横位置(ピクセル)
},
// =====================
// 色設定
// =====================
colors: {
background: 'white', // フキダシの背景色
border: '#333', // フキダシの枠線色
borderWidth: 3, // 枠線の太さ(ピクセル)
dotColor: '#333', // ドットの色
heartColor: '#ff69b4' // ハートの色
},
// =====================
// アニメーション設定
// =====================
animation: {
// 浮遊アニメーション
float: {
duration: 2000, // 1周期の時間(ミリ秒)
distance: 15 // 上下移動の距離(ピクセル)
},
// ドットアニメーション
dots: {
interval: 500, // ドット表示間隔(ミリ秒)
fadeSpeed: 1500 // フェード速度(ミリ秒)
},
// ハートアニメーション
heart: {
duration: 1000, // 鼓動の速度(ミリ秒)
scale: 1.2 // 拡大率
},
// フェード効果
fade: {
show: 300, // 表示時のフェード時間(ミリ秒)
hide: 300 // 非表示時のフェード時間(ミリ秒)
},
// 移動アニメーション
move: {
duration: 500, // デフォルトの移動時間(ミリ秒)
easing: 'swing' // イージング('swing' or 'linear')
}
},
// =====================
// スタイル設定
// =====================
style: {
borderRadius: '50%', // フキダシの角丸(円形)
boxShadow: '0 4px 8px rgba(0,0,0,0.2)', // 影
fontSize: {
dot: 20, // ドットのサイズ(px)
heart: 28 // ハートのサイズ(px)
},
zIndex: 9999 // 重なり順序(大きいほど前面)
},
// =====================
// デザインプリセット
// =====================
// presets の値を使ってデザインを一括変更できます
presets: {
// デフォルト(白いフキダシ)
default: {
background: 'white',
border: '#333',
heartColor: '#ff69b4'
},
// パステルピンク
pink: {
background: '#ffe4e1',
border: '#ff69b4',
heartColor: '#ff1493'
},
// パステルブルー
blue: {
background: '#e6f3ff',
border: '#4a90e2',
heartColor: '#ff69b4'
},
// パステルイエロー
yellow: {
background: '#fff9e6',
border: '#ffd700',
heartColor: '#ff69b4'
},
// ダーク
dark: {
background: '#2c2c2c',
border: '#fff',
heartColor: '#ff1493',
dotColor: '#fff'
},
// グラデーション(CSS gradientを使用)
gradient: {
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: '#fff',
heartColor: '#fff',
dotColor: '#fff'
}
}
};
// プリセットを適用する関数
window.applySpeechBubblePreset = function(presetName) {
var preset = window.SpeechBubbleConfig.presets[presetName];
if (preset) {
if (preset.background) window.SpeechBubbleConfig.colors.background = preset.background;
if (preset.border) window.SpeechBubbleConfig.colors.border = preset.border;
if (preset.heartColor) window.SpeechBubbleConfig.colors.heartColor = preset.heartColor;
if (preset.dotColor) window.SpeechBubbleConfig.colors.dotColor = preset.dotColor;
// スタイルを再適用
$("#speech_bubble_style").remove();
console.log("プリセット「" + presetName + "」を適用しました");
}
};
[endscript]
;===============================================
; カスタマイズ例
;===============================================
; ◆ プリセットを使う場合
; [iscript]
; applySpeechBubblePreset('pink'); // ピンクのフキダシに変更
; [endscript]
; ◆ 個別に設定を変更する場合
; [iscript]
; window.SpeechBubbleConfig.size.width = 80;
; window.SpeechBubbleConfig.size.height = 80;
; window.SpeechBubbleConfig.colors.background = '#ffe4e1';
; window.SpeechBubbleConfig.animation.float.distance = 20;
; [endscript]
; ◆ アニメーション速度を変更する場合
; [iscript]
; window.SpeechBubbleConfig.animation.float.duration = 1500; // 速く
; window.SpeechBubbleConfig.animation.dots.interval = 300; // ドットを速く
; [endscript]
;===============================================
; 設定テストシナリオ
;===============================================
[macro name="test_preset"]
[iscript]
applySpeechBubblePreset(mp.preset);
[endscript]
[speech_bubble_clear_all]
[wait time=100]
[speech_bubble_show character="test" x=400 y=200]
[endmacro]
;===============================================
; 使用例:シナリオの先頭でプリセット適用
;===============================================
; タイトル画面やゲーム開始時に以下を記述
; [call storage="system/speech_bubble_config.ks"]
; [iscript]
; applySpeechBubblePreset('pink');
; [endscript]
speech_bubble_macro.ks
..\data\scenario\systemに入れます
;===============================================
; Live2D Speech Bubble System
; モデルの頭上にフキダシを表示するシステム
;===============================================
;-----------------------------------------------
; CSS初期化
;-----------------------------------------------
[iscript]
// CSS追加(初回のみ)
if (!$("#speech_bubble_style").length) {
var styleHTML =
'<style id="speech_bubble_style">' +
'.speech-bubble {' +
'position: relative !important;' +
'width: 100px;' +
'height: 80px;' +
'pointer-events: none;' +
'animation: float 2s ease-in-out infinite;' +
'}' +
'.bubble-content {' +
'width: 100%;' +
'height: 100%;' +
'background: white;' +
'border: 3px solid #333 ;' +
'border-radius: 60px;' +
'box-shadow: 0 4px 8px rgba(0,0,0,0.2);' +
'display: flex;' +
'align-items: center;' +
'justify-content: center;' +
'position: relative;' +
'}' +
'.bubble-content::after {' +
'content: "";' +
'position: absolute;' +
'bottom: -8px;' +
'left: 26.5px;' +
'width: 0;' +
'height: 0;' +
'border-left: 8px solid transparent;' +
'border-right: 8px solid transparent;' +
'border-top: 20px solid white;' +
'}' +
'.bubble-content::before {' +
'content: "";' +
'position: absolute;' +
'bottom: -12px;' +
'left: 22.3px;' +
'width: 0;' +
'height: 0;' +
'border-left: 14px solid transparent;' +
'border-right: 14px solid transparent;' +
'border-top: 22px solid #333 ;' +
'z-index: -1;' +
'}' +
'.bubble-body {' +
'font-size: 30px;' +
'color: #333 ;' +
'display: flex;' +
'gap: 3px;' +
'align-items: center;' +
'justify-content: center;' +
'}' +
'.dot {' +
'opacity: 0;' +
'animation: fadeInOut 1.5s infinite;' +
'}' +
'.dot1 { animation-delay: 0s; }' +
'.dot2 { animation-delay: 0.3s; }' +
'.dot3 { animation-delay: 0.6s; }' +
'.heart {' +
'font-size: 40px !important;' +
'color: #ff69b4 !important;' +
'display: inline-block !important;' +
'animation: heartbeat 1s ease-in-out infinite !important;' +
'transform-origin: center center !important;' +
'line-height: 1 !important;' +
'}' +
'@keyframes float {' +
'0%, 100% { transform: translateY(0px); }' +
'50% { transform: translateY(-15px); }' +
'}' +
'@keyframes fadeInOut {' +
'0%, 100% { opacity: 0; }' +
'50% { opacity: 1; }' +
'}' +
'@keyframes heartbeat {' +
'0%, 100% { transform: scale(1); }' +
'50% { transform: scale(1.3); }' +
'}' +
'</style>';
$("head").append(styleHTML);
}
// ドットアニメーション関数
window.startDotAnimation = function(bubble_id) {
var dots = $("#" + bubble_id + " .dot");
var index = 0;
var showNextDot = function() {
dots.css("opacity", "0");
for (var i = 0; i <= index; i++) {
$(dots[i]).css("opacity", "1");
}
index = (index + 1) % 4;
};
showNextDot();
window["dotInterval_" + bubble_id] = setInterval(showNextDot, 500);
};
[endscript]
;-----------------------------------------------
; [speech_bubble_show] タグ
; パラメータ:
; character : キャラクター名(必須)
; x : X座標(必須)
; y : Y座標(必須)
; size : サイズ倍率(デフォルト: 1.0)
;-----------------------------------------------
[macro name="speech_bubble_show"]
[iscript]
(function() {
var character = mp.character || "default";
var x = parseInt(mp.x) || 0;
var y = parseInt(mp.y) || 0;
var size = parseFloat(mp.size) || 1.0;
var bubble_id = "speech_bubble_" + character;
var wrapper_id = bubble_id + "_wrapper";
// イベント変数名
var eventVarName = character.replace("model", "event") + "_cleared";
// フキダシ内容更新関数
var updateBubbleContent = function() {
var event_cleared = (typeof f[eventVarName] !== "undefined") ? f[eventVarName] : false;
var $wrapper = $("#" + wrapper_id);
// フキダシが存在しない場合は新規作成
if ($wrapper.length === 0) {
var wrapperStyle = "position: absolute; left:" + x + "px; top:" + y + "px; transform: scaleX(" + size + ") scaleY(" + Math.abs(size) + "); transform-origin: center center; z-index: 999999;";
var bubbleHTML =
'<div id="' + wrapper_id + '" style="' + wrapperStyle + '">' +
'<div id="' + bubble_id + '" class="speech-bubble" data-event-cleared="' + event_cleared + '">' +
'<div class="bubble-content">' +
'<div class="bubble-body">' +
'<span class="dot dot1">•</span>' +
'<span class="dot dot2">•</span>' +
'<span class="dot dot3">•</span>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
$("body").append(bubbleHTML);
$wrapper = $("#" + wrapper_id);
}
var $bubble = $("#" + bubble_id);
// data属性を更新
$bubble.attr("data-event-cleared", event_cleared);
// ドットアニメーションを停止
if (window["dotInterval_" + bubble_id]) {
clearInterval(window["dotInterval_" + bubble_id]);
delete window["dotInterval_" + bubble_id];
}
// 内容のみを更新
if (event_cleared) {
$bubble.find(".bubble-body").html('<span class="heart">♥</span>');
} else {
$bubble.find(".bubble-body").html(
'<span class="dot dot1">•</span>' +
'<span class="dot dot2">•</span>' +
'<span class="dot dot3">•</span>'
);
window.startDotAnimation(bubble_id);
}
};
// 初回表示
updateBubbleContent();
// 既存の監視を停止
if (window["bubbleWatcher_" + character]) {
clearInterval(window["bubbleWatcher_" + character]);
}
// 変数を監視して自動更新
window["bubbleWatcher_" + character] = setInterval(function() {
var currentState = (typeof f[eventVarName] !== "undefined") ? f[eventVarName] : false;
var $bubble = $("#" + bubble_id);
var displayedState = $bubble.attr("data-event-cleared");
if (displayedState === undefined) {
displayedState = "false";
}
var displayedBool = (displayedState === "true");
if (currentState !== displayedBool) {
updateBubbleContent();
}
}, 100);
})();
[endscript]
[endmacro]
;-----------------------------------------------
; [speech_bubble_hide] タグ
; パラメータ:
; character : キャラクター名(必須)
;-----------------------------------------------
[macro name="speech_bubble_hide"]
[iscript]
(function() {
var character = mp.character || "default";
var bubble_id = "speech_bubble_" + character;
// 監視を停止
if (window["bubbleWatcher_" + character]) {
clearInterval(window["bubbleWatcher_" + character]);
delete window["bubbleWatcher_" + character];
}
// ドットアニメーションを停止
if (window["dotInterval_" + bubble_id]) {
clearInterval(window["dotInterval_" + bubble_id]);
delete window["dotInterval_" + bubble_id];
}
$("#" + bubble_id + "_wrapper").fadeOut(300, function() {
$(this).remove();
});
})();
[endscript]
[endmacro]
;-----------------------------------------------
; [speech_bubble_clear_all] タグ
; 全てのフキダシを削除
;-----------------------------------------------
[macro name="speech_bubble_clear_all"]
[iscript]
(function() {
// 全ての監視を停止
for (var key in window) {
if (key.indexOf("bubbleWatcher_") === 0) {
clearInterval(window[key]);
delete window[key];
}
}
// 全てのドットアニメーションを停止
$(".speech-bubble").each(function() {
var bubble_id = $(this).attr("id");
if (window["dotInterval_" + bubble_id]) {
clearInterval(window["dotInterval_" + bubble_id]);
delete window["dotInterval_" + bubble_id];
}
});
$("[id$='_wrapper']").remove();
})();
[endscript]
[endmacro]
;-----------------------------------------------
; [speech_bubble_calc] タグ
; フキダシ座標を計算するヘルパータグ
; パラメータ:
; model_x : モデルのX座標(-1.0~1.0)
; model_y : モデルのY座標(-1.0~1.0)
; scale : モデルのスケール
; var_x : 結果を格納する変数名(X座標)
; var_y : 結果を格納する変数名(Y座標)
; width : モデルの基準幅(デフォルト: 600)
; height : モデルの基準高さ(デフォルト: 1200)
; offset_y : 頭の位置比率(デフォルト: 0.1)
;-----------------------------------------------
[macro name="speech_bubble_calc"]
[iscript]
(function() {
var model_x_actual = parseFloat(mp.model_x) || 0;
var model_y_relative = parseFloat(mp.model_y) || 0;
var model_scale = parseFloat(mp.scale) || 1.0;
var var_x = mp.var_x || "bubble_x";
var var_y = mp.var_y || "bubble_y";
// オプションパラメータ
var model_width_base = parseInt(mp.width) || 600;
var model_height_base = parseInt(mp.height) || 1200;
var head_offset_y_ratio_base = parseFloat(mp.offset_y) || 0.1;
// 固定値
var screen_width = 1280;
var screen_height = 720;
var x_correction_base = 0.125;
// 計算
var head_offset_y_ratio = head_offset_y_ratio_base * model_scale;
var x_correction = x_correction_base * model_scale;
var model_x_relative = model_x_actual - x_correction;
var model_x = (model_x_relative + 1.0) / 2.0 * screen_width;
var model_y = (model_y_relative + 1.0) / 2.0 * screen_height;
var model_height = model_height_base * model_scale;
// 結果を変数に格納
f[var_x] = model_x;
f[var_y] = model_y - (model_height * head_offset_y_ratio);
})();
[endscript]
[endmacro]
;===============================================
; システム初期化完了
;===============================================
1キャラ用
;===============================================
; フキダシシステム 実用版サンプル(1キャラ版)
;===============================================
[title name="フキダシサンプル"]
;マクロ読み込み
[call storage="system/speech_bubble_macro.ks"]
;背景(お好みの背景に変更してください)
[bg storage="room.jpg" time=1000]
;フキダシ表示の設定と計算
[iscript]
// =====================================
// 設定項目
// =====================================
// ゲーム画面のサイズ(固定)
var screen_width = 1280;
var screen_height = 720;
// モデルの基準サイズ(スケール1.0の時のピクセル値)
// ※Live2Dモデルファイルの実際のサイズを入れてください
var model_width_base = 600; // モデルの幅
var model_height_base = 1200; // モデルの高さ
// 調整値の基準(スケール1.0の時の値)
// 頭と吹き出しの距離
// 大きくする→フキダシが上に 小さくする→フキダシが下に
var head_offset_y_ratio_base = 0.1; // スケール1.0換算
// X座標の補正値(通常は変更不要)
var x_correction_base = 0.125; // スケール1.0換算
// ========== モデル設定 ==========
// ※Live2Dコンポーネントで設定した値を入れてください
// X座標は大体0.05前後を引いた数値がいい感じです。
// Y座標は大体そのまま~0.3前後を引いた数値がいい感じです。
var model_x_actual = -0.45; // コンポーネントのX座標(-1.0~1.0)
var model_y_relative = -0.5; // コンポーネントのY座標(-1.0~1.0)
var model_scale = 0.8; // コンポーネントのスケール
// =====================================
// 自動計算(以下は変更不要)
// =====================================
// スケールに応じた調整値を計算
var head_offset_y_ratio = head_offset_y_ratio_base * model_scale;
var x_correction = x_correction_base * model_scale;
var model_x_relative = model_x_actual - x_correction;
// ピクセル座標に変換
// コンポーネントの-1.0~1.0を画面のピクセル座標に変換
var model_x = (model_x_relative + 1.0) / 2.0 * screen_width;
var model_y = (model_y_relative + 1.0) / 2.0 * screen_height;
var model_height = model_height_base * model_scale;
// フキダシの座標を計算(モデルの真上に配置)
f.bubble_x = model_x;
f.bubble_y = model_y - (model_height * head_offset_y_ratio);
[endscript]
;イベントクリア状態を初期化(未クリア)
[eval exp="f.event1_cleared = false"]
[speech_bubble_show character="model1" x=&f.bubble_x y=&f.bubble_y size="-0.5"]
;自動更新付きフキダシ表示
[speech_bubble_show character="model1" x=&f.bubble_x y=&f.bubble_y]
;ここから下はコンポーネントで挿入する会話やイベントを配置
;※変数「event1_cleared」をtrueに変更すると自動的にハートマークに変わります
複数キャラ用 4人版
;===============================================
; フキダシシステム 実用版サンプル(4キャラ版)
;===============================================
[title name="フキダシサンプル 4キャラ版"]
;マクロ読み込み
[call storage="system/speech_bubble_macro.ks"]
;背景(お好みの背景に変更してください)
[bg storage="room.jpg" time=1000]
;フキダシ表示の設定と計算
[iscript]
// =====================================
// 設定項目
// =====================================
// ゲーム画面のサイズ(固定)
var screen_width = 1280;
var screen_height = 720;
// モデルの基準サイズ(スケール1.0の時のピクセル値)
// ※Live2Dモデルファイルの実際のサイズを入れてください
var model_width_base = 600; // モデルの幅
var model_height_base = 1200; // モデルの高さ
// 調整値の基準(スケール1.0の時の値)
// 頭と吹き出しの距離
// 大きくする→フキダシが上に 小さくする→フキダシが下に
var head_offset_y_ratio_base = 0.1; // スケール1.0換算
// X座標の補正値(通常は変更不要)
var x_correction_base = 0.125; // スケール1.0換算
// ========== モデル1の設定 ==========
// ※Live2Dコンポーネントで設定した値を入れてください
// X座標は大体0.05前後を引いた数値がいい感じです。
// Y座標は大体そのまま~0.3前後を引いた数値がいい感じです。
var model1_x_actual = -0.6; // コンポーネントのX座標(-1.0~1.0)
var model1_y_relative = -0.5; // コンポーネントのY座標(-1.0~1.0)
var model1_scale = 0.4; // コンポーネントのスケール
// ========== モデル2の設定 ==========
var model2_x_actual = -0.2;
var model2_y_relative = -0.5;
var model2_scale = 0.4;
// ========== モデル3の設定 ==========
var model3_x_actual = 0.2;
var model3_y_relative = -0.5;
var model3_scale = 0.4;
// ========== モデル4の設定 ==========
var model4_x_actual = 0.6;
var model4_y_relative = -0.5;
var model4_scale = 0.4;
// =====================================
// 自動計算(以下は変更不要)
// =====================================
// ===== モデル1のフキダシ座標計算 =====
var head_offset_y_ratio1 = head_offset_y_ratio_base * model1_scale;
var x_correction1 = x_correction_base * model1_scale;
var model1_x_relative = model1_x_actual - x_correction1;
var model1_x = (model1_x_relative + 1.0) / 2.0 * screen_width;
var model1_y = (model1_y_relative + 1.0) / 2.0 * screen_height;
var model1_height = model_height_base * model1_scale;
f.bubble1_x = model1_x;
f.bubble1_y = model1_y - (model1_height * head_offset_y_ratio1);
// ===== モデル2のフキダシ座標計算 =====
var head_offset_y_ratio2 = head_offset_y_ratio_base * model2_scale;
var x_correction2 = x_correction_base * model2_scale;
var model2_x_relative = model2_x_actual - x_correction2;
var model2_x = (model2_x_relative + 1.0) / 2.0 * screen_width;
var model2_y = (model2_y_relative + 1.0) / 2.0 * screen_height;
var model2_height = model_height_base * model2_scale;
f.bubble2_x = model2_x;
f.bubble2_y = model2_y - (model2_height * head_offset_y_ratio2);
// ===== モデル3のフキダシ座標計算 =====
var head_offset_y_ratio3 = head_offset_y_ratio_base * model3_scale;
var x_correction3 = x_correction_base * model3_scale;
var model3_x_relative = model3_x_actual - x_correction3;
var model3_x = (model3_x_relative + 1.0) / 2.0 * screen_width;
var model3_y = (model3_y_relative + 1.0) / 2.0 * screen_height;
var model3_height = model_height_base * model3_scale;
f.bubble3_x = model3_x;
f.bubble3_y = model3_y - (model3_height * head_offset_y_ratio3);
// ===== モデル4のフキダシ座標計算 =====
var head_offset_y_ratio4 = head_offset_y_ratio_base * model4_scale;
var x_correction4 = x_correction_base * model4_scale;
var model4_x_relative = model4_x_actual - x_correction4;
var model4_x = (model4_x_relative + 1.0) / 2.0 * screen_width;
var model4_y = (model4_y_relative + 1.0) / 2.0 * screen_height;
var model4_height = model_height_base * model4_scale;
f.bubble4_x = model4_x;
f.bubble4_y = model4_y - (model4_height * head_offset_y_ratio4);
[endscript]
;イベントクリア状態を初期化(全て未クリア)
[eval exp="f.event1_cleared = false"]
[eval exp="f.event2_cleared = false"]
[eval exp="f.event3_cleared = false"]
[eval exp="f.event4_cleared = false"]
;自動更新付きフキダシ表示(4キャラ分)
;サイズを変更したい場合は size="0.5" などを追加してください
[speech_bubble_show character="model1" x=&f.bubble1_x y=&f.bubble1_y]
[speech_bubble_show character="model2" x=&f.bubble2_x y=&f.bubble2_y]
[speech_bubble_show character="model3" x=&f.bubble3_x y=&f.bubble3_y]
[speech_bubble_show character="model4" x=&f.bubble4_x y=&f.bubble4_y]
;ここから下はコンポーネントで挿入する会話やイベントを配置
;※変数「event1_cleared」~「event4_cleared」をtrueに変更すると自動的にハートマークに変わります
;========== 使用例:イベントクリア処理 ==========
;モデル1のイベントクリア
;[eval exp="f.event1_cleared = true"]
;モデル2のイベントクリア
;[eval exp="f.event2_cleared = true"]
;モデル3のイベントクリア
;[eval exp="f.event3_cleared = true"]
;モデル4のイベントクリア
;[eval exp="f.event4_cleared = true"]
[s]
コンポーネントで変数を変更
【クリアの場合】
変数: event1_cleared
操作: 代入
オペランド: 定数
値: true📝 使い方
各モデルの設定部分で、Live2Dコンポーネントの座標を入れるだけです!
その他
;フキダシを非表示
[speech_bubble_hide character="model1"]
;全フキダシ削除
[speech_bubble_clear_all]