I need some help creating a Pacman snake.

I started making a variation of Pacman, with some changes to the original idea. My Pacman is controlled by mouse and can move in any direction, and it's a snake.

My only problem is a mouth. I don't know, how to make it work. I want it either be active all the time or only when snake moves, any solution will work. Can you help me?

Here is my code:https://gist.github.com/anonymous/d002d99b90b2288ee6f8f3a16ee49c2e

Tagged:

Answers

  • Instead of posting an external link to your full project, please provide a MCVE that we can all work from. In your case, that might be as simple as a circle or rectangle that faces a direction and shows a mouth.

  • edited December 2017

    I've tried to post the code here using the "C", but it puts the entire code in one long string. And if you want to work with my code you can easily copy it from Gist to Processing.

  • Pac-snake is going to be a pain to put a mouth on... unless you make some serious changes to the way he is being drawn. Here, I tried to draw a face on him to demonstrate what I mean:

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2,height/2);
      vel = new PVector();
      target = new PVector(mouseX,mouseY);
      for(int i = 0; i < num; i++) {
          loc = points[i];
          PVector distance = PVector.sub(target, loc); 
          vel = PVector.mult(distance, ease);
          loc.add(vel);
          target = loc;
          fill(255-i*2, 220-i*1, 120-i*0.2);
          ellipse(loc.x, loc.y, num-i, num-i);
      }
      fill(0);
      stroke(0);
      ellipse(mouseX+30,mouseY-10, 20, 20);
      ellipse(mouseX-30,mouseY-10, 20, 20);
      rect(mouseX-30,mouseY+20,60,10);
    }
    

    Notice how when the head moves behind the tail, the face shows through the tail? This is because you draw the head first, then each segment of the tail in order. If you then try to put a mouth on him, well, it will show on top of the tail - probably not what you want.

  • "Okay," you might say, "I see that. So why don't we just draw the head first, then the mouth, then the tail?"

    Good idea! Doesn't work so well though:

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2, height/2);
      vel = new PVector();
      target = new PVector(mouseX, mouseY);
      for (int i = 0; i < num; i++) {
        loc = points[i];
        PVector distance = PVector.sub(target, loc); 
        vel = PVector.mult(distance, ease);
        loc.add(vel);
        target = loc;
        fill(255-i*2, 220-i*1, 120-i*0.2);
        ellipse(loc.x, loc.y, num-i, num-i);
        if ( i == 0 ) {
          fill(0);
          stroke(0);
          ellipse(mouseX+30, mouseY-10, 20, 20);
          ellipse(mouseX-30, mouseY-10, 20, 20);
          rect(mouseX-30, mouseY+20, 60, 10);
        }
        noStroke();
      }
    }
    
  • Lol, looks fine to me, I was actually thinking about creating a new class just for the animation of mouth.

    Screen Shot 2017-12-15 at 9.49.22 AM

  • "Wait!" your inner thought process yells, "Why don;t we draw the TAIL first, then the head, THEN the mouth?"

    YES! That will work. But this is the "serious changes to the way he is being drawn" I mentioned before. Additionally, it also puts the head "in front" of the tail, whereas before, the TAIL would appear in front of the head.

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2, height/2);
      vel = new PVector();
      target = new PVector(mouseX, mouseY);
      for (int i = 0; i < num; i++) {
        loc = points[i];
        PVector distance = PVector.sub(target, loc); 
        vel = PVector.mult(distance, ease);
        loc.add(vel);
        target = loc;
      }
      for( int i = num-1; i>=0; i--){
        fill(255-i*2, 220-i*1, 120-i*0.2);
        ellipse(points[i].x, points[i].y, num-i, num-i);
      }
    }
    

    I encourage you to compare this with your original code! Notice that the simulation of each point's movement is now done first, and only after that is done are any points drawn - starting with the end of the tail first and moving up to drawing the head last. Thus the head is now in front of all parts of the tail.

    Hopefully you can accept this subtle but necessary change to your Pac-snake.

  • Now we can put the face on him!

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2, height/2);
      vel = new PVector();
      target = new PVector(mouseX, mouseY);
      for (int i = 0; i < num; i++) {
        loc = points[i];
        PVector distance = PVector.sub(target, loc); 
        vel = PVector.mult(distance, ease);
        loc.add(vel);
        target = loc;
      }
      for ( int i = num-1; i>=0; i--) {
        fill(255-i*2, 220-i*1, 120-i*0.2);
        ellipse(points[i].x, points[i].y, num-i, num-i);
      }
      fill(0);
      stroke(0);
      translate(points[0].x,points[0].y);
      ellipse(30, -10, 20, 20);
      ellipse(-30, -10, 20, 20);
      rect(-30, 20, 60, 10);
    }
    

    Looks silly, yes, but this is a KEY STEP in being able to give him a proper Pacman mouth: We have shown that we can properly draw something on the head of the snake without it looking awful.

  • Thank you very much, man.

  • Another key step is being able to figure out which way the mouth should be pointing, and how much it should be opening. Both of these things are really based on the direction the head is moving, so let's work out that vector again.

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2, height/2);
      vel = new PVector();
      target = new PVector(mouseX, mouseY);
      for (int i = 0; i < num; i++) {
        loc = points[i];
        PVector distance = PVector.sub(target, loc); 
        vel = PVector.mult(distance, ease);
        loc.add(vel);
        target = loc;
      }
      for ( int i = num-1; i>=0; i--) {
        fill(255-i*2, 220-i*1, 120-i*0.2);
        ellipse(points[i].x, points[i].y, num-i, num-i);
      }
      target = new PVector(mouseX, mouseY);
      PVector mouthd = PVector.sub(target, points[0]);
      mouthd.mult(3);
      translate(points[0].x, points[0].y);
      stroke(0);
      line(0, 0, mouthd.x, mouthd.y);
    }
    

    Notice the black line. It points in the direction of head movement, and its magnitude is related to head speed. Great!

  • Currently you are calculating sizes from the head down to the tail, but you want to render with the head on top -- that is, you want to draw from the tail up to the head.

    Use two loops -- loop forward and calculate, then loop backward and draw.

      for(int i = 0; i < num; i++) {
          loc = points[i];
          PVector distance = PVector.sub(target, loc); 
          vel = PVector.mult(distance, ease);
          loc.add(vel);
          target = loc;
      }
      for(int i = num-1; i >= 0; i--) {
          loc = points[i];
          fill(255-i*2, 220-i*1, 120-i*0.2);
          ellipse(loc.x, loc.y, num-i, num-i);
      }
    

    You could actually also do this just as easily with one loop -- but this might make the code more intuitive to you based on you how your easing and scaling works.

    Screen Shot 2017-12-15 at 10.06.57 AM

  • edited December 2017 Answer ✓

    All that is left to do is draw an arc in the right place, with the right amount of openness. This looks good to me:

    PVector target, loc, vel;
    PVector[] points;
    float ease = 0.3;
    boolean easing = true;
    int q, w, num = 150;
    int[] vals;
    
    void setup() {
      size(900, 900, P2D);
      noStroke();
      points = new PVector[num];
      for (int i = 0; i < num; i++) {
        points[i] = new PVector(width/2, height/2);
      }
    }
    
    void draw() {
      background(#222C32);
      noStroke();
    
      loc = new PVector(width/2, height/2);
      vel = new PVector();
      target = new PVector(mouseX, mouseY);
      for (int i = 0; i < num; i++) {
        loc = points[i];
        PVector distance = PVector.sub(target, loc); 
        vel = PVector.mult(distance, ease);
        loc.add(vel);
        target = loc;
      }
      for ( int i = num-1; i>=0; i--) {
        fill(255-i*2, 220-i*1, 120-i*0.2);
        ellipse(points[i].x, points[i].y, num-i, num-i);
      }
      target = new PVector(mouseX, mouseY);
      PVector mouthd = PVector.sub(target, points[0]);
      mouthd.mult(3);
      translate(points[0].x, points[0].y);
      rotate(atan2(mouthd.y, mouthd.x));
      noStroke();//stroke(255,220,120);
      //strokeWeight(3);
      //fill(0);
      fill(128,110,60);
      float amt = map(dist(0,0,mouthd.x,mouthd.y),0,100,0,QUARTER_PI/2.0);
      arc(0,0,num,num,-amt,amt);
    //  line(0, 0, mouthd.x, mouthd.y);
    }
    

    Notice that the darker yellow color for the back of the mouth is actually drawn in front of the head. I would suggest you leave it like this, because then it looks like a mouth without us having to draw what would be behind it.

    You could also try filling this small mouth arc with the background color - this gives the illusion that the mouth is actually opening, but this effect is ruined when the snake tries to bite its own tail. Try it.

    A solid black mouth arc looks pretty good too. It's up to you.

  • (@TfGuy44 -- sorry I missed you had a series-in-process when I posted.)

  • No worries. But make this the last time! >:) :))

Sign In or Register to comment.