I draw my snake in P5js :)

Legos LightLegos Light
3 min read

I wrote a simple snake using p5js library.

Demo

The code

Simply begin with an index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>snake fish and more</title>
</head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
    <script src="segment.js"></script>   
    <script src="snake.js"></script>
    <script src="sketch.js"></script>
</body>
</html>

Then, create a sketch.js and create a snake object.

let snake;
function setup(){
    // create a canvas of your favorite size width and height
    createCanvas(600, 400);
    // start point where the snake be
    let point = new p5.Vector(300, 200);
    // create a snake
    snake = new Snake(point.x, point.y, 15, 20);    
}

function draw(){
    background(51);
    // make the snake follow the mouse cursor
    snake.follow(mouseX, mouseY);
   // draw it
    snake.draw();    
}

Now, define the snake.js

class Snake {
    constructor(x, y, nSegments, segmentLength) {
        this.x = x;
        this.y = y;
        this.head = new Segment(createVector(x, y), segmentLength, 0);
        this.segmentLength = segmentLength;

        let current = this.head;
        for (let i = 0; i < nSegments; i++) {
            let next = new Segment(current.p2, segmentLength, 0);
            current.child = next;
            current = next;
        }
    }

    follow(tx, ty) {
        this.head.follow(tx, ty);
    }

    drawHead() {
        strokeWeight(1);
        stroke("#000");

        push();
        fill("#fff");
        translate(this.head.p1.x, this.head.p1.y);
        rotate(this.head.angle);
        ellipse(0, 0, this.segmentLength*1.5, this.segmentLength);

        // eyes                
        fill("#fff");
        ellipse(this.segmentLength / 4, -this.segmentLength / 4, 5, 5);
        ellipse(this.segmentLength / 4, this.segmentLength / 4, 5, 5);
        // pupils
        fill("#000");
        ellipse(this.segmentLength / 4, -this.segmentLength / 4, 3, 3);
        ellipse(this.segmentLength / 4, this.segmentLength / 4, 3, 3);        

        pop();
    }

    drawSegment(segment, color) {
        strokeWeight(10);
        stroke(color);
        line(segment.p1.x, segment.p1.y, segment.p2.x, segment.p2.y);
    }

    draw() {
        let next = this.head;
        let i = 0;        
        while (next) {
            let color = i % 2 === 0 ? "#fff" : "#000";
            this.drawSegment(next, color);
            next = next.child;
            i++;
        }

        this.drawHead();
    }
}

The main algorithm

Lastly, define a "piece" of snake. Basically, a snake is a chain of segments. Each segment has start point P1 and end point P2. All segments are chained together which start point of this will be the end point of the segment right before it.

The main idea of this is the application of kinematic which implemented in the follow function describe below:

  • For each of the segment, execute the follow(target) function

  • Calculate the new segment angle between the target and P2

  • Set P1 at the target position

  • Update P2's position with given P1's position and the angle

    Illustration of kinematic position update

class Segment {
    constructor(startPoint, len, angle = 0) {
        this.p1 = startPoint;
        this.p2 = createVector(0, 0);
        this.len = len;
        this.angle = angle;
        this.update();
    }

    // kinematic update
    follow(tx, ty) {
        // calculate the new angle
        this.angle = Math.atan2(ty - this.p2.y, tx - this.p2.x);
        // set p1 at the target's position
        this.p1.x = tx;
        this.p1.y = ty;
        // recalculate the p2's position 
        this.update();
        // apply to the child
        if (this.child) {
            this.child.p1 = this.p2;
            this.child.follow(this.p2.x, this.p2.y);
        }
    }

    update() {
        let dx = this.len * cos(this.angle);
        let dy = this.len * sin(this.angle);
        this.p2.x = this.p1.x - dx;
        this.p2.y = this.p1.y - dy;
    }

    draw() {
        stroke(255);
        line(this.p1.x, this.p1.y, this.p2.x, this.p2.y);
        fill("#f00")
        ellipse(this.p1.x, this.p1.y, 3, 3);
        fill("#0f0")
        ellipse(this.p2.x, this.p2.y, 3, 3);
    }
}

done!

0
Subscribe to my newsletter

Read articles from Legos Light directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Legos Light
Legos Light