惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
小众软件
小众软件
J
Java Code Geeks
V
Visual Studio Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
博客园 - 叶小钗
N
Netflix TechBlog - Medium
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
T
The Exploit Database - CXSecurity.com
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
Hacker News: Ask HN
Hacker News: Ask HN
Hacker News - Newest:
Hacker News - Newest: "LLM"
AWS News Blog
AWS News Blog
Webroot Blog
Webroot Blog
The Last Watchdog
The Last Watchdog
T
Threatpost
I
Intezer
T
Tenable Blog
L
LINUX DO - 热门话题
T
Tailwind CSS Blog
Scott Helme
Scott Helme
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cybersecurity and Infrastructure Security Agency CISA
Engineering at Meta
Engineering at Meta
S
Schneier on Security
Recent Announcements
Recent Announcements
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
F
Fortinet All Blogs
腾讯CDC
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
量子位
H
Hacker News: Front Page
V2EX - 技术
V2EX - 技术
Google Online Security Blog
Google Online Security Blog
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
博客园 - Franky
www.infosecurity-magazine.com
www.infosecurity-magazine.com

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Zero-config Debugging with Deno and OpenTelemetry | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Build a Typesafe API with tRPC and Deno | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno How to add Monaco to a Next.js app and securely run untrusted user code | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno Webhooks suck, but here are alternatives | Deno
Build a dinosaur runner game with Deno, pt. 2 | Deno
Jo Franchett · 2025-12-15 · via Deno

This series of blog posts will guide you through building a simple browser-based dinosaur runner game using Deno.

  1. Setup a basic project
  2. Game loop, canvas, and controls
  3. Obstacles and collision detection
  4. Databases and global leaderboards
  5. Player profiles, customization, and live tuning
  6. Observability, metrics, and alerting

Setting up the Game Loop, Canvas & Controls

In Stage 1, we set up the foundation of the Dino Runner game: an HTTP server with static file serving, and a basic page which we deployed to the web with Deno Deploy.

Stage 2 is where we’ll start to bring the game to life.

We’ll turn that simple landing page into a moving canvas with a jumping character with keyboard and mouse controls. We’ll also learn about game loops using requestAnimationFrame.

Keep reading and build along or view the entire source here.

What you’ll Learn

By the end of this stage you will have:

  • Set up an HTML5 canvas as the main game area
  • Implemented a game loop using requestAnimationFrame
  • Added keyboard and mouse/touch input for jumping
  • Built a basic physics system (gravity + jumping)
  • Deployed your new project to the cloud.

Project structure

Stage 2 uses a simple but realistic full-stack layout, building on what we set up in Stage 1:

Runner Game/
├── src/                    # Server-side source code
│   ├── main.ts             # Server entry point
│   └── routes/             # Route definitions
│       └── api.routes.ts   # API route definitions
├── public/                 # Client-side static files
│   ├── index.html          # Main landing page & game canvas
│   ├── js/
│   │   └── game.js         # Client-side game logic
│   └── css/
│       └── styles.css      # Styling
├── deno.json               # Deno configuration
└── .env                    # Environment variables
  • Server-side code (src/) is written in TypeScript and served by Deno + Oak.
  • Client-side (public/) is where your game canvas, JS game engine and CSS live.
  • deno.json and env files keep things easy to configure and deploy.

Setting up the HTML5 Canvas

The core of the game will be rendered inside an HTML5 <canvas> element. This allows us to draw graphics dynamically using JavaScript.

In stage 1 we wrote a very basic index.html page. Now, we will enhance it to include the game canvas and some UI elements. Replace the internals of the <body> of your public/index.html with the following:

<main>
  <h1>🦕 Dino Runner</h1>
  <section class="container canvas-container">
    <canvas id="gameCanvas" width="800" height="200"></canvas>
  </section>
</main>

We want this to feel like a game, so let’s also add a score display and a ‘Click to Start’ message. Directly after the <canvas> tag, inside the <section> element, add the following HTML:

