1

ここ数日、これを実装しようとして問題が発生しています。私がやろうとしていることに関して同様の質問を広範囲に検索しましたが、私の問題に直接役立つ質問に出くわしていません。

UserControl基本的に、クラスのグリッドにタイルをレンダリングしています。これは、私が開発している Tile Engine ベースのワールド エディター用です。これは、オープン ワールド ドキュメントといくつかのタイルをブラッシングしたスクリーンショットです。

ここに画像の説明を入力

Bitmap最初は、ワールドのプレビュー キャンバスとなる を自分のコントロールで使用するつもりでした。たとえば、ブラシ ツールを使用すると、マウスを動かして左ボタンを押したままにすると、カーソルの下にある最も近いタイルがブラシのタイルに設定され、layerビットマップにペイントされます。コントロールのメソッドは、ペイント イベントのクリッピング四角形に関してビットマップが描画OnPaintされる場所にオーバーライドされます。layer

この方法の問題点は、大きなワールドを扱う場合、ビットマップが非常に大きくなることです。このアプリケーションは、ワールド サイズに対応できるようにする必要があります。無効化されるたびに大きなビットマップをコントロールにレンダリングすると、パフォーマンスの問題が発生することは明らかです。

現在、コントロールのオーバーライドされたOnPaintイベントでタイルをコントロールに直接描画しています。多くのメモリを必要としないため、これは素晴らしいことです。たとえば、タイルごとの(1000, 1000)ワールド(20, 20)(合計キャンバス サイズは(20000, 20000)) は、アプリケーション全体で約 18 MB のメモリで実行されます。コントロールが無効化されるたびに、ビューポート内のすべてのタイルを反復処理するため、メモリを集中的に使用するわけではありませんが、かなりプロセッサを集中的に使用します。これにより、非常に厄介なちらつきが発生します。

私が達成したいのは、メモリ使用量とパフォーマンスの点で中間を満たす方法です。コントロールが再描画されるとき (フォームのサイズ変更、フォーカスとぼかし、スクロールなど) にちらつきがないように、基本的に世界をダブル バッファーします。Photoshop を例にとると、開いているドキュメントがコンテナのビューポートからはみ出す場合、どのようにレンダリングするのでしょうか?

OnPaint参考までに、上記の直接描画方法を使用している私のコントロールのオーバーライドを次に示します。

getRenderBoundsPaintEventArgs.ClipRectangle世界のすべてのタイルをループして表示されているかどうかを確認する代わりに、表示されているタイルをレンダリングするために使用される四角形を返します。

protected override void OnPaint(PaintEventArgs e)
{
    WorldSettings settings = worldSettings();

    Rectangle bounds = getRenderBounds(e.ClipRectangle),
        drawLocation = new Rectangle(Point.Empty, settings.TileSize);

    e.Graphics.InterpolationMode = 
        System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
    e.Graphics.SmoothingMode = 
        System.Drawing.Drawing2D.SmoothingMode.None;
    e.Graphics.PixelOffsetMode = 
        System.Drawing.Drawing2D.PixelOffsetMode.None;
    e.Graphics.CompositingQuality = 
        System.Drawing.Drawing2D.CompositingQuality.HighSpeed;

    for (int x = bounds.X; x < bounds.Width; x++)
    {
        for (int y = bounds.Y; y < bounds.Height; y++)
        {
            if (!inWorld(x, y))
                continue;

            Tile tile = getTile(x, y);

            if (tile == null)
                continue;

            drawLocation.X = x * settings.TileSize.Width;
            drawLocation.Y = y * settings.TileSize.Height;

            e.Graphics.DrawImage(img, 
                drawLocation, 
                tileRectangle, 
                GraphicsUnit.Pixel);
        }
    }
}

私のコードからさらにコンテキストが必要な場合は、コメントしてください。

4

1 に答える 1

1

秘訣は、これには大きなビットマップをまったく使用しないことです。可視領域をカバーするビットマップのみが必要です。次に、目に見えるものを描きます。

これを実現するには、ビットマップとは別にデータを維持する必要があります。これは、ワールド位置などの各ブロックの情報を保持する単純なクラスを持つ単純な配列または配列/リストにすることができます。

ブロックが可視領域内にある場合は、それを描画します。配列全体を反復する必要がある場合とそうでない場合がありますが、それは実際には問題ではありません (別のスレッドで可視配列を計算することもできます)。すべてのブロックを繰り返さないように領域インデックスを作成することで、関数をよりインテリジェントにすることもできます。

配列に新しいブロックを追加するには、キャンバスの位置を世界座標に計算し、追加してから、配列 (またはブロックが描画される領域) を再度レンダリングします。

これは、スクロール可能な領域を持つコントロールがシステムによって描画される方法でもあります。

ダブルバッファリングを有効にすると、クリアでちらつきがなくなります。

この場合、個別のスクロール バーを備えたパネルも使用し、スクロール バーの相対位置を計算します。

于 2012-12-06T02:05:42.920 に答える