2013-07-18 32 views
0

我現在在教我自己的遊戲編程和重新制作一些經典遊戲來學習不同的技巧和東西(建議在gamedev.net上建議fine article)。在Java模式下工作很好,在Javascript中也不是那麼順利

我成功編寫了PONG,現在正在研究Snake(我認爲這就是蠕蟲的含義)。現在我想出了很多東西,除了兩件事我不能籠絡我的腦海。

我的算法很簡單:玩家控制頭部和身體。頭部有它自己的類,並從那裏每個段被控制爲一個單獨的對象。這些分段各自控制由速度矢量定義的它們自己的運動。第一部分獨立於控制其餘部分的數組。所以頭部只是發送命令到第一個分段,第一個分段發送給其餘的分段。

該系統基於BendinPoints。基本上每個段都有一個用於存儲BendingPoint座標的變量以及在到達BendingPoint時要採用的速度矢量。一個標誌告訴它它當前是否擁有一個BendingPoint或可以自由接受一個新的座標。

所以當它轉動時,頭部將其轉動的位置轉換成其轉動的方向(速度矢量)。第一部分在第二部分傳遞,第二部分傳遞到第三部分,依此類推。如果有一個通過,每個細分只會傳遞給下一個訂單。一旦舊部分完成,每個部分只會收到新的訂單。

現在我的問題是雙重的。一:這在Java模式下工作正常,但不能在Javascript模式下工作,我不明白爲什麼。二:有時候,當我改變方向的速度太快時,除了頭部和第一部分之外,其餘的部分似乎失去了蹤跡,並且流連忘返。

我希望代碼註釋能解釋其餘的部分。原諒我的新生。

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; 
    } 
} 
+0

是第2部分是什麼在第1部分會發生什麼?如果沒有,比「不起作用」更清楚 – Daniel

+0

不,這兩個問題是完全不同的。代碼實際運行時,我在Java模式下運行它,但我面臨的第二個問題,只有當我改變方向太快。但在Javascript模式下,菜單本身拒絕顯示。 – ScionOfBytes

+0

「Java模式」和「JavaScript模式」是什麼意思? Java和JavaScript是完全不同的編程語言。 – Pointy

回答

0

Javascript模式似乎有與textWidth()的問題;爲了克服它,你可以改變textWidth = textWidth(menuItem);在MenuItem.render()中爲textWidth = 200;或找到一個相當的工作。順便說一下,我注意到,當我在Javascript中運行它時,它不會佔用它的全部尺寸和尺寸()。指揮是罪魁禍首。它似乎需要數字而不是變量才能正常工作(設置它的大小(800,600);工作)

您的其他問題似乎源於事實,即當您按下按鈕但您不應用更改直到draw()函數。不幸的是,我不能在你的代碼中進行細微的修改,這使我相信你需要以不同的方式重構車削系統。我的建議是解耦每個人必須離開前一個位置的地方,然後選擇存儲他必須進入ArrayList的位置和方向,ArrayList將隨着每個回合而基本增長。然後,蛇的每一位都必須通過它自己的方向和位置的ArrayList,而不必考慮其前一個的瞬間變化。當它到達其中一個位置時,只需將其沿正確的方向旋轉,刪除到達的方向和位置,然後開始前往下一個位置。 ArrayList基本上就像一個隊列,在那裏你添加到最後但從開始刪除。

也許是有道理的張貼您的問題的第二部分https://gamedev.stackexchange.com/

+0

工程就像一個魅力! :D我使用LinkedList而不是ArrayList,但算法保持不變,它的工作原理非常漂亮。謝謝。 – ScionOfBytes

相關問題