2017-05-26 15 views
5

हल अप टेलीपोर्ट, अंतिम एल्गोरिथ्म के लिए पोस्ट के निचले देख नहीं करना चाहिएटकराव का पता लगाने वस्तु

पृष्ठभूमि: मैं जे एस और एचटीएमएल कैनवास तत्व का उपयोग कर एक 2 डी प्लेटफ़ॉर्मर पर काम कर रहा हूँ। स्तर का नक्शा टाइल-आधारित है, लेकिन खिलाड़ी टाइल्स पर क्लैंप नहीं है। मैं "Tiny Platformer" on Code inComplete में उल्लिखित टक्कर पहचान एल्गोरिदम का उपयोग कर रहा हूं। यह मोटे तौर पर एक किनारे के मामले (या '' लेज 'केस) को छोड़कर काम करता है।

समस्या:

gif of ledge issue

खिलाड़ी नीचे गिर रही है और यह भी सही चलती, दीवार में। जैसे ही यह गिरता है, यह आधार की ऊंचाई तक पहुंचता है। इसके बजाय खिलाड़ी को टेलीपोर्टिंग के बिना सामान्य रूप से गिरना चाहिए।

क्या इस व्यवहार को रोकने के लिए एल्गोरिदम बदलने का कोई तरीका है? यदि नहीं, तो क्या आप एक वैकल्पिक टक्कर पहचान एल्गोरिदम का सुझाव दे सकते हैं? आदर्श रूप से कोई भी फिक्स खिलाड़ी को हमेशा गिरने का अनुमान नहीं लगाएगा, क्योंकि खेल में खिलाड़ी की गिरावट की दिशा ऊपर/नीचे/बाएं/दाएं के बीच स्विच होती है।

एल्गोरिथ्म:

  1. खिलाड़ी की नई स्थिति कोई टकराव मानते हुए गणना की जाती है। (नीचे दिए गए कोड में नहीं दिखाया गया है)

  2. getBorderTiles नामक एक फ़ंक्शन एक ऑब्जेक्ट (प्लेयर) लेता है और प्रत्येक खिलाड़ी के 4 कोनों को स्पर्श करने वाली टाइल्स देता है। चूंकि खिलाड़ी टाइल से बड़ा नहीं है, इसलिए उन सीमाओं की टाइलें आवश्यक रूप से एकमात्र टाइल्स हैं जो खिलाड़ी स्पर्श कर रही हैं। ध्यान दें कि इनमें से कुछ टाइल्स समान हो सकते हैं। उदाहरण के लिए यदि खिलाड़ी केवल एक कॉलम पर कब्जा कर रहा है, तो बाएं-दाएं/दाएं-नीचे की टाइलें बाएं-नीचे/दाएं-नीचे की टाइलें समान होंगी। यदि ऐसा होता है, getBorderTiles अभी भी सभी चार टाइल्स लौटाता है, लेकिन कुछ समान होंगे।

  3. यह यह देखने के लिए कि क्या वे ठोस हैं, इन स्तर सीमाओं (2 डी सरणी) में इन सीमाओं की जांच करता है। यदि एक टाइल ठोस है, तो वस्तु उस टाइल के साथ टकराने जा रही है।

  4. यह ऊपर/नीचे/बाएं/दाएं टकराव का परीक्षण करता है। यदि खिलाड़ी नीचे गिर रहा है और नीचे टाइल के साथ टकरा रहा है लेकिन संबंधित टाइल के साथ टकरा नहीं रहा है, तो खिलाड़ी नीचे गिर रहा है। यदि खिलाड़ी बाएं टाइल के साथ बाएं और टकराने जा रहा है लेकिन संबंधित दाएं टाइल से टकरा नहीं रहा है, तो यह बायीं तरफ है। आदि ऊपर/नीचे चेक बाएं/दाएं चेक से पहले किए जाते हैं। बाएं/दाएं चेक करने से पहले ऊपर/नीचे टकराव होने पर वेरिएबल टाइल स्टोर करने वाले चर समायोजित किए जाते हैं। उदाहरण के लिए यदि खिलाड़ी नीचे गिर जाता है, तो उसे ऊपर की टाइल में धकेल दिया जाएगा, इसलिए बीएल/बीआर टाइल्स अब टीएल/टीआर टाइल्स के समान हैं।

  5. खिलाड़ी का एक्स, वाई, और गति समायोजित दिशाओं के आधार पर समायोजित की जाती है।

क्यों एल्गोरिथ्म विफल रहता है:

See this image.

नीचे दाईं टाइल ठोस है लेकिन शीर्ष ठीक है, तो (4 कदम) खिलाड़ी नीचे टकरा और (5 कदम नहीं है) यह धक्का दिया गया है। इसके अलावा, यह बीआर टाइल के साथ टकराता है लेकिन बीएल नहीं, इसलिए यह सही हो जाता है और बाएं धक्का दिया जाता है। अंत में, खिलाड़ी को ऊपर और बाईं ओर बाईं ओर प्रस्तुत किया जाता है। असल में यह teleported है।

समाधान पर प्रयास: मैंने इसे ठीक करने का प्रयास किया, लेकिन यह केवल एक और मुद्दा बनाया। मैंने एक चेक जोड़ा ताकि खिलाड़ी केवल टाइल के साथ टकराए, अगर वह उस टाइल के भीतर कुछ दूरी है (3px कहें)। यदि खिलाड़ी केवल बीआर टाइल में मुश्किल से था, तो एल्गोरिदम नीचे टक्कर दर्ज नहीं करेगा, इसलिए खिलाड़ी टेलीपोर्ट नहीं करेगा। हालांकि, अगर खिलाड़ी किसी अन्य परिदृश्य में जमीन पर गिर गया, तो टक्कर को तब तक स्वीकार नहीं किया जब तक कि खिलाड़ी जमीन पर बहुत दूर नहीं हो जाता। खिलाड़ी जमीन पर थोड़ा सा गिरने पर चिल्लाया, जमीन के शीर्ष पर वापस धकेल दिया, फिर गिर गया, आदि

इसे पढ़ने के लिए धन्यवाद। मेरे लिए आपका फ़ीडबैक वाकई सराहनीय है।

वर्तमान एल्गोरिथ्म कोड:

var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level 
 
     tileTL = borderTiles.topLeft, 
 
     tileTR = borderTiles.topRight, 
 
     tileBL = borderTiles.bottomLeft, 
 
     tileBR = borderTiles.bottomRight, 
 
     coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile 
 
     xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles) 
 
     yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles) 
 
     typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1 
 
     typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1, 
 
     typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1, 
 
     typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1, 
 
     collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid 
 
     collidesTR = typeTR == TILETYPE.SOLID, 
 
     collidesBL = typeBL == TILETYPE.SOLID, 
 
     collidesBR = typeBR == TILETYPE.SOLID, 
 
     collidesUp = false, 
 
     collidesDown = false, 
 
     collidesLeft = false, 
 
     collidesRight = false; 
 

 
