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

推荐订阅源

D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
爱范儿
爱范儿
WordPress大学
WordPress大学
T
Tor Project blog
Jina AI
Jina AI
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
雷峰网
雷峰网
博客园 - 聂微东
B
Blog RSS Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements
有赞技术团队
有赞技术团队
GbyAI
GbyAI
aimingoo的专栏
aimingoo的专栏
U
Unit 42
博客园 - Franky
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
罗磊的独立博客
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
C
Check Point Blog
Martin Fowler
Martin Fowler
The Cloudflare Blog
N
Netflix TechBlog - Medium
小众软件
小众软件
T
Tailwind CSS Blog
T
The Blog of Author Tim Ferriss
月光博客
月光博客
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
H
Help Net Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
The Exploit Database - CXSecurity.com
S
Securelist
I
Intezer
Spread Privacy
Spread Privacy

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. 2 | 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. 3 | Deno
Jo Franchett · 2025-12-22 · 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

Obstacles and collision detection

In Stage 2, we set up the basic game loop and rendering for our dinosaur runner game, but it still isn’t really a game, there’s nothing to ‘play’. In Stage 3, we will introduce obstacles for the dino to jump over, as well as implement collision detection to determine if the dino hits an obstacle, and ends the game.

What you’ll Learn

By the end of this stage, you will have learned:

  • How to create and manage obstacles in the game
  • Implementing collision detection between the dino and obstacles
  • Updating game state based on collisions

Setting up the game state

We’re going to add some new properties to our game state to manage obstacles and scoring. Open up your game.js file and locate the constructor of your DinoGame class. In here we’ll add properties to the game state to set the initial game speed, the frame count that we’ll need to animate the dino and a high score tracker:

this.initialGameSpeed = 3;
this.frameCount = 0;
this.highScore = this.loadHighScore();

Scaffolding out the game methods

Our game currently has a startGame() and a resetGame() method. We’ll be adding several new methods to handle obstacle spawning, updating, collision detection, and scoring.

For now lets add empty method stubs to the class so we can fill them in later. Add these just below your existing methods:

gameOver() {}

loadHighScore() {}

saveHighScore() {}

updateHighScore() {}

drawGameOver() {}

The obstacle system

In game design terms, obstacles are typically represented as objects with properties such as position, size, and type. They have a bounding box that can be used for collision detection.

We’ll implement a simple obstacle system where obstacles are spawned at random intervals and move towards the dino. The player must jump over these obstacles to avoid collisions. As they successfully avoid obstacles, their score increases, and the game speed ramps up to increase difficulty.

In our game.js, lets add some more properties to the constructor to manage obstacles (these can go just before the this.init(); call):


this.obstacles = [];
this.obstacleSpawnTimer = 0;
this.obstacleSpawnRate = 120;
this.minObstacleSpawnRate = 60;

This creates an empty array to hold active obstacles, a timer to track when to spawn the next obstacle, and a spawn rate that determines how often obstacles appear. The minObstacleSpawnRate will ensure that obstacles don’t spawn too frequently as the game speeds up.

in the startGame() method, set the obstacle timer to zero so it starts fresh each round:

this.obstacleSpawnTimer = 0;

Spawning and updating obstacles

Obstacles are just rectangles that enter from the right edge and move left at the current game speed. Add these methods to the class, they can go below the jump() method:

spawnObstacle() {
    const obstacleTypes = [
        { width: 20, height: 40, type: "cactus-small" },
        { width: 25, height: 50, type: "cactus-medium" },
        { width: 30, height: 35, type: "cactus-wide" },
    ];

    const obstacle =
        obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];

    this.obstacles.push({
        x: this.canvas.width,
        y: this.groundY - obstacle.height,
        width: obstacle.width,
        height: obstacle.height,
        type: obstacle.type,
    });
}

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

    this.obstacleSpawnTimer++;
    if (this.obstacleSpawnTimer >= this.obstacleSpawnRate) {
        this.spawnObstacle();
        this.obstacleSpawnTimer = 0;
    }

    for (let i = this.obstacles.length - 1; i >= 0; i--) {
        this.obstacles[i].x -= this.gameSpeed;

        if (this.obstacles[i].x + this.obstacles[i].width < 0) {
            this.obstacles.splice(i, 1);
            this.score += 10;
        }
    }
}

