เกริ่นนำ
ฤดูใบไม้ผลิที่ผ่านมา (2010) ฉันสนใจการรองรับ HTML5 และเทคโนโลยีที่เกี่ยวข้องที่เพิ่มขึ้นอย่างรวดเร็ว ในตอนนั้น เพื่อนคนหนึ่งและผมต้องท้าทายกันในการแข่งขันพัฒนาเกมระยะเวลา 2 สัปดาห์เพื่อพัฒนาทักษะการเขียนโปรแกรมและการพัฒนา รวมถึงทำให้ไอเดียของเกมที่ผลัดกันเล่นกันเข้ามากลายเป็นจริง ดังนั้น ฉันจึงเริ่มรวมองค์ประกอบ HTML5 ไว้ในรายการแข่งขันเพื่อให้เข้าใจเกี่ยวกับ วิธีการทำงานขององค์ประกอบเหล่านี้ได้ดีขึ้น และสามารถทำสิ่งต่างๆ ที่แทบจะเป็นไปไม่ได้เลยเมื่อใช้ข้อกำหนด HTML ก่อนหน้านี้
จากฟีเจอร์ใหม่ๆ มากมายใน HTML5 การรองรับแท็ก Canvas ที่เพิ่มขึ้นทำให้ฉันมีโอกาสน่าตื่นเต้นในการปรับใช้งานศิลปะแบบอินเทอร์แอกทีฟโดยใช้ JavaScript ซึ่งทำให้ฉันลองใช้เกมปริศนาที่ตอนนี้มีชื่อว่า Entanglement ผมได้สร้างต้นแบบโดยใช้ด้านหลังของกระเบื้อง Settlers of Catan แล้ว การใช้ชิ้นส่วนนี้เป็นพิมพ์เขียว จึงมีองค์ประกอบสำคัญ 3 อย่างในการสร้างชิ้นส่วนหกเหลี่ยมใน Canvas ของ HTML5 สำหรับการเล่นบนเว็บ นั่นก็คือ การวาดรูปหกเหลี่ยม วาดเส้นทาง และการหมุนชิ้นส่วน ต่อไปนี้จะอธิบายรายละเอียดว่าฉันประสบความสำเร็จกับแต่ละสิ่งเหล่านี้ ในแบบฟอร์มปัจจุบันอย่างไร
การวาดหกเหลี่ยม
ใน Entanglement เวอร์ชันต้นฉบับ เราใช้วิธีวาดผืนผ้าใบหลายวิธีเพื่อวาดรูปหกเหลี่ยม แต่รูปแบบปัจจุบันของเกมใช้ drawImage()
เพื่อวาดพื้นผิวที่ตัดมาจากภาพต่อเรียง
ผมรวมรูปภาพต่างๆ เข้าด้วยกันเป็นไฟล์เดียว ดังนั้นจึงมีเพียงคำขอเดียวที่ส่งไปยังเซิร์ฟเวอร์ แทนที่จะเป็น 10 รายการ ในการวาดหกเหลี่ยมที่เลือกลงในผืนผ้าใบ เราจะต้องรวบรวมเครื่องมือเข้าด้วยกันก่อน เช่น ผืนผ้าใบ บริบท และรูปภาพ
หากต้องการสร้างแคนวาส สิ่งที่เราต้องใช้มีเพียงแท็ก Canvas ในเอกสาร HTML ดังนี้
<canvas id="myCanvas"></canvas>
ฉันได้ตั้งรหัสเพื่อให้เราดึงรหัสลงในสคริปต์ได้
var cvs = document.getElementById('myCanvas');
ต่อมาคือเราต้องจับบริบทแบบ 2 มิติของผืนผ้าใบเพื่อเริ่มวาดสิ่งต่างๆ ดังนี้
var ctx = cvs.getContext('2d');
สุดท้ายนี้ ต้องมีรูปภาพด้วย หากชื่อ "tiles.png" อยู่ในโฟลเดอร์เดียวกับ หน้าเว็บ เราจะหาไฟล์ได้โดยทำดังนี้
var img = new Image();
img.src = 'tiles.png';
ตอนนี้เรามีคอมโพเนนต์ทั้ง 3 อย่างแล้ว เราจึงสามารถใช้ ctx.drawImage() เพื่อวาดรูปหกเหลี่ยมที่ต้องการจากภาพต่อเรียงไปยัง Canvas ได้ ดังนี้
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
ในกรณีนี้ เราใช้หกเหลี่ยมที่ 4 จากด้านซ้ายบนแถวบนสุด เราจะวาดรูปนี้บนผืนผ้าใบที่มุมซ้ายบน โดยให้มีขนาดเท่ากับต้นฉบับ สมมติว่ารูปหกเหลี่ยมกว้าง 400 พิกเซลและสูง 346 พิกเซล ทั้งหมดจะมีลักษณะดังนี้
var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
เราได้คัดลอกรูปภาพบางส่วนไปยังผืนผ้าใบสำเร็จแล้ว โดยมีสาเหตุดังนี้
การวาดเส้นทาง
ตอนนี้เราวาดรูปหกเหลี่ยมลงในผืนผ้าใบแล้ว เราจะวาดเส้น 2-3 เส้นบนผืนผ้าใบ ก่อนอื่น เราจะมาดูรูปเรขาคณิตที่เกี่ยวกับชิ้นส่วนหกเหลี่ยม เราต้องการให้สิ้นสุด 2 บรรทัดต่อด้าน โดยแต่ละเส้นสิ้นสุดเป็น 1/4 จากปลายแต่ละด้าน และ 1/2 ของขอบให้ห่างจากกัน เช่น
เรายังอยากได้เส้นโค้งสวยๆ จากการทดลองลองผิดลองถูกสักเล็กน้อย ทำให้ผมพบว่าถ้าผมสร้างเส้นตั้งฉากจากขอบที่แต่ละปลายทาง สี่แยกของปลายทางแต่ละคู่ที่อยู่รอบมุมของหกเหลี่ยมมุมที่กำหนดจะเป็นจุดควบคุมที่ละเอียดกว่าของปลายทางที่ระบุ
ตอนนี้เราจับคู่ทั้งปลายทางและจุดควบคุมกับระนาบคาร์ทีเซียนที่สอดคล้องกับรูปภาพบนผืนผ้าใบของเรา และเราก็พร้อมที่จะกลับไปยังโค้ดแล้ว กล่าวง่ายๆ คือเราจะเริ่มต้นด้วยบรรทัดเดียว เราจะเริ่มต้นด้วยการวาดเส้นทาง จากปลายทางด้านซ้ายบนไปยังปลายทางด้านขวาล่าง สำหรับภาพหกเหลี่ยมก่อนหน้านี้ของเรามีขนาด 400x346 ซึ่งจะทำให้ปลายทางบนสุดมี 150 พิกเซลในด้านต่างๆ และ 0 พิกเซลลงโดยสั้นลง (150, 0) จุดควบคุมคือ (150, 86) ปลายทางขอบด้านล่างคือ (250, 346) โดยมีจุดควบคุมเป็น (250, 260)
เพียงมีพิกัดของเราอยู่ในมือ คุณก็พร้อมที่จะเริ่มวาดแล้ว เราจะเริ่มใหม่ด้วย ctx.beginPath() จากนั้นย้ายไปยังปลายทางแรกโดยใช้
ctx.moveTo(pointX1,pointY1);
จากนั้นเราสามารถวาดเส้นเองโดยใช้ ctx.bezierCurveTo() ดังนี้:
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
เนื่องจากเราต้องการให้เส้นมีเส้นขอบสวยสบาย เราจึงจะลากเส้นเส้นทางนี้ 2 ครั้งโดยใช้ความกว้างและสีที่แตกต่างกันในแต่ละครั้ง ระบบจะตั้งค่าสีโดยใช้คุณสมบัติ ctx.stageStyle และความกว้างโดยใช้ ctx.lineWidth โดยรวมแล้ว การวาดบรรทัดแรกจะมีหน้าตาแบบนี้
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ตอนนี้เรามีชิ้นส่วนหกเหลี่ยมที่บรรทัดแรกลากผ่าน
เมื่อป้อนพิกัดสำหรับปลายทางอีก 10 แห่ง รวมถึงจุดควบคุมเส้นโค้งเบซิเยร์ที่เกี่ยวข้อง เราสามารถทำตามขั้นตอนข้างต้นซ้ำได้ และอาจสร้างชิ้นส่วนในลักษณะดังนี้
การหมุนผ้าใบ
เมื่อได้การ์ดแล้ว เราอยากจะพลิกโฉมให้
จะได้ใช้เส้นทางต่างๆ ในเกมได้ เราใช้ ctx.translate()
และ ctx.rotate()
เพื่อทำเช่นนี้โดยใช้ Canvas เราต้องการให้ชิ้นส่วนหมุนประมาณจุดกึ่งกลาง
ขั้นตอนแรกคือการย้ายจุดอ้างอิงของ Canvas ไปไว้ตรงกลางของชิ้นส่วนหกเหลี่ยม
ในการดำเนินการนี้ เราจะใช้:
ctx.translate(originX, originY);
โดยที่ originX จะเป็นครึ่งหนึ่งของความกว้างของไทล์หกเหลี่ยม และ originY จะเป็นครึ่งหนึ่งของความสูง ซึ่งทำให้เรามีค่าดังนี้
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ตอนนี้เราสามารถหมุนชิ้นส่วนด้วยจุดศูนย์กลางใหม่ เนื่องจากรูปหกเหลี่ยมมี 6 ด้าน เราจะต้องหมุนด้วยค่าพหุคูณของ Math.PI หารด้วย 3 เราจะทำให้เรียบง่ายและหมุนตามเข็มนาฬิกาเพียงครั้งเดียวโดยใช้:
ctx.rotate(Math.PI / 3);
แต่เนื่องจากรูปหกเหลี่ยมและเส้นของเราใช้พิกัดเดิม (0,0) เป็นต้นทาง เมื่อเราหมุนหน้าจอเสร็จแล้ว เราจะต้องแปลกลับก่อนที่จะวาด ตอนนี้เรามี
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
การวางการแปลและการหมุนด้านบนก่อนโค้ดการแสดงผลของเราจะทำให้โค้ดแสดงผล แบบหมุนได้
สรุป
ด้านบนผมได้ไฮไลต์ความสามารถ 2-3 อย่างที่ HTML5 มีให้ ด้วยการใช้แท็ก Canvas ซึ่งรวมถึงการแสดงภาพ การวาดเส้นโค้งเบซิเยร์ และการหมุน Canvas การใช้แท็ก Canvas ของ HTML5 และเครื่องมือวาดภาพ JavaScript สำหรับ Entanglement เป็นประสบการณ์ที่น่าพึงพอใจ และฉันหวังว่าจะได้ดูแอปพลิเคชันและเกมใหม่ๆ ที่คนอื่นสร้างขึ้นด้วยเทคโนโลยีแบบเปิดและที่กำลังพัฒนานี้
โค้ดอ้างอิง
ตัวอย่างโค้ดทั้งหมดที่ระบุไว้ข้างต้นได้รวมอยู่ด้านล่างนี้เป็นข้อมูลอ้างอิง
var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();