/// <summary> /// Initialize the singleton. Call before first use. /// </summary> /// <param name="device">The initialized graphics device. Used to calculate screen position.</param> public void Initialize(GraphicsDevice device) { mTransform = Matrix.Identity; mTargetPosition = new Vector2(device.Viewport.Width * 0.5f, device.Viewport.Height * 0.5f); mLastPosition = new Vector2(); mCurBlendFrames = 0; mBlendFrames = 10; mScreenCenter = Matrix.CreateTranslation(device.Viewport.Width * 0.5f, device.Viewport.Height * 0.5f, 0); #if SMALL_WINDOW mZoomAmount = 4.0f; #else mZoomAmount = 8.0f; #endif mTransform = Matrix.CreateTranslation(-new Vector3(0f, 0f, 0.0f)) * //Matrix.CreateRotationZ(Rotation) * Matrix.CreateScale(new Vector3(mZoomAmount)) * mScreenCenter; mTransformUI = Matrix.CreateScale(new Vector3(mZoomAmount)); mViewRectangle = new Math.Rectangle(); }
/// <summary> /// Constructor. /// </summary> /// <param name="clusterSize">How many tiles along each wall of a Cluster. Assumes square tiles.</param> /// <param name="tileWidth">The width in pixels of a single Tile in this Level.</param> /// <param name="tileHeight">The height in pixels of a single Tile in this Level.</param> public Cluster(Int32 clusterSize, Int32 tileWidth, Int32 tileHeight) : base() { mClusterSize = clusterSize; mTileDimensions = new Point(tileWidth, tileHeight); mBounds = new Math.Rectangle(tileWidth * clusterSize, tileHeight * clusterSize); mNeighbours = new Cluster[(Int32)AdjacentClusterDirections.COUNT]; }
/// <summary> /// Constructor. /// </summary> /// <param name="clusterSize">How many tiles along each wall of a Cluster. Assumes square tiles.</param> /// <param name="tileWidth">The width in pixels of a single Tile in this Level.</param> /// <param name="tileHeight">The height in pixels of a single Tile in this Level.</param> public Cluster(Int32 clusterSize, Int32 tileWidth, Int32 tileHeight) : base() { mClusterSize = clusterSize; mTileDimensions = new Point(tileWidth, tileHeight); mBounds = new Math.Rectangle(tileWidth * clusterSize, tileHeight * clusterSize); mNeighbours = new Cluster[Enum.GetNames(typeof(AdjacentClusterDirections)).Length]; }
/// <summary> /// Since this Graph is organized into clusters, we can severly limit the amount of debug draw needed /// by indexing directly into clusters base on camera position. /// </summary> /// <param name="showLinks"></param> public override void DebugDraw(Boolean showLinks) { //DebugCheckNodes(); Color col = Color.Orange; // How many pixels wide/high is a single cluster? This will be needed to go from // screen size, to cluster index. Int32 pixelsPerClusterX = mClusterSize * mGetMapInfoMsg.mInfo_Out.mTileWidth; Int32 pixelsPerClusterY = mClusterSize * mGetMapInfoMsg.mInfo_Out.mTileHeight; // Based on the current position of the camera, figure out where in the array of clusters they // start to become visible on screen. Also figure out where they stop being visible again. MBHEngine.Math.Rectangle view = CameraManager.pInstance.pViewRect; Int32 startX = (Int32)MathHelper.Max((Int32)view.pLeft / pixelsPerClusterX, 0); Int32 endX = (Int32)MathHelper.Min((Int32)view.pRight / pixelsPerClusterX + 1, mClusters.GetLength(0)); Int32 startY = (Int32)MathHelper.Max((Int32)view.pTop / pixelsPerClusterY, 0); Int32 endY = (Int32)MathHelper.Min((Int32)view.pBottom / pixelsPerClusterY + 1, mClusters.GetLength(1)); for (Int32 y = startY; y < endY; y++) { for (Int32 x = startX; x < endX; x++) { Cluster temp = mClusters[x, y]; for (Int32 i = 0; i < temp.pNodes.Count; i++) { DrawNode(temp.pNodes[i], showLinks); } // Render the cluster boundaries as well. MBHEngine.Math.Rectangle tempRect = temp.pTopLeft.mCollisionRect; //DebugShapeDisplay.pInstance.AddAABB(tempRect, col); DebugShapeDisplay.pInstance.AddAABB(temp.pBounds, col, false); } } }
/// <summary> /// Call this to initialize an GameObject with data supplied in a file. /// </summary> /// <param name="fileName">The file to load from.</param> public virtual void LoadContent(String fileName) { // Give this object a unique id and increment the counter so that the next // object gets a unique id as well. mID = mUniqueIDCounter++; mDirection = new Direction(); mFactoryInfo = new GameObjectFactory.FactoryInfo(); mClassifications = new List <GameObjectDefinition.Classifications>(); mCollisionRectangle = new Math.Rectangle(); mRenderRectangle = new Math.Rectangle(); mTemplateFileName = fileName; if (null != fileName) { GameObjectDefinition def = GameObjectManager.pInstance.pContentManager.Load <GameObjectDefinition>(fileName); mRenderPriority = def.mRenderPriority; mDoUpdate = def.mDoUpdate; mDoRender = def.mDoRender; mPosition = def.mPosition; mRotation = def.mRotation; mScale = def.mScale; mIsStatic = def.mIsStatic; mCollisionRectangle = new Math.Rectangle(def.mCollisionBoxDimensions); mCollisionRectangle.pCenterPoint = mPosition; // Being lazy for now. Just assume that a scaler of collision box is big enough to always show character. mRenderRectangle = new Math.Rectangle(def.mCollisionBoxDimensions * 4f); mRenderRectangle.pCenterPoint = mPosition; mMotionRoot = def.mMotionRoot; if (def.mCollisionRoot == null) { mCollisionRoot = Vector2.Zero; } else { mCollisionRoot = def.mCollisionRoot; } for (Int32 i = 0; def.mClassifications != null && i < def.mClassifications.Count; i++) { mClassifications.Add(def.mClassifications[i]); } mBlendMode = def.mBlendMode; for (Int32 i = 0; i < def.mBehaviourFileNames.Count; i++) { String goRootPath = System.IO.Path.GetDirectoryName(fileName); Behaviour.Behaviour temp = CreateBehaviourByName(def.mBehaviourClassNames[i], goRootPath + "\\Behaviours\\" + def.mBehaviourFileNames[i]); mBehaviours.Add(temp); } } else { mRenderPriority = 50; mDoUpdate = true; mDoRender = true; mBlendMode = GameObjectDefinition.BlendMode.STANDARD; } }
/// <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 = null, Boolean drawDebug = true) { // 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> /// 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)); }
/// <summary> /// Initialize the singleton. Call before first use. /// </summary> /// <param name="device">The initialized graphics device. Used to calculate screen position.</param> public void Initialize(GraphicsDevice device) { mTransform = Matrix.Identity; mTargetPosition = new Vector2(GameObjectManager.pInstance.pGraphics.PreferredBackBufferWidth * 0.5f, GameObjectManager.pInstance.pGraphics.PreferredBackBufferHeight * 0.5f); mLastPosition = new Vector2(); mCurBlendFrames = 0; mCurZoomBlendFrames = 0; mBlendFrames = 10; mZoomBlendFrames = 0; mScreenCenter = Matrix.CreateTranslation(GameObjectManager.pInstance.pGraphics.PreferredBackBufferWidth * 0.5f, GameObjectManager.pInstance.pGraphics.PreferredBackBufferHeight * 0.5f, 0); #if WINDOWS_PHONE mZoomAmount = 4.0f; #elif __ANDROID__ float originalHeight = 144.0f; float originalWidth = 204.8f; float originalScale = 1.0f; // at originalHeight/width the scale should be... float scaleToOrigialY = GameObjectManager.pInstance.pGraphics.PreferredBackBufferHeight / originalHeight; float scaleToOrigialX = GameObjectManager.pInstance.pGraphics.PreferredBackBufferWidth / originalWidth; Console.WriteLine( "Res: " + GameObjectManager.pInstance.pGraphics.PreferredBackBufferWidth.ToString() + "x" + GameObjectManager.pInstance.pGraphics.PreferredBackBufferHeight.ToString()); mZoomAmount = originalScale * scaleToOrigialY; #elif SMALL_WINDOW mZoomAmount = 3.2f; #else mZoomAmount = 6.4f; #endif mTargetZoomAmount = mDefaultZoomAmount = mZoomAmount; mTransform = Matrix.CreateTranslation(-new Vector3(0f, 0f, 0.0f)) * //Matrix.CreateRotationZ(Rotation) * Matrix.CreateScale(new Vector3(mZoomAmount)) * mScreenCenter; mTransformUI = Matrix.CreateScale(new Vector3(mDefaultZoomAmount)); mViewRectangle = new Math.Rectangle(); // Find the center of the screen. Single x = ((GameObjectManager.pInstance.pGraphics.PreferredBackBufferWidth * 0.5f) / pZoomScale); Single y = ((GameObjectManager.pInstance.pGraphics.PreferredBackBufferHeight * 0.5f) / pZoomScale); // Since the screen always has 0,0 at the top left of the screen, we can get the width and height simply // by doubling the center point. Single width = x * 2; Single height = y * 2; // This rectangle should never change (unless we change resolutions). mScreenRectangle = new Math.Rectangle(new Vector2(width, height), new Vector2(x, y)); }