<div class="game-ui">
  <div class="score">Score: <span id="score">0</span></div>
  <div class="game-status" id="gameStatus">Click to Start!</div>
</div>

The class attributes on these elements will allow us to style them later with CSS and the ids will let us update them dynamically with JavaScript.

Finally, lets add some instructions for the player, to show which keys to use. Below the canvas-container section, add this HTML:

<section class="container">
  <h3>Controls</h3>
  <div class="control-grid">
    <kbd>Space</kbd>
    <kbd></kbd>
    <span class="click">️Click</span>
    <span>Jump</span>
    <span>Jump</span>
    <span>Start/Jump</span>
  </div>
</section>

You can now serve your game locally with deno task dev and open it in your browser. You should see a blank canvas and some basic text elements. Very boring, lets style it up!

Styling the Game UI

Next, let’s add some basic styles to make our game look a bit nicer. Open public/css/styles.css and add the following CSS:

:root {
  --primary: #66c2ff;
  --primary-dark: #0077cc;
  --secondary: #cbd1e1;
  --tertiary: #e5e9f2;
  --bg: #f1f3f9;
  --dark: #1e253b;
}

html {
  box-sizing: border-box;
}
*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 0 auto;
  padding: 1rem;
  background-color: var(--bg);
  color: var(--dark);
  line-height: 1.6;
}

h1 {
  margin: 1.5rem auto;
  color: var(--dark);
}

h2,
h3 {
  margin: 1rem 0;
  color: var(--dark);
  text-align: center;
}



.container {
  margin-bottom: 1.5rem;
  padding: 1rem;
  border: 2px solid #cbd1e1;
  border-radius: 1px;
  background: white;
  box-shadow: 2px 4px 0px 0px var(--tertiary);
  text-align: center;
}

.canvas-container {
  position: relative;
  padding: 1rem;
  display: inline-block;
  width: calc(804px + 2rem); 
  max-width: 100%;
}

.status {
  background-color: #eee;
  border-color: var(--primary);
}

#gameCanvas {
  display: block;
  background: linear-gradient(
    to bottom,
    SkyBlue 0%,
    SkyBlue 79%,
    SandyBrown 79%,
    SandyBrown 100%
  );
  cursor: pointer;
}

.game-ui {
  position: absolute;
  top: 15px;
  left: 15px;
  right: 15px;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  pointer-events: none;
}

.score,
.game-status {
  padding: 0.3rem 1rem;
  border: 2px solid #cbd1e1;
  border-radius: 1px;
  background: white;
  box-shadow: 2px 4px 0px 0px rgba(0, 0, 0, 0.2);
  text-align: center;
  font-family: var(--font-serif);
  text-decoration: none;
}

.score {
  min-width: 120px;
  text-align: left;
}

.game-status {
  background: var(--primary);
  border-color: var(--primary-dark);
  border-radius: 4px;
  box-shadow: 2px 4px 0px 0px rgba(0, 0, 0, 0.2);
}

.control-grid {
  display: grid;
  justify-content: center;
  place-items: center;
  gap: 1rem;
  grid-template-columns: repeat(3, 100px);
  margin: 1rem auto;
}

kbd {
  background: #f0f0f0;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  width: max-content;
}

.click {
  padding: 3px 0px;
}


@media (max-width: 860px) {
  #gameCanvas {
    width: 100%;
    max-width: 800px;
    height: auto;
  }

  .canvas-container {
    width: 100%;
    box-sizing: border-box;
  }

  .control-grid {
    flex-direction: column;
    align-items: center;
  }
}

@media (max-width: 600px) {
  body {
    padding: 0;
  }
}

You can of course customize these styles and make the game your own.

Blank canvas of the game

Now its time for the fun part - adding the game logic with JavaScript!

The game engine

The core of our game logic will live in public/js/game.js. This file will handle rendering to the canvas, processing user input, and managing game state.

