เล็ง

ปรับปรุงหน้า :
วันที่สร้างเพจ :

หมายเหตุเกี่ยวกับเคล็ดลับนี้

ตัวอย่างนี้อ้างอิงจากโปรแกรมที่ประกาศบนเว็บไซต์ต่อไปนี้ ฉันเปลี่ยนรหัสเล็กน้อยเพื่อให้ง่ายต่อการเข้าใจและอธิบายเป็นภาษาญี่ปุ่น โดยทั่วไปเราใช้รหัสเดิมตามที่เป็นอยู่ดังนั้นหากคุณนํามาใช้ในโปรแกรมเกมของคุณให้แก้ไขในเวลาที่เหมาะสมและใช้งานได้

ไซต์อ้างอิง

นอกจากนี้ยังมีการอธิบายบนสมมติฐานว่าคุณมีความรู้พื้นฐานเกี่ยวกับ MonoGame และ XNA ดู เคล็ดลับ MonoGame และ เคล็ดลับ XNA สําหรับ ruddly

โดยเฉพาะอย่างยิ่ง, คณิตศาสตร์, เวกเตอร์, ฟังก์ชันตรีโกณมิติ, เมทริกซ์, ฯลฯ เป็นสิ่งจําเป็น, ดังนั้นโปรดรู้ว่ามันคืออะไรในระดับหนึ่ง

สิ่งแวดล้อม

แท่น
  • วินโดวส์ 10
  • สามารถใช้รหัสบนแพลตฟอร์มอื่น ๆ ที่เปิดใช้งาน MonoGame
Visual Studio
  • Visual Studio 2019
.NET Core
  • 3.1
โมโนเกม
  • 3.8

เกี่ยวกับตัวอย่าง

แสงหมุนเพื่อติดตามแสงในทิศทางที่เป้าหมายเป็น

หากคุณย้ายแมวด้วยเมาส์หรือกุญแจ

แสงตามแมว

วิธีการใช้งาน

เกมคืออะไร
คีย์บอร์ดแพด (XInput) เมาส์สัมผัส
การเคลื่อนไหวของแมว ↑↓←→
  • ไม้ซ้าย
  • ดีแพด
ปุ่ม ซ้าย สัมผัสได้ทุกที่
จบเกม Esc ย้อนกลับ - -

สิ่งที่ต้องเตรียม

ภาพที่ย้ายเป้าหมายและไฟเพื่อติดตาม

โปรแกรม

ดาวน์โหลดโปรแกรมสําหรับรหัสทั้งหมด

ค่าคงที่

/// <summary>猫が動くスピード。これはフレームあたりのピクセル数です。</summary>
const float CatSpeed = 10.0f;

/// <summary>スポットライトが回転する速度。これはフレームあたりのラジアンで表されます。</summary>
const float SpotlightTurnSpeed = 0.025f;

การเคลื่อนไหวของแมวและการติดตามแสงไม่ได้เกิดขึ้นทันที แต่ย้ายและหมุนเฟรมตามเฟรม

โดยวิธีการเนื่องจากเวลาเกมไม่ได้ใช้ในตัวอย่างนี้การทํางานเวลาจริงและความเร็วอาจแตกต่างกันขึ้นอยู่กับแพลตฟอร์ม พยายามใช้เวลาเล่นเกมในเกมจริงที่คุณทํา

สนาม

readonly GraphicsDeviceManager _graphics;

/// <summary>画像を表示するための SpriteBatch です。</summary>
SpriteBatch _spriteBatch;

/// <summary>スポットライトのテクスチャー(画像)です。</summary>
Texture2D _spotlightTexture;

/// <summary>スポットライトの位置です。</summary>
Vector2 _spotlightPosition = new Vector2();

/// <summary>スポットライトの中心位置です、ここを中心に回転します。</summary>
Vector2 _spotlightOrigin = new Vector2();

/// <summary>スポットライトが現在向いている角度。単位はラジアンです。値 0 は右を指します。</summary>
float _spotlightAngle = 0.0f;


/// <summary>猫のテクスチャー(画像)です。</summary>
Texture2D _catTexture;

/// <summary>猫の位置です。</summary>
Vector2 _catPosition = new Vector2();

