การวาดโลกเกมที่มีมิติเท่ากัน


178

วิธีที่ถูกต้องในการวาดไทล์กระเบื้องในสามมิติคืออะไร?

ฉันอ่านการอ้างอิง (เช่นอันนี้ ) ที่แนะนำการเรียงลำดับไพ่ในลักษณะที่จะซิกแซกในแต่ละคอลัมน์ในการแสดงอาร์เรย์ 2 มิติของแผนที่ ฉันคิดว่าพวกเขาควรจะวาดในรูปแบบของเพชรมากขึ้นซึ่งสิ่งที่ถูกดึงดูดไปที่หน้าจอนั้นจะเกี่ยวข้องอย่างใกล้ชิดยิ่งขึ้นกับสิ่งที่อาเรย์ 2 มิติจะมีลักษณะเหมือนหมุนเพียงเล็กน้อย

มีข้อดีหรือข้อเสียของวิธีการอย่างใดอย่างหนึ่ง?

คำตอบ:


504

อัปเดต: อัลกอริทึมการเรนเดอร์แผนที่ที่ถูกต้อง, เพิ่มภาพประกอบมากขึ้น, เปลี่ยนรูปแบบ

บางทีความได้เปรียบของเทคนิค "ซิกแซก" สำหรับการทำแผนที่กระเบื้องไปที่หน้าจออาจกล่าวได้ว่าการเรียงตัวของกระเบื้องxและyพิกัดนั้นอยู่ในแกนตั้งและแนวนอน

วิธีการ "วาดภาพในเพชร":

โดยการวาดแผนที่แบบวาดภาพสามมิติโดยใช้ "การวาดในเพชร" ซึ่งฉันเชื่อว่าหมายถึงเพียงการแสดงผลแผนที่โดยใช้ซ้อน - forห่วงเหนืออาร์เรย์สองมิติเช่นตัวอย่างนี้:

tile_map[][] = [[...],...]

for (cellY = 0; cellY < tile_map.size; cellY++):
    for (cellX = 0; cellX < tile_map[cellY].size cellX++):
        draw(
            tile_map[cellX][cellY],
            screenX = (cellX * tile_width  / 2) + (cellY * tile_width  / 2)
            screenY = (cellY * tile_height / 2) - (cellX * tile_height / 2)
        )

ความได้เปรียบ:

ข้อดีของวิธีนี้คือมันใช้งานได้ง่าย forมีตรรกะไปข้างหน้าค่อนข้างตรงไปตรงมาซึ่งทำงานได้อย่างต่อเนื่องตลอดทั้งแผ่นกระเบื้อง

ข้อด้อย:

ข้อเสียอย่างหนึ่งของวิธีการนี้คือxและyพิกัดของแผ่นกระเบื้องบนแผนที่จะเพิ่มขึ้นในแนวเส้นทแยงมุมซึ่งอาจทำให้ยากต่อการมองเห็นตำแหน่งบนหน้าจอไปยังแผนที่ที่แสดงเป็นอาร์เรย์:

รูปภาพของแผนที่ย่อย

อย่างไรก็ตามจะมีข้อผิดพลาดในการใช้โค้ดตัวอย่างด้านบน - ลำดับการเรนเดอร์จะทำให้กระเบื้องที่ควรจะอยู่ด้านหลังไทล์บางอันถูกลากไปด้านบนของไทล์:

ส่งผลให้ภาพจากลำดับการแสดงผลไม่ถูกต้อง

ในการที่จะแก้ไขปัญหานี้คำสั่งภายในfor-loop จะต้องถูกกลับรายการ - เริ่มจากมูลค่าสูงสุดและแสดงไปยังค่าที่ต่ำกว่า:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    for (j = tile_map[i].size; j >= 0; j--):  // Changed loop condition here.
        draw(
            tile_map[i][j],
            x = (j * tile_width / 2) + (i * tile_width / 2)
            y = (i * tile_height / 2) - (j * tile_height / 2)
        )

ด้วยการแก้ไขข้างต้นการแสดงผลของแผนที่ควรได้รับการแก้ไข:

ส่งผลให้ภาพจากลำดับการแสดงผลที่ถูกต้อง

วิธีการ "ซิกแซก"

ความได้เปรียบ:

บางทีข้อดีของวิธีการ "ซิกแซก" คือแผนที่ที่เรนเดอร์อาจดูเหมือนจะเล็กกว่าแนวตั้งเล็กน้อยกว่าวิธี "ไดมอนด์":

วิธี Zig-zag ในการเรนเดอร์ดูเหมือนว่ากะทัดรัด

ข้อด้อย:

จากการพยายามใช้เทคนิค zig-zag ข้อเสียอาจเป็นว่ามันยากกว่านิดหน่อยในการเขียนรหัสการเรนเดอร์เพราะมันไม่สามารถเขียนได้อย่างง่ายเหมือนซ้อนกัน - forลูปเหนือแต่ละองค์ประกอบในอาเรย์:

tile_map[][] = [[...],...]

for (i = 0; i < tile_map.size; i++):
    if i is odd:
        offset_x = tile_width / 2
    else:
        offset_x = 0

    for (j = 0; j < tile_map[i].size; j++):
        draw(
            tile_map[i][j],
            x = (j * tile_width) + offset_x,
            y = i * tile_height / 2
        )

นอกจากนี้อาจเป็นเรื่องยากเล็กน้อยที่จะพยายามคิดหาพิกัดของไทล์เนื่องจากลักษณะของลำดับการเรนเดอร์:

พิกัดบนการแสดงผลคำสั่ง zig-zag

หมายเหตุ: ภาพประกอบที่รวมอยู่ในคำตอบนี้ถูกสร้างขึ้นด้วยการนำ Java มาใช้ของรหัสการเรนเดอร์ที่แสดงโดยมีintอาร์เรย์ต่อไปนี้เป็นแผนที่:

tileMap = new int[][] {
    {0, 1, 2, 3},
    {3, 2, 1, 0},
    {0, 0, 1, 1},
    {2, 2, 3, 3}
};

ภาพย่อยคือ:

  • tileImage[0] -> กล่องที่มีกล่องด้านใน
  • tileImage[1] -> กล่องดำ
  • tileImage[2] -> กล่องสีขาว
  • tileImage[3] -> กล่องที่มีวัตถุสีเทาสูงอยู่ในนั้น

หมายเหตุเกี่ยวกับความกว้างและความสูงของกระเบื้อง

ตัวแปรtile_widthและtile_heightสิ่งที่ใช้ในตัวอย่างโค้ดข้างต้นอ้างถึงความกว้างและความสูงของการเรียงต่อกันในภาพที่แสดงถึงการเรียงต่อกัน:

ภาพแสดงความกว้างและความสูงของกระเบื้อง

การใช้ขนาดของภาพจะใช้งานได้ตราบใดที่ขนาดภาพและขนาดกระเบื้องตรงกัน มิฉะนั้นแมปกระเบื้องอาจถูกแสดงด้วยช่องว่างระหว่างแผ่น


136
คุณวาดรูปได้ นั่นคือความพยายาม
zaratustra

ไชโย coobird ฉันใช้วิธีเพชรสำหรับเกมปัจจุบันที่ฉันพัฒนาและใช้การได้ดี ขอบคุณอีกครั้ง.
Benny Hallett

3
แล้วความสูงหลายชั้นล่ะ? ฉันสามารถวาดมันให้ยิ้มได้โดยเริ่มจากระดับต่ำสุดและดำเนินต่อไปจนถึงระดับสูงสุดหรือไม่
NagyI

2
@DomenicDatti: ขอขอบคุณสำหรับคำชนิดของคุณ :)
coobird

2
นี่มันเจ๋งมาก. ฉันเพียงแค่ใช้วิธีการเพชรของคุณและยังอยู่ในความต้องการของใครบางคนที่จะได้รับกรณี coords j = (2 * x - 4 * y) / tilewidth * 0.5; i = (p.x * 2 / tilewidth) - j;ตารางจากตำแหน่งหน้าจอผมทำอย่างนี้:
Kyr Dunenkoff

10

การทำงานให้สำเร็จลุล่วง ฉันคิดว่าโดยซิกแซกคุณหมายถึงสิ่งนี้: (ตัวเลขเป็นลำดับของการแสดงผล)

..  ..  01  ..  ..
  ..  06  02  ..
..  11  07  03  ..
  16  12  08  04
21  17  13  09  05
  22  18  14  10
..  23  19  15  ..
  ..  24  20  ..
..  ..  25  ..  ..

และโดยเพชรคุณหมายถึง:

..  ..  ..  ..  ..
  01  02  03  04
..  05  06  07  ..
  08  09  10  11
..  12  13  14  ..
  15  16  17  18
..  19  20  21  ..
  22  23  24  25
..  ..  ..  ..  ..

วิธีแรกต้องการการเรนเดอร์ที่มากขึ้นเพื่อให้วาดแบบเต็มหน้าจอ แต่คุณสามารถทำการตรวจสอบขอบเขตและข้ามไทล์ใดก็ได้แบบเต็มหน้าจอ ทั้งสองวิธีจะต้องมีการบดตัวเลขเพื่อหาตำแหน่งของไทล์ 01 ในท้ายที่สุดทั้งสองวิธีมีความเท่าเทียมกันในแง่ของคณิตศาสตร์ที่จำเป็นสำหรับประสิทธิภาพในระดับหนึ่ง


14
จริง ๆ แล้วฉันหมายถึงวิธีอื่น ๆ รูปทรงเพชร (ซึ่งทำให้ขอบของแผนที่ราบเรียบ) และวิธีการซิกแซกทำให้ขอบแหลมคมออกมา
Benny Hallett

1

หากคุณมีแผ่นกระเบื้องที่เกินขอบเขตของเพชรของคุณฉันขอแนะนำให้วาดตามลำดับความลึก:

...1...
..234..
.56789.
..abc..
...d...

1

คำตอบของ Coobird นั้นถูกต้องครบถ้วน อย่างไรก็ตามฉันได้รวมคำแนะนำของเขากับผู้ที่มาจากเว็บไซต์อื่นเพื่อสร้างรหัสที่ทำงานในแอพของฉัน (iOS / Objective-C) ซึ่งฉันต้องการแบ่งปันกับทุกคนที่มาที่นี่เพื่อค้นหาสิ่งนั้น กรุณาถ้าคุณต้องการ / โหวตขึ้นคำตอบทำเช่นเดียวกันสำหรับต้นฉบับ; ทั้งหมดที่ฉันทำคือ "ยืนบนไหล่ของยักษ์"

สำหรับการเรียงลำดับเทคนิคของฉันเป็นอัลกอริทึมของจิตรกรที่แก้ไข: แต่ละวัตถุมี (a) ความสูงของฐาน (ฉันเรียกว่า "ระดับ") และ (b) X / Y สำหรับ "ฐาน" หรือ "เท้า" ของ ภาพ (ตัวอย่าง: ฐานของอวตารอยู่ที่เท้าของเขาฐานของต้นไม้อยู่ที่รากของมันฐานของเครื่องบินอยู่ตรงกลางภาพ ฯลฯ ) จากนั้นฉันก็เรียงลำดับต่ำสุดถึงระดับสูงสุดจากนั้นต่ำสุด (สูงสุดบนหน้าจอ) ถึงฐานสูงสุด - Y จากนั้นต่ำสุด (ซ้ายสุด) ถึงสูงสุด -X วิธีนี้ทำให้กระเบื้องเรียงตามที่เราคาดไว้

รหัสสำหรับแปลงหน้าจอ (จุด) เป็นไทล์ (เซลล์) และย้อนกลับ:

typedef struct ASIntCell {  // like CGPoint, but with int-s vice float-s
    int x;
    int y;
} ASIntCell;

// Cell-math helper here:
//      http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511
// Although we had to rotate the coordinates because...
// X increases NE (not SE)
// Y increases SE (not SW)
+ (ASIntCell) cellForPoint: (CGPoint) point
{
    const float halfHeight = rfcRowHeight / 2.;

    ASIntCell cell;
    cell.x = ((point.x / rfcColWidth) - ((point.y - halfHeight) / rfcRowHeight));
    cell.y = ((point.x / rfcColWidth) + ((point.y + halfHeight) / rfcRowHeight));

    return cell;
}


// Cell-math helper here:
//      http://stackoverflow.com/questions/892811/drawing-isometric-game-worlds/893063
// X increases NE,
// Y increases SE
+ (CGPoint) centerForCell: (ASIntCell) cell
{
    CGPoint result;

    result.x = (cell.x * rfcColWidth  / 2) + (cell.y * rfcColWidth  / 2);
    result.y = (cell.y * rfcRowHeight / 2) - (cell.x * rfcRowHeight / 2);

    return result;
}

1

คุณสามารถใช้ระยะทางแบบยุคลิดจากจุดที่ผู้ชมสูงสุดและใกล้ที่สุดยกเว้นว่าจะไม่ถูกต้องนัก มันส่งผลในการจัดเรียงทรงกลม คุณสามารถยืดออกได้โดยมองจากที่ไกล ๆ ไกลออกไปความโค้งจะแบนออก ดังนั้นเพียงแค่เพิ่มว่า 1,000 เข้าไปในองค์ประกอบ x, y และ z แต่ละตัวเพื่อให้ x ', y' และ z ' การเรียงลำดับบน x '* x' + y '* y' + z '* z'


0

ปัญหาที่แท้จริงคือเมื่อคุณจำเป็นต้องวาดแผ่นกระเบื้อง / สไปรต์ที่ตัดกัน / ขยายสองแผ่นหรือมากกว่านั้น

หลังจาก 2 (ยาก) เดือนของการวิเคราะห์ส่วนบุคคลของปัญหาในที่สุดฉันก็พบและใช้ "การวาดภาพเรนเดอร์ที่ถูกต้อง" สำหรับเกม cocos2d-js ใหม่ของฉัน โซลูชันประกอบด้วยการทำแผนที่สำหรับแต่ละไทล์ (ไวต่อการสัมผัส) ซึ่งสไปรต์คือ "ด้านหน้าด้านหลังด้านบนและด้านหลัง" เมื่อทำเช่นนั้นคุณสามารถวาดพวกเขาตาม "ตรรกะซ้ำ"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.