【解決】エネミー画像のタッチ範囲を調整する方法

やまQ
記事: 32
登録日時: 2017年6月26日(月) 14:26

【解決】エネミー画像のタッチ範囲を調整する方法

投稿記事by やまQ » 2022年1月25日(火) 17:58

※分かりにくいのでタイトル変更しました。元タイトルは『敵画像を複数にする方法』です。

ツクールMZのフロントビュー戦闘についての質問です。
スクリプトは全く分かりません。

マウス操作で敵ターゲットを選択する時、重なっている敵を思い通りに選択できません。
改善したいのですが私には無理です。お手上げ状態です。

お力添えをお願いします。


---- ---- ---- ---- ---- ---- ---- ----

まず、
しぐれん様の『戦闘時にターゲットのタッチ選択』プラグインを使っています。
http://tm.lucky-duet.com/viewtopic.php?f=5&t=9043
ターゲットを選択する際、敵画像をクリックで選択できる優れものです。感謝です。
ツクールMV用ですが、MZでも正常に動作します。


以下のように敵を配置した戦闘(フロントビュー)で、
重なっている敵をマウス操作で選択するのが非常に難しくなります。
ss1-1.jpg
ss1-1.jpg (82.01 KiB) 閲覧数: 1733 回




上図の敵画像の透明部分を塗りつぶした状態です。手前は赤色、後は黄色で塗りつぶしています。
敵画像の透明部分も判定されているのが原因で、感覚的に選択できないのだと思います。
ss1-2.jpg
ss1-2.jpg (82.1 KiB) 閲覧数: 1733 回


---- ---- ---- ---- ---- ---- ---- ----

そこで、画像を2つ用意する方法を考えました。
1枚目はマウス判定用、2枚目が表示用の画像です。
1枚目は何も描かれていない画像にします。大切なのは画像サイズです。
2枚目は敵の姿が描かれた画像です。1枚目の画像の中心と同じ座標に表示します。
z(表示順)を後ろの方にします。
ss1-3.jpg
ss1-3.jpg (50.01 KiB) 閲覧数: 1733 回


---- ---- ---- ---- ---- ---- ---- ----

プラグインを作ろうと思い、
まず、rmmz_sprites.jsのSprite_Enemyの部分をまるまる抜き出して(コピペ)、
別ファイルにしました。

次に、
Sprite_Enemy.prototype.loadBitmap をコピペして Sprite_Enemy.prototype.loadBitmap2 にしました。

Sprite_Enemy.prototype.updateBitmap

const nametest = this._enemy.battlerName()+"-tes"; // ♪追加 2枚目の敵画像のファイル名

this.loadBitmap2(nametest); // ♪追加 2枚目の画像を表示
を追記しました。

すると、エラーは出なくなったのですが、2枚目の画像も表示されません。
エラーがないので、どこがダメなのかわかりません。


ここで詰まってしまいました。



一応、コピペしただけのスクリプト全文を載せておきます。

コード: 全て選択

// ※rmmz_sprites.jsよりコピペ
//-----------------------------------------------------------------------------
// Sprite_Enemy
//
// The sprite for displaying an enemy.
// 敵を表示するためのスプライト。

function Sprite_Enemy() {
    this.initialize(...arguments);
}


Sprite_Enemy.prototype = Object.create(Sprite_Battler.prototype);
Sprite_Enemy.prototype.constructor = Sprite_Enemy;

Sprite_Enemy.prototype.initialize = function(battler) {
    Sprite_Battler.prototype.initialize.call(this, battler);
};

Sprite_Enemy.prototype.initMembers = function() {
    Sprite_Battler.prototype.initMembers.call(this); //
    this._enemy = null; //
    this._appeared = false; //
    this._battlerName = ""; //
    this._battlerHue = 0; //
    this._effectType = null; //
    this._effectDuration = 0; //
    this._shake = 0; //
    this.createStateIconSprite(); //
};