/// <summary>猫の中心位置です。</summary>
Vector2 _catOrigin = new Vector2();

โดยทั่วไปคุณเพียงแค่มีข้อมูลที่จะแสดงสไปรท์ สิ่งสําคัญคือ _spotlightAngle การคํานวณโดยอัตโนมัติเพื่อชี้ไปที่เป้าหมาย

ผู้สร้าง

public AimingGame()
{
  graphics = new GraphicsDeviceManager(this);
  ontent.RootDirectory = "Content";
  sMouseVisible = true;

  graphics.PreferredBackBufferWidth = 320;
  graphics.PreferredBackBufferHeight = 480;

  // フルスクリーンにしたい場合はコメントを外してください。
  //graphics.IsFullScreen = true;
}

ไม่มีอะไรต้องจําไว้นอกเหนือจากความละเอียดที่ต่ํากว่าสําหรับมือถือ

เริ่มต้นวิธีการ

protected override void Initialize()
{
  base.Initialize();

  // base.Initialize が完了すると、GraphicsDevice が作成され、ビューポートの大きさがわかります。
  // スポットライトを画面の中央に配置する必要があるため、ビューポートを使用してそれがどこにあるかを計算します。
  Viewport vp = _graphics.GraphicsDevice.Viewport;
  _spotlightPosition.X = vp.X + vp.Width / 2;
  _spotlightPosition.Y = vp.Y + vp.Height / 2;

  // もう一度ビューポートサイズを使用して、今度は猫を画面に配置します。位置は x=1/4 y=1/2 です。
  _catPosition.X = vp.X + vp.Width / 4;
  _catPosition.Y = vp.Y + vp.Height / 2;
}

กําหนดตําแหน่งเริ่มต้นของแต่ละภาพ โปรดทราบว่าคุณกําลัง Viewport เขียนรหัสหลังจาก base.Initialize() ใช้

วิธีการโหลดคอนเทนต์

protected override void LoadContent()
{
  // テクスチャをロードし、スプライトバッチを作成します。
  _spotlightTexture = Content.Load<Texture2D>("spotlight");
  _catTexture = Content.Load<Texture2D>("cat");
  _spriteBatch = new SpriteBatch(_graphics.GraphicsDevice);

  // テクスチャをロードしたので、それらを使用して、描画時に使用するいくつかの値を計算できます。
  // スポットライトを描くときは、光源の周りを回転する必要があります。
  // 今回用意した画像は左中央が光源なのでその位置を中心位置として設定します。
  _spotlightOrigin.X = 0;
  _spotlightOrigin.Y = _spotlightTexture.Height / 2;

  // 猫の中心位置を決定します。とりあえず画像の真ん中とします。
  _catOrigin.X = _catTexture.Width / 2;
  _catOrigin.Y = _catTexture.Height / 2;
}

SpriteBatch ฉันกําลังสร้างและโหลดพื้นผิว

นอกจากนี้ตําแหน่งกึ่งกลางของภาพแมวและตําแหน่งกึ่งกลาง (แกนหมุน) ของสปอตไลท์ถูกตั้งค่าไว้ที่นี่ เนื่องจากตําแหน่งกึ่งกลางจะเปลี่ยนไปตามภาพ มันถูกตั้งค่าเป็นรายบุคคล

วิธีการอัพเดต

protected override void Update(GameTime gameTime)
{
  HandleInput();

  // 猫が画面外に出ないように制御します。
  Viewport vp = _graphics.GraphicsDevice.Viewport;
  _catPosition.X = MathHelper.Clamp(_catPosition.X, vp.X, vp.X + vp.Width);
  _catPosition.Y = MathHelper.Clamp(_catPosition.Y, vp.Y, vp.Y + vp.Height);

  // TurnToFace 関数を使用して、_spotlightAngle を更新して猫の方を向くようにします。
  _spotlightAngle = TurnToFace(_spotlightPosition, _catPosition, _spotlightAngle, SpotlightTurnSpeed);

  base.Update(gameTime);
}

วิธี HandleInput จะจัดการการดําเนินงานของผู้เล่นซึ่งจะกล่าวถึงในภายหลัง ตําแหน่งของแมวจะถูกกําหนดโดยการป้อนข้อมูล นอกจากนี้เรายังViewport ใช้เพื่อป้องกันไม่ให้แมวปรากฏบนหน้าจอ

