2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

カードを自然な感じで重ねる判定条件を考える

Posted at

背景

HTML5のCanvasとJavascriptを使って、スパイダーソリティア(ゲーム)を開発をしています。

問題点

掴んだカードを他のカードの上に移動させる場合、移動先のカードを判定する必要があります。

マウスポインタの座標を基準に判定すると、マウスポインタが移動先のカードの上にのっている必要がある為、カードの画像は重なっていても、移動先として判定されないケースがあります。

解決策

カードを掴んでいる状態では、ユーザーはマウスポインタではなく、表示されているカードの位置を基準にしていると考えられます。

そこで、判定基準をマウスポインタ座標からカードの重心(中心)に変更することで、ユーザーの認知に近い自然な判定条件を目指します。

カードの重心を計算する

カードの重心を計算する実装はシンプルです。

マウスポインタ座標(x, y)からカードを掴んだときの左上からのズレ量(offset_x, offset_y)を引くことで、今表示しているカードの左上座標が求まります。

この左上座標にカード幅の半分、カード高さの半分を足すと、カードの重心になります。

center.js
const center_x = (x - offset_x + card_width / 2);   // X方向の重心
const center_y = (y - offset_y + card_height / 2);  // Y方向の重心

まとめ

マウスポインタ座標からカードの重心基準に変更することで、自然な操作感にすることができました。

サンプルコード

最後に、今回説明した内容のサンプルコードを掲載しておきます。

center.js
const card_width = 50;                                            // カードの幅
const card_height = 80;                                           // カードの高さ
const src_rect = new DOMRect(10, 100, card_width, card_height);    // 移動元カードがある位置
const dest_rect = new DOMRect(120, 100, card_width, card_height);  // 移動先カードがある位置

const canvas = document.getElementById("canvas");
canvas.addEventListener('mousedown', onMouseDown, false);
canvas.addEventListener('mouseup', onMouseUp, false);
canvas.addEventListener('mousemove', onMouseMove, false);
var ctx = canvas.getContext("2d");
ctx.fillStyle = "darkgreen";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.fillRect(src_rect.left, src_rect.top, src_rect.width, src_rect.height);
ctx.fillStyle = "yellow";
ctx.fillRect(dest_rect.left, dest_rect.top, dest_rect.width, dest_rect.height);

var holding = false;  // カードをつかんでいる状態ならtrue
var offset_x = 0;     // マウスポインタ座標とカード左端座標のズレ幅
var offset_y = 0;     // マウスポインタ座標とカード上端座標のズレ幅

function onMouseDown(event){
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  if(x >= src_rect.left && x <= src_rect.right){
    if(y >= src_rect.top && y <= src_rect.bottom){
      offset_x = x - src_rect.left;
      offset_y = y - src_rect.top;
      holding = true;
    }
  }
  event.preventDefault();
}

function onMouseUp(event){
  const rect = canvas.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  const center_x = (x - offset_x + card_width / 2);   // X方向の重心
  const center_y = (y - offset_y + card_height / 2);  // Y方向の重心
  if(center_x >= dest_rect.left && center_x <= dest_rect.right){
    if(center_y >= dest_rect.top && center_y <= dest_rect.bottom){
      window.alert("重心基準で重なった!");
    }
  }
  holding = false;
  event.preventDefault();
}

function onMouseMove(event){
  if(holding){
    const rect = canvas.getBoundingClientRect();
    const x = event.clientX - rect.left;
    const y = event.clientY - rect.top; 
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "darkgreen";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "yellow";
    ctx.fillRect(dest_rect.left, dest_rect.top, dest_rect.width, dest_rect.height);
    ctx.fillStyle = "red";
    ctx.fillRect(x - offset_x, y - offset_y, src_rect.width, src_rect.height);
    event.preventDefault();
  }
}
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Center</title>
</head>
<body>
  <canvas id="canvas" width="300" height="300"></canvas>
  <script src="./center.js"></script>
</body>
</html>
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?