投稿記事

RPGツクールの記事 (6)

饗庭淵/喘葉の森 2022/12/03 01:19

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

饗庭淵/喘葉の森 2022/11/08 19:04

『乳愛奴○調教計画』クローズドβテストプレイヤー募集!

制作中のゲーム『乳愛奴○調教計画』のクローズドβテストを開始します。


概要:
パイズリオンリー調教ゲームです。プレイ時間は3~4時間ほど。ゲームサイズは1.37GBほどです。
テストはDiscordのサーバー上で行い、β版をプレイしたうえでバグ報告や意見、感想などをいただきます。
参加いただいた方にはエンドロールに名前を掲載させていただくことがあります(サーバー内でご希望を伺います)

募集人数:
10人

テスト期間:
11月末まで予定

募集条件:
・18歳以上
・PCでゲームがプレイできる
・Discordサーバーに参加できる
・テスト期間中にゲームをプレイし、バグ報告などができる

注意:
「βテストに参加している」といった旨は公にして構いませんが、ゲーム内容については発売まで口外しないようお願いします。

報酬:
ささやかですが、本作のギフトコードを提供します(よって、本作をご購入いただく必要はありませんのでご注意ください)

参加方法:
こちらからDiscordサーバーに参加(参加人数に上限のあるURLです)
※募集上限に達しました。ありがとうございます!
(のちに追加募集をするかもしれませんが、いったん締め切ります)

『温泉旅館のパイズリ怪異』のβテストで利用したサーバーを使い回しています。
前回参加された方は任意でご参加いただけると幸いです。

以上です。よろしくお願いします。

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

饗庭淵/喘葉の森 2022/08/29 19:00

次回作『乳愛奴○調教計画』の進捗④


長らくご無沙汰しており、申し訳ありませんでした。

Q.なにしてたの?
A.その、いろいろ……

と、変に誤魔化すのもアレですし、まず重要な点からお話しましょう。
制作状況ですが、かなり進んでます
といっても、前回の進捗報告から約9ヵ月、ずっと制作を続けていたわけではありません。
制作を再開したのは7月頭くらいからです。
つまり、あれから9ヵ月経ちましたが、製作期間に当たるは2ヵ月ほどです。

どうしてこんなことに?
いやそもそも、2ヵ月前に製作再開したならその旨を先に報告しなさいよ。
それはですね……みなさまにお見せしたいものがありまして……。


はい。ついにここまでできました。

ホーム画面で揺れ揺れしてタッチしたら反応するlive2dです。
放置してると台詞が流れたりもします。
この形を……みなさまに動画でお届けしたかった……。
と思ってたら2ヵ月経ってました。

むろん、2ヵ月間live2dと睨み合いをしていたわけではありません。
live2dのモーション設定などは友人に頼みました。
僕は絵を描いてパーツ分けだけして渡しました。
つまりこのlive2dはそいつの功績! でかしたはんぺん!
その間に僕は調教シーンやらイベントシーンやらをモリモリ実装してました。

そういうわけで、進捗としては「調教完了!」という段階までは過不足なく遊べる状態までできあがりました。

なに?! 調教完了でゲームクリアではないのか!?


一応、クリアといえばクリアです。エンドロールも流れます。
いうなれば「第一部完」といったところですね。ファントムペインもそうだったじゃないですか。
エンドロールが流れたあと、やり込みモードみたいなノリで「第二部」がはじまります(予定)。
そっちについてはほとんど手つかずですが……あとはそこを実装するだけ……。
ホントに「だけ」という分量かは……ちょっとよくわからない……。

ちなみにボリュームとして、第一部はだいたい2時間くらい。
第二部は未知です。
といっても第一部よりは長くはならないかな……いや長くなるかな……どうかな……。


さて、アレですね。
ちょっとゲーム内容からは逸れますが、この9ヶ月間なにをしていたのか、すべてをお話します。

