web-dev-qa-db-ja.com

HTML5 Canvasカメラ/ビューポート-実際に行う方法

私はこれが1000回前に解決されたと確信しています:960 * 560のサイズのキャンバスと5000 * 3000のサイズの部屋を手に入れました。プレーヤーの場所に応じて、常に960 * 560だけが描画されます。プレイヤーは常に真ん中にいるはずですが、国境に近い場合-最適なビューを計算する必要があります)。プレーヤーは、WASDまたは矢印キーを使用して完全に自由に移動できます。そして、すべてのオブジェクトは自分自身を移動する必要があります-その代わりに、プレイヤー以外のすべてを移動して、プレイヤーが移動するような錯覚を作成します。

私は今、これらの2つの質問を見つけました。

HTML5-キャンバス用のビューポートの作成 は機能しますが、このタイプのゲームの場合のみ、私のコードを再現できません。

html5キャンバスのビュー「中心」を変更する はより有望であり、パフォーマンスも高いようですが、他のすべてのオブジェクトをプレイヤーに対して正しく描画するためだけに理解し、キャンバスビューポートを相対的にスクロールする方法ではありませんプレーヤーに、私はもちろん最初に達成したい。

私のコード(簡略化-ゲームのロジックは別です):

var canvas = document.getElementById("game");
canvas.tabIndex = 0;
canvas.focus();
var cc = canvas.getContext("2d");

// Define viewports for scrolling inside the canvas

/* Viewport x position */   view_xview = 0;
/* Viewport y position */   view_yview = 0;
/* Viewport width */        view_wview = 960;
/* Viewport height */       view_hview = 560;
/* Sector width */          room_width = 5000;
/* Sector height */         room_height = 3000;

canvas.width = view_wview;
canvas.height = view_hview;

function draw()
{
    clear();
    requestAnimFrame(draw);

    // World's end and viewport
    if (player.x < 20) player.x = 20;
    if (player.y < 20) player.y = 20;
    if (player.x > room_width-20) player.x = room_width-20;
    if (player.y > room_height-20) player.y = room_height-20;

    if (player.x > view_wview/2) ... ?
    if (player.y > view_hview/2) ... ?
}

私がそれを機能させようとしている方法は完全に間違っていると感じており、どのようにしようとしているのかさえ知りません... context.transform-thingについてどう思いますか?

あなたが私の説明を理解し、誰かがアイデアを持っていることを願っています。敬具

44
user2337969

LIVE DEMO atjsfiddle.net

このデモは、実際のゲームシナリオでのビューポートの使用方法を示しています。矢印キーを使用して、プレーヤーを部屋に移動します。大きな部屋は、長方形を使用してその場で生成され、結果は画像に保存されます。

プレーヤーは、境界線に近い場合を除いて、常に中央にいることに注意してください(必要に応じて)。


次に、コードの主要部分、少なくとも見ただけでは理解しにくい部分について説明します。


DrawImageを使用して、ビューポートの位置に応じて大きな画像を描画します

DrawImageメソッドのバリアントには、8つの新しいパラメーターがあります。このメソッドを使用して、ソース画像の一部をスライスし、キャンバスに描画できます。

drawImage(image、sx、sy、sWidth、sHeight、dx、dy、dWidth、dHeight)

最初のパラメータ画像は、他のバリアントと同様に、画像オブジェクトへの参照または別のキャンバス要素への参照です。他の8つのパラメーターについては、以下の画像を見るのが最善です。最初の4つのパラメーターは、ソースイメージ上のスライスの位置とサイズを定義します。最後の4つのパラメーターは、宛先キャンバス上の位置とサイズを定義します。

Canvas drawImage

フォント: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Using_images

デモでの動作:

部屋を表す大きな画像があり、ビューポート内の部分のみをキャンバスに表示したい。トリミング位置(sx、sy)はカメラ(xView、yView)の同じ位置であり、トリミング寸法はビューポート(canvas)と同じなので、sWidth=canvas.widthおよびsHeight=canvas.height