TurnToFace วิธีการหมุนสปอตไลต์ ตําแหน่งของสปอตไลท์และตําแหน่งของแมวมุมปัจจุบันของแสงและความเร็วในการหมุนสูงสุดจะถูกกําหนดเพื่อกําหนดการวางแนวของแสงในเฟรม เราจะพูดถึงเรื่องที่เราคุยกันทีหลัง

โดยวิธีการที่ฉันไม่ได้ใช้มันในเคล็ดลับนี้ แต่คุณต้องใช้ตัวแปร gameTime เพื่อให้ตรงกับความเร็วของเกม

วิธีจัดการอินพุท

/// <summary>
/// 入力を処理します。
/// </summary>
void HandleInput()
{
  KeyboardState currentKeyboardState = Keyboard.GetState();
  GamePadState currentGamePadState = GamePad.GetState(PlayerIndex.One);
  MouseState currentMouseState = Mouse.GetState();
  TouchCollection currentTouchState = TouchPanel.GetState();

  // ゲーム終了操作を確認します。
  if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
      currentGamePadState.Buttons.Back == ButtonState.Pressed)
  {
    Exit();
  }

  // ユーザーが猫を動かしたいかどうかを確認します。 catMovement というベクトルを作成します。
  // これは、すべてのユーザーの入力の合計を格納します。
  Vector2 catMovement = currentGamePadState.ThumbSticks.Left;

  // y を反転:スティックでは、下は -1 ですが、画面では、下がプラスです。
  catMovement.Y *= -1;

  if (currentKeyboardState.IsKeyDown(Keys.Left) ||
      currentGamePadState.DPad.Left == ButtonState.Pressed)
  {
    catMovement.X -= 1.0f;
  }
  if (currentKeyboardState.IsKeyDown(Keys.Right) ||
      currentGamePadState.DPad.Right == ButtonState.Pressed)
  {
    catMovement.X += 1.0f;
  }
  if (currentKeyboardState.IsKeyDown(Keys.Up) ||
      currentGamePadState.DPad.Up == ButtonState.Pressed)
  {
    catMovement.Y -= 1.0f;
  }
  if (currentKeyboardState.IsKeyDown(Keys.Down) ||
      currentGamePadState.DPad.Down == ButtonState.Pressed)
  {
    catMovement.Y += 1.0f;
  }

  // タッチポイントに向かって移動します。
  // CatSpeed からタッチポイントまでの距離内に入ると、猫の速度を落とします。
  float smoothStop = 1;

  //if (currentTouchState != null )
  {
    if (currentTouchState.Count > 0)
    {
      Vector2 touchPosition = currentTouchState[0].Position;
      if (touchPosition != _catPosition)
      {
        catMovement = touchPosition - _catPosition;
        float delta = CatSpeed - MathHelper.Clamp(catMovement.Length(), 0, CatSpeed);
        smoothStop = 1 - delta / CatSpeed;
      }
    }
  }

  Vector2 mousePosition = new Vector2(currentMouseState.X, currentMouseState.Y);
  if (currentMouseState.LeftButton == ButtonState.Pressed && mousePosition != _catPosition)
  {
    catMovement = mousePosition - _catPosition;
    float delta = CatSpeed - MathHelper.Clamp(catMovement.Length(), 0, CatSpeed);
    smoothStop = 1 - delta / CatSpeed;
  }

  // ユーザーの入力を正規化して、猫が CatSpeed より速く進むことができないようにします。
  if (catMovement != Vector2.Zero)
  {
    catMovement.Normalize();
  }

  _catPosition += catMovement * CatSpeed * smoothStop;
}

กระบวนการป้อนข้อมูลของผู้เล่น สิ่งที่เรากําลังทําที่นี่คือการย้ายแมวและจุดสิ้นสุดของการดําเนินการเกม

อุปกรณ์อินพุตได้รับการสนับสนุนในหลากหลายเขตข้อมูล: แป้นพิมพ์, เกมแพด, เมาส์, ระบบสัมผัส

