5

まず、私は LINQ を初めて使用するので、LINQ の詳細がよくわかりません。私は今、いくつかのコードでそれを使用しようとしていますが、私の診断によると、同じ方法で for ループを使用するのとほぼ同じ速さのようです。ただし、私が扱っているリストは非常に劇的に増加する可能性があるため、これがどれだけうまくスケーリングされるかはわかりません.

私は LINQ を衝突検出機能 (まだ開発中) の一部として使用しており、それを使用して、チェックに関連するものだけにリストを選別しています。

LINQ のバージョンは次のとおりです。

partial class Actor {
    public virtual bool checkActorsForCollision(Vector2 toCheck) {
        Vector2 floored=new Vector2((int)toCheck.X, (int)toCheck.Y);

        if(!causingCollision) // skip if this actor doesn't collide
            return false;

        foreach(
            Actor actor in
            from a in GamePlay.actors
            where a.causingCollision==true&&a.isAlive
            select a
            )
            if( // ignore offscreen collisions, we don't care about them
                (actor.location.X>GamePlay.onScreenMinimum.X)
                &&
                (actor.location.Y>GamePlay.onScreenMinimum.Y)
                &&
                (actor.location.X<GamePlay.onScreenMaximum.X)
                &&
                (actor.location.Y<GamePlay.onScreenMaximum.Y)
                )
                if(actor!=this) { // ignore collisions with self
                    Vector2 actorfloor=new Vector2((int)actor.location.X, (int)actor.location.Y);

                    if((floored.X==actorfloor.X)&&(floored.Y==actorfloor.Y))
                        return true;
                }

        return false;
    }
}

これは私の以前の方法です:

partial class Actor {
    public virtual bool checkActorsForCollision(Vector2 toCheck) {
        Vector2 floored=new Vector2((int)toCheck.X, (int)toCheck.Y);

        if(!causingCollision) // skip if this actor doesn't collide
            return false;

        for(int i=0; i<GamePlay.actors.Count; i++)
            if( // ignore offscreen collisions, we don't care about them
                (GamePlay.actors[i].location.X>GamePlay.onScreenMinimum.X)
                &&
                (GamePlay.actors[i].location.Y>GamePlay.onScreenMinimum.Y)
                &&
                (GamePlay.actors[i].location.X<GamePlay.onScreenMaximum.X)
                &&
                (GamePlay.actors[i].location.Y<GamePlay.onScreenMaximum.Y)
                )
                if( // ignore collisions with self
                    (GamePlay.actors[i].isAlive)
                    &&
                    (GamePlay.actors[i]!=this)
                    &&
                    (GamePlay.actors[i].causingCollision)
                    ) {
                    Vector2 actorfloor=
                        new Vector2(
                            (int)GamePlay.actors[i].location.X,
                            (int)GamePlay.actors[i].location.Y
                            );

                    if((floored.X==actorfloor.X)&&(floored.Y==actorfloor.Y))
                        return true;
                }

        return false;
    }
}

現時点ではどちらもほとんど実行されませんが (ただし、1 秒間に何度も実行されます)、プロジェクトがビルドされて複雑になるにつれて、一度に処理するオブジェクトがはるかに多くなり、衝突をチェックするコードがより詳細になります。 .

4

5 に答える 5

11

あなたのコードはかなり良さそうです。私は動作するコードを変更することはあまり好きではありませんが、読みやすくするためにコードを書き直したい場合は、次のようにします。

まず、述語「is off the screen」を抽象化します。おそらくGamePlayのメソッドにします。座標が境界内にあるかどうかを毎回チェックするこのビジネスは、(1) 実装の詳細であり、(2) コードを読みにくくします。将来的には、オブジェクトが画面上にあるかどうかを判断するための、より洗練されたメカニズムが提供される可能性があります。

次に、ベクター フローリング操作を抽象化します。おそらくVectorのメソッドにします。このメソッドは、既存のベクターを変更するのではなく、新しいベクターを返す必要があることに注意してください。

3 番目に、ベクトルに対して等値演算子を作成します。

第 4 に、メソッドに適切な名前を付けます。述語は、「IsFoo」または「HasFoo」の形式にする必要があります。あなたはそれを質問としてではなく、命令として表現しました。

第 5 に、ループはまったく必要ありません。

第六に、言うのは奇妙somebool == trueです。ただ言ってsomeboolください。前者は「この bool が true である場合」を意味し、不必要に複雑です。

これがどのように変化するか見てみましょう:

public virtual bool HasCollisionWithAnyActor(Vector2 toCheck)
{
    // "Easy out": if this actor does not cause collisions then
    // we know that it is not colliding with any actor.
    if (!causingCollision)
      return false;

    Vector2 floored = toCheck.Floor();

    var collidingActors = 
      from actor in GamePlay.actors
      where actor != this
      where actor.causingCollision
      where actor.isAlive
      where GamePlay.IsOnScreen(actor.location)
      where actor.location.Floor() == floored
      select actor;

    return collidingActors.Any();
}

メソッドのバージョンよりも読みやすいことを見てください! X座標とY座標をいじることはありません。ヘルパー メソッドにそのすべてのスカットワークを実行させます。コードはセマンティクスを明確に表現するようになりました。画面上で他の生きている、衝突を引き起こすアクターとの衝突があるかどうか教えてください。

于 2013-01-23T16:44:02.410 に答える
2

