0

私は現在、ゲームのプログラミングを独学で学んでおり、古典的なゲームのいくつかを作り直して、さまざまなトリックやものを学習しています ( gamedev.netの優れた記事で提案されているように)。

私は PONG のコーディングに成功し、現在 Snake に取り組んでいます (これが Worm の意味だと思います)。今、私は多くのことを理解しましたが、私の心を包み込むことができない2つのことを除いて.

私のアルゴリズムは単純です。プレーヤーが頭をコントロールし、体がそれに従います。頭には独自のクラスがあり、そこから各セグメントが個別のオブジェクトとして制御されます。セグメントはそれぞれ、速度ベクトルによって定義される独自の動きを制御します。最初のセグメントは、残りのセグメントを制御する配列から独立しています。したがって、ヘッドは最初のセグメントに命令を送信するだけで、最初のセグメントはそれを残りのセグメントに送信します。

システムは BendinPoints に基づいています。基本的に、各セグメントには、BendingPoint 座標と、その BendingPoint に到達したときに取る速度ベクトルを格納するための変数があります。フラグは、現在 BendingPoint を所有しているか、新しい座標を自由に受け入れることができるかを示します。

そのため、頭が回転すると、回転した場所と回転した方向 (速度ベクトル) が送信されます。最初のセグメントはそれを 2 番目のセグメントに渡し、2 番目のセグメントはそれを 3 番目のセグメントに渡します。各セグメントは、渡す注文がある場合にのみ、注文を次のセグメントに渡します。各セグメントは、古い注文が完了すると、新しい注文のみを受け取ります。

今、私の問題は2つあります。1: Java モードでは問題なく動作しますが、Javascript モードでは動作せず、その理由がわかりません。そして 2: 急に方向転換をすると、頭と最初の体節以外の体の残りの部分が道に迷い、さまようように見えることがあります。

コードのコメントが残りを説明することを願っています。私の初心者を許してください。

String MODE;

Menu menu;
String[] menuItems={"START","INSTRUCTIONS","CREDITS","EXIT"};

/*@ pjs font="data/waved.ttf" */
/*@ pjs font="data/sixty.ttf" */
PFont sMenu=createFont("waved",72);
PFont sItem=createFont("sixty",35);

String gOverText="GAME OVER";
String hScoreText="Your score is: ";

String iControl="W,A,S,D turns the Snake in the respective direction.";
String iScore="Each Food increases Score by 1 and a segement is added.";
String iScore2="After every 10 points, number of segments added per Food increases by 1.";

String cBy="coded By";
String cName="Le Shaun";

MenuItem back;

Snake snk;
Food fd;

int hScore;
int dF;

float sWidth=800;
float sHeight=600;

PVector sLoc=new PVector(sWidth/2,sHeight/2);
PVector sVel=new PVector(0,-1);
float sRad=10;
color sCol=#9D6C0A;

PVector fLoc=new PVector(450,300);
float fRad=10;
color fCol=#FCF18C;