制作を再開したのが7月なら、それまでなぜ中断していたのか?
「喘葉の森」といえば、どちらかといえばM向け寄りの作風でしたね。
ですが、今作は調教ものです。ヒロインの尊厳を粉々に破壊します。
そのうえアレですよ、「パイズリメイン」「本番なし」ですよ。
そして、作者が「喘ぎ声」が嫌いと来ている。
つまり、「調教」という題材のくせに「快楽責め」がないわけです。
まあ、まったくないわけではないですが、極力避けています。



ほら見てください。調教内容がパイズリばっかり

あとはせいぜい「精液風呂」とか「仮想体験」とかそんなんです。
仮想体験でもやることはもちろんパイズリなんですが。
あとは「膨乳施術」があるのみ。清々しいまでの乳特化。
乳に関係ないのはそれこそ精液風呂くらいなものです。

となると、参考になる先行作品がないわけです。
こんな作品ほかにあるかって、ねーよカス!

そういうわけで、制作がだいぶ行き詰っていたのがありました。
うおおお……なにも思いつかねえ……どうすれば……。
で、誘惑の魔の手が伸びてきます。
手が動かないまま徒に時間だけが過ぎるのももったいないし、ちょっと他の作品の構想でも練っておくか……。
そのまま手が滑り、12月ごろから6月にかけて小説を2本ほど書いてました。

一本は『対怪異アンドロイド開発研究室』という作品。
もう一本は未発表です。

一本で満足しとけよ!!!!
で、でも、10万字程度の小説って2ヵ月以内くらいで書き終わるから、すぐかなって……。

はい。経緯説明終わりです。
さて、行き詰っていた制作はどうなったのか?
これだけ間が開いたこともあり(ほんとぉ?)、光明が見えてきました。
制作を中止していた間に参考になりそうな書籍も発売してました。
あれこれと「ホントにこのままでいいのか?」「抜本的に見直す必要があるのでは?」とか悩みを抱えていましたが、「これからいける」という道筋を見つけられました。

ゲームとしてはかなり形になっています。
本作は「体験性」重視のゲームで、「競技性」「攻略性」は薄く、イベントシーンが膨大なのでゲームつくってるというより映像つくってる感覚です。


Q.で、完成はいつごろ?
A.年内……! 年内には発表したい……!



あ、それとこういう類の一枚絵って描こうと思えば数時間でササッと描けることがわかったので、エロとは関係ないスチルも何枚か描いて演出強化などもしてました。
そういうことしてるから制作が伸び伸びになるんじゃない?

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

饗庭淵/喘葉の森 2021/12/06 01:39

次回作『乳愛奴○調教計画』の進捗③


膨乳施術です。リザルト画面もあります。

「膨乳」というか「育乳」と呼ぶべきか……
いずれにせよこれこそが本作のメイン要素といえるでしょう。
これのおかげでどのパイズリシーンも乳サイズごとの差分を描くことに!!
乳だけでなく作業量も膨れ上がってひぃひぃ言ってます。

さて、本作の制作状況ですが……。
絵だけはいっぱい描いたなあとか思ってたんですが、改めてタスクを洗い出してみるとまだまだ描かないといけない絵がいっぱいあることがわかりました。
どうして……。



そういうわけで最近描いたものです。
なにやら洗脳装置みたいな、仮想体験みたいなアレです。

「あれ? それっぽいのはすでに描いてなかった?」


これですね。
上の動画を見ればわかりますが、この絵は膨乳施術の演出に使うことにしました。
もともとはそれと合わせて仮想体験みたいな演出にも使い回そうかと思ってたんですが、やっぱこう、膨乳専用の絵にした方が、こう、いいかなって……
そんなわけで仮想体験の方は新規に描き下しました。
こういうことしてるから作業量が膨らむのでは?


あとはこんなんです。なんでしょうねこれ……。
なにかアクロバティックな、屈辱的な、凌○とか調教とかにふさわしい乳交体位が欲しいなあと思ってこうなりました。
ひどい絵面だ……。

ふだんのM寄りっぽいのだとこういう構図は出すことあまりなさそうなので作品としてだいぶ差別化されるのではないでしょうか。



それから研究ツリーを実装してみました。
前回いろいろアイデアを募集して、それを組み込んだりしています。
まだ少ないような、なにか増やすかも、これで完成かも、とかはまだ悩んでます。

