「慣性」とは「動いているものは動き続けようとする」という物理において当たり前の法則のことですね。
今回はその慣性を持ったオプションを作ります。
実はグラディウスIIIには「スネークオプション」というオプションがあり、そいつが慣性を持ったような動きをするのですが・・・しかしながらこいつが非常に厄介で、まあ一言でいうと意味わからん動きをするのです。
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_csFVxpo-ltzK7nWli81Lb9dWhIBii5_QYxY8Eiy3oOCQw3w5eFbrlLtaZLu3ez7fdWGRzP7FuwpAWp7f7dm4-eK9XOBFNe5K7KN9ifxhD634uwzewF-K4KeFXMnK8Kn2_Law-12_FP0/s320/option-snake3.gif)
あまりにも思い通りに動かないので、
「このオプションを作ったのは誰だあっ!!」と
海原雄山ばりに怒鳴ることうけあい。
スネークオプションは、プレイヤーたちの間では完全に「使えない子」扱いでした・・・
だいぶ話が逸れましたが、今回は、グラディウスIIIのスネークオプションとは違って、割と素直に動くものを作ります。
サンプルコード
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Inertial
{
/// <summary>
/// This is the main type for your game.
/// </summary>
public class Game1 : Game
{
// プレイヤー移動速度
const float PlayerSpeed = 7.0f;
// オプションの距離
const float OptionDistance = 100f;
// 減衰率
const float Dump = 0.95f;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// ぱっちぃのテクスチャ
Texture2D texturePacchi;
// かまトゥのテクスチャ
Texture2D textureKamatoo;
// プレイヤーの位置
Vector2 playerPosition = new Vector2(200, 200);
// オプションの位置
Vector2 optionPosition;
// オプションの速度
Vector2 optionVelocity;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
// 画像を読み込む
texturePacchi = Content.Load<Texture2D>("pacchi_32");
textureKamatoo = Content.Load<Texture2D>("kamatoo_32");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// game-specific content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
// 上下左右が押されたらプレイヤーを移動させる
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
playerPosition.X -= PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
playerPosition.X += PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
playerPosition.Y -= PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
playerPosition.Y += PlayerSpeed;
}
// オプションの移動前の位置をとっておく
Vector2 prevOptionPosition = optionPosition;
// 速度の分だけ移動させる
optionPosition += optionVelocity;
// オプションが親から離れている場合、位置補正(範囲内へのひっぱり)が必要。
if (Vector2.Distance(optionPosition, playerPosition) > OptionDistance)
{
// 親から見たオプションへの方向ベクトルを算出
Vector2 direction = optionPosition - playerPosition;
// 正規化(長さを1に)
direction.Normalize();
// 位置を補正(ひっぱり)
optionPosition = playerPosition + direction * OptionDistance;
// 今回移動した量を速度として保持しておく
optionVelocity = optionPosition - prevOptionPosition;
// 速度を減衰させる。そうしないと、グワングワン回転して動いてしまう
optionVelocity *= Dump;
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
// オプションを描画
spriteBatch.Draw(textureKamatoo, optionPosition, Color.White);
// プレイヤーを描画
spriteBatch.Draw(texturePacchi, playerPosition, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8jXhHPj70I-4DYa3lSobUj0kLTeTxAmfk0X8mXx1wsFmLgsS1TA9H84_1jFMtOmnQqbf8JvcFN7hlR7zRpfKjG3-kFMoYYmB9xNSZMoEgsUJcFRj9QbLd1qwl9pC6AZ3ubyWyHiWzpTY/s1600/option-inertial1.gif)
解説
ある程度離れたら引っ張ってくるという点において、引きずりオプションのときとかなり似てます。
今回は、引っ張った際に、勢い(velocity)を保存して、その後も勢いによって動き続けるようにしています。
また、減衰率(Dump)によって、勢いを弱める工夫もしています。Dump=0.95なので、毎フレーム、勢いが95%に減っていき、そのうち止まります。これが無いとぐわんぐわん動いて大変です(試しにDumpをかけるのをやめてみればわかります)。
オプションを増やすともっと面白い
上記はコードを単純にするために、オプションの個数が1つですが、オプションの数が多いと、もっと面白いです。![](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAO3fsn5krYslhDrMsXBk3Vq9weNNYQ82h6Dhb4tySiX3_90IzBOHCAWh1eXaIUkaEhu_lB2qs-SuiG3WnxzkB3fhJbbxryHoY0I2Xhn7Cw9KkckyRYO1a2UIx59dLyX85dAQcjncPc8Y/s1600/option-inertial5.gif)
かなり自由自在に動かせます。振り回すこともできます。
グラディウスIIIのスネークオプションもこれくらい素直だったら・・・と思いましたが、ここまで自由に動かせると、強すぎてバランス崩壊かも😅
いやー、しかし、グラディウスIIIは難しすぎてバランス崩壊してるから、これくらい強い味方がいて、トントンかな!?
話がだいぶ逸れました。
オプションの個数を増やすのにぜひチャレンジしてみてください。
なるべく自力でチャレンジしてみてほしいですが、一応、サンプルコードも載せておきます。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
namespace InertialMultiple
{
/// <summary>
/// This is the main type for your game.
/// </summary>
public class Game1 : Game
{
// プレイヤー移動速度
const float PlayerSpeed = 7.0f;
// オプションの距離
const float OptionDistance = 50f;
// 減衰率
const float Dump = 0.93f;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// ぱっちぃのテクスチャ
Texture2D texturePacchi;
// かまトゥのテクスチャ
Texture2D textureKamatoo;
// プレイヤーの位置
Vector2 playerPosition = new Vector2(200, 200);
// オプションの位置(複数個分)
List<Vector2> optionPositions = new List<Vector2>();
// オプションの速度(複数個分)
List<Vector2> optionVelocities = new List<Vector2>();
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
// オプションの情報を5個分用意
for (int i = 0; i < 5; i++)
{
optionPositions.Add(playerPosition);
optionVelocities.Add(Vector2.Zero);
}
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
// 画像を読み込む
texturePacchi = Content.Load<Texture2D>("pacchi_32");
textureKamatoo = Content.Load<Texture2D>("kamatoo_32");
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// game-specific content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// TODO: Add your update logic here
// 上下左右が押されたらプレイヤーを移動させる
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
playerPosition.X -= PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
playerPosition.X += PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Up))
{
playerPosition.Y -= PlayerSpeed;
}
if (Keyboard.GetState().IsKeyDown(Keys.Down))
{
playerPosition.Y += PlayerSpeed;
}
for (int i = 0; i < optionPositions.Count; i++)
{
// オプションの移動前の位置をとっておく
Vector2 prevOptionPosition = optionPositions[i];
// 速度の分だけ移動させる
optionPositions[i] += optionVelocities[i];
// 親の位置。最初のオプションの親はプレイヤー。それ以外のオプションは自分のひとつ前のオプション
Vector2 parentPosition = i == 0 ? playerPosition : optionPositions[i - 1];
// オプションが親から離れている場合、位置補正(範囲内へのひっぱり)が必要。
if (Vector2.Distance(optionPositions[i], parentPosition) > OptionDistance)
{
// 親から見たオプションへの方向ベクトルを算出
Vector2 direction = optionPositions[i] - parentPosition;
// 正規化(長さを1に)
direction.Normalize();
// 位置を補正(ひっぱり)
optionPositions[i] = parentPosition + direction * OptionDistance;
// 今回移動した量を速度として保持しておく
optionVelocities[i] = optionPositions[i] - prevOptionPosition;
// 速度を減衰させる。そうしないと、グワングワン回転して動いてしまう
optionVelocities[i] *= Dump;
}
}
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
// オプションを描画
for (int i = 0; i < optionPositions.Count; i++)
{
spriteBatch.Draw(textureKamatoo, optionPositions[i], Color.White);
}
// プレイヤーを描画
spriteBatch.Draw(texturePacchi, playerPosition, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
オプションのpositionとvelocityをListにしています。
なるべく手間をかけずにオプションを複数化するために、こうしましたが、普通はこういう場合は、オプションをクラスにして、その中にpositionやvelocityを持たせます。
色んなオプションを作る目次へ戻る
0 件のコメント:
コメントを投稿