การตัดสินการชนกันแบบบอลต่อลูก
สรุป
ทรงกลมที่ครอบคลุมแต่ละรุ่นใช้เพื่อตัดสินการตี ในตัวอย่างนี้การตรวจจับการชนจะดําเนินการสําหรับแบบจําลองทรงกลมสองแบบ
สภาพแวดล้อมในการทํางาน
เบื้องต้น
เวอร์ชัน XNA ที่รองรับ |
|
แพลตฟอร์มที่รองรับ |
|
Windows เวอร์ชัน Vertex Shader ที่จําเป็น | 2.0 |
Windows เวอร์ชัน Pixel Shader ที่จําเป็น | 2.0 |
สภาพแวดล้อมในการทํางาน
แท่น |
|
วิธีการทํางานกับตัวอย่าง
ตัวควบคุม Xbox เมาส์แป้นพิมพ์ที่ใช้งานได้ | 360 | สัมผัส | ||
---|---|---|---|---|
ลูกบอลเคลื่อนที่ 1 | ↑↓←→ | สติ๊กซ้าย | ปุ่มซ้าย & ลาก | - |
สาร
เกี่ยวกับ Hit Judgment
ในเกมยิงปืนและเกมแอคชั่น การชนกันต่างๆ เกิดขึ้น เช่น การชนกันระหว่างตัวละครกับกระสุน และการชนกันระหว่างตัวละคร ดังนั้นจึงจําเป็นต้องเขียนโปรแกรมเพื่อตัดสินพวกเขา ในโปรแกรมโดยทั่วไปจะเรียกว่าการตรวจจับการชน การตัดสินการตีมีหลากหลายรูปแบบ ตั้งแต่แบบง่ายไปจนถึงซับซ้อนทางคณิตศาสตร์และทางกายภาพ โดยทั่วไป เกมมักจะถูกสร้างขึ้นเพื่อลดภาระการประมวลผลมากกว่าความแม่นยํา และหากไม่มีการเบี่ยงเบนอย่างมากในพื้นที่ ก็ไม่สําคัญมากนักหากไม่ถูกต้อง
เคล็ดลับนี้อธิบายการตรวจจับการตีแบบ "บอลต่อบอล" ที่พบบ่อยที่สุดและเป็นภาระน้อยที่สุด เรากําลังพูดถึงการตัดสินการชนกันในพื้นที่สามมิติ แต่เป็นไปได้ที่จะแทนที่กระบวนการเดียวกันเกือบทั้งหมดสําหรับการชนกันระหว่างวงกลมในพื้นที่สองมิติ
การตัดสินลูกบอลและลูกบอลตีทํางานอย่างไร
ใช้พารามิเตอร์สองตัวเพื่อกําหนดการชนกันของทรงกลมและทรงกลม: "ตําแหน่ง" ของแต่ละรุ่นและ "รัศมี" ของขนาดของแบบจําลอง ในการตรวจสอบว่าพารามิเตอร์ทั้งสองนี้ถูกต้องหรือไม่มันเป็นเรื่องง่ายที่จะเข้าใจโดยดูที่รูปด้านล่าง
- 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" ซึ่งทําให้ง่ายต่อการจัดการพารามิเตอร์ของทรงกลมและการตรวจจับการตี ดังนั้นฉันจึงต้องการใช้
แต่ละ ModelMesh ของคลาส Model ที่โหลดมีข้อมูล Sphere "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;
แต่ละ ModelMesh ในแบบจําลองมีคุณสมบัติ BoundingSphere ที่ช่วยให้คุณได้รับข้อมูลเกี่ยวกับทรงกลมที่ครอบคลุมแบบจําลองใน ModelMesh เนื่องจากโมเดลที่เราใช้มี ModelMesh เดียว เราจึงได้คัดลอกทรงกลมรวมจากดัชนีแรกไปยัง baseBoundingSphere
เนื่องจากรัศมีของทรงกลมคงที่ รัศมีจึงถูกตั้งค่าไว้ล่วงหน้าในคุณสมบัติรัศมีของ BoundingSpheres ทั้งสอง
ตีคําพิพากษา
// 衝突判定用の球を設定
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 มันจะส่งคืนบูลเพื่อดูว่ามันชนกันหรือไม่ดังนั้นเราจึงเข้าใจ ธงนี้ใช้เพื่อวาดอักขระเพื่อดูว่าถูกต้องหรือไม่
วิธีคํานวณการตัดสินการตี
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);
}
}
}