さて、最近感じているのがアイデアの枯渇問題です。
ぼんやりと「こんな作品をつくりたいなあ」というアイデアなら大量にストックがあります。
ですが、実際に作品をつくるにはぼんやりしたアイデアのままでは形になりません。
一つの作品をつくるには「幹」となるアイデアだけでなく、大量の「枝葉」のアイデアが必要です。
この枝葉のアイデアを搾り出すのが難しい!

とにかくアイデアが足りない!

アイデアが足りないから特に「意図」もなく手癖でつくってしまう……。


そういうことが多くて困ってます。
前作の『温泉旅館』でもだいぶ無理やりに搾り出しました。
作品が完成しているからといって十分なアイデアがあったわけではなく、多くの割合でアイデアもなく適当に埋め合わされた箇所というのが含まれてしまっているのです。
アイデアが足りなくても作品はつくれてしまうわけですね。
作品全体に「意図」を張り巡らせるのはとても難しい……。

そういうわけで、なんかこうアイデアを無限に生成する装置が欲しいです。
装置というか、人というか、システムというか……。

前に観た庵野監督のドキュメンタリーでは、「自分の中から出てくるものはつまらない」ということで大量のプリヴィズを撮って取捨選択したり、部下とブレインストーミングしまくったりしていろんな意見を出してもらいながら大量に無視したりしていました。

ああいう環境いいですね。
アレと似たようなことできないかしらね……。
前回の記事でいろいろアイデアいただいてありがたく思ってますが、残念ながら大半は無視され本作に反映されることはないかとは思います。
とはいえ、それが大事なのではないかもしれません。
直接採用されなくてもなんらかの糧にはなってるはずです。たぶん。


本作でやりたいことは、調教ものなので最初は拒絶していたのにじょじょに堕ちていく、なんかそういうアレです。
それを成立させるために大量のテキストで厚みを持たせたい、というので今は苦労しています。

ここでいう「アイデア」とは具体的にはテキストです。
パイズリするときにどんな台詞が出てくるか?
調教の進行に応じてどのような表現がふさわしいか?
なにか感情の機微を表現しているか?
そのテキストは今後なにかの伏線になるのか?
そういうのを特に考えずに……というか、考える余裕がないので無理やり捻り出しているのが現状というわけです。

あとはゲームなので数値設計もそうですね。
まだそのへんは手をつけてませんが……。

うおおお、なんかこう、なんかできんか……なんか……。
ブレインストーミングできる場をつくりますかね……ううむ……。

まあ、こんなグダグダ言ってないでなにも浮かばないなりに手を動かさないと完成しないわけですが。

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

饗庭淵/喘葉の森 2021/10/27 20:01

どこでもパイズリシステムを実装しよう

どこでも……? パイズリ……? システム……?


これのことです。

要は常にヒロインが後ろからついてきて話しかけるとどこでもパイズリができるというそのままのシステムなのですが、意外とそれっぽいシステムのゲームを見ません。
それこそ私の過去作『黒先輩』くらいなものでしょうか。

わりと汎用性の高いシステムな気がするので実装方法について共有しておきます。

※以下の記事は中~上級者向けです。
プラグイン? オーバーライド? と聞いてわからない方にはあまりオススメできません。


・フォロワーに独立してもらおう

似たようなことは過去作『魔機人形』でやったものです。
本作はそのマイナーチェンジ版といえます。

後輩にせよ魔機人形にせよ、ついてくるこいつらはなにものなのか?


こいつらです。

こいつらのことを「フォロワー」と呼びます。
デフォルトではただプレイヤーの後ろをついてくるだけの存在ですが、本作ではこのフォロワーを独立させ、当たり判定をつけて機能させています。

まずは独立させる方法です。
プレイヤーに追従して動く処理がありますので、これを消しましょう。
この処理はGame_FollowersのupdateMoveで各フォロワーごとにchaseCharacterが実行されることで動いてます。
(Game_FollowersとはGame_Follower=個々のフォロワーを格納しているクラスのことです)
ので、以下のようにオーバーライドするとよいでしょう。