トリミング位置または位置に基づくトリミング寸法が無効な場合、drawImageはキャンバスに何も描画しないため、トリミング寸法に注意する必要があります。そのため、以下のifセクションが必要です。

var sx, sy, dx, dy;
var sWidth, sHeight, dWidth, dHeight;

// offset point to crop the image
sx = xView;
sy = yView;

// dimensions of cropped image          
sWidth =  context.canvas.width;
sHeight = context.canvas.height;

// if cropped image is smaller than canvas we need to change the source dimensions
if(image.width - sx < sWidth){
    sWidth = image.width - sx;
}
if(image.height - sy < sHeight){
    sHeight = image.height - sy; 
}

// location on canvas to draw the croped image
dx = 0;
dy = 0;
// match destination with source to not scale the image
dWidth = sWidth;
dHeight = sHeight;          

// draw the cropped image
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

ビューポートに関連するゲームオブジェクトの描画

ゲームを書くときは、ゲーム内の各オブジェクトのロジックとレンダリングを分離することをお勧めします。そのため、デモにはupdateおよびdraw関数があります。 updateメソッドは、「ゲームワールド」上の位置などのオブジェクトのステータスを変更し、物理、アニメーション状態などを適用します。drawメソッドは、実際にオブジェクトをレンダリングし、オブジェクトは、レンダリングコンテキストとビューポートプロパティを知る必要があります。

ゲームオブジェクトは、ゲームワールドの位置を考慮して更新されることに注意してください。つまり、オブジェクトの(x、y)位置はワールド内の位置です。それにもかかわらず、ビューポートは変化しているため、オブジェクトを適切にレンダリングする必要があり、レンダリング位置はワールドの位置とは異なります。

変換は簡単です:

ワールド(部屋)内のオブジェクトの位置:(x, y)
ビューポートの位置:(xView, yView)

レンダリング位置(x-xView, y-yView)

これは、あらゆる種類の座標、負の座標でも機能します。


ゲームカメラ

ゲームオブジェクトには、個別の更新メソッドがあります。デモの実装では、カメラはゲームオブジェクトとして扱われ、個別の更新メソッドも備えています。

カメラオブジェクトは、ビューポートの左上の位置を保持します(xView, yView)、追跡対象のオブジェクト、ビューポートを表す長方形、ゲームの世界の境界を表す長方形、カメラが移動する前にプレーヤーが移動できる各境界線の最小距離(xDeadZone、yDeadZone)。また、カメラの自由度(軸)を定義しました。 RPGなどのトップビュースタイルのゲームの場合、カメラはx(horizontal)とy(vertical)軸の両方で移動できます。

プレイヤーをビューポートの中央に維持するために、各軸のdeadZoneをキャンバスの中心に収束するように設定します。コードのフォロー機能を見てください:

camera.follow(player、canvas.width/2、canvas.height/2)

:以下の更新セクションを参照してください。マップ(部屋)のいずれかの寸法がキャンバスよりも小さい場合、期待される動作が生成されません。


世界の限界

カメラを含む各オブジェクトには独自の更新機能があるため、ゲームの世界の境界を簡単に確認できます。更新機能の最後に、移動をブロックするコードを忘れずに配置してください。


デモンストレーション

完全なコードを見て、自分で試してください。コードのほとんどの部分には、あなたをガイドするコメントがあります。 Javascriptの基本とプロトタイプの操作方法を知っていると仮定します(Javaのような言語でクラスと同様の動作をするという理由だけで、プロトタイプオブジェクトに「クラス」という用語を使用することがあります)。

LIVE DEMO

完全なコード:

<!DOCTYPE HTML>
<html>
<body>
<canvas id="gameCanvas" width=400 height=400 />
<script>
// wrapper for our game "classes", "methods" and "objects"
window.Game = {};