//down and up 
 
     if (object.vy < 0 && ((collidesTL && !collidesBL) || (collidesTR && !collidesBR))) { 
 
     collidesUp = true; 
 
     /*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__ 
 
     variables as this affects collision testing, but is it not necessary to change the tile__ variables. */ 
 
     collidesTL = collidesBL; 
 
     collidesTR = collidesBR; 
 
     } else if (object.vy > 0 && ((collidesBL && !collidesTL) || (collidesBR && !collidesTR))) { 
 
     collidesDown = true; 
 
     /*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__ 
 
     variables as this affects collision testing, but is it not necessary to change the tile__ variables. */ 
 
     collidesBL = collidesTL; 
 
     collidesBR = collidesTR; 
 
     } 
 

 
     //left and right 
 
     if (object.vx < 0 && ((collidesTL && !collidesTR) || (collidesBL && !collidesBR))) { 
 
     collidesLeft = true; 
 
     } else if (object.vx > 0 && ((collidesTR && !collidesTL) || (collidesBR && !collidesBL))) { 
 
     collidesRight = true; 
 
     } 
 

 
     if (collidesUp) { 
 
     object.vy = 0; 
 
     object.y = yBottom; 
 
     } 
 
     if (collidesDown) { 
 
     object.vy = 0; 
 
     object.y = yBottom - object.height; 
 
     } 
 
     if (collidesLeft) { 
 
     object.vx = 0; 
 
     object.x = xRight; 
 
     } 
 
     if (collidesRight) { 
 
     object.vx = 0; 
 
     object.x = xRight - object.width; 
 
     }