Sprite_Enemy.prototype.createStateIconSprite = function() {
    this._stateIconSprite = new Sprite_StateIcon();
    this.addChild(this._stateIconSprite);
};

Sprite_Enemy.prototype.setBattler = function(battler) {
    Sprite_Battler.prototype.setBattler.call(this, battler);
    this._enemy = battler;
    this.setHome(battler.screenX(), battler.screenY());
    this._stateIconSprite.setup(battler);
};

Sprite_Enemy.prototype.update = function() {
    Sprite_Battler.prototype.update.call(this);
    if (this._enemy) {
        this.updateEffect();
        this.updateStateSprite();
    }
};



Sprite_Enemy.prototype.updateBitmap = function() {
    Sprite_Battler.prototype.updateBitmap.call(this);
    const name = this._enemy.battlerName();
    const nametest = this._enemy.battlerName()+"-tes"; // ♪追加 2枚目の敵画像のファイル名
    const hue = this._enemy.battlerHue();
    if (this._battlerName !== name || this._battlerHue !== hue) {
        this._battlerName = name;
        this._battlerHue = hue;
        this.loadBitmap(name);
        this.loadBitmap2(nametest); // ♪追加 2枚目の画像を表示
        this.setHue(hue);
        this.initVisibility();
    }
};

Sprite_Enemy.prototype.loadBitmap = function(name) {
    if ($gameSystem.isSideView()) {
        this.bitmap = ImageManager.loadSvEnemy(name); // サイドビュ
    } else {
        this.bitmap = ImageManager.loadEnemy(name); // フロントビュ
    }
};

Sprite_Enemy.prototype.loadBitmap2 = function(name) { // ♪追加 2枚目の敵画像
    if ($gameSystem.isSideView()) {
        this.bitmap2 = ImageManager.loadSvEnemy(name); // サイドビュ
    } else {
        this.bitmap2 = ImageManager.loadEnemy(name); // フロントビュ
    }
};


Sprite_Enemy.prototype.updateFrame = function() {
    Sprite_Battler.prototype.updateFrame.call(this);
    if (this._effectType === "bossCollapse") {
        this.setFrame(0, 0, this.bitmap.width, this._effectDuration);
        this.setFrame(0, 0, this.bitmap2.width, this._effectDuration);
    } else {
        this.setFrame(0, 0, this.bitmap.width, this.bitmap.height);
        this.setFrame(0, 0, this.bitmap2.width, this.bitmap2.height);
    }
};




Sprite_Enemy.prototype.setHue = function(hue) {
    Sprite_Battler.prototype.setHue.call(this, hue);
    for (const child of this.children) {
        if (child.setHue) {
            child.setHue(-hue);
        }
    }
};

Sprite_Enemy.prototype.updatePosition = function() {
    Sprite_Battler.prototype.updatePosition.call(this);
    this.x += this._shake;
};

Sprite_Enemy.prototype.updateStateSprite = function() {
    this._stateIconSprite.y = -Math.round((this.bitmap.height + 40) * 0.9);
    if (this._stateIconSprite.y < 20 - this.y) {
        this._stateIconSprite.y = 20 - this.y;
    }
    this._stateIconSprite.y = -Math.round((this.bitmap2.height + 40) * 0.9);
    if (this._stateIconSprite.y < 20 - this.y) {
        this._stateIconSprite.y = 20 - this.y;
    }
};

Sprite_Enemy.prototype.initVisibility = function() {
    this._appeared = this._enemy.isAlive();
    if (!this._appeared) {
        this.opacity = 0;
    }
};

Sprite_Enemy.prototype.setupEffect = function() {
    if (this._appeared && this._enemy.isEffectRequested()) {
        this.startEffect(this._enemy.effectType());
        this._enemy.clearEffect();
    }
    if (!this._appeared && this._enemy.isAlive()) {
        this.startEffect("appear");
    } else if (this._appeared && this._enemy.isHidden()) {
        this.startEffect("disappear");
    }
};