ここで、LINQ バージョンは以前のものよりも高速です。これは、GamePlay.actors[i]. 次に、ループ内actorsで何度もアクセスする場合は、配列にアクセスします。for

public virtual bool checkActorsForCollision(Vector2 toCheck)
{
    Vector2 floored = new Vector2((int) toCheck.X, (int) toCheck.Y);

    if (!causingCollision) // skip if this actor doesn't collide
    return false;

    for (int i = 0; i < GamePlay.actors.Count; i++)
    {
        Actor actor = GamePlay.actors[i];
        // ignore offscreen collisions, we don't care about them
        if ((actor.location.X > GamePlay.onScreenMinimum.X) &&
            (actor.location.Y > GamePlay.onScreenMinimum.Y) &&
            (actor.location.X < GamePlay.onScreenMaximum.X) &&
            (actor.location.Y < GamePlay.onScreenMaximum.Y))
        {

            if ((actor.isAlive) && (actor != this) &&
                (actor.causingCollision)) // ignore collisions with self
            {
                Vector2 actorfloor =
                  new Vector2((int) actor.location.X,
                              (int) actor.location.Y);
                if ((floored.X == actorfloor.X) &&
                    (floored.Y == actorfloor.Y))
                  return true;
            }
        }
    }
    return false;
}

さて、LINQ は一般的に非常に優れたパフォーマンスを発揮します。forただし、クラシックバージョンを使用した方がよい場合もあります。コードの信頼性とパフォーマンスの適切なバランスを見つける必要があります (LINQ のバージョンとforバージョンを比較して)。私としては、LINQ の構文と信頼性が本当に気に入っているので、LINQ を使用したり悪用したりしています。

完全な LINQ バージョンは次のとおりです。

return (from actor in GamePlay.actors
        where actor.causingCollision && a.isAlive
        where actor != this
        where (actor.location.X > GamePlay.onScreenMinimum.X) &&
              (actor.location.Y > GamePlay.onScreenMinimum.Y) &&
              (actor.location.X < GamePlay.onScreenMaximum.X) &&
              (actor.location.Y < GamePlay.onScreenMaximum.Y)
        select new Vector2((int)actor.location.X,
                           (int)actor.location.Y)).Any(actorfloor => (floored.X == actorfloor.X) &&
                                                                     (floored.Y == actorfloor.Y));
于 2013-01-23T13:17:33.710 に答える
1

LINQは非常に最適化されており、その出力は手動でforを書き込むのと同じです。ちなみに、拡張メソッドははるかに読みやすいです。あなたの例は次のようになります:

public virtual bool checkActorsForCollision(Vector2 toCheck)
{
    Vector2 floored = new Vector2((int)toCheck.X, (int)toCheck.Y);

    // skip if this actor doesn't collide
    if (!causingCollision)
    {
        return false;
    }

    return GamePlay.actors.Where( actor =>    
            (actor.causingCollision == true) 
            && actor.isAlive
            // ignore offscreen collisions, we don't care about them
            && (actor.location.X > GamePlay.onScreenMinimum.X) 
            && (actor.location.Y > GamePlay.onScreenMinimum.Y)          
            && (actor.location.X < GamePlay.onScreenMaximum.X) 
            && (actor.location.Y < GamePlay.onScreenMaximum.Y)
            // ignore collisions with self
            && (actor != this) )
        .Select( actor => new Vector2((int)actor.location.X, (int)actor.location.Y) )
        .Any( actorFloor => 
            (floored.X == actorfloor.X) 
            && (floored.Y == actorfloor.Y));
}

アプリケーションのプロファイルを作成していない場合、「パフォーマンス上の理由」でLINQを使用しない理由はありません。

于 2013-01-23T13:25:20.343 に答える
1

ここでLINQを使用しても問題はありません。パフォーマンスが向上しているかどうかを本当に知りたい場合は、Diagnostics.StopWatch で測定を行う必要があります。

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.elapsedticks.aspx

また、このような LINQ をさらに使用して、関数をもう少しコンパクトにすることもできます。

return (from actor in (from a in GamePlay.actors
                                   where a.CausingCollision == true && a.IsAlive
                                   select a)
                    where (actor.location.X > GamePlay.onScreenMinimum.X) && (actor.location.Y > GamePlay.onScreenMinimum.Y) && (actor.location.X < GamePlay.onScreenMaximum.X) && (actor.location.Y < GamePlay.onScreenMaximum.Y)
                    where actor != this
                    select new Vector2((int) actor.location.X, (int) actor.location.Y)).Any(actorfloor => (floored.X == actorfloor.X) && (floored.Y == actorfloor.Y));
于 2013-01-23T13:17:55.787 に答える
1

質問を理解しているかどうかはわかりませんが、一般的には、追加のループや if/else 呼び出しを作成する代わりに、LINQ を使用する方が常に優れたソリューションだと思います。

参考までに: 次のように、キーワードの代わりに LINQ-Methodes を使用することもできます。

foreach(Actor actor in from a in GamePlay.actors
                           where a.causingCollision == true 
                           && a.isAlive
                           select a)
{
  //...
}

以下と同じです:

foreach(Actor actor in GamePlay.actors.Where( a => a.causingCollision == true && a.isAlive))
{
  //...
}

どちらの場合も、戻り値はフィルター処理された IEnumerable です。私の個人的な意見は、メソッドの方が読みやすく理解しやすいということです。Where(...) では、完全なラムダ式を配置して、必要なアクタのみを完全に選択できます。

于 2013-01-23T13:18:27.583 に答える