अद्यतन: maraca समाधान के साथ हल। एल्गोरिदम नीचे है। असल में यह परीक्षण करता है (एक्स तब वाई) और हल टकराव, और फिर यह परीक्षण (वाई तब एक्स) परीक्षण करता है और इस तरह टकराव हल करता है। एक छोटी दूरी पर चलने वाले खिलाड़ी में जो भी टेस्ट परिणाम होता है वह वह होता है जो इस्तेमाल होने वाला होता है।

दिलचस्प बात यह है कि जब खिलाड़ी शीर्ष और बाएं दिशा दोनों में टकरा रहा है तो इसके लिए एक विशेष मामला आवश्यक है। शायद यह इस तथ्य से संबंधित है कि खिलाड़ी (x, y) समन्वय अपने शीर्ष बाएं कोने पर है। इस मामले में, परीक्षण जो खिलाड़ी को एक लंबी दूरी पर ले जाने वाले परिणाम में उपयोग किया जाना चाहिए।

gif showing why special case is needed

खिलाड़ी ब्लैक बॉक्स और पीले बॉक्स का प्रतिनिधित्व करता है जहां खिलाड़ी अगर यह अन्य परीक्षण (परीक्षण है कि खिलाड़ी के परिणामस्वरूप का इस्तेमाल किया था एक लंबे समय तक चलती होगा: यह इस gif में स्पष्ट है दूरी)। आदर्श रूप से खिलाड़ी को दीवार में नहीं जाना चाहिए, और इसके बजाय यह होना चाहिए जहां पीला बॉक्स है। इस प्रकार इस परिदृश्य में लंबी दूरी की परीक्षा का उपयोग किया जाना चाहिए।

यहां त्वरित और गंदे कार्यान्वयन है। यह बिल्कुल अनुकूलित नहीं है लेकिन उम्मीद है कि यह एल्गोरिदम के चरणों को स्पष्ट रूप से दिखाता है।

