1月, 2012 のアーカイブ

今回は、HPゲージの実装方法についてメモります。HPゲージと書きましたが、ゲージ系統なら数字を弄るだけで何用にでも出来ます(進行率、パワー、等)。

こんな感じになります:

 

今回の実装方法は、ゲージの量により、描画する画像の横幅を決め、その上に外枠の画像(ゲージの四角い線が描いてある画像)を書きます。これにより、見かけ的には、ゲージの中身が減っていっているように見えます(実際に減っている場合のみですが)。使う画像は、このような感じです:

ゲージの量を元に描画する横幅を計算するには、以下の式を使います:

横幅 = (現在の値 / 最高値) * 外枠の横幅

 

これをHPゲージに置き換えるとこうなります。

横幅 = (残りHP / HP最大値) * 外枠の横幅

 

外枠の横幅は、わざとゲージ外にはみ出したいとき意外は、横幅の最大値なので、これが計算に使われています。

(現在の値 / 最高値)で得られる答えは、0.0f~1.0fの値になり、これは全体の何%を表すか示しています(0.0fが0%、1.0fが100%)。これに横幅の最大値を掛けることにより、横幅の何%で描画すればいいかわかります。(0.5fの場合は横幅の50%, つまり横幅が100の場合 100 * 0.5fで50になり、ゲージの中身は外枠の50%だと言うのがわかります。)

 

では実装方法です。まずは、ゲージクラスの変数宣言とコンストラクタです。

class Gauge
{
Texture2D m_backgroundTexture;
Texture2D m_pixel;
float m_maxValue;
public float m_currentValue;
float m_width;
Rectangle m_bounds;
Color m_colour;

public Gauge(Texture2D backgroundTex, Texture2D pixel, Rectangle bounds, float startAmount, float maxValue, float width, Color colour)
{
m_backgroundTexture = backgroundTex;
m_pixel = pixel;
m_bounds = bounds;
m_width = width;
m_maxValue = maxValue;
m_currentValue = startAmount;
m_colour = colour;
}

backgroundTexといのが、外枠の画像で、pixelといのは 1×1の白い画像です。これを引き伸ばしてゲージの中身にします。

次に描画処理です。ゲージの中身の計算もココでします。

public void Draw(SpriteBatch sp)
{
// ゲージの量を計算
int width = (int)((m_currentValue / m_maxValue) * m_width);

// ゲージの中身を描画
sp.Draw(m_pixel, new Rectangle(m_bounds.X, m_bounds.Y, width, m_bounds.Height), m_colour);

// 四角い背景描画
sp.Draw(m_backgroundTexture, m_bounds, Color.White);
}

これで後はゲージクラスを使うだけです。

<pre>protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

// ゲージの初期化
Texture2D border = Content.Load<Texture2D>("gauge");
Texture2D pixel = Content.Load<Texture2D>("pixel");
int width = 350;
Rectangle bounds = new Rectangle(100, 100, width, 50);

hpGauge = new Gauge(border, pixel, bounds, 100, 100, width, Color.LightGreen);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
hpGauge.Draw(spriteBatch);
spriteBatch.End();

base.Draw(gameTime);
}

以上です。この上にゲージクラス内のm_currentValueを減らすと、ゲージが減っているように見えます。

金曜日から昨日の夜までNII会場でGlobal Game Jamに参加してきました!

Global Game Jamは与えられたテーマにそったゲームを48時間(NII会場は46時間でしたが)で開発するイベントです。

チームは主催側に決められ、今回は結構運に恵まれました。ちなみに、今回のチームでの最大の幸運はUnityを使わずにすんだ事でした :p

今回はテーマがオロボロスということで、Qixのようなゲームに追加要素を加えたゲームを開発しました。ちなみに、オロボロスをっぽいところは、空間を取る時に、自分の尻尾に噛み付ところと、ゲーム自体がほぼ無限に続けられるからです。

 

動画:

 

ゲームは本家ホームページ内でのゲーム紹介ページでダウンロード出来ます。

 – http://globalgamejam.org/2012/circle-ouroboros

 

===気が向いたら追記します===

今回は、2DのSTGなどによくある流れる背景の作り方をメモります。

実装方法は基本的に:

–       背景を毎フレーム移動

–       もし、背景の位置がテクスチャアの高さを超えたら一番上までもどす

–       描画時には、一回現在地で描画し、その後描画した背景の上にもう一回描画する

を繰り返すことです。背景を2回描画することによって背景が途切れないように見えます(2回目の描画は一回目の真上に描画されるため、一つの大きな画像に見えます)。

実装するにはまず、クラスと必要な変数を宣言します