void setup(){
  size(int(sWidth),int(sHeight));
  snk=new Snake(sLoc,sVel,sRad,sCol);
  fd=new Food(fLoc,fRad,fCol);
  frameRate(60);

  hScore=0;
  dF=1;

  menu=new Menu("SNAKE",menuItems,sMenu,sItem,color(#9D6C0A),color(#8CC610),color(#EDE724),color(#674707),color(255,0));

  MODE="NIL";

  back=new MenuItem("BACK",sItem,width/2,height/1.5,height/25,color(#8CC610),color(#EDE724),color(#674707),color(255,0));    //Common back button for some of the screens.
}

//Current screen is controlled by MODES. Each MODE defines which parts of the game will run, whether it be individual screens or the main gameplay itself.

void draw(){
  background(#EDB824);

  if(MODE.equals("NIL")){
    menu.render();
    MODE=menu.whichItem();
  }
  else if(MODE.equals("START")){  
    fd.render();
    if(fd.isEaten(snk)){
      for(int i=1;i<=dF;i++){
      snk.sInc();
      }
      hScore++;
    }

    snk.render();
    snk.update();
    if(snk.isDead()){
     MODE="GAMEOVER";
     sLoc=new PVector(width/2,height/2);
     fLoc=new PVector(width/2+100,height/2+100);
     sVel=new PVector(+1,0);
     snk=new Snake(sLoc,sVel,sRad,sCol);
    }

    dF=int(hScore/10)+1;

    textFont(sItem);
    textSize(height/25);
    text(str(hScore),width-textWidth(str(hScore))*3,height-height/25);
  }
  else if(MODE.equals("GAMEOVER")){
    stroke(0);
    fill(#9D6C0A);
    textFont(sMenu);
    textSize(72);
    text(gOverText,width/2-textWidth(gOverText)/2,height/3);
    text(hScoreText+hScore,width/2-textWidth(gOverText)/2,height/2);

    back.render();
    back.update();
    if(back.getClicked()){
      back.unClick();
      MODE="NIL";
      hScore=0;
      frameRate(60);
    }
  }
  else if(MODE.equals("INSTRUCTIONS")){
    stroke(0);
    fill(#9D6C0A);
    textFont(sMenu);
    textSize(72);
    text("INSTRUCTIONS",width/2-textWidth("INSTRUCTIONS")/2,height/3);

    textFont(sItem);
    textSize(20);
    text(iControl,width/2-textWidth(iControl)/2,height/2);
    text(iScore,width/2-textWidth(iScore)/2,height/2+35);
    text(iScore2,width/2-textWidth(iScore2)/2,height/2+70);

    back.render();
    back.update();
    if(back.getClicked()){
      back.unClick();
      MODE="NIL";
    }
  }
  else if(MODE.equals("CREDITS")){
    stroke(0);
    fill(#9D6C0A);
    textFont(sItem);

    textSize(35);
    text(cBy,width/2-textWidth(cBy)/2,height/2);

    textSize(45);
    text(cName,width/2-textWidth(cName)/2,height/1.7);

    back.render();
    back.update();
    if(back.getClicked()){
      back.unClick();
      MODE="NIL";
    }
  }


  //println(MODE);
}

void keyReleased(){
  if(MODE.equals("START")){
    String temp="";
    temp+=key;
    temp=temp.toUpperCase();
    snk.changeDir(temp);

    if(key=='v' || key=='V'){
      frameRate(60);
    }
  }
}

void keyPressed(){
    if(MODE.equals("START")){
    if(key=='v' || key=='V'){
      frameRate(180);
    }
  }
}

void mouseClicked(){
  if(MODE.equals("NIL")){
    menu.passTo(mouseX,mouseY);
  }
  if(MODE.equals("GAMEOVER") || MODE.equals("INSTRUCTIONS") || MODE.equals("CREDITS")){
    back.mClicked(mouseX,mouseY);
  }
}


//Menu class uses the objects from the MenuItem and forms a menu with a title and a list of MenuItem objects.

/*
  Constructor: Str-MenuTitle, Str[]-MenuItems, PF-MenuFont, PF-MenuItemFont, c-TitleColor, c-ItemTextColor, c-ItemBackColor, c-ItemHoverTextColor, c-ItemHoverBackColor.

  Methods:
      void render() - Renders the MenuTitle and the MenuItems.
      void passTo(float,float) - Passes the mouse coords to each MenuItem to check whether it has been clicked.
      void passTo(int) - Resets the clickState of the specified MenuItem by calling the unClick() method on that MenuItem.
      String whichItem() - Checks all the MenuItems for a their clickState and returns the one that's been clicked.
*/

class Menu{
  String titleT;

  PFont titleF;
  PFont menuItem;

  color titleC;

  float spacer;    //This is used to define the space between successive MenuItem objects.
  float iniY=height/2.5;

  MenuItem[] menuItems;

  Menu(String titleT,String[] menuItemsNames,PFont titleF,PFont menuItemF,color titleC,color menuItemC,color menuBackC,color itemHoverC,color backHoverC){
    this.titleT=titleT;
    this.titleF=titleF;
    this.titleC=titleC;

    menuItems=new MenuItem[menuItemsNames.length];  //Initializes the MenuItem objects depending on the array passed to it. This makes the menu system very flexible.
    spacer=48;
    for(int i=0;i<menuItemsNames.length;i++){      
      menuItems[i]=new MenuItem(menuItemsNames[i],menuItemF,width/2,iniY+(spacer*i),height/25,menuItemC,menuBackC,itemHoverC,backHoverC);
    }
  }

  void render(){  //Renders the menu.
    textFont(titleF);
    textSize(92);
    fill(titleC);
    text(titleT,width/2-(textWidth(titleT)/2),height/3.8);

    for(int i=0;i<menuItems.length;i++){
      menuItems[i].update();
      menuItems[i].render();
    }
  }

  void passTo(float mX,float mY){    //This accepts the X,Y mouse coords when the mouse is clicked and passes it to the relevant MenuItem object to check if the click occurs on that object.
    for(int i=0;i<menuItems.length;i++){
      menuItems[i].mClicked(mX,mY);
    }
  }

  /*void passTo(int item){  //This accepts an ineteger value and resets that particular menu item's click state.
    menuItems[item].unClick();
  }*/

  String whichItem(){  //Checks each time if the clickState of any MenuItem object is true. If it is, returns the array position of the relevant object.
    for(int i=0;i<menuItems.length;i++){
      if(menuItems[i].getClicked()){
        menuItems[i].unClick();
        return menuItems[i].menuItem;
      }
    }
    return "NIL";
  }
}


//MenuItem holds the attributes and methods relating to each single item on the menu. Thus each item is treated as a separate object.
//Each MenuItem object comprises mainly of a foreground text and a background object. 



class MenuItem{

  String menuItem;
  PFont menuFont;
  float itemX;
  float itemY;
  float itemSize;
  color itemColor;
  color backColor;
  color pressedColor;
  color pressedBack;

  color presentItem;
  color presentBack;

  float textWidth;

  boolean clickState=false;  //This vairable is used to check the clickState of the menu item. If the mouse is clicked over the menu item, this variable becomes true.

  MenuItem(String menuItem,PFont menuFont,float itemX,float itemY,float itemSize,color itemColor,color backColor,color pressedColor,color pressedBack){
    this.menuItem=menuItem;
    this.menuFont=menuFont;
    this.itemX=itemX;
    this.itemY=itemY;
    this.itemSize=itemSize;
    this.itemColor=itemColor;
    this.backColor=backColor;
    this.pressedColor=pressedColor;
    this.pressedBack=pressedBack;
  }

  void render(){    //Handles the rendering for individual menu objects.
    textFont(menuFont);
    textSize(itemSize);
    textWidth=textWidth(menuItem);

    stroke(0);
    fill(presentBack);
    rectMode(CENTER);
    rect(itemX,itemY,textWidth*1.3,itemSize*1.4,50);

    fill(presentItem);
    text(menuItem,itemX-textWidth/2,itemY+itemSize*.3);
  }

  void update(){             //Constatnly checks for the state of the object. If the mouse is over it a certain style is show and otherwise another style is shown.
    if(mouseX<(itemX+(textWidth*1.3)/2) && mouseX>(itemX-(textWidth*1.3)/2) && mouseY<(itemY+(itemSize*1.4)/2) && mouseY>(itemY-(itemSize*1.4)/2)){
     presentItem=pressedColor;
     presentBack=pressedBack;
     noStroke();
    }
    else{
     presentItem=itemColor;
     presentBack=backColor;
    }
  }

  boolean getClicked(){    //Returns the clickState of the object.
    return clickState;
  }

  void unClick(){      //Resets the click state after having been clicked once.
    clickState=false;
  }

  void mClicked(float mX,float mY){  //Changes the clickState of the object depending on the position of the mouse as inputs.
    if(mX<(itemX+(textWidth*1.3)/2) && mX>(itemX-(textWidth*1.3)/2) && mY<(itemY+(itemSize*1.4)/2) && mY>(itemY-(itemSize*1.4)/2)){
      clickState=true;
      println(menuItem);
    }
  }
}



/*
  All control comes from the Snake's head. The head works directly with the first segment(SnakeBits object) and the first segement works with the rest of the body.
  Each time a food is consumed, a new segment is created, it's position and velocity calculated as per the position of the last segment.
  A loop checks whether each segment is open to receiving a new set of orders(BendingPoint and the velocity for that point), and passes on if so.

*/

class Snake{  //Controls the snake's head as well as the segment objects.  

  PVector sLoc;  //Location and Velocity.
  PVector sVel;

  float sRad;  //Radius and Color
  float shRad;    
  color sCol;

  float baseVel;  //The base velocity of the snake.

  SnakeBits[] sBits={};  //Array of SnakeBits objects that forms the segments.

  PVector hold;

  Snake(PVector sLoc,PVector sVel,float sRad,color sCol){
    this.sLoc=sLoc;
    this.sVel=sVel;
    this.sRad=sRad;
    this.shRad=sRad*1.;
    this.sCol=sCol;
    this.baseVel=abs(sVel.x>0 ? sVel.x : sVel.y);  //The snake is initially given a vector in one of the cardinal directions. Whatever the value of velocity is in either direction is stored.

    hold=PVector.mult(sVel,shRad+sRad);
    hold=PVector.sub(sLoc,hold);
    sBits=(SnakeBits[])append(sBits,new SnakeBits(hold,sVel,sRad,sCol));
  }

  void update(){  //Updates the movement of the head as well as the segments.
    updateBP();

    sLoc.add(sVel);
    for(int i=0;i<sBits.length;i++){
      sBits[i].update();
    }    
  }

  void render(){  //The display.
    stroke(0);
    fill(sCol);
    ellipse(sLoc.x,sLoc.y,shRad*2.2,shRad*2.2);

    for(int i=0;i<sBits.length;i++){
      sBits[i].render();
    }
  }

  void sInc(){  //Gets called each time a food item is eaten, and increases the size of the snake by adding segments based on the velocity vector of the last segment.
    int lastInd=sBits.length-1;
    hold=PVector.mult(sBits[lastInd].sbVel,sRad*2);
    hold=PVector.sub(sBits[lastInd].sbLoc,hold);
    PVector appVel=new PVector(sBits[lastInd].sbVel.x,sBits[lastInd].sbVel.y);
    SnakeBits appBits=new SnakeBits(hold,appVel,sRad,sCol);
    sBits=(SnakeBits[])append(sBits,appBits);
  }

  void changeDir(String dir){  //Gets called when a directional button is pressed. 
    PVector chng=new PVector(0,0);  //Direction change can only occur perpendicular to the current direction. Uses baseVel to set the new direction.
    if(!sBits[0].hasBP){
      if(degrees(sVel.heading())==0 || degrees(sVel.heading())==180){
        if(dir.equals("W")){
          chng=new PVector(0,-baseVel);
          sVel=chng;
          updateFBP();
        }
        else if(dir.equals("S")){
          chng=new PVector(0,baseVel);
          sVel=chng;
          updateFBP();
        }
      }
      else if(degrees(sVel.heading())==90 || degrees(sVel.heading())==-90){
        if(dir.equals("D")){
          chng=new PVector(baseVel,0);
          sVel=chng;
          updateFBP();
        }
        else if(dir.equals("A")){
          chng=new PVector(-baseVel,0);
          sVel=chng;
          updateFBP();
        }
      }
    }
  }

  boolean isDead(){  //Checks for collision against the wall or it's own tail.
    if((sLoc.x-shRad)<0 || (sLoc.x+shRad)>width || (sLoc.y-shRad)<0 || (sLoc.y+shRad)>height){
       println("WALL");
      return true;
    }
    PVector temp;
    for(int i=0;i<sBits.length;i++){ 
      if(dist(sLoc.x,sLoc.y,sBits[i].sbLoc.x,sBits[i].sbLoc.y)<(shRad+sRad-sRad*.6)){
        println("TAIL");
        println(sLoc.x+" "+sLoc.y+" "+sBits[i].sbLoc.x+" "+sBits[i].sbLoc.y+" "+dist(sLoc.x,sLoc.y,sBits[i].sbLoc.x,sBits[i].sbLoc.y)+" "+(shRad+sRad-sRad*.6));
        return true;
      }
    }

    return false;
 }

 void updateFBP(){     //Updates the first segment's BendingPoint.
      sBits[0].takeNewBP(sLoc,sVel);
      sBits[0].hasNewBP(true);  
  }

 void updateBP(){  //Updates the rest of the segments as per the system of receiving new orders once the current orders have been executed.
   for(int i=0;i<sBits.length-1;i++){
     if(sBits[i].hasBP && !sBits[i+1].hasBP){
       sBits[i+1].takeNewBP(sBits[i].newBP,sBits[i].newVel);
       sBits[i+1].hasNewBP(true);
     }
   }
 }


}


/*
  Each SnakeBit has it's independent movement system. It holds a BendPoint(newBP) variable, a New BP Velocity(newVel) variable and a flag(hasBP) to show whether it has a new Bend Point.
  When the SnakeBit already has a BP, it will wait till it reaches that BP and then take on the velocity from newVel. It's flag will be set to false.
  In this state it will be open to receiving a new set of orders: a new BP and the velocity to take on for that BP. Thus new BP's are not taken on till the previous BP has been cleared.
*/

class SnakeBits{  //The individual bits of the snake that make up its body.

  boolean hasBP;

  PVector sbLoc;  //Location and Velocity vectors.
  PVector sbVel;

  float sbRad;  //Radius and color of the segment.
  color sbCol;

  PVector newBP;  //This works with the changeDir() method. It holds the position at which the direction will be changed.
  PVector newVel;  //Stores the new Velocity vector that will be applied when the above position is reached.

  SnakeBits(PVector sbLoc,PVector sbVel,float sbRad,color sbCol){
    this.sbLoc=sbLoc;
    this.sbVel=sbVel;
    this.sbRad=sbRad;
    this.sbCol=sbCol;
    newVel=new PVector(sbVel.x,sbVel.y);
    newBP=new PVector(width*2,height*2);  //Initialized it as such to avoid problems during first run.
    hasBP=false;
  }

  void render(){
    stroke(0);
    fill(sbCol);
    ellipse(sbLoc.x,sbLoc.y,sbRad*2,sbRad*2);
  }

  void update(){
    sbLoc.add(sbVel);  //Both updates the Location and checks if it's time to change direction.
    changeDir(); 
  }

  void changeDir(){
    if(sbLoc.x==newBP.x && sbLoc.y==newBP.y && hasBP){  //As soon as the segment reaches the Location where a change in dir is needed, the Velocity is changed over to the new velocity vector.
      println("FTRUE");
      hasNewBP(false);
      sbVel.x=newVel.x;  sbVel.y=newVel.y;
      newBP=new PVector(width*2,height*2);
    }

  }

  void takeNewBP(PVector pos,PVector vel){  //Called externally by the Snake class. Takes where last segment changed direction and stores that location as well as the new velocity vector.
    newBP.x=pos.x;  newBP.y=pos.y;
    newVel.x=vel.x; newVel.y=vel.y; 
  }

  void hasNewBP(boolean dat){  //Updates the hasBP state by accepting a boolean and assigning it to hasBP.
    hasBP=dat;
  }
}


class Food{

  PVector fLoc;

  float fRad;
  color fCol;

  Food(PVector fLoc,float fRad,color fCol){
    this.fLoc=fLoc;
    this.fRad=fRad;
    this.fCol=fCol;
  }

  void render(){
    stroke(0);
    fill(fCol);
    ellipse(fLoc.x,fLoc.y,fRad*2,fRad*2);
  }

  boolean isEaten(Snake sn){
    PVector temp;
    temp=PVector.sub(fLoc,sn.sLoc);
    if(temp.mag()<(sn.shRad+fRad)){
      reset(sn);
      return true;
    }

    return false;
  }

  void reset(Snake sn){
    boolean set=false;
    PVector tmp=new PVector();
    while(!set){
      tmp=new PVector(random(fRad,width-fRad),random(fRad,height-fRad));
      set=true;
      for(int i=0;i<sn.sBits.length;i++){
        if(dist(tmp.x,tmp.y,sn.sBits[i].sbLoc.x,sn.sBits[i].sbLoc.y)<(fRad+sn.sRad) || dist(tmp.x,tmp.y,sn.sLoc.x,sn.sLoc.y)<(fRad+sn.shRad)){
          set=false;
          break;
        }
      }
    }
    fLoc=tmp;
  }
}
4

1 に答える 1