function handleCollision(object) { 
 
    var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level 
 
     tileTL = borderTiles.topLeft, 
 
     tileTR = borderTiles.topRight, 
 
     tileBL = borderTiles.bottomLeft, 
 
     tileBR = borderTiles.bottomRight, 
 
     coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile 
 
     xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles) 
 
     yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles) 
 
     typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1 
 
     typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1, 
 
     typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1, 
 
     typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1, 
 
     collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid 
 
     collidesTR = typeTR == TILETYPE.SOLID, 
 
     collidesBL = typeBL == TILETYPE.SOLID, 
 
     collidesBR = typeBR == TILETYPE.SOLID, 
 
     collidesUp = false, 
 
     collidesDown = false, 
 
     collidesLeft = false, 
 
     collidesRight = false, 
 
     originalX = object.x, //the object's coordinates have already been adjusted according to its velocity, but not according to collisions 
 
     originalY = object.y, 
 
     px1 = originalX, 
 
     px2 = originalX, 
 
     py1 = originalY, 
 
     py2 = originalY, 
 
     vx1 = object.vx, 
 
     vx2 = object.vx, 
 
     vy1 = object.vy, 
 
     vy2 = object.vy, 
 
     d1 = 0, 
 
     d2 = 0, 
 
     conflict1 = false, 
 
     conflict2 = false, 
 
     tempCollidesTL = collidesTL, 
 
     tempCollidesTR = collidesTR, 
 
     tempCollidesBL = collidesBL, 
 
     tempCollidesBR = collidesBR; 
 

 
    //left and right 
 
    //step 1.1 
 
    if (object.vx > 0) { 
 
    if (collidesTR || collidesBR) { 
 
     vx1 = 0; 
 
     px1 = xRight - object.width; 
 
     conflict1 = true; 
 
     tempCollidesTR = false; 
 
     tempCollidesBR = false; 
 
    } 
 
    } 
 
    if (object.vx < 0) { 
 
    if (collidesTL || collidesBL) { 
 
     vx1 = 0; 
 
     px1 = xRight; 
 
     conflict1 = true; 
 
     tempCollidesTL = false; 
 
     tempCollidesBL = false; 
 
     collidesLeft = true; 
 
    } 
 
    } 
 
    //step 2.1 
 
    if (object.vy > 0) { 
 
    if (tempCollidesBL || tempCollidesBR) { 
 
     vy1 = 0; 
 
     py1 = yBottom - object.height; 
 
    } 
 
    } 
 
    if (object.vy < 0) { 
 
    if (tempCollidesTL || tempCollidesTR) { 
 
     vy1 = 0; 
 
     py1 = yBottom; 
 
     collidesUp = true; 
 
    } 
 
    } 
 
    //step 3.1 
 
    if (conflict1) { 
 
    d1 = Math.abs(px1 - originalX) + Math.abs(py1 - originalY); 
 
    } else { 
 
    object.x = px1; 
 
    object.y = py1; 
 
    object.vx = vx1; 
 
    object.vy = vy1; 
 
    return; //(the player's x and y position already correspond to its non-colliding values) 
 
    } 
 

 
    //reset the tempCollides variables for another runthrough 
 
    tempCollidesTL = collidesTL; 
 
    tempCollidesTR = collidesTR; 
 
    tempCollidesBL = collidesBL; 
 
    tempCollidesBR = collidesBR; 
 

 
    //step 1.2 
 
    if (object.vy > 0) { 
 
    if (collidesBL || collidesBR) { 
 
     vy2 = 0; 
 
     py2 = yBottom - object.height; 
 
     conflict2 = true; 
 
     tempCollidesBL = false; 
 
     tempCollidesBR = false; 
 
    } 
 
    } 
 
    if (object.vy < 0) { 
 
    if (collidesTL || collidesTR) { 
 
     vy2 = 0; 
 
     py2 = yBottom; 
 
     conflict2 = true; 
 
     tempCollidesTL = false; 
 
     tempCollidesTR = false; 
 
    } 
 
    } 
 
    //step 2.2 
 
    if (object.vx > 0) { 
 
    if (tempCollidesTR || tempCollidesBR) { 
 
     vx2 = 0; 
 
     px2 = xRight - object.width; 
 
     conflict2 = true; 
 
    } 
 
    } 
 
    if (object.vx < 0) { 
 
    if (tempCollidesTL || tempCollidesTL) { 
 
     vx2 = 0; 
 
     px2 = xRight; 
 
     conflict2 = true; 
 
    } 
 
    } 
 
    //step 3.2 
 
    if (conflict2) { 
 
    d2 = Math.abs(px2 - originalX) + Math.abs(py2 - originalY); 
 
    console.log("d1: " + d1 + "; d2: " + d2); 
 
    } else { 
 
    object.x = px1; 
 
    object.y = py1; 
 
    object.vx = vx1; 
 
    object.vy = vy1; 
 
    return; 
 
    } 
 

 
    //step 5 
 
    //special case: when colliding with the ceiling and left side (in which case the top right and bottom left tiles are solid) 
 
    if (collidesTR && collidesBL) { 
 
    if (d1 <= d2) { 
 
     object.x = px2; 
 
     object.y = py2; 
 
     object.vx = vx2; 
 
     object.vy = vy2; 
 
    } else { 
 
     object.x = px1; 
 
     object.y = py1; 
 
     object.vx = vx1; 
 
     object.vy = vy1; 
 
    } 
 
    return; 
 
    } 
 
    if (d1 <= d2) { 
 
    object.x = px1; 
 
    object.y = py1; 
 
    object.vx = vx1; 
 
    object.vy = vy1; 
 
    } else { 
 
    object.x = px2; 
 
    object.y = py2; 
 
    object.vx = vx2; 
 
    object.vy = vy2; 
 
    } 
 
}

+0

यह भी ध्यान रखें नीचे की ओर जब टाइल ऊंचाई के निचले आधे में हो रहा teleports देखते हैं कि। यदि आप उदाहरण में लैंडिंग कर रहे हैं तो समस्या और भी गंभीर होगी, तो आपको गति 0 के साथ मध्य हवा में एक स्थिति में नीचे भेज दिया जाएगा और फिर उस गुरुत्वाकर्षण के बाद थोड़ा सा बाएं स्थानांतरित हो जाएगा फिर से मुझे लगता है। – maraca

उत्तर

1

