Puntería

Actualización de la página :
Fecha de creación de la página :

Notas sobre estos consejos

Este ejemplo se basa en los programas publicados en los siguientes sitios. Cambio un poco el código para que sea más fácil de entender y explicar en japonés. Básicamente, usamos el código original tal cual, por lo que si realmente lo adopta en su programa de juego, corríjalo de manera oportuna y úselo.

Sitio de referencia

Además, se explica asumiendo que tiene algunos conocimientos básicos sobre MonoGame y XNA. Consulte Consejos de MonoGame y Consejos de XNA para el ruddly.

En particular, como las matemáticas, los vectores, las funciones trigonométricas, las matrices, etc. son esenciales, así que por favor sepa cuáles son hasta cierto punto.

medio ambiente

plataforma
  • Windows 10
  • El código se puede utilizar en otras plataformas habilitadas para MonoGame
Visual Studio
  • Visual Studio 2019
.NET Core
  • 3.1
MonoJuego
  • 3.8

Acerca de las muestras

La luz gira para rastrear la luz en la dirección en la que está el objetivo.

Si mueves al gato con un ratón o una llave,

La luz sigue al gato.

Cómo operar

Qué hacer con el teclado Gamepad (XInput) Mouse Touch
Movimiento del gato ↑↓←→
  • Stick izquierdo
  • DPad
Botón izquierdo Toca en cualquier lugar
Fin del juego Esc Atrás - -

Qué preparar

Imágenes que mueven el objetivo y las luces a rastrear.

programa

Descargue el programa para obtener todo el código.

constante

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

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

El movimiento del gato y el seguimiento de la luz no son instantáneos, sino que se mueven y giran fotograma a fotograma.

Por cierto, dado que el tiempo de juego no se utiliza en esta muestra, la operación de tiempo real y la velocidad pueden ser diferentes dependiendo de la plataforma. Intenta usar el tiempo de juego en el juego real que haces.

campo

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();

Básicamente, solo tienes la información para mostrar el sprite. Lo importante es _spotlightAngle tener un cálculo automático para apuntar al objetivo.

constructor

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

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

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

No hay nada que tener en cuenta aparte de las resoluciones más bajas para móviles.

Método Initialize

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;
}

Determina la posición inicial de cada imagen. Tenga en cuenta que está Viewport escribiendo código después de base.Initialize() usar .

Método LoadContent

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 Estoy creando y cargando texturas.

Además, la posición central de la imagen del gato y la posición central (eje de rotación) del foco se establecen aquí. Porque la posición central cambia en función de la imagen Se establece individualmente.

Método de actualización

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);
}

El método HandleInput maneja las operaciones del jugador, que se discutirán más adelante. La posición del gato está determinada por la entrada. TambiénViewport lo usamos para evitar que el gato aparezca en la pantalla.

TurnToFace El método gira el foco. La posición del foco y la posición del gato, el ángulo actual de la luz y la velocidad máxima de rotación ahora se determinan para determinar la orientación de la luz en el marco. Hablaremos de lo que estamos tratando más adelante.

Por cierto, no lo estoy usando en estos Consejos, pero debes usar la variable gameTime para que coincida con la velocidad del juego.

Método HandleInput

/// <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;
}

El proceso de entrada del jugador. Lo que estamos haciendo aquí es el movimiento del gato y la operación final del juego .

Los dispositivos de entrada son compatibles con una variedad de campos: teclado, gamepad, mouse, táctil .

Básicamente, solo me muevo con una tecla direccional, tocándola o apuntando al gato a la posición en la que hice clic, por lo que no entraré en demasiados detalles. Como punto de control detallado,

  • El stick se invierte porque la dirección hacia arriba es positiva, mientras que la posición de la pantalla es positiva en la dirección hacia abajo.
  • No se mueva más allá de los puntos después de que el gato alcance la posición deseada con el mouse o el tacto
  • Una vez que haya determinado la dirección de desplazamiento, normalícela para convertirla en un vector unitario (solo dirección) y finalmente multiplique la velocidad de movimiento.

Se trata de.

Método 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)
{
  // :
  // :
}

Este es el proceso principal para Tips. El proceso de determinar el ángulo para que el foco se enfrente al gato. Pase la posición del foco, la posición del gato, el ángulo actual, la velocidad máxima de rotación como argumentos. El valor devuelto es la posición de rotación final. No es la cantidad de rotación desde la posición actual.

// この図を参照してください。
// 
//      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);

Como se mencionó en los comentarios, trigonométrica (funciones trigonométricas inversas) se utiliza para calcular ángulos a partir de posiciones. Si explicas los contenidos de las matemáticas, será un consejo, por lo que debes saber que debes usar arctangente aquí. El método utilizado es Math.Atan2 . Math.Atan nótese que no lo es.

Consulte la siguiente figura para ver el valor devuelto. Devuelve un valor de -π a +π con la dirección de +x como 0. Tenga en cuenta que la dirección +y devuelve un resultado positivo, pero en las coordenadas de la ventana la parte inferior es la dirección +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);

Una vez que tenga un ángulo para apuntar el objetivo, el resto se determina el ángulo desde la posición actual hasta el ángulo deseado. Aquí tenemos la velocidad máxima que se puede girar en un cuadro, MathHelper.Clamp(difference, -turnSpeed, turnSpeed) no supera el valor máximo.

Además, si se calcula simplemente por más o menos, gira hacia atrás más allá del rango de +π y -π, por lo que lo ajusto con el método WrapAngle. Este método se describe en la siguiente sección.

Finalmente, cuando se determine el ángulo al que se gira, regrese.

Método 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;
}

Si el ángulo excede el rango de -π a +π, puede girar en la dirección opuesta a la dirección que debe girarse, Si se excede este rango, agregue o reste 2π para mantenerlo dentro del rango anterior.

Método de dibujo

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);
}

Una vez que haya calculado la posición y la rotación, todo lo que tiene que hacer es dibujar el sprite a lo largo de ese número. Para expresar al gato como si la luz fuera golpeada, se realiza una síntesis aditiva al dibujar el foco.

Resumen

Creo que es muy importante conocer esta técnica porque hay relativamente muchas situaciones en el juego en las que decides en qué dirección está tu oponente. Este método se usa comúnmente en juegos 2D, y se puede calcular usando cuaterniones en juegos 3D. Pero hay muchas cosas que usan cálculos 2D en 3D, por lo que es seguro saberlo.