ボタン等の重なりにおける対策を考える

## やりたい事
ボタン等のオブジェクトの重なりにおいて，マウスの位置と表示上のオブジェクトの前後関係を一致させ，正しく動作させる。

## 対策が無い場合

オブジェクトをレイヤーとして考えるとき，以下のような場合があったとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953314/129efff3-887f-c6ff-0595-9116e9c34331.png)
もし重なった場合のことを考えなかった場合は…
![Desktop_2023_03_11_-_01_48_59_03__00_00_14_48_-_00_00_30_25__mp4_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953314/80d39a2f-64d3-d2b2-bbcd-ed8c9933b6cb.gif)
マウスポインタが2つのオブジェクトの重なっている部分に被った際に，下にあるオブジェクトまで反応しています。このまま押し込むと2つのボタンが同時に機能してしまい，思いがけない操作が発生する可能性があります。

## 実際に構築してみる
まず，判定の方法は簡略化すると
```cpp
void Object::Collide() {
  if (!enabled) return; // オブジェクトとして無効の場合，判定しない
  bool beforeMouseClicked = mouseClicked;
  bool releaseFromMouse = false;

  // マウスがオブジェクトの領域内か
  if (pos.x <= GetMouse().x &&
      pos.x + size.x >= GetMouse().x &&
      pos.y <= GetMouse().y &&
      pos.y + size.y >= GetMouse().y) {
    // マウスが接触している
    mouseHit = true;

    if (GetClick(LEFT_BUTTON) >= PressFrame::FIRST) {
      // マウスのクリックが1フレーム目か
      if (GetClick(LEFT_BUTTON) == PressFrame::FIRST)
        mouseClicked = true;
    } else {
      // マウスが離れている
      mouseClicked = false;
      releaseFromMouse = true;
    }

  } else {
    // マウスが当たっていない
    mouseHit = false;
    if (GetClick(LEFT_BUTTON) >= PressFrame::FIRST && !mouseClicked) {
      mouseSelected = false;
    }
  }

  if (GetClick(LEFT_BUTTON) == PressFrame::ZERO)
    mouseClicked = false;

  // マウスが離れた直後で，範囲内であった場合
  if (beforeMouseClicked && !mouseClicked && releaseFromMouse) {
    mouseSelected = true;
  }
}
```

### 重なった部分における対策の流れ
:::note warn
**最初の比較時の動作**
比較対象が何もない場合はそのまま更新して構いません。
:::

1. 新しい比較対象を引数として受け取る。
1. **登録済みの比較対象と異なる**場合 (かつ優先順位が登録済み比較対象の方が低い場合)
    1. **新しい比較対象がクリック(長押し)されている**状態の場合
        1. 登録済みの比較対象がもつ，**マウスに関する全状態を解除**する。
    1. それ以外の場合
        1. 登録済みの比較対象がもつ，**マウスのホバー状態を解除**する。
1. 1.に戻る。

### コードで書いてみる
実際に簡略化して書いてみましょう。
```cpp
#pragma once
#include "Object.hpp"

// 重なり対策クラス
class Overlapping {
 public:
  // 比較対象を更新
  static void Register(Object* _object) {
    if (topObject == nullptr)
      // 空の場合はそのまま登録して終了
      topObject = _object;

    else if (topObject != _object /*&& _object->GetEnforcedCollisionNum() >= topObject->GetEnforcedCollisionNum()*/) {
      // 自分を比較しない場合 (，かつ優先順位が登録済みの比較対象以上の場合) は比較対象を更新

      if (_object->GetMouseClicked())
        // 新しい比較対象がクリック中なら下にあるオブジェクトのマウス状態を全解除
        topObject->SetNoMouseWithClick(); // hovered = clicked = selected = false
      else
        // それ以外なら下にあるオブジェクトの接触状態を解除
        topObject->SetNoMouse(); // hovered = false

      topObject = _object; // 比較対象を更新
    }
  }

 private:
  static Object* topObject; // 比較対象
};
```
以上が「[重なった部分における対策の流れ](#重なった部分における対策の流れ)」を基に設計したクラスです。最後に，`Collide`関数で比較対象を更新するために1行追加します。

```diff_cpp
// (前略)
    if (GetClick(LEFT_BUTTON) >= PressFrame::FIRST) {
      // マウスのクリックが1フレーム目か
      if (GetClick(LEFT_BUTTON) == PressFrame::FIRST)
        mouseClicked = true;
    } else {
      // マウスが離れている
      mouseClicked = false;
      releaseFromMouse = true;
    }

+   Overlapping::Register(this);

  } else {
    // マウスが当たっていない
    mouseHit = false;
    if (GetClick(LEFT_BUTTON) >= PressFrame::FIRST && !mouseClicked) {
      mouseSelected = false;
    }
  }
// (後略)
```

これで，ボタン動作が思うように動いてきたと思います。

## 実行例

![Desktop_2023_03_11_-_01_47_45_02__00_00_00_00_-_00_00_14_48__mp4_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953314/a44a5dd7-c16c-4d88-1264-4c9d486cc821.gif)

## まとめ
- 重なった際は，下にあるオブジェクトのマウスに関する状態を変更させる！
- クリック時は他のオブジェクトへの入力状態を解除させる！

## 関連記事
今回のオブジェクトの判定は，以下の記事内(`void Obj::Object2D::CollideMouseAsBox()`)の補足となっております。

https://qiita.com/isirmt/items/4b8c65a77f06a8d9ae7c