//
Sprite_Enemy.prototype.startEffect = function(effectType) {
    this._effectType = effectType;
    switch (this._effectType) {
        case "appear":
            this.startAppear();
            break;
        case "disappear":
            this.startDisappear();
            break;
        case "whiten":
            this.startWhiten();
            break;
        case "blink":
            this.startBlink();
            break;
        case "collapse":
            this.startCollapse();
            break;
        case "bossCollapse":
            this.startBossCollapse();
            break;
        case "instantCollapse":
            this.startInstantCollapse();
            break;
    }
    this.revertToNormal();
};

Sprite_Enemy.prototype.startAppear = function() {
    this._effectDuration = 16; // たぶん、エフェクトの間隔
    this._appeared = true;
};

Sprite_Enemy.prototype.startDisappear = function() {
    this._effectDuration = 32; // たぶん、エフェクトの間隔
    this._appeared = false;
};

Sprite_Enemy.prototype.startWhiten = function() {
    this._effectDuration = 16; // たぶん、エフェクトの間隔
};

Sprite_Enemy.prototype.startBlink = function() {
    this._effectDuration = 20; // たぶん、エフェクトの間隔
};

Sprite_Enemy.prototype.startCollapse = function() {
    this._effectDuration = 32; // たぶん、エフェクトの間隔
    this._appeared = false;
};

Sprite_Enemy.prototype.startBossCollapse = function() {
    this._effectDuration = this.bitmap.height;
    this._appeared = false;
};

Sprite_Enemy.prototype.startInstantCollapse = function() {
    this._effectDuration = 16; // たぶん、エフェクトの間隔
    this._appeared = false;
};

//
Sprite_Enemy.prototype.updateEffect = function() {
    this.setupEffect();
    if (this._effectDuration > 0) {
        this._effectDuration--;
        switch (this._effectType) {
            case "whiten":
                this.updateWhiten();
                break;
            case "blink":
                this.updateBlink();
                break;
            case "appear":
                this.updateAppear();
                break;
            case "disappear":
                this.updateDisappear();
                break;
            case "collapse":
                this.updateCollapse();
                break;
            case "bossCollapse":
                this.updateBossCollapse();
                break;
            case "instantCollapse":
                this.updateInstantCollapse();
                break;
        }
        if (this._effectDuration === 0) {
            this._effectType = null;
        }
    }
};

Sprite_Enemy.prototype.isEffecting = function() {
    return this._effectType !== null;
};

Sprite_Enemy.prototype.revertToNormal = function() {
    this._shake = 0;
    this.blendMode = 0;
    this.opacity = 255;
    this.setBlendColor([0, 0, 0, 0]);
};

Sprite_Enemy.prototype.updateWhiten = function() {
    const alpha = 128 - (16 - this._effectDuration) * 8;
    this.setBlendColor([255, 255, 255, alpha]);
};

Sprite_Enemy.prototype.updateBlink = function() {
    this.opacity = this._effectDuration % 10 < 5 ? 255 : 0;
};

Sprite_Enemy.prototype.updateAppear = function() {
    this.opacity = (16 - this._effectDuration) * 16;
};

Sprite_Enemy.prototype.updateDisappear = function() {
    this.opacity = 256 - (32 - this._effectDuration) * 10;
};

Sprite_Enemy.prototype.updateCollapse = function() {
    this.blendMode = 1;
    this.setBlendColor([255, 128, 128, 128]);
    this.opacity *= this._effectDuration / (this._effectDuration + 1);
};

Sprite_Enemy.prototype.updateBossCollapse = function() {
    this._shake = (this._effectDuration % 2) * 4 - 2;
    this.blendMode = 1;
    this.opacity *= this._effectDuration / (this._effectDuration + 1);
    this.setBlendColor([255, 255, 255, 255 - this.opacity]);
    if (this._effectDuration % 20 === 19) {
        SoundManager.playBossCollapse2();
    }
};

