볼 대 볼 충돌 판정
요약
각 모델을 포함하는 구는 적중 판정을 내리는 데 사용됩니다. 이 샘플에서는 두 개의 구 모델에 대해 충돌 감지가 수행됩니다.
운영 환경
필수 구성 요소
지원되는 XNA 버전 |
|
지원되는 플랫폼 |
|
Windows 필수 버텍스 셰이더 버전 | 2.0 |
Windows 필수 픽셀 셰이더 버전 | 2.0 |
운영 환경
플랫폼 |
|
샘플로 작업하는 방법
작동 키보드Xbox | 360 컨트롤러마우스 | 터치 | ||
---|---|---|---|---|
무빙 볼 1 | ↑↓←→ | 왼쪽 스틱 | 왼쪽 버튼 & 드래그 | - |
물질
히트 판정에 대하여
슈팅 게임이나 액션 게임에서는 캐릭터와 총알의 충돌, 캐릭터의 충돌 등 다양한 충돌이 발생하기 때문에 이를 판단하는 프로그램을 작성할 필요가 있습니다. 프로그램에서는 일반적으로 이를 충돌 감지라고 합니다. 타격 판정에는 단순한 것부터 수학적, 물리적으로 복잡한 것까지 다양한 패턴이 있습니다. 일반적으로 게임은 정확도보다는 처리 부하를 줄이기 위해 만들어지는 경우가 많고, 영역에 극단적 인 편차가 없으면 정확하지 않아도 별로 중요하지 않습니다.
이 팁은 가장 일반적이고 부담이 적은 "공 대 공" 타격 감지에 대해 설명합니다. 구(球)와 구(球)이기 때문에 3차원 공간에서의 충돌 판정을 이야기하고 있지만, 2차원 공간에서의 원의 충돌도 거의 같은 과정으로 치환할 수 있다.
볼과 볼의 타격 판정은 어떻게 작동하는가
구와 구의 충돌을 결정하는 데 사용되는 두 가지 매개변수는 각 모델의 "위치"와 모델 크기의 "반경"입니다. 이 두 매개변수가 맞는지 확인하려면 아래 그림을 보면 쉽게 이해할 수 있습니다.
- P: 모델의 위치 L: 두 점 사이의 거리(P2-P1) R: 구의 반지름
"두 모델 사이의 거리"가 "L"이고 "두 모델의 반지름의 합"이 "R"이면 "L < R"은 충돌하고 있음을 의미합니다. 한편, "L > R"이면 충돌이 없는 것을 의미합니다. "L = R"의 충돌 감지는 어느 쪽에도 중요하지 않습니다.
밭
<summary>
モデル
</summary>
private Model model = null;
<summary>
モデルの基本包括球
</summary>
private BoundingSphere baseBoundingSphere = new BoundingSphere();
<summary>
球1の位置
</summary>
private Vector3 sphere1Position = new Vector3(0.0f, 0.0f, 0.0f);
<summary>
球2の位置
</summary>
private Vector3 sphere2Position = new Vector3(1.5f, 0.0f, -3.0f);
<summary>
球1の包括球
</summary>
private BoundingSphere sphere1BoundingSphere = new BoundingSphere();
<summary>
球2の包括球
</summary>
private BoundingSphere sphere2BoundingSphere = new BoundingSphere();
<summary>
衝突フラグ
</summary>
private bool isCollision = false;
히트 검출 처리는 독자적인 프로그램을 작성할 수 있지만, XNA Framework에는 구의 파라미터나 히트 검출을 쉽게 처리할 수 있는 「BoundingSphere」라는 구조가 있기 때문에, 사용하고 싶습니다.
로드된 Model 클래스의 각 ModelMesh에는 이미 모델을 포함하는 구체 정보 "BoundingSphere"가 포함되어 있으므로 이를 검색하기 위해 "baseBoundingSphere" 필드를 준비했습니다.
다른 기능으로는 각 구의 위치, 두 구의 영향을 결정하는 데 사용되는 BoundingSphere 및 충돌 플래그가 있습니다.
부하
// モデルを作成
this.model = this.Content.Load<Model>("Sphere");
// 包括球取得
this.baseBoundingSphere = this.model.Meshes[0].BoundingSphere;
// 各モデル用の包括球半径設定
this.sphere1BoundingSphere.Radius = this.baseBoundingSphere.Radius;
this.sphere2BoundingSphere.Radius = this.baseBoundingSphere.Radius;
모델의 각 모델 메시에는 모델 메시의 모델을 포함하는 구체에 대한 정보를 얻을 수 있는 BoundingSphere 속성이 있습니다. 우리가 사용하고 있는 모델에는 단일 ModelMesh가 있기 때문에, 첫 번째 인덱스에서 baseBoundingSphere 로 포괄 구체를 복사했습니다.
구의 반지름이 고정되어 있으므로 반지름은 두 BoundingSpheres의 Radius 속성에서 미리 설정됩니다.
히트 판정
// 衝突判定用の球を設定
this.sphere1BoundingSphere.Center =
this.sphere1Position + this.baseBoundingSphere.Center;
this.sphere2BoundingSphere.Center =
this.sphere2Position + this.baseBoundingSphere.Center;
// 衝突判定
this.isCollision =
this.sphere1BoundingSphere.Intersects(this.sphere2BoundingSphere);
BoundingSphere.Intersects 메서드를 사용하여 구 간 충돌을 확인할 수 있습니다. 두 BoundingSpheres에 대한 반경이 이미 설정되어 있으므로 BoundingSphere.Center 속성을 구의 위치와 원래 우산 구의 중심 좌표로 설정합니다. 원래 구의 중심 좌표가 추가되는 이유는 포함 구의 중심이 반드시 원점이 아니기 때문입니다.
다른 BoundingSphere를 BoundingSphere.Intersects에 대한 인수로 지정하면 충돌 여부를 확인하기 위해 bool을 반환하므로 얻을 수 있습니다. 이 플래그는 문자가 올바른지 확인하기 위해 문자를 그리는 데 사용됩니다.
히트 판정 계산 방법
XNA Framework는 구뿐만 아니라 광선(선) 및 상자와 같은 다양한 모양의 충돌에도 사용할 수 있는 BoundingSphere라는 유용한 충돌 감지 구조를 제공합니다.
그러나 구 간의 충돌인 경우 BoundingSphere.Intersects를 사용하지 않고 간단한 계산으로 확인할 수 있습니다.
- 결과(명중 여부) = 구 2의 위치와 구 1의 위치 사이의 거리 < 구 1의 반지름 + 구 2의 반지름
프로그래밍 방식으로 작성하는 경우
this.isCollision = (this.sphere2Position - this.sphere1Position).Length() <
(this.sphere1BoundingSphere.Radius + this.sphere2BoundingSphere.Radius);
와 닿습니다. (위의 충돌 감지 프로세스는 포괄 구의 중심이 완벽한 구와 같은 원점이라고 가정하므로 모델의 포괄 구의 중심을 고려하지 않습니다.) 생각하면 다음과 같습니다)
this.isCollision = ((this.sphere2Position + this.baseBoundingSphere.Center) -
(this.sphere1Position + this.baseBoundingSphere.Center)).Length() <
(this.sphere1BoundingSphere.Radius + this.sphere2BoundingSphere.Radius);
모든 코드
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
#if WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
#endif
namespace CollisionSphereAndSphere
{
<summary>
ゲームメインクラス
</summary>
public class GameMain : Microsoft.Xna.Framework.Game
{
<summary>
グラフィックデバイス管理クラス
</summary>
private GraphicsDeviceManager graphics = null;
<summary>
スプライトのバッチ化クラス
</summary>
private SpriteBatch spriteBatch = null;
<summary>
スプライトでテキストを描画するためのフォント
</summary>
private SpriteFont font = null;
<summary>
モデル
</summary>
private Model model = null;
<summary>
モデルの基本包括球
</summary>
private BoundingSphere baseBoundingSphere = new BoundingSphere();
<summary>
球1の位置
</summary>
private Vector3 sphere1Position = new Vector3(0.0f, 0.0f, 0.0f);
<summary>
球2の位置
</summary>
private Vector3 sphere2Position = new Vector3(1.5f, 0.0f, -3.0f);
<summary>
球1の包括球
</summary>
private BoundingSphere sphere1BoundingSphere = new BoundingSphere();
<summary>
球2の包括球
</summary>
private BoundingSphere sphere2BoundingSphere = new BoundingSphere();
<summary>
衝突フラグ
</summary>
private bool isCollision = false;
<summary>
前回のマウスの状態
</summary>
private MouseState oldMouseState;
<summary>
GameMain コンストラクタ
</summary>
public GameMain()
{
// グラフィックデバイス管理クラスの作成
this.graphics = new GraphicsDeviceManager(this);
// ゲームコンテンツのルートディレクトリを設定
this.Content.RootDirectory = "Content";
#if WINDOWS_PHONE
// Windows Phone のデフォルトのフレームレートは 30 FPS
this.TargetElapsedTime = TimeSpan.FromTicks(333333);
// バックバッファサイズの設定
this.graphics.PreferredBackBufferWidth = 480;
this.graphics.PreferredBackBufferHeight = 800;
// フルスクリーン表示
this.graphics.IsFullScreen = true;
#endif
}
<summary>
ゲームが始まる前の初期化処理を行うメソッド
グラフィック以外のデータの読み込み、コンポーネントの初期化を行う
</summary>
protected override void Initialize()
{
// TODO: ここに初期化ロジックを書いてください
// コンポーネントの初期化などを行います
base.Initialize();
}
<summary>
ゲームが始まるときに一回だけ呼ばれ
すべてのゲームコンテンツを読み込みます
</summary>
protected override void LoadContent()
{
// テクスチャーを描画するためのスプライトバッチクラスを作成します
this.spriteBatch = new SpriteBatch(this.GraphicsDevice);
// フォントをコンテントパイプラインから読み込む
this.font = this.Content.Load<SpriteFont>("Font");
// モデルを作成
this.model = this.Content.Load<Model>("Sphere");
// 包括球取得
this.baseBoundingSphere = this.model.Meshes[0].BoundingSphere;
// 各モデル用の包括球半径設定
this.sphere1BoundingSphere.Radius = this.baseBoundingSphere.Radius;
this.sphere2BoundingSphere.Radius = this.baseBoundingSphere.Radius;
// あらかじめパラメータを設定しておく
foreach (ModelMesh mesh in this.model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
// デフォルトのライト適用
effect.EnableDefaultLighting();
// ビューマトリックスをあらかじめ設定
effect.View = Matrix.CreateLookAt(
new Vector3(0.0f, 10.0f, 1.0f),
Vector3.Zero,
Vector3.Up
);
// プロジェクションマトリックスをあらかじめ設定
effect.Projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians(45.0f),
(float)this.GraphicsDevice.Viewport.Width /
(float)this.GraphicsDevice.Viewport.Height,
1.0f,
100.0f
);
}
}
}
<summary>
ゲームが終了するときに一回だけ呼ばれ
すべてのゲームコンテンツをアンロードします
</summary>
protected override void UnloadContent()
{
// TODO: ContentManager で管理されていないコンテンツを
// ここでアンロードしてください
}
<summary>
描画以外のデータ更新等の処理を行うメソッド
主に入力処理、衝突判定などの物理計算、オーディオの再生など
</summary>
<param name="gameTime">このメソッドが呼ばれたときのゲーム時間</param>
protected override void Update(GameTime gameTime)
{
KeyboardState keyboardState = Keyboard.GetState();
MouseState mouseState = Mouse.GetState();
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
// Xbox 360 コントローラ、Windows Phone の BACK ボタンを押したときに
// ゲームを終了させます
if (gamePadState.Buttons.Back == ButtonState.Pressed)
{
this.Exit();
}
float speed = 0.1f;
// 球1の位置を移動させる
if (gamePadState.IsConnected)
{
this.sphere1Position.X += gamePadState.ThumbSticks.Left.X * speed;
this.sphere1Position.Z -= gamePadState.ThumbSticks.Left.Y * speed;
}
if (keyboardState.IsKeyDown(Keys.Left))
{
this.sphere1Position.X -= speed;
}
if (keyboardState.IsKeyDown(Keys.Right))
{
this.sphere1Position.X += speed;
}
if (keyboardState.IsKeyDown(Keys.Down))
{
this.sphere1Position.Z += speed;
}
if (keyboardState.IsKeyDown(Keys.Up))
{
this.sphere1Position.Z -= speed;
}
if (mouseState.LeftButton == ButtonState.Pressed)
{
// 直前にマウスの左ボタンが押されていない場合は差分を0にする
if (this.oldMouseState.LeftButton == ButtonState.Released)
{
this.oldMouseState = mouseState;
}
this.sphere1Position += new Vector3((mouseState.X - this.oldMouseState.X) * 0.01f,
0,
(mouseState.Y - this.oldMouseState.Y) * 0.01f);
}
// マウスの状態記憶
this.oldMouseState = mouseState;
// 衝突判定用の球を設定
this.sphere1BoundingSphere.Center =
this.sphere1Position + this.baseBoundingSphere.Center;
this.sphere2BoundingSphere.Center =
this.sphere2Position + this.baseBoundingSphere.Center;
// 衝突判定
this.isCollision =
this.sphere1BoundingSphere.Intersects(this.sphere2BoundingSphere);
// 登録された GameComponent を更新する
base.Update(gameTime);
}
<summary>
描画処理を行うメソッド
</summary>
<param name="gameTime">このメソッドが呼ばれたときのゲーム時間</param>
protected override void Draw(GameTime gameTime)
{
// 画面を指定した色でクリアします
this.GraphicsDevice.Clear(Color.CornflowerBlue);
// 深度バッファを有効にする
this.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
foreach (ModelMesh mesh in this.model.Meshes)
{
// 球1を描画
foreach (BasicEffect effect in mesh.Effects)
{
// ワールドマトリックス(位置指定)
effect.World = Matrix.CreateTranslation(this.sphere1Position);
}
mesh.Draw();
// 球2を描画
foreach (BasicEffect effect in mesh.Effects)
{
// ワールドマトリックス(位置指定)
effect.World = Matrix.CreateTranslation(this.sphere2Position);
}
mesh.Draw();
}
// スプライトの描画準備
this.spriteBatch.Begin();
// 衝突判定表示
this.spriteBatch.DrawString(this.font,
"IsCollision : " + this.isCollision,
new Vector2(30.0f, 30.0f), Color.White);
// スプライトの一括描画
this.spriteBatch.End();
// 登録された DrawableGameComponent を描画する
base.Draw(gameTime);
}
}
}