class ScrollingBackground
{
Texture2D m_texture; // 背景
Vector2 m_position;  // 背景の位置
Vector2 m_textureOrigin; // テクスチャアの原点
float m_scrollSpeed; // 背景を流す速度
int m_screenHeight; // 画面の高さ

次に、コンストラクタを実装します。テクスチャアの原点はわざと真ん中の天辺になるようにずらしてますが、これは楽だったからこう実装しただけなので、他の部分の変更も忘れなければ、自由に変更してください。スタート時の位置も同じです。

public ScrollingBackground(Texture2D texture, int screenHeight, float scrollSpeed)
{
m_scrollSpeed = scrollSpeed;
m_screenHeight = screenHeight;
m_texture = texture;

// 原点を、画像天辺の真ん中にする
m_textureOrigin = new Vector2(texture.Width / 2, 0);

// スタート地点
m_position = new Vector2(m_textureOrigin.X, screenHeight / 2);
}

次に、更新処理を実装します。これはシンプルに上で書いたように背景を移動し、ある位置を越えたら上に戻すだけです。

public void Update(float delta)
{
// 背景の移動
m_position.Y += m_scrollSpeed * delta;

// もし背景が画面から外れたら、上に移動
if (m_position.Y >= m_texture.Height)
{
m_position.Y = 0.0f;
}
}

ちなみに、このif文は`%`オペレーターを使うことで実装することも出来ます:

public void Update(float delta)
{
// 背景の移動
m_position.Y += m_scrollSpeed * delta;

// もし背景が画面から外れたら、上に移動
m_position.Y = m_position.Y % m_texture.Height;
}

後は、描画処理です。上で書いたように必要がある場合は、2回描画します。

public void Draw(SpriteBatch sp)
{
if (m_position.Y < m_screenHeight)
{
// まだ画面内にある場合、描画する
sp.Draw(m_texture, m_position, null, Color.White, 0.0f, m_textureOrigin, 1.0f, SpriteEffects.None, 0.0f);
}

// 先に描画した背景の上にもう1回描画する
sp.Draw(m_texture, m_position - new Vector2(0, m_texture.Height), null, Color.White, 0.0f, m_textureOrigin, 1.0f, SpriteEffects.None, 0.0f);
}

これで後はGame1クラス(もしくは別の背景を使うクラス)内でインスタンスを作り、Update()とDraw()を呼ぶだけです。

ScrollingBackground background;

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

Texture2D texture = Content.Load<Texture2D>("back");
background = new ScrollingBackground(texture, graphics.PreferredBackBufferHeight, 64.0f);
}

protected override void Update(GameTime gameTime)
{
background.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();
background.Draw(spriteBatch);
spriteBatch.End();

base.Draw(gameTime);
}

以上です。実装後はこんな感じになります(つまらない動画ですが):

前に書いたGameComponentのメモの続きです。

今回は、Game1登録したらUpdate()だけでなくDraw()メソッドも自動で呼ばれるGameComponentの作り方を乗せます。

XNAには描画も出来るGameComponentが既に備わっています。GameComponentクラスの代わりに、DrawableGameComponentクラスを継承するとLoadContent()メソッドとDraw()メソッドをオーバーライドできます。Initialize()とUpdate()同様Game1内のbase.メソッド名()で順番に呼ばれます。

 

今回は前に作ったFPSカウンターのクラスで実装例を書きます。まず、FPSカウンターのクラスで、DrawableGameComponentクラスを継承し、コンストラクタも書きます。

class FPSCounter : DrawableGameComponent
{
float m_fps;    // 実際のFPS
float m_interval; // FPSの更新速度(殆どの場合は1秒)
float m_updateTimer; // 更新するまでを計るタイマー
int m_frameCount; // 現在のフレーム数

SpriteBatch m_spriteBatch;
SpriteFont m_font;

public FPSCounter(Game game)
: base(game)
{
m_fps = 0.0f;
m_interval = 1.0f;
m_updateTimer = 0.0f;
m_frameCount = 0;
}

次に、LoadContent()メソッドをオーバーライドします。

protected override void LoadContent()
{
m_spriteBatch = new SpriteBatch(Game.GraphicsDevice);
m_font = Game.Content.Load<SpriteFont>("Font");
base.LoadContent();
}

そして、Draw()メソッドをオーバーライドします。

public override void Draw(GameTime gameTime)
{
// フレーム数を増やす(目標は1秒に60回)
m_frameCount++;

// タイマーに前のフレームから過ぎた時間を加算する
m_updateTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;

// タイマーが1秒を超えたら
if (m_updateTimer > m_interval)
{
// FPSを計算する, 速度が下がっていた場合はここで差を計算する
m_fps = m_frameCount / m_updateTimer;

// タイマーとカウンターをリセットする
m_frameCount = 0;
m_updateTimer -= m_interval;
}

// FPSの数値を描画する
m_spriteBatch.Begin();
m_spriteBatch.DrawString(m_font, string.Format("FPS: {0:F2}", m_fps), new Vector2(0, 0), Color.Black);
m_spriteBatch.DrawString(m_font, string.Format("FPS: {0:F2}", m_fps), new Vector2(1, 1), Color.White);
m_spriteBatch.End();

base.Draw(gameTime);
}
} // クラス実装終了

今回は紹介していませんが、GameComponent同様Initialize()とUpdate()メソッドもオーバーライド出来ます。

最後に、Game1クラス内で、FPSカウンターを登録します。

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";

// Inputクラスをコンポーネントとして、登録する
Components.Add(new Input(this));

// FPSクラスを描画できるコンポーネントとして、登録する
Components.Add(new FPSCounter(this));
}

これで、Game1でDraw()メソッドを呼ばなくても、自動的にFPSが描画されます(SpriteFontは事前にプロジェクトに追加しましたので、そのままコピペしても動きません)。

今回は以上です。

今回は、SpritBatch使用する際の、速度についてメモります。

SpritBatch使用しての2D画像の描画は板ポリゴン(二つの平べったい3角形をくっ付けている四角いポリゴン)を使って描画されています。

グラフィックカードは性質上、何も邪魔が入らずに一気に大量のポリゴンを描画する事を好みます。ですが、描画のたびに画像を変更するとポリゴンの描画に邪魔が入り、その分遅くなります。

そこで、大きいテクスチャアに大量の中身を保存し、それを使用して部分的に描画した方が早くなります。

例を言うと、アニメーションがあるテクスチャアは、1コマずつ別の画像に保存するより、一つのテクスチャアにまとめている事が多いですが、それは使いやすさだけではなく、パフォーマンスにも良いからです(意図しているかどうかは別ですが)。

このように:

====== 追記+変更予定 (もしくは別のメモで)=====