Sprite_Enemy.prototype.updateInstantCollapse = function() {
    this.opacity = 0;
};

Sprite_Enemy.prototype.damageOffsetX = function() {
    return Sprite_Battler.prototype.damageOffsetX.call(this);
};

Sprite_Enemy.prototype.damageOffsetY = function() {
    return Sprite_Battler.prototype.damageOffsetY.call(this) - 8;
};



使いそうなファイルも添付しておきます。



余談になりますが、状態(ステート)によって敵画像を変更したいとも考えていて、
その足がかりになると思い、この方法を選びました。

つまり、2枚目の画像ファイル名を変更すれば、
毒で苦しそうだったり、目をつむって寝てたりできる!
と企んでいます。


よろしくお願いします。
添付ファイル
goblin.zip
使いそうな画像ファイルと、出来損ないのプラグインファイルです
(428.21 KiB) ダウンロード数: 8 回
最後に編集したユーザー やまQ on 2022年1月25日(火) 22:49 [ 編集 2 回目 ]

アバター
WTR
記事: 558
登録日時: 2015年12月22日(火) 19:14

Re: 敵画像を複数にする方法

投稿記事by WTR » 2022年1月25日(火) 20:55

まず根本的な勘違いであろうという指摘からなんですが
ツクールMZ はプラグインなしでエネミーのタッチ選択が可能です。

続いて本題。
タイトルと関係なくなっちゃいますが
タッチの反応範囲を狭められたらいいのかなと思ったのでそっち方面から攻める案を提案します。

Sprite_Enemy は Sprite_Battler を継承し、Sprite_Battler は Sprite_Clickable を継承しています。
で、タッチ範囲は Sprite_Clickable に定義されています。

コード: 全て選択

Sprite_Clickable.prototype.hitTest = function(x, y) {
    const rect = new Rectangle(
        -this.anchor.x * this.width,
        -this.anchor.y * this.height,
        this.width,
        this.height
    );
    return rect.contains(x, y);
};

↾が継承元の処理で、これを Sprite_Enemy 向けに作り変えます。

コード: 全て選択

(() => {
   "use strict";
   Sprite_Enemy.prototype.hitTest = function(x, y) {
      const touchMargin = this._enemy.enemy().meta["touchMargin"];
      const touchMarginX = !!touchMargin ? Number(touchMargin.split(",")[0]) : 0;
      const touchMarginY = !!touchMargin ? Number(touchMargin.split(",")[1]) : 0;
       const rect = new Rectangle(
         -this.anchor.x * this.width + touchMarginX,
         -this.anchor.y * this.height + touchMarginY,
         this.width - touchMarginX * 2,
         this.height - touchMarginY * 2
       );
       return rect.contains(x, y);
   };
})();

↾を適当な名前でプラグインとして保存し、エネミーのメモ欄に <touchMargin: 100, 20>
とか記載すると
エネミー画像の左右100pix、上下20pix はタッチ反応しなくなります。
もっと巧い & 安全な書き方がありそうな気がしますがとりあえず動くはず…

余談で書かれている内容は別件として考えた方がよいと思います。個人的には。
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
アバター
WTR
記事: 558
登録日時: 2015年12月22日(火) 19:14

Re: 敵画像を複数にする方法

投稿記事by WTR » 2022年1月25日(火) 21:12

勝手に話を進めてしまいましたが
もともとの質問だった画像が表示されません、という件は
loadBitmap2 で this.bitmap2 に画像をロードする処理が追加されていますが
これを画面に表示するところまでは処理が追加されていなくて
データとして持っているだけでどこにも使われていない状態で止まっているためですね。
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
やまQ
記事: 32
登録日時: 2017年6月26日(月) 14:26

Re: 【解決】エネミー画像のタッチ範囲を調整する方法

投稿記事by やまQ » 2022年1月25日(火) 22:47