Game_Followers.prototype.updateMove = function() {
};

Game_Follower.prototype.chaseCharacter = function(character) {
};

要らない処理なので空白にしてしまうわけです。


Game_Followers.prototype.update = function() {
    /*if (this.areGathering()) {
        if (!this.areMoving()) {
            this.updateMove();
        }
        if (this.areGathered()) {
            this._gathering = false;
        }
    }*/
    for (const follower of this._data) {
        follower.update();
    }
};

あるいはこうでもよいです。
プレイヤーの後ろをついてくる処理をコメントアウトしています。


さて、以上を適用してゲームを実行するとどうなるでしょう?


見事フォロワーを置き去りにできました。

・フォロワーに動いてもらおう

さて、動かなくなったので今度は代わりに別の動きをしてもらう処理を書きます。
ツクールにおいてリアルタイムの処理はupdateという関数でなされています。
この関数は毎フレーム、つまり1秒に60回呼び出されるものです。

では、Game_Followerのupdateにはなにが書かれているでしょう。

Game_Follower.prototype.update = function() {
    Game_Character.prototype.update.call(this);
    this.setMoveSpeed($gamePlayer.realMoveSpeed());
    this.setOpacity($gamePlayer.opacity());
    this.setBlendMode($gamePlayer.blendMode());
    this.setWalkAnime($gamePlayer.hasWalkAnime());
    this.setStepAnime($gamePlayer.hasStepAnime());
    this.setDirectionFix($gamePlayer.isDirectionFixed());
    this.setTransparent($gamePlayer.isTransparent());
};

いろいろ書かれていますが……
要するにこれは全部「プレイヤーに従え」という処理です。
いけません。まだプレイヤーから独立できていないようです。消しましょう。

Game_Follower.prototype.update = function() {
    Game_Character.prototype.update.call(this);
    this.setTransparent($gamePlayer.isTransparent());
    this.updateDashing();
    this.updateAction();
};

こうなります。

ちなみにsetTransparentは透明設定で、これだけは残しておきます。
プレイヤーが透明の場合はフォロワーが透明でだいたい問題ないからです。
(もし問題がある場合はこの行も消す)

さて、唐突に表れたupdateDashingupdateActionですが、これは今から書きます。
updateDashingは本来Game_Playerのみが持つ関数で、文字通り「ダッシュしているかどうか」を判定する関数です。
updateActionはまったくの独自定義関数です。重要なのはこちらです。

まずは簡単に「プレイヤーの方を向く」という処理を書いてみます。

Game_Follower.prototype.updateDashing = function() {
};

Game_Follower.prototype.updateAction = function() {
    this.turnTowardCharacter($gamePlayer);
};

updateDashingの方はひとまず仮に空のものを置いておきます。


これでプレイヤーの方を向く動きができました。
あとはこの応用です。
そうですね、次はランダムに動かしてみましょう。

Game_Follower.prototype.updateAction = function() {
    this.moveRandom();
};

これでフォロワーは好き勝手に動き回るはずです。


いくらなんでも好き勝手すぎる。

どうしてこんなことに?
updateは毎フレーム、つまり1秒間に60回呼び出される関数です。
1秒に60回もランダムに動けばこうなってしまうのは当然の結果です。

よって、行動回数を抑制する処理を書かねばなりません。
そうですね、動くのは1秒に1回くらいで十分でしょう。
つまり60フレームに一回動けばいいわけです。

Game_Follower.prototype.updateAction = function() {
    if(this._waitCount > 0){
        this._waitCount--;
        return;
    }
    this.moveRandom();
    this._waitCount = 60;
};

つまりこうです。


今度はまともな動きになりました。
障害物は平気で無視してますが、これはまだフォロワーに当たり判定を実装していないからです。
動くようになると気になりますので、次は当たり判定を実装してみます。


・フォロワーを実体化させよう

フォロワーには当たり判定がありません。
壁も平気ですり抜けるし、プレイヤーとも触れ合えません。なぜでしょう?

①すり抜けフラグがtrueになっているから
②衝突判定がそもそも実装されていないから

