/// <summary> /// converts a cartesian form vector to a polar form angle /// </summary> /// <param name="direction">the direction vector being converted</param> /// <returns>the polar form angle</returns> public static float CartesianToPolar(Vector direction) { if (direction.X == 0 && direction.Y == 0) // has no direction, so return angle of 0 { return 0; } else { return (float)Math.Atan2((double)direction.X, -(double)direction.Y); } }
/// <summary> /// sets up and begins new round /// </summary> /// <param name="form">the form being used for the game</param> public static void StartNew(Form form) { LeftMovement1 = false; RightMovement1 = false; LeftMovement2 = false; RightMovement2 = false; LastPlayerHit = Player.Player1; LabelLevels.Text = "Levels Completed: " + LevelsCompleted.ToString(); UpdateScores(); Balls = new List<Ball>(); Blocks = new List<Block>(); FormSize = new Vector(form.Size.Width, form.Size.Height - 100); PictureBox paddle1 = new PictureBox(); form.Controls.Add(paddle1); if (CurrentMode == GameMode.Coop || CurrentMode == GameMode.Versus) // 2 player { PictureBox paddle2 = new PictureBox(); form.Controls.Add(paddle2); PaddleObject2 = new Paddle(paddle2, new Vector(FormSize.X * 0.75f, FormSize.Y)); PaddleObject1 = new Paddle(paddle1, new Vector(FormSize.X * 0.25f, FormSize.Y)); } else // 1 player { PaddleObject1 = new Paddle(paddle1, new Vector(FormSize.X * 0.5f, FormSize.Y)); PaddleObject2 = null; } currentForm = form; BallsInPlay = 0; AddBall(); int blockRows = ((CurrentMode == GameMode.Classic) ? DefaultBlockRows : BlockRows); int blockColumns = ((CurrentMode == GameMode.Classic) ? DefaultBlockColumns : BlockColumns); int blockWidth = (int)FormSize.X / blockColumns; int blockHeight = (int)FormSize.Y / 3 / blockRows; const int blockFrame = 4; for (int x = 0; x < blockColumns; x++) { for (int y = 0; y < blockRows; y++) { float rowPercentage = (float)y / (float)blockRows; PictureBox newPictureBox = new PictureBox(); newPictureBox.BackColor = Color.FromArgb((int)Math.Round(255 * Math.Max(0, 1 - rowPercentage * 2), 0), (int)Math.Round(255 * (1 - 2 * Math.Abs(0.5f - rowPercentage)), 0), (int)Math.Round(255 * Math.Max(0, rowPercentage * 2 - 1), 0)); currentForm.Controls.Add(newPictureBox); Vector newBlockPosition = new Vector(x * blockWidth + blockFrame * 0.5f, y * blockHeight + blockFrame * 0.5f); Vector newBlockSize = new Vector(blockWidth - blockFrame, blockHeight - blockFrame); Block newBlock = new Block(newPictureBox, newBlockPosition, newBlockSize); newBlock.Points = 50 + 50 * (blockRows - y); } } gameThread = new Thread(GameThread); gameThread.IsBackground = true; gameThread.Start(); form.Disposed += delegate { gameThread.Abort(); }; InPlay = true; }
/// <summary> /// calculates whether or not 2 boxes intersect using the AABB bounding boxes method /// </summary> /// <param name="position1">the vector position of the first box</param> /// <param name="size1">the vector size of the first box</param> /// <param name="position2">the vector position of the second box</param> /// <param name="size2">the vector size of the second box</param> /// <returns>whether the boxes intersect of not</returns> public static bool Intersects(Vector position1, Vector size1, Vector position2, Vector size2) { return (position1.X + size1.X >= position2.X && position1.X <= position2.X + size2.X && position1.Y + size1.Y >= position2.Y && position1.Y <= position2.Y + size2.Y); }
/// <summary> /// try to move the ball. Handles block, paddle and wall collisions /// </summary> /// <param name="newVelocity">the velocity the ball is trying to move with</param> /// <param name="timeElapsed">the time since the last move</param> private void Move(Vector newVelocity, float timeElapsed) { Vector positionChange = Vector.Mul(newVelocity, timeElapsed * 1000); Vector position = this.Position; Vector size = this.Size; Vector newPosition = Vector.Add(position, positionChange); List<Block> blocksHit = new List<Block>(); List<int> collidedRays = new List<int>(); bool reflectedX = false; bool reflectedY = false; foreach (Block block in Game.Blocks) { if (Box.Intersects(block.Position, block.Size, newPosition, size)) { //blockHit = true; // IMPORTANT -- // Each Vector ray of ball in collideswith algorithms SHOULD only be collided with once. // More than once can result in blocks being destroyed that shouldn't!!! CollisionReturn collisionBlockReturn = CollidesWithBlock(block, position, positionChange, collidedRays); if (collisionBlockReturn.Intersects) // hits a block { if ((collisionBlockReturn.Edge == 0 || collisionBlockReturn.Edge == 2) && !reflectedY) // Y { newVelocity.Y *= -1; reflectedY = true; } if ((collisionBlockReturn.Edge == 1 || collisionBlockReturn.Edge == 3) && !reflectedX) // X { newVelocity.X *= -1; reflectedX = true; } this.Velocity = newVelocity; blocksHit.Add(block); collidedRays.Add(collisionBlockReturn.Ray); } } } foreach (Block block in blocksHit) { Game.DestroyBlock(block); } if (blocksHit.Count <= 0) { if (Box.Intersects(Game.PaddleObject1.Position, Game.PaddleObject1.Size, newPosition, size)) { CollisionReturn collisionPaddleReturn = CollidesWithPaddle(Game.PaddleObject1, position, positionChange); //System.Console.WriteLine(collisionPaddleReturn.Intersects + ", " + collisionPaddleReturn.length + ", " + collisionPaddleReturn.Edge); if (collisionPaddleReturn.Intersects) // hits paddle { Game.LastPlayerHit = Player.Player1; // ID EDGE IS 0 THEN ADD SPIN + ANGLE if (collisionPaddleReturn.Edge == 0 || collisionPaddleReturn.Edge == 2) // Y { newVelocity.Y *= -1; float speed = this.Speed; float currentAngle = Angles.CartesianToPolar(newVelocity); float newAngle = (position.X + size.X * 0.5f - Game.PaddleObject1.Position.X - Game.PaddleObject1.Size.X * 0.5f) / 175 * 90 + Game.PaddleObject1.Speed * 30; // left - negative angle, right - positive angle newAngle += Angles.Deg(currentAngle); while (newAngle < 0) { newAngle += 360; } if (newAngle < 180) { newAngle = Math.Min(newAngle, 85); } else { newAngle = Math.Max(newAngle, 275); } Vector newDirection = Angles.PolarToCartesian(Angles.Rad(newAngle)); newVelocity = Vector.Mul(newDirection, speed); //newVelocity = new Vector((float)Math.Round(newVelocity.X, 0), (float)Math.Round(newVelocity.Y, 0)); } else // X - 1 || 3 { // 2 different edges have raycast at same distance, therefore wrong edge can be chosen when ball hits very edge newVelocity.X *= -1; } this.Velocity = newVelocity; Move(newVelocity, timeElapsed); } } else if (Game.PaddleObject2 != null && Box.Intersects(Game.PaddleObject2.Position, Game.PaddleObject2.Size, newPosition, size)) { CollisionReturn collisionPaddleReturn = CollidesWithPaddle(Game.PaddleObject2, position, positionChange); //System.Console.WriteLine(collisionPaddleReturn.Intersects + ", " + collisionPaddleReturn.length + ", " + collisionPaddleReturn.Edge); if (collisionPaddleReturn.Intersects) // hits paddle { Game.LastPlayerHit = Player.Player2; // ID EDGE IS 0 THEN ADD SPIN + ANGLE if (collisionPaddleReturn.Edge == 0 || collisionPaddleReturn.Edge == 2) // Y { newVelocity.Y *= -1; float speed = this.Speed; float currentAngle = Angles.CartesianToPolar(newVelocity); float newAngle = (position.X + size.X * 0.5f - Game.PaddleObject2.Position.X - Game.PaddleObject2.Size.X * 0.5f) / 175 * 90 + Game.PaddleObject2.Speed * 30; // left - negative angle, right - positive angle newAngle += Angles.Deg(currentAngle); while (newAngle < 0) { newAngle += 360; } if (newAngle < 180) { newAngle = Math.Min(newAngle, 85); } else { newAngle = Math.Max(newAngle, 275); } Vector newDirection = Angles.PolarToCartesian(Angles.Rad(newAngle)); newVelocity = Vector.Mul(newDirection, speed); //newVelocity = new Vector((float)Math.Round(newVelocity.X, 0), (float)Math.Round(newVelocity.Y, 0)); } else // X - 1 || 3 { // 2 different edges have raycast at same distance, therefore wrong edge can be chosen when ball hits very edge newVelocity.X *= -1; } this.Velocity = newVelocity; Move(newVelocity, timeElapsed); } } else // check walls { bool velocityChanged = false; bool ballDestroyed = false; if (newPosition.X < 0) { newVelocity.X *= -1; velocityChanged = true; } if (newPosition.X + size.X > Game.FormSize.X) { newVelocity.X *= -1; velocityChanged = true; } if (newPosition.Y < 0) { newVelocity.Y *= -1; velocityChanged = true; } if (newPosition.Y + size.Y > Game.FormSize.Y) { Game.DestroyBall(this); ballDestroyed = true; } if (!ballDestroyed) { if (velocityChanged) { this.Velocity = newVelocity; Move(newVelocity, timeElapsed); } else { this.Position = newPosition; } } } } else { Move(newVelocity, timeElapsed); } }
public int Points; // how many points the block awards when destroyed #endregion Fields #region Constructors // class constructor. A block will not move, so I can set the edges at creation, assuming they will never move public Block(PictureBox picture, Vector position, Vector size) { PictureBox = picture; Position = position; Size = size; Edges[TopEdge] = new Ray(position, new Vector(Size.X, 0)); // top edge Edges[RightEdge] = new Ray(Vector.Add(position, new Vector(Size.X, 0)), new Vector(0, Size.Y)); // right edge Edges[BottomEdge] = new Ray(Vector.Add(position, new Vector(0, Size.Y)), new Vector(Size.X, 0)); // bottom edge Edges[LeftEdge] = new Ray(position, new Vector(0, Size.Y)); // left edge Game.Blocks.Add(this); }
/// <summary> /// calculates collisions between the ball and a block, and finds the block surface tha was hit /// </summary> /// <param name="block">the block being hit</param> /// <param name="newPosition">the position the ball has</param> /// <param name="newVelocity">the velocity the ball is trying to move with</param> /// <param name="collidedRays">a list of rayIDs which have already collided with a surface</param> /// <returns>the position, length and surface of the collision</returns> public CollisionReturn CollidesWithBlock(Block block, Vector newPosition, Vector newVelocity, List<int> collidedRays) { // vector rays of moving ball Ray[] Vectors = new Ray[4]; Vectors[TopEdge] = new Ray(newPosition, newVelocity); Vectors[RightEdge] = new Ray(Vector.Add(newPosition, new Vector(Size.X, 0)), newVelocity); Vectors[BottomEdge] = new Ray(Vector.Add(newPosition, new Vector(0, Size.Y)), newVelocity); Vectors[LeftEdge] = new Ray(Vector.Add(newPosition, new Vector(Size.X, Size.Y)), newVelocity); CollisionReturn collisionReturn = new CollisionReturn(false); int rayID = 0; foreach (Ray ray in Vectors) { int edgeID = 0; if (!collidedRays.Contains(rayID)) // if the ray being used hasn't already collided with a box { // check every edge of the block foreach (Ray edge in block.Edges) { RaycastReturn raycastReturn = Ray.Raycast(ray, edge); // see if the ray meets the edge if (raycastReturn.Intersects) { // if there has already been a collision, check for a shorter one if (collisionReturn.Intersects) { if (raycastReturn.length < collisionReturn.length) // if the ball velocity ray hits this edge before any other ray hits an edge { collisionReturn = new CollisionReturn(raycastReturn); collisionReturn.Edge = edgeID; collisionReturn.Ray = rayID; } } // haven't had a ray collision yet, so use this collision by default else { collisionReturn = new CollisionReturn(raycastReturn); collisionReturn.Edge = edgeID; collisionReturn.Ray = rayID; } } edgeID++; // check next edge } } rayID++; // check next ray } return collisionReturn; // return results }
/// <summary> /// calculates collisions between the ball and a paddle, and finds the paddle surface that was hit /// </summary> /// <param name="paddle">the paddle that is being hit</param> /// <param name="newPosition">the position the ball has</param> /// <param name="newVelocity">the velocity the ball is trying to move with</param> /// <returns>the position, length and surface of the collision</returns> public CollisionReturn CollidesWithPaddle(Paddle paddle, Vector newPosition, Vector newVelocity) { Ray[] Vectors = new Ray[4]; // vector rays of moving ball Vectors[TopEdge] = new Ray(newPosition, newVelocity); Vectors[RightEdge] = new Ray(Vector.Add(newPosition, new Vector(Size.X, 0)), newVelocity); Vectors[BottomEdge] = new Ray(Vector.Add(newPosition, new Vector(0, Size.Y)), newVelocity); Vectors[LeftEdge] = new Ray(Vector.Add(newPosition, new Vector(Size.X, Size.Y)), newVelocity); CollisionReturn collisionReturn = new CollisionReturn(false); foreach (Ray ray in Vectors) { int edgeID = 0; foreach (Ray edge in paddle.Edges) { RaycastReturn raycastReturn = Ray.Raycast(ray, edge); if (raycastReturn.Intersects) { if (collisionReturn.Intersects) { if (raycastReturn.length < collisionReturn.length) { collisionReturn = new CollisionReturn(raycastReturn); collisionReturn.Edge = edgeID; } } else { collisionReturn = new CollisionReturn(raycastReturn); collisionReturn.Edge = edgeID; } } edgeID++; } } return collisionReturn; }
/// <summary> /// to simplify adding 2 vectors together /// </summary> /// <param name="vector1">the first vector being added</param> /// <param name="vector2">the second vector being added</param> /// <returns>the composition of the 2 vectors</returns> public static Vector Add(Vector vector1, Vector vector2) { return new Vector(vector1.X + vector2.X, vector1.Y + vector2.Y); }
/// <summary> /// to simplify multiplying a vector by a scale factor /// </summary> /// <param name="vector">the vector being multiplied</param> /// <param name="alpha">the scale factor to multiply it with</param> /// <returns>the product of the vector and the scale factor</returns> public static Vector Mul(Vector vector, float alpha) { return new Vector(vector.X * alpha, vector.Y * alpha); }
/// <summary> /// will check for collisions between 2 rays and return the results /// </summary> /// <param name="ray1">the first ray in the collision</param> /// <param name="ray2">the second ray in the collision</param> /// <returns>the position and length of the raycast collision</returns> public static RaycastReturn Raycast(Ray ray1, Ray ray2) { // shorten the variable names float x1 = ray1.Position.X; float y1 = ray1.Position.Y; float a1 = ray1.Direction.X; float b1 = ray1.Direction.Y; float x2 = ray2.Position.X; float y2 = ray2.Position.Y; float a2 = ray2.Direction.X; float b2 = ray2.Direction.Y; if (a1 * b2 == a2 * b1) // parallel, so won't collide { return new RaycastReturn(false); // rays don't collide, so return false collision } else // they collide at some point, given their lengths are infinite { float i = (x2 * b2 + a2 * y1 - a2 * y2 - x1 * b2) / (a1 * b2 - a2 * b1); // distance between start and collision along ray1 Vector position = new Vector(x1 + i * a1, y1 + i * b1); // form coordinates of collision if (i > 1 || i < 0) // outside length of ray1 { return new RaycastReturn(false); // do not meet each other, so return false collision } else // they collide within their lengths { float length = i * ray1.Length; // length of ray1 up to collision return new RaycastReturn(true, position, length); // return true collision } } }
// detailed class constructor for (true) raycast collisions public RaycastReturn(bool intersects, Vector position, float length) { this.Intersects = intersects; this.Position = position; this.length = length; }
public Vector Velocity; // the speed and direction of the ball in vector form #endregion Fields #region Constructors // class constructor, requires a picturebox and a starting velocity public Ball(PictureBox picture, Vector velocity) { this.PictureBox = picture; this.Velocity = velocity; // the game is in a 2 player mode, and player 2 lost the ball, so player 2 gets the new ball if ((Game.CurrentMode == GameMode.Coop || Game.CurrentMode == GameMode.Versus) && Game.LastPlayerLost == Player.Player2) { this.Position = new Vector(Game.PaddleObject2.Position.X + Game.PaddleObject2.Size.X * 0.5f - Size.X * 0.5f, Game.PaddleObject2.Position.Y - Size.Y); Game.LastPlayerHit = Player.Player2; // player 2 starts with the ball } // otherwise player1 gets the ball else { this.Position = new Vector(Game.PaddleObject1.Position.X + Game.PaddleObject1.Size.X * 0.5f - Size.X * 0.5f, Game.PaddleObject1.Position.Y - Size.Y);//new Vector(Game.FormSize.X * 0.5f - Size.X * 0.5f, Game.FormSize.Y * 0.5f - Size.Y * 0.5f); Game.LastPlayerHit = Player.Player1; // player 1 starts with the new ball } Game.Balls.Add(this); }
// class constructor for length if unit direction vector is given public Ray(Vector position, Vector direction, float length) { this.Position = position; this.Direction = direction; this.Length = length; }
// class constructor with position and direction public Ray(Vector position, Vector direction) { this.Position = position; this.Direction = direction; }
public Vector Position; // form coordinates of the ray #endregion Fields #region Constructors // empty ray class constructor public Ray() { this.Position = new Vector(); this.Direction = new Vector(); }
private Ray[] edges = new Ray[4]; // privately stored box edges to be used internally, as it should only be set by CalculateEdges() #endregion Fields #region Constructors // class constructor, needs a picturebox and a starting position. The edges are not calculated as they are not needed at the start, and can still be used later public Paddle(PictureBox pictureBox, Vector position) { PictureBox = pictureBox; PictureBox.BackColor = Color.DodgerBlue; PictureBox.Size = new Size(150, 25); Position = new Vector(position.X - Size.X * 0.5f, position.Y - Size.Y); }