早速、試してみました。
思っていた通りの動作でした。

丁寧に説明して頂いてありがとうございます。すごく分かりやすいです。
画像が表示されない事も、ステートで画像変更の事も合点しました。
デフォルトでマウス操作できたとは知りませんでした。
勘違いしてたみたいです。恥ずかしい><

プラグインの頭に、

コード: 全て選択

// エネミー画像のタッチ範囲を調整する
// ツクマテ内『 エネミー画像のタッチ範囲を調整する方法 』
// https://tm.lucky-duet.com/viewtopic.php?f=99&t=12329
/*:
 * @target MZ
 * @plugindesc エネミー画像のタッチ範囲を調整
 * @author WTR
 * @version 1.0
 * @help
 * ターゲット選択時などで、エネミー画像に
 * タッチに反応しない範囲を設定できます。
 *
 * エネミーのメモ欄に以下のタグを記述します。
 *
 * <touchMargin: x, y>   // xyは外側のピクセル数です。
 */


と記述して、使用させて頂きます。
本当にありがとうございました。

不備がなければ、ご返信には及びません。
アバター
WTR
記事: 558
登録日時: 2015年12月22日(火) 19:14

Re: 【解決】エネミー画像のタッチ範囲を調整する方法

投稿記事by WTR » 2022年1月26日(水) 19:17

1日経ってみると、なんとも間抜けな提案をしてしまったような気がしてきています…

透明部分を除外すればよかったのに、わざわざメモ欄に書かせるのは不親切だなぁと。
それ自体は難しくない気がするのですが
ついでにマウスオーバー・アウトの仕様も見直したほうがよさそうな気がして思案中です。
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
アバター
WTR
記事: 558
登録日時: 2015年12月22日(火) 19:14

Re: 【解決】エネミー画像のタッチ範囲を調整する方法

投稿記事by WTR » 2022年1月26日(水) 20:50

正直あまり自信はないもののとりあえず挙動はスムーズになったような気がします。
メモ欄も要らないし、こっちのほうがよさそう。

コード: 全て選択

(() => {
   "use strict";

   // 不透明な部分のみ hit 判定する
   Sprite_Enemy.prototype.hitTest = function(x, y) {
      const xx = x + this.anchor.x * this.width;
      const yy = y + this.anchor.y * this.height;
      const isOpaque = this.bitmap ? this.bitmap.getAlphaPixel(xx, yy) > 0 : false;
      return Sprite_Clickable.prototype.hitTest.call(this, x, y) && isOpaque;
   };

   // 複数の Sprite_Enemy が重なっている場合、表示優先度最大のものを選択する
   Sprite_Enemy.prototype.onMouseEnter = function() {
      const priorEnemy = SceneManager._scene._spriteset._enemySprites
         .filter(sprite => sprite._appeared && sprite.isBeingTouched())
         .map(sprite => sprite._enemy)
         .pop();
      if (priorEnemy) {
         $gameTemp.setTouchState(priorEnemy, "select");
      }
   };

   // onMouseEnter と同じ処理でいい気がする
   Sprite_Enemy.prototype.onMouseExit = function() {
      this.onMouseEnter();
   };

})();
Twitter、はじめました。
https://twitter.com/wtr_in_reverie/
やまQ
記事: 32
登録日時: 2017年6月26日(月) 14:26

Re: 【解決】エネミー画像のタッチ範囲を調整する方法

投稿記事by やまQ » 2022年1月27日(木) 06:10

返信が遅れました。ごめんなさい。


新しい方法のプラグインを試してみました。

結果、想像以上に良かったです。タグすら書かなくていいのは楽です。
軽くテストプレイして、誤選択は起こりませんでした。
素晴らしいです。



どちらを使うか悩ましいですが、ひとまずは新しい方を使わせて貰います。

作成のヒントくらいは頂けるかな程度に思っていたので、とても助かりました。
ありがとうございました。

“MZ:質問” へ戻る