答えは両方です。
まずは①から見てみます。

Game_Follower.prototype.initialize = function(memberIndex) {
    Game_Character.prototype.initialize.call(this);
    this._memberIndex = memberIndex;
    this.setTransparent($dataSystem.optTransparent);
    this.setThrough(true);
};

Game_Followerのinitialize(初期化)関数です。
ここにあまりにもそれっぽい記述があります。
this.setThrough(true)です。これを消しましょう。


プレイヤーやお互いのことは無視しますが、マップ上の障害物にはちゃんとぶつかってるのがわかると思います。
つまり、キャラクター同士の衝突判定とマップの衝突判定は別物だということです。

次は②のキャラクター同士の衝突判定を実装します。

Game_CharacterBase.prototype.isCollidedWithCharacters = function(x, y) {
    return this.isCollidedWithEvents(x, y) || this.isCollidedWithVehicles(x, y) || this.isCollidedWithFollowers(x,y);
};

Game_CharacterBase.prototype.isCollidedWithFollowers = function(x, y) {
    const followers = $gamePlayer.followersXyNt(x, y);
    return followers.some(function(follower) {
        return follower.isVisible();
    });
};

Game_Player.prototype.followersXyNt = function(x, y) {
    var followers = this.followers().visibleFollowers();
    return followers.filter(function(follower) {
        return follower.posNt(x, y);
    });
};

もとからある衝突判定をもとにフォロワーにも適用させたものです。

この処理を書くとどうなるか?
プレイヤーがフォロワーを通り抜けできなくなります。
ですが、フォロワーの方はプレイヤーを通り抜けます
衝突判定はそれぞれのキャラクターごとに設定しなければならないのです。
これはGame_Eventを参考にしましょう。

Game_Follower.prototype.isCollidedWithCharacters = function(x, y) {
    return (Game_Character.prototype.isCollidedWithCharacters.call(this, x, y) ||
            this.isCollidedWithPlayerCharacters(x, y));
};

Game_Follower.prototype.isCollidedWithPlayerCharacters = function(x, y) {
    return this.isVisible() && $gamePlayer.isCollided(x, y);
};

つまりこうです。


これでフォロワーが実体を得ることができました。


・フォロワーに話しかけられるようにしよう

せっかく実体化したので話しかける処理を入れましょう。
これもGame_Eventが参考になります。
どちらかというとプレイヤーが主体となる処理なのでGame_Playerの方に書きます。

Game_Player.prototype.triggerButtonAction = function() {
    if (Input.isTriggered("ok")) {
        if (this.getOnOffVehicle()) {
            return true;
        }
        this.checkEventTriggerHere([0]);
        if ($gameMap.setupStartingEvent()) {
            return true;
        }
        this.checkEventTriggerThere([0, 1, 2]);
        if ($gameMap.setupStartingEvent()) {
            return true;
        }
    }
    return false;
};

プレイヤーがイベントを調べるときの関数はこのへんです。
近い処理はcheckEventTriggerThereです。
これを参考にcheckFollowersTriggerThereという関数を書いて挿入してみます。

Game_Player.prototype.triggerButtonAction = function() {
    if (Input.isTriggered("ok")) {
        if (this.getOnOffVehicle()) {
            return true;
        }
        if(this.checkFollowersTriggerThere()){
            return true;
        }
        this.checkEventTriggerHere([0]);
        if ($gameMap.setupStartingEvent()) {
            return true;
        }
        this.checkEventTriggerThere([0, 1, 2]);
        if ($gameMap.setupStartingEvent()) {
            return true;
        }
    }
    return false;
};

Game_Player.prototype.checkFollowersTriggerThere = function() {
    const direction = this.direction();
    const x1 = this.x;
    const y1 = this.y;
    const x2 = $gameMap.roundXWithDirection(x1, direction);
    const y2 = $gameMap.roundYWithDirection(y1, direction);
    this.followers().forEach(function(follower) {
        if(follower.isVisible() && follower.posNt(x2,y2)) {
            follower.talkPlayer();
            return true;
        }
    }, this);
};

