Click here to Skip to main content
15,892,059 members
Articles / Multimedia / DirectX
Article

Endogine sprite engine

Rate me:
Please Sign up or sign in to vote.
4.84/5 (53 votes)
17 Jul 200615 min read 717.1K   22.1K   216   121
Sprite engine for D3D and GDI+ (with several game examples).

Sample Image

Some of the examples are included in the source.

Introduction

Endogine is a sprite and game engine, originally written in Macromedia Director to overcome the many limitations of its default sprite engine. I started working in my spare time on a C# version of the project about a year ago, and now it has far more features than the original - in fact, I've redesigned the architecture many times so it has little in common with the Director++ paradigm I tried to achieve. Moving away from those patterns has made the project much better. However, there are still a lot of things I want to implement before I can even call it a beta.

Some of the features are:

  • Easy media management.
  • Sprite hierarchy (parent/child relations, where children's Rotation, LOC etc. are inherited from the parent).
  • Behaviors.
  • Collision detection.
  • Plugin-based rendering (Direct3D, GDI+, Irrlicht is next).
  • Custom raster operations.
  • Procedural textures (Perlin/Wood/Marble/Plasma/others).
  • Particle systems.
  • Flash, Photoshop, and Director import (not scripts). NB: Only prototype functionality.
  • Mouse events by sprite (enter/leave/up/down etc. events).
  • Widgets (button, frame, window, scrollbar etc.). All are sprite-based, so blending/scaling/rotating works on widget elements as well.
  • Animator object (can animate almost any property of a sprite).
  • Interpolators (for generating smooth animations, color gradients etc.).
  • Sprite texts (each character is a sprite which can be animated, supports custom multicolor bitmap fonts and kerning).
  • Example game prototypes (Puzzle Bobble, Parallax Asteroids, Snooker/Minigolf, Cave Hunter).
  • IDE with scene graph, sprite/behavior editing, resource management, and debugging tools.
  • Simple scripting language, FlowScript, for animation and sound control.
  • Plug-in sound system (currently BASS or DirectSound).
  • New - Color editor toolset: Gradient, Painter-style HSB picker, Color swatches (supports .aco, .act, Painter.txt).
  • New - Classes for RGB, HSB, HSL, HWB, Lab, XYZ color spaces (with plug-in functionality). Picker that handles any 3-dimensional color space.

Sample Image

Some of the current GUI tools (editors, managers etc.).

Background

I had been developing games professionally in Macromedia Director for 10 years, and was very disappointed with the development of the product the last 5 years. To make up for this, I wrote several graphical sub-systems, some very project-specific, but finally I designed one that fulfilled the more generic criteria I had for a 2D game creation graphics API. It was being developed in Director's scripting language Lingo from autumn 2004 to spring 2005, and since then it's a C# project..

It's a prototype

The current engine design is not carved in stone, and I have already made several major changes during its development, and even more are planned.

Optimizations will have to wait until all functionality is implemented. The GDI+ mode is extremely slow, because I haven't ported my dirty rect system yet. The D3D full-screen mode has the best performance.

The code is poorly commented at this stage, as it is still possible I'll rewrite many parts. If there is a demand for documentation, I will create it as questions appear. For now, you can get a feel for how to use it, by investigating the Tests project.

Example projects

There are two solutions in the download. One is the actual engine, including a project called Tests which contains most of the examples and code. I choose to include it in the solution since it's a little bit easier to develop/debug the engine if the test is part of it, but that's not the way your own projects should be set up. The MusicGame project is closer to how it should be done.

There's also a simple tutorial text on how to set up your own project.

I wanted to have a simple, but real-life testbed, so I'm creating a few game prototypes. Currently, they are Puzzle Bobble, a scrolling asteroid game, a golf/snooker game, CaveHunter, and Space Invaders. Other non-game tests are also available in the project. Turn them on and off by bringing the main window into focus and select items from the "Engine tests" menu.

Example walkthrough

Note: For using dialogs / editors, the Endogine.Editors.dll has to be present in the .exe folder. For sound, include Endogine.Audio.Bass.dll and the files in BASS.zip (shareware license).

To try out some of the examples in the Tests project, run the Tests solution and follow these steps:

  • Start in 3D/MDI mode (default).
  • Set focus on the Main window, so the Engine Tests menu appears in the MDI parent.
  • Select Particle System from the menu. The green "dialog" controls a few aspects of the particle system. The top slider is numParticles, the bottom the size. The buttons switch between color and size schemes. After playing around with it, turn it off by selecting Particle System again from the menu.
  • Select GDI+ random procedural, and also Font from the menu. This demonstrates a few ways to create procedural textures and manipulate bitmaps, as well as the support for bitmap fonts. Each letter sprite also has a behavior that makes it swing. Note that both are extremely slow - they're using my old systems. I'll upgrade them soon which will make them a hundred times faster.
  • Go to the SceneGraphViewer, and expand the nodes until you get to the Label sprite. Right-click it and select the Loc/Scale/Rot control. Try the different modes of changing the properties. Notice the mouse wrap-around feature.
  • Close the Loc/Scale/Rot control, go back to the SceneGraphViewer, expand the Label node. Right-click one of the letter sprites and select Properties (the LocY and Rotation properties are under the program control so they are hard to change here).
  • Click the Behaviors button to see which behaviors the sprite has. Mark ThisMovie.BhSwing, and click Remove to make it stop swinging. (Add... and Properties... aren't implemented yet).
  • Stop the program, and start it again, but now deselect MDI mode (because of .NET's keyboard problems in MDI mode).
  • Set focus to the Main window and select Puzzle Bobble from the menu. Player 1 uses the arrow keys to steer (Down to shoot, Up to center), player 2 uses AWSD. Select it again to turn it off. Note: due to changes in the ERectangle(F) classes, there's currently a bug which makes the ball stick in the wrong places. Will look into that later. (May be fixed.)
  • Select Snooker from the menu and click-drag on the white ball to shoot it. Open the Property Inspector for the topology sprite object, change Depth and Function to see how a new image is generated, use the Loc/Scale/Rot control to drag it around, and see how the balls react (buggy, sometimes!).
  • Select Parallax Scroll from the menu and try the Asteroids-like game. Arrow keys to steer, Space to shoot. Note: the Camera control's LOC won't work now, because the camera is managed by the player, centering the camera over the ship for each frame update. That's how the parallax works - just move the camera, and the parallax layers will automatically create the depth effect.
  • (Broken in the Feb '06 version) Go to the main menu, Edit, check Onscreen sprite edit, and right-click on a sprite on the screen, and you'll get a menu with the sprites under the mouse LOC. Select one of the sprites, and it should appear as selected in the SceneGraph. It doesn't update properly now, so you'll have to click the SceneGraph's toolbar to see the selection.

Using the code

Prerequisites: .NET 2.0 and DirectX 9.0c with Managed Extensions (Feb 06) for the demo executable, and DirectX SDK Feb 06 for compiling the source. You can download SharpDevelop 2.0 or Microsoft's Visual Studio Express C# for free, if you need a C# developer IDE. Read the README.txt included in the source for further instructions.

Note that Managed DirectX versions aren't backwards nor forwards compatible. I think later versions will work if you recompile the project, but the demo executable needs MDX Feb 06.

I'm currently redesigning the workflow, and you would need a fairly long list of instructions in order to set up a new solution which uses Endogine, which I haven't written. The easiest way to get started is by looking at the Tests project. You can also have a look at the MusicGame solution, which is more like how a real project would be organized.

Most of the terminology is borrowed from Director. Some examples of sprite creation:

C#
Sprite sp1 = new Sprite();
//Loads the first bitmap file named 
//Ball.* from the default directory
sp1.MemberName = "Ball";
sp1.Loc = new Point(30,30); //moves the sprite

Sprite sp2 = new Sprite();
//If it's an animated gif, 
//the sprite will automatically animate
sp2.MemberName = "GifAnim";
sp2.Animator.StepSize = 0.1; //set the animation speed
sp2.Rect = new RectangleF(50,50,200,200); //stretches and moves the sprite

Sprite sp2Child = new Sprite();
//same texture/bitmap as sp1's will be used 
//- no duplicate memory areas
sp2Child.MemberName = "Ball";
//now, sp2Child will follow sp2's location, 
//rotation, and stretching
sp2Child.Parent = sp2;

Road map

I'll be using the engine for some commercial projects this year. This means I'll concentrate all features that are necessary for those games, and that I probably won't work on polishing the "common" feature set a lot.

There will be a number of updates during the year, but I've revised my 1.0 ETA to late autumn '06. I expect the projects to put an evolutionary pressure on the engine, forcing refactoring and new usage patterns, but resulting in a much better architecture. Another side effect is documentation; I'll have to write at least a few tutorials for the other team members.

Currently, I put most of my spare time into PaintLab, an image editor, which is based on OpenBlackBox, an open source modular signal processing framework. They both use Endogine as their graphics core, and many GUI elements are Endogine editors, so it's common that I need to improve/add stuff in Endogine while working on them.

Goals for next update (unchanged)

I've started using Subversion for source control, which will make it easier to assemble new versions for posting, so updates with new tutorials and bug-fixes should appear more often. Some probable tasks for the next few months:

  • Switch between IDE/MDI mode and "clean" mode in runtime.
  • Clean up terminology, duplicate systems, continue transition to .NET 2.0.
  • Look into supporting XAML, harmonize with its terminology and patterns.
  • Fix IDE GUI, work on some more editors.

History

Update 2006-07-06

Again, other projects have taken most of my time - currently it's the OpenBlackBox/Endogine-based PaintLab paint program. Side effects for Endogine:

  • Color management tools: editors, color space converters, color palette file filters etc.
  • Refactoring: WinForms elements are being moved out of the main project into a separate DLL. Simplifies porting to mono or WPF, and makes the core DLL 50% smaller.
  • Improved Canvas, Vector3, Vector4, and Matrix4 classes.
  • Improved PSD parser.
  • More collision/intersection detection algorithms.
  • Several new or updated user controls.

Update 2006-03-15

I've focused on releasing the first version of OpenBlackBox, so most of the modifications have been made in order to provide interop functionality (OBB highly dependent on Endogine).

  • Optimizations (can be many times faster when lots of vertices are involved).
  • Continued transition to .NET 2.0.
  • Requires DirectX SDK Feb '06.
  • Simple support for HLSL shaders and RenderToTexture.
  • Better proxies for pixel manipulation - Canvas and PixelDataProvider classes instead of the old PixelManipulator.
  • Extended interfaces to the rendering parts of Endogine. Projects (such as OpenBlackBox) can take advantage of the abstracted rendering API without necessarily firing up the whole Endogine sprite system.
  • Forgot to mention it last time, but there's a small tutorial included since the last update.

That's pretty much it. But don't miss OpenBlackBox, in time it will become a very useful component for developing applications with Endogine!

Update 2006-02-01

Some major architectural changes in this version, especially in the textures/bitmap/animation system. The transition isn't complete yet, so several usage patterns can co-exist and cause some confusion. Some utilities will be needed to make the animation system easier to use.

  • Moved to .NET 2.0. Note: refactoring is still in progress (e.g., generics isn't used everywhere yet).
  • The Isometric example has been removed. I've continued to work on it as a separate project, which I'll make available later if possible.
  • Renderers are now plug-ins, allowing for easier development/deployment. (Also started on a Tao- based OpenGL renderer, but lost my temper with its API.)
  • PixelManipulator - easy access to pixels regardless of if the source is a bitmap or texture surface.
  • Examples of how to use the PixelManipulator (adapted Smoke and Cellular Automata3 from processing.org - thanks Mike Davis and Glen Murphy for letting me use them).
  • C# version of Carlos J. Quintero's VB.NET TriStateTreeView - thanks Carlos. Added a TriStateTreeNode class.
  • Started on a plugin-based sound system. Currently, I've implemented two sound sub-systems: BASS and a simple DirectX player. OpenAL can be done, but BASS is cross-platform enough, and it has a better feature set. Later, I'll add DirectShow support.
  • Included a modified version of Leslie Sanford's MidiToolKit (supports multiple playbacks and has a slightly different messaging system). Thanks!
  • Flash parser/renderer has been restructured and improved. Can render basic shapes and animations.
  • System for managing file system and settings for different configurations (a bit like app.config but easier to use and better for my purposes).
  • RegEx-based file finder (extended search mechanism so you can use non-regex counters like [39-80] and [1-130pad:3] - the latter will find the string 001 to 130).
  • Helper for creating packed textures (tree map packing).
  • Abstraction layer for texture usage - the user doesn't have to care if a sprite's image comes from a packed texture or not.
  • The concept of Members is on its way out, replaced by PicRefs. Macromedia Director terminology in general will disappear over time from now on.
  • New, more abstracted animation system. Not fully implemented.
  • .NET scripting system with code injection. Currently only implemented for Boo, but will support other languages. Will be restructured later, with a strategy pattern for the different languages.
  • New vastly improved bitmap font system, both for rendering and creating fonts. Real-time kerning instead of precalculated kerning tables.
  • Localization/translation helper (for multi-language products).
  • A number of helper classes such as the IntervalString class (translates "-1-3,5-7" to and from the array [-1,0,1,2,3,5,6,7]).
  • Unknown number of bug fixes and optimizations.

Update 2005-10-10

Since the last update wasn't that exciting from a gaming POV, I decided to throw in a new version with a prototype isometric game. Totally R.A.D.

  • Isometric rendering, based on David Skoglund's game CrunchTime, including his graphics (see _readme.txt in the Isometric folder). Thanks, pal!
  • A Star pathfinding algorithm adapted from an implementation by John Kenedy. Thank you!
  • Removed references to LiveInterface.
  • Added "resource fork" for bitmap files - an XML file with additional info such as number of animation frames and offset point.

Update 2005-10-04

OK, it's over a month late, and it doesn't include stuff you might have been waiting for, and the things I've been working on - mainly creating two script languages - aren't that useful in their current state (especially with no good examples of their use). I think it was worth putting some time into, as I'm certain FlowScript will become a great tool later on. Here's what I've got for this version:

  • Compiled using the August 2005 SDK.
  • Added Space Invaders prototype.
  • Added curves rendering to .swf import (it still renders strange images).
  • Reorganized the engine into a .dll.
  • Fixed mouse LOC <-> screen LOC error.
  • Fixed transparency / alpha / color-key issues.
  • Map (probably a bad name) class - like a SortedList, but accepts multiple identical "keys".
  • Node class, like XmlNode, but for non-text data.
  • Started preparing the use of 3D matrices for scale/LOC/rotation.
  • Removed DirectDraw support.
  • Basic sound support.
  • A badly sync'ed drum machine example.
  • FlowScript, a time-based scripting language, aimed at non-programmers for animation and sound control, based on:
  • EScript, simple scripting language based on reflection (no bytecode).
  • Simple CheckBox widget.

Update 2005-08-01

  • Compiled using the June 2005 SDK.
  • Sprite.Cursor property (works like Control.Cursor).
  • Simple XML editor.
  • .NET standardized serializing.
  • PropertyGrid instead of custom property editors.
  • VersatileDataGrid User Control (a new DataGrid control which implements functionality missing in .NET's standard DataGrid).
  • TreeGrid User Control - a bit like Explorer, but the right-hand pane is a VersatileDataGrid locked to the treeview.
  • Two new game prototypes: CaveHunter and Snooker/MiniGolf.
  • ResourceManager editor w/ drag 'n' drop to scene (and to SceneGraph viewer).
  • Better structure.
  • Transformer behavior - Photoshop-style sprite overlay for moving/scaling/rotating.
  • Scene XML import/export.
  • Director Xtra for exporting movies to Endogine XML scene format.
  • Import Photoshop documents - each layer becomes a sprite, layer effects become behaviors. Decoding of layer bitmaps incomplete.
  • Import Flash swf files (rendering code incomplete).
  • BinaryReverseReader which reads bytes in reverse order (for .psd), and BinaryFlashReader which reads data with sub-byte precision (for .swf).
  • Extended EPoint(F) and ERectangle(F) classes. Note that Puzzle Bobble doesn't work properly after the latest changes, I'll take care of that later.

Update 2005-07-07

  • Camera node.
  • Parallax layers.
  • LOC/Scale control toolbox.
  • User Controls: ValueEdit (arrow keys to change a Point), JogShuttle (mouse drag to change a Point value by jog or shuttle method).
  • MDI mode crash fixed (thanks to adrian cirstei) - but keys still don't work in MDI mode.
  • Multiple Scene Graph windows.
  • Select sprites directly in scene.
  • Asteroids-type game with parallax layers.
  • Extended EPoint(F) and ERectangle(F) classes.

Update 2005-06-27

  • Optional MDI interface: editors and game window as MDI windows. (Problem: I get an error when trying to create the 3D device in a MDI window.)
  • Scene Graph: treeview of all sprites.
  • Sprite marker: creates a marker around the sprite which is selected in the scene graph.
  • Property Inspector: interface for viewing and editing sprite properties.
  • Sprite Behaviors: easy way to add functionality to sprites. The swinging letters animation is done with a behavior.
  • Behavior Inspector: add/remove/edit behaviors in runtime.
  • Inks use an enumeration instead of an int (ROPs.Multiply instead of 103).
  • Switched from MS' too simplistic Point(F)/Rectangle(F) classes to my own EPoint(F)/ERectangle(F), which have operator overloading and many more methods. Note that I've chosen to write them as classes, not structs - i.e., they're passed as references, not values.
  • Easier keyboard handling (assigns names to keys - makes it easier to let the user define keys, or to have multiple players on one keyboard).
  • Puzzle Bobble allows multiple players in each area.

You can read about my early thoughts about the Endogine concept, future plans, and see some Shockwave/Lingo demos here.

I have added a Endogine C# specific page here, but it probably lags behind this page.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: How to clear screen? Pin
Megalan9-Jul-08 7:29
Megalan9-Jul-08 7:29 
GeneralRe: How to clear screen? Pin
Jonas Beckeman9-Jul-08 12:00
Jonas Beckeman9-Jul-08 12:00 
GeneralRe: How to clear screen? [modified] Pin
Megalan9-Jul-08 23:08
Megalan9-Jul-08 23:08 
GeneralRe: How to clear screen? Pin
Jonas Beckeman10-Jul-08 1:42
Jonas Beckeman10-Jul-08 1:42 
GeneralRe: How to clear screen? [modified] Pin
Megalan10-Jul-08 2:20
Megalan10-Jul-08 2:20 
GeneralDisplaying JPG image bytes on the form using Direct X Pin
sraddhan28-Feb-08 19:32
sraddhan28-Feb-08 19:32 
GeneralRe: Displaying JPG image bytes on the form using Direct X Pin
Jonas Beckeman29-Feb-08 3:40
Jonas Beckeman29-Feb-08 3:40 
GeneralAStar Path Finding Pin
PBirch17-Sep-07 21:55
PBirch17-Sep-07 21:55 
I wanted to send a thanks for your contribution. I used your AStar routine as the basis of a rewrite to encapsulate the implementation a bit more. It's about 90% as far as features (needs some more events defined) but from testing so far functionally it appears pretty clean. I'd love to hear your thoughts.

Below is:
AStarSearcher
AStarNode
AStarSearchResult

and an Init routine for the search nodes

/******************************
AStarSearcher
******************************/

public class AStarSearcher<br />
  {<br />
    private Vector3 _minBoundary;<br />
    private Vector3 _maxBoundary;<br />
<br />
    private bool _paused = false;<br />
    private bool _sorted = false;<br />
<br />
    private List<AStarNode> _nodeList = new List<AStarNode>();<br />
    private List<AStarNode> _tempNodes = new List<AStarNode>();<br />
<br />
    public event EventHandler<AStarNodeEventArgs> OnNodeAdded;<br />
    public event EventHandler<AStarNodeEventArgs> OnNodeRemoved;<br />
<br />
    public AStarSearcher(Vector3 MinBoundary, Vector3 MaxBoundary)<br />
    {<br />
      _minBoundary = MinBoundary;<br />
      _maxBoundary = MaxBoundary;<br />
      InitializeNodes();<br />
    }<br />
<br />
    // Need to be able to add and remove nodes from the nodeList<br />
<br />
    public void InitializeNodes()<br />
    {<br />
      _nodeList = new List<AStarNode>();<br />
      InitializeTempNodes();<br />
    }<br />
<br />
    public void InitializeTempNodes()<br />
    {<br />
      // Detatch from all relations<br />
      for (int i = 0; i < _tempNodes.Count; i++)<br />
      {<br />
        RemoveTempNode(_tempNodes[0]);<br />
      }<br />
      // Don't need to reset to new node list since it's now empty<br />
      // but for good measure...<br />
      _tempNodes = new List<AStarNode>();<br />
    }<br />
<br />
    public void AddNode(Vector3 Position)<br />
    {<br />
      AStarNode node = new AStarNode(this, Position);<br />
      AddNode(node);<br />
    }<br />
<br />
    public void AddNode(AStarNode Node)<br />
    {<br />
      if(!IsInBounds(Node.Position))<br />
      {<br />
        throw new Exception("Node out of bounds");<br />
      }<br />
      _nodeList.Add(Node);<br />
      _sorted = false;<br />
      NodeAdded(Node);<br />
    }<br />
<br />
    public void AddTempNode(AStarNode Node)<br />
    {<br />
      if (!IsInBounds(Node.Position))<br />
      {<br />
        throw new Exception("Temp node out of bounds");<br />
      }<br />
      _tempNodes.Add(Node);<br />
      NodeAdded(Node);<br />
      // Look into setting the temp nodes availability to<br />
      // that of the closest real node<br />
    }<br />
<br />
    public void RemoveNode(AStarNode Node)<br />
    {<br />
      if (!_nodeList.Contains(Node))<br />
      {<br />
        return;<br />
      }<br />
      _nodeList.Remove(Node);<br />
      NodeRemoved(Node);<br />
    }<br />
<br />
    public void RemoveTempNode(AStarNode Node)<br />
    {<br />
      if (!_tempNodes.Contains(Node))<br />
      {<br />
        return;<br />
      }<br />
      _tempNodes.Remove(Node);<br />
      NodeRemoved(Node);<br />
    }<br />
    <br />
    public List<AStarNode> GetSearchNodes()<br />
    {<br />
      // Search scope is the list of nodes and temp nodes together<br />
      List<AStarNode> result = new List<AStarNode>();<br />
      result.AddRange(_nodeList);<br />
      result.AddRange(_tempNodes);<br />
      return result;<br />
    }<br />
<br />
    public void SortNodes()<br />
    {<br />
      if (!_sorted)<br />
      {<br />
        _nodeList.Sort(AStarNode.ByPosition);<br />
        _sorted = true;<br />
      }<br />
    }<br />
<br />
    public void UpdateCost(AStarNode Node, List<AStarNode> Goals)<br />
    {<br />
      // Manhattan might be faster but the results get skewed for<br />
      // diagonal results so no good<br />
      double costToStart = 0;<br />
      if (Node.Parent != null)<br />
      {<br />
        // Put an emphasis on the cost to move so best path is followed<br />
        // With the right node layout this can encourage a better overall path<br />
        // But there may be some pitfalls with irregularly spaced nodes<br />
        double distance = Node.Position.Distance(Node.Parent.Position);<br />
        costToStart = Node.Parent.CostToStart + Node.Parent.Cost + (distance * distance);<br />
      }<br />
      Node.CostToStart = costToStart;<br />
      // Get the average distance of all goals<br />
      // Might be a better way to calc this and/or allow weighting for goal priorities<br />
      Node.CostToGoal = GetAverageDistance(Node, Goals);<br />
      Node.TotalCost = Node.CostToStart + Node.CostToGoal + Node.Cost;<br />
    }<br />
<br />
    public double GetAverageDistance(AStarNode Node, List<AStarNode> Available)<br />
    {<br />
      if (Available.Count == 0)<br />
      {<br />
        return 0;<br />
      }<br />
      double result = 0;<br />
      foreach (AStarNode node in Available)<br />
      {<br />
        result += Node.Position.Distance(node.Position);<br />
      }<br />
      return result / Available.Count;<br />
    }<br />
<br />
    public AStarNode FindClosestNode(AStarNode Node, List<AStarNode> Available)<br />
    {<br />
      if (Available.Count == 0)<br />
      {<br />
        return Node;<br />
      }<br />
      double min = Node.Position.Distance(Available[0].Position);<br />
      int index = 0;<br />
      for (int i = 1; i < Available.Count; i++)<br />
      {<br />
        double d = Node.Position.Distance(Available[i].Position);<br />
        if (d < min)<br />
        {<br />
          min = d;<br />
          index = i;<br />
        }<br />
      }<br />
      return Available[index];<br />
    }<br />
<br />
    /// <summary><br />
    /// Tries to find the best path to the destination or goal location<br />
    /// </summary><br />
    /// <param name="Goal">The end point of the path</param><br />
    /// <returns>The result of the search and, if successful, the path</returns><br />
    public AStarSearchResult FindShortestPath(Vector3 Start, Vector3 Goal)<br />
    {<br />
      List<Vector3> goals = new List<Vector3>();<br />
      goals.Add(Goal);<br />
      return FindShortestPath(Start, goals);<br />
    }<br />
<br />
    /// <summary><br />
    /// Tries to find the shortest path to the nearest destination or goal location<br />
    /// </summary><br />
    /// <param name="Goals">Available end points to choose from</param><br />
    /// <returns>The result of the search and, if successful, the path</returns><br />
    public AStarSearchResult FindShortestPath(Vector3 Start, List<Vector3> Goals)<br />
    {<br />
      // Must have at least one goal to choose from<br />
      if(Goals.Count == 0)<br />
      {<br />
        return new AStarSearchResult();<br />
      }<br />
      // Clear any temp nodes<br />
      InitializeTempNodes();<br />
      // Housekeeping lists<br />
      List<AStarNode> openNodes = new List<AStarNode>();<br />
      List<AStarNode> closedNodes = new List<AStarNode>();<br />
      // Create the path container<br />
      List<Vector3> solution = new List<Vector3>();<br />
      // Get the list of goal nodes to check against for success<br />
      List<AStarNode> goalNodes = ConvertGoalsToNodes(Goals);<br />
      // Get the starting node<br />
      AStarNode startNode = ConvertToStartNode(Start);<br />
      // Add the start node to the open list<br />
      openNodes.Add(startNode);<br />
      // Set startNode as the current to begin with<br />
      AStarNode current = startNode;<br />
<br />
      UpdateCost(startNode, goalNodes);<br />
      // Keep searching until all possibilities are exhausted (no path avail)<br />
      // Could be tweaked to provide best attempt if goal can't be reached<br />
      while (openNodes.Count > 0)<br />
      {<br />
        // Find the cheapest node in the open list<br />
        current = RemoveCheapestNode(openNodes);<br />
        // No nodes found in the open list<br />
        if (current == null)<br />
        {<br />
          return new AStarSearchResult();<br />
        }<br />
        // Have we found a goal node yet?<br />
        if (goalNodes.Contains(current))<br />
        {<br />
          // Walk up the parent chain to build the path<br />
          while (current != null)<br />
          {<br />
            solution.Insert(0, current.Position);<br />
            current = current.Parent;<br />
          }<br />
          AStarSearchResult result = new AStarSearchResult(solution);<br />
          return result;<br />
        }<br />
        // Haven't found it yet<br />
        // Get all related nodes... from current<br />
        List<AStarNode> relatedNodes = current.RelatedNodes;<br />
        foreach (AStarNode relatedNode in relatedNodes)<br />
        {<br />
          // Check if the related node can be traversed<br />
          if (!relatedNode.Available)<br />
          {<br />
            continue;<br />
          }<br />
          // Make sure the node hasn't already been queued<br />
          if (openNodes.Contains(relatedNode) || closedNodes.Contains(relatedNode))<br />
          {<br />
            continue;<br />
          }<br />
          // Create the relationship to the current node<br />
          relatedNode.Parent = current;<br />
          // Update the cost of the related node to reflect the parent cost<br />
          UpdateCost(relatedNode, goalNodes);<br />
          // Add the new node to the list to be searched<br />
          openNodes.Add(relatedNode);<br />
        }<br />
        closedNodes.Add(current);<br />
      }<br />
      // Search failed with no results<br />
      return new AStarSearchResult();<br />
    }<br />
<br />
    public AStarNode GetClosestNode(AStarNode Node)<br />
    {<br />
      SortNodes();<br />
      // this should work if the node list is sorted by position<br />
      foreach (AStarNode node in _nodeList)<br />
      {<br />
        if (Node.Position.X < node.Position.X && Node.Position.Y < node.Position.Y)<br />
        {<br />
          return node;<br />
        }<br />
      }<br />
      // No nodes were smaller in x and y so take the greatest x, y node<br />
      return _nodeList[_nodeList.Count - 1];<br />
    }<br />
<br />
    public void BindToClosestNode(AStarNode Node)<br />
    {<br />
      // Get the closest node from the perm list<br />
      AStarNode closest = GetClosestNode(Node);<br />
      // Add it as a relation<br />
      Node.AddRelatedNode(closest);<br />
      // Bind to the three closest to surround the new node as tight as possible<br />
      // Sort the potential related nodes by distance<br />
      Comparison<AStarNode> byDistance = delegate(AStarNode One, AStarNode Two)<br />
      {<br />
        double distOne = Node.Position.Distance(One.Position);<br />
        double distTwo = Node.Position.Distance(Two.Position);<br />
        return distOne.CompareTo(distTwo);<br />
      };<br />
      closest.RelatedNodes.Sort(byDistance);<br />
      // Borrow it's two closest relationships<br />
      Node.AddRelatedNode(closest.RelatedNodes[0]);<br />
      Node.AddRelatedNode(closest.RelatedNodes[1]);<br />
    }<br />
<br />
    // Can change ConvertToStartNode and ConvertGoalsToNodes to find<br />
    // the closest node instead of exact matches to simulate 'snap-to'<br />
    // like searching<br />
    public AStarNode ConvertToStartNode(Vector3 Start)<br />
    {<br />
      // Look for the start in the current search nodes<br />
      foreach (AStarNode node in GetSearchNodes())<br />
      {<br />
        if (node.Position == Start)<br />
        {<br />
          return node;<br />
        }<br />
      }<br />
      // Create a temp node for the start<br />
      AStarNode startNode = new AStarNode(this, Start);<br />
      BindToClosestNode(startNode);<br />
      // Add it to the temp nodes to put it in the search scope<br />
      AddTempNode(startNode);<br />
      return startNode;<br />
    }<br />
<br />
    public List<AStarNode> ConvertGoalsToNodes(List<Vector3> Goals)<br />
    {<br />
      List<AStarNode> goalNodes = new List<AStarNode>();<br />
      foreach (Vector3 goal in Goals)<br />
      {<br />
        bool found = false;<br />
        // Look at all current search nodes for a match<br />
        foreach (AStarNode node in GetSearchNodes())<br />
        {<br />
          // The goal is already a node<br />
          if (node.Position == goal)<br />
          {<br />
            goalNodes.Add(node);<br />
            found = true;<br />
            break;<br />
          }<br />
        }<br />
        if (!found)<br />
        {<br />
          // Create a temp node for the goal<br />
          AStarNode goalNode = new AStarNode(this, goal);<br />
          BindToClosestNode(goalNode);<br />
          // Add it to the result list<br />
          goalNodes.Add(goalNode);<br />
          // Add it to the temp nodes to allow it to be searched for<br />
          AddTempNode(goalNode);<br />
        }<br />
      }<br />
      return goalNodes;<br />
    }<br />
<br />
    public AStarNode RemoveCheapestNode(List<AStarNode> Nodes)<br />
    {<br />
      if (Nodes.Count == 0)<br />
      {<br />
        return null;<br />
      }<br />
      double min = Nodes[0].TotalCost;<br />
      int index = 0;<br />
      for (int i = 1; i < Nodes.Count; i++)<br />
      {<br />
        if (Nodes[i].TotalCost < min)<br />
        {<br />
          min = Nodes[i].TotalCost;<br />
          index = i;<br />
        }<br />
      }<br />
      AStarNode result = Nodes[index];<br />
      Nodes.RemoveAt(index);<br />
      return result;<br />
    }<br />
<br />
    public bool IsAvailable(AStarNode Node)<br />
    {<br />
      if (!IsInBounds(Node.Position))<br />
      {<br />
        return false;<br />
      }<br />
      if (!Node.Available)<br />
      {<br />
        return false;<br />
      }<br />
      return true;<br />
    }<br />
<br />
    public bool IsInBounds(Vector3 Position)<br />
    {<br />
      return<br />
        Position.X > _minBoundary.X &&<br />
        Position.Y > _minBoundary.Y &&<br />
        Position.X < _maxBoundary.X &&<br />
        Position.Y < _maxBoundary.Y;<br />
    }<br />
<br />
    private void NodeAdded(AStarNode Node)<br />
    {<br />
      if (OnNodeAdded != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(Node);<br />
        OnNodeAdded(this, args);<br />
      }<br />
    }<br />
<br />
    private void NodeRemoved(AStarNode Node)<br />
    {<br />
      if (OnNodeRemoved != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(Node);<br />
        OnNodeRemoved(this, args);<br />
      }<br />
    }<br />
  }


/*****************************************
AStar Node
*****************************************/
public class AStarNode : IComparable<AStarNode><br />
  {<br />
    // Provide a mechanism for an outside party to provide the node's cost<br />
    private AStarSearcher _owner = null;<br />
    private AStarNode _parent = null;<br />
    private List<AStarNode> _relatedNodes = new List<AStarNode>();<br />
    private Vector3 _position;<br />
    private bool _available = true;<br />
    private double _cost = 0;<br />
    private double _totalCost = 0;<br />
    private double _costToStart = 0;<br />
    private double _costToGoal = 0;<br />
<br />
    public event EventHandler<AStarNodeEventArgs> OnParentChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnPositionChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnAvailableChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnCostChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnTotalCostChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnCostToStartChanged;<br />
    public event EventHandler<AStarNodeEventArgs> OnCostToGoalChanged;<br />
    public event EventHandler<AStarRelatedNodesEventArgs> OnRelated;<br />
    public event EventHandler<AStarRelatedNodesEventArgs> OnUnRelated;<br />
<br />
    public AStarNode(AStarSearcher Owner)<br />
    {<br />
      Initialize(Owner, new Vector3(0, 0, 0));<br />
    }<br />
<br />
    public AStarNode(AStarSearcher Owner, Vector3 Position)<br />
    {<br />
      Initialize(Owner, Position);<br />
    }<br />
<br />
    private void Initialize(AStarSearcher Owner, Vector3 Position)<br />
    {<br />
      _owner = Owner;<br />
      _position = Position;<br />
      _owner.OnNodeRemoved += new EventHandler<AStarNodeEventArgs>(_owner_OnNodeRemoved);<br />
    }<br />
<br />
    void _owner_OnNodeRemoved(object sender, AStarNodeEventArgs e)<br />
    {<br />
      if (!_relatedNodes.Contains(e.Node))<br />
      {<br />
        return;<br />
      }<br />
      _relatedNodes.Remove(e.Node);<br />
    }<br />
<br />
    public void Dispose()<br />
    {<br />
      _parent = null;<br />
    }<br />
<br />
    public AStarNode Parent<br />
    {<br />
      get<br />
      {<br />
        return _parent;<br />
      }<br />
      set<br />
      {<br />
        if (_parent == value)<br />
        {<br />
          return;<br />
        }<br />
        _parent = value;<br />
        if (_parent == null)<br />
        {<br />
          CostToStart = 0;<br />
          return;<br />
        }<br />
        ParentChanged();<br />
      }<br />
    }<br />
<br />
    public List<AStarNode> RelatedNodes<br />
    {<br />
      get<br />
      {<br />
        return _relatedNodes;<br />
      }<br />
    }<br />
<br />
    public Vector3 Position<br />
    {<br />
      get<br />
      {<br />
        return _position;<br />
      }<br />
      set<br />
      {<br />
        if(_position == value)<br />
        {<br />
          return;<br />
        }<br />
        _position = value;<br />
        PositionChanged();<br />
      }<br />
    }<br />
<br />
    public bool Available<br />
    {<br />
      get<br />
      {<br />
        return _available;<br />
      }<br />
      set<br />
      {<br />
        if (_available == value)<br />
        {<br />
          return;<br />
        }<br />
        _available = value;<br />
        AvailableChanged();<br />
      }<br />
    }<br />
<br />
    public double Cost<br />
    {<br />
      get<br />
      {<br />
        return _cost;<br />
      }<br />
      set<br />
      {<br />
        if (_cost == value)<br />
        {<br />
          return;<br />
        }<br />
        _cost = value;<br />
        CostChanged();<br />
      }<br />
    }<br />
<br />
    public double TotalCost<br />
    {<br />
      get<br />
      {<br />
        return _totalCost;<br />
      }<br />
      set<br />
      {<br />
        if (_totalCost == value)<br />
        {<br />
          return;<br />
        }<br />
        _totalCost = value;<br />
        TotalCostChanged();<br />
      }<br />
    }<br />
<br />
    public double CostToStart<br />
    {<br />
      get<br />
      {<br />
        return _costToStart;<br />
      }<br />
      set<br />
      {<br />
        if (_costToStart == value)<br />
        {<br />
          return;<br />
        }<br />
        _costToStart = value;<br />
        CostToStartChanged();<br />
      }<br />
    }<br />
<br />
    public double CostToGoal<br />
    {<br />
      get<br />
      {<br />
        return _costToGoal;<br />
      }<br />
      set<br />
      {<br />
        if (_costToGoal == value)<br />
        {<br />
          return;<br />
        }<br />
        _costToGoal = value;<br />
        CostToGoalChanged();<br />
      }<br />
    }<br />
<br />
    public void Reset()<br />
    {<br />
      Cost = 1;<br />
      TotalCost = 0;<br />
      CostToStart = 0;<br />
      CostToGoal = 0;<br />
      Parent = null;<br />
    }<br />
<br />
    private void ParentChanged()<br />
    {<br />
      if (OnParentChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnParentChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void PositionChanged()<br />
    {<br />
      if (OnPositionChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnPositionChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void AvailableChanged()<br />
    {<br />
      if (OnAvailableChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnAvailableChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void CostChanged()<br />
    {<br />
      if (OnCostChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnCostChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void TotalCostChanged()<br />
    {<br />
      if (OnTotalCostChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnTotalCostChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void CostToStartChanged()<br />
    {<br />
      if (OnCostToStartChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnCostToStartChanged(this, args);<br />
      }<br />
    }<br />
<br />
    private void CostToGoalChanged()<br />
    {<br />
      if (OnCostToGoalChanged != null)<br />
      {<br />
        AStarNodeEventArgs args = new AStarNodeEventArgs(this);<br />
        OnCostToGoalChanged(this, args);<br />
      }<br />
    }<br />
    private void Related(AStarNode Other)<br />
    {<br />
      if (OnRelated != null)<br />
      {<br />
        AStarRelatedNodesEventArgs args = new AStarRelatedNodesEventArgs(true, this, Other);<br />
        OnRelated(this, args);<br />
      }<br />
    }<br />
<br />
    private void UnRelated(AStarNode Other)<br />
    {<br />
      if (OnUnRelated != null)<br />
      {<br />
        AStarRelatedNodesEventArgs args = new AStarRelatedNodesEventArgs(false, this, Other);<br />
        OnUnRelated(this, args);<br />
      }<br />
    }<br />
<br />
    /// <summary><br />
    /// Add this node to the target node and create the opposite relation as well<br />
    /// </summary><br />
    /// <param name="RelatedNode">The other node to create the relation to</param><br />
    public void AddRelatedNode(AStarNode RelatedNode)<br />
    {<br />
      if (RelatedNode == this)<br />
      {<br />
        return;<br />
      }<br />
      if (_relatedNodes.Contains(RelatedNode))<br />
      {<br />
        return;<br />
      }<br />
      _relatedNodes.Add(RelatedNode);<br />
      // Could avoid possible repeat calls on add but related node list should<br />
      // be short enough that it would only be a marginal savings<br />
      RelatedNode.AddRelatedNode(this);<br />
      Related(RelatedNode);<br />
    }<br />
<br />
    public void RemoveRelatedNode(AStarNode RelatedNode)<br />
    {<br />
      if (!_relatedNodes.Contains(RelatedNode))<br />
      {<br />
        return;<br />
      }<br />
      _relatedNodes.Remove(RelatedNode);<br />
      RelatedNode.RemoveRelatedNode(RelatedNode);<br />
      UnRelated(RelatedNode);<br />
    }<br />
<br />
    public static Comparison<AStarNode> ByTotalCost<br />
    {<br />
      get<br />
      {<br />
        return delegate(AStarNode one, AStarNode two)<br />
        {<br />
          return one.TotalCost.CompareTo(two.TotalCost);<br />
        };<br />
      }<br />
    }<br />
<br />
    public static Comparison<AStarNode> ByPosition<br />
    {<br />
      get<br />
      {<br />
        return delegate(AStarNode one, AStarNode two)<br />
        {<br />
          // sort primarily by x<br />
          int x = one.Position.X.CompareTo(two.Position.X);<br />
          if (x != 0)<br />
          {<br />
            return x;<br />
          }<br />
          // if x is the same then sort by y<br />
          return one.Position.Y.CompareTo(two.Position.Y);<br />
<br />
        };<br />
      }<br />
    }<br />
<br />
    #region IComparable<AStarNode> Members<br />
<br />
    public int CompareTo(AStarNode other)<br />
    {<br />
      return this.Position.CompareTo(other.Position);<br />
    }<br />
<br />
    #endregion<br />
  }


/***********************************
AStar Search Results
***********************************/
public class AStarSearchResult<br />
  {<br />
    private List<Vector3> _solution;<br />
    private bool _foundSolution;<br />
<br />
    public AStarSearchResult()<br />
    {<br />
      _foundSolution = false;<br />
      _solution = new List<Vector3>();<br />
    }<br />
<br />
    public AStarSearchResult(List<Vector3> Solution)<br />
    {<br />
      if (Solution.Count == 0)<br />
      {<br />
        _foundSolution = false;<br />
        return;<br />
      }<br />
      _foundSolution = true;<br />
      _solution = Solution;<br />
    }<br />
<br />
    public bool FoundSolution<br />
    {<br />
      get<br />
      {<br />
        return _foundSolution;<br />
      }<br />
    }<br />
<br />
    public List<Vector3> Solution<br />
    {<br />
      get<br />
      {<br />
        return _solution;<br />
      }<br />
    }<br />
  }


/***************************************
AStart Provider initialization routine
This routine is for square tiles but the nature of the
searcher is such that is can support other board
types
Also, if the state of the nodes can be linked to the
state of the board via events or an observer etc
you should only need to init once so subsequent searches
benefit from the one time cost to build
***************************************/
public void InitAStarSearcher(AStarSearcher PathProvider, GameBoard GameBoard)<br />
    {<br />
      PathProvider.InitializeNodes();<br />
      AStarNode[,] primaryNodes = new AStarNode[GameBoard.TilesAcross, GameBoard.TilesDown];<br />
      AStarNode[,] secondaryNodes = new AStarNode[GameBoard.TilesAcross - 1, GameBoard.TilesDown - 1];<br />
      // Add all the tile nodes<br />
      for (int x = 0; x < GameBoard.TilesAcross; x++)<br />
      {<br />
        for (int y = 0; y < GameBoard.TilesDown; y++)<br />
        {<br />
          // Get top left position for the tile <br />
          // offset by half for the center points<br />
          double left = GameBoard.Tiles[x, y].Left + (_width / 2);<br />
          double top = GameBoard.Tiles[x, y].Top + (_height / 2);<br />
          AStarNode primaryNode = new AStarNode(PathProvider, new Vector3(left, top, 0));<br />
          // Need to link the tile to the node somehow (observer)<br />
          // Node is available if the tile is available<br />
          primaryNode.Available = GameBoard.Tiles[x, y].Terrain.LandPassable;<br />
          primaryNodes[x, y] = primaryNode;<br />
          PathProvider.AddNode(primaryNode);<br />
          // Get top left starting from second row & col<br />
          // This give the cross point for each tile<br />
          if (x > 0 && y > 0)<br />
          {<br />
            AStarNode secondaryNode = new AStarNode(PathProvider, new Vector3(GameBoard.Tiles[x, y].Left, GameBoard.Tiles[x, y].Top, 0));<br />
            // Node is available if all surrounding nodes are available<br />
            // This avoids the issue in a square map where corners have to<br />
            // be cut to make optimal paths but cannot pass through the corner<br />
            // of an impassable tile<br />
            secondaryNode.Available =<br />
              GameBoard.Tiles[x, y].Terrain.LandPassable &&<br />
              GameBoard.Tiles[x - 1, y].Terrain.LandPassable &&<br />
              GameBoard.Tiles[x - 1, y - 1].Terrain.LandPassable &&<br />
              GameBoard.Tiles[x, y - 1].Terrain.LandPassable;<br />
            secondaryNodes[x - 1, y - 1] = secondaryNode;<br />
            PathProvider.AddNode(secondaryNode);<br />
<br />
            // Build related node lists<br />
            // lines with -> means that the bi-directionality of related nodes makes this redundant<br />
            // For the current primary node add the nodes above and to the left<br />
            primaryNodes[x, y].AddRelatedNode(primaryNodes[x, y - 1]);<br />
            primaryNodes[x, y].AddRelatedNode(primaryNodes[x - 1, y]);<br />
            // Also add the corner, secondary node to it's upper left<br />
            // -> primaryNodes[x, y].AddRelatedNode(secondaryNode[x - 1, y - 1]);<br />
            // For node up and left add nodes to it's right and it's bottom<br />
            primaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y]);<br />
            primaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y - 1]);<br />
            // Also add the corner, secondary node to it's lower right<br />
            // -> primaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNode[x - 1, y - 1]);<br />
            // For node above add the node to it's left and the current node<br />
            primaryNodes[x, y - 1].AddRelatedNode(primaryNodes[x - 1, y - 1]);<br />
            // -> primaryNodes[x, y - 1].AddRelatedNode(primaryNodes[x, y]);<br />
            // Also add the corner, secondary node to it's lower left<br />
            // -> primaryNodes[x, y - 1].AddRelatedNode(secondaryNode[x - 1, y - 1]);<br />
            // For node to the left add the node above it and the current node<br />
            primaryNodes[x - 1, y].AddRelatedNode(primaryNodes[x - 1, y - 1]);<br />
            // -> primaryNodes[x - 1, y].AddRelatedNode(primaryNodes[x, y]);<br />
            // Also add the corner, secondary node to it's upper right<br />
            // -> primaryNodes[x - 1, y].AddRelatedNode(secondaryNode[x - 1, y - 1]);<br />
            // For the secondary node add all four corner nodes<br />
            secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y]);<br />
            secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y]);<br />
            secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y - 1]);<br />
            secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y - 1]);<br />
<br />
            if(x > 1)<br />
            {<br />
              // For the secondary node add the secondary node to it's left<br />
              secondaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNodes[x - 2, y - 1]);<br />
            }<br />
            if(y > 1)<br />
            {<br />
              // For the secondary node add the secondary node above it<br />
              secondaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNodes[x - 1, y - 2]);<br />
            }<br />
          }<br />
        }<br />
      }<br />
    }


/******************************************
Example of search request
******************************************/
Point start = new Point(50, 50);<br />
        Point goal1 = new Point(405, 400);<br />
        Point goal2 = new Point(300, 400);<br />
        List<Vector3> goals = new List<Vector3>();<br />
        goals.Add(new Vector3(goal1.X, goal1.Y, 0));<br />
        goals.Add(new Vector3(goal2.X, goal2.Y, 0));<br />
<br />
        AStarSearchResult result = _pathProvider.FindShortestPath(<br />
          new Vector3(start.X, start.Y, 0),<br />
          goals);

QuestionWhere is the project at? Pin
Charles Parcell29-Aug-07 7:03
Charles Parcell29-Aug-07 7:03 
AnswerRe: Where is the project at? Pin
Jonas Beckeman2-Sep-07 0:01
Jonas Beckeman2-Sep-07 0:01 
QuestionWhat about compressing textures? Pin
Menorbriam9-Jul-07 1:35
Menorbriam9-Jul-07 1:35 
AnswerRe: What about compressing textures? Pin
Jonas Beckeman1-Sep-07 23:37
Jonas Beckeman1-Sep-07 23:37 
GeneralGreat work Pin
Sean Yixiang Lu26-Mar-07 19:28
Sean Yixiang Lu26-Mar-07 19:28 
GeneralNo, it ain't dead, it just looks it Pin
Jonas Beckeman13-Dec-06 9:01
Jonas Beckeman13-Dec-06 9:01 
GeneralRe: No, it ain't dead, it just looks it Pin
Scotty_G1-Feb-07 16:44
Scotty_G1-Feb-07 16:44 
GeneralLoaderLock problem at runtime Pin
Tinolayco19-Nov-06 5:29
Tinolayco19-Nov-06 5:29 
GeneralRe: LoaderLock problem at runtime Pin
Jonas Beckeman19-Nov-06 22:15
Jonas Beckeman19-Nov-06 22:15 
Generalhello world Pin
tartaros17-Sep-06 5:44
tartaros17-Sep-06 5:44 
GeneralRe: hello world Pin
tartaros17-Sep-06 6:25
tartaros17-Sep-06 6:25 
GeneralRe: hello world Pin
Jonas Beckeman27-Sep-06 8:53
Jonas Beckeman27-Sep-06 8:53 
GeneralMusic Game compile error [modified] Pin
Po5i3-Aug-06 19:03
Po5i3-Aug-06 19:03 
GeneralRe: Music Game compile error Pin
Jonas Beckeman (2)3-Aug-06 22:05
Jonas Beckeman (2)3-Aug-06 22:05 
GeneralRe: Music Game compile error Pin
Po5i4-Aug-06 1:43
Po5i4-Aug-06 1:43 
GeneralRe: Music Game compile error Pin
Jonas Beckeman4-Aug-06 3:08
Jonas Beckeman4-Aug-06 3:08 
GeneralRe: Music Game compile error Pin
Po5i4-Aug-06 6:48
Po5i4-Aug-06 6:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.