Here, we set up some obstacle types with different sizes. The spawnObstacle() method randomly selects one and adds it to the obstacles array at the right edge of the canvas.

The updateObstacles() method increments the spawn timer and spawns a new obstacle when the timer exceeds the spawn rate. It also moves each obstacle left by the current game speed and removes any that have moved off-screen, awarding points for each successfully avoided obstacle.

Collision detection and difficulty ramping

Collision detection is how games determine if two objects have come into contact. We can check the bounding boxes of the dino and each obstacle to see if they overlap. If they do, the game ends.

Collision detection just compares the dino’s rectangle with each obstacle’s rectangle. If they overlap, the run ends. Immediately after adding updateObstacles, drop in these helpers:

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

    for (let obstacle of this.obstacles) {
        const isOverlapping =
            this.dino.x < obstacle.x + obstacle.width &&
            this.dino.x + this.dino.width > obstacle.x &&
            this.dino.y < obstacle.y + obstacle.height &&
            this.dino.y + this.dino.height > obstacle.y;

        if (isOverlapping) {
            this.gameOver();
            return;
        }
    }
}

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

    const difficultyLevel = Math.floor(this.score / 200);
    this.gameSpeed = this.initialGameSpeed + difficultyLevel * 0.5;
    this.obstacleSpawnRate = Math.max(
        this.minObstacleSpawnRate,
        120 - difficultyLevel * 10,
    );
}

The difficultyLevel calculation increments every ~200 points, increasing both speed and spawn frequency so the game keeps getting harder the longer the player survives.

Drawing obstacles

To make the cacti feel less repetitive, each obstacle type gets a slightly different silhouette. Add this method to the class, it can go just below the drawDino() method:

drawObstacles() {
    this.ctx.fillStyle = "olive";

    for (let obstacle of this.obstacles) {
        this.ctx.fillRect(
            obstacle.x,
            obstacle.y,
            obstacle.width,
            obstacle.height,
        );

        this.ctx.fillStyle = "darkolivegreen";
        if (obstacle.type === "cactus-small") {
            this.ctx.fillRect(obstacle.x - 3, obstacle.y + 10, 6, 4);
            this.ctx.fillRect(obstacle.x + obstacle.width - 3, obstacle.y + 20, 6, 4);
        } else if (obstacle.type === "cactus-medium") {
            this.ctx.fillRect(obstacle.x - 4, obstacle.y + 8, 8, 6);
            this.ctx.fillRect(obstacle.x + obstacle.width - 4, obstacle.y + 15, 8, 6);
            this.ctx.fillRect(obstacle.x + obstacle.width / 2 - 2, obstacle.y + 25, 4, 8);
        } else if (obstacle.type === "cactus-wide") {
            this.ctx.fillRect(obstacle.x - 5, obstacle.y + 5, 10, 8);
            this.ctx.fillRect(obstacle.x + obstacle.width - 5, obstacle.y + 10, 10, 8);
            this.ctx.fillRect(obstacle.x + obstacle.width / 2 - 3, obstacle.y + 20, 6, 6);
        }

        this.ctx.fillStyle = "olive";
    }
}

Animating the dino

The dino has two little legs that we can move up and down as it runs to make a very simple running animation. Update the drawDino() method to the following:

drawDino() {
    const legOffset = this.gameState === "playing" && !this.dino.isJumping
        ? (Math.floor(this.frameCount / 10) % 2) * 2
        : 0;

    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 + legOffset,
            6,
            8 - legOffset,
        );
        this.ctx.fillRect(
            this.dino.x + 24,
            this.dino.y + 40 - legOffset,
            6,
            8 + legOffset,
        );
    }
}

Rendering the game components

We now need to update the render() method to draw the obstacles and the dino, and to overlay the instruction and game-over screens when appropriate:

Update the render() method so it clears the canvas, calls drawObstacles(), then drawDino(), and overlays the instruction and game-over screens when appropriate:

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

    
    this.drawObstacles();

    
    this.drawDino();

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

    
    if (this.gameState === "gameOver") {
      this.drawGameOver();
    }
  }

Hooking it up to physics, game start and reset

We now need to update the startGame() and resetGame() methods to ensure the obstacle system is properly initialized and cleared when starting or resetting the game.

Update the startGame() method to reset the obstacles array when the game starts, and set the game speed and frame count to their initial values:

startGame() {
  this.gameState = "playing";
  this.score = 0;
  this.gameSpeed = this.initialGameSpeed;
  this.obstacles = [];
  this.obstacleSpawnTimer = 0;
  this.frameCount = 0;
  this.updateScore();
  this.updateStatus("");
}

Then update the resetGame() method to clear out any leftover obstacles so restarting feels instant:

resetGame() {
  this.gameState = "waiting";
  this.score = 0;
  this.gameSpeed = this.initialGameSpeed;
  this.obstacles = [];
  this.obstacleSpawnTimer = 0;
  this.frameCount = 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!");
}

And finally, add the obstacle updates and collision checks to the updatePhysics() method:

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

    this.frameCount++;

    
    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();

    
    this.updateObstacles();

    
    this.checkCollisions();

    
    this.updateGameDifficulty();
  }

Game over

So far we have the dino jumping and obstacles moving, but nothing happens when they collide. Let’s fix that!

When a collision is detected, we need to end the game and display the game over screen. Update the gameOver() method stub, to set the game state to “gameOver”, save the high score, and log a message:

gameOver() {
   this.gameState = "gameOver";
   this.saveHighScore();
   this.updateHighScore();
   this.updateStatus("Game Over! Click to restart");
   console.log(`Game Over! Final Score: ${Math.floor(this.score)}`);
 }

Next we’ll update the drawGameOver() method to overlay a simple game over message on the canvas:

drawGameOver() {
    
    this.ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
    this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

    
    this.ctx.fillStyle = "white";
    this.ctx.font = "36px Arial";
    this.ctx.textAlign = "center";
    this.ctx.fillText(
      "GAME OVER",
      this.canvas.width / 2,
      this.canvas.height / 2 - 40,
    );

    
    this.ctx.font = "20px Arial";
    this.ctx.fillText(
      `Final Score: ${Math.floor(this.score)}`,
      this.canvas.width / 2,
      this.canvas.height / 2 - 5,
    );

    
    if (Math.floor(this.score) === this.highScore && this.highScore > 0) {
      this.ctx.fillStyle = "gold";
      this.ctx.fillText(
        "🏆 NEW HIGH SCORE! 🏆",
        this.canvas.width / 2,
        this.canvas.height / 2 + 25,
      );
    } else if (this.highScore > 0) {
      this.ctx.fillStyle = "#CCCCCC";
      this.ctx.fillText(
        `High Score: ${this.highScore}`,
        this.canvas.width / 2,
        this.canvas.height / 2 + 25,
      );
    }

    
    this.ctx.fillStyle = "#FFFFFF";
    this.ctx.font = "16px Arial";
    this.ctx.fillText(
      "Click or press SPACE to restart",
      this.canvas.width / 2,
      this.canvas.height / 2 + 55,
    );
  }

High scores

We want players to feel rewarded for their best runs, so let’s implement a simple high score system using localStorage to store scores. We’ll create some methods to load, save, and update the high score, as well as modify the game over and reset logic to incorporate high score tracking. Update the stubbed methods to your DinoGame class:

loadHighScore() {
    return parseInt(localStorage.getItem("dinoHighScore")) || 0;
}

saveHighScore() {
    if (Math.floor(this.score) > this.highScore) {
        this.highScore = Math.floor(this.score);
        localStorage.setItem("dinoHighScore", this.highScore);
        console.log(`New High Score: ${this.highScore}!`);
    }
}

updateHighScore() {
    if (this.highScoreElement) {
        this.highScoreElement.textContent = this.highScore;
    }
}

Now we need to update the init() method to call the updateHighScore() method to and update the high score display when the game starts, add the following line to the init() method:

Finalizing Stage 3

Outside the class we keep the same health check from Stage 2 so you can verify the API route is alive when the page loads:

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 3 complete: Full game with obstacles and collision detection!",
  );
});

When you run your game, you should see this!

The game now has obstacles and collision detection!

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 following command to update the project you previously deployed in Stage 2:

Once deployed, you’ll have a fully functional dinosaur runner game where the dino can jump over obstacles, and the game ends upon collision. You’ll also have a URL that you can share to let others play your game!

Stage 4 is coming up soon, where we’ll connect the game to a backend so you can save scores server-side and open the door to multiplayer leaderboards. It’s going to get competitive!

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