Game_Followers.prototype.forEach = function(callback, thisObject) {
    this._data.forEach(callback, thisObject);
};

Game_Follower.prototype.talkPlayer = function() {
    this.turnTowardPlayer();
};

待ってくれたまえ。コードの洪水をワッといっきにあびせかけるのは!


なにはともあれ、これで話しかけるとこっちを向く処理まではできました。

先にも書いた通りcheckFollowersTriggerThereという関数の中身はcheckEventTriggerThereを参考にしたものです。
プレイヤーの現在位置と向きをもとに、その先にフォロワーがいるかどうかを判定しています。
決定キーを押したときフォロワーがいたのなら話しかけたという処理が成立し、talkPlayerという関数が呼び出されます。

talkPlayerの中身は現在は「プレイヤーの方を向く」だけです。
this.followers().forEach~のあたりは、フォロワーは複数いるのでfor文で回してすべてのフォロワーについて確認しているということです。

さて、ここまでできたらtalkPlayerの中身を書き換えるだけで話しかけるという処理は完成します。
コモンイベントでも呼び出しましょう。

Game_Follower.prototype.talkPlayer = function() {
    this.turnTowardPlayer();
    this._waitCount = 60;
    $gameTemp.reserveCommonEvent(1);
};

ただ、これではすべてのフォロワーで同じコモンイベントが呼び出されることになります。

キャラによって別のコモンイベントを呼び出したい場合はたとえば以下のように書きます。

Game_Follower.prototype.talkPlayer = function() {
    this.turnTowardPlayer();
    this._waitCount = 60;
    const actorId = this.actor().actorId();
    $gameTemp.reserveCommonEvent(actorId);
};

これでアクターIDと同じ番号のコモンイベントが呼び出されます。


こんな感じにコモンイベントを組んでおきます。
さて、実行してみましょう。


おお……これはもう、完成なのでは……??


・フォロワーが邪魔!!

ただ、このシステムには問題があります。
フォロワーが実体をもって動き回るせいで邪魔なのです。

ふつうのRPGでも町の人が邪魔をして通れなくて困る経験はあると思います。
フォロワーはそれが常について回るのです。どうすればいいでしょう?

解決手法はほかにもあるかもしれませんが、一定時間以上ぶつかり続けると場所を交換する処理を組みました。

これもプレイヤーが主体となる処理です。Game_Playerに記述します。
checkEventTriggerTouch関数のなかに入れ込んでしまいましょう。

const _Game_Player_checkEventTriggerTouch = Game_Player.prototype.checkEventTriggerTouch;
Game_Player.prototype.checkEventTriggerTouch = function(x, y) {
    _Game_Player_checkEventTriggerTouch.apply(this, arguments);
	this.checkFollowersTouching(x, y);
};

Game_Player.prototype.checkFollowersTouching = function(x, y) {
    this.followers().forEach(function(follower) {
        if(follower.posNt(x,y)){
            follower.turnTowardPlayer();
            follower.countObstacle();
            if(this.isDashing()) follower.countObstacle();
        }else{
            follower.resetObstacle();
        }
        if(follower.obstacle() > 20) {
            this.swapFollower(follower);
        }
    }, this);
};

まずこれは20フレームぶつかり続けるとswapFollowerが起こるという処理です。
countObstacleはフレームをカウントする関数です。
resetObstacleはカウントをリセットする関数です。
以下のように書きます

Game_Follower.prototype.countObstacle = function() {
    this._obstacle++;
};

Game_Follower.prototype.resetObstacle = function() {
    this._obstacle = 0;
};

Game_Follower.prototype.obstacle = function() {
    return this._obstacle;
};

swapFollowerはjumpでプレイヤーとフォロワーの位置を交換する処理です。

Game_Player.prototype.swapFollower = function(follower) {
    this.jumpStraight();
    follower.turnTowardPlayer();
    follower.jumpStraight();
    follower.resetObstacle();
};