โดยทั่วไปฉันแค่เคลื่อนไหวด้วยปุ่มทิศทางสัมผัสหรือชี้แมวไปที่ตําแหน่งที่ฉันคลิกดังนั้นฉันจะไม่ลงรายละเอียดมากเกินไป เป็นจุดควบคุมโดยละเอียด

  • ไม้จะกลับด้านเนื่องจากทิศทางขึ้นเป็นบวกในขณะที่ตําแหน่งหน้าจอเป็นบวกในทิศทางลง
  • อย่าเคลื่อนที่เกินจุดหลังจากที่แมวถึงตําแหน่งที่ต้องการด้วยเมาส์หรือสัมผัส
  • เมื่อคุณกําหนดทิศทางการเดินทางแล้ว ให้ปรับให้เป็นปกติเพื่อให้เป็นเวกเตอร์หน่วย (ทิศทางเท่านั้น) และในที่สุดก็คูณความเร็วในการเคลื่อนที่

มันมาถึงแล้ว

วิธี TurnToFace

/// <summary>
/// オブジェクトの位置、ターゲットの位置、現在の角度、および最大回転速度を指定して、
/// オブジェクトが直面する必要のある角度を計算します。
/// </summary>
/// <param name="position">オブジェクトの位置。ここではスポットライトの位置。</param>
/// <param name="faceThis">ターゲットの位置。ここでは猫の位置。</param>
/// <param name="currentAngle">現在の角度。</param>
/// <param name="turnSpeed">最大回転速度。</param>
/// <returns>決定された角度。</returns>
private static float TurnToFace(Vector2 position, Vector2 faceThis, float currentAngle, float turnSpeed)
{
  // :
  // :
}

นี่เป็นกระบวนการหลักสําหรับเคล็ดลับ กระบวนการกําหนดมุมเพื่อให้สปอตไลท์หันหน้าเข้าหาแมว ผ่านตําแหน่งสปอตไลท์, ตําแหน่งแมว, มุมปัจจุบัน, ความเร็วในการหมุนสูงสุดเป็นอาร์กิวเมนต์ ค่าที่ส่งกลับคือตําแหน่งการหมุนขั้นสุดท้าย ไม่ใช่จํานวนการหมุนจากตําแหน่งปัจจุบัน

// この図を参照してください。
// 
//      C 
//     /|
//    / |
//   /  | y
//  / o |
// S----
//     x
// 
// ここで、S はスポットライトの位置、C は猫の位置、o は猫を指すためにスポットライトが向いている角度です。
// o の値を知る必要があります。
// これには三角法を使用して算出します。
// 
//      tan(theta)       = 高さ / 底辺
//      tan(o)           = y / x
// 
// この方程式の両辺のアークタンジェントを取ると
// 
//      arctan( tan(o) ) = arctan( y / x )
//      o                = arctan( y / x )
// 
// したがって、x と y を使用して、「desiredAngle」である o を見つけることができます。
// x と y は、2つのオブジェクト間の位置の違いにすぎません。
float x = faceThis.X - position.X;
float y = faceThis.Y - position.Y;

// Atan2 関数を使用します。Atanは、y / x のアークタンジェントを計算し、x と y の符号を使用して、
// 結果を入れるデカルト象限を決定するという追加の利点があります。
// https://docs.microsoft.com/dotnet/api/system.math.atan2
float desiredAngle = (float)Math.Atan2(y, x);

ดังที่ได้กล่าวไว้ในความคิดเห็นตรีโกณมิติ (ฟังก์ชันตรีโกณมิติผกผัน) ใช้ในการคํานวณมุมจากตําแหน่ง หากคุณอธิบายเนื้อหาของคณิตศาสตร์มันจะเป็นเคล็ดลับดังนั้นคุณควรรู้ว่าคุณควรใช้อาร์กแทนเจนต์ที่นี่ วิธีการที่ใช้คือ Math.Atan2 Math.Atan โปรดทราบว่าไม่ใช่

ดูรูปต่อไปนี้สําหรับค่าที่ส่งกลับ ส่งกลับค่า -π ถึง +π โดยมีทิศทางเป็น +x เป็น 0 โปรดสังเกตว่าทิศทาง +y จะส่งกลับผลลัพธ์ที่เป็นบวก แต่ที่พิกัดหน้าต่าง ด้านล่างคือทิศทาง +y

