{"slug": "build-a-dinosaur-runner-game-with-deno-pt-2", "title": "Build a dinosaur runner game with Deno, pt. 2", "summary": "This article is part two of a series on building a browser-based dinosaur runner game using Deno. It focuses on implementing the game loop, HTML5 canvas rendering, and keyboard/mouse controls for jumping, along with a basic physics system. The guide also covers project structure, UI styling, and deploying the game to the cloud.", "body_md": "# Build a dinosaur runner game with Deno, pt. 2\n\nThis series of blog posts will guide you through building a simple browser-based dinosaur runner game using Deno.\n\n[Setup a basic project][Game loop, canvas, and controls][Obstacles and collision detection][Databases and global leaderboards]- Player profiles, customization, and live tuning\n- Observability, metrics, and alerting\n\nSetting up the Game Loop, Canvas & Controls\n\n[In Stage 1](/blog/build-a-game-with-deno-1), we set up the foundation of the\nDino Runner game: an HTTP server with static file serving, and a basic page\nwhich we deployed to the web with [Deno Deploy](/deploy).\n\nStage 2 is where we’ll start to bring the game to life.\n\nWe’ll turn that simple landing page into a moving canvas with a jumping\ncharacter with keyboard and mouse controls. We’ll also learn about game loops\nusing `requestAnimationFrame`\n\n.\n\nKeep reading and build along or\n[view the entire source here](https://github.com/thisisjofrank/game-tutorial).\n\nWhat you’ll Learn\n\nBy the end of this stage you will have:\n\n- Set up an\n**HTML5 canvas** as the main game area - Implemented a\n**game loop** using`requestAnimationFrame`\n\n- Added\n**keyboard and mouse/touch input** for jumping - Built a basic\n**physics system**(gravity + jumping) - Deployed your new project to the cloud.\n\nProject structure\n\nStage 2 uses a simple but realistic full-stack layout, building on what we set up in Stage 1:\n\n```\nRunner Game/\n├── src/                    # Server-side source code\n│   ├── main.ts             # Server entry point\n│   └── routes/             # Route definitions\n│       └── api.routes.ts   # API route definitions\n├── public/                 # Client-side static files\n│   ├── index.html          # Main landing page & game canvas\n│   ├── js/\n│   │   └── game.js         # Client-side game logic\n│   └── css/\n│       └── styles.css      # Styling\n├── deno.json               # Deno configuration\n└── .env                    # Environment variables\n```\n\n- Server-side code (src/) is written in TypeScript and served by Deno + Oak.\n- Client-side (public/) is where your game canvas, JS game engine and CSS live.\n- deno.json and env files keep things easy to configure and deploy.\n\nSetting up the HTML5 Canvas\n\nThe core of the game will be rendered inside an\n[HTML5 <canvas>](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)\nelement. This allows us to draw graphics dynamically using JavaScript.\n\nIn stage 1 we wrote a very basic `index.html`\n\npage. Now, we will enhance it to\ninclude the game canvas and some UI elements. Replace the internals of the\n`<body>`\n\nof your `public/index.html`\n\nwith the following:\n\n```\n<main>\n  <h1>🦕 Dino Runner</h1>\n  <section class=\"container canvas-container\">\n    <canvas id=\"gameCanvas\" width=\"800\" height=\"200\"></canvas>\n  </section>\n</main>\n```\n\nWe want this to feel like a game, so let’s also add a score display and a ‘Click\nto Start’ message. Directly after the `<canvas>`\n\ntag, inside the `<section>`\n\nelement, add the following HTML:\n\n```\n<div class=\"game-ui\">\n  <div class=\"score\">Score: <span id=\"score\">0</span></div>\n  <div class=\"game-status\" id=\"gameStatus\">Click to Start!</div>\n</div>\n```\n\nThe `class`\n\nattributes on these elements will allow us to style them later with\nCSS and the `id`\n\ns will let us update them dynamically with JavaScript.\n\nFinally, lets add some instructions for the player, to show which keys to use.\nBelow the `canvas-container`\n\nsection, add this HTML:\n\n```\n<section class=\"container\">\n  <h3>Controls</h3>\n  <div class=\"control-grid\">\n    <kbd>Space</kbd>\n    <kbd>↑</kbd>\n    <span class=\"click\">️Click</span>\n    <span>Jump</span>\n    <span>Jump</span>\n    <span>Start/Jump</span>\n  </div>\n</section>\n```\n\nYou can now serve your game locally with `deno task dev`\n\nand open it in your\nbrowser. You should see a blank canvas and some basic text elements. Very\nboring, lets style it up!\n\nStyling the Game UI\n\nNext, let’s add some basic styles to make our game look a bit nicer. Open\n`public/css/styles.css`\n\nand add the following CSS:\n\n```\n:root {\n  --primary: #66c2ff;\n  --primary-dark: #0077cc;\n  --secondary: #cbd1e1;\n  --tertiary: #e5e9f2;\n  --bg: #f1f3f9;\n  --dark: #1e253b;\n}\n\nhtml {\n  box-sizing: border-box;\n}\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0 auto;\n  padding: 1rem;\n  background-color: var(--bg);\n  color: var(--dark);\n  line-height: 1.6;\n}\n\nh1 {\n  margin: 1.5rem auto;\n  color: var(--dark);\n}\n\nh2,\nh3 {\n  margin: 1rem 0;\n  color: var(--dark);\n  text-align: center;\n}\n\n/* Game Canvas Styles */\n\n.container {\n  margin-bottom: 1.5rem;\n  padding: 1rem;\n  border: 2px solid #cbd1e1;\n  border-radius: 1px;\n  background: white;\n  box-shadow: 2px 4px 0px 0px var(--tertiary);\n  text-align: center;\n}\n\n.canvas-container {\n  position: relative;\n  padding: 1rem;\n  display: inline-block;\n  width: calc(804px + 2rem); /* 800px canvas + 2px border */\n  max-width: 100%;\n}\n\n.status {\n  background-color: #eee;\n  border-color: var(--primary);\n}\n\n#gameCanvas {\n  display: block;\n  background: linear-gradient(\n    to bottom,\n    SkyBlue 0%,\n    SkyBlue 79%,\n    SandyBrown 79%,\n    SandyBrown 100%\n  );\n  cursor: pointer;\n}\n\n.game-ui {\n  position: absolute;\n  top: 15px;\n  left: 15px;\n  right: 15px;\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  pointer-events: none;\n}\n\n.score,\n.game-status {\n  padding: 0.3rem 1rem;\n  border: 2px solid #cbd1e1;\n  border-radius: 1px;\n  background: white;\n  box-shadow: 2px 4px 0px 0px rgba(0, 0, 0, 0.2);\n  text-align: center;\n  font-family: var(--font-serif);\n  text-decoration: none;\n}\n\n.score {\n  min-width: 120px;\n  text-align: left;\n}\n\n.game-status {\n  background: var(--primary);\n  border-color: var(--primary-dark);\n  border-radius: 4px;\n  box-shadow: 2px 4px 0px 0px rgba(0, 0, 0, 0.2);\n}\n\n.control-grid {\n  display: grid;\n  justify-content: center;\n  place-items: center;\n  gap: 1rem;\n  grid-template-columns: repeat(3, 100px);\n  margin: 1rem auto;\n}\n\nkbd {\n  background: #f0f0f0;\n  border: 1px solid #ccc;\n  border-radius: 4px;\n  padding: 4px 8px;\n  font-size: 12px;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n  width: max-content;\n}\n\n.click {\n  padding: 3px 0px;\n}\n\n/* Responsive Design */\n@media (max-width: 860px) {\n  #gameCanvas {\n    width: 100%;\n    max-width: 800px;\n    height: auto;\n  }\n\n  .canvas-container {\n    width: 100%;\n    box-sizing: border-box;\n  }\n\n  .control-grid {\n    flex-direction: column;\n    align-items: center;\n  }\n}\n\n@media (max-width: 600px) {\n  body {\n    padding: 0;\n  }\n}\n```\n\nYou can of course customize these styles and make the game your own.\n\nNow its time for the fun part - adding the game logic with JavaScript!\n\nThe game engine\n\nThe core of our game logic will live in `public/js/game.js`\n\n. This file will\nhandle rendering to the canvas, processing user input, and managing game state.\n\nLet’s start by setting up the basic structure of our game engine. Open\n`public/js/game.js`\n\nand add the following code:\n\n```\nconsole.log(\"Dinosaur Runner Game - Stage 2\");\n\nclass DinoGame {\n  constructor() {\n    this.canvas = document.getElementById(\"gameCanvas\");\n    this.ctx = this.canvas.getContext(\"2d\");\n    this.scoreElement = document.getElementById(\"score\");\n    this.statusElement = document.getElementById(\"gameStatus\");\n\n    // Game state\n    this.gameState = \"waiting\"; // 'waiting', 'playing', 'gameOver'\n    this.score = 0;\n    this.gameSpeed = 2;\n\n    // Dino properties\n    this.dino = {\n      x: 50,\n      y: 150,\n      width: 40,\n      height: 40,\n      velocityY: 0,\n      isJumping: false,\n      groundY: 150,\n    };\n\n    // Physics\n    this.gravity = 0.6;\n    this.jumpStrength = -12;\n\n    // Ground\n    this.groundY = 180;\n\n    this.init();\n  }\n\n  init() {\n    this.setupEventListeners();\n    this.gameLoop();\n    this.updateStatus(\"Click to Start!\");\n  }\n}\n```\n\nWe’ve described a game which has a state, a score, a dino character with position and velocity, and some basic physics properties like gravity and jump strength. Changing the gravity will make the dino fall faster or slower, and changing the jump strength will affect how high it jumps. The ground is the y-coordinate where the dino stands when not jumping.\n\nThis skeleton wires up the canvas, tracks a single dino object, and immediately\ncalls `init()`\n\nso the rest of our hooks run once the class is instantiated. Make\nsure `public/index.html`\n\nloads this script by ensuring the following line is\nbefore the closing `</body>`\n\ntag:\n\n```\n<script type=\"module\" src=\"/js/game.js\"></script>\n```\n\nNow we can implement each method in small, testable chunks.\n\nGame loops, frames and animation\n\nThe ‘game loop’ is the heartbeat of the game. It loops once a `frame`\n\nto\ncontinuously update the game state and render the scene. We’ll use\n[ requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame)\nto create a smooth loop that loops in time with the browser’s refresh rate.\n\nFrames are the individual images that make up the animation. The more frames per second (FPS), the smoother the animation appears. Most browsers aim for 60 FPS, which means the game loop will run approximately every 16.67 milliseconds.\n\nWe already called `this.gameLoop()`\n\nin the constructor, next we’ll implement all\nof the mechanics to implement a functioning game loop.\n\nPlayer input and event listeners\n\nWe want both keyboard and mouse/touch controls, for multiple device support.\nWe’ll set up event listeners for `keydown`\n\nand `click`\n\nto handle these inputs.\n\nAppend these methods inside the `DinoGame`\n\nclass:\n\n``` js\nsetupEventListeners() {\n  document.addEventListener(\"keydown\", (e) => {\n    if (e.code === \"Space\" || e.code === \"ArrowUp\") {\n      e.preventDefault();\n      this.handleJump();\n    }\n  });\n\n  this.canvas.addEventListener(\"click\", () => {\n    this.handleJump();\n  });\n}\n\nhandleJump() {\n  if (this.gameState === \"waiting\") {\n    this.startGame();\n  } else if (this.gameState === \"playing\" && !this.dino.isJumping) {\n    this.jump();\n  } else if (this.gameState === \"gameOver\") {\n    this.resetGame();\n  }\n}\n```\n\n`setupEventListeners()`\n\nkeeps things device-agnostic and prevents the space bar\nfrom scrolling the page mid-game. `handleJump()`\n\nis our state gatekeeper: it\neither starts the run, triggers a jump, or resets the game after a crash.\n\nStarting the run, jumping, and physics\n\nNext we need to implement the actual jump mechanics and basic physics. We’ll use simple gravity and velocity to simulate jumping and falling. Jumping will set an upward velocity, and gravity will pull the dino back down to the ground.\n\nOn game start, we reset the score to 0, update the UI to reflect that and we set\na `game state`\n\nto `\"playing\"`\n\n.\n\nOn jump, we set an initial negative velocity (the jump strength) to propel the dino upwards and then mark it as jumping to prevent double jumps.\n\nWhen jumping we also apply gravity to bring the dino back down, and check for ground collision to reset its position.\n\nFor every completed jump, we’ll increment the score slightly to reward survival time.\n\nAdd these methods below the previous ones, still inside the `DinoGame`\n\nclass:\n\n```\nstartGame() {\n  this.gameState = \"playing\";\n  this.score = 0;\n  this.updateScore();\n  this.updateStatus(\"\");\n  console.log(\"Game started!\");\n}\n\njump() {\n  if (!this.dino.isJumping) {\n    this.dino.velocityY = this.jumpStrength;\n    this.dino.isJumping = true;\n    console.log(\"Dino jumped!\");\n  }\n}\n\nupdatePhysics() {\n  if (this.gameState !== \"playing\") return;\n\n  this.dino.velocityY += this.gravity;\n  this.dino.y += this.dino.velocityY;\n\n  if (this.dino.y >= this.dino.groundY) {\n    this.dino.y = this.dino.groundY;\n    this.dino.velocityY = 0;\n    this.dino.isJumping = false;\n  }\n\n  this.score += 0.1;\n  this.updateScore();\n}\n```\n\nGravity slowly increases `velocityY`\n\n, the ground check clamps the dinosaur back\nto the floor, and the score ticks up a tiny amount every frame so longer\nsurvival means higher points. Now we need to implement the rendering logic.\n\nRendering the scene\n\nNext we handle drawing to the canvas, first we completely clear the canvas, then we paint the dino, and overlay instructions while waiting to start.\n\nGame graphics are drawn to the canvas with the `ctx`\n\n(context) object we grabbed\nright at the start. We clear the canvas each frame, draw the ground line, then\ndraw the dino at its current position.\n\nThe ‘dino’ that we are drawing is just a green rectangle with some extra details to give it two little legs and a face. This keeps things simple for now with a very basic leg animation, but you could easily swap this out for sprite images or more complex shapes later.\n\n```\nrender() {\n  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n  this.drawDino();\n\n  if (this.gameState === \"waiting\") {\n    this.drawInstructions();\n  }\n}\n\ndrawDino() {\n  const strideActive = this.gameState === \"playing\" && !this.dino.isJumping;\n  const legStride = strideActive ? (Math.floor(this.frameCount / 8) % 2 === 0 ? 2 : -2) : 0;\n  const legBaseY = this.dino.y + this.dino.height - 2;\n\n  this.ctx.fillStyle = \"green\";\n  this.ctx.fillRect(\n    this.dino.x,\n    this.dino.y,\n    this.dino.width,\n    this.dino.height,\n  );\n\n  this.ctx.fillStyle = \"darkgreen\";\n  this.ctx.fillRect(this.dino.x + 25, this.dino.y + 8, 4, 4);\n  this.ctx.fillRect(this.dino.x + 30, this.dino.y + 20, 8, 2);\n\n  if (!this.dino.isJumping) {\n    this.ctx.fillStyle = \"green\";\n    this.ctx.fillRect(this.dino.x + 10, this.dino.y + 40, 6, 8);\n    this.ctx.fillRect(this.dino.x + 24, this.dino.y + 40, 6, 8);\n  }\n}\n\ndrawInstructions() {\n  this.ctx.fillStyle = \"black\";\n  this.ctx.font = \"24px Arial\";\n  this.ctx.textAlign = \"center\";\n  this.ctx.fillText(\n    \"Press SPACE or ↑ to jump!\",\n    this.canvas.width / 2,\n    this.canvas.height / 2 - 20,\n  );\n\n  this.ctx.font = \"16px Arial\";\n  this.ctx.fillText(\n    \"Click anywhere to start\",\n    this.canvas.width / 2,\n    this.canvas.height / 2 + 10,\n  );\n}\n```\n\nBecause we render every animation frame, keeping `drawDino()`\n\nsmall makes tweaks\neasy. In future we could add new colors, simple animations or sprites.\n\nUI helpers and resetting\n\nWhen the game state changes we update the DOM elements to reflect those changes,\nfor example game reset and score updates. Add the following methods inside the\n`DinoGame`\n\nclass:\n\n```\nupdateScore() {\n  this.scoreElement.textContent = Math.floor(this.score);\n}\n\nupdateStatus(message) {\n  this.statusElement.textContent = message;\n  this.statusElement.style.display = message ? \"block\" : \"none\";\n}\n\nresetGame() {\n  this.gameState = \"waiting\";\n  this.score = 0;\n  this.dino.y = this.dino.groundY;\n  this.dino.velocityY = 0;\n  this.dino.isJumping = false;\n  this.updateScore();\n  this.updateStatus(\"Click to Start!\");\n  console.log(\"Game reset!\");\n}\n```\n\n`resetGame()`\n\nwill get called after a collision (we’ll add obstacles in the next\nstage) so the player can immediately start again without refreshing.\n\nGame loop\n\nFinally we wire up the game loop plus a quick API call that proves the server route we built in Stage 1 still works:\n\n```\ngameLoop() {\n  this.updatePhysics();\n  this.render();\n  requestAnimationFrame(() => this.gameLoop());\n}\n```\n\n`requestAnimationFrame`\n\nkeeps our update/draw cycle locked to the browser’s\nrefresh rate, and the `load`\n\nhandler ensures the DOM is fully ready before\nquerying elements.\n\nInstantiating the game\n\nAt the very bottom of `public/js/game.js`\n\n, outside the `DinoGame`\n\nclass, add the\nfollowing lines to create a new instance of the game. We’ll also add a quick\nhealth check to the server API we built in Stage 1:\n\n``` js\nasync function checkHealth() {\n  try {\n    const response = await fetch(\"/api/health\");\n    const data = await response.json();\n    console.log(\"Server health check:\", data);\n  } catch (error) {\n    console.error(\"Health check failed:\", error);\n  }\n}\n\nwindow.addEventListener(\"load\", () => {\n  checkHealth();\n  new DinoGame();\n  console.log(\"Stage 2 complete: Canvas game with controls ready!\");\n});\n```\n\nWhen we run `deno task dev`\n\nand point our browser to `localhost:8001`\n\n, we can\nsee our game!\n\nYou can also play it right now, but there are zero obstacles (yet).\n\nDeploy your updated game\n\nNow that we have a basic game loop, jumping dino, and score tracking, it’s time to deploy our updated game to the web! In your terminal you can run the deploy command again:\n\n```\ndeno deploy\n```\n\nThis will push your changes to Deno Deploy and give you a live URL where you can see your game in action.\n\nAt this point the dino can jump, land, and accrue points. In the next stage we’ll add obstacles and collision detection to create a proper game!\n\n*What are you building with Deno? Let us know on\nTwitter,\nBluesky, or\nDiscord.*", "url": "https://wpnews.pro/news/build-a-dinosaur-runner-game-with-deno-pt-2", "canonical_source": "https://deno.com/blog/build-a-game-with-deno-2", "published_at": "2025-12-15 15:00:00+00:00", "updated_at": "2026-05-22 12:21:22.858866+00:00", "lang": "en", "topics": ["developer-tools", "cloud-computing", "open-source"], "entities": ["Deno", "Deno Deploy"], "alternates": {"html": "https://wpnews.pro/news/build-a-dinosaur-runner-game-with-deno-pt-2", "markdown": "https://wpnews.pro/news/build-a-dinosaur-runner-game-with-deno-pt-2.md", "text": "https://wpnews.pro/news/build-a-dinosaur-runner-game-with-deno-pt-2.txt", "jsonld": "https://wpnews.pro/news/build-a-dinosaur-runner-game-with-deno-pt-2.jsonld"}}