Game_CharacterBase.prototype.jumpStraight = function() {
    var x = 0;
    var y = 0;
    const d = this.direction();
    if(d == 8){
        y = -1;
    }else if(d == 6){
        x = 1;
    }else if(d == 4){
        x = -1;
    }else if(d == 2){
        y = 1;
    }
    this.jump(x,y);
};

ここで注意ですが、ツクールはデフォだとプレイヤーがジャンプするとフォロワーも一緒にジャンプする仕様になってます。フォロワーに自我はないのか。
ので、その処理を無効にする必要があります。

Game_Player.prototype.jump = function(xPlus, yPlus) {
    Game_Character.prototype.jump.call(this, xPlus, yPlus);
    //this._followers.jumpAll();
};

this._followers.jumpAll();をコメントアウトしましょう。


これで邪魔なフォロワーと立ち位置を交換する処理が書けました。やったね。


・フォロワーについてきてもらおう

肝心なことを忘れていました。
今のままではフォロワーとは名ばかりのただランダムに動き回るだけの存在です。
よって、とりあえずランダムに動くだけの処理を書いていたupdateActionを改良します。

また、主人公を追いかける処理ですがツクールにはデフォで経路探索の関数が用意されています。
クリック操作によるプレイヤー移動でクリック先に向かう動きに使われています。
これを利用させてもらいましょう。
というより、ツクールMVのプラグインであるSmartPathをちょっと改造させてもらって使います。

Game_CharacterBase.prototype.setTarget = function(target, targetX, targetY) {
    this._target = target;
    if (this._target) {
      this.setTargetXy();
    } else {
      this._targetX = targetX;
      this._targetY = targetY;
    }
};

これは向かう先、つまり文字通りターゲットをセットする関数です。
第一引数のtargetにはcharacter、第二・第三引数は座標をセットします。
フォロワーの向かう先はプレイヤーですので$gamePlayerを入れればよい、はずですが……

実は、これには問題があります。
フォロワーは$gamePlayerの中に含まれているクラスなのです。
つまり無限ループ構造が発生します。
MVのころはこれで問題なかったのですが、JSON変換かなんかの仕様が変わった影響で、MZだとセーブができなくなるのです。

よって、苦肉の策ですが以下のようにします。

Game_CharacterBase.prototype.setTargetXy = function() {
    if(this._target === 'player'){
      this._targetX = $gamePlayer.x;
      this._targetY = $gamePlayer.y;
    }else{
      this._targetX = this._target.x;
      this._targetY = this._target.y;
    }
};

プレイヤーを追いかけたい場合は$gamePlayerではなく"player"と文字列を入れることにします。

Game_CharacterBase.prototype.clearTarget = function() {
    this._target = null;
    this._targetX = null;
    this._targetY = null;
};

さらにターゲット関連の変数を初期化する関数もつくり……

const _Game_CharacterBase_updateStop = Game_CharacterBase.prototype.updateStop;
Game_CharacterBase.prototype.updateStop = function() {
    _Game_CharacterBase_updateStop.call(this);
    if (this._target) {
        this.setTargetXy();
    }
    if (this._targetX != null) {
        direction = this.findDirectionTo(this._targetX, this._targetY);
        if (direction > 0){
            this.moveStraight(direction);
        }
    }
};

updateStopという関数にエイリアスで書き加えることで経路探索で追いかける処理をつくります。
findDirectionToという関数がそれです。
それなりに精度の高い経路探索ですので、殺人ピエロが追いかけてくるみたいな動作にも使えます。

さて、準備ができましたのでフォロワーに動きを組み込みましょう。
そうですね、3タイル以上離れたら追いかけフラグが立つ仕様にしましょう。
新たにchasePlayerという関数を用意します。

Game_Follower.prototype.updateAction = function() {
    if(this._waitCount > 0){
        this._waitCount--;
        return;
    }
    this.chasePlayer();
};

Game_Follower.prototype.chasePlayer = function() {
    // ついていく
    if(this._follow && this._waitCount <= 0){  
        this.setTarget('player');
    }
    // 一定以上離れた
    if(!this._follow && this.distance($gamePlayer) > 3){
        this._follow = true;
        this._waitCount = 60;
    }
};