// wrapper for "class" Rectangle
(function() {
  function Rectangle(left, top, width, height) {
    this.left = left || 0;
    this.top = top || 0;
    this.width = width || 0;
    this.height = height || 0;
    this.right = this.left + this.width;
    this.bottom = this.top + this.height;
  }

  Rectangle.prototype.set = function(left, top, /*optional*/ width, /*optional*/ height) {
    this.left = left;
    this.top = top;
    this.width = width || this.width;
    this.height = height || this.height
    this.right = (this.left + this.width);
    this.bottom = (this.top + this.height);
  }

  Rectangle.prototype.within = function(r) {
    return (r.left <= this.left &&
      r.right >= this.right &&
      r.top <= this.top &&
      r.bottom >= this.bottom);
  }

  Rectangle.prototype.overlaps = function(r) {
    return (this.left < r.right &&
      r.left < this.right &&
      this.top < r.bottom &&
      r.top < this.bottom);
  }

  // add "class" Rectangle to our Game object
  Game.Rectangle = Rectangle;
})();

// wrapper for "class" Camera (avoid global objects)
(function() {

  // possibles axis to move the camera
  var AXIS = {
    NONE: 1,
    HORIZONTAL: 2,
    VERTICAL: 3,
    BOTH: 4
  };

  // Camera constructor
  function Camera(xView, yView, viewportWidth, viewportHeight, worldWidth, worldHeight) {
    // position of camera (left-top coordinate)
    this.xView = xView || 0;
    this.yView = yView || 0;

    // distance from followed object to border before camera starts move
    this.xDeadZone = 0; // min distance to horizontal borders
    this.yDeadZone = 0; // min distance to vertical borders

    // viewport dimensions
    this.wView = viewportWidth;
    this.hView = viewportHeight;

    // allow camera to move in vertical and horizontal axis
    this.axis = AXIS.BOTH;

    // object that should be followed
    this.followed = null;

    // rectangle that represents the viewport
    this.viewportRect = new Game.Rectangle(this.xView, this.yView, this.wView, this.hView);

    // rectangle that represents the world's boundary (room's boundary)
    this.worldRect = new Game.Rectangle(0, 0, worldWidth, worldHeight);

  }

  // gameObject needs to have "x" and "y" properties (as world(or room) position)
  Camera.prototype.follow = function(gameObject, xDeadZone, yDeadZone) {
    this.followed = gameObject;
    this.xDeadZone = xDeadZone;
    this.yDeadZone = yDeadZone;
  }

  Camera.prototype.update = function() {
    // keep following the player (or other desired object)
    if (this.followed != null) {
      if (this.axis == AXIS.HORIZONTAL || this.axis == AXIS.BOTH) {
        // moves camera on horizontal axis based on followed object position
        if (this.followed.x - this.xView + this.xDeadZone > this.wView)
          this.xView = this.followed.x - (this.wView - this.xDeadZone);
        else if (this.followed.x - this.xDeadZone < this.xView)
          this.xView = this.followed.x - this.xDeadZone;

      }
      if (this.axis == AXIS.VERTICAL || this.axis == AXIS.BOTH) {
        // moves camera on vertical axis based on followed object position
        if (this.followed.y - this.yView + this.yDeadZone > this.hView)
          this.yView = this.followed.y - (this.hView - this.yDeadZone);
        else if (this.followed.y - this.yDeadZone < this.yView)
          this.yView = this.followed.y - this.yDeadZone;
      }

    }

    // update viewportRect
    this.viewportRect.set(this.xView, this.yView);

    // don't let camera leaves the world's boundary
    if (!this.viewportRect.within(this.worldRect)) {
      if (this.viewportRect.left < this.worldRect.left)
        this.xView = this.worldRect.left;
      if (this.viewportRect.top < this.worldRect.top)
        this.yView = this.worldRect.top;
      if (this.viewportRect.right > this.worldRect.right)
        this.xView = this.worldRect.right - this.wView;
      if (this.viewportRect.bottom > this.worldRect.bottom)
        this.yView = this.worldRect.bottom - this.hView;
    }

  }

  // add "class" Camera to our Game object
  Game.Camera = Camera;

})();

