HTML5 Canvas에서 페인트 될 2D 배열의 텍스트 맵 관리


46

그래서 재미있게 HTML5 RPG를 만들고 있습니다. 지도는 <canvas>(512px 너비, 352px 높이 | 가로 16 타일, 위 아래 11 타일)입니다. 을 칠하는 더 효율적인 방법이 있는지 알고 싶습니다 <canvas>.

여기 내가 지금 가진 방법이 있습니다.

지도에서 타일을로드하고 페인트하는 방법

지도는 Image()조각을 사용하여 타일 (32x32)로 페인트됩니다 . 이미지 파일은 간단한 for루프를 통해로드 tiles[]되고를 사용하여 PAINTED 라고하는 배열에 배치됩니다 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구조를 사용하여 다른지도를 얻습니다 . 위의 2d 배열 returnImage()저장 되면 각 배열의 해당 숫자가 내부 에 저장된 대로 페인트됩니다 tile[]. 그리고 drawImage()발생과에 따라 페인트 것입니다 xy및 번으로 32올바른에 페인트하는 x-y좌표입니다.

다중 맵 전환이 발생하는 방법

내 게임으로,지도 추적 할 다섯 가지가 있습니다 currentID, leftID, rightID, upID,와 bottomID.

  • currentID : 현재지도의 현재 ID입니다.
  • leftID :currentID 현재지도의 왼쪽에서 나갈 때로드 할 ID입니다 .
  • rightID :currentID 현재지도의 오른쪽에서 나갈 때로드 할 ID입니다 .
  • downID :currentID 현재지도 하단에서 나갈 때로드 할 ID입니다 .
  • upID :currentID 현재지도 상단에서 나갈 때로드 할 ID입니다 .

참고로 뭔가 : 하나하면 leftID, rightID, upID, 또는 bottomID특정하지 않은 수단 것을 그들이 있습니다 0. 그것은 그들이지도의 측면을 떠날 수 없다는 것을 의미합니다. 그것은 단지 보이지 않는 봉쇄입니다.

그래서, 한 사람 한 번 그들은 바닥에 종료하는 경우가 예를 들어 ... 종료 위치에 따라지도의 측면을 종료, bottomID의 수는 것이다 map로드 따라서지도에 그려진 될 수있다.

보다 나은 시각화를 지원하는 표현형 .GIF는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

보시다시피 조만간 많은지도로 많은 ID를 다룰 것 입니다. 그리고 그것은 약간 혼란스럽고 열렬 할 수 있습니다.

확실한 장점은 한 번에 176 개의 타일을로드하고 작은 512x352 캔버스를 새로 고치고 한 번에 하나의 맵을 처리한다는 것입니다. 많은지도를 다룰 때 MAP ID가 때때로 혼동 될 수 있다는 단점이 있습니다.

내 질문

  • 이것은 타일을 사용하여 맵을 저장하는 효율적인 방법입니까, 아니면 맵을 처리하는 더 좋은 방법이 있습니까?

나는 거대한지도의 선을 따라 생각하고 있었다. 지도 크기는 크고 모두 하나의 2D 배열입니다. 그러나 뷰포트는 여전히 512x352 픽셀입니다.

시각화를 돕기 위해 (이 질문에 대해) 만든 또 다른 .gif가 있습니다.

여기에 이미지 설명을 입력하십시오

내 영어를 이해할 수 없다면 죄송합니다. 이해하기 어려운 것이 있으면 문의하십시오. 잘만되면 나는 그것을 명확히했다. 감사.


16
노력은 그래픽과 함께이 질문에 넣어 모든 공평한 가치가 있습니다. :)
Tapio

답변:


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;
}

자바 스크립트 표현은 다음과 같습니다

<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를 사용해야합니다.

내가 고려해야 할 한 가지는 (자바 스크립트에서 중요한지 확실하지 않지만) 많은 단일 파일을 사용하는 대신 모든 타일 (또는 가능한 한 많은)을 하나의 큰 텍스처에 넣는 것입니다.

수행 방법을 설명하는 링크는 다음과 같습니다. http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/


명확히하기 위해 ... viewport.x는 "531"로 표시되고 viewport.y는 "352"이고 tileize = "32"입니다. 그리고 ..?
테스트

카메라의 위치가 531임을 의미하면 예입니다. 위 코드에 주석을 추가했습니다.
톰 반 그린

JavaScript를 사용하고 있습니다 ... Java와 거의 동일합니다.
테스트

예, js 기반 뷰포트 클래스를 만들어야합니다 (또는 4, 변수 x, y, w 및 h를 만들 수 있습니다). 또한 int 값에 할당 된 계산을 바닥에 놓아야합니다 (Math.floor). 뷰포트 클래스의 모양에 대한 코드 예제를 추가했습니다. 사용하기 전에 선언을했는지 확인하십시오.
톰 반 그린

그냥 당신이 넣어 640, 480..하지만 그것은 제대로 작동 할 수 있다고 말하는가 512, 352?
테스트

3

맵을 타일로 저장하면 자산을 재사용하고 맵을 다시 디자인 할 수 있습니다. 현실적으로 플레이어에게 많은 이점을 제공하지는 않습니다. 또한 매번 모든 타일을 다시 그립니다. 내가 할 일은 다음과 같습니다.

