{"id":42,"date":"2022-05-20T15:10:22","date_gmt":"2022-05-20T19:10:22","guid":{"rendered":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/?p=42"},"modified":"2022-05-20T15:10:22","modified_gmt":"2022-05-20T19:10:22","slug":"day-5-hard-pong-collisions","status":"publish","type":"post","link":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/?p=42","title":{"rendered":"Day 5: Hard Pong Collisions"},"content":{"rendered":"\n<p>In our latest class, we established our collision and bouncing system using a two-step process:<\/p>\n\n\n\n<ol><li>Identify whether or not our ball was still inside of the field of play<\/li><li>If we are outside of the box, figure out what our position inside should be and move the ball there.<\/li><\/ol>\n\n\n\n<p>To use this, we will run a number of checks and scenarios using the &#8220;if\/elseif&#8221; and &#8220;switch&#8221; conditional statements.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h3 class=\"wp-block-heading\">Part 1: Determining the boundaries<\/h3>\n\n\n\n<p>Next week, we will use Unity to process our collisions, and it will do this quite efficiently.  For now, however, we will create our own collision system to get a sense of how these work.  In our ideal situation,  walls have perfect reflection, and so if the ball were to move in a path that would move it beyond the edge of the playing field, it would perfectly bounce back along that axis to remain in play.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/courses.ideate.cmu.edu\/53-353\/m2020\/wp-content\/uploads\/2020\/05\/image-2-1024x579.png\" alt=\"\" class=\"wp-image-45\"\/><\/figure>\n\n\n\n<p>Since we are only reflecting based upon the outer walls, this process is easy &#8211; we simply need to make sure that with each move, the ball remains inside of the outer boundaries, and if it does not, we need to adjust the position to keep it inside.   These checks will require us to know the exact numeric position of these border lines, and we will refer to them repeatedly.   Rather than repeatedly hard code these values, it would make sense for us to define a set of private variables to hold these values.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    private float FIELD_LEFT = -48.5f;\n    private float FIELD_RIGHT = 48.5f;\n    private float FIELD_TOP = 23.5f;\n    private float FIELD_BOTTOM = -23.5f;<\/pre>\n\n\n\n<p>In class, we originally set these as 50, -50, 25, and -25, to reflect the edges of the plane that we created.&nbsp; But since our ball has a 3 unit radius, we trim 1.5 units off of each side to give it the appearance of bouncing off of the \u201cedge\u201d.&nbsp; Also note the use here of SNAKE_CASE for the variable names.&nbsp; While we are not setting these to be \u201cstatic\u201d variables (meaning their value will not change), we are going to treat them as such, and use the snake case format to remind us that these are holding constants, ones that we should not alter.<\/p>\n\n\n\n<p>Now that we know our boundaries, we have to check each time the ball moves to see if it crossed one of these boundaries.&nbsp; If it did, we will have to process that bounce.&nbsp; Let\u2019s start by creating the following code to check our ball position.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">     void CheckBall () {\n         Vector3 curPos = myBall.transform.position;\n \n         if (curPos.z >= FIELD_TOP) {\n             \/\/ bounce off of top\n             BounceBall(curPos, \"top\");\n         } else if (curPos.z &lt;= FIELD_BOTTOM) {\n             \/\/ bounce off of bottom\n             BounceBall(curPos, \"bottom\");\n         }\n \n         if (curPos.x >= FIELD_RIGHT) {\n             \/\/ bounce off of right\n             BounceBall(curPos, \"right\");\n         } else if (curPos.x &lt;= FIELD_LEFT) {\n             \/\/ bounce off of left\n             BounceBall(curPos, \"left\");\n         }\n \n     }<\/pre>\n\n\n\n<p>In this function, we do the following:<\/p>\n\n\n\n<ol><li>Get the current position of the ball (assuming the ball just moved).&nbsp; &nbsp;Note that we don\u2019t have to declare a \u201cnew\u201d Vector 3, because the object we are passing into this variable, the current position of the ball, is already of that structure.<\/li><li>We test to see if it has crossed the upper or lower boundaries.&nbsp; Since both will never be true, we can use a \u201cif-elseif\u201d statement to check one condition, and then the other if the first is not met.<\/li><li>If the condition is met (i.e., the ball crossed the left or right boundary), we run the&nbsp;<strong>BounceBall ( )&nbsp;<\/strong>command that we will create next.&nbsp; Notice that this time we actually put something inside of the parenthesis of the function.&nbsp; These are called \u201cparameters\u201d, and they are values that are passed to the function to be processed.&nbsp; In this case we are passing the position of the ball, and the direction it should be bouncing off of.<\/li><li>Repeat the process for left\/right walls.<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Part 2: The &#8220;switch&#8221; statement<\/h3>\n\n\n\n<p class=\"EnlighterJSRAW\">Now let\u2019s make\u00a0<strong>BounceBall ( )<\/strong>.\u00a0 We start out with the declaration of the function like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"> void BounceBall (Vector3 bouncePos, string bounceObj) { ... }<\/pre>\n\n\n\n<p>This function, like most, will be declared as a\u00a0<strong>void<\/strong>.\u00a0 This is because we do not expect anything to return from it.\u00a0 If we were writing a function that would return the value of two decimals added together, we would declare it as a\u00a0<strong>float<\/strong>.\u00a0 If we wanted to write a function that would return TRUE or FALSE conditions, we would define it as a\u00a0<strong>bool<\/strong>.\u00a0 But for functions where something happens but nothing comes back, we use <strong>void<\/strong>.<\/p>\n\n\n\n<p>During this declaration, we create two parameter variables to hold the values the function call is passing to this.&nbsp; These variables must be appropriately typed, so here we declare a Vector3 called \u201cbouncePos\u201d that will receive the position of the ball, and a string called \u201cbounceObj\u201d that will hold the text of which wall we are bouncing off of so that we know how to reposition the ball and change the heading.<\/p>\n\n\n\n<p>Since we have four possible bounces here (top, bottom, left, and right), we are going to use a\u00a0<strong>switch<\/strong>\u00a0statement to determine which code runs.\u00a0 \u00a0Switch statements look like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    switch (someVariable) {\n \n         case [value 1]:\n             [some code]\n             break;\n         case [value 2]:\n             [some other code]\n             break;\n         case [value 3]:\n             [yet more code]\n             break;\n             ...\n         default:\n             [default code]\n             break;\n         }<\/pre>\n\n\n\n<p>The \u201cswitch\u201d tests a variable against conditions and if it finds a match in a \u201ccase\u201d, it runs that code.&nbsp; If it does not find a match and reaches the end, the \u201cdefault\u201d option will run.&nbsp; Notice that each case has a \u201cbreak\u201d call.&nbsp; This will stop any further evaluation and move back outside of the switch statement.&nbsp; &nbsp;If you do not include a \u201cbreak\u201d, then your case will run and \u201cdefault\u201d will also run, so make sure you remember to escape the statement with a break.<\/p>\n\n\n\n<p>To process the bounce, we covered some basic geometry in class that we will use as our \u201cbounce math\u201d.&nbsp; To summarize, if a ball is traveling up and to the right and encounters the top wall, the bounced ball will still move as far to the right as it would have had the wall not been there.&nbsp; So for a \u201ctop\u201d bounce, we know our X value is still valid.&nbsp; Our math tells us that our Z value needs to be changed to be the same distance below the top edge as it currently is above (CURRENT POSITION minus BOUNDARY).&nbsp; So our new position = BOUNDARY \u2013 (CURRENT POSITION \u2013 BOUNDARY), or (2 * BOUNDARY) \u2013 CURRENT POSITION.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Finishing our Bounce<\/h2>\n\n\n\n<p>We completed our <strong>BounceBall( )<\/strong> method to read like this.  Notice that we have chosen multiple methods to get both the bounce position and the revised direction.  All of these evaluate to the same result, they are different ways of expressing the same operation.   Our new bounced position can be defined as <em><code>BOUNDARY - (position - BOUNDARY)<\/code><\/em>, which is the same as <em><code>BOUNDARY + (BOUNDARY - position)<\/code> <\/em>which is the same as <em><code>(2 * BOUNDARY) - position<\/code>.<\/em><\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"632\" src=\"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/wp-content\/uploads\/2022\/05\/image-3-1024x632.png\" alt=\"\" class=\"wp-image-46\" srcset=\"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/wp-content\/uploads\/2022\/05\/image-3-1024x632.png 1024w, https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/wp-content\/uploads\/2022\/05\/image-3-300x185.png 300w, https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/wp-content\/uploads\/2022\/05\/image-3-768x474.png 768w, https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/wp-content\/uploads\/2022\/05\/image-3.png 1100w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure><\/div>\n\n\n\n<p>For our direction, we need to flip the value in either the x or z axis, which we can do by saying <code><em>value =value * -1<\/em>,<\/code> or <code><em>value = -value<\/em> <\/code> or <code> <em>value *= -1<\/em><\/code>.  All of these evaluate to the same result.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">private void BounceBall(Vector3 bouncePos, string bounceObj)\n{\n    switch(bounceObj)\n    {\n        case \"top\":\n            \/\/ bouncing off of the top\n            bouncePos.z = FIELD_TOP - (bouncePos.z - FIELD_TOP);\n            direction.z = -1.0f * direction.z;\n            break;\n\n        case \"bottom\":\n            \/\/ bounce off of the bottom\n            bouncePos.z = FIELD_BOTTOM - (bouncePos.z - FIELD_BOTTOM);\n            direction.z = -direction.z;\n            break;\n\n        case \"left\":\n            \/\/ bounce off of the left\n            bouncePos.x = 2 * FIELD_LEFT - bouncePos.x;\n            direction.x = -direction.x;\n            break;\n\n        case \"right\":\n            \/\/ bounce off of the right\n            bouncePos.x = 2 * FIELD_RIGHT - bouncePos.x;\n            direction.x *= -1;\n            break;\n\n        default:\n            Debug.Log(\"BounceBall Switch reached default\");\n            break;\n    }\n\n    \/\/update the ball position\n    myBall.transform.position = bouncePos;\n\n}<\/pre>\n\n\n\n<p>Now our ball bounces against all four walls.  But we have a new problem &#8211; the bounce is not clean.  Our ball travels into the wall a bit before returning to us, instead of cleanly bouncing off of the sides.  This is because our boundary position corresponds with the edge of the wall, but our ball position is at the center of our sphere object.  Rather than building a complicated checking mechanism (after all, that&#8217;s what they physics system is for), we will simply reduce each boundary&#8217;s value by 1.5 units towards the origin, thus creating an invisible fence that will keep our ball the distance of the radius from the walls.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Assignment:<\/h2>\n\n\n\n<p>Your assignment for this weekend has two challenges:<\/p>\n\n\n\n<ol><li>Make the ball bounce off of your paddle (front-face only) to keep it in play<\/li><li>Limit the movement of the paddles to stay within the field of play (don&#8217;t go through the walls)<\/li><\/ol>\n\n\n\n<p>Details are available here:  <a href=\"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/?p=35\" data-type=\"post\" data-id=\"35\">Assignment 1: Paddles<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<pre class=\"wp-block-preformatted\">Ping.cs<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Collections;\nusing System.Collections.Generic;\nusing UnityEngine;\n\npublic class Ping : MonoBehaviour\n{\n    public GameObject myBall;\n    public GameObject leftPaddle;\n    public GameObject rightPaddle;\n\n    public Vector3 direction;\n    public float speed;\n\n    public float paddleSpeed;\n\n    \/\/ Boundary Variables\n    private float FIELD_LEFT = -48.5f;\n    private float FIELD_RIGHT = 48.5f;\n    private float FIELD_TOP = 23.5f;\n    private float FIELD_BOTTOM = -23.5f;\n\n    \/\/ Start is called before the first frame update\n    void Start()\n    {\n        myBall.transform.position = new Vector3();  \/\/ Reset the ball to 0,0,0\n        \/\/ normalize the direction vector\n        direction.Normalize();\n    }\n\n    \/\/ Update is called once per frame\n    void Update()\n    {\n        \/\/ move the ball\n        myBall.transform.position = myBall.transform.position + (direction * speed * Time.deltaTime);\n\n        \/\/ check the ball position\n        CheckBall();\n\n        \/\/ move the paddles\n        MovePaddles();\n\n    }\n\n    private void CheckBall()\n    {\n        Vector3 curPos = myBall.transform.position;\n\n        if (curPos.z >= FIELD_TOP)\n        {\n            \/\/ bounce off of the top\n            BounceBall(curPos, \"top\");\n        } else if (curPos.z &lt;= FIELD_BOTTOM)\n        {\n            \/\/ bounce off of the bottom\n            BounceBall(curPos, \"bottom\");\n        }\n\n        if (curPos.x &lt;= FIELD_LEFT)\n        {\n            \/\/ bounce off of the left\n            BounceBall(curPos, \"left\");\n        } else if (curPos.x >= FIELD_RIGHT)\n        {\n            \/\/ bounce off of the right\n            BounceBall(curPos, \"right\");\n        }\n    }\n\n    private void BounceBall(Vector3 bouncePos, string bounceObj)\n    {\n        switch(bounceObj)\n        {\n            case \"top\":\n                \/\/ bouncing off of the top\n                bouncePos.z = FIELD_TOP - (bouncePos.z - FIELD_TOP);\n                direction.z = -1.0f * direction.z;\n                break;\n\n            case \"bottom\":\n                \/\/ bounce off of the bottom\n                bouncePos.z = FIELD_BOTTOM - (bouncePos.z - FIELD_BOTTOM);\n                direction.z = -direction.z;\n                break;\n\n            case \"left\":\n                \/\/ bounce off of the left\n                bouncePos.x = 2 * FIELD_LEFT - bouncePos.x;\n                direction.x = -direction.x;\n                break;\n\n            case \"right\":\n                \/\/ bounce off of the right\n                bouncePos.x = 2 * FIELD_RIGHT - bouncePos.x;\n                direction.x *= -1;\n                break;\n\n            default:\n                Debug.Log(\"BounceBall Switch reached default\");\n                break;\n        }\n\n        \/\/update the ball position\n        myBall.transform.position = bouncePos;\n\n    }\n\n    private void MovePaddles()\n    {\n        \/\/ get the current position of the paddles\n        Vector3 leftPadPos = leftPaddle.transform.position;\n        Vector3 rightPadPos = rightPaddle.transform.position;\n\n        \/\/ adjust that position based on the keys pressed\n        if (Input.GetKey(\"up\"))\n        {\n            rightPadPos.z = rightPadPos.z + (paddleSpeed * Time.deltaTime);\n        }\n        if (Input.GetKey(\"down\"))\n        {\n            rightPadPos.z = rightPadPos.z - (paddleSpeed * Time.deltaTime);\n        }\n        if (Input.GetKey(KeyCode.W))\n        {\n            leftPadPos.z += (paddleSpeed * Time.deltaTime);\n        }\n        if (Input.GetKey(KeyCode.S))\n        {\n            leftPadPos.z -= (paddleSpeed * Time.deltaTime);\n        }\n\n        \/\/ put the new position back into the object's transform\n        rightPaddle.transform.position = rightPadPos;\n        leftPaddle.transform.position = leftPadPos;\n    }\n}<\/pre>\n","protected":false},"excerpt":{"rendered":"<p><a class=\"more-link\"  href=\"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/?p=42\"><span class=\"more-text\"><\/span><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"greenlet_layout":[]},"categories":[6],"tags":[],"_links":{"self":[{"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/posts\/42"}],"collection":[{"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=42"}],"version-history":[{"count":4,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/posts\/42\/revisions"}],"predecessor-version":[{"id":47,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=\/wp\/v2\/posts\/42\/revisions\/47"}],"wp:attachment":[{"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=42"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=42"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/53-353\/m2022\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=42"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}