// wrapper for "class" Player
(function() {
  function Player(x, y) {
    // (x, y) = center of object
    // ATTENTION:
    // it represents the player position on the world(room), not the canvas position
    this.x = x;
    this.y = y;

    // move speed in pixels per second
    this.speed = 200;

    // render properties
    this.width = 50;
    this.height = 50;
  }

  Player.prototype.update = function(step, worldWidth, worldHeight) {
    // parameter step is the time between frames ( in seconds )

    // check controls and move the player accordingly
    if (Game.controls.left)
      this.x -= this.speed * step;
    if (Game.controls.up)
      this.y -= this.speed * step;
    if (Game.controls.right)
      this.x += this.speed * step;
    if (Game.controls.down)
      this.y += this.speed * step;

    // don't let player leaves the world's boundary
    if (this.x - this.width / 2 < 0) {
      this.x = this.width / 2;
    }
    if (this.y - this.height / 2 < 0) {
      this.y = this.height / 2;
    }
    if (this.x + this.width / 2 > worldWidth) {
      this.x = worldWidth - this.width / 2;
    }
    if (this.y + this.height / 2 > worldHeight) {
      this.y = worldHeight - this.height / 2;
    }
  }

  Player.prototype.draw = function(context, xView, yView) {
    // draw a simple rectangle shape as our player model
    context.save();
    context.fillStyle = "black";
    // before draw we need to convert player world's position to canvas position            
    context.fillRect((this.x - this.width / 2) - xView, (this.y - this.height / 2) - yView, this.width, this.height);
    context.restore();
  }

  // add "class" Player to our Game object
  Game.Player = Player;

})();

