HTML5 Canvasにペイントされる2D配列のテキストマップの管理


46

それで、私はただの楽しみのためにHTML5 RPGを作っています。マップは<canvas>(幅512ピクセル、高さ352ピクセル|横16タイル、縦11タイル)です。をペイントするより効率的な方法があるかどうかを知りたいです<canvas>

ここに私が今それを持っている方法があります。

マップ上でのタイルのロード方法とペイント方法

マップは、Image()ピースを使用してタイル(32x32)でペイントされています。画像ファイルは単純なforループを介してロードされ、tiles[]を使用してペイントされるように呼び出される配列に入れられdrawImage()ます。

まず、タイルを読み込みます...

ここに画像の説明を入力してください

そして、それがどのように行われているのですか:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

当然、プレーヤーがゲームを開始すると、最後に中断したマップがロードされます。しかし、ここでは、すべての草の地図です。

現在、マップは2D配列を使用しています。以下にマップの例を示します。

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

単純なif構造を使用してさまざまなマップを取得します。上記の2次元配列がいったんであるとreturn、各配列の対応する数はImage()格納された内部に従って描かれますtile[]。次いでdrawImage()に従って発生し、ペイントであろうxyし、回それによって32正しい上にペイントするx-y座標。

複数のマップの切り替えが発生する方法

私のゲームでは、マップはを追跡する5つのことを持っている:currentIDleftIDrightIDupID、とbottomID

  • currentID:現在のマップの現在のID。
  • leftID:currentID現在のマップの左側で終了するときにロードするID 。
  • rightID:currentID現在のマップの右側で終了するときにロードするID 。
  • downID:currentID現在のマップの下部で終了するときにロードするID 。
  • upID:currentID現在のマップの上部で終了するときにロードするID 。

ノートに何か:いずれかの場合はleftIDrightIDupID、またはbottomID、特定されていないことを意味することを、彼らはあります0。つまり、マップのその側から離れることはできません。それは単に目に見えない封鎖です。

そのため、人が出た場所に応じて、マップの側面を出ると、たとえば、下に出た場合、ロードbottomIDする数がmapマップ上にペイントされます。

視覚化を支援する代表的な.GIFを次に示します。

ここに画像の説明を入力してください

ご覧のとおり、遅かれ早かれ、多くのマップを使用して、多くの ID を処理します。そして、それは少し混乱し多忙になる可能性があります。

明らかな長所は、一度に176タイルを読み込み、小さな512x352キャンバスを更新し、一度に1つのマップを処理することです。欠点は、多くのマップを処理するとき、MAP idが時々混乱する可能性があることです。

私の質問

  • これは、マップを保存するための効率的な方法ですか(タイルを使用する場合)、またはマップを処理するためのより良い方法がありますか?

私は巨大な地図の線に沿って考えていました。マップサイズは大きく、すべて1つの2D配列です。ただし、ビューポートは512x352ピクセルのままです。

視覚化を支援するために(この質問のために)作成した別の.gifを次に示します。

ここに画像の説明を入力してください

あなたが私の英語を理解できない場合は申し訳ありません。わからないことは何でも聞いてください。うまくいけば、私はそれを明確にした。ありがとう。


16
グラフィックスでこの疑問に取り組む努力は、すべて賛成に値します。:)
タピオ

回答:


5

編集:私の答えはあなたのコードに基づいているが、実際にはあなたの質問に答えていないことがわかりました。その情報を使用できる場合に備えて、古い回答を保持しました。

編集2:私は元のコードの2つの問題を修正しました:-端xとyの計算での+1の追加は間違って括弧内にありましたが、分割後に追加する必要があります。-xとyの値を検証するのを忘れました。

現在のマップをメモリに保存して、周囲のマップをロードするだけで済みます。サイズが5x5の単一レベルの2次元配列で構成される世界を想像してください。プレーヤーはフィールド1から開始します。レベルの上部と左の境界は世界の端にあるため、ロードする必要はありません。したがって、その場合、レベル1/1がアクティブで、レベル1/2と2/1がロードされます...プレイヤーが右に移動すると、すべてのレベル(移動先のレベルを除く)がアンロードされ、新しい周囲はロードされました。レベル2/1がアクティブになり、1 / 1、2 / 2、3 / 1がロードされるようになりました。

これがあなたにそれがどのように行われるかのアイデアを与えることを願っています。しかし、ゲーム中にすべてのレベルをシミュレートする必要がある場合、このアプローチはあまりうまくいきません。ただし、未使用のレベルを凍結できる場合、これは正常に機能するはずです。

古い答え:

レベル(またタイルベース)をレンダリングするときに行うことは、タイル配列のどのアイテムがビューポートと交差するかを計算し、それらのタイルのみをレンダリングすることです。そのように、あなたは大きな地図を持つことができますが、画面上でその部分をレンダリングするだけです。

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

注:上記のコードはテストされていませんが、何をすべきかがわかるはずです。

ビューポート表現の基本的な例を次に示します。

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

javascript表現は次のようになります

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

私のコード例を使用する場合、intおよびfloat宣言をvarで置き換える必要があります。また、同じ動作を得るために、intベースの値に割り当てられている計算でMath.floorを使用していることを確認してください。

私が検討することの1つは(これがjavascriptで問題になるかどうかはわかりませんが)、多くの単一ファイルを使用する代わりに、すべてのタイル(またはできるだけ多く)を1つの大きなテクスチャに配置することです。

これを行う方法を説明するリンクはこちらです:http : //thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


明確にするために、viewport.xは「531」、viewport.yは「352」、tilesize =「32」と指定されます。など..?
テスト

カメラの位置が531であることを意味する場合は、はい。上記のコードにコメントを追加しました。
トムヴァングリーン

私はJavaScriptを使用しています...私はJavaはこれとほぼ同じです。
テスト