// これで猫を向くために必要な設定角度がわかりました。turnSpeed (回転スピード) に制約されていなければ簡単です。
// desiredAngle を返すだけです。
// 代わりに回転量を計算し、それが turnSpeed を超えないようにする必要があります。

// まず、WrapAngle を使用して、-Pi から Pi(-180度から180度)の結果を取得し、
// どれだけ回転させたいかを判断します。
// これは猫の方向に向くのに必要な回転角度です。
float difference = WrapAngle(desiredAngle - currentAngle);

// -turnSpeed と turnSpeed の間にクランプします。
// 要は1フレームの回転角度上限を超えないようにします。
difference = MathHelper.Clamp(difference, -turnSpeed, turnSpeed);

// したがって、ターゲットに最も近いのは currentAngle + difference です。
// もう一度 WrapAngle を使用して、それを返します。
return WrapAngle(currentAngle + difference);

เมื่อคุณมีมุมที่จะชี้เป้าหมายส่วนที่เหลือจะถูกกําหนดมุมจากตําแหน่งปัจจุบันไปยังมุมที่ต้องการ ที่นี่เรามีความเร็วสูงสุดที่สามารถหมุนได้ในเฟรมเดียว MathHelper.Clamp(difference, -turnSpeed, turnSpeed) ไม่เกินค่าสูงสุด

นอกจากนี้หากคํานวณโดยบวกหรือลบมันหมุนไปข้างหลังเกินช่วงของ +π และ -πดังนั้นฉันจึงปรับด้วยวิธีการ WrapAngle วิธีการนี้อธิบายไว้ในส่วนถัดไป

ในที่สุดเมื่อกําหนดมุมที่หมุนแล้วให้กลับมา

วิธีการห่อหุ้ม

/// <summary>
/// -Pi と Pi の間のラジアンで表される角度を返します。
/// 例えば degree で -200°なら +360°して 160°とします。反対側も同様です。
/// </summary>
private static float WrapAngle(float radians)
{
  while (radians < -MathHelper.Pi)
  {
    radians += MathHelper.TwoPi;
  }
  while (radians > MathHelper.Pi)
  {
    radians -= MathHelper.TwoPi;
  }
  return radians;
}

ถ้ามุมเกินช่วง -π ถึง +π, มุมอาจหมุนไปในทิศทางตรงกันข้ามกับทิศที่ควรหมุน ถ้าช่วงนี้เกินให้บวกหรือลบ 2π เพื่อให้อยู่ภายในช่วงข้างต้น

วิธีการวาด

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice device = _graphics.GraphicsDevice;

  device.Clear(Color.Black);

  // 猫を描画します。
  _spriteBatch.Begin();
  _spriteBatch.Draw(_catTexture, _catPosition, null, Color.White, 0.0f, _catOrigin, 1.0f, SpriteEffects.None, 0.0f);
  _spriteBatch.End();

  // 加算合成でスプライトバッチを開始し、スポットライトを当てます。 加算合成は、ライトや火などの効果に非常に適しています。
  _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
  _spriteBatch.Draw(_spotlightTexture, _spotlightPosition, null, Color.White, _spotlightAngle, _spotlightOrigin, 1.0f, SpriteEffects.None, 0.0f);
  _spriteBatch.End();

  base.Draw(gameTime);
}

เมื่อคุณคํานวณตําแหน่งและการหมุนแล้ว สิ่งที่คุณต้องทําคือวาดสไปรท์ตามจํานวนนั้น เพื่อแสดงแมวราวกับว่าแสงถูกตีการสังเคราะห์สารเติมแต่งจะดําเนินการเมื่อวาดสปอตไลท์

สรุป

ฉันคิดว่ามันสําคัญมากที่จะต้องรู้เกี่ยวกับเทคนิคนี้เพราะมีหลายสถานการณ์ในเกมที่คุณตัดสินใจว่าฝ่ายตรงข้ามของคุณอยู่ในทิศทางใด วิธีนี้ใช้กันทั่วไปในเกม 2 มิติและสามารถคํานวณได้โดยใช้ quaternions ในเกม 3 มิติ แต่มีหลายสิ่งหลายอย่างที่ใช้การคํานวณ 2 มิติในรูปแบบ 3 มิติดังนั้นจึงปลอดภัยที่จะรู้