// wrapper for "class" Map
(function() {
  function Map(width, height) {
    // map dimensions
    this.width = width;
    this.height = height;

    // map texture
    this.image = null;
  }

  // creates a prodedural generated map (you can use an image instead)
  Map.prototype.generate = function() {
    var ctx = document.createElement("canvas").getContext("2d");
    ctx.canvas.width = this.width;
    ctx.canvas.height = this.height;

    var rows = ~~(this.width / 44) + 1;
    var columns = ~~(this.height / 44) + 1;

    var color = "red";
    ctx.save();
    ctx.fillStyle = "red";
    for (var x = 0, i = 0; i < rows; x += 44, i++) {
      ctx.beginPath();
      for (var y = 0, j = 0; j < columns; y += 44, j++) {
        ctx.rect(x, y, 40, 40);
      }
      color = (color == "red" ? "blue" : "red");
      ctx.fillStyle = color;
      ctx.fill();
      ctx.closePath();
    }
    ctx.restore();

    // store the generate map as this image texture
    this.image = new Image();
    this.image.src = ctx.canvas.toDataURL("image/png");

    // clear context
    ctx = null;
  }

  // draw the map adjusted to camera
  Map.prototype.draw = function(context, xView, yView) {
    // easiest way: draw the entire map changing only the destination coordinate in canvas
    // canvas will cull the image by itself (no performance gaps -> in hardware accelerated environments, at least)
    /*context.drawImage(this.image, 0, 0, this.image.width, this.image.height, -xView, -yView, this.image.width, this.image.height);*/

    // didactic way ( "s" is for "source" and "d" is for "destination" in the variable names):

    var sx, sy, dx, dy;
    var sWidth, sHeight, dWidth, dHeight;

    // offset point to crop the image
    sx = xView;
    sy = yView;

    // dimensions of cropped image          
    sWidth = context.canvas.width;
    sHeight = context.canvas.height;

    // if cropped image is smaller than canvas we need to change the source dimensions
    if (this.image.width - sx < sWidth) {
      sWidth = this.image.width - sx;
    }
    if (this.image.height - sy < sHeight) {
      sHeight = this.image.height - sy;
    }

    // location on canvas to draw the croped image
    dx = 0;
    dy = 0;
    // match destination with source to not scale the image
    dWidth = sWidth;
    dHeight = sHeight;

    context.drawImage(this.image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
  }

  // add "class" Map to our Game object
  Game.Map = Map;

})();

// Game Script
(function() {
  // prepaire our game canvas
  var canvas = document.getElementById("gameCanvas");
  var context = canvas.getContext("2d");

  // game settings: 
  var FPS = 30;
  var INTERVAL = 1000 / FPS; // milliseconds
  var STEP = INTERVAL / 1000 // seconds

  // setup an object that represents the room
  var room = {
    width: 500,
    height: 300,
    map: new Game.Map(500, 300)
  };

  // generate a large image texture for the room
  room.map.generate();

  // setup player
  var player = new Game.Player(50, 50);

  // Old camera setup. It not works with maps smaller than canvas. Keeping the code deactivated here as reference.
  /* var camera = new Game.Camera(0, 0, canvas.width, canvas.height, room.width, room.height);*/
  /* camera.follow(player, canvas.width / 2, canvas.height / 2); */

  // Set the right viewport size for the camera
  var vWidth = Math.min(room.width, canvas.width);
  var vHeight = Math.min(room.height, canvas.height);

  // Setup the camera
  var camera = new Game.Camera(0, 0, vWidth, vHeight, room.width, room.height);
  camera.follow(player, vWidth / 2, vHeight / 2);

  // Game update function
  var update = function() {
    player.update(STEP, room.width, room.height);
    camera.update();
  }

  // Game draw function
  var draw = function() {
    // clear the entire canvas
    context.clearRect(0, 0, canvas.width, canvas.height);

    // redraw all objects
    room.map.draw(context, camera.xView, camera.yView);
    player.draw(context, camera.xView, camera.yView);
  }

  // Game Loop
  var gameLoop = function() {
    update();
    draw();
  }

  // <-- configure play/pause capabilities:

  // Using setInterval instead of requestAnimationFrame for better cross browser support,
  // but it's easy to change to a requestAnimationFrame polyfill.

  var runningId = -1;

  Game.play = function() {
    if (runningId == -1) {
      runningId = setInterval(function() {
        gameLoop();
      }, INTERVAL);
      console.log("play");
    }
  }

  Game.togglePause = function() {
    if (runningId == -1) {
      Game.play();
    } else {
      clearInterval(runningId);
      runningId = -1;
      console.log("paused");
    }
  }

  // -->

})();

// <-- configure Game controls:

Game.controls = {
  left: false,
  up: false,
  right: false,
  down: false,
};

window.addEventListener("keydown", function(e) {
  switch (e.keyCode) {
    case 37: // left arrow
      Game.controls.left = true;
      break;
    case 38: // up arrow
      Game.controls.up = true;
      break;
    case 39: // right arrow
      Game.controls.right = true;
      break;
    case 40: // down arrow
      Game.controls.down = true;
      break;
  }
}, false);

window.addEventListener("keyup", function(e) {
  switch (e.keyCode) {
    case 37: // left arrow
      Game.controls.left = false;
      break;
    case 38: // up arrow
      Game.controls.up = false;
      break;
    case 39: // right arrow
      Game.controls.right = false;
      break;
    case 40: // down arrow
      Game.controls.down = false;
      break;
    case 80: // key P pauses the game
      Game.togglePause();
      break;
  }
}, false);

// -->

// start the game when page is loaded
window.onload = function() {
  Game.play();
}

</script>
</body>
</html>


[〜#〜] update [〜#〜]

マップ(部屋)の幅や高さがキャンバスよりも小さい場合、前のコードは正しく機能しません。これを解決するには、ゲームスクリプトで次のようにカメラのセットアップを行います。

// Set the right viewport size for the camera
var vWidth = Math.min(room.width, canvas.width);
var vHeight = Math.min(room.height, canvas.height);

var camera = new Game.Camera(0, 0, vWidth, vHeight, room.width, room.height);
camera.follow(player, vWidth / 2, vHeight / 2);

ビューポートはマップ(部屋)とキャンバスの間の最小値になることをカメラコンストラクターに伝える必要があります。そして、プレーヤーをそのビューポートの中央に配置して結合したいので、camera.follow関数も更新する必要があります。


お気軽にエラーを報告したり、提案を追加してください。

80

受け入れられた答えのコードは少し多いです。そのシンプルな:

function draw() {
    ctx.setTransform(1,0,0,1,0,0);//reset the transform matrix as it is cumulative
    ctx.clearRect(0, 0, canvas.width, canvas.height);//clear the viewport AFTER the matrix is reset

    //Clamp the camera position to the world bounds while centering the camera around the player                                             
    var camX = clamp(-player.x + canvas.width/2, yourWorld.minX, yourWorld.maxX - canvas.width);
    var camY = clamp(-player.y + canvas.height/2, yourWorld.minY, yourWorld.maxY - canvas.height);

    ctx.translate( camX, camY );    

    //Draw everything
}

クランプは次のようになります。

function clamp(value, min, max){
    if(value < min) return min;
    else if(value > max) return max;
    return value;
}
20
Colton

ここでは、キャンバスを使用して、キャンバスよりも大きい別の画像のビューポートにする方法を示します

ビューポートは、実際には、ユーザーに表示される大きな画像の一部を切り取ったものです。

この場合、ビューポートはキャンバス上でユーザーに表示されます(キャンバスはビューポートです)。

最初に、大きな画像の周りにビューポートをパンする移動関数をコーディングします。

この関数は、ビューポートの左上隅を指定された方向に5ピクセルずつ移動します。

function move(direction){
    switch (direction){
        case "left":
            left-=5;
            break;
        case "up":
            top-=5;
            break;
        case "right":
            left+=5;
            break;
        case "down":
            top+=5
            break;
    }
    draw(top,left);
}

Move関数はdraw関数を呼び出します。

Draw()では、drawImage関数は大きな画像の指定された部分を切り取ります。

drawImageは、その「トリミングされた背景」をキャンバス上のユーザーに表示します。

context.clearRect(0,0,game.width,game.height);
context.drawImage(background,cropLeft,cropTop,cropWidth,cropHeight,
                     0,0,viewWidth,viewHeight);

この例では、

背景は完全な背景画像です(通常は表示されませんが、トリミングのソースです)。

cropLeftとcropTopは、背景画像のどこからトリミングを開始するかを定義します。

cropWidthとcropHeightは、背景画像から切り取られる長方形の大きさを定義します。

0,0は、背景から切り取られたサブイメージがビューポートキャンバスの0,0に描画されることを示します。

viewWidthとviewHeightは、ビューポートキャンバスの幅と高さです

数値を使用したdrawImageの例を次に示します。

ビューポート(=ディスプレイキャンバス)が幅150ピクセル、高さ100ピクセルだとします。

context.drawImage(background,75,50,150,100,0,0,150,100);

75と50は、背景画像の位置x = 75/y = 50でトリミングが開始されると言います。

150,100は、切り取られる長方形が幅150、高さ100になると言います。

0,0,150,100は、切り取られた長方形の画像がビューポートキャンバスのフルサイズを使用して表示されることを示します。

ビューポートを描く仕組みについてはこれで終わりです...キーコントロールを追加するだけです!

ここにコードとフィドルがあります: http://jsfiddle.net/m1erickson/vXqyc/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>

<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var game=document.getElementById("game");
    var gameCtx=game.getContext("2d");

    var left=20;
    var top=20;

    var background=new Image();
    background.onload=function(){
        canvas.width=background.width/2;
        canvas.height=background.height/2;
        gameCtx.fillStyle="red";
        gameCtx.strokeStyle="blue";
        gameCtx.lineWidth=3;
        ctx.fillStyle="red";
        ctx.strokeStyle="blue";
        ctx.lineWidth=3;
        move(top,left);
    }
    background.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/game.jpg";


    function move(direction){
        switch (direction){
            case "left":
                left-=5;
                break;
            case "up":
                top-=5;
                break;
            case "right":
                left+=5;
                break;
            case "down":
                top+=5
                break;
        }
        draw(top,left);
    }

    function draw(top,left){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.drawImage(background,0,0,background.width,background.height,0,0,canvas.width,canvas.height);
        gameCtx.clearRect(0,0,game.width,game.height);
        gameCtx.drawImage(background,left,top,250,150,0,0,250,150);
        gameCtx.beginPath();
        gameCtx.arc(125,75,10,0,Math.PI*2,false);
        gameCtx.closePath();
        gameCtx.fill();
        gameCtx.stroke();
        ctx.beginPath();
        ctx.rect(left/2,top/2,125,75);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(left/2+125/2,top/2+75/2,5,0,Math.PI*2,false);
        ctx.stroke();
        ctx.fill();
    }

    $("#moveLeft").click(function(){move("left");}); 
    $("#moveRight").click(function(){move("right");}); 
    $("#moveUp").click(function(){move("up");}); 
    $("#moveDown").click(function(){move("down");}); 

}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="game" width=250 height=150></canvas><br>
    <canvas id="canvas" width=500 height=300></canvas><br>
    <button id="moveLeft">Left</button>
    <button id="moveRight">Right</button>
    <button id="moveUp">Up</button>
    <button id="moveDown">Down</button>
</body>
</html>
5
markE

今のあなたのやり方は私には正しいようです。ただし、 "20"の境界を変数に変更するので、必要に応じてレベルまたはゲーム全体の境界を簡単に変更できます。

このロジックを特定の「ビューポート」メソッドに抽象化し、「カメラ」をマップ上のどこに配置する必要があるかを判断するために必要な計算を処理し、キャラクターのX座標とY座標があなたのカメラ。

また、そのメソッドを反転し、文字の位置に基づいてカメラの位置を決定することもできます(例:(position.x - (desired_camera_size.width / 2)))そこからカメラを描きます。

カメラの位置がわかれば、部屋自体をキャンバスの最初のレイヤーとして描くことを心配することができます。

1

これは、ビューポートを各フレームでターゲットのxおよびy座標 コルトンの状態として に設定する簡単な問題です。変換は必要ありませんが、必要に応じて使用できます。私のために働いた式は:

function update() {

  // Assign the viewport to follow a target for this frame
  viewport.x = -target.x + canvas.width / 2;
  viewport.y = -target.y + canvas.height / 2;

  // Draw each entity, including the target, relative to the viewport
  ctx.fillRect(
    entity.x + viewport.x, 
    entity.y + viewport.y,
    entity.size,
    entity.size
  );
}

マップへのクランプはオプションの2番目のステップです。

function update() {

  // Assign the viewport to follow a target for this frame
  viewport.x = -target.x + canvas.width / 2;
  viewport.y = -target.y + canvas.height / 2;

  // Keep viewport in map bounds
  viewport.x = clamp(viewport.x, canvas.width - map.width, 0);
  viewport.y = clamp(viewport.y, canvas.height - map.height, 0);

  // Draw each entity, including the target, relative to the viewport
  ctx.fillRect(
    entity.x + viewport.x, 
    entity.y + viewport.y,
    entity.size,
    entity.size
  );
}

// Restrict n to a range between lo and hi
const clamp = (n, lo, hi) => n < lo ? lo : n > hi ? hi : n;

次に例を示します。 https://jsfiddle.net/ggorlen/7yv7u572/

1
ggorlen

@ gustavo-carvalhoのソリューションは驚異的ですが、大規模な計算と認知オーバーヘッドが伴います。 @Coltonのアプローチは正しい方向への一歩です。残念なことに、彼の答えでは十分に詳しく説明されていませんでした。私は彼のアイデアを受け取り、それを使って このCodePen を作成しました。 @ user2337969がcontext.translateの使用を求めているものを正確に達成します。利点は、マップやプレーヤーの座標をオフセットする必要がないため、それらの描画はxおよびyを直接使用するのと同じくらい簡単で、はるかに簡単です。

2Dカメラは、より大きなマップ内でパンする長方形と考えてください。その左上隅はマップの(x, y)座標にあり、そのサイズはキャンバスのサイズ、つまりcanvas.widthcanvas.heightです。つまり、xの範囲は0からmap.width - canvas.width、およびyから0からmap.height - canvas.height(包括的)です。これらは、@ Coltonのminメソッドにフィードするmaxclampです。

ただし、それを機能させるには、xyの記号を反転する必要がありました。なぜなら、 context.translate で、正の値はキャンバスを右(カメラが左にパンするように錯覚を作る)と負-左(カメラが右にパンするように)。

0
Alex