यह इसलिए होता है क्योंकि आप पहली बार दोनों दिशाओं में टकराव का पता लगाने और बाद में आप स्थिति को समायोजित। "ऊपर/नीचे" पहले अपडेट किया गया है (गुरुत्वाकर्षण की दिशा)। पहले "बाएं/दाएं" को समायोजित करने से समस्या केवल खराब हो जाएगी (प्रत्येक पतन के बाद आपको दाएं या बाएं टेलीपोर्ट किया जा सकता है)।

केवल त्वरित और गंदी ठीक मैं के साथ आ सकता है (गुरुत्व-अपरिवर्तनीय):

  1. एक ही दिशा में दो प्रासंगिक अंक की टक्कर की गणना (जैसे जब केवल बाईं दो अंक छोड़ दिया जा रहा बात)। फिर उस दिशा में गति और स्थिति समायोजित करें।

  2. दूसरी दिशा में दो (समायोजित) प्रासंगिक बिंदुओं की टक्कर की गणना करें। टकराव पर उस निर्देश की स्थिति और गति समायोजित करें।

  3. यदि चरण 1 में कोई टक्कर नहीं थी तो आप परिवर्तनों को जारी रख सकते हैं और वापस आ सकते हैं। अन्यथा चरण 1 से पहले मूल स्थिति की तुलना में दूरी dx + dy की गणना करें।

  4. चरण 1 से 3 को दोहराएँ। लेकिन इस बार आप पहले दूसरी दिशा से शुरू करते हैं।

  5. छोटी दूरी के साथ परिवर्तन करें (जब तक आपको पहले चरण 3 में कोई अच्छा बदलाव नहीं मिला)।

संपादित करें: उदाहरण

sizes: sTile = 50, sPlayer = 20 
old position (fine, top-left corner): oX = 27, oY = 35 
speeds: vX = 7, vY = 10 
new position: x = oX + vX = 34, y = oY + vY = 45 => (34, 45) 
solid: tile at (50, 50) 

1.1. Checking x-direction, relevant points for positive vX are the ones to the right: 
    (54, 45) and (54, 65). The latter gives a conflict and we need to correct the 
    position to p1 = (30, 45) and speed v1 = (0, 10). 

2.1. Checking y-direction based on previous position, relevant points: (30, 65) and 
    (50, 65). There is no conflict, p1 and v1 remain unchanged. 

3.1. There was a conflict in step 1.1. so we cannot return the current result 
    immediately and have to calculate the distance d1 = 4 + 0 = 4. 

1.2. Checking y-direction first this time, relevant points: (34, 65) and (54, 65). 
    Because the latter gives a conflict we calculate p2 = (34, 30) and v2 = (7, 0). 

2.2. Checking x-direction based on step 1.2., relevant points: (54, 30) and (54, 50). 
    There is no conflict, p2 and v2 remain unchanged. 

3.2. Because there was a conflict in step 1.2. we calculate the distance d2 = 15. 

5. Change position and speed to p1 and v1 because d1 is smaller than d2. 
+0

हाय, प्रतिक्रिया के लिए धन्यवाद! दुर्भाग्य से जब भी मैं आपके समाधान का पालन करता हूं वही व्यवहार तब भी होता है। शायद मैंने इसे गलत समझा? मैंने पहली पोस्ट में अपना कार्यान्वयन जोड़ा। यदि नहीं, तो मैं एक अलग एल्गोरिदम पर एक साथ स्विच कर सकता हूं। – myohmywhoami

+0

हां ऐसा लगता है कि आप गलत समझते हैं। आपने केवल चेक को अलग किया है लेकिन ऊपर वर्णित अनुसार एल्गोरिदम का पालन नहीं किया है। असल में आपको मूल स्थिति को दो बार कॉपी करना होगा, फिर पहले एक एक्स-दिशा को सही करें और दूसरी वाई-दिशा पहले (और समायोजित करें!)। उन दो नई स्थितियों के आधार पर आप दोनों पदों (और समायोजित) के लिए दूसरी दिशा की जांच करते हैं। अब आप दोनों संभावनाओं के लिए मूल की दूरी की गणना करते हैं और बेहतर लेते हैं (आपके उदाहरण मामले में एक बार खिलाड़ी को थोड़ा बाएं स्थानांतरित किया जाएगा और दूसरी बार वह ऊपर की तरफ बढ़ेगा, इसलिए इसे काम करना चाहिए) – maraca

+0

@myohmywhoami जोड़ा उदाहरण – maraca

संबंधित मुद्दे