전체지도를 하나의 큰 배열로 저장하십시오. 맵과 서브맵, 잠재적으로 서브 서브맵이있는 점은 없습니다 (예를 들어 건물에 들어가는 경우). 당신은 어쨌든 그것을로드하고 있으며 이것은 모든 것을 멋지고 단순하게 유지합니다.

지도를 한 번에 렌더링합니다. 맵을 렌더링 한 오프 스크린 캔버스를 만들어 게임에서 사용하십시오. 이 페이지 는 간단한 자바 스크립트 함수로이를 수행하는 방법을 보여줍니다.

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 맵은 176KB임을 명심하십시오) 이와 같이 맵을 렌더링하면 대부분의 브라우저에서 큰 성능 향상을 볼 수 있습니다 .

이것이 제공하는 것은 하나의 큰 이미지를 편집하는 데 어려움을 겪지 않고 전 세계에서 타일을 재사용 할 수있는 유연성뿐만 아니라 빠르고 쉽게 실행할 수 있으며 하나의 '지도'(또는 원하는만큼의 '지도') 만 고려할 수 있다는 것입니다 .



1

ID를 추적하는 쉬운 방법은 다른 2D 배열을 사용하는 것입니다. 해당 "메타 배열"에서 x, y 만 유지하면 다른 ID를 쉽게 찾을 수 있습니다.

즉, 최근 I / O 2012에서 2D 타일 캔버스 게임 (실제로는 HTML5 멀티 플레이어이지만 타일 엔진도 포함됨)에 대한 Google의 의견은 다음과 같습니다. http://www.youtube.com/watch?v=Prkyd5n0P7k It 소스 코드도 제공됩니다.


1

전체 맵을 먼저 배열에로드하는 아이디어는 효율적인 방법이지만 JavaScript Injection을 쉽게 얻을 수 있으며 누구나 맵을 알고 모험을 시작할 수 있습니다.

RPG 게임 웹 기반에서도 작업 중입니다.지도를로드하는 방식은 PHP 페이지에서 Ajax를 통해 이루어지며 데이터베이스에서 맵을로드합니다 .X, Y, Z 위치 및 vlaue 타일을 긋고 마지막으로 플레이어를 움직입니다.

나는 그것을보다 효율적으로 만들기 위해 여전히 노력하고 있지만지도는 지금 까지이 방법을 유지할 수있을 정도로 빨리로드됩니다.


1
데모를 볼 수 있습니까?
테스트

1

아니 , 배열은 타일 맵을 저장하는 가장 효율적인 방법입니다 .

메모리를 조금 덜 필요로하지만 데이터를로드하고 액세스하는 데 비용이 많이 드는 구조가 몇 개있을 수 있습니다.


그러나 다음 맵을로드 할 때 고려해야 할 사항이 있습니다. 맵이 특별히 크지 않은 경우, 실제로 가장 오래 걸리는 것은 서버가 맵을 전달하기를 기다리는 것입니다. 실제 로딩에는 몇 밀리 초 밖에 걸리지 않습니다. 그러나이 대기는 비동기 적으로 수행 할 수 있으며 게임의 흐름을 차단 할 필요가 없습니다. 따라서 가장 좋은 것은 필요할 때가 아니라 전에 데이터를로드하는 것입니다. 플레이어는 하나의 맵에 있으며 이제 모든 인접한 맵을로드합니다. 플레이어는 다음 맵으로 이동하고 모든 인접한 맵 등을 다시로드합니다. 플레이어는 게임이 백그라운드에서로드되고 있음을 알지 못합니다.

이 방법으로 인접 맵도 그려서 경계가 적은 세계의 환상을 만들 수 있습니다.이 경우 인접 맵뿐만 아니라 인접 맵도로드해야합니다.


0

왜 그렇게 생각하는지 잘 모르겠습니다. 조만간 많은지도에서 볼 수 있듯이 많은 ID를 다룰 것입니다. 그리고 그것은 약간 혼란스럽고 열렬 할 수 있습니다.

LeftID는 항상 currentID의 -1이되고 rightID는 항상 currentID의 +1이됩니다.

UpID는 항상 현재 ID의-(총 맵 너비)이고 downID는 항상 0을 제외하고는 현재 ID의 + (총 맵 너비)입니다.

단일 맵은 여러 맵으로 나눌 수 있으며 xxxX가 id 인 mapIDXXXX와 함께 순차적으로 저장됩니다. 그렇게하면 혼란 스러울 것이 없습니다. 귀하의 사이트를 방문한 결과 잘못된 것으로 보입니다. 문제는지도 편집기에 따라 크기 제한이 적용되어 큰지도를 저장시 여러 개의 작은지도로 분해하는 데 방해가되며 크기 제한이 있습니다. 아마도 기술 솔루션을 제한 할 것입니다.

나는 모든 방향으로 스크롤하는 Javascript 맵 편집기를 작성했습니다 .1000 x 1000 (크기를 권장하지는 않습니다) 타일은 여전히 ​​50 x 50 맵만큼 훌륭하게 움직이며 시차 스크롤을 포함하여 3 층입니다. json 형식으로 저장하고 읽습니다. 약 2meg의 이메일이 마음에 들지 않으면 사본을 보내드립니다. 그것은 github에 있어야하지만 괜찮은 (법적) 타일 세트를 얻지 못했기 때문에 아직 거기에 넣지 않아도됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.