背景
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>