距離を測るためのdistanceという関数も独自定義のものです。
以下のように書きます。

Game_CharacterBase.prototype.distance = function(target) {
    const sx = this.deltaXFrom(target.x);
    const sy = this.deltaYFrom(target.y);
    const ax = Math.abs(sx);
    const ay = Math.abs(sy);
    return Math.sqrt(ax**2 + ay**2);;
};


ついてくるようになりました。モテモテですね。

さて、これをもう少し改良して離れすぎるとダッシュで近づいてきたり、暇なときはブラブラしたりする動きを加えてみましょう。

Game_Follower.prototype.chasePlayer = function() {
    // ついていく
    if(this._follow && this._waitCount <= 0){  
        this.setTarget('player');
    }
    // 暇
    if(!this._follow && this.distance($gamePlayer) > 1){
        this.moveRandom();
        this._dashing = false;
        this._waitCount = 60;
    }
    // 一定以上離れた
    if(!this._follow && this.distance($gamePlayer) > 3){
        this._follow = true;
        this._waitCount = 60;
    }
    if(this.distance($gamePlayer) > 6){
        this._follow = true;
        this._dashing = true;
        this._waitCount = 0;
    }
    // 隣接
    if(this.distance($gamePlayer) <= 1){
        this._follow = false;
        this._dashing = false;
        this._waitCount = 60;
        this.clearTarget();
    }
};

ちょっと長い関数になってしまいました。
また、ダッシュしてるかどうかを示す関数の定義も行います。

Game_Follower.prototype.isDashing = function() {
    return this._dashing && !$gameMap.isEventRunning() && !$gameMap.isDashDisabled();
};

this._dashingはそのままダッシュさせたいときにtureにする変数です。
あとはイベント中はダッシュしないダッシュ禁止マップではダッシュしないと書いてあります。
どういう動きになるでしょうか。


わんこみたいで可愛いですね。


・一応用意しておきたい、緊急ワープ

馬鹿な!? さすがにもう完成なのでは……

ほとんど問題ないといえばないのですが、経路探索も完璧ではありません。
あまりに入り組んでいると、どうしてもプレイヤーの場所まで辿り着けない場合も出てきます。


このように極端に複雑なマップだと経路探索も振り切れてしまいます。
こうなってしまうと、話しかけたい仲間がそばにいなくて面倒ですよね。
よって、緊急ワープを実装します。

と、コードを引用しようと思ったら長い長い……。
urgencyWarpという関数がそうなのですが、えっと、最後にプラグインを配布しますのでそこで確認ください。

簡単に紹介しますと、プレイヤーの周囲8座標を調べて通行可能かどうかを判定し、可能ならその場所に飛ぶ、というものです。
通行可能かどうかの判定というのは、たとえば「壁の中にいる」を防止するための措置です。

考え方としましては、あまりに長い間プレイヤーのもとに辿り着けないとurgencyWarpが発動する、というものです。


これでいつまでもいっしょだね。

・そしてどこでもパイズリへ――


以上の処理が完成すれば、あとはコモンイベントの内容をあれこれするだけでこうなります。
改めて一から紹介してみると結構めんどくさいことしてますね。

さらには、このシステムからどこでもパイズリに至るには――
現在位置の情報からふさわしいパイズリを判定したり、会話内容を状況に応じて変えたりと、他にもいろいろあります。

IndependentFollower.js (15.61kB)

ダウンロード

というわけで、今回の内容をまとめてプラグイン化したものがこちらです。
これを配布してくれるならこれまでの講義はいったい?! という感はありますが、実際に使おうとした場合はいくらか手直ししたくなる部分が出てくるかと思います。
そのためのリファレンス代わりとしてこの記事はご利用ください。
本プラグインを導入される場合にはツクールMZの公式プラグインPluginCommonBasePluginBaseFunctionが必要です。

また、記事内容と異なる点としてはトークイベントの指定法をより汎用性の高いものにしています。


このようにメモ欄に記述します。

さて、これで技術的な準備は整いました。
どこでもパイズリシステムはもう目前です。さあ、歩み出しましょう。

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

« 1 2

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索