{"id":2823,"date":"2023-11-30T23:51:07","date_gmt":"2023-12-01T07:51:07","guid":{"rendered":"https:\/\/rose.dev\/blog\/?p=2823"},"modified":"2024-01-24T21:13:40","modified_gmt":"2024-01-25T05:13:40","slug":"pure-javascript-asteroids-clone-with-enemy-ships-source-code","status":"publish","type":"post","link":"https:\/\/rose.dev\/blog\/2023\/11\/30\/pure-javascript-asteroids-clone-with-enemy-ships-source-code\/","title":{"rendered":"Pure JavaScript Asteroids Clone with Enemy Ships Source Code"},"content":{"rendered":"\n<p>There are many acceptable JavaScript game engines out nowadays, but often you can get good performance from writing your own simple engine or renderer depending on your use case. The code for this project will be on my GitHub linked below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What goes into writing a game engine?<\/h2>\n\n\n\n<p>Ideally, we want to handle a few important things.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>States, whether that be states of objects (alive, dead, moving, the type of enemy)<\/li>\n\n\n\n<li>Rendering<\/li>\n\n\n\n<li>Spawnable objects (with previously mentioned states)<\/li>\n\n\n\n<li>Input<\/li>\n\n\n\n<li>Save data<\/li>\n<\/ol>\n\n\n\n<p>We approach this task with an object-oriented mindset instead of a functional programming mindset. Although there are a few global variables such as the overall running game state or the object pool arrays, most of the memory or information we need to remember occurs on a per-object basis.<\/p>\n\n\n\n<p>We will be using a &#8216;<a rel=\"noreferrer noopener\" href=\"https:\/\/www.w3schools.com\/html\/html5_canvas.asp\" target=\"_blank\">Canvas<\/a>&#8216; to draw our simple asteroid graphics. Writing a 3d renderer in JS is a much more complex task, although libraries like <a rel=\"noreferrer noopener\" href=\"https:\/\/threejs.org\/\" target=\"_blank\">threeJS<\/a> exist to get you started.<\/p>\n\n\n\n<p>To begin with, we want to define a Vector2D class that we can reuse throughout our game. I&#8217;m familiar with Unity so I imagine an implementation similar to their engine&#8217;s GameObject setup, but any class that can read \/ write an X and Y will work.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">var Vec2D = (function() {\nvar create = function(x, y) {\n        var obj = Object.create(def);\n        obj.setXY(x, y);\n\n        return obj;\n    };\n\n    var def = {\n        _x: 1,\n        _y: 0,\n\n        getX: function() {\n            return this._x;\n        },\n\n        setX: function(value) {\n            this._x = value;\n        },\n\n        getY: function() {\n            return this._y;\n        },\n\n        setY: function(value) {\n            this._y = value;\n        },\n\n        setXY: function(x, y) {\n            this._x = x;\n            this._y = y;\n        },\n\n        getLength: function() {\n            return Math.sqrt(this._x * this._x + this._y * this._y);\n        },\n\n        setLength: function(length) {\n            var angle = this.getAngle();\n            this._x = Math.cos(angle) * length;\n            this._y = Math.sin(angle) * length;\n        },\n\n        getAngle: function() {\n            return Math.atan2(this._y, this._x);\n        },\n\n        setAngle: function(angle) {\n            var length = this.getLength();\n            this._x = Math.cos(angle) * length;\n            this._y = Math.sin(angle) * length;\n        },\n\n        add: function(vector) {\n            this._x += vector.getX();\n            this._y += vector.getY();\n        },\n\n        sub: function(vector) {\n            this._x -= vector.getX();\n            this._y -= vector.getY();\n        },\n\n        mul: function(value) {\n            this._x *= value;\n            this._y *= value;\n        },\n\n        div: function(value) {\n            this._x \/= value;\n            this._y \/= value;\n        }\n    };\n\n    return {\n        create: create\n    };\n}());       <\/code><\/pre>\n\n\n\n<p>This will allow us to reference positions easier. It&#8217;s vital to implement a few capabilities for our renderer. One important need is to be able to draw an object to our canvas at a specified position, and have the capability to clear said canvas, preparing for the next frame the game renders.<\/p>\n\n\n\n<p>To draw a line, we can write JavaScript such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">var c = document.getElementById(\"canvas\");\nvar ctx = c.getContext(\"2d\");\nctx.moveTo(0, 0);\nctx.lineTo(200, 100);\nctx.stroke();<\/code><\/pre>\n\n\n\n<p>And if we wanted to clear our canvas, we can use <code>clearRect<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">ctx.clearRect(0, 0, canvas.width, canvas.height);<\/code><\/pre>\n\n\n\n<p>We can define a render function to handle our different objects.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">window.getAnimationFrame =\n    window.requestAnimationFrame ||\n    window.webkitRequestAnimationFrame ||\n    window.mozRequestAnimationFrame ||\n    window.oRequestAnimationFrame ||\n    window.msRequestAnimationFrame ||\nfunction(callback) {\n    window.setTimeout(callback, 16.6);\n};\nrender(){\n    context.clearRect(0,0,screenWidth,screenHeight);\n    renderShips();\n    renderAsteroids();\n    renderBullets();\n    getAnimationFrame(loop);\n}\n\nrenderShips(){\n    ship.renderSelf();\n    for (int i = 0; i &lt; enemies.length; i++)\n    enemies.renderSelf();\n}\n...etc<\/code><\/pre>\n\n\n\n<p>Then an example render self function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">renderSelf: function() {\n    if (this.hasDied)\n        return;\n    context.save();\n    context.translate(this.pos.getX() &gt;&gt; 0, this.pos.getY() &gt;&gt; 0);\n    context.rotate(this.angle);\n    context.strokeStyle = playerColor;\n    context.lineWidth = (Math.random() &gt; 0.9) ? 4 : 2;\n    context.beginPath();\n    context.moveTo(10, 0);\n    context.lineTo(-10, -10);\n    context.lineTo(-10, 10);\n    context.lineTo(10, 0);\n    context.stroke();\n    context.closePath();\n\n    context.restore();\n}<\/code><\/pre>\n\n\n\n<p>Which would render our object assuming a class holding some variables with our Vector2 class we described earlier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">var Ship = (function() {\nvar create = function(x, y, ref) {\n    var obj = Object.create(def);\n    obj.ref = ref;\n    obj.angle = 0;\n    obj.pos = Vec2D.create(x, y);\n    obj.vel = Vec2D.create(0, 0);\n    obj.thrust = Vec2D.create(0, 0);\n    obj.invincible = false;\n    obj.hasDied = false;\n    obj.radius = 8;\n    obj.idleDelay = 0;\n    obj.isSpectating = false;\n\n    return obj;\n};\n...etc<\/code><\/pre>\n\n\n\n<p>We are handling rendering and state management from inside an object now. All that just for a triangle.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"250\" height=\"250\" sizes=\"auto, (max-width: 250px) 100vw, 250px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameplayertriangle.png\" alt=\"player ship\" class=\"wp-image-2826\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameplayertriangle.png 250w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameplayertriangle-150x150.png 150w\" \/><\/figure>\n\n\n\n<p>We aren&#8217;t done yet. Next we need to handle Input. The goal with creating object classes is reusability and extensibility. We don&#8217;t need to spawn multiple instances of an input, so we can handle that globally. Your Input function may look something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">window.onkeydown = function(e) {\n    switch (e.keyCode) {\n        \/\/key A or LEFT\n        case 65:\n        case 37:\n            keyLeft = true;\n            break;\n            \/\/key W or UP\n        case 87:\n        case 38:\n            keyUp = true;\n            break;\n            \/\/key D or RIGHT\n        case 68:\n        case 39:\n            keyRight = true;\n            break;\n            \/\/key S or DOWN\n        case 83:\n        case 40:\n            keyDown = true;\n            break;\n            \/\/key Space\n        case 32:\n        case 75:\n            keySpace = true;\n            break;\n            \/\/key Shift\n        case 16:\n            keyShift = true;\n            break;\n    }\n\n    e.preventDefault();\n};\n\nwindow.onkeyup = function(e) {\n    switch (e.keyCode) {\n        \/\/key A or LEFT\n        case 65:\n        case 37:\n            keyLeft = false;\n            break;\n            \/\/key W or UP\n        case 87:\n        case 38:\n            keyUp = false;\n            break;\n            \/\/key D or RIGHT\n        case 68:\n        case 39:\n            keyRight = false;\n            break;\n            \/\/key S or DOWN\n        case 83:\n        case 40:\n            keyDown = false;\n            break;\n            \/\/key Space\n        case 75:\n        case 32:\n            keySpace = false;\n            break;\n            \/\/key Shift\n        case 16:\n            keyShift = false;\n            break;\n    }\n\n    e.preventDefault();\n};<\/code><\/pre>\n\n\n\n<p><code>e.preventDefault()<\/code> will stop users from accidentally hitting keys such as ctrl + L and losing focus from the window, or jumping the page with Space, for instance.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">function updateShip() {\n    ship.update();\n\n    if (ship.hasDied) return;\n\n    if (keySpace) ship.shoot();\n    if (keyLeft &amp;&amp; keyShift) ship.angle -= 0.1;\n    else if (keyLeft) ship.angle -= 0.05;\n    if (keyRight &amp;&amp; keyShift) ship.angle += 0.1;\n    else if (keyRight) ship.angle += 0.05;\n\n    if (keyUp) {\n        ship.thrust.setLength(0.1);\n        ship.thrust.setAngle(ship.angle);\n    } else {\n        ship.vel.mul(0.94);\n        ship.thrust.setLength(0);\n    }\n\n    if (ship.pos.getX() &gt; screenWidth) ship.pos.setX(0);\n    else if (ship.pos.getX() &lt; 0) ship.pos.setX(screenWidth);\n\n    if (ship.pos.getY() &gt; screenHeight) ship.pos.setY(0);\n    else if (ship.pos.getY() &lt; 0) ship.pos.setY(screenHeight);\n}\n\n...etc\n\nfunction checkDistanceCollision(obj1, obj2) {\n    var vx = obj1.pos.getX() - obj2.pos.getX();\n    var vy = obj1.pos.getY() - obj2.pos.getY();\n    var vec = Vec2D.create(vx, vy);\n\n    if (vec.getLength() &lt; obj1.radius + obj2.radius) {\n        return true;\n    }\n\n    return false;\n}\n\n...etc<\/code><\/pre>\n\n\n\n<p>Once we have the ability to render a reusable object to a canvas and read \/ write a position that can be checked, we use that as a template to create other objects (particles, asteroids, other ships).<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:50%\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"322\" height=\"322\" sizes=\"auto, (max-width: 322px) 100vw, 322px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidgameasteroid.png\" alt=\"hexagon asteroid\" class=\"wp-image-2829\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidgameasteroid.png 322w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidgameasteroid-300x300.png 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidgameasteroid-150x150.png 150w\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:50%\">\n<figure class=\"wp-block-image size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"168\" height=\"168\" sizes=\"auto, (max-width: 168px) 100vw, 168px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidscolorshapesexample.png\" alt=\"enemy ship example\" class=\"wp-image-2830\" style=\"width:168px;height:168px\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidscolorshapesexample.png 168w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidscolorshapesexample-150x150.png 150w\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p>You can make interesting graphics with just basic shapes. We handle collision by assigning either an xWidth and yWidth + xOffset and yOffset, OR a radius. This again would be assigned to the object itself to keep track of.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"912\" height=\"584\" sizes=\"auto, (max-width: 912px) 100vw, 912px\" src=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameexample.png\" alt=\"asteroids game example\" class=\"wp-image-2832\" srcset=\"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameexample.png 912w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameexample-300x192.png 300w, https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/asteroidsgameexample-768x492.png 768w\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Further Techniques<\/h2>\n\n\n\n<p>If we can control the rendering manually we can leave an &#8216;afterimage&#8217; on our canvas before rendering the next frame as opposed to clearing it entirely. To do this, we can manipulate the canvas&#8217; global alpha.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ Get the canvas element and its 2D rendering context\nconst canvas = document.getElementById('myCanvas');\nconst ctx = canvas.getContext('2d');\n\/\/ Set the initial alpha value\nlet alpha = 0.1; \/\/ You can adjust this value to control the fading speed\n\/\/ Function to create the afterimage effect\nfunction createAfterimage() {\n    \/\/ Set a semi-transparent color for the shapes\n    ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;\n    \/\/ Fill a rectangle covering the entire canvas\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    \/\/ Decrease alpha for the next frame\n    alpha *= 0.9; \/\/ You can adjust this multiplier for a different fade rate\n    \/\/ Request animation frame to update\n    requestAnimationFrame(createAfterimage);\n}\n\/\/ Call the function to start creating the afterimage effect\ncreateAfterimage();<\/code><\/pre>\n\n\n\n<p>And a simple localStorage can be used to save scores.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">function checkLocalScores() {\n    if (localStorage.getItem(\"rocks\") != null) {\n        visualRocks = localStorage.getItem(\"rocks\");\n    }\n    if (localStorage.getItem(\"deaths\") != null) {\n        visualDeaths = localStorage.getItem(\"deaths\");\n    }\n    if (localStorage.getItem(\"enemyShips\") != null) {\n        visualEnemyShips = localStorage.getItem(\"enemyShips\");\n    }\n    updateVisualStats();\n}\nfunction saveLocalScores() {\n    localStorage.setItem(\"rocks\", visualRocks);\n    localStorage.setItem(\"deaths\", visualDeaths);\n    localStorage.setItem(\"enemyShips\", visualEnemyShips);\n}<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">End Result<\/h2>\n\n\n\n<p>You can <a rel=\"noreferrer noopener\" href=\"http:\/\/rose.dev\/_games\/com.gen.splatteroids\/\" target=\"_blank\">see and play the game here<\/a>. <\/p>\n\n\n\n<p><a rel=\"noreferrer noopener\" href=\"http:\/\/github.com\/gen3vra\/splatteroidspublic\" target=\"_blank\">Source code is here<\/a>. \u2728<\/p>\n<hr>\r\nIt helps me if you share this post\r\n<br\/>\r\n<br\/>\r\nPublished 2023-11-30 23:51:07 ","protected":false},"excerpt":{"rendered":"<p>There are many acceptable JavaScript game engines out nowadays, but often you can get good performance from writing your own simple engine or renderer depending on your use case. The code for this project will be on my GitHub linked below. What goes into writing a game engine? Ideally, we want to handle a few &hellip; <a href=\"https:\/\/rose.dev\/blog\/2023\/11\/30\/pure-javascript-asteroids-clone-with-enemy-ships-source-code\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Pure JavaScript Asteroids Clone with Enemy Ships Source Code<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":2845,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":""},"categories":[922,832,833],"tags":[1193,1189,1190,1188,858,1187,1194,1191,1192],"class_list":["post-2823","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-release","category-software","category-technology","tag-2d","tag-asteroids","tag-clone","tag-engine","tag-game","tag-javascript","tag-renderer","tag-ships","tag-spaceship"],"jetpack_featured_media_url":"https:\/\/rose.dev\/blog\/wp-content\/uploads\/2023\/11\/splatteroidsgamepreview.png","_links":{"self":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/2823","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/comments?post=2823"}],"version-history":[{"count":20,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/2823\/revisions"}],"predecessor-version":[{"id":3151,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/posts\/2823\/revisions\/3151"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/media\/2845"}],"wp:attachment":[{"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/media?parent=2823"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/categories?post=2823"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rose.dev\/blog\/wp-json\/wp\/v2\/tags?post=2823"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}