/// <summary> /// See parent. /// </summary> /// <param name="msg"></param> public override void OnMessage(ref MBHEngine.Behaviour.BehaviourMessage msg) { base.OnMessage(ref msg); if (msg is HitCountDisplay.TrialScoreLimitReachedMessage) { if (GetCurrentState() is StateEmpty) { AdvanceToState("StateTrialModeLimitRoot"); } } }
/// <summary> /// Perform the path finding. Call repeatedly to continue to try and find the path over a number /// of frames. /// </summary> /// <param name="restrictedArea">Restrict choosen nodes to this area.</param> /// <returns>The result of the path finding for this frame.</returns> public Result PlanPath(MBHEngine.Math.Rectangle restrictedArea, Boolean drawDebug) { // If there is no tile at the destination then there is no path finding to do. if (mEnd == null) { return Result.NotStarted; } if (drawDebug) { // If we have a destination draw it. Even if there isn't a source yet. DebugShapeDisplay.pInstance.AddPoint(mEnd.pPosition, 2.0f, Color.Yellow); } // If the destination is a solid tile then we will never be able to solve the path. if (null != mEnd && !mEnd.IsEmpty()) { // We consider this a failure, similar to if a destination was surrounded by solid. return Result.InvalidLocation; } // If our source position is not on a tile then there is no path finding to do. if (mStart == null) { return Result.NotStarted; } // If our source position is not on a tile, or that tile is solid we cannot ever solve // this path, so abort right away. if (mStart != null && !mStart.IsEmpty()) { // Trying to path find to a solid tile is considered a failure. return Result.InvalidLocation; } if (drawDebug) { // If there is a source, draw it. DebugShapeDisplay.pInstance.AddPoint(mStart.pPosition, 2.0f, Color.Orange); } // If the path hasn't already been invalidated this frame, we need to check that // the path didn't get blocked from something like the Player placing blocks. // TODO: This could be changed to only do this check when receiving specific events, // such as the ObjectPlacement Behaviour telling it that a new block has been // placed. if (!mPathInvalidated) { // Loop through the current path and check for any tiles that are not // empty. If they aren't empty this path is no longer valid as there is // something now blocking it. // PathNode node = mBestPathEnd; while (null != node) { if (!node.pGraphNode.IsEmpty()) { // Setting this flag will force the path finder to start from // the begining. mPathInvalidated = true; // No need to loop any further. One blockade is enough. break; } node = node.pPrevious; } } // If the path has become invalid, we need to restart the pathing algorithm. if (mPathInvalidated) { ClearNodeLists(); // First thing we need to do is add the first node to the open list. PathNode p = mUnusedNodes.Pop(); p.pGraphNode = mStart; // There is no cost because it is the starting node. p.pCostFromStart = 0; // For H we use the actual distance to the destination. The Manhattan Heuristic method. /* p.pCostToEnd = System.Math.Max(System.Math.Abs( p.pGraphNode.pPosition.X - mEnd.pPosition.X), System.Math.Abs(p.pGraphNode.pPosition.Y - mEnd.pPosition.Y)); */ Vector2 source = p.pGraphNode.pPosition; Single h_diagonal = System.Math.Min(System.Math.Abs(source.X - mEnd.pPosition.X), System.Math.Abs(source.Y - mEnd.pPosition.Y)); Single h_straight = System.Math.Abs(source.X - mEnd.pPosition.X) + System.Math.Abs(source.Y - mEnd.pPosition.Y); p.pCostToEnd = (11.314f) * h_diagonal + 8.0f * (h_straight - 2 * h_diagonal); // Add it to the list, and start the search! mOpenNodes.Add(p); // If the path was invalidated that assume that it is not longer solved. mSolved = false; // The path is no longer invalid. It has begun. mPathInvalidated = false; } // Track how many times this planner has looped this time. Int32 count = 0; const Int32 maxLoops = 30; // Loop until all possibilities have been exhusted, the time slice is expired or the // path is solved. while (mOpenNodes.Count > 0 && count < maxLoops && !mSolved)// && (!drawDebug || Input.InputManager.pInstance.CheckAction(Input.InputManager.InputActions.B, true))) { count++; mBestPathEnd = mOpenNodes[0]; //HPAStar.NavMesh.DebugCheckNode(mBestPathEnd.pGraphNode); for (Int32 i = 0; i < mOpenNodes.Count; i++) { if (mOpenNodes[i].pFinalCost <= mBestPathEnd.pFinalCost) { mBestPathEnd = mOpenNodes[i]; } } mOpenNodes.Remove(mBestPathEnd); mClosedNodes.Add(mBestPathEnd); // End the search once the destination node is added to the closed list. // if (mBestPathEnd.pGraphNode == mEnd) { OnPathSolved(drawDebug); mSolved = true; break; } for (Int32 i = 0; i < mBestPathEnd.pGraphNode.pNeighbours.Count; i++) { GraphNode.Neighbour nextNode = mBestPathEnd.pGraphNode.pNeighbours[i]; if (nextNode.mGraphNode.IsPassable(mBestPathEnd.pGraphNode) && (restrictedArea == null || restrictedArea.Intersects(nextNode.mGraphNode.pPosition))) { Boolean found = false; for (Int32 j = 0; j < mClosedNodes.Count; j++) { if (mClosedNodes[j].pGraphNode == nextNode.mGraphNode) { found = true; break; } } // This node is already in the closed list, so move on to the next node. if (found) { continue; } // 3-b. If it isn’t on the open list, add it to the open list. // Make the current square the parent of this square. Record the F, G, and H costs of the square. // TODO: Get a better way to know if something is in the opened list already. // PathNode foundNode = null; for (Int32 j = 0; j < mOpenNodes.Count; j++) { if (mOpenNodes[j].pGraphNode == nextNode.mGraphNode) { foundNode = mOpenNodes[j]; break; } } // Calculate the cost of moving to this node. This is the distance between the two nodes. // This will be needed in both the case where the node is in the open list already, // and the case where it is not. // The cost is the cost of the previous node plus the distance cost to this node. Single costFromCurrentBest = mBestPathEnd.pCostFromStart + nextNode.mCostToTravel; // If the node was not found it needs to be added to the open list. if (foundNode == null) { // Create a new node and add it to the open list so it can been considered for pathing // in the updates to follow. PathNode p = mUnusedNodes.Pop(); p.pGraphNode = nextNode.mGraphNode; // For now it points back to the current node. This can be overwritten if another node // leads here with a lower cost (see else statement below). p.pPrevious = mBestPathEnd; // The cost to get to this node (G) is calculated above. p.pCostFromStart = costFromCurrentBest; // Combo Vector2 source = p.pGraphNode.pPosition; Single h_diagonal = System.Math.Min(System.Math.Abs(source.X - mEnd.pPosition.X), System.Math.Abs(source.Y - mEnd.pPosition.Y)); Single h_straight = System.Math.Abs(source.X - mEnd.pPosition.X) + System.Math.Abs(source.Y - mEnd.pPosition.Y); p.pCostToEnd = (11.314f) * h_diagonal + 8.0f * (h_straight - 2 * h_diagonal); //p.mCostToEnd *= (10.0f + (1.0f/1000.0f)); /* p.pCostToEnd = System.Math.Max(System.Math.Abs( p.pGraphNode.pPosition.X - mEnd.pPosition.X), System.Math.Abs(p.pGraphNode.pPosition.Y - mEnd.pPosition.Y)); */ mOpenNodes.Add(p); // Ending the search now will alomost always result in the best path // but it is possible for it to fail. if (p.pGraphNode == mEnd) { // Since the path is now solved, update mCurBest so that it is used for // tracing back through the path from now on. mBestPathEnd = p; OnPathSolved(drawDebug); break; } } else { // If it is on the open list already, check to see if this path to that square is better, // using G cost as the measure. A lower G cost means that this is a better path. If so, // change the parent of the square to the current square, and recalculate the G and F // scores of the square. If you are keeping your open list sorted by F score, you may need // to resort the list to account for the change. if (foundNode.pCostFromStart > costFromCurrentBest) { foundNode.pPrevious = mBestPathEnd; foundNode.pCostFromStart = costFromCurrentBest; } } } } } // Draw the path. if (drawDebug && mBestPathEnd != null) { DebugDraw(); } if (mSolved) { return Result.Solved; } else if (mOpenNodes.Count > 0) { return Result.InProgress; } else { return Result.Failed; } }
/// <summary> /// Does a collision check between every tile in the level and a specified rectangle. Requires /// both the starting position and the desired position, so that it can figure out details about /// how it collided with different tiles. /// </summary> /// <param name="startRect">The location of this collision rect at the start of this movement.</param> /// <param name="endRect">The rectangle representing the final position that it is trying to get to.</param> /// <param name="collideX">Did this check yield a collision in the X direction?</param> /// <param name="collideY">Did this check yield a collision in the Y direction?</param> /// <param name="collidePointX">Where did the X collision happen (if it did)?</param> /// <param name="collidePointY">Where did the Y collision happen (if it did)?</param> /// <returns></returns> private Boolean CheckForCollision( MBHEngine.Math.Rectangle startRect, MBHEngine.Math.Rectangle endRect, out Boolean collideX, out Boolean collideY, out Single collidePointX, out Single collidePointY) { // Which direction is this thing heading? // Needed to determine which walls to check against (eg. no need to check left wall if we are moving left). Vector2 dir = endRect.pCenterPoint - startRect.pCenterPoint; // Start by assuming no collisions occured. Boolean hit = false; collideX = false; collideY = false; collidePointX = collidePointY = 0; // We don't want to check against every tile in the world. Instead attempt to narrow it down // based on the fact that the tiles can be mapped from their x,y position to their index // into the array. Int32 checkRange = 5; Single x2 = (endRect.pCenterPoint.X / mMapInfo.mTileWidth) - ((Single)checkRange * 0.5f); Int32 startX = System.Math.Max((Int32)System.Math.Round(x2), 0); Single y2 = (endRect.pCenterPoint.Y / mMapInfo.mTileHeight) - ((Single)checkRange * 0.5f); Int32 startY = System.Math.Max((Int32)System.Math.Round(y2), 0); // Loop through every time checking for collisions. for (Int32 y = startY; y < mMapInfo.mMapHeight && y < startY + checkRange; y++) { for (Int32 x = startX; x < mMapInfo.mMapWidth && x < startX + checkRange; x++) { // Is this tile solid and does it have any active walls? // It may be solid with no walls in the case of one completly surrounded. if (mCollisionGrid[x, y].mType != Level.Tile.TileTypes.Empty && mCollisionGrid[x, y].mActiveWalls != Tile.WallTypes.None) { // This tile has been considered for a collision. It will be changed to type 2 if there is a // collision. mCollisionGrid[x, y].SetAttribute(Tile.Attribute.CollisionChecked); // Calculate the center point of the tile. Vector2 cent = new Vector2((x * mMapInfo.mTileWidth) + (mMapInfo.mTileWidth * 0.5f), (y * mMapInfo.mTileHeight) + (mMapInfo.mTileHeight * 0.5f)); // Create a rectangle to represent the tile. //MBHEngine.Math.Rectangle tileRect = new MBHEngine.Math.Rectangle(mTileWidth, mTileHeight, cent); // Does the place we are trying to move to intersect with the tile? if (mCollisionGrid[x, y].mCollisionRect.Intersects(endRect)) { // If we are moving right, and we hit a tile with a left wall... if (dir.X > 0 && (mCollisionGrid[x, y].mActiveWalls & Tile.WallTypes.Left) != Tile.WallTypes.None) { // Create a line from the center of our destination... mCollisionRectMovement.pPointA = mCollisionGrid[x, y].mCollisionRect.pCenterPoint; mCollisionRectMovement.pPointB = endRect.pCenterPoint; // ...and a line representing the wall we are testing against. mCollisionGrid[x, y].mCollisionRect.GetLeftEdge(ref mCollisionWall); // If those two lines intersect, then we count this collision. // We do this line check to avoid colliding with both top/bottom and // left/right in the same movement. That causes issues like getting stuck in // tight corridors. Vector2 intersect = new Vector2(); if (mCollisionWall.Intersects(mCollisionRectMovement, ref intersect)) { DebugShapeDisplay.pInstance.AddSegment(mCollisionRectMovement, Color.DarkRed); DebugShapeDisplay.pInstance.AddPoint(intersect, 1, Color.Orange); // We have collide along the x axis. collideX = true; collidePointX = x * mMapInfo.mTileWidth; } } // If we are moving left, and we hit a tile with a right wall... else if (dir.X < 0 && (mCollisionGrid[x, y].mActiveWalls & Tile.WallTypes.Right) != Tile.WallTypes.None) { mCollisionRectMovement.pPointA = mCollisionGrid[x, y].mCollisionRect.pCenterPoint; mCollisionRectMovement.pPointB = endRect.pCenterPoint; mCollisionGrid[x, y].mCollisionRect.GetRightEdge(ref mCollisionWall); Vector2 intersect = new Vector2(); if (mCollisionWall.Intersects(mCollisionRectMovement, ref intersect)) { DebugShapeDisplay.pInstance.AddSegment(mCollisionRectMovement, Color.DarkRed); DebugShapeDisplay.pInstance.AddPoint(intersect, 1, Color.Orange); // We have collide along the x axis. collideX = true; collidePointX = x * mMapInfo.mTileWidth + mMapInfo.mTileWidth; } } if (dir.Y > 0 && (mCollisionGrid[x, y].mActiveWalls & Tile.WallTypes.Top) != Tile.WallTypes.None) { mCollisionRectMovement.pPointA = mCollisionGrid[x, y].mCollisionRect.pCenterPoint; mCollisionRectMovement.pPointB = endRect.pCenterPoint; mCollisionGrid[x, y].mCollisionRect.GetTopEdge(ref mCollisionWall); Vector2 intersect = new Vector2(); if (mCollisionWall.Intersects(mCollisionRectMovement, ref intersect)) { DebugShapeDisplay.pInstance.AddSegment(mCollisionRectMovement, Color.DarkRed); DebugShapeDisplay.pInstance.AddPoint(intersect, 1, Color.Orange); // We have collide along the y axis. collideY = true; collidePointY = y * mMapInfo.mTileHeight; } } else if (dir.Y < 0 && (mCollisionGrid[x, y].mActiveWalls & Tile.WallTypes.Bottom) != Tile.WallTypes.None) { mCollisionRectMovement.pPointA = mCollisionGrid[x, y].mCollisionRect.pCenterPoint; mCollisionRectMovement.pPointB = endRect.pCenterPoint; mCollisionGrid[x, y].mCollisionRect.GetBottomEdge(ref mCollisionWall); Vector2 intersect = new Vector2(); if (mCollisionWall.Intersects(mCollisionRectMovement, ref intersect)) { DebugShapeDisplay.pInstance.AddSegment(mCollisionRectMovement, Color.DarkRed); DebugShapeDisplay.pInstance.AddPoint(intersect, 1, Color.Orange); // We have collide along the y axis. collideY = true; collidePointY = y * mMapInfo.mTileHeight + mMapInfo.mTileHeight; } } //DebugMessageDisplay.pInstance.AddDynamicMessage("Collide Dir: " + dir); // Set the collision type temporarily to 2, to signal that it collided. mCollisionGrid[x, y].SetAttribute(Tile.Attribute.Collision); // If we make it to this point there was a collision of some type. hit = true; } } } } return hit; }
/// <summary> /// Checks if a Rectangle is on screen at all. /// </summary> /// <param name="rect">The rectangle to check.</param> /// <returns></returns> public Boolean IsOnCamera(MBHEngine.Math.Rectangle rect) { return mViewRectangle.Intersects(rect); }