DEV Community

Cover image for portals in javascript games
noobjonh
noobjonh

Posted on

portals in javascript games

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:

the html

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>
Enter fullscreen mode Exit fullscreen mode

The css

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

The js

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
Enter fullscreen mode Exit fullscreen mode

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
    }
})
Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)