portals in javascript games
Tags: javascript, webdev, tutorial, gamedev
Published: 2022-04-23 07:17:56

I made a square surface in which all 4 sides are portals to the opposite side just like the 2d representation of a torus surface using only html, css and vanilla javascript.
If you would like  a video representation of the same here is a link to the video:
[](https://youtu.be/7f_UdDlyGho)

## <u>the html</u>
we start by doing a basic setup of the html and adding a html5 canvas tag

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TORUS</title>
    <link rel="stylesheet" href="style.css">
    <script src="script.js" defer></script>
</head>
<body>
    <canvas id="canvas1"></canvas>
</body>
</html>
```

## <u>The css</u>
We also setup the css by setting the margins, paddings and box sizing.

```
*{
    margin:0;
    padding:0;
    box-sizing:border-box;
    font-family: 'Courier New', Courier, monospace;
}
```

We also set up  the body to ensure the canvas element is centered

```
body{
    background-color: #c4fdfd4b;
    width: 100vw;
    height: 100vh;
    display:flex;
    justify-content: center;
    align-items: center;
}
```
We complete the css setup by setting the canvas element

```
#canvas1{
    background-color: #f7debfa6;
    position: relative;
    height:500px;
    width:700px;
    top:0;
    left:0;
    border:solid black 2px;
}
```

## <u>The js</u>

## canvas sizing
We begin by sizing the canvas by setting its size the same to the height and width written in the css

```
const canvas = document.getElementById('canvas1')
const ctx = canvas.getContext('2d')
canvas.width=700
canvas.height=500
```

## Controls
We set the variables to control the motion of the ball

```
let dx = 2
let dy = 2
document.addEventListener('keydown',function(e){
    switch(e.code){
        case 'ArrowUp':
            dy=-2
            break
        case 'ArrowLeft':
            dx=-2
            break
        case 'ArrowDown':
            dy=2
            break
        case 'ArrowRight':
            dx=2
            break
    }
})
```

## ball class
The class to make the ball

```
class Ball{
    constructor(){
        this.radius=50
        this.x=canvas.width/2
        this.y=canvas.height/2
        this.xpos1=this.x
        this.xpos2=this.x
        this.ypos1=this.y
        this.ypos2=this.y
    }
    update(){
        this.x+=dx
        this.y+=dy
        this.xpos1+=dx
        this.xpos2+=dx
        this.ypos1+=dy
        this.ypos2+=dy

        ///////////////////////////////////////
        if(this.x>=canvas.width+this.radius && dx==2){
            this.x=this.x-canvas.width
        }else if(this.x<=-this.radius && dx==-2){
            this.x=this.x+canvas.width
        }
        ///////////////////////////////////////
        if(this.y>=canvas.height+this.radius && dy==2){
            this.y=this.y-canvas.height
        }else if(this.y<=-this.radius && dy==-2){
            this.y=this.y+canvas.height
        }
    }
    draw(){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
}
```

## animate function
This is the function that moves the ball

```
let ball1 = new Ball()
function animateMotion(){
    ctx.clearRect(0,0,canvas.width,canvas.height)
    ball1.draw()
    ball1.update()
    requestAnimationFrame(animateMotion)
}
animateMotion()

```

## duplicate method
We add a duplicate method to the ball class  to duplicate the ball as it goes through the portal

```
class Ball{
    constructor(){
        this.radius=50
        this.x=canvas.width/2
        this.y=canvas.height/2
        this.xpos1=this.x
        this.xpos2=this.x
        this.ypos1=this.y
        this.ypos2=this.y
    }
    update(){
        this.x+=dx
        this.y+=dy
        this.xpos1+=dx
        this.xpos2+=dx
        this.ypos1+=dy
        this.ypos2+=dy

        ///////////////////////////////////////
        if(this.x>=canvas.width+this.radius && dx==2){
            this.x=this.x-canvas.width
        }else if(this.x<=-this.radius && dx==-2){
            this.x=this.x+canvas.width
        }
        ///////////////////////////////////////
        if(this.y>=canvas.height+this.radius && dy==2){
            this.y=this.y-canvas.height
        }else if(this.y<=-this.radius && dy==-2){
            this.y=this.y+canvas.height
        }
    }
    draw(){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
duplicate(xcor,ycor){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
}
```

## clone tracking
This is to know when the ball is in contact with the portal so it can make the clone. It is added to the update method in the ball class

```
class Ball{
    constructor(){
        this.radius=50
        this.x=canvas.width/2
        this.y=canvas.height/2
        this.xpos1=this.x
        this.xpos2=this.x
        this.ypos1=this.y
        this.ypos2=this.y
    }
    update(){
        this.x+=dx
        this.y+=dy
        this.xpos1+=dx
        this.xpos2+=dx
        this.ypos1+=dy
        this.ypos2+=dy

        ///////////////////////////////////////
        if(this.x>=canvas.width+this.radius && dx==2){
            this.x=this.x-canvas.width
        }else if(this.x<=-this.radius && dx==-2){
            this.x=this.x+canvas.width
        }
                
        if(this.x>=canvas.width-this.radius){
            this.xpos1=this.x-canvas.width
        }else if(this.x<=this.radius){
            this.xpos2=this.x+canvas.width
        }else{
            this.xpos1=this.x
            this.xpos2=this.x
        }
        ///////////////////////////////////////
        if(this.y>=canvas.height+this.radius && dy==2){
            this.y=this.y-canvas.height
        }else if(this.y<=-this.radius && dy==-2){
            this.y=this.y+canvas.height
        }

        if(this.y>=canvas.height-this.radius){
            this.ypos1=this.y-canvas.height
        }else if(this.y<=this.radius){
            this.ypos2=this.y+canvas.height
        }else{
            this.ypos1=this.y
            this.ypos2=this.y
        }
    }
    draw(){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
    duplicate(xcor,ycor){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
}
```

## clone conditions
This are the conditions that have to be met for the clone to be created.  This is added to the update method.

```
class Ball{
    constructor(){
        this.radius=50
        this.x=canvas.width/2
        this.y=canvas.height/2
        this.xpos1=this.x
        this.xpos2=this.x
        this.ypos1=this.y
        this.ypos2=this.y
    }
    update(){
        this.x+=dx
        this.y+=dy
        this.xpos1+=dx
        this.xpos2+=dx
        this.ypos1+=dy
        this.ypos2+=dy

        ///////////////////////////////////////
        if(this.x>=canvas.width+this.radius && dx==2){
            this.x=this.x-canvas.width
        }else if(this.x<=-this.radius && dx==-2){
            this.x=this.x+canvas.width
        }
                
        if(this.x>=canvas.width-this.radius){
            this.xpos1=this.x-canvas.width
        }else if(this.x<=this.radius){
            this.xpos2=this.x+canvas.width
        }else{
            this.xpos1=this.x
            this.xpos2=this.x
        }
        ///////////////////////////////////////
        if(this.y>=canvas.height+this.radius && dy==2){
            this.y=this.y-canvas.height
        }else if(this.y<=-this.radius && dy==-2){
            this.y=this.y+canvas.height
        }

        if(this.y>=canvas.height-this.radius){
            this.ypos1=this.y-canvas.height
        }else if(this.y<=this.radius){
            this.ypos2=this.y+canvas.height
        }else{
            this.ypos1=this.y
            this.ypos2=this.y
        }

        ///////////////////////////////////////////////////////////////////
         if(this.x>=canvas.width-this.radius || this.x<=this.radius || this.y>=canvas.height-this.radius || this.y<=this.radius ){
            this.duplicate(this.xpos1,this.ypos1)
            this.duplicate(this.xpos2,this.ypos2)
            this.duplicate(this.xpos1,this.ypos2)
            this.duplicate(this.xpos2,this.ypos1)
        }
    }
    draw(){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(this.x,this.y,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
    duplicate(xcor,ycor){
        ctx.lineWidth=2
        ctx.strokeStyle='black'
        ctx.fillStyle='green'
        ctx.beginPath()
        ctx.arc(xcor,ycor,this.radius,0,Math.PI*2)
        ctx.stroke()
        ctx.closePath()
        ctx.fill()
    }
}
```