Let’s start by setting up the basic structure of our game engine. Open public/js/game.js and add the following code:

console.log("Dinosaur Runner Game - Stage 2");

class DinoGame {
  constructor() {
    this.canvas = document.getElementById("gameCanvas");
    this.ctx = this.canvas.getContext("2d");
    this.scoreElement = document.getElementById("score");
    this.statusElement = document.getElementById("gameStatus");

    
    this.gameState = "waiting"; 
    this.score = 0;
    this.gameSpeed = 2;

    
    this.dino = {
      x: 50,
      y: 150,
      width: 40,
      height: 40,
      velocityY: 0,
      isJumping: false,
      groundY: 150,
    };

    
    this.gravity = 0.6;
    this.jumpStrength = -12;

    
    this.groundY = 180;

    this.init();
  }

  init() {
    this.setupEventListeners();
    this.gameLoop();
    this.updateStatus("Click to Start!");
  }
}

We’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.

This skeleton wires up the canvas, tracks a single dino object, and immediately calls init() so the rest of our hooks run once the class is instantiated. Make sure public/index.html loads this script by ensuring the following line is before the closing </body> tag:

<script type="module" src="/js/game.js"></script>

Now we can implement each method in small, testable chunks.

Game loops, frames and animation

The ‘game loop’ is the heartbeat of the game. It loops once a frame to continuously update the game state and render the scene. We’ll use requestAnimationFrame to create a smooth loop that loops in time with the browser’s refresh rate.

Frames 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.

We already called this.gameLoop() in the constructor, next we’ll implement all of the mechanics to implement a functioning game loop.

Player input and event listeners

We want both keyboard and mouse/touch controls, for multiple device support. We’ll set up event listeners for keydown and click to handle these inputs.

Append these methods inside the DinoGame class:

setupEventListeners() {
  document.addEventListener("keydown", (e) => {
    if (e.code === "Space" || e.code === "ArrowUp") {
      e.preventDefault();
      this.handleJump();
    }
  });

  this.canvas.addEventListener("click", () => {
    this.handleJump();
  });
}

handleJump() {
  if (this.gameState === "waiting") {
    this.startGame();
  } else if (this.gameState === "playing" && !this.dino.isJumping) {
    this.jump();
  } else if (this.gameState === "gameOver") {
    this.resetGame();
  }
}

setupEventListeners() keeps things device-agnostic and prevents the space bar from scrolling the page mid-game. handleJump() is our state gatekeeper: it either starts the run, triggers a jump, or resets the game after a crash.

Starting the run, jumping, and physics

Next 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.

On game start, we reset the score to 0, update the UI to reflect that and we set a game state to "playing".

On 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.

When jumping we also apply gravity to bring the dino back down, and check for ground collision to reset its position.

For every completed jump, we’ll increment the score slightly to reward survival time.

Add these methods below the previous ones, still inside the DinoGame class:

startGame() {
  this.gameState = "playing";
  this.score = 0;
  this.updateScore();
  this.updateStatus("");
  console.log("Game started!");
}

jump() {
  if (!this.dino.isJumping) {
    this.dino.velocityY = this.jumpStrength;
    this.dino.isJumping = true;
    console.log("Dino jumped!");
  }
}

updatePhysics() {
  if (this.gameState !== "playing") return;

  this.dino.velocityY += this.gravity;
  this.dino.y += this.dino.velocityY;

  if (this.dino.y >= this.dino.groundY) {
    this.dino.y = this.dino.groundY;
    this.dino.velocityY = 0;
    this.dino.isJumping = false;
  }

  this.score += 0.1;
  this.updateScore();
}

Gravity slowly increases velocityY, the ground check clamps the dinosaur back to the floor, and the score ticks up a tiny amount every frame so longer survival means higher points. Now we need to implement the rendering logic.

Rendering the scene

Next we handle drawing to the canvas, first we completely clear the canvas, then we paint the dino, and overlay instructions while waiting to start.

