DEV Community

loading
loading

Posted on

Pong using Deno and caviar as a native app using WebGPU

Recently Deno has been taking the world by storm. Deno is a fast, secure, and open-source runtime environment created as a replacement for Node.js. In this tutorial we will create a basic pong game using the Caviar library which uses WebGPU bindings and resembles most javascript game engines. Feel free to check out caviar here.

you can find the final version in the examples repo right here aswell.

lets get started shall we?

Project Setup

for this project all we'll need is a file hierarchy as such

├── pong
│   ├── src
│   │   ├── scenes
│   │   │   ├── Game.ts
│   ├── main.ts
Enter fullscreen mode Exit fullscreen mode

lets start by creating a basic setup in main.ts

import { World } from 'https://deno.land/x/caviar/mod.ts';
import { Game } from './src/scenes/Game.ts';

const pong = new World({
    title: "test",
    width: 1300,
    height: 800,
    resizable: true,
}, [Game]);
await pong.start();
Enter fullscreen mode Exit fullscreen mode

after this we will no longer need to touch main.ts and we'll direct our attention to the Game.ts file

Setting up our Components

in the Game.ts file we'll add the following code to create our first player

import { PICO8, Scene, TextureSprite } from 'https://deno.land/x/caviar/mod.ts';

export class Game extends Scene {
    public p1: TextureSprite | undefined;
    public setup() {
      this.p1 = new TextureSprite(this, 0, 336, {
        data: [
          ".9.",
          ".9.",
          ".9.",
        ],
        pixelWidth: 32,
        pixelHeight: 32,
        palette: PICO8,
      })
      this.addChild(this.p1);
    }
    public update() {

    }

}
Enter fullscreen mode Exit fullscreen mode

we create a new texture sprite with 3 pixels down and we use the built-in PICO8 palette.
if we run our code using deno run -A --unstable main.ts we should get a window looking something like screenshot1
we now create a second player and a ball the same way

export class Game extends Scene {
    public ball: TextureSprite | undefined;
    public p1: TextureSprite | undefined;
    public p2: TextureSprite | undefined;
    public setup() {
      this.p1 = new TextureSprite(this, 0, 336, {
        data: [
          ".9.",
          ".9.",
          ".9.",
        ],
        pixelWidth: 32,
        pixelHeight: 32,
        palette: PICO8,
      });
      this.p2 = new TextureSprite(this, 1168, 336, {
        data: [
          ".A.",
          ".A.",
          ".A.",
        ],
        pixelWidth: 32,
        pixelHeight: 32,
        palette: PICO8,
      });
      this.ball = new TextureSprite(this, 568, 336, {
        data: [
          "E",
        ],
        pixelWidth: 32,
        pixelHeight: 32,
        palette: PICO8,
      });
      this.addChild(this.p1);
      this.addChild(this.p2);
      this.addChild(this.ball);
    }
    public update() {

    }

}
Enter fullscreen mode Exit fullscreen mode

screenshot2

Movement

to listen for keypresses in Caviar you need to define which keys you want to listen for, do this at the beginning of the setup method. In this tutorial we'll listen for W,S,E and D.

 ...
public setup() {
    this.setKeys(['W','S','E','D']);
    ...
Enter fullscreen mode Exit fullscreen mode

next we'll create a keyDown method and check for each key and change the player's positions based on the key pressed

...
public keyDown(key: any) {
    const p1 = this.p1 as TextureSprite;
    const p2 = this.p2 as TextureSprite;
    switch (key) {
      case "W":
        if (p1.y > 25) p1.setY(p1.y - 4);
        break;
      case "S":
        if (p1.y < 700) p1.setY(p1.y + 4);
        break;
      case "E":
        if (p2.y > 25) p2.setY(p2.y - 4);
        break;
      case "D":
        if (p2.y < 700) p2.setY(p2.y + 4);
        break;
    }
  }
Enter fullscreen mode Exit fullscreen mode

screenshot3

Ball Movement

first lets create 2 new properties vx and vy for the balls velocity and why not also make a score property aswell

...
public vx = 2;
public vy = 2;
public score: number[] = [0,0];
...
Enter fullscreen mode Exit fullscreen mode

now we add the ball physics to the update function

...
public update() {
    const ball = this.ball as TextureSprite;
    const p1 = this.p1 as TextureSprite;
    const p2 = this.p2 as TextureSprite;
    if (ball.y > 25 || ball.y < 10) { 
      this.vy *= -1;
    }

    if ( 
      ball.x < p1.x + 32 + 10 &&
      ball.y > p1.y &&
      ball.y < p1.y + 96
    ) {
      this.vx *= -1.1;
      this.vy = Math.floor(Math.random() * 8) - 4; 
    }

    if (
      ball.x > p2.x - 10 && 
      ball.y > p2.y &&
      ball.y < p2.y + p2.height
    ) {
      this.vx *= -1.1;
      this.vy = Math.floor(Math.random() * 8) - 4; 
    }
    if (ball.y < 25 || ball.y > 800) {
      this.vy *= -1;
    }
    if (ball.x < 25) {
      //p1 side
      ball.setX(568);
      ball.setY(336);
      this.score[1] += 1;
      this.vx = 4;
      this.vy = 4;
    }
    if (ball.x > 1168) {
      //p2 side
      ball.setX(568);
      ball.setY(336);
      this.score[0] += 1;
      this.vx = -4;
      this.vy = 4;
    } 
    ball.setX(ball.x + this.vx);
    ball.setY(ball.y + this.vy);

  }
...
Enter fullscreen mode Exit fullscreen mode

demo
now the game should be working.

Conclusion

Caviar is currently only native (time of writing) but we plan to implement cross platform features in the future. feel free to contribute here

Top comments (0)