ええ、jsベースのビューポートクラスを作成するだけです(または、4つの変数x、y、w、hを作成することもできます)。また、int値に割り当てられたこれらの計算を確実にフロアリング(Math.floor)する必要があります。ビューポートクラスがどのように見えるかを示すコード例を追加しました。使用法の前に宣言を置くようにしてください。
トムヴァングリーン

あなたが入れたと言って640, 480..しかし、それは512, 352右で動作することができますか?
テスト

3

マップをタイルとして保存すると、アセットを再利用したり、マップを再設計したりするのに役立ちます。現実的には、プレーヤーにはそれほど多くのメリットはありませんが。さらに、毎回すべてのタイルを再描画します。ここに私がやることがあります:

マップ全体を1つの大きな配列として保存します。マップやサブマップ、および潜在的にサブサブマップ(たとえば、建物に入る場合)を持つポイントはありません。とにかくそれをすべてロードしており、これによりすべてが素晴らしくシンプルに保たれます。

マップを一度にレンダリングします。マップをレンダリングするオフスクリーンキャンバスを用意し、それをゲームで使用します。このページでは、簡単なjavascript関数を使用してそれを行う方法を示します。

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

これは、マップがあまり変わらないことを前提としています。サブマップをインプレース(たとえば、建物の内部)でレンダリングしたい場合は、それをキャンバスにもレンダリングしてから、その上に描画します。これを使用してマップをレンダリングする方法の例を次に示します。

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

これにより、地図の一部がの中心に描画されmapCentreX, mapCentreYます。これにより、マップ全体のスムーズなスクロールとサブタイルの動きが提供されます-プレーヤーの位置をタイルの1/32として保存できるため、マップ全体でスムーズに動きます(明らかにしたい場合は、スタイルごとにタイルごとに移動する場合は、32単位でインクリメントできます。

必要に応じてマップをチャンクすることも、世界が巨大でターゲットシステムのメモリに収まらない場合もあります(ピクセルあたり1バイトでも512x352マップは176 KBであることに注意してください) -このようにマップをレンダリングすると、ほとんどのブラウザーでパフォーマンスが大幅に向上します。

これにより、1つの大きな画像を編集するという頭痛なしに世界中でタイルを再利用できる柔軟性が得られますが、これをすばやく簡単に実行し、1つの「マップ」(または好きなだけの「マップ」)だけを考慮できます。



1

IDを追跡する簡単な方法は、別の2D配列を使用することです。その「メタ配列」でx、yを維持するだけで、他のIDを簡単に検索できます。

そうは言っても、Googleの最近のI / O 2012からの2Dタイルキャンバスゲーム(実際にはHTML5マルチプレイヤーですが、タイルエンジンもカバーされています)に対する取り組みは次のとおりです。http : //www.youtube.com/watch?v=Prkyd5n0P7k Itソースコードも利用可能です。


1

最初にマップ全体を配列にロードするというアイデアは効率的な方法ですが、JavaScriptインジェクションを取得するのは簡単であり、誰でもマップを知ることができ、冒険に出かけます。

私はRPGゲームもウェブベースで作業しています。私のマップをロードする方法は、PHPページからAjaxを介してデータベースからマップをロードすることです。タイルの、それを描画し、最終的にプレイヤーが移動します。

私はそれをもっと効率的にするためにまだ取り組んでいますが、地図は今までのようにそれを維持するのに十分速くロードされます。


1
デモを見ることができますか?
テスト

1

いいえ配列はタイルマップを保存する最も効率的な方法です

わずかなメモリしか必要としない構造がいくつかありますが、データのロードとアクセスにかかるコストははるかに高くなります。


ただし、次のマップをロードするときは、いくつかの考慮事項が必要です。マップが特に大きくない場合、実際に最も時間がかかるのは、サーバーがマップを配信するのを待つことです。実際の読み込みには数ミリ秒しかかかりません。しかし、この待機は非同期で行うことができ、ゲームのフローをブロックする必要はありません。したがって、必要なときにデータをロードするのではなく、前にロードするのが最良の方法です。プレーヤーは1つのマップにあり、隣接するすべてのマップをロードします。プレイヤーは次のマップに移動したり、隣接するすべてのマップを再びロードしたりなどします。プレイヤーは、ゲームがバックグラウンドでロードされていることに気付くことさえありません。

この方法では、隣接するマップも描画することで、境界線のない世界の錯覚を作成することもできます。その場合、隣接するマップだけでなく、それらの隣接するマップもロードする必要があります。


0

なぜあなたが思うのか分かりません:遅かれ早かれ、あなたが見ることができるように、多くの地図で、私は多くのIDを扱っています。そして、それは少し混乱し多忙になる可能性があります。

LeftIDは常にcurrentIDの-1になり、rightIDは常にcurrentIDの+1になります。

UpIDは常に現在のIDの-(マップ全体の幅)になり、downIDは常に現在のIDの+(マップ全体の幅)になります。ただし、0の場合はエッジに到達したことを意味します。

1つのマップを複数のマップに分割し、mapIDXXXXで順番に保存できます(XXXXはID)。そうすれば、混乱することはありません。私はあなたのサイトに行ったことがありますが、間違っている可能性があります、問題はあなたの自動化が大きな地図を保存時に複数の小さな地図に分割することを妨げるサイズ制限を課しているあなたの地図エディタにあり、この制限はおそらく技術的な解決策を制限しています。

私は、すべての方向、1000 x 1000(私はそのサイズはお勧めしません)タイルにスクロールするJavascriptマップエディターを書きました。json形式で保存して読み取ります。約2メガのメールを気にしないと言った場合、コピーをお送りします。これはgithub上にあることを意図していますが、まともな(合法的な)タイルセットがありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.