AIイラストで作った立ち絵で目パチ口パクしてみた
昨日の記事「AIイラストで目パチ差分を作るのが思いのほか難しかった件」では目パチが綺麗に出来ず、実験は失敗に終わったため、再挑戦してみました。
どうもCSSアニメーションの同期タイミングがズレているのも原因のようで、微調整していたのですがラチが明かないのでJavaScriptで作り直しました。結局こっちのほうがコードもすっきりして見やすいかも。
AIイラストで作った立ち絵で目パチ口パク&ミニゲーム(?)
ゲーム…というほどのゲーム性は全然ないのですが… SSRカットインが出たらクリアってことでひとつ。
パンツ見せてくれるかどうかのガチャ+それが成功してから再度SSRガチャなので、初期値だとなかなか出てこないと思います。
どうしても出ねー!という方は、パンツ出現確率を両方100%にすれば必ず出てきますので、そちらをどうぞ。
…そいや、そういう救済措置使ったら、ゲーム内の女の子が「CGフル化使っちゃったね~?ざこですねぇ?」と煽ってくれるエロゲあったんだけど、あれは、とても、よかったなぁ…なんだっけな。
…………………………………………………検索中…………………………………………………
あった。これだ。メスガキサマーライフ。
これの何が良いって、MAP移動中もずぅ~っと妹が一緒で、いつでもどこでもサクサクぬきぬきしてくれるんですよね。メスガキものって感じよりもお兄ちゃん好き過ぎる子が何とかメスガキっぽくしようとしてる感があってそこもまたビクンビクンきます。
一通りのイベントこなしたハズなのに、取り逃したCGがあり、やむなくオプション画面から全CG解放ボタンを押したのですが、そこでさっきのセリフ。…………………………ふぅ。この作者…わかっておられる…と思いました。同人ゲーで安いし、ちょい生意気な妹モノが好きな人にはオススメ。
ベース絵
今日のオカズを今日のうちに作る、を目標としているため、できるだけ楽に作りたい。というわけで基本はこの2枚。
Prompt
(1girl,tachi-e),loli,flat chest,purple big eyes,pink bob hair,black hoodies,white T-shirt,pink fril skirt,(white panties:0)
,clear face
,standing
,(simple flat white background:1.2)
Negative prompt: (worst quality, low quality:1.2), bad anatomy, lowres, text, bad hands, extra hands, extra arms, bad fingers, missing fingers, extra fingers, multipul angle, split view, blur, (multiple angle), crop
Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 903569541, Size: 512x512, Model hash: be03be0110, Model: hassakuHentaiModel_hassakuv1m, Denoising strength: 0.6, Clip skip: 2, Hires upscale: 2, Hires upscaler: Latent
1ガールだって強調してんのに、二人出てきたけど、切り取りやすそうだったので採用。
たぶん512x512pxで生成したので、もうひとり入りまっせ?とAI君がいらん気を使って入れてくれたんだろうね…。余白あると何か描かないと気が済まない性格っぽい。ちなみに昨日は512x768pxでちゃんと縦長指定してました。なぜ忘れたし。…鳥頭だからだ!
目パチ口パク用のパーツ
なくても良いのだけれど、昨日のリベンジがしたかったので目パチ差分2種。半目はちょっと色が合ってなかったのが残念。
それと、リベンジなら目パチだけじゃなくて口パクも入れたいよな、ってことで口開け差分。
立ち絵にかぶせるだけなので、綺麗に瞳や口の形に切り取る必要はありません。むしろ、瞳や口の大きさに合わせて画像サイズを小さくしちゃうと、今度は座標を合わせるのに苦労するため、ベース絵と同じ1024x1024pxのままにしておくほうが楽。これなら座標気にすることもなく雑に絵を重ねるだけで目パチ実装できちゃう。
カットインパンツ
目パチ、口パクだけの掲載をして終わりで良いのか? わざわざアダルト可のレンタルサーバーまで使ってアダルトサイト運営してるのに、それでええんか?満足なんか?と自問自答。
せ、せめてパンチラだけでも…と、Inpaintするも、ベース絵とかけ離れた絵を出すのは不可能。txt2imgで作り直すと顔は…多少寄せられるにしても、衣装まったく同じとか無理無理。絶対無理。
そこでふと天啓が。AIイラスト使ったエロ漫画ではどうしていた? 違うコマに登場させるとき、どうやって同一人物と思わせるようにしていた? ………カットだ。絵をカットして拡大している風にみせて、キャラと異なる部分は見えないようにしていた。
それをエロゲで使っちゃいけない理由はない、ってことでパンチラカットインとなりました。
もっとこう、ペルソナみたいにナウでヤングなおしゃれでナナメのカットインとか作ってみたいけど、一度にアレコレ詰め込み過ぎても、ね、ね。(翻訳:面倒になった)
技術解説(JavaScript)
はーい、ここからは技術系のお話なので興味ない方はスルーするんだよー。
目パチの実装方法
window.addEventListener('load', function () {
setInterval(function () {
if (Math.random() > parseFloat(document.getElementById('double_blink_ratio').value / 100)) {
blink_count = 1;
} else {
blink_count = 2;
}
let eye_ms = 0;
let eye_ms_inc = parseInt(document.getElementById('blink_speed').value);
let eye_elms = document.getElementsByClassName('blink_eyes');
while (blink_count > 0) {
for (let i = 0; i < eye_elms.length; i++) {
setTimeout(function () { eye_elms[i].style.display = 'block'; }, eye_ms);
eye_ms += eye_ms_inc;
}
for (let i = eye_elms.length - 1; i >= 0; i--) {
setTimeout(function () { eye_elms[i].style.display = 'none'; }, eye_ms);
eye_ms += eye_ms_inc;
}
blink_count--;
}
}, 3000);
});
せっかくCSSじゃなくて、JavaScriptでやるのだから、たまに2回連続でのまばたきとか入れたいなぁ~と思ってしまい、ちょっぴり長くなったけど、単に目パチしたいだけなら、setIntervalで3000ミリ秒ごとに1回、閉目の絵を出すだけのこと。
開いているか、閉じているか、の二択ならまぁホントにそれだけで簡単なのだけれど、より自然に 1)開き目→2)半目→3)閉じ目→4)半目→5)開き目というようにしたい、更にヌルヌルにしたくなっても差分を増やすだけでコードを修正しなくても対応できるようにしたい、とか考え始めると思考の沼に陥るかも知れない。でも対応した。
今回の例では、以下の3枚が重なっている。
<img src="20230421_blinking_eyes_01.webp">
<img src="20230421_blinking_eyes_02.webp" class="blink_eyes">
<img src="20230421_blinking_eyes_03.webp" class="blink_eyes">
01がベースの立ち絵で、02が半目、03が閉じ目。
02以降がいくら増えて、例えば30枚くらいになったとしても先述のJavaScriptで対応可能。すべてはクラス名「blink_eyes」を参照しているから。
画像を順番に重ねて行って、最後まで行ったら今度は逆に閉じていく。それを愚直にコーディングしようとするとコードが長くなってしまうので、1)重ねていく(display:blockにする)処理 2)消していく(display:noneにする)処理の2つに分けて考える。
すると、途端に簡単な話になり、重ねていくのは下記のとおり。blink_eyesクラスを上から順番にdisplay:blockにしていくだけ。
let eye_elms = document.getElementsByClassName('blink_eyes');
for (let i = 0; i < eye_elms.length; i++) {
setTimeout(function () { eye_elms[i].style.display = 'block'; }, eye_ms);
eye_ms += eye_ms_inc;
}
そして、消していくのはfor文を逆にするだけ。
但し、このfor文の中で直接スタイルシートをいじってはいけない。ブラウザの処理を止めてしまうからね。毎回、setTimeoutを50ミリ秒ずつズラしながら、重ねていく処理、消していく処理をそれぞれ実行するだけで良い。
こうすることで、差分を何枚に増やそうとも(処理速度が十分なら)対応できるコードになった。
口パクの実装方法
目パチは3秒に1回自動実行で良かったけれど、口パクの場合は再生に何等かのキッカケが必要となる。
…一生ずっとしゃべり続けているキャラなら無限ループでも良いけれど、それはないだろうし。普通は声をかけたり、シナリオを進めたり、タッチしたり、まぁとにかく何らかのアクションに対応する形で口が動くわけだ。
なので、今回は「パンツ見せて」ボタンを作った。なぜかって?パンツを見たいから、それ以外の理由が必要かね?
let btn_enabled = true;
document.getElementById('btn_talk').onclick = function () {
if (!btn_enabled) return;
btn_enabled = false;
elm_balloons = document.getElementsByClassName('balloon');
baloon_idx = parseInt(Math.random() * elm_balloons.length);
document.getElementsByClassName('lip_sync')[0].classList.add('talking');
setTimeout(function () {
btn_enabled = true;
document.getElementsByClassName('lip_sync')[0].classList.remove('talking');
elm_balloons[baloon_idx].style.display = 'none';
}, 1500);
}
全部載せると長いからある程度端折ったけど、おおむねこんなもんで良いハズ。
btn_talkというのは「パンツ見せて」ボタンのID。そこがクリックされた際、まずやらなきゃいけないのは二重クリック防止。ボタンに応じてふきだしが出るわけだけれど、「パンツ見せて」「パンツ見せて」「パンツ見せて」「やだ」「やだ」「やだ」と画面に重なって出てくるのは悪夢だろう。それはそれで面白そうだけれど。ともかく、btn_enabled変数をフラグとして使うことで、現在しゃべっている最中なのかどうか判断している。
ちなみに、このふきだしについては、こんな感じでHTMLソース中で定義している。
<div class="balloon">…いやです</div>
<div class="balloon">…無理です</div>
<div class="balloon">…お断りします</div>
<div class="balloon">…ダメです</div>
<div class="balloon">…何言ってるんですか</div>
<div class="balloon">…変態</div>
<div class="balloon2">…ちょっとだけですよ</div>
<div class="balloon2">…はい、おしまい!</div>
「パンツ見せて」ボタンが押された際、クラス名balloonの中からランダムでメッセージを選び、画面上に表示(display:block)される仕組み。
そして、ふきだしを表示した直後、setTimeout関数で1500ミリ秒後に、ふきだしを消すコードを予約するという流れ。これで1.5秒後にふきだしが消えてくれるわけだ。もっと長いセリフを実装する場合はメッセージの長さによって、ふきだしを消すまでの時間を計算する処理が必要となる。
そして、同時に下記のコードにより、lip_syncクラスを持つ画像にtalkingクラスを付けている。
document.getElementsByClassName('lip_sync')[0].classList.add('talking');
これだけ聞くと何のこっちゃな話だけれど、lip_syncクラスを持つ画像はコレ。
<img src="20230421_lip_01.webp" class="lip_sync">
口を開いた差分は1枚しかないので、IDでも良かったのだけれど、目パチと同じで将来的に複数にする可能性もあるか?と考えてクラス名での識別とした。
そしてtalkingクラスの定義はコレ。
.talking {
opacity:0;
animation:fedeInOut 0.2s infinite alternate;
}
@keyframes fedeInOut {
100% {opacity:1;}
}
開口差分はopacity:0;で透明状態にしておき、しゃべりはじめたときにclassList.add('talking');でtalkingクラスを付与し、0.2秒間隔で透明/不透明を無限(infinite)に繰り返すCSSアニメーションをさせている。
これでしゃべっているときだけ口がパクパク動く仕組みの出来上がり。
こうしてわざわざ日本語で説明すると長いし難しそうだけれど、コードは大したことないので、興味のあるJavaScript初心者はぜひチャレンジしてもらいたい。このブログに掲載しているぼくが書いたソースはMITライセンス扱い(雑に言うとお好きにどうぞ)で構わない。えっちな自作ゲームをどんどん作ってくれ。
まとめ
パーカー少女かわいい。しゅき。













