【HTML&CSSではじめるミニゲーム作成の基本】スプライトの接触判定をじっくり理解しよう
※ 当ページには【広告/PR】を含む場合があります。
2023/12/24
HTML/CSSでスプライトをキーボード入力操作できるプログラムを作成する
<body>
<section id="game-stage">
<div id="taco-screen">
<div id="taco"></div>
<div>
</section>
<section id="console-area">
<input type="text" id="game-status" name="message" value="">
</section>
</body>
id
body {
display: block;
margin: 0;
padding: 0;
}
#game-stage {
display: block;
position: relative;
width: 100%;
height: 280px;
padding: 0;
margin: 0 auto;
pointer-events: none;
/* ポイント① ... カスタムプロパティの利用 */
--x-pos: 20px;
--y-pos: 0px;
}
#taco-screen {
display: block;
position: absolute;
background: aqua;
padding: 0;
width: 80px;
height: 60px;
/* ポイント① ... カスタムプロパティの利用 */
top: var(--y-pos);
left: var(--x-pos);
}
#taco {
display: block;
width: 100%;
height: 100%;
background: transparent url('https://raw.githubusercontent.com/tacoskingdom/commonBlogMaterial/main/tacokin-p-school/blog109/character_juken_tako_okuto_pass.svg') no-repeat 0 0;
background-size: 80px 60px;
}
#console-area {
display: block;
position: absolute;
width: 100%;
height: 20px;
top: 280px;
padding: 0 0 0 0;
margin: 0 auto;
}
#game-status {
width: 100%;
font-size: 1.1rem;
background: #f5b560;
pointer-events: none;
border: navajowhite;
outline: none;
}
「キーボードの矢印キーからスプライトを動かす」
<script>タグ内
const isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--a', 0);
if (isSupported) {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}`;
document.body.addEventListener("keydown", (e) => {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}`;
const key = e.keyCode;
if(key === 40){
stage.style.setProperty('--y-pos', `${parseInt(ypos.replace('px','')) + 4}px`);
}
if(key === 39){
stage.style.setProperty('--x-pos', `${parseInt(xpos.replace('px','')) + 4}px`);
}
if(key === 38){
stage.style.setProperty('--y-pos', `${parseInt(ypos.replace('px','')) - 4}px`);
}
if(key === 37){
stage.style.setProperty('--x-pos', `${parseInt(xpos.replace('px','')) - 4}px`);
}
e.preventDefault();
}, false);
}
else {
const input = document.getElementById("game-status");
input.value ='お使いのブラウザはカスタムプロパティ非対応です';
}
座標軸に沿った囲みボックスで接触判定
囲みボックス
1. 壁面(画面枠)との接触
2. 他のスプライトとの接触
壁面(画面枠)との接触
if (
(スプライトのL < 左側の壁枠のx座標) || (スプライトのL+W > 右側の壁枠のx座標)
) {
//スプライトのx座標を変更しない
}
if (
(スプライトのT < 上側の壁枠のy座標) || (スプライトのT+H > 下側の壁枠のy座標)
) {
//スプライトのy座標を変更しない
}
他のスプライトとの接触
囲みボックス
//x軸の位置関係からチェック
if ((自分のL > 相手のL+W) || (自分のL+W < 相手のL)) {
//衝突はしていないときの処理
...
}
else {
//衝突している可能性があるので、y軸の位置関係もチェック
if ((自分のT > 相手のT+H) || (自分のT+H < 相手のT)) {
//衝突はしていないときの処理
...
}
else {
//衝突しているときの処理
...
}
}
囲みボックスの接触判定を実装してみる
<body>
<section id="game-stage">
<div id="taco-screen">
<div id="taco"></div>
</div>
<!-- 👇の要素を追加 -->
<div id="dagon-screen">
<div id="dagon"></div>
</div>
</section>
<section id="console-area">
<input type="text" id= "game-status" name="message" value="">
</section>
</body>
body {
display: block;
margin: 0;
padding: 0;
}
#game-stage {
display: block;
position: relative;
width: 100%;
height: 280px;
padding: 0;
margin: 0 auto;
pointer-events: none;
--x-pos: 20px;
--y-pos: 0px;
/* 👇敵スプライトの座標 */
--x2-pos: 120px;
--y2-pos: 50px;
/* 👇接触判定時の背景色 */
--collision-state: aqua;
}
#taco-screen {
display: block;
position: absolute;
padding: 0;
width: 80px;
height: 60px;
top: var(--y-pos);
left: var(--x-pos);
/* 👇接触時に色が切り替わるように修正 */
background: var(--collision-state);
z-index: 1;
}
#taco {
display: block;
width: 100%;
height: 100%;
background: transparent url('https://raw.githubusercontent.com/tacoskingdom/commonBlogMaterial/main/tacokin-p-school/blog109/character_juken_tako_okuto_pass.svg') no-repeat 0 0;
background-size: 80px 60px;
}
/* 👇接触対象の敵スプライトのスタイルを追加 */
#dagon-screen {
display: block;
position: absolute;
padding: 0;
top: var(--y2-pos);
left: var(--x2-pos);
width: 80px;
height: 60px;
background: var(--collision-state);
z-index: 0;
}
#dagon {
display: block;
width: 100%;
height: 100%;
background: transparent url('https://raw.githubusercontent.com/tacoskingdom/commonBlogMaterial/main/tacokin-p-school/blog109/character_cthulhu_kuturufu.svg') no-repeat 0 0;
background-size: 80px 60px;
}
#console-area {
display: block;
position: absolute;
width: 100%;
height: 20px;
top: 280px;
padding: 0 0 0 0;
margin: 0 auto;
}
#game-status {
width: 100%;
font-size: 1.1rem;
background: #f5b560;
pointer-events: none;
border: navajowhite;
outline: none;
}
const isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--a', 0);
if (isSupported) {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}`;
document.body.addEventListener("keydown", (e) => {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const stageW = Number(styles.width.replace('px',''));
const stageH = Number(styles.height.replace('px',''));
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const x2pos = styles.getPropertyValue('--x2-pos');
const y2pos = styles.getPropertyValue('--y2-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}|[敵の位置]x=${x2pos},y=${y2pos}`;
//👇2つのスプライトの座標と画像サイズ
const _x1 = parseInt(xpos.replace('px',''));
const _y1 = parseInt(ypos.replace('px',''));
const _x2 = parseInt(x2pos.replace('px',''));
const _y2 = parseInt(y2pos.replace('px',''));
const tacoW = 80;
const tacoH = 60;
const dagonW = 80;
const dagonH = 60;
const key = e.keyCode;
if(key === 40){
//下に移動(y座標を+4)
let _ny = _y1 + 4;
//👇画面下に接触した場合、座標を元に戻す
if (_ny + tacoH > stageH) _ny -= 4;
stage.style.setProperty('--y-pos', `${_ny}px`);
//👇敵スプライトとの接触判定
//x軸の位置関係からチェック
if (_x1 > _x2 + dagonW || _x1 + tacoW < _x2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突している可能性があるので、y軸の位置関係もチェック
if (_ny > _y2+dagonH || _ny+tacoH < _y2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突しているときの処理
stage.style.setProperty('--collision-state', 'red');
}
}
}
if(key === 39){
//右に移動(x座標を+4)
let _nx = _x1 + 4;
//👇画面右に接触した場合、座標を元に戻す
if (_nx + tacoW > stageW) _nx -= 4;
stage.style.setProperty('--x-pos', `${_nx}px`);
//👇敵スプライトとの接触判定
//x軸の位置関係からチェック
if (_nx > _x2 + dagonW || _nx + tacoW < _x2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突している可能性があるので、y軸の位置関係もチェック
if (_y1 > _y2+dagonH || _y1+tacoH < _y2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突しているときの処理
stage.style.setProperty('--collision-state', 'red');
}
}
}
if(key === 38){
//上に移動(y座標を-4)
let _ny = _y1 - 4;
//👇画面上に接触した場合、座標を元に戻す
if (_ny < 0) _ny += 4;
stage.style.setProperty('--y-pos', `${_ny}px`);
//👇敵スプライトとの接触判定
//x軸の位置関係からチェック
if (_x1 > _x2 + dagonW || _x1 + tacoW < _x2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突している可能性があるので、y軸の位置関係もチェック
if (_ny > _y2+dagonH || _ny+tacoH < _y2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突しているときの処理
stage.style.setProperty('--collision-state', 'red');
}
}
}
if(key === 37){
//左に移動(x座標を-4)
let _nx = _x1 - 4;
//👇画面左に接触した場合、座標を元に戻す
if (_nx < 0) _nx += 4;
stage.style.setProperty('--x-pos', `${_nx}px`);
//👇敵スプライトとの接触判定
//x軸の位置関係からチェック
if (_nx > _x2 + dagonW || _nx + tacoW < _x2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突している可能性があるので、y軸の位置関係もチェック
if (_y1 > _y2+dagonH || _y1+tacoH < _y2) {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
else {
//衝突しているときの処理
stage.style.setProperty('--collision-state', 'red');
}
}
}
e.preventDefault();
}, false);
}
else {
const input = document.getElementById("game-status");
input.value ='お使いのブラウザはカスタムプロパティ非対応です';
}
円形領域で接触判定
if (
(スプライト中心のx座標 - R < 左側の壁枠のx座標) || (スプライト中心のx座標 + R > 右側の壁枠のx座標)
) {
//スプライトのx座標を変更しない
}
if (
(スプライト中心のy座標 - R < 上側の壁枠のy座標) || (スプライト中心のy座標 + R > 下側の壁枠のy座標)
) {
//スプライトのy座標を変更しない
}
if (自分のR + 相手のR > D) {
//衝突はしていないときの処理
...
}
else {
//衝突しているときの処理
...
}
円形領域の接触判定を実装してみる
/* ...中略 */
#taco-screen {
display: block;
position: absolute;
padding: 0;
background: var(--collision-state);
z-index: 1;
width: 80px;
top: var(--y-pos);
left: var(--x-pos);
/* 👇円形領域になるように調整 */
height: 80px;
border-radius: 50%;
}
#taco {
display: block;
width: 100%;
height: 100%;
background: transparent url('https://raw.githubusercontent.com/tacoskingdom/commonBlogMaterial/main/tacokin-p-school/blog109/character_juken_tako_okuto_pass.svg') no-repeat 0 0;
/* 👇円形領域になるように調整 */
background-size: 80px 80px;
}
#dagon-screen {
display: block;
position: absolute;
padding: 0;
top: var(--y2-pos);
left: var(--x2-pos);
width: 80px;
background: var(--collision-state);
z-index: 0;
/* 👇円形領域になるように調整 */
height: 80px;
border-radius: 50%;
}
#dagon {
display: block;
width: 100%;
height: 100%;
background: transparent url('https://raw.githubusercontent.com/tacoskingdom/commonBlogMaterial/main/tacokin-p-school/blog109/character_cthulhu_kuturufu.svg') no-repeat 0 0;
/* 👇円形領域になるように調整 */
background-size: 80px 80px;
}
/* ...以下略 */
const isSupported = window.CSS && window.CSS.supports && window.CSS.supports('--a', 0);
if (isSupported) {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}`;
document.body.addEventListener("keydown", (e) => {
const stage = document.getElementById("game-stage");
const styles = getComputedStyle(stage);
const stageW = Number(styles.width.replace('px',''));
const stageH = Number(styles.height.replace('px',''));
const xpos = styles.getPropertyValue('--x-pos');
const ypos = styles.getPropertyValue('--y-pos');
const x2pos = styles.getPropertyValue('--x2-pos');
const y2pos = styles.getPropertyValue('--y2-pos');
const input = document.getElementById("game-status");
input.value = `矢印キーで動きます|[タコの位置]x=${xpos},y=${ypos}|[敵の位置]x=${x2pos},y=${y2pos}`;
//👇2つのスプライトの座標と画像サイズと円形領域の半径
const _x1 = parseInt(xpos.replace('px',''));
const _y1 = parseInt(ypos.replace('px',''));
const _x2 = parseInt(x2pos.replace('px',''));
const _y2 = parseInt(y2pos.replace('px',''));
const tacoW = 80;
const tacoH = 80;
const tacoR = 40;
const dagonW = 80;
const dagonH = 80;
const dagonR = 40;
//2つのスプライトの中心位置
let _cx1 = _x1 + tacoW/2;
let _cy1 = _y1 + tacoH/2;
let _cx2 = _x2 + dagonW/2;
let _cy2 = _y2 + dagonH/2;
const key = e.keyCode;
if(key === 40){
//下に移動(y座標を+4)
let _ny = _y1 + 4;
//👇画面下に接触した場合、座標を元に戻す
_cy1 = _ny + tacoH/2;
if (_cy1 + tacoR > stageH) {
_ny -= 4;
_cy1 = _ny + tacoH/2;
}
stage.style.setProperty('--y-pos', `${_ny}px`);
}
if(key === 39){
//右に移動(x座標を+4)
let _nx = _x1 + 4;
//👇画面右に接触した場合、座標を元に戻す
_cx1 = _nx + tacoW/2;
if (_cx1 + tacoR > stageW) {
_nx -= 4;
_cx1 = _nx + tacoW/2;
}
stage.style.setProperty('--x-pos', `${_nx}px`);
}
if(key === 38){
//上に移動(y座標を-4)
let _ny = _y1 - 4;
//👇画面上に接触した場合、座標を元に戻す
_cy1 = _ny + tacoH/2;
if (_cy1 - tacoR < 0) {
_ny += 4;
_cy1 = _ny + tacoH/2;
}
stage.style.setProperty('--y-pos', `${_ny}px`);
}
if(key === 37){
//左に移動(x座標を-4)
let _nx = _x1 - 4;
//👇画面左に接触した場合、座標を元に戻す
_cx1 = _nx + tacoW/2;
if (_cx1 - tacoR < 0) {
_nx += 4;
_cx1 = _nx + tacoW/2;
}
}
//👇敵スプライトとの接触判定(中心距離の位置関係からチェック)
const d = Math.sqrt((_cx1 - _cx2)*(_cx1 - _cx2) + (_cy1 - _cy2)*(_cy1 - _cy2));
if (tacoR + dagonR > d) {
//衝突しているときの処理
stage.style.setProperty('--collision-state', 'red');
}
else {
//衝突はしていないときの処理
stage.style.setProperty('--collision-state', 'aqua');
}
e.preventDefault();
}, false);
}
else {
const input = document.getElementById("game-status");
input.value ='お使いのブラウザはカスタムプロパティ非対応です';
}
まとめ
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー