การออกแบบแผนที่ทั้งหมดกับการออกแบบอาร์เรย์แบบเรียงต่อกัน


11

ฉันกำลังทำงานกับ RPG 2 มิติซึ่งจะแสดงแผนที่ดันเจี้ยน / เมืองปกติ (สร้างขึ้นล่วงหน้า)

ฉันกำลังใช้ไทล์แล้วฉันจะรวมกันเพื่อทำแผนที่ แผนเดิมของฉันคือการรวบรวมกระเบื้องโดยใช้ Photoshop หรือโปรแกรมกราฟิกอื่น ๆ เพื่อให้ได้ภาพที่ใหญ่กว่าที่ฉันสามารถใช้เป็นแผนที่ได้

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

วิธีที่พบมากที่สุดคืออะไรและอะไรคือข้อดี / ข้อเสียของแต่ละวิธี?


1
ปัญหาหลักคือหน่วยความจำ พื้นผิวที่เติมเต็มหน้าจอ 1080p ครั้งเดียวคือประมาณ 60mb ดังนั้นภาพจึงใช้ int ต่อพิกเซลไทล์จึงใช้ int ต่อ (pixel / (tilesize * tilesize)) ดังนั้นถ้าแผ่นกระเบื้องมี 32,32 คุณสามารถแสดงแผนที่ 1024 ครั้งที่มีขนาดใหญ่โดยใช้กระเบื้องเทียบกับพิกเซล นอกจากนี้เวลาในการเปลี่ยนพื้นผิวก็จะส่งผลกระทบต่อการผลักดันความทรงจำมากมายรอบตัว ฯลฯ ...
ClassicThunder

คำตอบ:


19

ก่อนอื่นให้ฉันบอกว่าเกม RPG 2 มิติใกล้เข้ามาแล้วและทำงานกับเครื่องยนต์ DX7 VB6 MORPG รุ่นเก่า (อย่าหัวเราะมันเมื่อ 8 ปีก่อนตอนนี้ :-)) เป็นสิ่งแรกที่ทำให้ฉันสนใจในการพัฒนาเกม . ไม่นานมานี้ฉันเริ่มแปลงเกมที่ฉันทำงานด้วยหนึ่งในเอ็นจินเหล่านั้นเพื่อใช้ XNA

ที่กล่าวว่าคำแนะนำของฉันคือการที่คุณใช้โครงสร้างแบบเรียงต่อกันกับเลเยอร์สำหรับแผนที่ของคุณ ด้วย API กราฟิกใด ๆ ที่คุณใช้คุณจะต้อง จำกัด ขนาดของพื้นผิวที่คุณสามารถโหลดได้ ไม่ต้องพูดถึงขีด จำกัด ของหน่วยความจำพื้นผิวกราฟิกการ์ด ดังนั้นเมื่อพิจารณาถึงสิ่งนี้หากคุณต้องการเพิ่มขนาดแผนที่ให้ใหญ่ที่สุดในขณะที่ไม่เพียง แต่ลดจำนวนและขนาดของพื้นผิวที่คุณโหลดลงในหน่วยความจำ แต่ยังลดขนาดของสินทรัพย์ของคุณลงในฮาร์ดไดรฟ์ของผู้ใช้ จะต้องไปกับกระเบื้องแน่นอน

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

  1. สิ่งแรกที่คุณต้องทำคือสร้าง tilesheet นี่เป็นเพียงภาพขนาดใหญ่ที่มีการเรียงต่อกันทั้งหมดของคุณอยู่ในตาราง สิ่งนี้ (และอาจเพิ่มได้ขึ้นอยู่กับจำนวนของไพ่) จะเป็นสิ่งเดียวที่คุณต้องโหลด เพียง 1 ภาพ! คุณสามารถโหลด 1 ต่อแผนที่หรือหนึ่งในทุกไทล์ในเกม องค์กรใดที่เหมาะกับคุณ
  2. ถัดไปคุณต้องเข้าใจว่าคุณสามารถใช้ "ชีต" นั้นและแปลไทล์แต่ละแผ่นเป็นตัวเลขได้อย่างไร มันค่อนข้างตรงไปตรงมากับคณิตศาสตร์ง่าย ๆ โปรดทราบว่าการหารที่นี่คือการหารจำนวนเต็มดังนั้นตำแหน่งทศนิยมจะลดลง (หรือปัดเศษลงหากคุณต้องการ) วิธีแปลงเซลล์ให้เป็นพิกัดและย้อนกลับ
  3. ตกลงตอนนี้คุณได้แบ่งแผ่นงานเป็นชุดของเซลล์ (ตัวเลข) แล้วคุณสามารถนำตัวเลขเหล่านั้นมาใส่ในภาชนะที่คุณต้องการ เพื่อความเรียบง่ายคุณสามารถใช้อาร์เรย์ 2 มิติ

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. ถัดไปคุณต้องการวาด อีกวิธีหนึ่งที่คุณสามารถทำให้สิ่งนี้มีประสิทธิภาพมากขึ้น (ขึ้นอยู่กับขนาดแผนที่) คือการคำนวณเฉพาะเซลล์ที่กล้องกำลังดูและวนซ้ำในขณะนั้น คุณสามารถทำได้โดยดึงข้อมูลพิกัดอาร์เรย์ไทล์แผนที่ของมุมบนซ้าย ( tl) และมุมขวาล่าง ( br) ของกล้อง จากนั้นวนซ้ำจากtl.X to br.Xและในลูปที่ซ้อนกันจากtl.Y to br.Yเพื่อวาด รหัสตัวอย่างด้านล่าง:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. แจ๊คพ็อ! นั่นคือพื้นฐานของเอ็นจิ้นไทล์ คุณจะเห็นว่าเป็นเรื่องง่ายที่จะมีแม้แต่แผนที่ขนาด 1000x1000 ที่มีค่าใช้จ่ายไม่มากนัก นอกจากนี้ถ้าคุณมีไพ่น้อยกว่า 255 แผ่นคุณสามารถใช้อาร์เรย์ไบต์ลดหน่วยความจำลงได้ 3 ไบต์ต่อเซลล์ ถ้า byte มีขนาดเล็กเกินไป ushort อาจเพียงพอสำหรับความต้องการของคุณ

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

แหล่งข้อมูล Tile-Engine ของฉัน
หมายเหตุ: สิ่งเหล่านี้มีการกำหนดเป้าหมายที่ XNA แต่มันมีผลกับทุกสิ่งมาก - คุณเพียงแค่ต้องเปลี่ยนการเรียกสาย

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

ทรัพยากร Tile-Engine อื่น ๆ

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

ว้าวนั่นเป็นข้อมูลที่มากกว่าที่ฉันถามเป็นสองเท่าและค่อนข้างตอบคำถามทั้งหมดที่ฉันไม่ได้ถามยอดเยี่ยมขอบคุณ :)
Cristol.GdM

@Mikalichov - ดีใจที่ฉันสามารถช่วยได้! :)
Richard Marskell - Drackir

โดยทั่วไปแล้วกับอาร์เรย์ 2 มิติที่คุณต้องการใช้มิติแรกเป็น Y ของคุณและถัดไปเป็น X ของคุณในหน่วยความจำอาร์เรย์จะเรียงตามลำดับ 0,0-i และ 1,0-i หากคุณวนซ้ำลูปกับไก่ตัวแรกคุณจะกระโดดไปมาในหน่วยความจำแทนที่จะตามเส้นทางการอ่านตามลำดับ เสมอสำหรับ (y) จากนั้นสำหรับ (x)
Brett W

@BrettW - จุดที่ถูกต้อง ขอบคุณ ฉันทำให้ฉันสงสัยว่าการจัดเก็บอาร์เรย์ 2 มิติใน. Net (คำสั่งแบบแถวหลักได้เรียนรู้สิ่งใหม่วันนี้! :-)) อย่างไรก็ตามหลังจากอัปเดตรหัสของฉันฉันรู้ว่ามันเหมือนกับสิ่งที่คุณกำลังอธิบายเพียงแค่เปลี่ยนตัวแปร ความแตกต่างคือสิ่งที่ลำดับการวาดไพ่ โค้ดตัวอย่างปัจจุบันของฉันดึงจากบนลงล่างซ้ายไปขวาในขณะที่สิ่งที่คุณอธิบายจะวาดจากซ้ายไปขวาบนลงล่าง ดังนั้นเพื่อความเรียบง่ายฉันจึงตัดสินใจเปลี่ยนกลับเป็นต้นฉบับ :-)
Richard Marskell - Drackir

6

ทั้งระบบที่ใช้กระเบื้องและรูปแบบคงที่ / ระบบพื้นผิวสามารถใช้เพื่อเป็นตัวแทนของโลกและแต่ละคนมีจุดแข็งที่แตกต่างกัน ไม่ว่าจะดีไปกว่าการใช้ฝีไม้ลายมืออื่น ๆ และสิ่งที่ดีที่สุดสำหรับชุดทักษะและความต้องการของคุณ

ที่ถูกกล่าวว่าทั้งสองระบบสามารถใช้แยกกันหรือรวมกัน

ระบบที่ใช้กระเบื้องมาตรฐานมีจุดแข็งดังต่อไปนี้:

  • Simple 2D Array ของกระเบื้อง
  • กระเบื้องแต่ละแผ่นมีวัสดุเดียว
    • วัสดุนี้อาจเป็นพื้นผิวเดียวหลาย ๆ หรือผสมผสานจากกระเบื้องโดยรอบ
    • สามารถนำพื้นผิวกลับมาใช้ใหม่ได้สำหรับไทล์ทุกประเภทนั้นลดการสร้างสินทรัพย์และการทำใหม่
  • แต่ละไทล์สามารถผ่านได้หรือไม่ (การตรวจสอบการชนกัน)
  • ง่ายต่อการแสดงผลและระบุชิ้นส่วนของแผนที่
    • ตำแหน่งตัวละครและตำแหน่งแผนที่เป็นพิกัดที่แน่นอน
    • ง่ายต่อการวนซ้ำผ่านไทล์ที่เกี่ยวข้องสำหรับพื้นที่หน้าจอ

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

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

ทั้งหมดในทุกสิ่งที่คุณจะพบว่าตัวเองมีโอกาสทำงานซ้ำน้อยลงด้วยระบบที่ใช้กระเบื้องมากกว่าแผนที่กราฟิกขนาดใหญ่ คุณอาจลงเอยด้วยการใช้ระบบแบบเรียงต่อกันสำหรับการชนแม้ว่าคุณจะไม่ได้ใช้มันสำหรับแผนที่ภาคพื้นดินของคุณ เพียงเพราะคุณใช้ไทล์ภายในไม่ได้หมายความว่าคุณไม่สามารถใช้โมเดลเพื่อแสดงออบเจ็กต์ของสภาพแวดล้อมที่อาจใช้ไทล์มากกว่า 1 แผ่นสำหรับพื้นที่ของพวกเขา

คุณจะต้องให้ข้อมูลเฉพาะเพิ่มเติมเกี่ยวกับสภาพแวดล้อม / เครื่องมือ / ภาษาของคุณเพื่อให้ตัวอย่างโค้ด


ขอบคุณ! ฉันทำงานภายใต้ C # และกำลังจะมี Final Fantasy 6 รุ่นที่คล้ายกัน (แต่มีความสามารถน้อยกว่า) (หรือโดยทั่วไปแล้วเป็น SNES RPG ส่วนใหญ่ของ Square Square) ดังนั้นแผ่นกระเบื้องและกระเบื้องโครงสร้าง (เช่นบ้าน) ความกังวลที่ยิ่งใหญ่ที่สุดของฉันคือฉันไม่เห็นวิธีการสร้างอาเรย์ 2 มิติโดยไม่ต้องใช้เวลาตรวจสอบ "มีมุมหญ้าที่นั่นจากนั้นสามแนวนอนตรงจากนั้นมุมบ้านแล้ว .. " ด้วยความผิดพลาดที่เป็นไปได้มากมาย ทาง
Cristol.GdM

ดูเหมือนว่าคุณจะได้ริชาร์ดในแง่ของรหัสเฉพาะฉันจะใช้ enum ของ tiletypes และใช้ bitflags เพื่อระบุค่าของไพ่ สิ่งนี้อนุญาตให้คุณเก็บค่า 32+ ในจำนวนเต็มเฉลี่ย นอกจากนี้คุณยังสามารถจัดเก็บธงหลายรายการต่อแผ่น ดังนั้นคุณสามารถพูดได้ Grass = true, Wall = true, Collision = true ฯลฯ โดยไม่มีตัวแปรแยกต่างหาก จากนั้นคุณเพียงแค่กำหนด "ชุดรูปแบบ" ซึ่งกำหนด tilesheet สำหรับกราฟิกสำหรับภูมิภาคของแผนที่นั้น
Brett W

3

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

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

สำหรับแผนที่ขนาดใหญ่ภาพบิตแมปขนาดใหญ่หนึ่งภาพสำหรับแผนที่ทั้งหมดอาจใช้พื้นที่ในหน่วยความจำมากและอาจใหญ่กว่าที่การ์ดกราฟิกรองรับสำหรับขนาดภาพเดียว แต่คุณจะไม่พบปัญหาใด ๆ กับไทล์เพราะคุณจัดสรรหน่วยความจำกราฟิกเพียงครั้งเดียวสำหรับไทล์แต่ละอัน (หรือผลรวมทั้งหมดหนึ่งอันเหมาะสมหากพวกมันทั้งหมดในหนึ่งแผ่น)

นอกจากนี้ยังง่ายต่อการนำข้อมูลไทล์กลับมาใช้ใหม่เช่นการชนกัน ตัวอย่างเช่นการรับไทล์ภูมิประเทศที่มุมบนซ้ายของเทพดาของตัวละคร:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

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

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