Game graphics are drawn to the canvas with the ctx (context) object we grabbed right at the start. We clear the canvas each frame, draw the ground line, then draw the dino at its current position.

The ‘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.

render() {
  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

  this.drawDino();

  if (this.gameState === "waiting") {
    this.drawInstructions();
  }
}

drawDino() {
  const strideActive = this.gameState === "playing" && !this.dino.isJumping;
  const legStride = strideActive ? (Math.floor(this.frameCount / 8) % 2 === 0 ? 2 : -2) : 0;
  const legBaseY = this.dino.y + this.dino.height - 2;

  this.ctx.fillStyle = "green";
  this.ctx.fillRect(
    this.dino.x,
    this.dino.y,
    this.dino.width,
    this.dino.height,
  );

  this.ctx.fillStyle = "darkgreen";
  this.ctx.fillRect(this.dino.x + 25, this.dino.y + 8, 4, 4);
  this.ctx.fillRect(this.dino.x + 30, this.dino.y + 20, 8, 2);

  if (!this.dino.isJumping) {
    this.ctx.fillStyle = "green";
    this.ctx.fillRect(this.dino.x + 10, this.dino.y + 40, 6, 8);
    this.ctx.fillRect(this.dino.x + 24, this.dino.y + 40, 6, 8);
  }
}

drawInstructions() {
  this.ctx.fillStyle = "black";
  this.ctx.font = "24px Arial";
  this.ctx.textAlign = "center";
  this.ctx.fillText(
    "Press SPACE or ↑ to jump!",
    this.canvas.width / 2,
    this.canvas.height / 2 - 20,
  );

  this.ctx.font = "16px Arial";
  this.ctx.fillText(
    "Click anywhere to start",
    this.canvas.width / 2,
    this.canvas.height / 2 + 10,
  );
}

Because we render every animation frame, keeping drawDino() small makes tweaks easy. In future we could add new colors, simple animations or sprites.

UI helpers and resetting

When the game state changes we update the DOM elements to reflect those changes, for example game reset and score updates. Add the following methods inside the DinoGame class:

updateScore() {
  this.scoreElement.textContent = Math.floor(this.score);
}

updateStatus(message) {
  this.statusElement.textContent = message;
  this.statusElement.style.display = message ? "block" : "none";
}

resetGame() {
  this.gameState = "waiting";
  this.score = 0;
  this.dino.y = this.dino.groundY;
  this.dino.velocityY = 0;
  this.dino.isJumping = false;
  this.updateScore();
  this.updateStatus("Click to Start!");
  console.log("Game reset!");
}

resetGame() will get called after a collision (we’ll add obstacles in the next stage) so the player can immediately start again without refreshing.

Game loop

Finally we wire up the game loop plus a quick API call that proves the server route we built in Stage 1 still works:

gameLoop() {
  this.updatePhysics();
  this.render();
  requestAnimationFrame(() => this.gameLoop());
}

requestAnimationFrame keeps our update/draw cycle locked to the browser’s refresh rate, and the load handler ensures the DOM is fully ready before querying elements.

Instantiating the game

At the very bottom of public/js/game.js, outside the DinoGame class, add the following lines to create a new instance of the game. We’ll also add a quick health check to the server API we built in Stage 1:

async function checkHealth() {
  try {
    const response = await fetch("/api/health");
    const data = await response.json();
    console.log("Server health check:", data);
  } catch (error) {
    console.error("Health check failed:", error);
  }
}

window.addEventListener("load", () => {
  checkHealth();
  new DinoGame();
  console.log("Stage 2 complete: Canvas game with controls ready!");
});

When we run deno task dev and point our browser to localhost:8001, we can see our game!

Game is ready

You can also play it right now, but there are zero obstacles (yet).

Deploy your updated game

Now 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:

This will push your changes to Deno Deploy and give you a live URL where you can see your game in action.

At 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!

What are you building with Deno? Let us know on Twitter, Bluesky, or Discord.