[{"data":1,"prerenderedAt":6471},["ShallowReactive",2],{"blog-list":3},[4,705,1778,2363,3042,4293,5014],{"id":5,"title":6,"body":7,"description":16,"extension":695,"meta":696,"navigation":103,"path":701,"seo":702,"stem":703,"__hash__":704},"blog\u002Fblog\u002Fcanvas-basics.md","HTML5 Canvas Basics: Getting Started with 2D Games",{"type":8,"value":9,"toc":693},"minimark",[10,689],[11,12,13,372],"i18n-text",{},[14,15,17,21,25,30,33,56,68,77,117,124,131,158,228,232,235,298,312,315],"template",{"v-slot:en":16},"",[18,19,6],"h1",{"id":20},"html5-canvas-basics-getting-started-with-2d-games",[22,23,24],"p",{},"HTML5 Canvas is a powerful tool for rendering 2D graphics in the browser, making it the perfect starting point for web-based game development. Let's explore the fundamental methods you'll use constantly.",[26,27,29],"h2",{"id":28},"_1-setting-up-the-context","1. Setting Up the Context",[22,31,32],{},"Before you can draw anything, you need to get the canvas element and its 2D rendering context:",[34,35,39],"pre",{"className":36,"code":37,"language":38,"meta":16,"style":16},"language-javascript shiki shiki-themes github-light github-dark","const canvas = document.getElementById('myCanvas');\nconst ctx = canvas.getContext('2d');\n","javascript",[40,41,42,50],"code",{"__ignoreMap":16},[43,44,47],"span",{"class":45,"line":46},"line",1,[43,48,49],{},"const canvas = document.getElementById('myCanvas');\n",[43,51,53],{"class":45,"line":52},2,[43,54,55],{},"const ctx = canvas.getContext('2d');\n",[26,57,59,60,63,64,67],{"id":58},"_2-drawing-rectangles-fillrect-strokerect","2. Drawing Rectangles (",[40,61,62],{},"fillRect"," & ",[40,65,66],{},"strokeRect",")",[22,69,70,71,73,74,76],{},"Rectangles are the most basic shape. ",[40,72,62],{}," draws a filled rectangle, while ",[40,75,66],{}," draws its outline.",[34,78,80],{"className":36,"code":79,"language":38,"meta":16,"style":16},"ctx.fillStyle = 'blue'; \u002F\u002F Determine inner color\n\u002F\u002F x, y, width, height\nctx.fillRect(10, 10, 50, 50); \u002F\u002F Draws a 50x50 blue square at x:10, y:10\n\nctx.strokeStyle = 'red'; \u002F\u002F Determine border color\nctx.strokeRect(70, 10, 50, 50); \u002F\u002F Draws a red outline\n",[40,81,82,87,92,98,105,111],{"__ignoreMap":16},[43,83,84],{"class":45,"line":46},[43,85,86],{},"ctx.fillStyle = 'blue'; \u002F\u002F Determine inner color\n",[43,88,89],{"class":45,"line":52},[43,90,91],{},"\u002F\u002F x, y, width, height\n",[43,93,95],{"class":45,"line":94},3,[43,96,97],{},"ctx.fillRect(10, 10, 50, 50); \u002F\u002F Draws a 50x50 blue square at x:10, y:10\n",[43,99,101],{"class":45,"line":100},4,[43,102,104],{"emptyLinePlaceholder":103},true,"\n",[43,106,108],{"class":45,"line":107},5,[43,109,110],{},"ctx.strokeStyle = 'red'; \u002F\u002F Determine border color\n",[43,112,114],{"class":45,"line":113},6,[43,115,116],{},"ctx.strokeRect(70, 10, 50, 50); \u002F\u002F Draws a red outline\n",[26,118,120,121,67],{"id":119},"_3-drawing-circles-and-arcs-arc","3. Drawing Circles and Arcs (",[40,122,123],{},"arc",[22,125,126,127,130],{},"To draw circles or curves, you define a path and use ",[40,128,129],{},"arc(x, y, radius, startAngle, endAngle, counterclockwise)",". Note that angles are measured in radians, not degrees.",[132,133,134,142,148],"ul",{},[135,136,137,141],"li",{},[138,139,140],"strong",{},"x, y:"," the coordinates of the arc's center.",[135,143,144,147],{},[138,145,146],{},"radius:"," the arc's radius.",[135,149,150,153,154,157],{},[138,151,152],{},"startAngle, endAngle:"," starting and ending angle in radians (A full circle is ",[40,155,156],{},"2 * Math.PI",").",[34,159,161],{"className":36,"code":160,"language":38,"meta":16,"style":16},"ctx.beginPath(); \u002F\u002F Start a new path\n\u002F\u002F x: 100, y: 75, radius: 50\n\u002F\u002F startAngle: 0, endAngle: 2 * Math.PI (a full circle)\nctx.arc(100, 75, 50, 0, 2 * Math.PI); \nctx.fillStyle = 'yellow';\nctx.fill();\n\n\u002F\u002F Drawing a half-circle outline (arc)\nctx.beginPath();\nctx.arc(200, 75, 40, 0, Math.PI); \u002F\u002F Half circle (180 degrees)\nctx.strokeStyle = 'green';\nctx.stroke();\n",[40,162,163,168,173,178,183,188,193,198,204,210,216,222],{"__ignoreMap":16},[43,164,165],{"class":45,"line":46},[43,166,167],{},"ctx.beginPath(); \u002F\u002F Start a new path\n",[43,169,170],{"class":45,"line":52},[43,171,172],{},"\u002F\u002F x: 100, y: 75, radius: 50\n",[43,174,175],{"class":45,"line":94},[43,176,177],{},"\u002F\u002F startAngle: 0, endAngle: 2 * Math.PI (a full circle)\n",[43,179,180],{"class":45,"line":100},[43,181,182],{},"ctx.arc(100, 75, 50, 0, 2 * Math.PI); \n",[43,184,185],{"class":45,"line":107},[43,186,187],{},"ctx.fillStyle = 'yellow';\n",[43,189,190],{"class":45,"line":113},[43,191,192],{},"ctx.fill();\n",[43,194,196],{"class":45,"line":195},7,[43,197,104],{"emptyLinePlaceholder":103},[43,199,201],{"class":45,"line":200},8,[43,202,203],{},"\u002F\u002F Drawing a half-circle outline (arc)\n",[43,205,207],{"class":45,"line":206},9,[43,208,209],{},"ctx.beginPath();\n",[43,211,213],{"class":45,"line":212},10,[43,214,215],{},"ctx.arc(200, 75, 40, 0, Math.PI); \u002F\u002F Half circle (180 degrees)\n",[43,217,219],{"class":45,"line":218},11,[43,220,221],{},"ctx.strokeStyle = 'green';\n",[43,223,225],{"class":45,"line":224},12,[43,226,227],{},"ctx.stroke();\n",[26,229,231],{"id":230},"_4-advanced-colors-and-linear-gradients","4. Advanced Colors and Linear Gradients",[22,233,234],{},"You can set colors using standard CSS color names, HEX, RGB, or RGBA for transparency. You can also create gradients!",[34,236,238],{"className":36,"code":237,"language":38,"meta":16,"style":16},"\u002F\u002F Simple color transparency using RGBA\nctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; \u002F\u002F 50% transparent red\n\n\u002F\u002F Creating a Linear Gradient: createLinearGradient(x0, y0, x1, y1)\n\u002F\u002F The parameters represent the start (x0, y0) and end (x1, y1) point\nconst gradient = ctx.createLinearGradient(0, 0, 150, 0); \u002F\u002F Horizontal gradient\ngradient.addColorStop(0, 'red');      \u002F\u002F Start color at offset 0\ngradient.addColorStop(0.5, 'yellow'); \u002F\u002F Middle color at offset 0.5\ngradient.addColorStop(1, 'green');    \u002F\u002F End color at offset 1\n\nctx.fillStyle = gradient; \u002F\u002F Set gradient as the fill style\nctx.fillRect(10, 150, 150, 50);\n",[40,239,240,245,250,254,259,264,269,274,279,284,288,293],{"__ignoreMap":16},[43,241,242],{"class":45,"line":46},[43,243,244],{},"\u002F\u002F Simple color transparency using RGBA\n",[43,246,247],{"class":45,"line":52},[43,248,249],{},"ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; \u002F\u002F 50% transparent red\n",[43,251,252],{"class":45,"line":94},[43,253,104],{"emptyLinePlaceholder":103},[43,255,256],{"class":45,"line":100},[43,257,258],{},"\u002F\u002F Creating a Linear Gradient: createLinearGradient(x0, y0, x1, y1)\n",[43,260,261],{"class":45,"line":107},[43,262,263],{},"\u002F\u002F The parameters represent the start (x0, y0) and end (x1, y1) point\n",[43,265,266],{"class":45,"line":113},[43,267,268],{},"const gradient = ctx.createLinearGradient(0, 0, 150, 0); \u002F\u002F Horizontal gradient\n",[43,270,271],{"class":45,"line":195},[43,272,273],{},"gradient.addColorStop(0, 'red');      \u002F\u002F Start color at offset 0\n",[43,275,276],{"class":45,"line":200},[43,277,278],{},"gradient.addColorStop(0.5, 'yellow'); \u002F\u002F Middle color at offset 0.5\n",[43,280,281],{"class":45,"line":206},[43,282,283],{},"gradient.addColorStop(1, 'green');    \u002F\u002F End color at offset 1\n",[43,285,286],{"class":45,"line":212},[43,287,104],{"emptyLinePlaceholder":103},[43,289,290],{"class":45,"line":218},[43,291,292],{},"ctx.fillStyle = gradient; \u002F\u002F Set gradient as the fill style\n",[43,294,295],{"class":45,"line":224},[43,296,297],{},"ctx.fillRect(10, 150, 150, 50);\n",[26,299,301,302,305,306,305,309,67],{"id":300},"_5-rendering-advanced-text-filltext-textalign-textbaseline","5. Rendering Advanced Text (",[40,303,304],{},"fillText",", ",[40,307,308],{},"textAlign",[40,310,311],{},"textBaseline",[22,313,314],{},"Rendering text on a canvas is straightforward, but you can also control its alignment and baseline (vertical origin point).",[34,316,318],{"className":36,"code":317,"language":38,"meta":16,"style":16},"ctx.font = '24px Arial'; \u002F\u002F Define font family and size\nctx.fillStyle = 'white';\n\n\u002F\u002F Horizontal alignment (left, center, right, start, end)\nctx.textAlign = 'center'; \n\n\u002F\u002F Vertical alignment (top, hanging, middle, alphabetic, ideographic, bottom)\nctx.textBaseline = 'middle'; \n\n\u002F\u002F Drawing the text (text string, x, y)\nctx.fillText('Center Aligned Text', canvas.width \u002F 2, canvas.height \u002F 2);\n",[40,319,320,325,330,334,339,344,348,353,358,362,367],{"__ignoreMap":16},[43,321,322],{"class":45,"line":46},[43,323,324],{},"ctx.font = '24px Arial'; \u002F\u002F Define font family and size\n",[43,326,327],{"class":45,"line":52},[43,328,329],{},"ctx.fillStyle = 'white';\n",[43,331,332],{"class":45,"line":94},[43,333,104],{"emptyLinePlaceholder":103},[43,335,336],{"class":45,"line":100},[43,337,338],{},"\u002F\u002F Horizontal alignment (left, center, right, start, end)\n",[43,340,341],{"class":45,"line":107},[43,342,343],{},"ctx.textAlign = 'center'; \n",[43,345,346],{"class":45,"line":113},[43,347,104],{"emptyLinePlaceholder":103},[43,349,350],{"class":45,"line":195},[43,351,352],{},"\u002F\u002F Vertical alignment (top, hanging, middle, alphabetic, ideographic, bottom)\n",[43,354,355],{"class":45,"line":200},[43,356,357],{},"ctx.textBaseline = 'middle'; \n",[43,359,360],{"class":45,"line":206},[43,361,104],{"emptyLinePlaceholder":103},[43,363,364],{"class":45,"line":212},[43,365,366],{},"\u002F\u002F Drawing the text (text string, x, y)\n",[43,368,369],{"class":45,"line":218},[43,370,371],{},"ctx.fillText('Center Aligned Text', canvas.width \u002F 2, canvas.height \u002F 2);\n",[14,373,374,378,381,385,388,400,408,417,451,457,463,482,540,547,553,621,631,634],{"v-slot:id":16},[18,375,377],{"id":376},"dasar-dasar-html5-canvas-memulai-game-2d","Dasar-Dasar HTML5 Canvas: Memulai Game 2D",[22,379,380],{},"HTML5 Canvas adalah fitur kuat untuk me-render grafis 2D di browser, menjadikannya titik awal yang sempurna untuk pengembangan game berbasis web. Mari kita pelajari metode dasar yang akan sering Anda gunakan.",[26,382,384],{"id":383},"_1-menyiapkan-context-konteks","1. Menyiapkan Context (Konteks)",[22,386,387],{},"Sebelum bisa menggambar, Anda harus mengambil elemen canvas dan konteks rendering 2D-nya:",[34,389,390],{"className":36,"code":37,"language":38,"meta":16,"style":16},[40,391,392,396],{"__ignoreMap":16},[43,393,394],{"class":45,"line":46},[43,395,49],{},[43,397,398],{"class":45,"line":52},[43,399,55],{},[26,401,403,404,63,406,67],{"id":402},"_2-menggambar-persegi-panjang-fillrect-strokerect","2. Menggambar Persegi Panjang (",[40,405,62],{},[40,407,66],{},[22,409,410,411,413,414,416],{},"Persegi panjang adalah bentuk paling dasar. ",[40,412,62],{}," menggambar bentuk berserta isinya, sedangkan ",[40,415,66],{}," hanya menggambar garis luarnya saja.",[34,418,420],{"className":36,"code":419,"language":38,"meta":16,"style":16},"ctx.fillStyle = 'blue'; \u002F\u002F Menentukan warna isian\n\u002F\u002F posis_x, posisi_y, lebar, tinggi\nctx.fillRect(10, 10, 50, 50); \u002F\u002F Kotak biru 50x50 pada posisi x:10, y:10\n\nctx.strokeStyle = 'red'; \u002F\u002F Menentukan warna perbatasan outline\nctx.strokeRect(70, 10, 50, 50); \u002F\u002F Membuat kotak garis merah\n",[40,421,422,427,432,437,441,446],{"__ignoreMap":16},[43,423,424],{"class":45,"line":46},[43,425,426],{},"ctx.fillStyle = 'blue'; \u002F\u002F Menentukan warna isian\n",[43,428,429],{"class":45,"line":52},[43,430,431],{},"\u002F\u002F posis_x, posisi_y, lebar, tinggi\n",[43,433,434],{"class":45,"line":94},[43,435,436],{},"ctx.fillRect(10, 10, 50, 50); \u002F\u002F Kotak biru 50x50 pada posisi x:10, y:10\n",[43,438,439],{"class":45,"line":100},[43,440,104],{"emptyLinePlaceholder":103},[43,442,443],{"class":45,"line":107},[43,444,445],{},"ctx.strokeStyle = 'red'; \u002F\u002F Menentukan warna perbatasan outline\n",[43,447,448],{"class":45,"line":113},[43,449,450],{},"ctx.strokeRect(70, 10, 50, 50); \u002F\u002F Membuat kotak garis merah\n",[26,452,454,455,67],{"id":453},"_3-menggambar-lingkaran-dan-arc-arc","3. Menggambar Lingkaran dan Arc (",[40,456,123],{},[22,458,459,460,462],{},"Untuk menggambar lingkaran, Anda harus membuat lintasan (path) menggunakan metode ",[40,461,129],{},". Perhatikan bahwa perhitungan sudut menggunakan metrik ukur radian, bukan derajat.",[132,464,465,470,475],{},[135,466,467,469],{},[138,468,140],{}," Titik tengah pusat lingkaran.",[135,471,472,474],{},[138,473,146],{}," Jari-jari lingkaran\u002Fkurva.",[135,476,477,479,480,157],{},[138,478,152],{}," Titik awal dan akhir derajat lengkungan dalam hitungan radian (Lingkaran penuh adalah ",[40,481,156],{},[34,483,485],{"className":36,"code":484,"language":38,"meta":16,"style":16},"ctx.beginPath(); \u002F\u002F Memulai lintasan jalur gambar baru\n\u002F\u002F x: 100, y: 75, radius (jari-jari): 50\n\u002F\u002F startAngle: 0, endAngle: 2 * Math.PI (lingkaran penuh \u002F 360 derajat)\nctx.arc(100, 75, 50, 0, 2 * Math.PI); \nctx.fillStyle = 'yellow';\nctx.fill();\n\n\u002F\u002F Menggambar setengah lingkaran (hanya garis lintasan lengkung)\nctx.beginPath();\nctx.arc(200, 75, 40, 0, Math.PI); \u002F\u002F Setengah lingkaran (180 derajat)\nctx.strokeStyle = 'green';\nctx.stroke();\n",[40,486,487,492,497,502,506,510,514,518,523,527,532,536],{"__ignoreMap":16},[43,488,489],{"class":45,"line":46},[43,490,491],{},"ctx.beginPath(); \u002F\u002F Memulai lintasan jalur gambar baru\n",[43,493,494],{"class":45,"line":52},[43,495,496],{},"\u002F\u002F x: 100, y: 75, radius (jari-jari): 50\n",[43,498,499],{"class":45,"line":94},[43,500,501],{},"\u002F\u002F startAngle: 0, endAngle: 2 * Math.PI (lingkaran penuh \u002F 360 derajat)\n",[43,503,504],{"class":45,"line":100},[43,505,182],{},[43,507,508],{"class":45,"line":107},[43,509,187],{},[43,511,512],{"class":45,"line":113},[43,513,192],{},[43,515,516],{"class":45,"line":195},[43,517,104],{"emptyLinePlaceholder":103},[43,519,520],{"class":45,"line":200},[43,521,522],{},"\u002F\u002F Menggambar setengah lingkaran (hanya garis lintasan lengkung)\n",[43,524,525],{"class":45,"line":206},[43,526,209],{},[43,528,529],{"class":45,"line":212},[43,530,531],{},"ctx.arc(200, 75, 40, 0, Math.PI); \u002F\u002F Setengah lingkaran (180 derajat)\n",[43,533,534],{"class":45,"line":218},[43,535,221],{},[43,537,538],{"class":45,"line":224},[43,539,227],{},[26,541,543,544,67],{"id":542},"_4-warna-lanjutan-dan-gradien-linear-createlineargradient","4. Warna Lanjutan dan Gradien Linear (",[40,545,546],{},"createLinearGradient",[22,548,549,550,552],{},"Anda dapat mengatur transparansi dengan nilai RGBA, dan juga membuat transisi gradien warna menggunakan ",[40,551,546],{},".",[34,554,556],{"className":36,"code":555,"language":38,"meta":16,"style":16},"\u002F\u002F Mengatur transparansi warna 50%\nctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; \u002F\u002F Merah yang 50% transparan\n\n\u002F\u002F Membuat Gradien Linear: createLinearGradient(x0, y0, x1, y1)\n\u002F\u002F Parameter menentukan titik mula (x0, y0) sampai selesai (x1, y1)\nconst gradient = ctx.createLinearGradient(0, 0, 150, 0); \u002F\u002F Gradien secara horizontal\ngradient.addColorStop(0, 'red');      \u002F\u002F Warna awal di offset 0\ngradient.addColorStop(0.5, 'yellow'); \u002F\u002F Warna tengah di offset 0.5\ngradient.addColorStop(1, 'green');    \u002F\u002F Warna akhir di offset 1\n\n\u002F\u002F Menerapkan objek gradien kepada style sebelum digambar\nctx.fillStyle = gradient;\nctx.fillRect(10, 150, 150, 50);\n",[40,557,558,563,568,572,577,582,587,592,597,602,606,611,616],{"__ignoreMap":16},[43,559,560],{"class":45,"line":46},[43,561,562],{},"\u002F\u002F Mengatur transparansi warna 50%\n",[43,564,565],{"class":45,"line":52},[43,566,567],{},"ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; \u002F\u002F Merah yang 50% transparan\n",[43,569,570],{"class":45,"line":94},[43,571,104],{"emptyLinePlaceholder":103},[43,573,574],{"class":45,"line":100},[43,575,576],{},"\u002F\u002F Membuat Gradien Linear: createLinearGradient(x0, y0, x1, y1)\n",[43,578,579],{"class":45,"line":107},[43,580,581],{},"\u002F\u002F Parameter menentukan titik mula (x0, y0) sampai selesai (x1, y1)\n",[43,583,584],{"class":45,"line":113},[43,585,586],{},"const gradient = ctx.createLinearGradient(0, 0, 150, 0); \u002F\u002F Gradien secara horizontal\n",[43,588,589],{"class":45,"line":195},[43,590,591],{},"gradient.addColorStop(0, 'red');      \u002F\u002F Warna awal di offset 0\n",[43,593,594],{"class":45,"line":200},[43,595,596],{},"gradient.addColorStop(0.5, 'yellow'); \u002F\u002F Warna tengah di offset 0.5\n",[43,598,599],{"class":45,"line":206},[43,600,601],{},"gradient.addColorStop(1, 'green');    \u002F\u002F Warna akhir di offset 1\n",[43,603,604],{"class":45,"line":212},[43,605,104],{"emptyLinePlaceholder":103},[43,607,608],{"class":45,"line":218},[43,609,610],{},"\u002F\u002F Menerapkan objek gradien kepada style sebelum digambar\n",[43,612,613],{"class":45,"line":224},[43,614,615],{},"ctx.fillStyle = gradient;\n",[43,617,619],{"class":45,"line":618},13,[43,620,297],{},[26,622,624,625,305,627,305,629,67],{"id":623},"_5-menampilkan-teks-lanjutan-filltext-textalign-textbaseline","5. Menampilkan Teks Lanjutan (",[40,626,304],{},[40,628,308],{},[40,630,311],{},[22,632,633],{},"Menulis teks itu mudah, tapi tahukah Anda bisa mengatur perataan horizontal (align) maupun vertikal (baseline)?",[34,635,637],{"className":36,"code":636,"language":38,"meta":16,"style":16},"ctx.font = '24px Arial'; \u002F\u002F Menentukan tipe font dan ukuran\nctx.fillStyle = 'white'; \u002F\u002F Menentukan warna text\n\n\u002F\u002F Perataan Horizontal teks (left, center, right, start, end)\nctx.textAlign = 'center'; \n\n\u002F\u002F Perataan Vertikal \u002F titik pangkal vertikal (top, middle, bottom, alphabetic, dll)\nctx.textBaseline = 'middle'; \n\n\u002F\u002F teks yang ditampilkan, posisi_x, posisi_y\nctx.fillText('Teks Rata Tengah \u002F Center', canvas.width \u002F 2, canvas.height \u002F 2);\n",[40,638,639,644,649,653,658,662,666,671,675,679,684],{"__ignoreMap":16},[43,640,641],{"class":45,"line":46},[43,642,643],{},"ctx.font = '24px Arial'; \u002F\u002F Menentukan tipe font dan ukuran\n",[43,645,646],{"class":45,"line":52},[43,647,648],{},"ctx.fillStyle = 'white'; \u002F\u002F Menentukan warna text\n",[43,650,651],{"class":45,"line":94},[43,652,104],{"emptyLinePlaceholder":103},[43,654,655],{"class":45,"line":100},[43,656,657],{},"\u002F\u002F Perataan Horizontal teks (left, center, right, start, end)\n",[43,659,660],{"class":45,"line":107},[43,661,343],{},[43,663,664],{"class":45,"line":113},[43,665,104],{"emptyLinePlaceholder":103},[43,667,668],{"class":45,"line":195},[43,669,670],{},"\u002F\u002F Perataan Vertikal \u002F titik pangkal vertikal (top, middle, bottom, alphabetic, dll)\n",[43,672,673],{"class":45,"line":200},[43,674,357],{},[43,676,677],{"class":45,"line":206},[43,678,104],{"emptyLinePlaceholder":103},[43,680,681],{"class":45,"line":212},[43,682,683],{},"\u002F\u002F teks yang ditampilkan, posisi_x, posisi_y\n",[43,685,686],{"class":45,"line":218},[43,687,688],{},"ctx.fillText('Teks Rata Tengah \u002F Center', canvas.width \u002F 2, canvas.height \u002F 2);\n",[690,691,692],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":16,"searchDepth":52,"depth":52,"links":694},[],"md",{"title_en":6,"title_id":377,"description_en":697,"description_id":698,"date":699,"readingTime":700},"Learn the fundamentals of HTML5 Canvas for game development, covering context, shapes, colors, gradients, and advanced text rendering.","Pelajari dasar-dasar HTML5 Canvas untuk pengembangan game, meliputi context, bentuk, warna, gradien, dan rendering teks lanjutan.","2026-03-25","8 min read","\u002Fblog\u002Fcanvas-basics",{"title":6,"description":16},"blog\u002Fcanvas-basics","TitJJMKX22Zkx1_gsUPGARhlNS4yFjchYdhcakNQmGQ",{"id":706,"title":707,"body":708,"description":16,"extension":695,"meta":1770,"navigation":103,"path":1774,"seo":1775,"stem":1776,"__hash__":1777},"blog\u002Fblog\u002Fcanvas-collision-physics.md","HTML5 Canvas: Advanced Physics and Collision (Hitbox)",{"type":8,"value":709,"toc":1768},[710,1766],[11,711,712,1248],{},[14,713,714,718,721,732,736,743,750,810,817,821,824,843,1091,1095,1112,1245],{"v-slot:en":16},[18,715,717],{"id":716},"html5-canvas-advanced-physics-and-hitbox-collision","HTML5 Canvas: Advanced Physics and Hitbox Collision",[22,719,720],{},"Drawing a player and applying gravity is fantastic, but a game isn't a game until objects interact. When a player's foot hits a ball, the ball needs to fly! When the ball hits the ground, it shouldn't just stick—it needs to bounce and roll to a stop.",[22,722,723,724,727,728,731],{},"This module will teach you ",[138,725,726],{},"Hitbox Collision Detection"," and ",[138,729,730],{},"Advanced Projectile Physics"," (Bounciness and Friction) in a simple, understandable way.",[26,733,735],{"id":734},"_1-the-hitbox-aabb-collision-detection","1. The Hitbox: AABB Collision Detection",[22,737,738,739,742],{},"In 2D games, we usually don't calculate collisions pixel-by-pixel—it's too slow. Instead, we draw an invisible rectangular box around our sprite called a ",[138,740,741],{},"Hitbox"," (or Bounding Box).",[22,744,745,746,749],{},"The industry standard algorithm for checking if two rectangles overlap is called ",[138,747,748],{},"AABB"," (Axis-Aligned Bounding Box). The math is surprisingly simple! We check four absolute limits:",[34,751,753],{"className":36,"code":752,"language":38,"meta":16,"style":16},"\u002F\u002F Function returns TRUE if Box1 and Box2 overlap \nfunction checkAABBCollision(box1, box2) {\n  \u002F\u002F We check if it's IMPOSSIBLE for them to collide first.\n  \u002F\u002F Example: Is Box1's right edge further left than Box2's left edge?\n  return (\n    box1.x \u003C box2.x + box2.width &&   \u002F\u002F Box1 Left is past Box2 Right\n    box1.x + box1.width > box2.x &&   \u002F\u002F Box1 Right is past Box2 Left\n    box1.y \u003C box2.y + box2.height &&  \u002F\u002F Box1 Top is past Box2 Bottom\n    box1.y + box1.height > box2.y     \u002F\u002F Box1 Bottom is past Box2 Top\n  );\n}\n",[40,754,755,760,765,770,775,780,785,790,795,800,805],{"__ignoreMap":16},[43,756,757],{"class":45,"line":46},[43,758,759],{},"\u002F\u002F Function returns TRUE if Box1 and Box2 overlap \n",[43,761,762],{"class":45,"line":52},[43,763,764],{},"function checkAABBCollision(box1, box2) {\n",[43,766,767],{"class":45,"line":94},[43,768,769],{},"  \u002F\u002F We check if it's IMPOSSIBLE for them to collide first.\n",[43,771,772],{"class":45,"line":100},[43,773,774],{},"  \u002F\u002F Example: Is Box1's right edge further left than Box2's left edge?\n",[43,776,777],{"class":45,"line":107},[43,778,779],{},"  return (\n",[43,781,782],{"class":45,"line":113},[43,783,784],{},"    box1.x \u003C box2.x + box2.width &&   \u002F\u002F Box1 Left is past Box2 Right\n",[43,786,787],{"class":45,"line":195},[43,788,789],{},"    box1.x + box1.width > box2.x &&   \u002F\u002F Box1 Right is past Box2 Left\n",[43,791,792],{"class":45,"line":200},[43,793,794],{},"    box1.y \u003C box2.y + box2.height &&  \u002F\u002F Box1 Top is past Box2 Bottom\n",[43,796,797],{"class":45,"line":206},[43,798,799],{},"    box1.y + box1.height > box2.y     \u002F\u002F Box1 Bottom is past Box2 Top\n",[43,801,802],{"class":45,"line":212},[43,803,804],{},"  );\n",[43,806,807],{"class":45,"line":218},[43,808,809],{},"}\n",[22,811,812,813,816],{},"If ",[138,814,815],{},"ALL"," those conditions are true, the two objects are currently crashing into each other!",[26,818,820],{"id":819},"_2-advanced-game-physics-the-ball","2. Advanced Game Physics: The Ball",[22,822,823],{},"If you are making a sports or platformer game, your ball needs to feel realistic. A true physics ball requires two new concepts:",[825,826,827,837],"ol",{},[135,828,829,832,833,836],{},[138,830,831],{},"Bounce \u002F Restitution",": When it hits the floor, it reverses direction ",[40,834,835],{},"(-dy)",", but it loses some energy so it doesn't bounce forever.",[135,838,839,842],{},[138,840,841],{},"Friction \u002F Drag",": When rolling left or right, the grass slows it down until it completely stops.",[34,844,846],{"className":36,"code":845,"language":38,"meta":16,"style":16},"class Ball {\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n    this.width = 50;  \u002F\u002F Standardizing hitbox\n    this.height = 50;\n    \n    \u002F\u002F Physics properties\n    this.dx = 0; \u002F\u002F Horizontal speed\n    this.dy = 0; \u002F\u002F Vertical falling speed\n    this.gravity = 0.5;\n    \n    \u002F\u002F THE SECRET INGREDIENTS\n    this.bounce = -0.7; \u002F\u002F Loses 30% of energy every bounce\n    this.friction = 0.98; \u002F\u002F Multiplies dx to slow it down 2% every frame\n  }\n\n  update(groundLevel) {\n    \u002F\u002F 1. Apply Gravity\n    this.dy += this.gravity;\n    \n    \u002F\u002F 2. Apply Friction to slow rolling\n    this.dx *= this.friction; \n\n    \u002F\u002F Stop completely if the speed is incredibly slow (micro-movements)\n    if (Math.abs(this.dx) \u003C 0.1) this.dx = 0;\n\n    \u002F\u002F 3. Move the Ball\n    this.x += this.dx;\n    this.y += this.dy;\n\n    \u002F\u002F 4. Floor Collision & Bouncing Logic\n    if (this.y + this.height >= groundLevel) {\n      this.y = groundLevel - this.height; \u002F\u002F Prevent sinking\n      \n      \u002F\u002F Only bounce if falling fast enough (prevents infinite micro-jitters)\n      if (this.dy > 1) {\n        this.dy *= this.bounce; \u002F\u002F Reverse direction and lose energy!\n      } else {\n        this.dy = 0; \u002F\u002F Settle on the ground completely\n      }\n    }\n  }\n}\n",[40,847,848,853,858,863,868,873,878,883,888,893,898,903,907,912,918,924,930,935,941,947,953,958,964,970,975,981,987,992,998,1004,1010,1015,1021,1027,1033,1039,1045,1051,1057,1063,1069,1075,1081,1086],{"__ignoreMap":16},[43,849,850],{"class":45,"line":46},[43,851,852],{},"class Ball {\n",[43,854,855],{"class":45,"line":52},[43,856,857],{},"  constructor(x, y) {\n",[43,859,860],{"class":45,"line":94},[43,861,862],{},"    this.x = x;\n",[43,864,865],{"class":45,"line":100},[43,866,867],{},"    this.y = y;\n",[43,869,870],{"class":45,"line":107},[43,871,872],{},"    this.width = 50;  \u002F\u002F Standardizing hitbox\n",[43,874,875],{"class":45,"line":113},[43,876,877],{},"    this.height = 50;\n",[43,879,880],{"class":45,"line":195},[43,881,882],{},"    \n",[43,884,885],{"class":45,"line":200},[43,886,887],{},"    \u002F\u002F Physics properties\n",[43,889,890],{"class":45,"line":206},[43,891,892],{},"    this.dx = 0; \u002F\u002F Horizontal speed\n",[43,894,895],{"class":45,"line":212},[43,896,897],{},"    this.dy = 0; \u002F\u002F Vertical falling speed\n",[43,899,900],{"class":45,"line":218},[43,901,902],{},"    this.gravity = 0.5;\n",[43,904,905],{"class":45,"line":224},[43,906,882],{},[43,908,909],{"class":45,"line":618},[43,910,911],{},"    \u002F\u002F THE SECRET INGREDIENTS\n",[43,913,915],{"class":45,"line":914},14,[43,916,917],{},"    this.bounce = -0.7; \u002F\u002F Loses 30% of energy every bounce\n",[43,919,921],{"class":45,"line":920},15,[43,922,923],{},"    this.friction = 0.98; \u002F\u002F Multiplies dx to slow it down 2% every frame\n",[43,925,927],{"class":45,"line":926},16,[43,928,929],{},"  }\n",[43,931,933],{"class":45,"line":932},17,[43,934,104],{"emptyLinePlaceholder":103},[43,936,938],{"class":45,"line":937},18,[43,939,940],{},"  update(groundLevel) {\n",[43,942,944],{"class":45,"line":943},19,[43,945,946],{},"    \u002F\u002F 1. Apply Gravity\n",[43,948,950],{"class":45,"line":949},20,[43,951,952],{},"    this.dy += this.gravity;\n",[43,954,956],{"class":45,"line":955},21,[43,957,882],{},[43,959,961],{"class":45,"line":960},22,[43,962,963],{},"    \u002F\u002F 2. Apply Friction to slow rolling\n",[43,965,967],{"class":45,"line":966},23,[43,968,969],{},"    this.dx *= this.friction; \n",[43,971,973],{"class":45,"line":972},24,[43,974,104],{"emptyLinePlaceholder":103},[43,976,978],{"class":45,"line":977},25,[43,979,980],{},"    \u002F\u002F Stop completely if the speed is incredibly slow (micro-movements)\n",[43,982,984],{"class":45,"line":983},26,[43,985,986],{},"    if (Math.abs(this.dx) \u003C 0.1) this.dx = 0;\n",[43,988,990],{"class":45,"line":989},27,[43,991,104],{"emptyLinePlaceholder":103},[43,993,995],{"class":45,"line":994},28,[43,996,997],{},"    \u002F\u002F 3. Move the Ball\n",[43,999,1001],{"class":45,"line":1000},29,[43,1002,1003],{},"    this.x += this.dx;\n",[43,1005,1007],{"class":45,"line":1006},30,[43,1008,1009],{},"    this.y += this.dy;\n",[43,1011,1013],{"class":45,"line":1012},31,[43,1014,104],{"emptyLinePlaceholder":103},[43,1016,1018],{"class":45,"line":1017},32,[43,1019,1020],{},"    \u002F\u002F 4. Floor Collision & Bouncing Logic\n",[43,1022,1024],{"class":45,"line":1023},33,[43,1025,1026],{},"    if (this.y + this.height >= groundLevel) {\n",[43,1028,1030],{"class":45,"line":1029},34,[43,1031,1032],{},"      this.y = groundLevel - this.height; \u002F\u002F Prevent sinking\n",[43,1034,1036],{"class":45,"line":1035},35,[43,1037,1038],{},"      \n",[43,1040,1042],{"class":45,"line":1041},36,[43,1043,1044],{},"      \u002F\u002F Only bounce if falling fast enough (prevents infinite micro-jitters)\n",[43,1046,1048],{"class":45,"line":1047},37,[43,1049,1050],{},"      if (this.dy > 1) {\n",[43,1052,1054],{"class":45,"line":1053},38,[43,1055,1056],{},"        this.dy *= this.bounce; \u002F\u002F Reverse direction and lose energy!\n",[43,1058,1060],{"class":45,"line":1059},39,[43,1061,1062],{},"      } else {\n",[43,1064,1066],{"class":45,"line":1065},40,[43,1067,1068],{},"        this.dy = 0; \u002F\u002F Settle on the ground completely\n",[43,1070,1072],{"class":45,"line":1071},41,[43,1073,1074],{},"      }\n",[43,1076,1078],{"class":45,"line":1077},42,[43,1079,1080],{},"    }\n",[43,1082,1084],{"class":45,"line":1083},43,[43,1085,929],{},[43,1087,1089],{"class":45,"line":1088},44,[43,1090,809],{},[26,1092,1094],{"id":1093},"_3-creating-the-impact-transferring-momentum","3. Creating the Impact (Transferring Momentum)",[22,1096,1097,1098,1100,1101,1104,1105,727,1108,1111],{},"How do we put everything together? Inside your Game Loop, you use your ",[40,1099,748],{}," math to check if the Player touches the Ball.\nIf they touch, AND the player is currently in the ",[40,1102,1103],{},"\"Kick\""," animation state, we instantly inject explosive velocity (",[40,1106,1107],{},"dx",[40,1109,1110],{},"dy",") into the ball!",[34,1113,1115],{"className":36,"code":1114,"language":38,"meta":16,"style":16},"\u002F\u002F Inside your requestAnimationFrame Loop...\n\n\u002F\u002F 1. Update entities\nplayer.update();\nball.update(canvas.height - 90);\n\n\u002F\u002F 2. Check Hitbox Collision\nif (checkAABBCollision(player, ball)) {\n  \n  \u002F\u002F Did the player hit the kick button just now?\n  if (player.state === \"Kick\") {\n    \n    \u002F\u002F BOOM! Transfer huge momentum to the ball\n    \u002F\u002F Shoot it upwards and forwards!\n    ball.dy = -15; \n    \n    \u002F\u002F Shoot left or right depending on player's side\n    if (player.position === \"left\") {\n      ball.dx = 20; \u002F\u002F Blast to the right\n    } else {\n      ball.dx = -20; \u002F\u002F Blast to the left\n    }\n  } else {\n    \u002F\u002F If just walking into it normally, nudge the ball slightly\n    ball.dx = player.dx * 1.2; \n  }\n}\n",[40,1116,1117,1122,1126,1131,1136,1141,1145,1150,1155,1160,1165,1170,1174,1179,1184,1189,1193,1198,1203,1208,1213,1218,1222,1227,1232,1237,1241],{"__ignoreMap":16},[43,1118,1119],{"class":45,"line":46},[43,1120,1121],{},"\u002F\u002F Inside your requestAnimationFrame Loop...\n",[43,1123,1124],{"class":45,"line":52},[43,1125,104],{"emptyLinePlaceholder":103},[43,1127,1128],{"class":45,"line":94},[43,1129,1130],{},"\u002F\u002F 1. Update entities\n",[43,1132,1133],{"class":45,"line":100},[43,1134,1135],{},"player.update();\n",[43,1137,1138],{"class":45,"line":107},[43,1139,1140],{},"ball.update(canvas.height - 90);\n",[43,1142,1143],{"class":45,"line":113},[43,1144,104],{"emptyLinePlaceholder":103},[43,1146,1147],{"class":45,"line":195},[43,1148,1149],{},"\u002F\u002F 2. Check Hitbox Collision\n",[43,1151,1152],{"class":45,"line":200},[43,1153,1154],{},"if (checkAABBCollision(player, ball)) {\n",[43,1156,1157],{"class":45,"line":206},[43,1158,1159],{},"  \n",[43,1161,1162],{"class":45,"line":212},[43,1163,1164],{},"  \u002F\u002F Did the player hit the kick button just now?\n",[43,1166,1167],{"class":45,"line":218},[43,1168,1169],{},"  if (player.state === \"Kick\") {\n",[43,1171,1172],{"class":45,"line":224},[43,1173,882],{},[43,1175,1176],{"class":45,"line":618},[43,1177,1178],{},"    \u002F\u002F BOOM! Transfer huge momentum to the ball\n",[43,1180,1181],{"class":45,"line":914},[43,1182,1183],{},"    \u002F\u002F Shoot it upwards and forwards!\n",[43,1185,1186],{"class":45,"line":920},[43,1187,1188],{},"    ball.dy = -15; \n",[43,1190,1191],{"class":45,"line":926},[43,1192,882],{},[43,1194,1195],{"class":45,"line":932},[43,1196,1197],{},"    \u002F\u002F Shoot left or right depending on player's side\n",[43,1199,1200],{"class":45,"line":937},[43,1201,1202],{},"    if (player.position === \"left\") {\n",[43,1204,1205],{"class":45,"line":943},[43,1206,1207],{},"      ball.dx = 20; \u002F\u002F Blast to the right\n",[43,1209,1210],{"class":45,"line":949},[43,1211,1212],{},"    } else {\n",[43,1214,1215],{"class":45,"line":955},[43,1216,1217],{},"      ball.dx = -20; \u002F\u002F Blast to the left\n",[43,1219,1220],{"class":45,"line":960},[43,1221,1080],{},[43,1223,1224],{"class":45,"line":966},[43,1225,1226],{},"  } else {\n",[43,1228,1229],{"class":45,"line":972},[43,1230,1231],{},"    \u002F\u002F If just walking into it normally, nudge the ball slightly\n",[43,1233,1234],{"class":45,"line":977},[43,1235,1236],{},"    ball.dx = player.dx * 1.2; \n",[43,1238,1239],{"class":45,"line":983},[43,1240,929],{},[43,1242,1243],{"class":45,"line":989},[43,1244,809],{},[22,1246,1247],{},"With these three steps, your characters no longer pass through objects like ghosts! You have successfully implemented a professional Physics Engine logic within HTML5 Canvas.",[14,1249,1250,1254,1262,1273,1280,1289,1296,1348,1355,1359,1362,1394,1600,1604,1614,1627,1759],{"v-slot:id":16},[18,1251,1253],{"id":1252},"html5-canvas-fisika-tingkat-lanjut-dan-hitbox-kolisi","HTML5 Canvas: Fisika Tingkat Lanjut dan Hitbox Kolisi",[22,1255,1256,1257,1261],{},"Menggambar karakter yang bisa melompat dan jatuh karena gravitasi memang mengagumkan, tapi game takkan hidup sampai ada \"interaksi fisik\". Saat kaki Jagoan menendang bola, bola harus meroket mental! Saat bola jatuh ke aspal lapangan, ia tak boleh langsung berhenti kaku—ia seharusnya memantul (",[1258,1259,1260],"em",{},"bouncing","), lalu pelan-pelan menggelinding mereda karena tergesek rumput lapangan.",[22,1263,1264,1265,1268,1269,1272],{},"Di modul ini, kita akan membongkar tuntas rahasia ",[138,1266,1267],{},"Hitbox (Deteksi Layar Sentuh Tabrakan)"," dan ",[138,1270,1271],{},"Fisika Objek Mati"," yang cukup kompleks namun dijelaskan secara sederhana!",[26,1274,1276,1277],{"id":1275},"_1-jantung-tabrakan-2d-aabb-collision","1. Jantung Tabrakan 2D: ",[1258,1278,1279],{},"AABB Collision",[22,1281,1282,1283,1285,1286,552],{},"Pada Game Engine 2D manapun, kita nyaris TIDAK PERNAH menghitung tabrakan gambar pixel-per-pixel karena itu akan menyiksa prosesor (terlalu berat). Sebagai gantinya, bayangkan semua gambar karakter dikurung oleh kotak tak terlihat (Transparan) yang kita sebut ",[138,1284,741],{}," \u002F ",[1258,1287,1288],{},"Bounding Box",[22,1290,1291,1292,1295],{},"Algoritma matematika sejuta umat yang sering dipakai untuk membaca apakah dua Hitbox sedang saling tindih menyentuh disebut ",[138,1293,1294],{},"AABB (Axis-Aligned Bounding Box)",". Rumusnya sangat sederhana, mengecek 4 batas batas ruang:",[34,1297,1299],{"className":36,"code":1298,"language":38,"meta":16,"style":16},"\u002F\u002F Fungsi rahasia mengembalikan nilai Bekerja(TRUE) jika Kotak 1 & 2 bersentuhan !\nfunction bacaTabrakanAABB(karakter, objekBola) {\n  \u002F\u002F Algoritmanya mengecek apakah TIDAK MUNGKIN mereka bertabrakan terlebih dahulu\n  return (\n    karakter.x \u003C objekBola.x + objekBola.width &&   \u002F\u002F Batas Kiri Pemain nabrak Kanan Bola\n    karakter.x + karakter.width > objekBola.x &&   \u002F\u002F Batas Kanan Pemain nabrak Kiri Bola\n    karakter.y \u003C objekBola.y + objekBola.height &&  \u002F\u002F Batas Atas Pemain nabrak Bawah Bola\n    karakter.y + karakter.height > objekBola.y     \u002F\u002F Batas Bawah Pemain menembus Atas Bola\n  );\n}\n",[40,1300,1301,1306,1311,1316,1320,1325,1330,1335,1340,1344],{"__ignoreMap":16},[43,1302,1303],{"class":45,"line":46},[43,1304,1305],{},"\u002F\u002F Fungsi rahasia mengembalikan nilai Bekerja(TRUE) jika Kotak 1 & 2 bersentuhan !\n",[43,1307,1308],{"class":45,"line":52},[43,1309,1310],{},"function bacaTabrakanAABB(karakter, objekBola) {\n",[43,1312,1313],{"class":45,"line":94},[43,1314,1315],{},"  \u002F\u002F Algoritmanya mengecek apakah TIDAK MUNGKIN mereka bertabrakan terlebih dahulu\n",[43,1317,1318],{"class":45,"line":100},[43,1319,779],{},[43,1321,1322],{"class":45,"line":107},[43,1323,1324],{},"    karakter.x \u003C objekBola.x + objekBola.width &&   \u002F\u002F Batas Kiri Pemain nabrak Kanan Bola\n",[43,1326,1327],{"class":45,"line":113},[43,1328,1329],{},"    karakter.x + karakter.width > objekBola.x &&   \u002F\u002F Batas Kanan Pemain nabrak Kiri Bola\n",[43,1331,1332],{"class":45,"line":195},[43,1333,1334],{},"    karakter.y \u003C objekBola.y + objekBola.height &&  \u002F\u002F Batas Atas Pemain nabrak Bawah Bola\n",[43,1336,1337],{"class":45,"line":200},[43,1338,1339],{},"    karakter.y + karakter.height > objekBola.y     \u002F\u002F Batas Bawah Pemain menembus Atas Bola\n",[43,1341,1342],{"class":45,"line":206},[43,1343,804],{},[43,1345,1346],{"class":45,"line":212},[43,1347,809],{},[22,1349,1350,1351,1354],{},"Apabila ",[138,1352,1353],{},"KEEMPAT"," syarat logika di atas terpenuhi semuanya dalam satu frame, dijamin pasti kedua objek itu sedang bertubrukan hebat (Collision)!",[26,1356,1358],{"id":1357},"_2-fisika-lanjutan-benda-mati-bola","2. Fisika Lanjutan: Benda Mati (Bola)",[22,1360,1361],{},"Apabila Anda meracik Game Olahraga atau Action, peluru pelontar \u002F bolanya harus terasa berbobot nyata dan mematuhi fisika bumi sejati. Sebuah bola cerdas butuh dua DNA Fisika baru:",[825,1363,1364,1381],{},[135,1365,1366,1373,1374,1377,1378,1380],{},[138,1367,1368,1369,1372],{},"Daya Pegas Pantul (",[1258,1370,1371],{},"Bounce"," \u002F Restitution)",": Saat menabrak aspal keras, alih-alih setop mendadak nilannya ",[40,1375,1376],{},"(dy=0)",", ia malah memutar arah berbalik angkasa secara negatif ",[40,1379,835],{},". Hanya saja tiap memantul kekuatannya diskon \u002F dipotong energi jatuhnya sehingga kelamaan hilang dan mati.",[135,1382,1383,1390,1391,1393],{},[138,1384,1385,1386,1389],{},"Gaya Gesek (",[1258,1387,1388],{},"Friction"," \u002F Drag)",": Tarikan kasar dari pasir lapangan rumput yang menyiksa laju kecepatan ",[40,1392,1107],{},". Makin menggelinding pelan memelan, lalu berhentih total.",[34,1395,1397],{"className":36,"code":1396,"language":38,"meta":16,"style":16},"class BolaFisika {\n  constructor(x, y) {\n    this.x = x;\n    this.y = y;\n    this.width = 50;  \u002F\u002F Patokan Lebar Kotak Hitbox Bola\n    this.height = 50; \u002F\u002F Tinggi Kotak Bola\n    \n    \u002F\u002F Status Momentum Otot Gerakan\n    this.dx = 0; \u002F\u002F Tarikan maju-mundur Horizontal\n    this.dy = 0; \u002F\u002F Daya jatuh\u002Fterbang Vertikal\n    this.gravity = 0.5; \u002F\u002F Berat Bola standar ideal\n    \n    \u002F\u002F DUA RESEP RAHASIA FISIKA BUMI:\n    this.bounce = -0.7; \u002F\u002F Tiap nabrak bumi, berbalik angkasa TAPI kehilangan 30% kekencangannya!\n    this.friction = 0.98; \u002F\u002F Tiap frame berlari, kecepatan dx-nya dipangkas aus disikon 2% terhalang rumput!\n  }\n\n  update(groundLevel) {\n    \u002F\u002F 1. Seret bola oleh Gravitasi per frame\n    this.dy += this.gravity;\n    \n    \u002F\u002F 2. Potong tenaga Lari Horizontal dgn Paksaan Daya Gesek Rumput\n    this.dx *= this.friction; \n\n    \u002F\u002F Jika tenaganya sudah teramat sangat pelan (mendekati cacat), matikan total kemudi rodanya!\n    if (Math.abs(this.dx) \u003C 0.1) this.dx = 0;\n\n    \u002F\u002F 3. Aplikasikan penambahan titik kordinat riil bola ini berlayar\n    this.x += this.dx;\n    this.y += this.dy;\n\n    \u002F\u002F 4. LOGIKA PANTULAN (Kolisi menghujam lantai bawah Bumi!)\n    if (this.y + this.height >= groundLevel) {\n      this.y = groundLevel - this.height; \u002F\u002F Tahan di tepi aspal daratan, cegah bolanya nyungsep tembus bumi\n      \n      \u002F\u002F Cuma kasi izin mental jika masih meluncur berkecepatan tinggi!\n      \u002F\u002F (Bila sudah lemah pantulannya dibawah 1 px, berhentikan bolanya istirahat menapas di tanah)\n      if (this.dy > 1) {\n        this.dy *= this.bounce; \u002F\u002F Balikkan arah tenaga dy ke angkasa (- minus) lalu diskon energi 30% pakai bounce! \n      } else {\n        this.dy = 0; \u002F\u002F Biarkan si Bola Istirahat sempurna pulas di rerumputan!\n      }\n    }\n  }\n}\n",[40,1398,1399,1404,1408,1412,1416,1421,1426,1430,1435,1440,1445,1450,1454,1459,1464,1469,1473,1477,1481,1486,1490,1494,1499,1503,1507,1512,1516,1520,1525,1529,1533,1537,1542,1546,1551,1555,1560,1565,1569,1574,1578,1583,1587,1591,1595],{"__ignoreMap":16},[43,1400,1401],{"class":45,"line":46},[43,1402,1403],{},"class BolaFisika {\n",[43,1405,1406],{"class":45,"line":52},[43,1407,857],{},[43,1409,1410],{"class":45,"line":94},[43,1411,862],{},[43,1413,1414],{"class":45,"line":100},[43,1415,867],{},[43,1417,1418],{"class":45,"line":107},[43,1419,1420],{},"    this.width = 50;  \u002F\u002F Patokan Lebar Kotak Hitbox Bola\n",[43,1422,1423],{"class":45,"line":113},[43,1424,1425],{},"    this.height = 50; \u002F\u002F Tinggi Kotak Bola\n",[43,1427,1428],{"class":45,"line":195},[43,1429,882],{},[43,1431,1432],{"class":45,"line":200},[43,1433,1434],{},"    \u002F\u002F Status Momentum Otot Gerakan\n",[43,1436,1437],{"class":45,"line":206},[43,1438,1439],{},"    this.dx = 0; \u002F\u002F Tarikan maju-mundur Horizontal\n",[43,1441,1442],{"class":45,"line":212},[43,1443,1444],{},"    this.dy = 0; \u002F\u002F Daya jatuh\u002Fterbang Vertikal\n",[43,1446,1447],{"class":45,"line":218},[43,1448,1449],{},"    this.gravity = 0.5; \u002F\u002F Berat Bola standar ideal\n",[43,1451,1452],{"class":45,"line":224},[43,1453,882],{},[43,1455,1456],{"class":45,"line":618},[43,1457,1458],{},"    \u002F\u002F DUA RESEP RAHASIA FISIKA BUMI:\n",[43,1460,1461],{"class":45,"line":914},[43,1462,1463],{},"    this.bounce = -0.7; \u002F\u002F Tiap nabrak bumi, berbalik angkasa TAPI kehilangan 30% kekencangannya!\n",[43,1465,1466],{"class":45,"line":920},[43,1467,1468],{},"    this.friction = 0.98; \u002F\u002F Tiap frame berlari, kecepatan dx-nya dipangkas aus disikon 2% terhalang rumput!\n",[43,1470,1471],{"class":45,"line":926},[43,1472,929],{},[43,1474,1475],{"class":45,"line":932},[43,1476,104],{"emptyLinePlaceholder":103},[43,1478,1479],{"class":45,"line":937},[43,1480,940],{},[43,1482,1483],{"class":45,"line":943},[43,1484,1485],{},"    \u002F\u002F 1. Seret bola oleh Gravitasi per frame\n",[43,1487,1488],{"class":45,"line":949},[43,1489,952],{},[43,1491,1492],{"class":45,"line":955},[43,1493,882],{},[43,1495,1496],{"class":45,"line":960},[43,1497,1498],{},"    \u002F\u002F 2. Potong tenaga Lari Horizontal dgn Paksaan Daya Gesek Rumput\n",[43,1500,1501],{"class":45,"line":966},[43,1502,969],{},[43,1504,1505],{"class":45,"line":972},[43,1506,104],{"emptyLinePlaceholder":103},[43,1508,1509],{"class":45,"line":977},[43,1510,1511],{},"    \u002F\u002F Jika tenaganya sudah teramat sangat pelan (mendekati cacat), matikan total kemudi rodanya!\n",[43,1513,1514],{"class":45,"line":983},[43,1515,986],{},[43,1517,1518],{"class":45,"line":989},[43,1519,104],{"emptyLinePlaceholder":103},[43,1521,1522],{"class":45,"line":994},[43,1523,1524],{},"    \u002F\u002F 3. Aplikasikan penambahan titik kordinat riil bola ini berlayar\n",[43,1526,1527],{"class":45,"line":1000},[43,1528,1003],{},[43,1530,1531],{"class":45,"line":1006},[43,1532,1009],{},[43,1534,1535],{"class":45,"line":1012},[43,1536,104],{"emptyLinePlaceholder":103},[43,1538,1539],{"class":45,"line":1017},[43,1540,1541],{},"    \u002F\u002F 4. LOGIKA PANTULAN (Kolisi menghujam lantai bawah Bumi!)\n",[43,1543,1544],{"class":45,"line":1023},[43,1545,1026],{},[43,1547,1548],{"class":45,"line":1029},[43,1549,1550],{},"      this.y = groundLevel - this.height; \u002F\u002F Tahan di tepi aspal daratan, cegah bolanya nyungsep tembus bumi\n",[43,1552,1553],{"class":45,"line":1035},[43,1554,1038],{},[43,1556,1557],{"class":45,"line":1041},[43,1558,1559],{},"      \u002F\u002F Cuma kasi izin mental jika masih meluncur berkecepatan tinggi!\n",[43,1561,1562],{"class":45,"line":1047},[43,1563,1564],{},"      \u002F\u002F (Bila sudah lemah pantulannya dibawah 1 px, berhentikan bolanya istirahat menapas di tanah)\n",[43,1566,1567],{"class":45,"line":1053},[43,1568,1050],{},[43,1570,1571],{"class":45,"line":1059},[43,1572,1573],{},"        this.dy *= this.bounce; \u002F\u002F Balikkan arah tenaga dy ke angkasa (- minus) lalu diskon energi 30% pakai bounce! \n",[43,1575,1576],{"class":45,"line":1065},[43,1577,1062],{},[43,1579,1580],{"class":45,"line":1071},[43,1581,1582],{},"        this.dy = 0; \u002F\u002F Biarkan si Bola Istirahat sempurna pulas di rerumputan!\n",[43,1584,1585],{"class":45,"line":1077},[43,1586,1074],{},[43,1588,1589],{"class":45,"line":1083},[43,1590,1080],{},[43,1592,1593],{"class":45,"line":1088},[43,1594,929],{},[43,1596,1598],{"class":45,"line":1597},45,[43,1599,809],{},[26,1601,1603],{"id":1602},"_3-moment-of-impact-transfer-energi-tendangan-lurus","3. Moment of Impact! (Transfer Energi Tendangan Lurus)",[22,1605,1606,1607,1610,1611,1613],{},"Bagaimana merekatkan gabungan Karakter Utama penendang tersebut dengan Logika Kolisi Bola pintar ini? Mudah saja! Di dalam rotasi ",[1258,1608,1609],{},"Game Loop",", kita gunakan Matematika sensor Hitbox ",[40,1612,748],{}," diatas guna membaca adegan jika Pemain Anda secara kulit menyentuh Hitbox Bola!",[22,1615,1616,1617,1620,1621,1623,1624,1626],{},"Dan Bila bersentuhan, DAN Si Pemain rupanya kebetulan sedang menekan Tombol ",[40,1618,1619],{},"\"KICK \u002F TENDANG\"",", Kita suntikkan paksa aliran energi Daya Ledak tinggi ke Nadi ",[40,1622,1107],{}," maupun ",[40,1625,1110],{}," peluru Bola tersebut seolah roket terbang!",[34,1628,1630],{"className":36,"code":1629,"language":38,"meta":16,"style":16},"\u002F\u002F Di rongga terdalam RequestAnimationFrame \u002F Game Loop Anda...\n\n\u002F\u002F 1. Eksekusi Logika Luruh Entitas Dulu!\nplayer.update();\nbola.update(canvas.height - 90); \u002F\u002F 90 adalah patokan tebal lantai\n\n\u002F\u002F 2. Radar Deteksi Tabrakan Fisik Dinyalakan (AABB System)\nif (bacaTabrakanAABB(player, bola)) {\n  \n  \u002F\u002F Oh, ternyata Hitbox bersentuhan, Apakah sang Pemain tepat bertepatan menyalakan status TEBASAN ANIMASI TENDANG(KICK)?\n  if (player.state === \"Kick\") {\n    \n    \u002F\u002F BOOOM!!! Transfer Energi rahang luar biasa kedalam Jasad Si Bola\n    \u002F\u002F Tembak mental ke Angkasa jauh ke atas (- Minus Besar)!\n    bola.dy = -15; \n    \n    \u002F\u002F Tembakan arah bola berdasarkan kubu si Pemain!\n    if (player.position === \"left\") {\n      bola.dx = 20; \u002F\u002F Jika dia dari markas Kiri, Dorong ledakan Kecepatan bola X melancip jauh ke arah Kanan Markas Musuh!\n    } else {\n      bola.dx = -20; \u002F\u002F Jika dia dari Kanan, dorong Tembakan Rudal ke wilayah Kiri Positif!\n    }\n  } else {\n    \u002F\u002F Apabila si pemain sekonyong-konyongnya jalan menabrak biasa gawang Bola secara damai?\n    \u002F\u002F Boleh digradasi sedikit seolah \"Menggiring (Dribble) Bola\"\n    bola.dx = player.dx * 1.2; \u002F\u002F Tambahan daya gesek dikit ke bola dari speed pelan kaki!\n  }\n}\n",[40,1631,1632,1637,1641,1646,1650,1655,1659,1664,1669,1673,1678,1682,1686,1691,1696,1701,1705,1710,1714,1719,1723,1728,1732,1736,1741,1746,1751,1755],{"__ignoreMap":16},[43,1633,1634],{"class":45,"line":46},[43,1635,1636],{},"\u002F\u002F Di rongga terdalam RequestAnimationFrame \u002F Game Loop Anda...\n",[43,1638,1639],{"class":45,"line":52},[43,1640,104],{"emptyLinePlaceholder":103},[43,1642,1643],{"class":45,"line":94},[43,1644,1645],{},"\u002F\u002F 1. Eksekusi Logika Luruh Entitas Dulu!\n",[43,1647,1648],{"class":45,"line":100},[43,1649,1135],{},[43,1651,1652],{"class":45,"line":107},[43,1653,1654],{},"bola.update(canvas.height - 90); \u002F\u002F 90 adalah patokan tebal lantai\n",[43,1656,1657],{"class":45,"line":113},[43,1658,104],{"emptyLinePlaceholder":103},[43,1660,1661],{"class":45,"line":195},[43,1662,1663],{},"\u002F\u002F 2. Radar Deteksi Tabrakan Fisik Dinyalakan (AABB System)\n",[43,1665,1666],{"class":45,"line":200},[43,1667,1668],{},"if (bacaTabrakanAABB(player, bola)) {\n",[43,1670,1671],{"class":45,"line":206},[43,1672,1159],{},[43,1674,1675],{"class":45,"line":212},[43,1676,1677],{},"  \u002F\u002F Oh, ternyata Hitbox bersentuhan, Apakah sang Pemain tepat bertepatan menyalakan status TEBASAN ANIMASI TENDANG(KICK)?\n",[43,1679,1680],{"class":45,"line":218},[43,1681,1169],{},[43,1683,1684],{"class":45,"line":224},[43,1685,882],{},[43,1687,1688],{"class":45,"line":618},[43,1689,1690],{},"    \u002F\u002F BOOOM!!! Transfer Energi rahang luar biasa kedalam Jasad Si Bola\n",[43,1692,1693],{"class":45,"line":914},[43,1694,1695],{},"    \u002F\u002F Tembak mental ke Angkasa jauh ke atas (- Minus Besar)!\n",[43,1697,1698],{"class":45,"line":920},[43,1699,1700],{},"    bola.dy = -15; \n",[43,1702,1703],{"class":45,"line":926},[43,1704,882],{},[43,1706,1707],{"class":45,"line":932},[43,1708,1709],{},"    \u002F\u002F Tembakan arah bola berdasarkan kubu si Pemain!\n",[43,1711,1712],{"class":45,"line":937},[43,1713,1202],{},[43,1715,1716],{"class":45,"line":943},[43,1717,1718],{},"      bola.dx = 20; \u002F\u002F Jika dia dari markas Kiri, Dorong ledakan Kecepatan bola X melancip jauh ke arah Kanan Markas Musuh!\n",[43,1720,1721],{"class":45,"line":949},[43,1722,1212],{},[43,1724,1725],{"class":45,"line":955},[43,1726,1727],{},"      bola.dx = -20; \u002F\u002F Jika dia dari Kanan, dorong Tembakan Rudal ke wilayah Kiri Positif!\n",[43,1729,1730],{"class":45,"line":960},[43,1731,1080],{},[43,1733,1734],{"class":45,"line":966},[43,1735,1226],{},[43,1737,1738],{"class":45,"line":972},[43,1739,1740],{},"    \u002F\u002F Apabila si pemain sekonyong-konyongnya jalan menabrak biasa gawang Bola secara damai?\n",[43,1742,1743],{"class":45,"line":977},[43,1744,1745],{},"    \u002F\u002F Boleh digradasi sedikit seolah \"Menggiring (Dribble) Bola\"\n",[43,1747,1748],{"class":45,"line":983},[43,1749,1750],{},"    bola.dx = player.dx * 1.2; \u002F\u002F Tambahan daya gesek dikit ke bola dari speed pelan kaki!\n",[43,1752,1753],{"class":45,"line":989},[43,1754,929],{},[43,1756,1757],{"class":45,"line":994},[43,1758,809],{},[22,1760,1761,1762,1765],{},"Melengkapi ketiga sistem tersebut, Tokoh dan Objek properti Game Anda tak lagi hampa saling menembus raba raga sebagai gumpalan bayang hantu! Anda berhasil mengakali penyematan ",[138,1763,1764],{},"Hukum Kolisi Gravitasi Game Engine Fisika (Physics System Simulator)"," kelas wahid langsung di dalam barisan Canvas Web Platform buatan mandiri murni anda tersebut!",[690,1767,692],{},{"title":16,"searchDepth":52,"depth":52,"links":1769},[],{"title_en":707,"title_id":1253,"description_en":1771,"description_id":1772,"date":699,"readingTime":1773},"Discover the secrets of 2D game collisions, projectile physics, bouncing balls, friction, and responsive hitboxes.","Temukan rahasia di balik tabrakan game 2D, fisika proyektil, bola memantul, gesekan lantai (friction), dan hitbox responsif.","12 min read","\u002Fblog\u002Fcanvas-collision-physics",{"title":707,"description":16},"blog\u002Fcanvas-collision-physics","JrWbGKGq-KjBvoDwOxbpq7s0f82sWZAwQFX4rYYc-hk",{"id":1779,"title":1780,"body":1781,"description":16,"extension":695,"meta":2355,"navigation":103,"path":2359,"seo":2360,"stem":2361,"__hash__":2362},"blog\u002Fblog\u002Fcanvas-game-loop.md","HTML5 Canvas: The Game Loop and Animation",{"type":8,"value":1782,"toc":2353},[1783,2351],[11,1784,1785,2060],{},[14,1786,1787,1790,1796,1799,1819,1827,1842,1850,1857,1862,1871,1875,1878,2053],{"v-slot:en":16},[18,1788,1780],{"id":1789},"html5-canvas-the-game-loop-and-animation",[22,1791,1792,1793,1795],{},"Drawing shapes on a canvas is just the beginning. To make a game, those shapes need to move! This is where the ",[138,1794,1609],{}," comes in. It's the beating heart of every video game ever made.",[22,1797,1798],{},"A Game Loop does three things repeatedly, usually 60 times a second (60 FPS):",[825,1800,1801,1807,1813],{},[135,1802,1803,1806],{},[138,1804,1805],{},"Clear"," the old drawings off the canvas.",[135,1808,1809,1812],{},[138,1810,1811],{},"Update"," the positions and logic of your game objects.",[135,1814,1815,1818],{},[138,1816,1817],{},"Draw"," the objects in their new positions.",[26,1820,1822,1823,1826],{"id":1821},"_1-why-requestanimationframe","1. Why ",[40,1824,1825],{},"requestAnimationFrame","?",[22,1828,1829,1830,1833,1834,1837,1838,1841],{},"In the early days of the web, developers used ",[40,1831,1832],{},"setInterval"," or ",[40,1835,1836],{},"setTimeout"," for animations. Today, we use ",[40,1839,1840],{},"window.requestAnimationFrame",". It's significantly better because:",[132,1843,1844,1847],{},[135,1845,1846],{},"It's optimized by the browser to match your screen's refresh rate (usually 60Hz or 144Hz for buttery smooth animations).",[135,1848,1849],{},"It pauses when the user switches to another browser tab, saving battery and CPU.",[26,1851,1853,1854,67],{"id":1852},"_2-clearing-the-frame-clearrect","2. Clearing the Frame (",[40,1855,1856],{},"clearRect",[22,1858,1859,1860,552],{},"If you move an object without clearing the canvas first, it will leave a permanent \"trail\" behind it, like a paintbrush dragging across a page. You must erase the entire canvas at the start of every frame using ",[40,1861,1856],{},[34,1863,1865],{"className":36,"code":1864,"language":38,"meta":16,"style":16},"ctx.clearRect(0, 0, canvas.width, canvas.height); \u002F\u002F Erases everything\n",[40,1866,1867],{"__ignoreMap":16},[43,1868,1869],{"class":45,"line":46},[43,1870,1864],{},[26,1872,1874],{"id":1873},"_3-building-a-simple-bouncing-ball","3. Building a Simple Bouncing Ball",[22,1876,1877],{},"Let's put it all together into a working Game Loop. We'll make a ball that bounces off the left and right walls of the canvas.",[34,1879,1881],{"className":36,"code":1880,"language":38,"meta":16,"style":16},"const canvas = document.getElementById('gameCanvas');\nconst ctx = canvas.getContext('2d');\n\n\u002F\u002F 1. Define our object state\nlet ball = {\n  x: 50,\n  y: 150,\n  radius: 20,\n  speedX: 5 \u002F\u002F How much X changes per frame (velocity)\n};\n\n\u002F\u002F 2. The Game Loop Function\nfunction update() {\n  \u002F\u002F Step A: Clear the Canvas\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  \n  \u002F\u002F Step B: Update Logic (Move the ball)\n  ball.x += ball.speedX;\n  \n  \u002F\u002F Bounce logic: If ball hits right or left edge, reverse speed\n  if (ball.x + ball.radius > canvas.width || ball.x - ball.radius \u003C 0) {\n    ball.speedX = -ball.speedX; \u002F\u002F Reverse direction\n  }\n  \n  \u002F\u002F Step C: Draw the new frame\n  ctx.beginPath();\n  ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);\n  ctx.fillStyle = 'red';\n  ctx.fill();\n  \n  \u002F\u002F Step D: Loop it! Call update again before the next screen repaint\n  requestAnimationFrame(update);\n}\n\n\u002F\u002F 3. Start the loop\nupdate();\n",[40,1882,1883,1888,1892,1896,1901,1906,1911,1916,1921,1926,1931,1935,1940,1945,1950,1955,1959,1964,1969,1973,1978,1983,1988,1992,1996,2001,2006,2011,2016,2021,2025,2030,2035,2039,2043,2048],{"__ignoreMap":16},[43,1884,1885],{"class":45,"line":46},[43,1886,1887],{},"const canvas = document.getElementById('gameCanvas');\n",[43,1889,1890],{"class":45,"line":52},[43,1891,55],{},[43,1893,1894],{"class":45,"line":94},[43,1895,104],{"emptyLinePlaceholder":103},[43,1897,1898],{"class":45,"line":100},[43,1899,1900],{},"\u002F\u002F 1. Define our object state\n",[43,1902,1903],{"class":45,"line":107},[43,1904,1905],{},"let ball = {\n",[43,1907,1908],{"class":45,"line":113},[43,1909,1910],{},"  x: 50,\n",[43,1912,1913],{"class":45,"line":195},[43,1914,1915],{},"  y: 150,\n",[43,1917,1918],{"class":45,"line":200},[43,1919,1920],{},"  radius: 20,\n",[43,1922,1923],{"class":45,"line":206},[43,1924,1925],{},"  speedX: 5 \u002F\u002F How much X changes per frame (velocity)\n",[43,1927,1928],{"class":45,"line":212},[43,1929,1930],{},"};\n",[43,1932,1933],{"class":45,"line":218},[43,1934,104],{"emptyLinePlaceholder":103},[43,1936,1937],{"class":45,"line":224},[43,1938,1939],{},"\u002F\u002F 2. The Game Loop Function\n",[43,1941,1942],{"class":45,"line":618},[43,1943,1944],{},"function update() {\n",[43,1946,1947],{"class":45,"line":914},[43,1948,1949],{},"  \u002F\u002F Step A: Clear the Canvas\n",[43,1951,1952],{"class":45,"line":920},[43,1953,1954],{},"  ctx.clearRect(0, 0, canvas.width, canvas.height);\n",[43,1956,1957],{"class":45,"line":926},[43,1958,1159],{},[43,1960,1961],{"class":45,"line":932},[43,1962,1963],{},"  \u002F\u002F Step B: Update Logic (Move the ball)\n",[43,1965,1966],{"class":45,"line":937},[43,1967,1968],{},"  ball.x += ball.speedX;\n",[43,1970,1971],{"class":45,"line":943},[43,1972,1159],{},[43,1974,1975],{"class":45,"line":949},[43,1976,1977],{},"  \u002F\u002F Bounce logic: If ball hits right or left edge, reverse speed\n",[43,1979,1980],{"class":45,"line":955},[43,1981,1982],{},"  if (ball.x + ball.radius > canvas.width || ball.x - ball.radius \u003C 0) {\n",[43,1984,1985],{"class":45,"line":960},[43,1986,1987],{},"    ball.speedX = -ball.speedX; \u002F\u002F Reverse direction\n",[43,1989,1990],{"class":45,"line":966},[43,1991,929],{},[43,1993,1994],{"class":45,"line":972},[43,1995,1159],{},[43,1997,1998],{"class":45,"line":977},[43,1999,2000],{},"  \u002F\u002F Step C: Draw the new frame\n",[43,2002,2003],{"class":45,"line":983},[43,2004,2005],{},"  ctx.beginPath();\n",[43,2007,2008],{"class":45,"line":989},[43,2009,2010],{},"  ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);\n",[43,2012,2013],{"class":45,"line":994},[43,2014,2015],{},"  ctx.fillStyle = 'red';\n",[43,2017,2018],{"class":45,"line":1000},[43,2019,2020],{},"  ctx.fill();\n",[43,2022,2023],{"class":45,"line":1006},[43,2024,1159],{},[43,2026,2027],{"class":45,"line":1012},[43,2028,2029],{},"  \u002F\u002F Step D: Loop it! Call update again before the next screen repaint\n",[43,2031,2032],{"class":45,"line":1017},[43,2033,2034],{},"  requestAnimationFrame(update);\n",[43,2036,2037],{"class":45,"line":1023},[43,2038,809],{},[43,2040,2041],{"class":45,"line":1029},[43,2042,104],{"emptyLinePlaceholder":103},[43,2044,2045],{"class":45,"line":1035},[43,2046,2047],{},"\u002F\u002F 3. Start the loop\n",[43,2049,2050],{"class":45,"line":1041},[43,2051,2052],{},"update();\n",[22,2054,2055,2056,2059],{},"And that’s it! The ",[40,2057,2058],{},"requestAnimationFrame(update)"," at the end creates an infinite loop that smoothly animates the ball back and forth across your screen. Once you understand this loop, you are officially making a game!",[14,2061,2062,2066,2073,2076,2096,2102,2114,2126,2132,2137,2152,2156,2159,2340],{"v-slot:id":16},[18,2063,2065],{"id":2064},"html5-canvas-game-loop-dan-animasi","HTML5 Canvas: Game Loop dan Animasi",[22,2067,2068,2069,2072],{},"Menggambar bentuk statis di dalam canvas hanyalah awal mulanya. Untuk membuat sebuah game, bentuk-bentuk itu harus dipaksa bergerak merespons interaksi! Di sinilah ",[138,2070,2071],{},"Game Loop (Siklus Game)"," berperan mengambil alih kemudi. Engine daur ulang inilah jantung yang berdetak di setiap game digital mana pun di jagat raya ini.",[22,2074,2075],{},"Sebuah Game Loop melakukan tiga fungsi ini secara berulang terus menerus secara stabil, tipikalnya dipanggil 60 kali per detiknya (60 Frames Per Second \u002F FPS):",[825,2077,2078,2084,2090],{},[135,2079,2080,2083],{},[138,2081,2082],{},"Bersihkan (Clear)"," layar kanvas secara massal dari sisa coretan frame gambar di detik sebelumnya.",[135,2085,2086,2089],{},[138,2087,2088],{},"Perbarui (Update)"," angka koordinat posisi variabel fisika game Anda (Contoh: Nambah nilai X geser ke kanan ujung layar).",[135,2091,2092,2095],{},[138,2093,2094],{},"Cetak Ulang (Draw)"," menggambar ulang wujud objek-objek tersebut di titik mendarat kordinat barunya.",[26,2097,2099,2100,1826],{"id":2098},"_1-mengapa-menggunakan-engine-requestanimationframe","1. Mengapa Menggunakan Engine ",[40,2101,1825],{},[22,2103,2104,2105,2107,2108,2110,2111,2113],{},"Di era lampau ketika browser web baru mengenal JS, programmer memodifikasi ",[40,2106,1832],{}," atau ",[40,2109,1836],{}," paksa untuk mengkalkulasi waktu gerak animasi. Tapi di standar modern sekarang kita mewajibkan pemakaian ",[40,2112,1840],{},". Kenapa ini jauh lebih dihormati?",[132,2115,2116,2119],{},[135,2117,2118],{},"Fungsi ini sinkronisasinya diawasi sepenuhnya oleh otak Mesin Browser demi menyamakan timing Refresh Rate spek asli monitor perangkat layar pemakainya (Misalnya menembus 120Hz\u002F144Hz) sehingga visual pergerakan animasinya serasa mulus merayap laksana mentega.",[135,2120,2121,2122,2125],{},"Animasi engine Loop akan otomatis dibekukan \u002F di-",[1258,2123,2124],{},"pause"," sejenak ketika pemain berpindah (alt-tab) asik melakukan browsing pada laman window tab browser lain. Ini adalah trik luar biasa menghemat penarikan aliran beban Baterai laptop\u002FRAM HP, serta meringankan panas performa CPU!",[26,2127,2129,2130,67],{"id":2128},"_2-sapu-bersih-pigmen-layar-keseluruhan-clearrect","2. Sapu Bersih Pigmen Layar Keseluruhan (",[40,2131,1856],{},[22,2133,2134,2135,552],{},"Canvas itu hakikat dasarnya persis bak papan lukis selembar coretan kertas putih yang dituangi cat berwarna secara permanen (Destructive Drawing).\nOleh karenanya bilamana Anda iseng menyuntik posisi koordinat angka X\u002FY agar gambarnya bergeser letak kedepan secara konstan, benda tersebut malahan pasti akan meninggalkan jejak \"ekor\" garis warna warna kotor berkepanjangan menyeret bak kuas kuas ngebut terhulus paksa mencoreng kertasnya. Karena itu hukum kanvas; \"layar lama harus dihancurkan sebelum melahirkan wujud masa depan!\" menggoreskan sintak metode ",[40,2136,1856],{},[34,2138,2140],{"className":36,"code":2139,"language":38,"meta":16,"style":16},"\u002F\u002F Menghancurkan jejak keseluruhan pigmen warna layar dari kordinat titik asalnya (0,0) sebanyak luas lebar dan total tingginya \nctx.clearRect(0, 0, canvas.width, canvas.height); \n",[40,2141,2142,2147],{"__ignoreMap":16},[43,2143,2144],{"class":45,"line":46},[43,2145,2146],{},"\u002F\u002F Menghancurkan jejak keseluruhan pigmen warna layar dari kordinat titik asalnya (0,0) sebanyak luas lebar dan total tingginya \n",[43,2148,2149],{"class":45,"line":52},[43,2150,2151],{},"ctx.clearRect(0, 0, canvas.width, canvas.height);\n",[26,2153,2155],{"id":2154},"_3-merakit-game-animasi-pertama-bola-pemantul-tembok","3. Merakit Game Animasi Pertama: \"Bola Pemantul Tembok\"",[22,2157,2158],{},"Mari kita gabungkan seluruh konsep ilmu di semua part seri dasar yang lalu untuk menciptakan Animasi Loop abadi! Kali ini ditambahkan bumbu resep deteksi benturan kiri kanan pinggiran layar dari kanvasnya.",[34,2160,2162],{"className":36,"code":2161,"language":38,"meta":16,"style":16},"const canvas = document.getElementById('myGameCanvas');\nconst ctx = canvas.getContext('2d');\n\n\u002F\u002F 1. Deklarasi Sifat\u002FKarakteristik Fisik Objek \nlet ball = {\n  x: 50,          \u002F\u002F Titik pusat koordinat X\n  y: 150,         \u002F\u002F Titik pusat koordinat Y  \n  radius: 20,     \u002F\u002F Lembaran jari-jari lingkaran (Radius Bulat)\n  speedX: 5       \u002F\u002F Daya lari pacu (Velocity): Menjumlah perpindahan pixel memanjang +5 pixel untuk tiap 1 Frame siklus update loop berjalan!\n};\n\n\u002F\u002F 2. Fungsi Engine Jantung Loop Game Utama Sang Eksekutor\nfunction gameLoop() {\n  \n  \u002F\u002F Fase A: PENGHAPUSAN MASAL - Reset Kebersihan Kanvas Papan Gambar\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n  \n  \u002F\u002F Fase B: EVALUASI LOGIC GERAK (Perbarui pergerakan)\n  ball.x += ball.speedX;  \u002F\u002F Dorong titik (x) laju bola ditambah beban akselerasi (speedX+5px) terus setiap putarannya\n  \n  \u002F\u002F Deteksi Pantulan Fisika Sederhana: \n  \u002F\u002F APABILA ujung kulit terluar kanan lingakaran (titik Pusatx + jarak radius bola) menembus lewat ukuran batas 'lebar canvas', \n  \u002F\u002F ATAU ujung terluar cangkang sisi posisi bola kiri (x - beban radius) membentur tembok sumbu mula layar pangkal nol (0).\n  if (ball.x + ball.radius > canvas.width || ball.x - ball.radius \u003C 0) {\n    ball.speedX = -ball.speedX; \u002F\u002F Arahkan nilai pantulnya jadi nilai yang berkebalikang(-) berputar hadap belakang! (Contoh awal jalan positif 5 obah mundur jadi minus -5) memantul berlawanan!.\n  }\n  \n  \u002F\u002F Fase C: MENGGAMBAR (Printing Ulang objek tersebut)\n  ctx.beginPath();\n  ctx.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI);\n  ctx.fillStyle = 'red';\n  ctx.fill();\n  \n  \u002F\u002F Fase D: Panggil kembali perintah memaksa Engine mengukir di frame pengulangan animasi secara rekap rekrusif berikutnya (sebelum refresh cat ulang repaint selanjutnya layarnya si monitor meledak)  \n  requestAnimationFrame(gameLoop);\n}\n\n\u002F\u002F 3. Pemicu Petir Kehidupan Pertama agar Game Loop Jaga Menyala Sepanjang Masa\ngameLoop();\n",[40,2163,2164,2169,2173,2177,2182,2186,2191,2196,2201,2206,2210,2214,2219,2224,2228,2233,2237,2241,2246,2251,2255,2260,2265,2270,2274,2279,2283,2287,2292,2296,2300,2304,2308,2312,2317,2322,2326,2330,2335],{"__ignoreMap":16},[43,2165,2166],{"class":45,"line":46},[43,2167,2168],{},"const canvas = document.getElementById('myGameCanvas');\n",[43,2170,2171],{"class":45,"line":52},[43,2172,55],{},[43,2174,2175],{"class":45,"line":94},[43,2176,104],{"emptyLinePlaceholder":103},[43,2178,2179],{"class":45,"line":100},[43,2180,2181],{},"\u002F\u002F 1. Deklarasi Sifat\u002FKarakteristik Fisik Objek \n",[43,2183,2184],{"class":45,"line":107},[43,2185,1905],{},[43,2187,2188],{"class":45,"line":113},[43,2189,2190],{},"  x: 50,          \u002F\u002F Titik pusat koordinat X\n",[43,2192,2193],{"class":45,"line":195},[43,2194,2195],{},"  y: 150,         \u002F\u002F Titik pusat koordinat Y  \n",[43,2197,2198],{"class":45,"line":200},[43,2199,2200],{},"  radius: 20,     \u002F\u002F Lembaran jari-jari lingkaran (Radius Bulat)\n",[43,2202,2203],{"class":45,"line":206},[43,2204,2205],{},"  speedX: 5       \u002F\u002F Daya lari pacu (Velocity): Menjumlah perpindahan pixel memanjang +5 pixel untuk tiap 1 Frame siklus update loop berjalan!\n",[43,2207,2208],{"class":45,"line":212},[43,2209,1930],{},[43,2211,2212],{"class":45,"line":218},[43,2213,104],{"emptyLinePlaceholder":103},[43,2215,2216],{"class":45,"line":224},[43,2217,2218],{},"\u002F\u002F 2. Fungsi Engine Jantung Loop Game Utama Sang Eksekutor\n",[43,2220,2221],{"class":45,"line":618},[43,2222,2223],{},"function gameLoop() {\n",[43,2225,2226],{"class":45,"line":914},[43,2227,1159],{},[43,2229,2230],{"class":45,"line":920},[43,2231,2232],{},"  \u002F\u002F Fase A: PENGHAPUSAN MASAL - Reset Kebersihan Kanvas Papan Gambar\n",[43,2234,2235],{"class":45,"line":926},[43,2236,1954],{},[43,2238,2239],{"class":45,"line":932},[43,2240,1159],{},[43,2242,2243],{"class":45,"line":937},[43,2244,2245],{},"  \u002F\u002F Fase B: EVALUASI LOGIC GERAK (Perbarui pergerakan)\n",[43,2247,2248],{"class":45,"line":943},[43,2249,2250],{},"  ball.x += ball.speedX;  \u002F\u002F Dorong titik (x) laju bola ditambah beban akselerasi (speedX+5px) terus setiap putarannya\n",[43,2252,2253],{"class":45,"line":949},[43,2254,1159],{},[43,2256,2257],{"class":45,"line":955},[43,2258,2259],{},"  \u002F\u002F Deteksi Pantulan Fisika Sederhana: \n",[43,2261,2262],{"class":45,"line":960},[43,2263,2264],{},"  \u002F\u002F APABILA ujung kulit terluar kanan lingakaran (titik Pusatx + jarak radius bola) menembus lewat ukuran batas 'lebar canvas', \n",[43,2266,2267],{"class":45,"line":966},[43,2268,2269],{},"  \u002F\u002F ATAU ujung terluar cangkang sisi posisi bola kiri (x - beban radius) membentur tembok sumbu mula layar pangkal nol (0).\n",[43,2271,2272],{"class":45,"line":972},[43,2273,1982],{},[43,2275,2276],{"class":45,"line":977},[43,2277,2278],{},"    ball.speedX = -ball.speedX; \u002F\u002F Arahkan nilai pantulnya jadi nilai yang berkebalikang(-) berputar hadap belakang! (Contoh awal jalan positif 5 obah mundur jadi minus -5) memantul berlawanan!.\n",[43,2280,2281],{"class":45,"line":983},[43,2282,929],{},[43,2284,2285],{"class":45,"line":989},[43,2286,1159],{},[43,2288,2289],{"class":45,"line":994},[43,2290,2291],{},"  \u002F\u002F Fase C: MENGGAMBAR (Printing Ulang objek tersebut)\n",[43,2293,2294],{"class":45,"line":1000},[43,2295,2005],{},[43,2297,2298],{"class":45,"line":1006},[43,2299,2010],{},[43,2301,2302],{"class":45,"line":1012},[43,2303,2015],{},[43,2305,2306],{"class":45,"line":1017},[43,2307,2020],{},[43,2309,2310],{"class":45,"line":1023},[43,2311,1159],{},[43,2313,2314],{"class":45,"line":1029},[43,2315,2316],{},"  \u002F\u002F Fase D: Panggil kembali perintah memaksa Engine mengukir di frame pengulangan animasi secara rekap rekrusif berikutnya (sebelum refresh cat ulang repaint selanjutnya layarnya si monitor meledak)  \n",[43,2318,2319],{"class":45,"line":1035},[43,2320,2321],{},"  requestAnimationFrame(gameLoop);\n",[43,2323,2324],{"class":45,"line":1041},[43,2325,809],{},[43,2327,2328],{"class":45,"line":1047},[43,2329,104],{"emptyLinePlaceholder":103},[43,2331,2332],{"class":45,"line":1053},[43,2333,2334],{},"\u002F\u002F 3. Pemicu Petir Kehidupan Pertama agar Game Loop Jaga Menyala Sepanjang Masa\n",[43,2336,2337],{"class":45,"line":1059},[43,2338,2339],{},"gameLoop();\n",[22,2341,2342,2343,2346,2347,2350],{},"Selesai sudah teorinya! Baris pemanggilan ",[40,2344,2345],{},"requestAnimationFrame(gameLoop)"," pada pengujung blok loop kode fungsi lah yang melahirkan daur siklus pengulangan tak berujung batas mutlak (Infinite Loop). Sekali Anda mantap mengerti perihal logikal perulangan fungsi engine Loop ini, Maka dengan ini secara ",[1258,2348,2349],{},"sah"," selamat, Anda sudah resmi dilantik menjadi sang arsitek Game Developer sejati!",[690,2352,692],{},{"title":16,"searchDepth":52,"depth":52,"links":2354},[],{"title_en":1780,"title_id":2065,"description_en":2356,"description_id":2357,"date":699,"readingTime":2358},"Learn the heart of game development: creating a smooth game loop using requestAnimationFrame, clearRect, and updating object positions.","Pelajari jantung dari pembuatan game: membuat game loop yang mulus menggunakan requestAnimationFrame, clearRect, dan update posisi objek.","6 min read","\u002Fblog\u002Fcanvas-game-loop",{"title":1780,"description":16},"blog\u002Fcanvas-game-loop","UVYvyXAIGX2HLeZrNBkxDlAmOKKAwJyCwkHSdWb-pL4",{"id":2364,"title":2365,"body":2366,"description":16,"extension":695,"meta":3033,"navigation":103,"path":3038,"seo":3039,"stem":3040,"__hash__":3041},"blog\u002Fblog\u002Fcanvas-player-input.md","HTML5 Canvas: Handling Player Input (Keyboard & Mouse)",{"type":8,"value":2367,"toc":3031},[2368,3029],[11,2369,2370,2679],{},[14,2371,2372,2376,2379,2383,2386,2396,2401,2435,2439,2494,2498,2501,2583,2587,2594,2669],{"v-slot:en":16},[18,2373,2375],{"id":2374},"html5-canvas-handling-player-input","HTML5 Canvas: Handling Player Input",[22,2377,2378],{},"A game without player input is just an animation. To make it a true game, we need to allow the player to control elements on the screen. Let's learn to capture keyboard and mouse input effectively!",[26,2380,2382],{"id":2381},"_1-the-right-way-to-handle-keyboard-input","1. The Right Way to Handle Keyboard Input",[22,2384,2385],{},"If you simply attach an event listener to a button press and move the player directly inside that listener, the movement will feel choppy and clunky.",[22,2387,2388,2389,2392,2393,2395],{},"The industry-standard way to do this is to keep track of ",[138,2390,2391],{},"which keys are currently being held down",". We do this by storing the state in an object, and then applying movement inside our ",[40,2394,1825],{}," Game Loop.",[2397,2398,2400],"h3",{"id":2399},"step-1-create-a-state-object","Step 1: Create a State Object",[34,2402,2404],{"className":36,"code":2403,"language":38,"meta":16,"style":16},"const keys = {\n  ArrowUp: false,\n  ArrowDown: false,\n  ArrowLeft: false,\n  ArrowRight: false\n};\n",[40,2405,2406,2411,2416,2421,2426,2431],{"__ignoreMap":16},[43,2407,2408],{"class":45,"line":46},[43,2409,2410],{},"const keys = {\n",[43,2412,2413],{"class":45,"line":52},[43,2414,2415],{},"  ArrowUp: false,\n",[43,2417,2418],{"class":45,"line":94},[43,2419,2420],{},"  ArrowDown: false,\n",[43,2422,2423],{"class":45,"line":100},[43,2424,2425],{},"  ArrowLeft: false,\n",[43,2427,2428],{"class":45,"line":107},[43,2429,2430],{},"  ArrowRight: false\n",[43,2432,2433],{"class":45,"line":113},[43,2434,1930],{},[2397,2436,2438],{"id":2437},"step-2-listeners-to-update-state","Step 2: Listeners to Update State",[34,2440,2442],{"className":36,"code":2441,"language":38,"meta":16,"style":16},"window.addEventListener('keydown', (e) => {\n  if (keys.hasOwnProperty(e.code)) {\n    keys[e.code] = true; \u002F\u002F Key is being pressed\n  }\n});\n\nwindow.addEventListener('keyup', (e) => {\n  if (keys.hasOwnProperty(e.code)) {\n    keys[e.code] = false; \u002F\u002F Key was released\n  }\n});\n",[40,2443,2444,2449,2454,2459,2463,2468,2472,2477,2481,2486,2490],{"__ignoreMap":16},[43,2445,2446],{"class":45,"line":46},[43,2447,2448],{},"window.addEventListener('keydown', (e) => {\n",[43,2450,2451],{"class":45,"line":52},[43,2452,2453],{},"  if (keys.hasOwnProperty(e.code)) {\n",[43,2455,2456],{"class":45,"line":94},[43,2457,2458],{},"    keys[e.code] = true; \u002F\u002F Key is being pressed\n",[43,2460,2461],{"class":45,"line":100},[43,2462,929],{},[43,2464,2465],{"class":45,"line":107},[43,2466,2467],{},"});\n",[43,2469,2470],{"class":45,"line":113},[43,2471,104],{"emptyLinePlaceholder":103},[43,2473,2474],{"class":45,"line":195},[43,2475,2476],{},"window.addEventListener('keyup', (e) => {\n",[43,2478,2479],{"class":45,"line":200},[43,2480,2453],{},[43,2482,2483],{"class":45,"line":206},[43,2484,2485],{},"    keys[e.code] = false; \u002F\u002F Key was released\n",[43,2487,2488],{"class":45,"line":212},[43,2489,929],{},[43,2491,2492],{"class":45,"line":218},[43,2493,2467],{},[2397,2495,2497],{"id":2496},"step-3-apply-to-game-loop","Step 3: Apply to Game Loop",[22,2499,2500],{},"Now, inside the same Game Loop we learned about previously!",[34,2502,2504],{"className":36,"code":2503,"language":38,"meta":16,"style":16},"let player = { x: 100, y: 100, size: 30, speed: 5 };\n\nfunction update() {\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n  \u002F\u002F Smooth movement checked every single frame!\n  if (keys.ArrowUp) player.y -= player.speed;\n  if (keys.ArrowDown) player.y += player.speed;\n  if (keys.ArrowLeft) player.x -= player.speed;\n  if (keys.ArrowRight) player.x += player.speed;\n\n  \u002F\u002F Draw Player\n  ctx.fillStyle = 'blue';\n  ctx.fillRect(player.x, player.y, player.size, player.size);\n\n  requestAnimationFrame(update);\n}\n",[40,2505,2506,2511,2515,2519,2523,2527,2532,2537,2542,2547,2552,2556,2561,2566,2571,2575,2579],{"__ignoreMap":16},[43,2507,2508],{"class":45,"line":46},[43,2509,2510],{},"let player = { x: 100, y: 100, size: 30, speed: 5 };\n",[43,2512,2513],{"class":45,"line":52},[43,2514,104],{"emptyLinePlaceholder":103},[43,2516,2517],{"class":45,"line":94},[43,2518,1944],{},[43,2520,2521],{"class":45,"line":100},[43,2522,1954],{},[43,2524,2525],{"class":45,"line":107},[43,2526,104],{"emptyLinePlaceholder":103},[43,2528,2529],{"class":45,"line":113},[43,2530,2531],{},"  \u002F\u002F Smooth movement checked every single frame!\n",[43,2533,2534],{"class":45,"line":195},[43,2535,2536],{},"  if (keys.ArrowUp) player.y -= player.speed;\n",[43,2538,2539],{"class":45,"line":200},[43,2540,2541],{},"  if (keys.ArrowDown) player.y += player.speed;\n",[43,2543,2544],{"class":45,"line":206},[43,2545,2546],{},"  if (keys.ArrowLeft) player.x -= player.speed;\n",[43,2548,2549],{"class":45,"line":212},[43,2550,2551],{},"  if (keys.ArrowRight) player.x += player.speed;\n",[43,2553,2554],{"class":45,"line":218},[43,2555,104],{"emptyLinePlaceholder":103},[43,2557,2558],{"class":45,"line":224},[43,2559,2560],{},"  \u002F\u002F Draw Player\n",[43,2562,2563],{"class":45,"line":618},[43,2564,2565],{},"  ctx.fillStyle = 'blue';\n",[43,2567,2568],{"class":45,"line":914},[43,2569,2570],{},"  ctx.fillRect(player.x, player.y, player.size, player.size);\n",[43,2572,2573],{"class":45,"line":920},[43,2574,104],{"emptyLinePlaceholder":103},[43,2576,2577],{"class":45,"line":926},[43,2578,2034],{},[43,2580,2581],{"class":45,"line":932},[43,2582,809],{},[26,2584,2586],{"id":2585},"_2-tracking-the-mouse","2. Tracking the Mouse",[22,2588,2589,2590,2593],{},"Mouse tracking is incredibly useful for shooting games, drag-and-drop puzzles, or custom cursors.\nBecause the standard ",[40,2591,2592],{},"e.clientX"," gives the position based on the whole browser window, we must subtract the canvas's padding\u002Fmargin bounds to get the exact X and Y on our game grid.",[34,2595,2597],{"className":36,"code":2596,"language":38,"meta":16,"style":16},"let mouse = { x: 0, y: 0 };\n\ncanvas.addEventListener('mousemove', (e) => {\n  \u002F\u002F Get the size and position of the canvas on the webpage\n  const rect = canvas.getBoundingClientRect(); \n  \n  \u002F\u002F Calculate exact coordinates relative to canvas\n  mouse.x = e.clientX - rect.left;\n  mouse.y = e.clientY - rect.top;\n});\n\n\u002F\u002F Want a click event?\ncanvas.addEventListener('click', (e) => {\n  console.log(`Pew pew! Gun fired at X: ${mouse.x}, Y: ${mouse.y}`);\n});\n",[40,2598,2599,2604,2608,2613,2618,2623,2627,2632,2637,2642,2646,2650,2655,2660,2665],{"__ignoreMap":16},[43,2600,2601],{"class":45,"line":46},[43,2602,2603],{},"let mouse = { x: 0, y: 0 };\n",[43,2605,2606],{"class":45,"line":52},[43,2607,104],{"emptyLinePlaceholder":103},[43,2609,2610],{"class":45,"line":94},[43,2611,2612],{},"canvas.addEventListener('mousemove', (e) => {\n",[43,2614,2615],{"class":45,"line":100},[43,2616,2617],{},"  \u002F\u002F Get the size and position of the canvas on the webpage\n",[43,2619,2620],{"class":45,"line":107},[43,2621,2622],{},"  const rect = canvas.getBoundingClientRect(); \n",[43,2624,2625],{"class":45,"line":113},[43,2626,1159],{},[43,2628,2629],{"class":45,"line":195},[43,2630,2631],{},"  \u002F\u002F Calculate exact coordinates relative to canvas\n",[43,2633,2634],{"class":45,"line":200},[43,2635,2636],{},"  mouse.x = e.clientX - rect.left;\n",[43,2638,2639],{"class":45,"line":206},[43,2640,2641],{},"  mouse.y = e.clientY - rect.top;\n",[43,2643,2644],{"class":45,"line":212},[43,2645,2467],{},[43,2647,2648],{"class":45,"line":218},[43,2649,104],{"emptyLinePlaceholder":103},[43,2651,2652],{"class":45,"line":224},[43,2653,2654],{},"\u002F\u002F Want a click event?\n",[43,2656,2657],{"class":45,"line":618},[43,2658,2659],{},"canvas.addEventListener('click', (e) => {\n",[43,2661,2662],{"class":45,"line":914},[43,2663,2664],{},"  console.log(`Pew pew! Gun fired at X: ${mouse.x}, Y: ${mouse.y}`);\n",[43,2666,2667],{"class":45,"line":920},[43,2668,2467],{},[22,2670,2671,2672,727,2675,2678],{},"Now you can build a game loop that constantly draws an crosshair image exactly at ",[40,2673,2674],{},"mouse.x",[40,2676,2677],{},"mouse.y","!",[14,2680,2681,2685,2695,2699,2710,2723,2727,2761,2765,2827,2831,2834,2921,2925,2932,2938,3016],{"v-slot:id":16},[18,2682,2684],{"id":2683},"html5-canvas-menangani-input-pemain","HTML5 Canvas: Menangani Input Pemain",[22,2686,2687,2688,2691,2692,2678],{},"Sebuah game tanpa input kendali pemain tidak lebih dari sekadar pemutaran video animasi. Agar ia bisa secara resmi divalidasi menjadi game yang interaktif, kita memerlukan izin perintah supaya para pemain dapat ikut menavigasi elemen layar. Mari pelajari cara tak terkalahkan dalam merekam ketukan interaksi tombol ",[1258,2689,2690],{},"Keyboard"," maupun sasaran ",[1258,2693,2694],{},"Mouse",[26,2696,2698],{"id":2697},"_1-cara-profesional-menangani-tombol-keyboard","1. Cara Profesional Menangani Tombol Keyboard",[22,2700,2701,2702,2705,2706,2709],{},"Satu kesalahan besar paling fatal mayoritas pemula adalah langsung \"menempelkan\" rumus matematika penambahan jarak gerakan berjalan (contoh iseng misal: ",[40,2703,2704],{},"player.y -= 5",") dicangkok mentah bulat layaknya teks di dalam fungsi interupsi ",[40,2707,2708],{},"keydown",". Efek buruknya? Gerakan tokoh protagonis buatan Anda akan nge-lag tersentak kaku bagai patah-pahat menunda, didera sistem sistem Operasi OS pengetik keyboard komputer.",[22,2711,2712,2713,2716,2717,2719,2720,2722],{},"Teknik kerahasiaan pabrik dari Industri \u002F Produsen Game Standart Dunia adalah dengan merubah pendekatan itu; mendata riwayat catatan jejak ",[138,2714,2715],{},"\"Tuts apa sajakah yang berstatus Sedang-Digenggam oleh Tekanan Jari Pemain SAAT INI?\"",". Data set status tersebut dihimpun sebagai Object di RAM, dan proses pemindahan langkah diserahkan langsung ke ",[1258,2718,1609],{}," beruntun setiap detiknya (",[40,2721,1825],{},") biar pergerakan terlihat lumer semulus sutra.",[2397,2724,2726],{"id":2725},"langkah-1-siapkan-keranjang-object-status-tombol","Langkah 1: Siapkan Keranjang Object Status Tombol",[34,2728,2730],{"className":36,"code":2729,"language":38,"meta":16,"style":16},"const tombolArah = {\n  ArrowUp: false,    \u002F\u002F Tombol Atas \n  ArrowDown: false,  \u002F\u002F Tombol Bawah\n  ArrowLeft: false,  \u002F\u002F Tombol Kiri\n  ArrowRight: false  \u002F\u002F Tombol Kanan\n};\n",[40,2731,2732,2737,2742,2747,2752,2757],{"__ignoreMap":16},[43,2733,2734],{"class":45,"line":46},[43,2735,2736],{},"const tombolArah = {\n",[43,2738,2739],{"class":45,"line":52},[43,2740,2741],{},"  ArrowUp: false,    \u002F\u002F Tombol Atas \n",[43,2743,2744],{"class":45,"line":94},[43,2745,2746],{},"  ArrowDown: false,  \u002F\u002F Tombol Bawah\n",[43,2748,2749],{"class":45,"line":100},[43,2750,2751],{},"  ArrowLeft: false,  \u002F\u002F Tombol Kiri\n",[43,2753,2754],{"class":45,"line":107},[43,2755,2756],{},"  ArrowRight: false  \u002F\u002F Tombol Kanan\n",[43,2758,2759],{"class":45,"line":113},[43,2760,1930],{},[2397,2762,2764],{"id":2763},"langkah-2-event-listener-sebagai-radar-pendengar","Langkah 2: Event Listener Sebagai Radar Pendengar",[34,2766,2768],{"className":36,"code":2767,"language":38,"meta":16,"style":16},"\u002F\u002F Kala tuas Keyboard disentuh turun di genggam jari\nwindow.addEventListener('keydown', (e) => {\n  if (tombolArah.hasOwnProperty(e.code)) {\n    tombolArah[e.code] = true; \u002F\u002F Setir gas mesin berstatus MENYALA (true)\n  }\n});\n\n\u002F\u002F Kala beban genggam jari cabut melepaskan kaitan dari tuasnya\nwindow.addEventListener('keyup', (e) => {\n  if (tombolArah.hasOwnProperty(e.code)) {\n    tombolArah[e.code] = false; \u002F\u002F Rem ditarik otomatis\u002FMatikan gas pacu lari (false)\n  }\n});\n",[40,2769,2770,2775,2779,2784,2789,2793,2797,2801,2806,2810,2814,2819,2823],{"__ignoreMap":16},[43,2771,2772],{"class":45,"line":46},[43,2773,2774],{},"\u002F\u002F Kala tuas Keyboard disentuh turun di genggam jari\n",[43,2776,2777],{"class":45,"line":52},[43,2778,2448],{},[43,2780,2781],{"class":45,"line":94},[43,2782,2783],{},"  if (tombolArah.hasOwnProperty(e.code)) {\n",[43,2785,2786],{"class":45,"line":100},[43,2787,2788],{},"    tombolArah[e.code] = true; \u002F\u002F Setir gas mesin berstatus MENYALA (true)\n",[43,2790,2791],{"class":45,"line":107},[43,2792,929],{},[43,2794,2795],{"class":45,"line":113},[43,2796,2467],{},[43,2798,2799],{"class":45,"line":195},[43,2800,104],{"emptyLinePlaceholder":103},[43,2802,2803],{"class":45,"line":200},[43,2804,2805],{},"\u002F\u002F Kala beban genggam jari cabut melepaskan kaitan dari tuasnya\n",[43,2807,2808],{"class":45,"line":206},[43,2809,2476],{},[43,2811,2812],{"class":45,"line":212},[43,2813,2783],{},[43,2815,2816],{"class":45,"line":218},[43,2817,2818],{},"    tombolArah[e.code] = false; \u002F\u002F Rem ditarik otomatis\u002FMatikan gas pacu lari (false)\n",[43,2820,2821],{"class":45,"line":224},[43,2822,929],{},[43,2824,2825],{"class":45,"line":618},[43,2826,2467],{},[2397,2828,2830],{"id":2829},"langkah-3-eksekusi-suntik-masal-ke-jantung-game-loop","Langkah 3: Eksekusi Suntik Masal ke Jantung Game Loop!",[22,2832,2833],{},"Mari gabungkan rumus radar tersebut di mesin \"Game Loop Pembaruan Frame\" (materi seri kanvas terakhir yang kita pelajari tempo lampau)!",[34,2835,2837],{"className":36,"code":2836,"language":38,"meta":16,"style":16},"let player = { x: 100, y: 100, size: 30, speed: 5 };\n\nfunction pembaruanGame() {\n  ctx.clearRect(0, 0, canvas.width, canvas.height); \u002F\u002F Hukum wajib No.1: Sapu layarnya!\n\n  \u002F\u002F UPDATE LOGIKA GERAKAN SUPREME 60 FPS YANG SUPER LICIN :\n  \u002F\u002F Dicek kondisi radarnya, dan paksakan digeser terus menerus setiap satu hela Frame berjalan!\n  if (tombolArah.ArrowUp) player.y -= player.speed;\n  if (tombolArah.ArrowDown) player.y += player.speed;\n  if (tombolArah.ArrowLeft) player.x -= player.speed;\n  if (tombolArah.ArrowRight) player.x += player.speed;\n\n  \u002F\u002F Tahap Cetak Render Kotak Karakter Pemain di pos terbaru.\n  ctx.fillStyle = 'blue';\n  ctx.fillRect(player.x, player.y, player.size, player.size);\n\n  requestAnimationFrame(pembaruanGame);\n}\n",[40,2838,2839,2843,2847,2852,2857,2861,2866,2871,2876,2881,2886,2891,2895,2900,2904,2908,2912,2917],{"__ignoreMap":16},[43,2840,2841],{"class":45,"line":46},[43,2842,2510],{},[43,2844,2845],{"class":45,"line":52},[43,2846,104],{"emptyLinePlaceholder":103},[43,2848,2849],{"class":45,"line":94},[43,2850,2851],{},"function pembaruanGame() {\n",[43,2853,2854],{"class":45,"line":100},[43,2855,2856],{},"  ctx.clearRect(0, 0, canvas.width, canvas.height); \u002F\u002F Hukum wajib No.1: Sapu layarnya!\n",[43,2858,2859],{"class":45,"line":107},[43,2860,104],{"emptyLinePlaceholder":103},[43,2862,2863],{"class":45,"line":113},[43,2864,2865],{},"  \u002F\u002F UPDATE LOGIKA GERAKAN SUPREME 60 FPS YANG SUPER LICIN :\n",[43,2867,2868],{"class":45,"line":195},[43,2869,2870],{},"  \u002F\u002F Dicek kondisi radarnya, dan paksakan digeser terus menerus setiap satu hela Frame berjalan!\n",[43,2872,2873],{"class":45,"line":200},[43,2874,2875],{},"  if (tombolArah.ArrowUp) player.y -= player.speed;\n",[43,2877,2878],{"class":45,"line":206},[43,2879,2880],{},"  if (tombolArah.ArrowDown) player.y += player.speed;\n",[43,2882,2883],{"class":45,"line":212},[43,2884,2885],{},"  if (tombolArah.ArrowLeft) player.x -= player.speed;\n",[43,2887,2888],{"class":45,"line":218},[43,2889,2890],{},"  if (tombolArah.ArrowRight) player.x += player.speed;\n",[43,2892,2893],{"class":45,"line":224},[43,2894,104],{"emptyLinePlaceholder":103},[43,2896,2897],{"class":45,"line":618},[43,2898,2899],{},"  \u002F\u002F Tahap Cetak Render Kotak Karakter Pemain di pos terbaru.\n",[43,2901,2902],{"class":45,"line":914},[43,2903,2565],{},[43,2905,2906],{"class":45,"line":920},[43,2907,2570],{},[43,2909,2910],{"class":45,"line":926},[43,2911,104],{"emptyLinePlaceholder":103},[43,2913,2914],{"class":45,"line":932},[43,2915,2916],{},"  requestAnimationFrame(pembaruanGame);\n",[43,2918,2919],{"class":45,"line":937},[43,2920,809],{},[26,2922,2924],{"id":2923},"_2-kalkulasi-koordinat-bidikan-tracking-target-mouse","2. Kalkulasi Koordinat Bidikan (Tracking Target Mouse)",[22,2926,2927,2928,2931],{},"Pengejaran koordinat kursor sentuhan ujung Tikus (Mouse) adalah kunci nyawa bagi genre peluru senapan (",[1258,2929,2930],{},"Shooting crosshairs","), bongkar pasang Puzzle layaknya Drag And Drop, maupun elemen strategi menyorot unit (RTS).",[22,2933,2934,2935,2937],{},"Mengingat rumus variabel mentah bawaan browser JavaScript itu ",[40,2936,2592],{}," yang hanya melaporkan perhitungan asal mula sentimeter dari pojok tepian kaca browser, kita DITUNTUT WAJIB mutlak memotong tepian garis bingkai kanvas (Border\u002FMargin) untuk mengail koordinat murni dari arena dalam Canvas.",[34,2939,2941],{"className":36,"code":2940,"language":38,"meta":16,"style":16},"let kordinatPusatMouse = { x: 0, y: 0 };\n\n\u002F\u002F Taruh radar di seluruh body Kanvas perihal pergeseran sekecil apapun\ncanvas.addEventListener('mousemove', (e) => {\n  \u002F\u002F Bantuan Penggaris Gaib JavaScript menilik besaran bingkai kanvas\n  const ukuranBingkaiGame = canvas.getBoundingClientRect(); \n  \n  \u002F\u002F Penggabungan titik selisih, tembus menuju arena titik Asli!\n  kordinatPusatMouse.x = e.clientX - ukuranBingkaiGame.left;\n  kordinatPusatMouse.y = e.clientY - ukuranBingkaiGame.top;\n});\n\n\u002F\u002F Ingin mekanisme melepaskan peluru ketika Jari nge-KLIK kiri kursor senapannya?\ncanvas.addEventListener('click', (e) => {\n  console.log(`DARR! Pistol meledak mencuat memuntahkan peluru pelacak tepat ke kordinat Sasaran Musuh X: ${kordinatPusatMouse.x}, Y: ${kordinatPusatMouse.y}`);\n});\n",[40,2942,2943,2948,2952,2957,2961,2966,2971,2975,2980,2985,2990,2994,2998,3003,3007,3012],{"__ignoreMap":16},[43,2944,2945],{"class":45,"line":46},[43,2946,2947],{},"let kordinatPusatMouse = { x: 0, y: 0 };\n",[43,2949,2950],{"class":45,"line":52},[43,2951,104],{"emptyLinePlaceholder":103},[43,2953,2954],{"class":45,"line":94},[43,2955,2956],{},"\u002F\u002F Taruh radar di seluruh body Kanvas perihal pergeseran sekecil apapun\n",[43,2958,2959],{"class":45,"line":100},[43,2960,2612],{},[43,2962,2963],{"class":45,"line":107},[43,2964,2965],{},"  \u002F\u002F Bantuan Penggaris Gaib JavaScript menilik besaran bingkai kanvas\n",[43,2967,2968],{"class":45,"line":113},[43,2969,2970],{},"  const ukuranBingkaiGame = canvas.getBoundingClientRect(); \n",[43,2972,2973],{"class":45,"line":195},[43,2974,1159],{},[43,2976,2977],{"class":45,"line":200},[43,2978,2979],{},"  \u002F\u002F Penggabungan titik selisih, tembus menuju arena titik Asli!\n",[43,2981,2982],{"class":45,"line":206},[43,2983,2984],{},"  kordinatPusatMouse.x = e.clientX - ukuranBingkaiGame.left;\n",[43,2986,2987],{"class":45,"line":212},[43,2988,2989],{},"  kordinatPusatMouse.y = e.clientY - ukuranBingkaiGame.top;\n",[43,2991,2992],{"class":45,"line":218},[43,2993,2467],{},[43,2995,2996],{"class":45,"line":224},[43,2997,104],{"emptyLinePlaceholder":103},[43,2999,3000],{"class":45,"line":618},[43,3001,3002],{},"\u002F\u002F Ingin mekanisme melepaskan peluru ketika Jari nge-KLIK kiri kursor senapannya?\n",[43,3004,3005],{"class":45,"line":914},[43,3006,2659],{},[43,3008,3009],{"class":45,"line":920},[43,3010,3011],{},"  console.log(`DARR! Pistol meledak mencuat memuntahkan peluru pelacak tepat ke kordinat Sasaran Musuh X: ${kordinatPusatMouse.x}, Y: ${kordinatPusatMouse.y}`);\n",[43,3013,3014],{"class":45,"line":926},[43,3015,2467],{},[22,3017,3018,3019,3022,3023,3028],{},"Dan dengan rumus senjata tersebut di tangan, impian merakit gambar logo sasaran Tembak atau siluet objek buatan memanjang (",[1258,3020,3021],{},"Custom Cursor Image","), sekarang otomatis terpacaki mengekor mengikuti di setiap sumbu lari ",[1258,3024,3025],{},[40,3026,3027],{},"kordinatPusatMouse.x"," !",[690,3030,692],{},{"title":16,"searchDepth":52,"depth":52,"links":3032},[],{"title_en":2365,"title_id":3034,"description_en":3035,"description_id":3036,"date":699,"readingTime":3037},"HTML5 Canvas: Menangani Input Pemain (Keyboard & Mouse)","Learn how to make your game interactive by capturing keyboard strokes for smooth movement and tracking mouse coordinates.","Pelajari cara membuat game Anda interaktif dengan mendengarkan input tombol keyboard dan melacak koordinat mouse pemain.","7 min read","\u002Fblog\u002Fcanvas-player-input",{"title":2365,"description":16},"blog\u002Fcanvas-player-input","QGiFmUpdifNaVcBkqvBvfYZ5fFqXvw3bH-jLssDyJrs",{"id":3043,"title":3044,"body":3045,"description":16,"extension":695,"meta":4285,"navigation":103,"path":4289,"seo":4290,"stem":4291,"__hash__":4292},"blog\u002Fblog\u002Fcanvas-sprite-physics.md","HTML5 Canvas: Sprite Animation and Game Physics",{"type":8,"value":3046,"toc":4283},[3047,4281],[11,3048,3049,3659],{},[14,3050,3051,3054,3064,3068,3080,3170,3174,3202,3303,3307,3326,3471,3475,3486,3496,3656],{"v-slot:en":16},[18,3052,3044],{"id":3053},"html5-canvas-sprite-animation-and-game-physics",[22,3055,3056,3057,3060,3061,552],{},"Now that we understand the Game Loop, inputs, and canvas transformations, it's time to put it all together. Let's look at how to build a complete ",[138,3058,3059],{},"Player Class"," using real production code as an example. We'll implement gravity for jumping and cycle through an array of images for ",[138,3062,3063],{},"Sprite Animation",[26,3065,3067],{"id":3066},"_1-preparing-the-image-assets","1. Preparing the Image Assets",[22,3069,3070,3071,305,3074,305,3077,157],{},"Instead of drawing a single square canvas shape, real games load sequential image items (frames). We load them carefully into memory and group them into Javascript objects based on the player's \"State\" (like ",[40,3072,3073],{},"Idle",[40,3075,3076],{},"Jump",[40,3078,3079],{},"MoveForward",[34,3081,3083],{"className":36,"code":3082,"language":38,"meta":16,"style":16},"\u002F\u002F Helper to generate the real DOM Image object\nfunction loadImg(src) {\n  const img = new Image();\n  img.src = src;\n  return img;\n}\n\n\u002F\u002F Group downloaded image arrays by player animation state\nfunction loadImagePlayer(country) {\n  const animations = { Idle: [], Jump: [], MoveForward: [] \u002F* ... *\u002F };\n  \n  \u002F\u002F Example: Loading 18 consecutive frames of Idle animation\n  for (let i = 0; i \u003C= 17; i++) {\n    \u002F\u002F String(i).padStart(2, \"0\") converts number 1 to exactly \"01\"\n    animations.Idle.push(loadImg(`.\u002FSprites\u002FCharacters\u002F${country}\u002FIdle\u002FIdle_0${String(i).padStart(2, \"0\")}.png`));\n  }\n  return animations;\n}\n",[40,3084,3085,3090,3095,3100,3105,3110,3114,3118,3123,3128,3133,3137,3142,3147,3152,3157,3161,3166],{"__ignoreMap":16},[43,3086,3087],{"class":45,"line":46},[43,3088,3089],{},"\u002F\u002F Helper to generate the real DOM Image object\n",[43,3091,3092],{"class":45,"line":52},[43,3093,3094],{},"function loadImg(src) {\n",[43,3096,3097],{"class":45,"line":94},[43,3098,3099],{},"  const img = new Image();\n",[43,3101,3102],{"class":45,"line":100},[43,3103,3104],{},"  img.src = src;\n",[43,3106,3107],{"class":45,"line":107},[43,3108,3109],{},"  return img;\n",[43,3111,3112],{"class":45,"line":113},[43,3113,809],{},[43,3115,3116],{"class":45,"line":195},[43,3117,104],{"emptyLinePlaceholder":103},[43,3119,3120],{"class":45,"line":200},[43,3121,3122],{},"\u002F\u002F Group downloaded image arrays by player animation state\n",[43,3124,3125],{"class":45,"line":206},[43,3126,3127],{},"function loadImagePlayer(country) {\n",[43,3129,3130],{"class":45,"line":212},[43,3131,3132],{},"  const animations = { Idle: [], Jump: [], MoveForward: [] \u002F* ... *\u002F };\n",[43,3134,3135],{"class":45,"line":218},[43,3136,1159],{},[43,3138,3139],{"class":45,"line":224},[43,3140,3141],{},"  \u002F\u002F Example: Loading 18 consecutive frames of Idle animation\n",[43,3143,3144],{"class":45,"line":618},[43,3145,3146],{},"  for (let i = 0; i \u003C= 17; i++) {\n",[43,3148,3149],{"class":45,"line":914},[43,3150,3151],{},"    \u002F\u002F String(i).padStart(2, \"0\") converts number 1 to exactly \"01\"\n",[43,3153,3154],{"class":45,"line":920},[43,3155,3156],{},"    animations.Idle.push(loadImg(`.\u002FSprites\u002FCharacters\u002F${country}\u002FIdle\u002FIdle_0${String(i).padStart(2, \"0\")}.png`));\n",[43,3158,3159],{"class":45,"line":926},[43,3160,929],{},[43,3162,3163],{"class":45,"line":932},[43,3164,3165],{},"  return animations;\n",[43,3167,3168],{"class":45,"line":937},[43,3169,809],{},[26,3171,3173],{"id":3172},"_2-the-player-class-structure","2. The Player Class Structure",[22,3175,3176,3177,3180,3181,3184,3185,3188,3189,305,3192,3195,3196,305,3199,157],{},"Using a JavaScript ",[40,3178,3179],{},"class"," is the best standard to handle complex game entities. Our player needs properties mapping its physical location (",[40,3182,3183],{},"x, y","), movement momentum variables (",[40,3186,3187],{},"dx, dy","), animation status (",[40,3190,3191],{},"frame",[40,3193,3194],{},"state","), and world physics (",[40,3197,3198],{},"speed",[40,3200,3201],{},"gravity",[34,3203,3205],{"className":36,"code":3204,"language":38,"meta":16,"style":16},"class Player {\n  constructor(country, position, ctx) {\n    this.ctx = ctx;\n    this.position = position; \u002F\u002F Is player on \"left\" or \"right\" side?\n    this.image = loadImagePlayer(country); \u002F\u002F Preload all sprite images\n    \n    \u002F\u002F Position and Physical Body Rules\n    this.size = 150;\n    this.x = position === \"left\" ? 300 : 600;\n    this.y = 0; \n    this.dy = 0; \u002F\u002F Delta Y: Vertical momentum velocity\n    this.speed = 5;\n    this.gravity = 0.2; \u002F\u002F Constant downward pulling force\n\n    \u002F\u002F Animation & State Control tracking\n    this.state = \"Idle\"; \u002F\u002F Default state\n    this.frame = 0; \u002F\u002F Current animation slide tracker\n    this.lastAnimate = Date.now(); \u002F\u002F Built-in timer to control frame pacing\n  }\n}\n",[40,3206,3207,3212,3217,3222,3227,3232,3236,3241,3246,3251,3256,3261,3266,3271,3275,3280,3285,3290,3295,3299],{"__ignoreMap":16},[43,3208,3209],{"class":45,"line":46},[43,3210,3211],{},"class Player {\n",[43,3213,3214],{"class":45,"line":52},[43,3215,3216],{},"  constructor(country, position, ctx) {\n",[43,3218,3219],{"class":45,"line":94},[43,3220,3221],{},"    this.ctx = ctx;\n",[43,3223,3224],{"class":45,"line":100},[43,3225,3226],{},"    this.position = position; \u002F\u002F Is player on \"left\" or \"right\" side?\n",[43,3228,3229],{"class":45,"line":107},[43,3230,3231],{},"    this.image = loadImagePlayer(country); \u002F\u002F Preload all sprite images\n",[43,3233,3234],{"class":45,"line":113},[43,3235,882],{},[43,3237,3238],{"class":45,"line":195},[43,3239,3240],{},"    \u002F\u002F Position and Physical Body Rules\n",[43,3242,3243],{"class":45,"line":200},[43,3244,3245],{},"    this.size = 150;\n",[43,3247,3248],{"class":45,"line":206},[43,3249,3250],{},"    this.x = position === \"left\" ? 300 : 600;\n",[43,3252,3253],{"class":45,"line":212},[43,3254,3255],{},"    this.y = 0; \n",[43,3257,3258],{"class":45,"line":218},[43,3259,3260],{},"    this.dy = 0; \u002F\u002F Delta Y: Vertical momentum velocity\n",[43,3262,3263],{"class":45,"line":224},[43,3264,3265],{},"    this.speed = 5;\n",[43,3267,3268],{"class":45,"line":618},[43,3269,3270],{},"    this.gravity = 0.2; \u002F\u002F Constant downward pulling force\n",[43,3272,3273],{"class":45,"line":914},[43,3274,104],{"emptyLinePlaceholder":103},[43,3276,3277],{"class":45,"line":920},[43,3278,3279],{},"    \u002F\u002F Animation & State Control tracking\n",[43,3281,3282],{"class":45,"line":926},[43,3283,3284],{},"    this.state = \"Idle\"; \u002F\u002F Default state\n",[43,3286,3287],{"class":45,"line":932},[43,3288,3289],{},"    this.frame = 0; \u002F\u002F Current animation slide tracker\n",[43,3291,3292],{"class":45,"line":937},[43,3293,3294],{},"    this.lastAnimate = Date.now(); \u002F\u002F Built-in timer to control frame pacing\n",[43,3296,3297],{"class":45,"line":943},[43,3298,929],{},[43,3300,3301],{"class":45,"line":949},[43,3302,809],{},[26,3304,3306],{"id":3305},"_3-game-physics-gravity-and-jumping","3. Game Physics: Gravity and Jumping",[22,3308,3309,3310,3313,3314,3317,3318,3321,3322,3325],{},"In our ",[40,3311,3312],{},"update()"," method, we handle the classic Jump mechanic. Gravity constantly adds weight pushing the player down (",[40,3315,3316],{},"this.dy += this.gravity","), unless they touch the Ground\u002FFloor level. Once on the ground, the downward falling stops (",[40,3319,3320],{},"dy = 0","). But if they hit the jump button, we apply an massive burst of negative upward velocity (",[40,3323,3324],{},"this.dy = -8",") to launch them!",[34,3327,3329],{"className":36,"code":3328,"language":38,"meta":16,"style":16},"  update() {\n    this.prevState = this.state; \u002F\u002F Track past state to detect animation switching\n    const groundLevel = canvas.height - this.size - 90;\n\n    \u002F\u002F Is the player Y level touching or below Ground floor?\n    const onGround = this.y >= groundLevel;\n\n    if (onGround) {\n      this.y = groundLevel; \u002F\u002F Snap firmly to floor (Anti sink)\n      this.dy = 0; \u002F\u002F Stop gravity fall speed\n\n      \u002F\u002F JUMP trigger from keyboard tracking Map\n      if (this.keys[\"arrowup\"]) {\n        this.dy = -8; \u002F\u002F Instant upward pushing force applied!\n      }\n    } else {\n      this.dy += this.gravity; \u002F\u002F Gravity pulling downward continuously mid-air\n    }\n\n    this.y += this.dy; \u002F\u002F Apply vertical momentum sum to the actual world Y pos!\n\n    \u002F\u002F STATE MACHINE : Intelligently determine what happens based on conditions\n    if (this.y \u003C groundLevel) this.state = \"Jump\";\n    else if (this.keys[\"space\"]) this.state = \"Kick\";\n    else if (this.keys[\"arrowright\"]) this.state = \"MoveForward\";\n    else this.state = \"Idle\";\n\n    \u002F\u002F Auto-reset frame to 0 if animation state changed (e.g., Idle -> Jump)\n    if (this.prevState !== this.state) this.frame = 0;\n  }\n",[40,3330,3331,3336,3341,3346,3350,3355,3360,3364,3369,3374,3379,3383,3388,3393,3398,3402,3406,3411,3415,3419,3424,3428,3433,3438,3443,3448,3453,3457,3462,3467],{"__ignoreMap":16},[43,3332,3333],{"class":45,"line":46},[43,3334,3335],{},"  update() {\n",[43,3337,3338],{"class":45,"line":52},[43,3339,3340],{},"    this.prevState = this.state; \u002F\u002F Track past state to detect animation switching\n",[43,3342,3343],{"class":45,"line":94},[43,3344,3345],{},"    const groundLevel = canvas.height - this.size - 90;\n",[43,3347,3348],{"class":45,"line":100},[43,3349,104],{"emptyLinePlaceholder":103},[43,3351,3352],{"class":45,"line":107},[43,3353,3354],{},"    \u002F\u002F Is the player Y level touching or below Ground floor?\n",[43,3356,3357],{"class":45,"line":113},[43,3358,3359],{},"    const onGround = this.y >= groundLevel;\n",[43,3361,3362],{"class":45,"line":195},[43,3363,104],{"emptyLinePlaceholder":103},[43,3365,3366],{"class":45,"line":200},[43,3367,3368],{},"    if (onGround) {\n",[43,3370,3371],{"class":45,"line":206},[43,3372,3373],{},"      this.y = groundLevel; \u002F\u002F Snap firmly to floor (Anti sink)\n",[43,3375,3376],{"class":45,"line":212},[43,3377,3378],{},"      this.dy = 0; \u002F\u002F Stop gravity fall speed\n",[43,3380,3381],{"class":45,"line":218},[43,3382,104],{"emptyLinePlaceholder":103},[43,3384,3385],{"class":45,"line":224},[43,3386,3387],{},"      \u002F\u002F JUMP trigger from keyboard tracking Map\n",[43,3389,3390],{"class":45,"line":618},[43,3391,3392],{},"      if (this.keys[\"arrowup\"]) {\n",[43,3394,3395],{"class":45,"line":914},[43,3396,3397],{},"        this.dy = -8; \u002F\u002F Instant upward pushing force applied!\n",[43,3399,3400],{"class":45,"line":920},[43,3401,1074],{},[43,3403,3404],{"class":45,"line":926},[43,3405,1212],{},[43,3407,3408],{"class":45,"line":932},[43,3409,3410],{},"      this.dy += this.gravity; \u002F\u002F Gravity pulling downward continuously mid-air\n",[43,3412,3413],{"class":45,"line":937},[43,3414,1080],{},[43,3416,3417],{"class":45,"line":943},[43,3418,104],{"emptyLinePlaceholder":103},[43,3420,3421],{"class":45,"line":949},[43,3422,3423],{},"    this.y += this.dy; \u002F\u002F Apply vertical momentum sum to the actual world Y pos!\n",[43,3425,3426],{"class":45,"line":955},[43,3427,104],{"emptyLinePlaceholder":103},[43,3429,3430],{"class":45,"line":960},[43,3431,3432],{},"    \u002F\u002F STATE MACHINE : Intelligently determine what happens based on conditions\n",[43,3434,3435],{"class":45,"line":966},[43,3436,3437],{},"    if (this.y \u003C groundLevel) this.state = \"Jump\";\n",[43,3439,3440],{"class":45,"line":972},[43,3441,3442],{},"    else if (this.keys[\"space\"]) this.state = \"Kick\";\n",[43,3444,3445],{"class":45,"line":977},[43,3446,3447],{},"    else if (this.keys[\"arrowright\"]) this.state = \"MoveForward\";\n",[43,3449,3450],{"class":45,"line":983},[43,3451,3452],{},"    else this.state = \"Idle\";\n",[43,3454,3455],{"class":45,"line":989},[43,3456,104],{"emptyLinePlaceholder":103},[43,3458,3459],{"class":45,"line":994},[43,3460,3461],{},"    \u002F\u002F Auto-reset frame to 0 if animation state changed (e.g., Idle -> Jump)\n",[43,3463,3464],{"class":45,"line":1000},[43,3465,3466],{},"    if (this.prevState !== this.state) this.frame = 0;\n",[43,3468,3469],{"class":45,"line":1006},[43,3470,929],{},[26,3472,3474],{"id":3473},"_4-animating-the-sprite-horizontal-flipping-tricks","4. Animating the Sprite & Horizontal Flipping Tricks",[22,3476,3477,3478,3481,3482,3485],{},"Finally, the ",[40,3479,3480],{},"animate()"," function controls which image slide from our array is painted. We use a built-in timer limiter logic (",[40,3483,3484],{},"Date.now()",") to slow down the animation pacing. Otherwise, the frames would change 60 times a second and skip too fast!",[22,3487,3488,3489,3492,3493,2678],{},"And lastly, Remember our Transformation (Origin Flip\u002FScale) lesson? If the player is on the \"Right\" side of the football field team, we want them facing left! So we shift our origin point using ",[40,3490,3491],{},"ctx.translate"," and mirror their rendering orientation with ",[40,3494,3495],{},"ctx.scale(-1, 1)",[34,3497,3499],{"className":36,"code":3498,"language":38,"meta":16,"style":16},"  animate() {\n    const activeImageArray = this.image[this.state];\n    const now = Date.now();\n    \n    \u002F\u002F Only increment frame every 50ms (Animation Pacing)\n    if (now - this.lastAnimate > 50) {\n      this.frame++;\n      \u002F\u002F Loop back to start if we exceed the frames length\n      if (this.frame >= activeImageArray.length) this.frame = 0; \n      \n      this.lastAnimate = now;\n    }\n\n    \u002F\u002F Flipping trick specifically for Player 2 (Right side)\n    if (this.position === \"right\") {\n      this.ctx.save();\n      \u002F\u002F Move context pivot exactly at character width before scale mirroring!\n      this.ctx.translate(this.x + this.size, this.y);\n      this.ctx.scale(-1, 1); \u002F\u002F Flip Horizontally!\n\n      this.ctx.drawImage(\n        this.image[this.state][this.frame], \u002F\u002F Print the exact sliding image index based on state\n        0, 0, this.size, this.size\n      );\n      this.ctx.restore(); \u002F\u002F Undo flip to secure next canvas painting!\n    } else {\n      \u002F\u002F Normal straightforward draw for Player 1 Left side\n      this.ctx.drawImage(\n        this.image[this.state][this.frame],\n        this.x, this.y, this.size, this.size\n      );\n    }\n  }\n",[40,3500,3501,3506,3511,3516,3520,3525,3530,3535,3540,3545,3549,3554,3558,3562,3567,3572,3577,3582,3587,3592,3596,3601,3606,3611,3616,3621,3625,3630,3634,3639,3644,3648,3652],{"__ignoreMap":16},[43,3502,3503],{"class":45,"line":46},[43,3504,3505],{},"  animate() {\n",[43,3507,3508],{"class":45,"line":52},[43,3509,3510],{},"    const activeImageArray = this.image[this.state];\n",[43,3512,3513],{"class":45,"line":94},[43,3514,3515],{},"    const now = Date.now();\n",[43,3517,3518],{"class":45,"line":100},[43,3519,882],{},[43,3521,3522],{"class":45,"line":107},[43,3523,3524],{},"    \u002F\u002F Only increment frame every 50ms (Animation Pacing)\n",[43,3526,3527],{"class":45,"line":113},[43,3528,3529],{},"    if (now - this.lastAnimate > 50) {\n",[43,3531,3532],{"class":45,"line":195},[43,3533,3534],{},"      this.frame++;\n",[43,3536,3537],{"class":45,"line":200},[43,3538,3539],{},"      \u002F\u002F Loop back to start if we exceed the frames length\n",[43,3541,3542],{"class":45,"line":206},[43,3543,3544],{},"      if (this.frame >= activeImageArray.length) this.frame = 0; \n",[43,3546,3547],{"class":45,"line":212},[43,3548,1038],{},[43,3550,3551],{"class":45,"line":218},[43,3552,3553],{},"      this.lastAnimate = now;\n",[43,3555,3556],{"class":45,"line":224},[43,3557,1080],{},[43,3559,3560],{"class":45,"line":618},[43,3561,104],{"emptyLinePlaceholder":103},[43,3563,3564],{"class":45,"line":914},[43,3565,3566],{},"    \u002F\u002F Flipping trick specifically for Player 2 (Right side)\n",[43,3568,3569],{"class":45,"line":920},[43,3570,3571],{},"    if (this.position === \"right\") {\n",[43,3573,3574],{"class":45,"line":926},[43,3575,3576],{},"      this.ctx.save();\n",[43,3578,3579],{"class":45,"line":932},[43,3580,3581],{},"      \u002F\u002F Move context pivot exactly at character width before scale mirroring!\n",[43,3583,3584],{"class":45,"line":937},[43,3585,3586],{},"      this.ctx.translate(this.x + this.size, this.y);\n",[43,3588,3589],{"class":45,"line":943},[43,3590,3591],{},"      this.ctx.scale(-1, 1); \u002F\u002F Flip Horizontally!\n",[43,3593,3594],{"class":45,"line":949},[43,3595,104],{"emptyLinePlaceholder":103},[43,3597,3598],{"class":45,"line":955},[43,3599,3600],{},"      this.ctx.drawImage(\n",[43,3602,3603],{"class":45,"line":960},[43,3604,3605],{},"        this.image[this.state][this.frame], \u002F\u002F Print the exact sliding image index based on state\n",[43,3607,3608],{"class":45,"line":966},[43,3609,3610],{},"        0, 0, this.size, this.size\n",[43,3612,3613],{"class":45,"line":972},[43,3614,3615],{},"      );\n",[43,3617,3618],{"class":45,"line":977},[43,3619,3620],{},"      this.ctx.restore(); \u002F\u002F Undo flip to secure next canvas painting!\n",[43,3622,3623],{"class":45,"line":983},[43,3624,1212],{},[43,3626,3627],{"class":45,"line":989},[43,3628,3629],{},"      \u002F\u002F Normal straightforward draw for Player 1 Left side\n",[43,3631,3632],{"class":45,"line":994},[43,3633,3600],{},[43,3635,3636],{"class":45,"line":1000},[43,3637,3638],{},"        this.image[this.state][this.frame],\n",[43,3640,3641],{"class":45,"line":1006},[43,3642,3643],{},"        this.x, this.y, this.size, this.size\n",[43,3645,3646],{"class":45,"line":1012},[43,3647,3615],{},[43,3649,3650],{"class":45,"line":1017},[43,3651,1080],{},[43,3653,3654],{"class":45,"line":1023},[43,3655,929],{},[22,3657,3658],{},"If you can conquer this system, this code pattern acts as the most rock-solid foundational blueprint for virtually every 2D platformer and fighting game code!",[14,3660,3661,3665,3680,3684,3700,3785,3789,3810,3903,3907,3925,3939,4090,4094,4107,4121,4278],{"v-slot:id":16},[18,3662,3664],{"id":3663},"html5-canvas-animasi-sprite-dan-fisika-game","HTML5 Canvas: Animasi Sprite dan Fisika Game",[22,3666,3667,3668,3671,3672,3675,3676,3679],{},"Setelah kita paham Game Loop, manajemen Input, serta teknik memutar Transformasi kanvas, ini saatnya menggunakan kode pamungkas untuk menggabungkan semuanya secara bersamaan. Mari kita bedah struktur fondasi dari ",[138,3669,3670],{},"Player Class \u002F Kelas Jagoan"," berdasarkan cuplikan kodingan game berstandar produksi nyata (production code). Kita akan menghidupkan hukum ",[1258,3673,3674],{},"Gravitasi"," agar karakter bisa melompat, sekaligus menggunakan sistem transisi ",[1258,3677,3678],{},"Animasi Gambar Sprite"," bersambung!",[26,3681,3683],{"id":3682},"_1-mempersiapkan-koleksi-aset-ekstensif","1. Mempersiapkan Koleksi Aset Ekstensif",[22,3685,3686,3687,3690,3691,3693,3694,3697,3698,157],{},"Lupakan menggambar kotak kaku. Game asli wajib memainkan gambar berderet (mirip film animasi tradisional frame-by-frame). Gambar-gambar tersebut kita muat dalam RAM lalu dan dikelompokkan ke array berdasarkan ",[1258,3688,3689],{},"Status Aktif"," emosinya (seperti kumpulan frame ",[40,3692,3073],{},", frame saat mundur ",[40,3695,3696],{},"MoveBackward",", saat melompat ",[40,3699,3076],{},[34,3701,3703],{"className":36,"code":3702,"language":38,"meta":16,"style":16},"\u002F\u002F Fungsi pembantu perakit objek DOM \u003Cimg \u002F> murni\nfunction loadImg(src) {\n  const img = new Image();\n  img.src = src;\n  return img; \n}\n\n\u002F\u002F Menghimpun array besar gambar dengan ketepatan file numbering\nfunction loadImagePlayer(country) {\n  const ImageState = { Idle: [], Jump: [], MoveForward: [] \u002F* ... *\u002F };\n  \n  \u002F\u002F Contoh: Menyedot 18 potong frame terpisah untuk Animasi Nafas\u002FDiam (Idle)\n  for (let i = 0; i \u003C= 17; i++) {\n    \u002F\u002F Trik Cerdas: String(i).padStart(2, \"0\") otomatis menyulap angka 1 menjadi teks \"01\"\n    ImageState.Idle.push(loadImg(`.\u002FSprites\u002FCharacters\u002F${country}\u002FIdle\u002FIdle_0${String(i).padStart(2, \"0\")}.png`));\n  }\n  return ImageState;\n}\n",[40,3704,3705,3710,3714,3718,3722,3727,3731,3735,3740,3744,3749,3753,3758,3762,3767,3772,3776,3781],{"__ignoreMap":16},[43,3706,3707],{"class":45,"line":46},[43,3708,3709],{},"\u002F\u002F Fungsi pembantu perakit objek DOM \u003Cimg \u002F> murni\n",[43,3711,3712],{"class":45,"line":52},[43,3713,3094],{},[43,3715,3716],{"class":45,"line":94},[43,3717,3099],{},[43,3719,3720],{"class":45,"line":100},[43,3721,3104],{},[43,3723,3724],{"class":45,"line":107},[43,3725,3726],{},"  return img; \n",[43,3728,3729],{"class":45,"line":113},[43,3730,809],{},[43,3732,3733],{"class":45,"line":195},[43,3734,104],{"emptyLinePlaceholder":103},[43,3736,3737],{"class":45,"line":200},[43,3738,3739],{},"\u002F\u002F Menghimpun array besar gambar dengan ketepatan file numbering\n",[43,3741,3742],{"class":45,"line":206},[43,3743,3127],{},[43,3745,3746],{"class":45,"line":212},[43,3747,3748],{},"  const ImageState = { Idle: [], Jump: [], MoveForward: [] \u002F* ... *\u002F };\n",[43,3750,3751],{"class":45,"line":218},[43,3752,1159],{},[43,3754,3755],{"class":45,"line":224},[43,3756,3757],{},"  \u002F\u002F Contoh: Menyedot 18 potong frame terpisah untuk Animasi Nafas\u002FDiam (Idle)\n",[43,3759,3760],{"class":45,"line":618},[43,3761,3146],{},[43,3763,3764],{"class":45,"line":914},[43,3765,3766],{},"    \u002F\u002F Trik Cerdas: String(i).padStart(2, \"0\") otomatis menyulap angka 1 menjadi teks \"01\"\n",[43,3768,3769],{"class":45,"line":920},[43,3770,3771],{},"    ImageState.Idle.push(loadImg(`.\u002FSprites\u002FCharacters\u002F${country}\u002FIdle\u002FIdle_0${String(i).padStart(2, \"0\")}.png`));\n",[43,3773,3774],{"class":45,"line":926},[43,3775,929],{},[43,3777,3778],{"class":45,"line":932},[43,3779,3780],{},"  return ImageState;\n",[43,3782,3783],{"class":45,"line":937},[43,3784,809],{},[26,3786,3788],{"id":3787},"_2-paradigma-kelas-pembungkus-organ-tubuhplayer-class","2. Paradigma Kelas Pembungkus Organ Tubuh(Player Class)",[22,3790,3791,3792,3794,3795,3797,3798,3800,3801,305,3803,3805,3806,305,3808,157],{},"Membungkus kode entitas dalam OOP Paradigma JavaScript ",[40,3793,3179],{}," adalah tata cara teraman di jagad pembuatan game. Si Karakter (Objek) setidaknya harus mengingat perihal letak lokasi keberadaannya (",[40,3796,3183],{},"), variabel laju luncurannya (",[40,3799,3187],{},"), pemutar slide frame yang terputar (",[40,3802,3191],{},[40,3804,3194],{},"), serta massa hukum gravitasi beban tubuh (",[40,3807,3198],{},[40,3809,3201],{},[34,3811,3813],{"className":36,"code":3812,"language":38,"meta":16,"style":16},"class Player {\n  constructor(country, position, ctx) {\n    this.ctx = ctx; \u002F\u002F kanvas layar referensi\n    this.position = position; \u002F\u002F Dari pihak \"left\" atau \"right\"?\n    this.image = loadImagePlayer(country); \n    \n    \u002F\u002F Dimensi dan Hukum Dunia\n    this.size = 150;\n    this.x = position === \"left\" ? 300 : 600; \u002F\u002F Alokasi pijakan start base awal\n    this.y = 0;\n    this.dy = 0; \u002F\u002F Delta Y: Kekuatan Tarik Turun\u002FKeatas (Momentum Vertical)\n    this.gravity = 0.2; \u002F\u002F Tambahan berat badan yang menarik konstan jatuh ke bawah \n\n    \u002F\u002F Ingatan Mesin Status Animasi saat ini (State machine)\n    this.state = \"Idle\"; \n    this.frame = 0; \u002F\u002F Penentu gambar keberapa yg wajib digambar dari array nya\n    this.lastAnimate = Date.now(); \u002F\u002F Stopwatch Delay laju putar Animasi (pacing kontroler)\n  }\n}\n",[40,3814,3815,3819,3823,3828,3833,3838,3842,3847,3851,3856,3861,3866,3871,3875,3880,3885,3890,3895,3899],{"__ignoreMap":16},[43,3816,3817],{"class":45,"line":46},[43,3818,3211],{},[43,3820,3821],{"class":45,"line":52},[43,3822,3216],{},[43,3824,3825],{"class":45,"line":94},[43,3826,3827],{},"    this.ctx = ctx; \u002F\u002F kanvas layar referensi\n",[43,3829,3830],{"class":45,"line":100},[43,3831,3832],{},"    this.position = position; \u002F\u002F Dari pihak \"left\" atau \"right\"?\n",[43,3834,3835],{"class":45,"line":107},[43,3836,3837],{},"    this.image = loadImagePlayer(country); \n",[43,3839,3840],{"class":45,"line":113},[43,3841,882],{},[43,3843,3844],{"class":45,"line":195},[43,3845,3846],{},"    \u002F\u002F Dimensi dan Hukum Dunia\n",[43,3848,3849],{"class":45,"line":200},[43,3850,3245],{},[43,3852,3853],{"class":45,"line":206},[43,3854,3855],{},"    this.x = position === \"left\" ? 300 : 600; \u002F\u002F Alokasi pijakan start base awal\n",[43,3857,3858],{"class":45,"line":212},[43,3859,3860],{},"    this.y = 0;\n",[43,3862,3863],{"class":45,"line":218},[43,3864,3865],{},"    this.dy = 0; \u002F\u002F Delta Y: Kekuatan Tarik Turun\u002FKeatas (Momentum Vertical)\n",[43,3867,3868],{"class":45,"line":224},[43,3869,3870],{},"    this.gravity = 0.2; \u002F\u002F Tambahan berat badan yang menarik konstan jatuh ke bawah \n",[43,3872,3873],{"class":45,"line":618},[43,3874,104],{"emptyLinePlaceholder":103},[43,3876,3877],{"class":45,"line":914},[43,3878,3879],{},"    \u002F\u002F Ingatan Mesin Status Animasi saat ini (State machine)\n",[43,3881,3882],{"class":45,"line":920},[43,3883,3884],{},"    this.state = \"Idle\"; \n",[43,3886,3887],{"class":45,"line":926},[43,3888,3889],{},"    this.frame = 0; \u002F\u002F Penentu gambar keberapa yg wajib digambar dari array nya\n",[43,3891,3892],{"class":45,"line":932},[43,3893,3894],{},"    this.lastAnimate = Date.now(); \u002F\u002F Stopwatch Delay laju putar Animasi (pacing kontroler)\n",[43,3896,3897],{"class":45,"line":937},[43,3898,929],{},[43,3900,3901],{"class":45,"line":943},[43,3902,809],{},[26,3904,3906],{"id":3905},"_3-penerapan-ilmu-fisika-gravitasi-lemparan-bebas","3. Penerapan Ilmu Fisika: Gravitasi & Lemparan Bebas",[22,3908,3909,3910,3912,3913,3916,3917,3920,3921,3924],{},"Konsep simulasi gravitasi sungguh brilian dan tak terduga! Di dalam ",[40,3911,3312],{},", variabel momentum tubuh vertikal ",[40,3914,3915],{},"this.dy"," terus menerus ditambah nilainya setiap ",[1258,3918,3919],{},"Frame"," oleh variabel beban konstan ",[40,3922,3923],{},"this.gravity",". Oleh karenanya badannya senantiasa terdorongan turun semakin jatuh!",[22,3926,3927,3928,3931,3932,3934,3935,3938],{},"Kecuali ia menabrak daratan (",[40,3929,3930],{},"groundLevel","). Sewaktu kakinya persis menyentuh tanah, perhitungan Gravitasi seketika ter-reset hangus menjadi nol (",[40,3933,3320],{},"). Tapii apabila ia membentur tombol Jump saat berpijak di dasar itu? Kita langsung jejalkan suntikan angka lemparan Ke atas bertolak keras (- Minus) pada dy (",[40,3936,3937],{},"dy = -8","), membuatnya sejenak melambung melawan gravitasi layaknya rudal roket terbang sesaat menyentuh angkasa!",[34,3940,3942],{"className":36,"code":3941,"language":38,"meta":16,"style":16},"  update() {\n    this.prevState = this.state; \u002F\u002F Recall ingatan di masa lalu memonitor pergantian aksi layar\n    \n    const groundLevel = canvas.height - this.size - 90; \u002F\u002F Kalkulasikan ketinggian batas tapak Lantai mutlak\n\n    \u002F\u002F Cek apakah sol sepatu `Y` nya tembus ke garis Tanah bumi?\n    const onGround = this.y >= groundLevel;\n\n    if (onGround) {\n      this.y = groundLevel; \u002F\u002F Paksa Posisi menempel sejajar di aspal (Anti-Glitch tembus lantai layar mati masuk neraka)\n      this.dy = 0; \u002F\u002F Matikan Beban Daya Tumbuk Jatuhnya\n\n      \u002F\u002F Trigger MELEDAKKAN Lompatan\n      if (this.keys[\"arrowup\"]) {\n        this.dy = -8; \u002F\u002F Tembakkan Rudal daya roket berakselerasi Ke-atas instan!\n      }\n    } else {\n      \u002F\u002F Jika nyatanya masih Melayang Udara, izinkan rantai hukum grafitasi mencekiknya memeras kekuatannya untuk jatuh ke tanah terus menerus tiap sekian MS \n      this.dy += this.gravity; \n    }\n\n    \u002F\u002F Akhirnya suntikan Total Akumulasi kalkulasi Vertikal (dy), mendarat dan mencemari Angka Titik Pijakan Lokasi Aslinya (y)\n    this.y += this.dy; \n\n    \u002F\u002F STATE MACHINE : Mesin Pendiktean Pergolakan Penggantian Aksi\n    if (this.y \u003C groundLevel) this.state = \"Jump\";\n    else if (this.keys[\"space\"]) this.state = \"Kick\";\n    else if (this.keys[\"arrowright\"]) this.state = \"MoveForward\";\n    else this.state = \"Idle\";\n\n    \u002F\u002F Mekanisme Keamanan: Reset Auto-rewind penggulungan slide video kembali ke file img indeks '0' ketika pergantian antar Gerak Tiba (spti diam tau2 lompat)\n    if (this.prevState !== this.state) this.frame = 0; \n  }\n",[40,3943,3944,3948,3953,3957,3962,3966,3971,3975,3979,3983,3988,3993,3997,4002,4006,4011,4015,4019,4024,4029,4033,4037,4042,4047,4051,4056,4060,4064,4068,4072,4076,4081,4086],{"__ignoreMap":16},[43,3945,3946],{"class":45,"line":46},[43,3947,3335],{},[43,3949,3950],{"class":45,"line":52},[43,3951,3952],{},"    this.prevState = this.state; \u002F\u002F Recall ingatan di masa lalu memonitor pergantian aksi layar\n",[43,3954,3955],{"class":45,"line":94},[43,3956,882],{},[43,3958,3959],{"class":45,"line":100},[43,3960,3961],{},"    const groundLevel = canvas.height - this.size - 90; \u002F\u002F Kalkulasikan ketinggian batas tapak Lantai mutlak\n",[43,3963,3964],{"class":45,"line":107},[43,3965,104],{"emptyLinePlaceholder":103},[43,3967,3968],{"class":45,"line":113},[43,3969,3970],{},"    \u002F\u002F Cek apakah sol sepatu `Y` nya tembus ke garis Tanah bumi?\n",[43,3972,3973],{"class":45,"line":195},[43,3974,3359],{},[43,3976,3977],{"class":45,"line":200},[43,3978,104],{"emptyLinePlaceholder":103},[43,3980,3981],{"class":45,"line":206},[43,3982,3368],{},[43,3984,3985],{"class":45,"line":212},[43,3986,3987],{},"      this.y = groundLevel; \u002F\u002F Paksa Posisi menempel sejajar di aspal (Anti-Glitch tembus lantai layar mati masuk neraka)\n",[43,3989,3990],{"class":45,"line":218},[43,3991,3992],{},"      this.dy = 0; \u002F\u002F Matikan Beban Daya Tumbuk Jatuhnya\n",[43,3994,3995],{"class":45,"line":224},[43,3996,104],{"emptyLinePlaceholder":103},[43,3998,3999],{"class":45,"line":618},[43,4000,4001],{},"      \u002F\u002F Trigger MELEDAKKAN Lompatan\n",[43,4003,4004],{"class":45,"line":914},[43,4005,3392],{},[43,4007,4008],{"class":45,"line":920},[43,4009,4010],{},"        this.dy = -8; \u002F\u002F Tembakkan Rudal daya roket berakselerasi Ke-atas instan!\n",[43,4012,4013],{"class":45,"line":926},[43,4014,1074],{},[43,4016,4017],{"class":45,"line":932},[43,4018,1212],{},[43,4020,4021],{"class":45,"line":937},[43,4022,4023],{},"      \u002F\u002F Jika nyatanya masih Melayang Udara, izinkan rantai hukum grafitasi mencekiknya memeras kekuatannya untuk jatuh ke tanah terus menerus tiap sekian MS \n",[43,4025,4026],{"class":45,"line":943},[43,4027,4028],{},"      this.dy += this.gravity; \n",[43,4030,4031],{"class":45,"line":949},[43,4032,1080],{},[43,4034,4035],{"class":45,"line":955},[43,4036,104],{"emptyLinePlaceholder":103},[43,4038,4039],{"class":45,"line":960},[43,4040,4041],{},"    \u002F\u002F Akhirnya suntikan Total Akumulasi kalkulasi Vertikal (dy), mendarat dan mencemari Angka Titik Pijakan Lokasi Aslinya (y)\n",[43,4043,4044],{"class":45,"line":966},[43,4045,4046],{},"    this.y += this.dy; \n",[43,4048,4049],{"class":45,"line":972},[43,4050,104],{"emptyLinePlaceholder":103},[43,4052,4053],{"class":45,"line":977},[43,4054,4055],{},"    \u002F\u002F STATE MACHINE : Mesin Pendiktean Pergolakan Penggantian Aksi\n",[43,4057,4058],{"class":45,"line":983},[43,4059,3437],{},[43,4061,4062],{"class":45,"line":989},[43,4063,3442],{},[43,4065,4066],{"class":45,"line":994},[43,4067,3447],{},[43,4069,4070],{"class":45,"line":1000},[43,4071,3452],{},[43,4073,4074],{"class":45,"line":1006},[43,4075,104],{"emptyLinePlaceholder":103},[43,4077,4078],{"class":45,"line":1012},[43,4079,4080],{},"    \u002F\u002F Mekanisme Keamanan: Reset Auto-rewind penggulungan slide video kembali ke file img indeks '0' ketika pergantian antar Gerak Tiba (spti diam tau2 lompat)\n",[43,4082,4083],{"class":45,"line":1017},[43,4084,4085],{},"    if (this.prevState !== this.state) this.frame = 0; \n",[43,4087,4088],{"class":45,"line":1023},[43,4089,929],{},[26,4091,4093],{"id":4092},"_4-render-animasi-frame-beruntun-pemantulan-cermin-otomatis-kaca-tim","4. Render Animasi Frame Beruntun & Pemantulan Cermin Otomatis Kaca Tim",[22,4095,4096,4097,4100,4101,4103,4104,4106],{},"Keahlian terakhir metode ",[1258,4098,4099],{},"core"," tersembunyi Sang Jagoan berada di ",[40,4102,3480],{},". Skrip ini menggunakan logika cerdas pembatas Timer rentang pias Waktu (",[40,4105,3484],{},"). Laju delay perulangan antar gambar dimanipulasi dengan batas angka \"lebih dari 50 Milikdetik\" supaya framenya tertahan ganti secara rasional. Bila limit ini tidak di rem, maka 18 frame tendangan animasimu akan habis secepat panah dalam wujud \"Seper Enam-Puluh\" (1\u002F60) detik di layar monitor! Manusia tidak akan menyadarinya.",[22,4108,4109,4110,4113,4114,4117,4118,4120],{},"Dan Terakhir, mari gunakan materi ",[1258,4111,4112],{},"Transformasi Flip (Minus Scala 1)"," yang baru saja diajarkan untuk Player pihak sebelah Tim Kanan (",[40,4115,4116],{},"position === \"right\"","). Daripada kerepotan melukis manual karakter dengan gambar pose menghindar mengarah sebaliknya hadap kiri, kita cukup menyuruh Program melakukan Rotasi pencerminan dimensi koordinat ",[40,4119,3495],{}," sesaat sebelum nge-print (drawImage) ke hadap Tim Sang Pemain Kiri Musuhnya.",[34,4122,4124],{"className":36,"code":4123,"language":38,"meta":16,"style":16},"  animate() {\n    \u002F\u002F Intip Array Koleksi Foto yang aktif saat Status Animainya di call ini\n    const KumpulanGambarAnimasiAktif = this.image[this.state];\n    const jamSkrg = Date.now();\n    \n    \u002F\u002F Gembok Stopwatch: Frame Tayang slide gambar selanjutnya HANYA tergeser apabila Waktu sudah terdiam berjaraknya melewati 50ms \n    if (jamSkrg - this.lastAnimate > 50) {\n      this.frame++; \u002F\u002F Geser penunjuk slider ke Next Nomor gambar \n      \u002F\u002F Gulung pemutar klise gambar jadi 0, persis sama saat Film animasinya habis tak tersisa \n      if (this.frame >= KumpulanGambarAnimasiAktif.length) this.frame = 0; \n      \n      this.lastAnimate = jamSkrg;\n    }\n\n    \u002F\u002F Trik Pembalikan Kaca Cermin Ekslusif Hanya untuk Pemain Posisi Markas \"Kanan\" Lapangan\n    if (this.position === \"right\") {\n      this.ctx.save();\n      \u002F\u002F Pusat Geser pindah tembus menjarah ke sayap depan titik x Character supaya orientasi cermin tdk ngawur\n      this.ctx.translate(this.x + this.size, this.y); \n      this.ctx.scale(-1, 1); \u002F\u002F Flip Putaran Bayangan Cermin Horizontal 180 Drajat Mutlak!\n\n      this.ctx.drawImage(\n        this.image[this.state][this.frame], \u002F\u002F Pilih selambar spesifik file gambar index persis sesuai penempatannya.\n        0, 0, this.size, this.size\n      );\n      this.ctx.restore(); \u002F\u002F Amankan ruang Kanvas kembali. Lepas bayangannya\n    } else {\n      \u002F\u002F Menggambar Normal Biasa untuk kubu Pemain di sisi Kiri Lapangan yang emang fitrahnya aslinya tergambar menghadap arah kanan\n      this.ctx.drawImage(\n        this.image[this.state][this.frame],\n        this.x, this.y, this.size, this.size\n      );\n    }\n  }\n",[40,4125,4126,4130,4135,4140,4145,4149,4154,4159,4164,4169,4174,4178,4183,4187,4191,4196,4200,4204,4209,4214,4219,4223,4227,4232,4236,4240,4245,4249,4254,4258,4262,4266,4270,4274],{"__ignoreMap":16},[43,4127,4128],{"class":45,"line":46},[43,4129,3505],{},[43,4131,4132],{"class":45,"line":52},[43,4133,4134],{},"    \u002F\u002F Intip Array Koleksi Foto yang aktif saat Status Animainya di call ini\n",[43,4136,4137],{"class":45,"line":94},[43,4138,4139],{},"    const KumpulanGambarAnimasiAktif = this.image[this.state];\n",[43,4141,4142],{"class":45,"line":100},[43,4143,4144],{},"    const jamSkrg = Date.now();\n",[43,4146,4147],{"class":45,"line":107},[43,4148,882],{},[43,4150,4151],{"class":45,"line":113},[43,4152,4153],{},"    \u002F\u002F Gembok Stopwatch: Frame Tayang slide gambar selanjutnya HANYA tergeser apabila Waktu sudah terdiam berjaraknya melewati 50ms \n",[43,4155,4156],{"class":45,"line":195},[43,4157,4158],{},"    if (jamSkrg - this.lastAnimate > 50) {\n",[43,4160,4161],{"class":45,"line":200},[43,4162,4163],{},"      this.frame++; \u002F\u002F Geser penunjuk slider ke Next Nomor gambar \n",[43,4165,4166],{"class":45,"line":206},[43,4167,4168],{},"      \u002F\u002F Gulung pemutar klise gambar jadi 0, persis sama saat Film animasinya habis tak tersisa \n",[43,4170,4171],{"class":45,"line":212},[43,4172,4173],{},"      if (this.frame >= KumpulanGambarAnimasiAktif.length) this.frame = 0; \n",[43,4175,4176],{"class":45,"line":218},[43,4177,1038],{},[43,4179,4180],{"class":45,"line":224},[43,4181,4182],{},"      this.lastAnimate = jamSkrg;\n",[43,4184,4185],{"class":45,"line":618},[43,4186,1080],{},[43,4188,4189],{"class":45,"line":914},[43,4190,104],{"emptyLinePlaceholder":103},[43,4192,4193],{"class":45,"line":920},[43,4194,4195],{},"    \u002F\u002F Trik Pembalikan Kaca Cermin Ekslusif Hanya untuk Pemain Posisi Markas \"Kanan\" Lapangan\n",[43,4197,4198],{"class":45,"line":926},[43,4199,3571],{},[43,4201,4202],{"class":45,"line":932},[43,4203,3576],{},[43,4205,4206],{"class":45,"line":937},[43,4207,4208],{},"      \u002F\u002F Pusat Geser pindah tembus menjarah ke sayap depan titik x Character supaya orientasi cermin tdk ngawur\n",[43,4210,4211],{"class":45,"line":943},[43,4212,4213],{},"      this.ctx.translate(this.x + this.size, this.y); \n",[43,4215,4216],{"class":45,"line":949},[43,4217,4218],{},"      this.ctx.scale(-1, 1); \u002F\u002F Flip Putaran Bayangan Cermin Horizontal 180 Drajat Mutlak!\n",[43,4220,4221],{"class":45,"line":955},[43,4222,104],{"emptyLinePlaceholder":103},[43,4224,4225],{"class":45,"line":960},[43,4226,3600],{},[43,4228,4229],{"class":45,"line":966},[43,4230,4231],{},"        this.image[this.state][this.frame], \u002F\u002F Pilih selambar spesifik file gambar index persis sesuai penempatannya.\n",[43,4233,4234],{"class":45,"line":972},[43,4235,3610],{},[43,4237,4238],{"class":45,"line":977},[43,4239,3615],{},[43,4241,4242],{"class":45,"line":983},[43,4243,4244],{},"      this.ctx.restore(); \u002F\u002F Amankan ruang Kanvas kembali. Lepas bayangannya\n",[43,4246,4247],{"class":45,"line":989},[43,4248,1212],{},[43,4250,4251],{"class":45,"line":994},[43,4252,4253],{},"      \u002F\u002F Menggambar Normal Biasa untuk kubu Pemain di sisi Kiri Lapangan yang emang fitrahnya aslinya tergambar menghadap arah kanan\n",[43,4255,4256],{"class":45,"line":1000},[43,4257,3600],{},[43,4259,4260],{"class":45,"line":1006},[43,4261,3638],{},[43,4263,4264],{"class":45,"line":1012},[43,4265,3643],{},[43,4267,4268],{"class":45,"line":1017},[43,4269,3615],{},[43,4271,4272],{"class":45,"line":1023},[43,4273,1080],{},[43,4275,4276],{"class":45,"line":1029},[43,4277,929],{},[22,4279,4280],{},"Bila dinaluri sedalam mungkin, arsitektur pattern di dalam file OOP kelas ini adalah Masterpiece mutlak dalam pembentukan hampir di seluruh kodingan dari game model tarung pertarungan (Fight\u002FBrawler), mapun Loncat-Loncatan Platform klasik Mario, Megaman yang ada belahan game manapun di muka dunia! Terus asah logika insting pengkodean keren hebat tersebut!",[690,4282,692],{},{"title":16,"searchDepth":52,"depth":52,"links":4284},[],{"title_en":3044,"title_id":3664,"description_en":4286,"description_id":4287,"date":699,"readingTime":4288},"Learn to build a complete Player Class with sprite frame animation, gravity physics, and state machines based on real game code.","Pelajari cara membangun Player Class lengkap dengan animasi frame sprite, fisika gravitasi, dan mesin state berdasarkan kode game nyata.","10 min read","\u002Fblog\u002Fcanvas-sprite-physics",{"title":3044,"description":16},"blog\u002Fcanvas-sprite-physics","75r73uZAexZp5wd-mZ_97ssvqhb3azzeebhC9xI3rBo",{"id":4294,"title":4295,"body":4296,"description":16,"extension":695,"meta":5007,"navigation":103,"path":5010,"seo":5011,"stem":5012,"__hash__":5013},"blog\u002Fblog\u002Fcanvas-transformations.md","HTML5 Canvas Transformations: Translate, Rotate, and Scale",{"type":8,"value":4297,"toc":5005},[4298,5003],[11,4299,4300,4658],{},[14,4301,4302,4305,4312,4323,4333,4342,4370,4377,4388,4464,4471,4484,4560,4567,4581,4587],{"v-slot:en":16},[18,4303,4295],{"id":4304},"html5-canvas-transformations-translate-rotate-and-scale",[22,4306,4307,4308,4311],{},"Once you have mastered drawing basic shapes, the next essential skill in canvas game development is understanding ",[138,4309,4310],{},"Transformations",". This includes moving, rotating, and scaling (or flipping) objects.",[22,4313,4314,4315,4318,4319,4322],{},"The most important concept in Canvas transformations is that you ",[138,4316,4317],{},"don't rotate or scale the shape itself",". Instead, you transform the ",[138,4320,4321],{},"entire canvas coordinate system"," before drawing the shape, and then you revert the coordinate system back. Let's see how!",[26,4324,4326,4327,727,4330],{"id":4325},"_1-state-management-save-and-restore","1. State Management: ",[40,4328,4329],{},"save()",[40,4331,4332],{},"restore()",[22,4334,4335,4336,4338,4339,4341],{},"Since transformations affect the entire canvas globally, you must use ",[40,4337,4329],{}," to store the current canvas state, apply your transformations, draw, and finally use ",[40,4340,4332],{}," to bring the canvas back to normal.",[34,4343,4345],{"className":36,"code":4344,"language":38,"meta":16,"style":16},"ctx.save(); \u002F\u002F Save the default un-transformed state\n\n\u002F\u002F ... Apply transformations and draw here ...\n\nctx.restore(); \u002F\u002F Revert back to the un-transformed state\n",[40,4346,4347,4352,4356,4361,4365],{"__ignoreMap":16},[43,4348,4349],{"class":45,"line":46},[43,4350,4351],{},"ctx.save(); \u002F\u002F Save the default un-transformed state\n",[43,4353,4354],{"class":45,"line":52},[43,4355,104],{"emptyLinePlaceholder":103},[43,4357,4358],{"class":45,"line":94},[43,4359,4360],{},"\u002F\u002F ... Apply transformations and draw here ...\n",[43,4362,4363],{"class":45,"line":100},[43,4364,104],{"emptyLinePlaceholder":103},[43,4366,4367],{"class":45,"line":107},[43,4368,4369],{},"ctx.restore(); \u002F\u002F Revert back to the un-transformed state\n",[26,4371,4373,4374],{"id":4372},"_2-moving-the-origin-translatex-y","2. Moving the Origin: ",[40,4375,4376],{},"translate(x, y)",[22,4378,4379,4380,4383,4384,4387],{},"By default, the origin ",[40,4381,4382],{},"(0, 0)"," is at the top-left corner of the canvas. ",[40,4385,4386],{},"translate"," moves this origin point. If you want to rotate an object around its center, you must first move the coordinate system's origin to the exact center of where you want to draw the object.",[34,4389,4391],{"className":36,"code":4390,"language":38,"meta":16,"style":16},"const x = 100; \u002F\u002F Position X\nconst y = 100; \u002F\u002F Position Y\nconst width = 50;\nconst height = 50;\n\nctx.save();\nctx.translate(x + width \u002F 2, y + height \u002F 2); \u002F\u002F Move origin to object's center\n\n\u002F\u002F Now, drawing at (0, 0) is actually drawing exactly at our translated center\nctx.fillStyle = 'blue';\n\n\u002F\u002F We must draw from -width\u002F2, -height\u002F2 because the origin is exactly in the middle!\nctx.fillRect(-width \u002F 2, -height \u002F 2, width, height); \n\nctx.restore(); \u002F\u002F Return origin to top-left (0, 0)\n",[40,4392,4393,4398,4403,4408,4413,4417,4422,4427,4431,4436,4441,4445,4450,4455,4459],{"__ignoreMap":16},[43,4394,4395],{"class":45,"line":46},[43,4396,4397],{},"const x = 100; \u002F\u002F Position X\n",[43,4399,4400],{"class":45,"line":52},[43,4401,4402],{},"const y = 100; \u002F\u002F Position Y\n",[43,4404,4405],{"class":45,"line":94},[43,4406,4407],{},"const width = 50;\n",[43,4409,4410],{"class":45,"line":100},[43,4411,4412],{},"const height = 50;\n",[43,4414,4415],{"class":45,"line":107},[43,4416,104],{"emptyLinePlaceholder":103},[43,4418,4419],{"class":45,"line":113},[43,4420,4421],{},"ctx.save();\n",[43,4423,4424],{"class":45,"line":195},[43,4425,4426],{},"ctx.translate(x + width \u002F 2, y + height \u002F 2); \u002F\u002F Move origin to object's center\n",[43,4428,4429],{"class":45,"line":200},[43,4430,104],{"emptyLinePlaceholder":103},[43,4432,4433],{"class":45,"line":206},[43,4434,4435],{},"\u002F\u002F Now, drawing at (0, 0) is actually drawing exactly at our translated center\n",[43,4437,4438],{"class":45,"line":212},[43,4439,4440],{},"ctx.fillStyle = 'blue';\n",[43,4442,4443],{"class":45,"line":218},[43,4444,104],{"emptyLinePlaceholder":103},[43,4446,4447],{"class":45,"line":224},[43,4448,4449],{},"\u002F\u002F We must draw from -width\u002F2, -height\u002F2 because the origin is exactly in the middle!\n",[43,4451,4452],{"class":45,"line":618},[43,4453,4454],{},"ctx.fillRect(-width \u002F 2, -height \u002F 2, width, height); \n",[43,4456,4457],{"class":45,"line":914},[43,4458,104],{"emptyLinePlaceholder":103},[43,4460,4461],{"class":45,"line":920},[43,4462,4463],{},"ctx.restore(); \u002F\u002F Return origin to top-left (0, 0)\n",[26,4465,4467,4468],{"id":4466},"_3-rotating-shapes-rotateangle","3. Rotating Shapes: ",[40,4469,4470],{},"rotate(angle)",[22,4472,4473,4476,4477,4479,4480,4483],{},[40,4474,4475],{},"ctx.rotate()"," rotates the grid around the current ",[40,4478,4382],{}," origin point. This is why we almost always translate the origin to the center of the shape ",[1258,4481,4482],{},"first",". Angles must be in radians, not degrees.",[34,4485,4487],{"className":36,"code":4486,"language":38,"meta":16,"style":16},"const angleInDegrees = 45;\nconst angleInRadians = angleInDegrees * Math.PI \u002F 180;\n\nctx.save();\n\u002F\u002F 1. Move origin to center point\nctx.translate(150, 150);\n\n\u002F\u002F 2. Rotate the grid\nctx.rotate(angleInRadians);\n\n\u002F\u002F 3. Draw shape centered at the new origin (0, 0)\nctx.fillStyle = 'red';\n\u002F\u002F Draws a 50x50 rotated square\nctx.fillRect(-25, -25, 50, 50); \nctx.restore();\n",[40,4488,4489,4494,4499,4503,4507,4512,4517,4521,4526,4531,4535,4540,4545,4550,4555],{"__ignoreMap":16},[43,4490,4491],{"class":45,"line":46},[43,4492,4493],{},"const angleInDegrees = 45;\n",[43,4495,4496],{"class":45,"line":52},[43,4497,4498],{},"const angleInRadians = angleInDegrees * Math.PI \u002F 180;\n",[43,4500,4501],{"class":45,"line":94},[43,4502,104],{"emptyLinePlaceholder":103},[43,4504,4505],{"class":45,"line":100},[43,4506,4421],{},[43,4508,4509],{"class":45,"line":107},[43,4510,4511],{},"\u002F\u002F 1. Move origin to center point\n",[43,4513,4514],{"class":45,"line":113},[43,4515,4516],{},"ctx.translate(150, 150);\n",[43,4518,4519],{"class":45,"line":195},[43,4520,104],{"emptyLinePlaceholder":103},[43,4522,4523],{"class":45,"line":200},[43,4524,4525],{},"\u002F\u002F 2. Rotate the grid\n",[43,4527,4528],{"class":45,"line":206},[43,4529,4530],{},"ctx.rotate(angleInRadians);\n",[43,4532,4533],{"class":45,"line":212},[43,4534,104],{"emptyLinePlaceholder":103},[43,4536,4537],{"class":45,"line":218},[43,4538,4539],{},"\u002F\u002F 3. Draw shape centered at the new origin (0, 0)\n",[43,4541,4542],{"class":45,"line":224},[43,4543,4544],{},"ctx.fillStyle = 'red';\n",[43,4546,4547],{"class":45,"line":618},[43,4548,4549],{},"\u002F\u002F Draws a 50x50 rotated square\n",[43,4551,4552],{"class":45,"line":914},[43,4553,4554],{},"ctx.fillRect(-25, -25, 50, 50); \n",[43,4556,4557],{"class":45,"line":920},[43,4558,4559],{},"ctx.restore();\n",[26,4561,4563,4564],{"id":4562},"_4-scaling-and-flipping-scalex-y","4. Scaling and Flipping: ",[40,4565,4566],{},"scale(x, y)",[22,4568,4569,4572,4573,4576,4577,4580],{},[40,4570,4571],{},"ctx.scale(x, y)"," scales the current grid. ",[40,4574,4575],{},"x: 2"," makes things twice as wide. ",[40,4578,4579],{},"x: 0.5"," makes them half size.",[22,4582,4583,4584,2678],{},"A very common trick in game development is to flip an image or shape horizontally (like a character turning left or right). You can do this by using a ",[138,4585,4586],{},"negative scale",[34,4588,4590],{"className":36,"code":4589,"language":38,"meta":16,"style":16},"ctx.save();\n\u002F\u002F 1. Move origin to the object's center first\nctx.translate(250, 200);\n\n\u002F\u002F 2. Flip horizontally (x = -1) and keep vertical scale normal (y = 1)\nctx.scale(-1, 1);\n\n\u002F\u002F 3. Draw element\nctx.font = '30px Arial';\nctx.textAlign = 'center';\nctx.textBaseline = 'middle';\n\u002F\u002F The text will be drawn backwards (flipped) like a mirror\nctx.fillText('FLIPPED!', 0, 0); \nctx.restore();\n",[40,4591,4592,4596,4601,4606,4610,4615,4620,4624,4629,4634,4639,4644,4649,4654],{"__ignoreMap":16},[43,4593,4594],{"class":45,"line":46},[43,4595,4421],{},[43,4597,4598],{"class":45,"line":52},[43,4599,4600],{},"\u002F\u002F 1. Move origin to the object's center first\n",[43,4602,4603],{"class":45,"line":94},[43,4604,4605],{},"ctx.translate(250, 200);\n",[43,4607,4608],{"class":45,"line":100},[43,4609,104],{"emptyLinePlaceholder":103},[43,4611,4612],{"class":45,"line":107},[43,4613,4614],{},"\u002F\u002F 2. Flip horizontally (x = -1) and keep vertical scale normal (y = 1)\n",[43,4616,4617],{"class":45,"line":113},[43,4618,4619],{},"ctx.scale(-1, 1);\n",[43,4621,4622],{"class":45,"line":195},[43,4623,104],{"emptyLinePlaceholder":103},[43,4625,4626],{"class":45,"line":200},[43,4627,4628],{},"\u002F\u002F 3. Draw element\n",[43,4630,4631],{"class":45,"line":206},[43,4632,4633],{},"ctx.font = '30px Arial';\n",[43,4635,4636],{"class":45,"line":212},[43,4637,4638],{},"ctx.textAlign = 'center';\n",[43,4640,4641],{"class":45,"line":218},[43,4642,4643],{},"ctx.textBaseline = 'middle';\n",[43,4645,4646],{"class":45,"line":224},[43,4647,4648],{},"\u002F\u002F The text will be drawn backwards (flipped) like a mirror\n",[43,4650,4651],{"class":45,"line":618},[43,4652,4653],{},"ctx.fillText('FLIPPED!', 0, 0); \n",[43,4655,4656],{"class":45,"line":914},[43,4657,4559],{},[14,4659,4660,4664,4671,4686,4694,4703,4731,4737,4746,4825,4831,4845,4917,4923,4929,4932],{"v-slot:id":16},[18,4661,4663],{"id":4662},"transformasi-html5-canvas-translate-rotate-dan-scale","Transformasi HTML5 Canvas: Translate, Rotate, dan Scale",[22,4665,4666,4667,4670],{},"Setelah menguasai cara menggambar bentuk dasar, kemampuan penting berikutnya dalam pembuatan game canvas adalah memahami ",[138,4668,4669],{},"Transformasi",". Ini termasuk memindahkan orientasi titik posisi (Translate), memutar arah kemiringan (Rotate), serta mengubah ukuran atau membalikkan arah sumbu (Scale \u002F Flip) bagi object apapun yang digambar selanjutnya.",[22,4672,4673,4674,4677,4678,4681,4682,4685],{},"Konsep yang paling mendasar di Canvas Game Engine adalah ",[138,4675,4676],{},"Anda tidak memutar atau mengubah orientasi objek yang digambar tersebut",". Yang dilakukan sebenarnya ialah ",[138,4679,4680],{},"memutar orientasi seluruh koordinat sumbu canvas (ruang kerjanya)"," ",[1258,4683,4684],{},"sebelum"," merender memoles gambar yang diiginkan, dan barulah seluruh setingan sumbu dipulihkan kembali ke semula posisinya.",[26,4687,4689,4690,1268,4692],{"id":4688},"_1-mengelola-state-ruang-kerja-save-dan-restore","1. Mengelola State Ruang Kerja: ",[40,4691,4329],{},[40,4693,4332],{},[22,4695,4696,4697,4699,4700,4702],{},"Oleh karena orientasi transformasi selalu menimpa ke keseluruhan grid Canvas secara global (merubah dunia), Anda diwajibkan untuk biasa menggunakan metode ",[40,4698,4329],{}," yang berguna merekam rekam jejak status aslinya (normal dunia sebelum digeser\u002Fdiputar). Setelah menerapkan pergerakan, Anda tinggal memanggil fungsi pendamping pasangannya yaitu ",[40,4701,4332],{}," agar seluruh orientasi titik Canvas berbalik pemulihannya ke kondisi normalnya tadi.",[34,4704,4706],{"className":36,"code":4705,"language":38,"meta":16,"style":16},"ctx.save(); \u002F\u002F Simpan koordinat dan orientasi normal-nya saat ini\n\n\u002F\u002F ... Terapkan modifikasi sumbu XY dengan Translate\u002FRotate, dilanjut Menggambar suatu wujud di dunia\u002Fgrid mutasi ini ...\n\nctx.restore(); \u002F\u002F Pemulihan paksa kembali titik orientasi canvas dari track record 'save' sebelumnya.\n",[40,4707,4708,4713,4717,4722,4726],{"__ignoreMap":16},[43,4709,4710],{"class":45,"line":46},[43,4711,4712],{},"ctx.save(); \u002F\u002F Simpan koordinat dan orientasi normal-nya saat ini\n",[43,4714,4715],{"class":45,"line":52},[43,4716,104],{"emptyLinePlaceholder":103},[43,4718,4719],{"class":45,"line":94},[43,4720,4721],{},"\u002F\u002F ... Terapkan modifikasi sumbu XY dengan Translate\u002FRotate, dilanjut Menggambar suatu wujud di dunia\u002Fgrid mutasi ini ...\n",[43,4723,4724],{"class":45,"line":100},[43,4725,104],{"emptyLinePlaceholder":103},[43,4727,4728],{"class":45,"line":107},[43,4729,4730],{},"ctx.restore(); \u002F\u002F Pemulihan paksa kembali titik orientasi canvas dari track record 'save' sebelumnya.\n",[26,4732,4734,4735],{"id":4733},"_2-memindahkan-titik-origin-orientasi-poros-translatex-y","2. Memindahkan Titik Origin Orientasi Poros: ",[40,4736,4376],{},[22,4738,4739,4740,4742,4743,4745],{},"Setelan bawaannya, titik asal origin Canvas Canvas ",[40,4741,4382],{}," selalu berada mati di sudut paling kiri-atas. Kode ",[40,4744,4386],{}," sengaja difungsikan mentransportasikan pemindahan titik poros origin tersebut kemanapun area layar yang kita rencanakan orientasikan pergerakannya. Saat Anda berniat memutar sebuah objek berporos di tengah (berbasis center point objek-nya layaknya kincir\u002Fjarum jam), maka syarat pertama absolut adalah Anda WAJIB memindahkan sumbu (0,0) persis masuk menembus ditengah wujud objek target perputaran.",[34,4747,4749],{"className":36,"code":4748,"language":38,"meta":16,"style":16},"const x = 100; \u002F\u002F Target Posisi X Asli\nconst y = 100; \u002F\u002F Target Posisi Y Asli\nconst lebar = 50;\nconst tinggi = 50;\n\nctx.save();\n\u002F\u002F Berlabuh memindahkan titik pusat 0,0 kanvas, tepat membelah pertengahan sumbu target Object\nctx.translate(x + lebar \u002F 2, y + tinggi \u002F 2); \n\n\u002F\u002F Karena target (0,0) kita kini bukan lagi dari ujung layar pojok kiri atas, melainkan sudah di per-tengahan objek yang ditarget ini\nctx.fillStyle = 'blue';\n\n\u002F\u002F Agar menggambarnya persis presisi di center, proses gambar persegi panjang wajib dimundurkan (- minus) dengan porsi dari setengah(2) ukurannya\nctx.fillRect(-lebar \u002F 2, -tinggi \u002F 2, lebar, tinggi); \n\nctx.restore(); \u002F\u002F Tarik pulang ruang kerja dunia kanvas ke pemusatan ujung 0,0 sudut kiri atas kembali.\n",[40,4750,4751,4756,4761,4766,4771,4775,4779,4784,4789,4793,4798,4802,4806,4811,4816,4820],{"__ignoreMap":16},[43,4752,4753],{"class":45,"line":46},[43,4754,4755],{},"const x = 100; \u002F\u002F Target Posisi X Asli\n",[43,4757,4758],{"class":45,"line":52},[43,4759,4760],{},"const y = 100; \u002F\u002F Target Posisi Y Asli\n",[43,4762,4763],{"class":45,"line":94},[43,4764,4765],{},"const lebar = 50;\n",[43,4767,4768],{"class":45,"line":100},[43,4769,4770],{},"const tinggi = 50;\n",[43,4772,4773],{"class":45,"line":107},[43,4774,104],{"emptyLinePlaceholder":103},[43,4776,4777],{"class":45,"line":113},[43,4778,4421],{},[43,4780,4781],{"class":45,"line":195},[43,4782,4783],{},"\u002F\u002F Berlabuh memindahkan titik pusat 0,0 kanvas, tepat membelah pertengahan sumbu target Object\n",[43,4785,4786],{"class":45,"line":200},[43,4787,4788],{},"ctx.translate(x + lebar \u002F 2, y + tinggi \u002F 2); \n",[43,4790,4791],{"class":45,"line":206},[43,4792,104],{"emptyLinePlaceholder":103},[43,4794,4795],{"class":45,"line":212},[43,4796,4797],{},"\u002F\u002F Karena target (0,0) kita kini bukan lagi dari ujung layar pojok kiri atas, melainkan sudah di per-tengahan objek yang ditarget ini\n",[43,4799,4800],{"class":45,"line":218},[43,4801,4440],{},[43,4803,4804],{"class":45,"line":224},[43,4805,104],{"emptyLinePlaceholder":103},[43,4807,4808],{"class":45,"line":618},[43,4809,4810],{},"\u002F\u002F Agar menggambarnya persis presisi di center, proses gambar persegi panjang wajib dimundurkan (- minus) dengan porsi dari setengah(2) ukurannya\n",[43,4812,4813],{"class":45,"line":914},[43,4814,4815],{},"ctx.fillRect(-lebar \u002F 2, -tinggi \u002F 2, lebar, tinggi); \n",[43,4817,4818],{"class":45,"line":920},[43,4819,104],{"emptyLinePlaceholder":103},[43,4821,4822],{"class":45,"line":926},[43,4823,4824],{},"ctx.restore(); \u002F\u002F Tarik pulang ruang kerja dunia kanvas ke pemusatan ujung 0,0 sudut kiri atas kembali.\n",[26,4826,4828,4829],{"id":4827},"_3-merotasi-kemiringan-derajat-bentuk-rotateangle","3. Merotasi Kemiringan Derajat Bentuk: ",[40,4830,4470],{},[22,4832,4833,4834,4837,4838,4841,4842,4844],{},"Metode ",[40,4835,4836],{},"ctx.rotate(radiant)"," mencondongkan putaran satu ruang dimensi di koordinat rotasi pangkal origin titik ",[40,4839,4840],{},"(0,0)"," terkini. Inilah akar penyebab utama kenapa kita memprioritaskan proses geser poros (",[40,4843,4386],{},") pusat orientasi ketengah persis bentuk objek! (Kalau (0,0) nya tidak dipindahkan dari ujung kiri atas, maka objek Anda bukannya akan berputar melintir di tempat, melainkan sebaliknya berputar nyasar layaknya gerakan planet berevolusi memutari bingkai ujung ujung seluruh web Canvas screen!)",[34,4846,4848],{"className":36,"code":4847,"language":38,"meta":16,"style":16},"const sudutDegree = 45; \u002F\u002F Angka derajat rotasi sudut\nconst hitunganRadian = sudutDegree * Math.PI \u002F 180; \u002F\u002F konversi ke hitungan Radian yang dipahami canvas\n\nctx.save();\n\u002F\u002F 1. Transplantasi\u002FGeser pemutar canvas poros ketengah sumbu object\nctx.translate(150, 150);\n\n\u002F\u002F 2. Putar kemiringan putaran seluruh medan orientasi (harus wajib dihitung kedalam Radian)\nctx.rotate(hitunganRadian);\n\n\u002F\u002F 3. Menggambar wujud ke poros koordinat (0,0) baru yg telah termiringkan tersebut\nctx.fillStyle = 'red';\n\u002F\u002F Berukuran lebar & tinggi (50x50), nilai posisi digeser x minus setengah dan y minus setengah (-25) kebalikannya \nctx.fillRect(-25, -25, 50, 50); \nctx.restore();\n",[40,4849,4850,4855,4860,4864,4868,4873,4877,4881,4886,4891,4895,4900,4904,4909,4913],{"__ignoreMap":16},[43,4851,4852],{"class":45,"line":46},[43,4853,4854],{},"const sudutDegree = 45; \u002F\u002F Angka derajat rotasi sudut\n",[43,4856,4857],{"class":45,"line":52},[43,4858,4859],{},"const hitunganRadian = sudutDegree * Math.PI \u002F 180; \u002F\u002F konversi ke hitungan Radian yang dipahami canvas\n",[43,4861,4862],{"class":45,"line":94},[43,4863,104],{"emptyLinePlaceholder":103},[43,4865,4866],{"class":45,"line":100},[43,4867,4421],{},[43,4869,4870],{"class":45,"line":107},[43,4871,4872],{},"\u002F\u002F 1. Transplantasi\u002FGeser pemutar canvas poros ketengah sumbu object\n",[43,4874,4875],{"class":45,"line":113},[43,4876,4516],{},[43,4878,4879],{"class":45,"line":195},[43,4880,104],{"emptyLinePlaceholder":103},[43,4882,4883],{"class":45,"line":200},[43,4884,4885],{},"\u002F\u002F 2. Putar kemiringan putaran seluruh medan orientasi (harus wajib dihitung kedalam Radian)\n",[43,4887,4888],{"class":45,"line":206},[43,4889,4890],{},"ctx.rotate(hitunganRadian);\n",[43,4892,4893],{"class":45,"line":212},[43,4894,104],{"emptyLinePlaceholder":103},[43,4896,4897],{"class":45,"line":218},[43,4898,4899],{},"\u002F\u002F 3. Menggambar wujud ke poros koordinat (0,0) baru yg telah termiringkan tersebut\n",[43,4901,4902],{"class":45,"line":224},[43,4903,4544],{},[43,4905,4906],{"class":45,"line":618},[43,4907,4908],{},"\u002F\u002F Berukuran lebar & tinggi (50x50), nilai posisi digeser x minus setengah dan y minus setengah (-25) kebalikannya \n",[43,4910,4911],{"class":45,"line":914},[43,4912,4554],{},[43,4914,4915],{"class":45,"line":920},[43,4916,4559],{},[26,4918,4920,4921],{"id":4919},"_4-efek-balikcermin-flip-gambar-serta-membesarkan-resolusi-rasio-scaleflip-scalex-y","4. Efek Balik\u002FCermin Flip Gambar serta Membesarkan Resolusi Rasio (Scale\u002FFlip): ",[40,4922,4566],{},[22,4924,4925,4926,4928],{},"Metode pembesaran dimensi ini merombak pelebaran panjang dan lebar pembesaran seluruh grid sumbu dari pergesaran skala ukur (Angka defaultnya adalah 1.0). Kalau Anda mengetik code ",[40,4927,4575],{},", bentuk otomatis merentang ter-zoom pelebarannya serentak selebar dua(2) kali rentang panjang lipat.",[22,4930,4931],{},"Salah satu rahasia paling fundamental pada manipulasi grafis trik mem-balik (Flipping) Sprites karakter pemain\u002Fmusuh dalam sebuah Game Platformer maupun Sidescroll yang bergerak di Game Development (Berbalik hadap kiri\u002Fberubah posisi ke kanan belakang). Ternyata ini memakai kelemahan skala Minus (- 1)",[34,4933,4935],{"className":36,"code":4934,"language":38,"meta":16,"style":16},"ctx.save();\n\u002F\u002F 1. Ubah poros point 0,0 ke badan pusat rotasinya\nctx.translate(250, 200);\n\n\u002F\u002F 2. Trik Memutarlompati Sumbu Orientasi Pembalik arah Gambar dengan ratio cerminan horizontal menggunakan penolakan minus (x = -1). Dimensi Vertical sengaja kita blok ditahan konstan (y = 1)\nctx.scale(-1, 1);\n\n\u002F\u002F 3. Gambaran Objek baru  di terjemahan efek Flip tersebut\nctx.font = '30px Arial';\nctx.textAlign = 'center';\nctx.textBaseline = 'middle';\n\n\u002F\u002F Karena orientasi dunianya terbalik(dari titik pusat yg kita atur tadi) - Text Yang Dituliskanpun Akan Jadi Terbaca seperti Tulisan Kaca Terbalik!\nctx.fillText('TEKS TERBALIK MENGHADAP KE KIRI!', 0, 0); \nctx.restore(); \u002F\u002F Pemulihan sumbu dimensi kembali normal\n",[40,4936,4937,4941,4946,4950,4954,4959,4963,4967,4972,4976,4980,4984,4988,4993,4998],{"__ignoreMap":16},[43,4938,4939],{"class":45,"line":46},[43,4940,4421],{},[43,4942,4943],{"class":45,"line":52},[43,4944,4945],{},"\u002F\u002F 1. Ubah poros point 0,0 ke badan pusat rotasinya\n",[43,4947,4948],{"class":45,"line":94},[43,4949,4605],{},[43,4951,4952],{"class":45,"line":100},[43,4953,104],{"emptyLinePlaceholder":103},[43,4955,4956],{"class":45,"line":107},[43,4957,4958],{},"\u002F\u002F 2. Trik Memutarlompati Sumbu Orientasi Pembalik arah Gambar dengan ratio cerminan horizontal menggunakan penolakan minus (x = -1). Dimensi Vertical sengaja kita blok ditahan konstan (y = 1)\n",[43,4960,4961],{"class":45,"line":113},[43,4962,4619],{},[43,4964,4965],{"class":45,"line":195},[43,4966,104],{"emptyLinePlaceholder":103},[43,4968,4969],{"class":45,"line":200},[43,4970,4971],{},"\u002F\u002F 3. Gambaran Objek baru  di terjemahan efek Flip tersebut\n",[43,4973,4974],{"class":45,"line":206},[43,4975,4633],{},[43,4977,4978],{"class":45,"line":212},[43,4979,4638],{},[43,4981,4982],{"class":45,"line":218},[43,4983,4643],{},[43,4985,4986],{"class":45,"line":224},[43,4987,104],{"emptyLinePlaceholder":103},[43,4989,4990],{"class":45,"line":618},[43,4991,4992],{},"\u002F\u002F Karena orientasi dunianya terbalik(dari titik pusat yg kita atur tadi) - Text Yang Dituliskanpun Akan Jadi Terbaca seperti Tulisan Kaca Terbalik!\n",[43,4994,4995],{"class":45,"line":914},[43,4996,4997],{},"ctx.fillText('TEKS TERBALIK MENGHADAP KE KIRI!', 0, 0); \n",[43,4999,5000],{"class":45,"line":920},[43,5001,5002],{},"ctx.restore(); \u002F\u002F Pemulihan sumbu dimensi kembali normal\n",[690,5004,692],{},{"title":16,"searchDepth":52,"depth":52,"links":5006},[],{"title_en":4295,"title_id":4663,"description_en":5008,"description_id":5009,"date":699,"readingTime":3037},"Learn how to transform Canvas elements by changing the origin point using translate, rotating shapes, and flipping or scaling them.","Pelajari cara mengubah bentuk Canvas dengan memindahkan titik origin lewat translate, melakukan rotasi, sampai membalik\u002Fflip gambar.","\u002Fblog\u002Fcanvas-transformations",{"title":4295,"description":16},"blog\u002Fcanvas-transformations","U2PaHHe_C5_3R3IFmkH3bS4XZugeXeyBg-rDdRMd8F8",{"id":5015,"title":5016,"body":5017,"description":16,"extension":695,"meta":6463,"navigation":103,"path":6467,"seo":6468,"stem":6469,"__hash__":6470},"blog\u002Fblog\u002Fgame-pathfinding-algorithms.md","Game Pathfinding Explained: DFS, BFS, Dijkstra, and A*",{"type":8,"value":5018,"toc":6461},[5019,6459],[11,5020,5021,5771],{},[14,5022,5023,5026,5032,5035,5039,5054,5057,5061,5064,5098,5103,5192,5196,5199,5225,5229,5329,5333,5344,5372,5377,5515,5519,5526,5583,5588,5736,5740,5768],{"v-slot:en":16},[18,5024,5016],{"id":5025},"game-pathfinding-explained-dfs-bfs-dijkstra-and-a",[22,5027,5028,5029,552],{},"Have you ever wondered how enemies in a game navigate a complex maze to find the player, or how a strategy game unit figures out the best route around obstacles? The answer lies in ",[138,5030,5031],{},"Pathfinding algorithms",[22,5033,5034],{},"In this guide, we'll explore the four most common pathfinding algorithms used in game development, from the simplest to the industry standard.",[26,5036,5038],{"id":5037},"_1-the-world-as-a-graph-or-grid","1. The World as a Graph or Grid",[22,5040,5041,5042,5045,5046,5049,5050,5053],{},"Before an algorithm can find a path, it needs to understand the game world. Most games represent the walkable world as a ",[138,5043,5044],{},"Grid"," (squares, hexagons) or a ",[138,5047,5048],{},"NavMesh"," (polygons). These can all be boiled down to a ",[138,5051,5052],{},"Graph",": a series of points (nodes) connected by lines (edges).",[22,5055,5056],{},"Our goal is simple: find a sequence of nodes from the start point to the target point.",[26,5058,5060],{"id":5059},"_2-depth-first-search-dfs","2. Depth-First Search (DFS)",[22,5062,5063],{},"Imagine walking through a maze. You pick a path, follow it until you hit a dead end, backtrack to the last intersection, and try another path. This is exactly how Depth-First Search works.",[132,5065,5066,5072,5078,5088],{},[135,5067,5068,5071],{},[138,5069,5070],{},"How it works:"," It dives as \"deep\" as possible into the graph before backtracking.",[135,5073,5074,5077],{},[138,5075,5076],{},"Pros:"," Uses very little memory.",[135,5079,5080,5083,5084,5087],{},[138,5081,5082],{},"Cons:"," It does ",[138,5085,5086],{},"not"," guarantee the shortest path. In a wide open grid, DFS might take a wild zigzagging route hitting every corner before finding the target.",[135,5089,5090,5093,5094,5097],{},[138,5091,5092],{},"Best for:"," Procedural maze generation or checking if a path ",[1258,5095,5096],{},"exists",", but terrible for actual character movement.",[22,5099,5100],{},[138,5101,5102],{},"Example Code (Grid-based):",[34,5104,5106],{"className":36,"code":5105,"language":38,"meta":16,"style":16},"function dfs_grid(grid, start, target) {\n  let stack = [start];\n  let visited = new Set();\n  \n  while (stack.length > 0) {\n    let current = stack.pop(); \u002F\u002F Pop from top (LIFO)\n    let key = `${current.x},${current.y}`;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Found\n    \n    if (!visited.has(key)) {\n      visited.add(key);\n      let neighbors = getNeighbors(grid, current); \u002F\u002F e.g., up, down, left, right\n      for (let neighbor of neighbors) stack.push(neighbor);\n    }\n  }\n  return false;\n}\n",[40,5107,5108,5113,5118,5123,5127,5132,5137,5142,5146,5151,5155,5160,5165,5170,5175,5179,5183,5188],{"__ignoreMap":16},[43,5109,5110],{"class":45,"line":46},[43,5111,5112],{},"function dfs_grid(grid, start, target) {\n",[43,5114,5115],{"class":45,"line":52},[43,5116,5117],{},"  let stack = [start];\n",[43,5119,5120],{"class":45,"line":94},[43,5121,5122],{},"  let visited = new Set();\n",[43,5124,5125],{"class":45,"line":100},[43,5126,1159],{},[43,5128,5129],{"class":45,"line":107},[43,5130,5131],{},"  while (stack.length > 0) {\n",[43,5133,5134],{"class":45,"line":113},[43,5135,5136],{},"    let current = stack.pop(); \u002F\u002F Pop from top (LIFO)\n",[43,5138,5139],{"class":45,"line":195},[43,5140,5141],{},"    let key = `${current.x},${current.y}`;\n",[43,5143,5144],{"class":45,"line":200},[43,5145,882],{},[43,5147,5148],{"class":45,"line":206},[43,5149,5150],{},"    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Found\n",[43,5152,5153],{"class":45,"line":212},[43,5154,882],{},[43,5156,5157],{"class":45,"line":218},[43,5158,5159],{},"    if (!visited.has(key)) {\n",[43,5161,5162],{"class":45,"line":224},[43,5163,5164],{},"      visited.add(key);\n",[43,5166,5167],{"class":45,"line":618},[43,5168,5169],{},"      let neighbors = getNeighbors(grid, current); \u002F\u002F e.g., up, down, left, right\n",[43,5171,5172],{"class":45,"line":914},[43,5173,5174],{},"      for (let neighbor of neighbors) stack.push(neighbor);\n",[43,5176,5177],{"class":45,"line":920},[43,5178,1080],{},[43,5180,5181],{"class":45,"line":926},[43,5182,929],{},[43,5184,5185],{"class":45,"line":932},[43,5186,5187],{},"  return false;\n",[43,5189,5190],{"class":45,"line":937},[43,5191,809],{},[26,5193,5195],{"id":5194},"_3-breadth-first-search-bfs","3. Breadth-First Search (BFS)",[22,5197,5198],{},"Unlike DFS, BFS explores the world symmetrically. Imagine dropping a stone in a pond; the ripples expand outwards in all directions equally.",[132,5200,5201,5206,5215,5220],{},[135,5202,5203,5205],{},[138,5204,5070],{}," It checks all immediate neighbors first, then the neighbors of those neighbors, expanding outwards step by step.",[135,5207,5208,5210,5211,5214],{},[138,5209,5076],{}," It ",[138,5212,5213],{},"guarantees"," the shortest path, provided every step has the same cost (unweighted graph).",[135,5216,5217,5219],{},[138,5218,5082],{}," It explores everywhere in all directions. It doesn't care where the target is until it accidentally bumps into it. Thus, it is slow and wastes memory.",[135,5221,5222,5224],{},[138,5223,5092],{}," Finding the shortest path on a simple, flat grid where every tile takes the same effort to walk on (e.g., standard tile-based puzzle games).",[22,5226,5227],{},[138,5228,5102],{},[34,5230,5232],{"className":36,"code":5231,"language":38,"meta":16,"style":16},"function bfs_grid(grid, start, target) {\n  let queue = [start];\n  let visited = new Set();\n  visited.add(`${start.x},${start.y}`);\n  \n  while (queue.length > 0) {\n    let current = queue.shift(); \u002F\u002F Remove from front (FIFO)\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Found\n    \n    let neighbors = getNeighbors(grid, current);\n    for (let neighbor of neighbors) {\n      let key = `${neighbor.x},${neighbor.y}`;\n      if (!visited.has(key)) {\n        visited.add(key);\n        queue.push(neighbor);\n      }\n    }\n  }\n  return false;\n}\n",[40,5233,5234,5239,5244,5248,5253,5257,5262,5267,5271,5275,5279,5284,5289,5294,5299,5304,5309,5313,5317,5321,5325],{"__ignoreMap":16},[43,5235,5236],{"class":45,"line":46},[43,5237,5238],{},"function bfs_grid(grid, start, target) {\n",[43,5240,5241],{"class":45,"line":52},[43,5242,5243],{},"  let queue = [start];\n",[43,5245,5246],{"class":45,"line":94},[43,5247,5122],{},[43,5249,5250],{"class":45,"line":100},[43,5251,5252],{},"  visited.add(`${start.x},${start.y}`);\n",[43,5254,5255],{"class":45,"line":107},[43,5256,1159],{},[43,5258,5259],{"class":45,"line":113},[43,5260,5261],{},"  while (queue.length > 0) {\n",[43,5263,5264],{"class":45,"line":195},[43,5265,5266],{},"    let current = queue.shift(); \u002F\u002F Remove from front (FIFO)\n",[43,5268,5269],{"class":45,"line":200},[43,5270,882],{},[43,5272,5273],{"class":45,"line":206},[43,5274,5150],{},[43,5276,5277],{"class":45,"line":212},[43,5278,882],{},[43,5280,5281],{"class":45,"line":218},[43,5282,5283],{},"    let neighbors = getNeighbors(grid, current);\n",[43,5285,5286],{"class":45,"line":224},[43,5287,5288],{},"    for (let neighbor of neighbors) {\n",[43,5290,5291],{"class":45,"line":618},[43,5292,5293],{},"      let key = `${neighbor.x},${neighbor.y}`;\n",[43,5295,5296],{"class":45,"line":914},[43,5297,5298],{},"      if (!visited.has(key)) {\n",[43,5300,5301],{"class":45,"line":920},[43,5302,5303],{},"        visited.add(key);\n",[43,5305,5306],{"class":45,"line":926},[43,5307,5308],{},"        queue.push(neighbor);\n",[43,5310,5311],{"class":45,"line":932},[43,5312,1074],{},[43,5314,5315],{"class":45,"line":937},[43,5316,1080],{},[43,5318,5319],{"class":45,"line":943},[43,5320,929],{},[43,5322,5323],{"class":45,"line":949},[43,5324,5187],{},[43,5326,5327],{"class":45,"line":955},[43,5328,809],{},[26,5330,5332],{"id":5331},"_4-dijkstras-algorithm","4. Dijkstra's Algorithm",[22,5334,5335,5336,5339,5340,5343],{},"What if the world isn't flat? What if walking through a swamp takes twice as much energy as walking on a road? BFS assumes every step costs the same (cost = 1). ",[138,5337,5338],{},"Dijkstra's Algorithm"," fixes this by introducing ",[138,5341,5342],{},"movement costs"," (weighted graphs).",[132,5345,5346,5351,5358,5363],{},[135,5347,5348,5350],{},[138,5349,5070],{}," It keeps track of the \"accumulated cost\" from the start node to all explored nodes. It always prioritizes exploring the next node with the lowest accumulated cost.",[135,5352,5353,5210,5355,5357],{},[138,5354,5076],{},[138,5356,5213],{}," the lowest-cost (shortest or easiest) path in any world with varied terrain (mountains, roads, water).",[135,5359,5360,5362],{},[138,5361,5082],{}," Like BFS, it expands in all directions (favoring the easy terrain first). It doesn't \"aim\" toward the target. In a large map, this is computationally expensive.",[135,5364,5365,5367,5368,5371],{},[138,5366,5092],{}," Strategy games with varied terrain types, but only if the target's location isn't known or if calculating routes to ",[1258,5369,5370],{},"many"," targets at once.",[22,5373,5374],{},[138,5375,5376],{},"Example Code (Grid-based with varied weights):",[34,5378,5380],{"className":36,"code":5379,"language":38,"meta":16,"style":16},"function dijkstra_grid(grid, start, target) {\n  \u002F\u002F Using an array and sorting it to simulate a Priority Queue (Min-Heap)\n  let pq = [{ node: start, cost: 0 }];\n  let costs = {};\n  costs[`${start.x},${start.y}`] = 0;\n  \n  while (pq.length > 0) {\n    pq.sort((a, b) => a.cost - b.cost); \u002F\u002F Always process cheapest cost node first\n    let current = pq.shift().node;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Found\n    \n    let currentCost = costs[`${current.x},${current.y}`];\n    let neighbors = getNeighbors(grid, current);\n    \n    for (let item of neighbors) { \u002F\u002F item contains .node and .weight (terrain difficulty)\n      let nextNode = item.node;\n      let newCost = currentCost + item.weight; \n      let key = `${nextNode.x},${nextNode.y}`;\n      \n      \u002F\u002F If unvisited or a cheaper path is found\n      if (!(key in costs) || newCost \u003C costs[key]) {\n        costs[key] = newCost;\n        pq.push({ node: nextNode, cost: newCost });\n      }\n    }\n  }\n  return false;\n}\n",[40,5381,5382,5387,5392,5397,5402,5407,5411,5416,5421,5426,5430,5434,5438,5443,5447,5451,5456,5461,5466,5471,5475,5480,5485,5490,5495,5499,5503,5507,5511],{"__ignoreMap":16},[43,5383,5384],{"class":45,"line":46},[43,5385,5386],{},"function dijkstra_grid(grid, start, target) {\n",[43,5388,5389],{"class":45,"line":52},[43,5390,5391],{},"  \u002F\u002F Using an array and sorting it to simulate a Priority Queue (Min-Heap)\n",[43,5393,5394],{"class":45,"line":94},[43,5395,5396],{},"  let pq = [{ node: start, cost: 0 }];\n",[43,5398,5399],{"class":45,"line":100},[43,5400,5401],{},"  let costs = {};\n",[43,5403,5404],{"class":45,"line":107},[43,5405,5406],{},"  costs[`${start.x},${start.y}`] = 0;\n",[43,5408,5409],{"class":45,"line":113},[43,5410,1159],{},[43,5412,5413],{"class":45,"line":195},[43,5414,5415],{},"  while (pq.length > 0) {\n",[43,5417,5418],{"class":45,"line":200},[43,5419,5420],{},"    pq.sort((a, b) => a.cost - b.cost); \u002F\u002F Always process cheapest cost node first\n",[43,5422,5423],{"class":45,"line":206},[43,5424,5425],{},"    let current = pq.shift().node;\n",[43,5427,5428],{"class":45,"line":212},[43,5429,882],{},[43,5431,5432],{"class":45,"line":218},[43,5433,5150],{},[43,5435,5436],{"class":45,"line":224},[43,5437,882],{},[43,5439,5440],{"class":45,"line":618},[43,5441,5442],{},"    let currentCost = costs[`${current.x},${current.y}`];\n",[43,5444,5445],{"class":45,"line":914},[43,5446,5283],{},[43,5448,5449],{"class":45,"line":920},[43,5450,882],{},[43,5452,5453],{"class":45,"line":926},[43,5454,5455],{},"    for (let item of neighbors) { \u002F\u002F item contains .node and .weight (terrain difficulty)\n",[43,5457,5458],{"class":45,"line":932},[43,5459,5460],{},"      let nextNode = item.node;\n",[43,5462,5463],{"class":45,"line":937},[43,5464,5465],{},"      let newCost = currentCost + item.weight; \n",[43,5467,5468],{"class":45,"line":943},[43,5469,5470],{},"      let key = `${nextNode.x},${nextNode.y}`;\n",[43,5472,5473],{"class":45,"line":949},[43,5474,1038],{},[43,5476,5477],{"class":45,"line":955},[43,5478,5479],{},"      \u002F\u002F If unvisited or a cheaper path is found\n",[43,5481,5482],{"class":45,"line":960},[43,5483,5484],{},"      if (!(key in costs) || newCost \u003C costs[key]) {\n",[43,5486,5487],{"class":45,"line":966},[43,5488,5489],{},"        costs[key] = newCost;\n",[43,5491,5492],{"class":45,"line":972},[43,5493,5494],{},"        pq.push({ node: nextNode, cost: newCost });\n",[43,5496,5497],{"class":45,"line":977},[43,5498,1074],{},[43,5500,5501],{"class":45,"line":983},[43,5502,1080],{},[43,5504,5505],{"class":45,"line":989},[43,5506,929],{},[43,5508,5509],{"class":45,"line":994},[43,5510,5187],{},[43,5512,5513],{"class":45,"line":1000},[43,5514,809],{},[26,5516,5518],{"id":5517},"_5-a-a-star-search-algorithm","5. A* (A-Star) Search Algorithm",[22,5520,5521,5522,5525],{},"Dijkstra is smart, but it's blind to the target. ",[138,5523,5524],{},"A","* takes Dijkstra's Algorithm and gives it a compass. This is the undisputed king of pathfinding in the game industry.",[132,5527,5528,5564,5573,5578],{},[135,5529,5530,5532,5533,5536,5537,5540],{},[138,5531,5070],{}," A* uses a ",[138,5534,5535],{},"heuristic"," (a smart guess) to estimate the distance remaining from a node to the goal.\nThe algorithm evaluates nodes based on the formula: ",[40,5538,5539],{},"F = G + H",[132,5541,5542,5548,5554],{},[135,5543,5544,5547],{},[138,5545,5546],{},"G:"," The exact cost from the start to the current node (like Dijkstra).",[135,5549,5550,5553],{},[138,5551,5552],{},"H (Heuristic):"," The estimated cost from the current node to the end target.",[135,5555,5556,5559,5560,5563],{},[138,5557,5558],{},"F:"," The total score. A* always explores the node with the lowest ",[40,5561,5562],{},"F"," first.",[135,5565,5566,5568,5569,5572],{},[138,5567,5076],{}," It is incredibly fast because it actively stretches ",[1258,5570,5571],{},"towards"," the target, ignoring paths that obviously go in the wrong direction. It guarantees the shortest path (if the heuristic is well-designed).",[135,5574,5575,5577],{},[138,5576,5082],{}," Can consume a lot of memory on massive maps, requiring optimizations like NavMeshes or Hierarchical Pathfinding.",[135,5579,5580,5582],{},[138,5581,5092],{}," Almost every modern game requiring intelligent point-A to point-B movement.",[22,5584,5585],{},[138,5586,5587],{},"Example Code (Grid-based with Heuristic):",[34,5589,5591],{"className":36,"code":5590,"language":38,"meta":16,"style":16},"\u002F\u002F Manhattan distance heuristic for 4-directional grid movement\nfunction heuristic(nodeA, nodeB) {\n  return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);\n}\n\nfunction a_star_grid(grid, start, target) {\n  let pq = [{ node: start, fCost: 0 }];\n  let gCosts = {}; \u002F\u002F Cost from start\n  gCosts[`${start.x},${start.y}`] = 0;\n  \n  while (pq.length > 0) {\n    pq.sort((a, b) => a.fCost - b.fCost); \u002F\u002F Explore lowest F-cost first\n    let current = pq.shift().node;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Found\n    \n    let neighbors = getNeighbors(grid, current);\n    for (let item of neighbors) {\n      let nextNode = item.node;\n      let newGCost = gCosts[`${current.x},${current.y}`] + item.weight; \n      let key = `${nextNode.x},${nextNode.y}`;\n      \n      if (!(key in gCosts) || newGCost \u003C gCosts[key]) {\n        gCosts[key] = newGCost;\n        \u002F\u002F F = G + H\n        let fCost = newGCost + heuristic(nextNode, target); \n        pq.push({ node: nextNode, fCost: fCost });\n      }\n    }\n  }\n  return false;\n}\n",[40,5592,5593,5598,5603,5608,5612,5616,5621,5626,5631,5636,5640,5644,5649,5653,5657,5661,5665,5669,5674,5678,5683,5687,5691,5696,5701,5706,5711,5716,5720,5724,5728,5732],{"__ignoreMap":16},[43,5594,5595],{"class":45,"line":46},[43,5596,5597],{},"\u002F\u002F Manhattan distance heuristic for 4-directional grid movement\n",[43,5599,5600],{"class":45,"line":52},[43,5601,5602],{},"function heuristic(nodeA, nodeB) {\n",[43,5604,5605],{"class":45,"line":94},[43,5606,5607],{},"  return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);\n",[43,5609,5610],{"class":45,"line":100},[43,5611,809],{},[43,5613,5614],{"class":45,"line":107},[43,5615,104],{"emptyLinePlaceholder":103},[43,5617,5618],{"class":45,"line":113},[43,5619,5620],{},"function a_star_grid(grid, start, target) {\n",[43,5622,5623],{"class":45,"line":195},[43,5624,5625],{},"  let pq = [{ node: start, fCost: 0 }];\n",[43,5627,5628],{"class":45,"line":200},[43,5629,5630],{},"  let gCosts = {}; \u002F\u002F Cost from start\n",[43,5632,5633],{"class":45,"line":206},[43,5634,5635],{},"  gCosts[`${start.x},${start.y}`] = 0;\n",[43,5637,5638],{"class":45,"line":212},[43,5639,1159],{},[43,5641,5642],{"class":45,"line":218},[43,5643,5415],{},[43,5645,5646],{"class":45,"line":224},[43,5647,5648],{},"    pq.sort((a, b) => a.fCost - b.fCost); \u002F\u002F Explore lowest F-cost first\n",[43,5650,5651],{"class":45,"line":618},[43,5652,5425],{},[43,5654,5655],{"class":45,"line":914},[43,5656,882],{},[43,5658,5659],{"class":45,"line":920},[43,5660,5150],{},[43,5662,5663],{"class":45,"line":926},[43,5664,882],{},[43,5666,5667],{"class":45,"line":932},[43,5668,5283],{},[43,5670,5671],{"class":45,"line":937},[43,5672,5673],{},"    for (let item of neighbors) {\n",[43,5675,5676],{"class":45,"line":943},[43,5677,5460],{},[43,5679,5680],{"class":45,"line":949},[43,5681,5682],{},"      let newGCost = gCosts[`${current.x},${current.y}`] + item.weight; \n",[43,5684,5685],{"class":45,"line":955},[43,5686,5470],{},[43,5688,5689],{"class":45,"line":960},[43,5690,1038],{},[43,5692,5693],{"class":45,"line":966},[43,5694,5695],{},"      if (!(key in gCosts) || newGCost \u003C gCosts[key]) {\n",[43,5697,5698],{"class":45,"line":972},[43,5699,5700],{},"        gCosts[key] = newGCost;\n",[43,5702,5703],{"class":45,"line":977},[43,5704,5705],{},"        \u002F\u002F F = G + H\n",[43,5707,5708],{"class":45,"line":983},[43,5709,5710],{},"        let fCost = newGCost + heuristic(nextNode, target); \n",[43,5712,5713],{"class":45,"line":989},[43,5714,5715],{},"        pq.push({ node: nextNode, fCost: fCost });\n",[43,5717,5718],{"class":45,"line":994},[43,5719,1074],{},[43,5721,5722],{"class":45,"line":1000},[43,5723,1080],{},[43,5725,5726],{"class":45,"line":1006},[43,5727,929],{},[43,5729,5730],{"class":45,"line":1012},[43,5731,5187],{},[43,5733,5734],{"class":45,"line":1017},[43,5735,809],{},[26,5737,5739],{"id":5738},"summary","Summary",[132,5741,5742,5748,5754,5760],{},[135,5743,5744,5747],{},[138,5745,5746],{},"DFS:"," Wanders aimlessly until it bumps into the goal. Bad for pathfinding.",[135,5749,5750,5753],{},[138,5751,5752],{},"BFS:"," Expands equally in all directions. Finds shortest path in unweighted grids.",[135,5755,5756,5759],{},[138,5757,5758],{},"Dijkstra:"," Expands favoring easier terrain. Finds lowest-cost path in weighted grids.",[135,5761,5762,5767],{},[1258,5763,5764,5766],{},[1258,5765,5524],{},":","* Explores smartly toward the target. The industry standard for efficient pathfinding.",[22,5769,5770],{},"Understanding these concepts will drastically improve how you approach AI movement in your games!",[14,5772,5773,5777,5783,5786,5790,5802,5805,5808,5811,5845,5850,5930,5933,5936,5962,5966,6056,6060,6071,6100,6105,6230,6234,6240,6287,6292,6428,6432,6456],{"v-slot:id":16},[18,5774,5776],{"id":5775},"memahami-pathfinding-game-dfs-bfs-dijkstra-dan-a","Memahami Pathfinding Game: DFS, BFS, Dijkstra, dan A*",[22,5778,5779,5780,552],{},"Pernahkah Anda bertanya-tanya bagaimana musuh dalam game bisa menavigasi labirin yang rumit untuk menemukan pemain, atau bagaimana unit permainan strategi mencari rute terbaik untuk menghindari rintangan? Jawabannya ada pada ",[138,5781,5782],{},"Algoritma Pathfinding",[22,5784,5785],{},"Dalam panduan ini, kita akan menjelajahi empat algoritma pathfinding paling umum yang digunakan dalam pengembangan game, dari yang paling sederhana hingga standar industri saat ini.",[26,5787,5789],{"id":5788},"_1-dunia-sebagai-graph-atau-grid","1. Dunia Sebagai Graph atau Grid",[22,5791,5792,5793,5795,5796,5798,5799,5801],{},"Sebelum sebuah algoritma dapat menemukan jalur, algoritma tersebut harus memahami simulasi dunia game. Sebagian besar game merepresentasikan area yang bisa dipijak sebagai ",[138,5794,5044],{}," (kotak-kotak, heksagon) atau ",[138,5797,5048],{}," (poligon). Semua ini pada dasarnya bisa disederhanakan menjadi ",[138,5800,5052],{}," (Graf): sekumpulan titik (node) yang dihubungkan oleh garis (edge).",[22,5803,5804],{},"Tujuan utama kita sangat jelas: menemukan urutan titik dari titik awal (start) menuju titik tujuan (target).",[26,5806,5060],{"id":5807},"_2-depth-first-search-dfs-1",[22,5809,5810],{},"Bayangkan Anda sedang berjalan di dalam labirin. Anda memilih satu lorong, menelusurinya terus sampai menemui jalan buntu, lalu kembali ke persimpangan terakhir dan mencoba lorong lain. Ini adalah cara kerja DFS secara harfiah.",[132,5812,5813,5819,5825,5835],{},[135,5814,5815,5818],{},[138,5816,5817],{},"Cara kerja:"," Menjelajah \"sedalam\" mungkin ke dalam rute graf sebelum akhirnya mundur (backtracking) jika menemui kegagalan.",[135,5820,5821,5824],{},[138,5822,5823],{},"Kelebihan:"," Menggunakan sangat sedikit memori.",[135,5826,5827,5830,5831,5834],{},[138,5828,5829],{},"Kekurangan:"," Algoritma ini ",[138,5832,5833],{},"tidak"," menjamin rute terpendek. Pada ruang terbuka, DFS bisa mengambil jalur zig-zag ekstrem ke seluruh penjuru ruangan sebelum menemukan targetnya.",[135,5836,5837,5840,5841,5844],{},[138,5838,5839],{},"Cocok untuk:"," Pembuatan labirin acak (procedural generation) atau memeriksa apakah sebuah ",[1258,5842,5843],{},"jalur itu ada",", tetapi amat sangat buruk untuk pergerakan karakter.",[22,5846,5847],{},[138,5848,5849],{},"Contoh Kode (Berbasis Grid):",[34,5851,5853],{"className":36,"code":5852,"language":38,"meta":16,"style":16},"function dfs_grid(grid, start, target) {\n  let stack = [start];\n  let visited = new Set();\n  \n  while (stack.length > 0) {\n    let current = stack.pop(); \u002F\u002F Ambil dari urutan paling akhir (LIFO)\n    let key = `${current.x},${current.y}`;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Ketemu\n    \n    if (!visited.has(key)) {\n      visited.add(key);\n      let neighbors = getNeighbors(grid, current); \u002F\u002F cth: atas, bawah, kiri, kanan\n      for (let neighbor of neighbors) stack.push(neighbor);\n    }\n  }\n  return false;\n}\n",[40,5854,5855,5859,5863,5867,5871,5875,5880,5884,5888,5893,5897,5901,5905,5910,5914,5918,5922,5926],{"__ignoreMap":16},[43,5856,5857],{"class":45,"line":46},[43,5858,5112],{},[43,5860,5861],{"class":45,"line":52},[43,5862,5117],{},[43,5864,5865],{"class":45,"line":94},[43,5866,5122],{},[43,5868,5869],{"class":45,"line":100},[43,5870,1159],{},[43,5872,5873],{"class":45,"line":107},[43,5874,5131],{},[43,5876,5877],{"class":45,"line":113},[43,5878,5879],{},"    let current = stack.pop(); \u002F\u002F Ambil dari urutan paling akhir (LIFO)\n",[43,5881,5882],{"class":45,"line":195},[43,5883,5141],{},[43,5885,5886],{"class":45,"line":200},[43,5887,882],{},[43,5889,5890],{"class":45,"line":206},[43,5891,5892],{},"    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Ketemu\n",[43,5894,5895],{"class":45,"line":212},[43,5896,882],{},[43,5898,5899],{"class":45,"line":218},[43,5900,5159],{},[43,5902,5903],{"class":45,"line":224},[43,5904,5164],{},[43,5906,5907],{"class":45,"line":618},[43,5908,5909],{},"      let neighbors = getNeighbors(grid, current); \u002F\u002F cth: atas, bawah, kiri, kanan\n",[43,5911,5912],{"class":45,"line":914},[43,5913,5174],{},[43,5915,5916],{"class":45,"line":920},[43,5917,1080],{},[43,5919,5920],{"class":45,"line":926},[43,5921,929],{},[43,5923,5924],{"class":45,"line":932},[43,5925,5187],{},[43,5927,5928],{"class":45,"line":937},[43,5929,809],{},[26,5931,5195],{"id":5932},"_3-breadth-first-search-bfs-1",[22,5934,5935],{},"Berbeda dengan DFS, BFS menjelajahi dunia secara merata. Bayangkan Anda menjatuhkan batu ke dalam kolam; riaknya melebar ke segala arah secara bersamaan.",[132,5937,5938,5943,5952,5957],{},[135,5939,5940,5942],{},[138,5941,5817],{}," Algoritma memeriksa semua area sekitarnya terlebih dahulu (radius 1 langkah), kemudian area di sekitar area tersebut (radius 2 langkah), dan terus melebar selangkah demi selangkah.",[135,5944,5945,5947,5948,5951],{},[138,5946,5823],{}," Secara otomatis ",[138,5949,5950],{},"menjamin"," rute terpendek, dengan syarat setiap langkah punya bobot kesulitan yang sama (unweighted graph).",[135,5953,5954,5956],{},[138,5955,5829],{}," BFS menjelajah ke segala penjuru arah. Ia tidak peduli di mana target berada hingga secara tidak sengaja \"menabrak\" target tersebut. Karena itu, kinerjanya lambat memproses banyak petak dan boros memori.",[135,5958,5959,5961],{},[138,5960,5839],{}," Mencari jalur terpendek pada grid kotak yang polos, di mana setiap area memakan usaha langkah yang sama (misalkan game puzzle tile base sederhana).",[22,5963,5964],{},[138,5965,5849],{},[34,5967,5969],{"className":36,"code":5968,"language":38,"meta":16,"style":16},"function bfs_grid(grid, start, target) {\n  let queue = [start];\n  let visited = new Set();\n  visited.add(`${start.x},${start.y}`);\n  \n  while (queue.length > 0) {\n    let current = queue.shift(); \u002F\u002F Ambil dari urutan paling awal (FIFO)\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Ketemu\n    \n    let neighbors = getNeighbors(grid, current);\n    for (let neighbor of neighbors) {\n      let key = `${neighbor.x},${neighbor.y}`;\n      if (!visited.has(key)) {\n        visited.add(key);\n        queue.push(neighbor);\n      }\n    }\n  }\n  return false;\n}\n",[40,5970,5971,5975,5979,5983,5987,5991,5995,6000,6004,6008,6012,6016,6020,6024,6028,6032,6036,6040,6044,6048,6052],{"__ignoreMap":16},[43,5972,5973],{"class":45,"line":46},[43,5974,5238],{},[43,5976,5977],{"class":45,"line":52},[43,5978,5243],{},[43,5980,5981],{"class":45,"line":94},[43,5982,5122],{},[43,5984,5985],{"class":45,"line":100},[43,5986,5252],{},[43,5988,5989],{"class":45,"line":107},[43,5990,1159],{},[43,5992,5993],{"class":45,"line":113},[43,5994,5261],{},[43,5996,5997],{"class":45,"line":195},[43,5998,5999],{},"    let current = queue.shift(); \u002F\u002F Ambil dari urutan paling awal (FIFO)\n",[43,6001,6002],{"class":45,"line":200},[43,6003,882],{},[43,6005,6006],{"class":45,"line":206},[43,6007,5892],{},[43,6009,6010],{"class":45,"line":212},[43,6011,882],{},[43,6013,6014],{"class":45,"line":218},[43,6015,5283],{},[43,6017,6018],{"class":45,"line":224},[43,6019,5288],{},[43,6021,6022],{"class":45,"line":618},[43,6023,5293],{},[43,6025,6026],{"class":45,"line":914},[43,6027,5298],{},[43,6029,6030],{"class":45,"line":920},[43,6031,5303],{},[43,6033,6034],{"class":45,"line":926},[43,6035,5308],{},[43,6037,6038],{"class":45,"line":932},[43,6039,1074],{},[43,6041,6042],{"class":45,"line":937},[43,6043,1080],{},[43,6045,6046],{"class":45,"line":943},[43,6047,929],{},[43,6049,6050],{"class":45,"line":949},[43,6051,5187],{},[43,6053,6054],{"class":45,"line":955},[43,6055,809],{},[26,6057,6059],{"id":6058},"_4-algoritma-dijkstra","4. Algoritma Dijkstra",[22,6061,6062,6063,6066,6067,6070],{},"Bagaimana jika dunia tidak sepenuhnya rata? Bagaimana bila berjalan melewati rawa membutuhkan waktu dua kali lipat dibanding berjalan di aspal? BFS mengasumsikan setiap langkah berharga sama (cost = 1). ",[138,6064,6065],{},"Algoritma Dijkstra"," memperbaiki kelemahan ini dengan memperkenalkan ",[138,6068,6069],{},"movement cost"," (bobot graf\u002Fweighted graph).",[132,6072,6073,6078,6086,6091],{},[135,6074,6075,6077],{},[138,6076,5817],{}," Melacak \"akumulasi biaya perjalanan\" dari titik awal ke semua titik yang sedang dijelajahi. Dijkstra akan selalu memprioritaskan penjelajahan ke titik berikutnya yang memiliki total poin\u002Fbiaya perjalanan terendah.",[135,6079,6080,6082,6083,6085],{},[138,6081,5823],{}," Dijkstra ",[138,6084,5950],{}," jalur dengan rintangan\u002Fbiaya termurah (terpendek atau termudah) di peta dengan medan yang bervariasi (gunung, jalan raya, air).",[135,6087,6088,6090],{},[138,6089,5829],{}," Sama seperti BFS, Dijkstra melebar secara radial (walau ia akan lebih dulu menjelajah daratan yang mudah). Ia tidak \"membidik\" ke arah tujuan. Pada ukuran map yang masif, melakukan ini terlalu intensif secara komputasi.",[135,6092,6093,6095,6096,6099],{},[138,6094,5839],{}," Permainan strategi (RTS) dengan banyak jenis medan, tapi hanya efisien bila posisi target tidak diketahui pasti atau ketika mencari rute ke ",[1258,6097,6098],{},"banyak"," objek sekaligus.",[22,6101,6102],{},[138,6103,6104],{},"Contoh Kode (Berbasis Grid dengan bobot bervariasi):",[34,6106,6108],{"className":36,"code":6107,"language":38,"meta":16,"style":16},"function dijkstra_grid(grid, start, target) {\n  \u002F\u002F Menggunakan array yang disortir untuk mensimulasikan Priority Queue (Min-Heap)\n  let pq = [{ node: start, cost: 0 }];\n  let costs = {};\n  costs[`${start.x},${start.y}`] = 0;\n  \n  while (pq.length > 0) {\n    pq.sort((a, b) => a.cost - b.cost); \u002F\u002F Proses jarak\u002Fbiaya termurah lebih dulu\n    let current = pq.shift().node;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Ketemu\n    \n    let currentCost = costs[`${current.x},${current.y}`];\n    let neighbors = getNeighbors(grid, current);\n    \n    for (let item of neighbors) { \u002F\u002F item berisi .node dan .weight (kesulitan medan)\n      let nextNode = item.node;\n      let newCost = currentCost + item.weight; \n      let key = `${nextNode.x},${nextNode.y}`;\n      \n      \u002F\u002F Jika belum dikunjungi atau menemukan rute yang lebih murah\n      if (!(key in costs) || newCost \u003C costs[key]) {\n        costs[key] = newCost;\n        pq.push({ node: nextNode, cost: newCost });\n      }\n    }\n  }\n  return false;\n}\n",[40,6109,6110,6114,6119,6123,6127,6131,6135,6139,6144,6148,6152,6156,6160,6164,6168,6172,6177,6181,6185,6189,6193,6198,6202,6206,6210,6214,6218,6222,6226],{"__ignoreMap":16},[43,6111,6112],{"class":45,"line":46},[43,6113,5386],{},[43,6115,6116],{"class":45,"line":52},[43,6117,6118],{},"  \u002F\u002F Menggunakan array yang disortir untuk mensimulasikan Priority Queue (Min-Heap)\n",[43,6120,6121],{"class":45,"line":94},[43,6122,5396],{},[43,6124,6125],{"class":45,"line":100},[43,6126,5401],{},[43,6128,6129],{"class":45,"line":107},[43,6130,5406],{},[43,6132,6133],{"class":45,"line":113},[43,6134,1159],{},[43,6136,6137],{"class":45,"line":195},[43,6138,5415],{},[43,6140,6141],{"class":45,"line":200},[43,6142,6143],{},"    pq.sort((a, b) => a.cost - b.cost); \u002F\u002F Proses jarak\u002Fbiaya termurah lebih dulu\n",[43,6145,6146],{"class":45,"line":206},[43,6147,5425],{},[43,6149,6150],{"class":45,"line":212},[43,6151,882],{},[43,6153,6154],{"class":45,"line":218},[43,6155,5892],{},[43,6157,6158],{"class":45,"line":224},[43,6159,882],{},[43,6161,6162],{"class":45,"line":618},[43,6163,5442],{},[43,6165,6166],{"class":45,"line":914},[43,6167,5283],{},[43,6169,6170],{"class":45,"line":920},[43,6171,882],{},[43,6173,6174],{"class":45,"line":926},[43,6175,6176],{},"    for (let item of neighbors) { \u002F\u002F item berisi .node dan .weight (kesulitan medan)\n",[43,6178,6179],{"class":45,"line":932},[43,6180,5460],{},[43,6182,6183],{"class":45,"line":937},[43,6184,5465],{},[43,6186,6187],{"class":45,"line":943},[43,6188,5470],{},[43,6190,6191],{"class":45,"line":949},[43,6192,1038],{},[43,6194,6195],{"class":45,"line":955},[43,6196,6197],{},"      \u002F\u002F Jika belum dikunjungi atau menemukan rute yang lebih murah\n",[43,6199,6200],{"class":45,"line":960},[43,6201,5484],{},[43,6203,6204],{"class":45,"line":966},[43,6205,5489],{},[43,6207,6208],{"class":45,"line":972},[43,6209,5494],{},[43,6211,6212],{"class":45,"line":977},[43,6213,1074],{},[43,6215,6216],{"class":45,"line":983},[43,6217,1080],{},[43,6219,6220],{"class":45,"line":989},[43,6221,929],{},[43,6223,6224],{"class":45,"line":994},[43,6225,5187],{},[43,6227,6228],{"class":45,"line":1000},[43,6229,809],{},[26,6231,6233],{"id":6232},"_5-algoritma-a-a-star","5. Algoritma A* (A-Star)",[22,6235,6236,6237,6239],{},"Dijkstra cukup pintar, tapi dia buta arah terhadap tujuan akhir. ",[138,6238,5524],{},"* lahir dengan cara mengambil pondasi dasar Dijkstra lalu memberikannya semacam \"kompas\". Algoritma Inilah raja pathfinding yang tak terbantahkan dalam industri game.",[132,6241,6242,6272,6277,6282],{},[135,6243,6244,6246,6247,6249,6250,6252],{},[138,6245,5817],{}," A* menggunakan fitur yang disebut ",[138,6248,5535],{}," (perkiraan logis yang cerdas) untuk memperkirakan sisa jarak dari titik saat ini ke garis finish.\nA* mengevaluasi wilayah berdasarkan rumus: ",[40,6251,5539],{},[132,6253,6254,6259,6264],{},[135,6255,6256,6258],{},[138,6257,5546],{}," Biaya akurat sesungguhnya dari garis awal ke titik saat ini (seperti Dijkstra).",[135,6260,6261,6263],{},[138,6262,5552],{}," Estimasi kasar jarak dari titik saat ini ke titik target.",[135,6265,6266,6268,6269,6271],{},[138,6267,5558],{}," Nilai total biaya. A* akan selalu memprioritaskan mengecek node dengan nilai ",[40,6270,5562],{}," terendah terlebih dahulu.",[135,6273,6274,6276],{},[138,6275,5823],{}," Sangat cepat dan responsif. Karena algoritma ini secara aktif \"meregang\" ke arah target, ia mengabaikan jalur yang jelas-jelas bergerak ke arah yang salah. A* tetap menjamin rute pergerakan terpendek (asalkan rumus heuristic di desain dengan benar).",[135,6278,6279,6281],{},[138,6280,5829],{}," Bisa memakan jumlah memori yang besar pada lingkungan raksasa (Open world map) sehingga memerlukan optimasi ekstra seperti sistem Hierarchical Pathfinding dan NavMesh poligon khusus.",[135,6283,6284,6286],{},[138,6285,5839],{}," Hampir semua AI modern mutakhir yang memicu pergerakan karakter A-ke-B.",[22,6288,6289],{},[138,6290,6291],{},"Contoh Kode (Berbasis Grid beserta Heuristic):",[34,6293,6295],{"className":36,"code":6294,"language":38,"meta":16,"style":16},"\u002F\u002F Rumus jarak Manhattan untuk pergerakan 4-arah dalam grid\nfunction heuristic(nodeA, nodeB) {\n  return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);\n}\n\nfunction a_star_grid(grid, start, target) {\n  let pq = [{ node: start, fCost: 0 }];\n  let gCosts = {}; \u002F\u002F Biaya (Cost) asli dari lokasi start\n  gCosts[`${start.x},${start.y}`] = 0;\n  \n  while (pq.length > 0) {\n    pq.sort((a, b) => a.fCost - b.fCost); \u002F\u002F Proses nilai F (F-cost) terendah terlebih dahulu\n    let current = pq.shift().node;\n    \n    if (current.x === target.x && current.y === target.y) return true; \u002F\u002F Ketemu\n    \n    let neighbors = getNeighbors(grid, current);\n    for (let item of neighbors) {\n      let nextNode = item.node;\n      let newGCost = gCosts[`${current.x},${current.y}`] + item.weight; \n      let key = `${nextNode.x},${nextNode.y}`;\n      \n      if (!(key in gCosts) || newGCost \u003C gCosts[key]) {\n        gCosts[key] = newGCost;\n        \u002F\u002F F = G + H\n        let fCost = newGCost + heuristic(nextNode, target); \n        pq.push({ node: nextNode, fCost: fCost });\n      }\n    }\n  }\n  return false;\n}\n",[40,6296,6297,6302,6306,6310,6314,6318,6322,6326,6331,6335,6339,6343,6348,6352,6356,6360,6364,6368,6372,6376,6380,6384,6388,6392,6396,6400,6404,6408,6412,6416,6420,6424],{"__ignoreMap":16},[43,6298,6299],{"class":45,"line":46},[43,6300,6301],{},"\u002F\u002F Rumus jarak Manhattan untuk pergerakan 4-arah dalam grid\n",[43,6303,6304],{"class":45,"line":52},[43,6305,5602],{},[43,6307,6308],{"class":45,"line":94},[43,6309,5607],{},[43,6311,6312],{"class":45,"line":100},[43,6313,809],{},[43,6315,6316],{"class":45,"line":107},[43,6317,104],{"emptyLinePlaceholder":103},[43,6319,6320],{"class":45,"line":113},[43,6321,5620],{},[43,6323,6324],{"class":45,"line":195},[43,6325,5625],{},[43,6327,6328],{"class":45,"line":200},[43,6329,6330],{},"  let gCosts = {}; \u002F\u002F Biaya (Cost) asli dari lokasi start\n",[43,6332,6333],{"class":45,"line":206},[43,6334,5635],{},[43,6336,6337],{"class":45,"line":212},[43,6338,1159],{},[43,6340,6341],{"class":45,"line":218},[43,6342,5415],{},[43,6344,6345],{"class":45,"line":224},[43,6346,6347],{},"    pq.sort((a, b) => a.fCost - b.fCost); \u002F\u002F Proses nilai F (F-cost) terendah terlebih dahulu\n",[43,6349,6350],{"class":45,"line":618},[43,6351,5425],{},[43,6353,6354],{"class":45,"line":914},[43,6355,882],{},[43,6357,6358],{"class":45,"line":920},[43,6359,5892],{},[43,6361,6362],{"class":45,"line":926},[43,6363,882],{},[43,6365,6366],{"class":45,"line":932},[43,6367,5283],{},[43,6369,6370],{"class":45,"line":937},[43,6371,5673],{},[43,6373,6374],{"class":45,"line":943},[43,6375,5460],{},[43,6377,6378],{"class":45,"line":949},[43,6379,5682],{},[43,6381,6382],{"class":45,"line":955},[43,6383,5470],{},[43,6385,6386],{"class":45,"line":960},[43,6387,1038],{},[43,6389,6390],{"class":45,"line":966},[43,6391,5695],{},[43,6393,6394],{"class":45,"line":972},[43,6395,5700],{},[43,6397,6398],{"class":45,"line":977},[43,6399,5705],{},[43,6401,6402],{"class":45,"line":983},[43,6403,5710],{},[43,6405,6406],{"class":45,"line":989},[43,6407,5715],{},[43,6409,6410],{"class":45,"line":994},[43,6411,1074],{},[43,6413,6414],{"class":45,"line":1000},[43,6415,1080],{},[43,6417,6418],{"class":45,"line":1006},[43,6419,929],{},[43,6421,6422],{"class":45,"line":1012},[43,6423,5187],{},[43,6425,6426],{"class":45,"line":1017},[43,6427,809],{},[26,6429,6431],{"id":6430},"kesimpulan-singkat","Kesimpulan Singkat",[132,6433,6434,6439,6444,6449],{},[135,6435,6436,6438],{},[138,6437,5746],{}," Berjalan tanpa tujuan jelas sampai menabrak finish. Buruk untuk pathfinding.",[135,6440,6441,6443],{},[138,6442,5752],{}," Bergerak meluas keluar seperti riak air. Menemukan rute terpendek di jarak kotak yang bobot jalannya rata.",[135,6445,6446,6448],{},[138,6447,5758],{}," Meluas namun mengutamakan medan jalan termurah. Menemukan rute berbiaya usaha terendah pada map bergelombang\u002Frintangan.",[135,6450,6451,6455],{},[1258,6452,6453,5766],{},[1258,6454,5524],{},"* Secara cerdas membidik ke arah tujuan finish. Solusi terbaik standar perindustrian.",[22,6457,6458],{},"Memahami konsep-konsep ini akan secara dramatis meningkatkan cara Anda merancang pergerakan kecerdasan buatan (AI) buatan sendiri!",[690,6460,692],{},{"title":16,"searchDepth":52,"depth":52,"links":6462},[],{"title_en":5016,"title_id":5776,"description_en":6464,"description_id":6465,"date":6466,"readingTime":1773},"A comprehensive guide to understanding pathfinding algorithms in game development, from basic DFS and BFS to advanced Dijkstra and A* (A-Star).","Panduan lengkap memahami algoritma pathfinding dalam pembuatan game, mulai dari dasar DFS dan BFS hingga Dijkstra dan A* (A-Star).","2026-03-26","\u002Fblog\u002Fgame-pathfinding-algorithms",{"title":5016,"description":16},"blog\u002Fgame-pathfinding-algorithms","P_zZNLUPB0fiinrVA7Mj7qKLFsgG9TyaTofAK4l4uGc",1783751390223]