非整数の速度値-これを行うよりクリーンな方法はありますか?


21

多くの場合、ピクセルベースのゲームでキャラクターを動かすために、2.5などの速度値を使用します。ただし、衝突検出は一般的に私が行うとより困難になります。だから私はこのようなことをすることになります:

moveX(2);
if (ticks % 2 == 0) { // or if (moveTime % 2 == 0)
    moveX(1);
}

私はそれを書く必要があるたびに内側にうんざりしますが、非整数の速度値でキャラクターを移動するよりクリーンな方法はありますか、これを永遠にやり続けるのですか?


11
整数単位を2.5(25に変換する)よりも小さくすることを検討したことがありますか(たとえば、表示単位の1/10)。
DMGregory

6
整数のみを使用して実装できるBresenhamラインアルゴリズムの採用を検討できます。
n0rd

1
これは、古い8ビットコンソールで一般的に行われています。固定小数点メソッドを使用してサブピクセルの動きを実装する方法の例については、次のような記事を参照してください。tasvideos.org
Lucas

回答:


13

ブレゼンハム

昔、人々がまだ線や円を描くための独自の基本的なビデオルーチンを書いていたとき、ブレゼンハムの線アルゴリズムを使用することは前代未聞ではありませんでした。

Bresenhamはこの問題を解決します。画面上にdxピクセルを水平方向に移動し、同時にdy垂直方向にピクセルをスパンする線を画面に描画します。行には固有の「浮動」文字があります。整数ピクセルを使用している場合でも、合理的な傾きになります。

ただし、アルゴリズムは高速でなければなりません。つまり、整数演算のみを使用できます。また、乗算も除算もせずに、加算と減算のみを行います。

それをあなたのケースに適応させることができます:

  • (x方向)(ブレゼンハムアルゴリズムの観点から)は時計です。
  • 「y方向」は、インクリメントする値です(つまり、キャラクターの位置-注意、これは実際にはスプライトの「y」や画面上の何かではなく、より抽象的な値です)

ここでの「x / y」は、画面上の場所ではなく、時間内のディメンションの値です。明らかに、スプライトが画面上で任意の方向に実行されている場合、2Dで2つ、3Dで3つ、別々に実行される複数のBresenhamがあります。

軸の1つに沿って0から25までの単純な動きでキャラクターを動かしたいとしましょう。速度2.5で移動しているため、フレーム10に到着します。

これは、(0,0)から(10,25)までの「線の描画」と同じです。Bresenhamのラインアルゴリズムをつかみ、実行させます。あなたがそれを正しく行えば(そしてあなたがそれを勉強すると、それがあなたがそれを正しくする方法が非常にすぐに明らかになります)、それはあなたのために11の「ポイント」を生成します(0,0)、(1,2)、(2、 5)、(3,7)、(4,10)...(10,25)。

適応のヒント

そのアルゴリズムをグーグルで検索してコードを見つけた場合(ウィキペディアにはかなり大きな条約があります)、次のことに注意する必要があります。

  • これは明らかに、すべての種類のために働くdxdy。ただし、特定の1つのケースに興味があります(つまり、決してありませんdx=0)。
  • 通常の実装では、正と負のどちらであるかdx、およびそうでないかどうかに応じて、画面上の象限についていくつかの異なるケースがあります。もちろん、ここで必要なものも選択します。ティックごとに増加する方向が常に「クロック」方向であることを特に確認する必要があります。dyabs(dx)>abs(dy)1

これらの単純化を適用すると、結果は非常に単純になり、実数を完全に取り除きます。


1
これは受け入れられた答えでなければなりません。80年代にC64でゲームをプログラミングし、90年代にPCでフラクタルをプログラムしてきましたが、それを避けることができるところならどこでも浮動小数点を使用することにまだうんざりしています。もちろん、今日のプロセッサに遍在するFPUでは、パフォーマンスの議論はほとんど意味がありませんが、浮動小数点演算にはさらに多くのトランジスタが必要で、より多くの電力を消費し、多くのプロセッサは使用されていない間にFPUを完全にシャットダウンします。そのため、浮動小数点を避けることで、モバイルユーザーは空のバッテリーをあまり早く吸わないことに感謝します。
Guntram BlohmはMonicaをサポートします

@GuntramBlohm固定小数点を使用する場合でも、受け入れられた答えは完璧に機能しますが、これはそれを行うのに良い方法だと思います。固定小数点数についてどう思いますか?
leetNightshade

これが8ビットと16ビットの時代にどのように行われたかを見つけた後、これを受け入れられた答えに変更しました。
アキュムレーター

26

あなたが望むことを正確に行うための素晴らしい方法があります。

float速度に加えて、実際の速度と丸められた速度のfloat差を格納および蓄積する2番目の変数が必要です。この差は、速度自体と組み合わされます。

#include <iostream>
#include <cmath>

int main()
{
    int pos = 10; 
    float vel = 0.3, vel_lag = 0;

    for (int i = 0; i < 20; i++)   
    {
        float real_vel = vel + vel_lag;
        int int_vel = std::lround(real_vel);
        vel_lag = real_vel - int_vel;

        std::cout << pos << ' ';
        pos += int_vel;
    }
}

出力:

10 10 11 11 11 12 12 12 12 13 13 13 14 14 14 15 15 15 15 16 16


5
なぜ速度と位置の両方にフロート(または固定小数点)を使用し、最後に整数のみに位置を丸めるよりもこのソリューションを好むのですか?
CodesInChaos 16

@CodesInChaos私はそのソリューションよりも私のソリューションを好まない。この答えを書いていたとき、私はそれについて知りませんでした。
HolyBlackCat 16

16

移動には浮動値を使用し、衝突とレンダリングには整数値を使用します。

以下に例を示します。

class Character {
    float position;
public:
    void move(float delta) {
        this->position += delta;
    }
    int getPosition() const {
        return lround(this->position);
    }
};

移動するときはmove()、端数位置を累積して使用します。ただし、このgetPosition()関数を使用することで、衝突とレンダリングで積分位置を処理できます。


ネットワークゲームの場合、ワールドシミュレーションに浮動小数点タイプを使用するのは難しい場合があることに注意してください。例:gafferongames.com/networking-for-game-programmers/…を参照してください。
liori

@liori floatのドロップイン置換のように動作する固定小数点クラスがある場合、それらはほとんどこれらの問題を解決しませんか?
leetNightshade 16

@leetNightshade:実装に依存します。
リオリ16

1
問題は実際には存在せず、最新のネットワークゲームを実行できるハードウェアにIEEE 754フロートがないものはありませんか?
ソペル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.