|

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.

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: Sprite sp1 = new Sprite();
sp1.MemberName = "Ball";
sp1.Loc = new Point(30,30);
Sprite sp2 = new Sprite();
sp2.MemberName = "GifAnim";
sp2.Animator.StepSize = 0.1;
sp2.Rect = new RectangleF(50,50,200,200);
Sprite sp2Child = new Sprite();
sp2Child.MemberName = "Ball";
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.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 105 (Total in Forum: 105) (Refresh) | FirstPrevNext |
|
|
 |
|
|
hi, i followed your tutorial01.txt built a project,but when i debug it .it throw a problem:
Exception has been thrown by the target of an invocation.
the location is program.cs --> endogine.Init(main, null, null);
this place.
i use vs.net2005. whats the problem?
from vevi
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
If you have references to all the Sprite-derived instances, just Dispose() them, otherwise you can iterate through the root sprite's (normally Stage.DefaultParent) children and Dispose() those, and they will dispose all their children recursively.
If you're going to use the engine seriously, I can provide a link to the latest version. There have been massive additions (and some breaking changes) to it since the last update here, but I haven't had time to rewrite the article or create tutorials for it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
It's available now at http://flowlabs.net/Download/Endogine080709.rar
I've had to remove some resources and projects because of copyright issues (eg BASS libraries), so you might get a few error messages when opening the solution, but you should be able to just ignore them.
Unfortunately, I won't have time to give any support ATM, so I'm afraid you're on your own - don't throw away your old projects just yet 
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
thanks 
ps yeah, there some errors with the vectronica and vectronicaapp (not found) and tao, bass, irrlicht... which versions of irrlicht and tao i should use?
modified on Thursday, July 10, 2008 6:28 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The OpenGL renderer is experimental, you should exclude that projects from the solution. The Irrlicht renderer, which provides DX/OGL/Software modes, actually works pretty OK, but I had to add some features to the Irrlicht engine so it's not one of the standard dlls (and there's still work to do on it). You can download my Irrlicht dlls here: http://flowlabs.net/Download/Irrlicht.rar
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
thanks for the help it's hard to find engine like this (usualy they all commercial)
modified on Thursday, July 10, 2008 8:27 AM
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Jonas,
I am developing a project, where in I am suppose to display jpeg images from MJPEG stream recieved from IP camera.
Current Status: Out of that continuous JPEG stream JPEG images are extracted successfully. Right now the JPEG images are being displayed on the picture box. e.g. pictureBox1.Image = image.
Problem: But CPU utilization is quite high. [for 5+ mega pixel image CPU utilization is 65%]
Hopeful way: Using Direct X, I think CPU utilization can be reduced upto 5%.
Question: Is there any way I can use Direct X which takes jpeg bytes and then renders them on the control.
Any help would be appreciated.
Thanks, Sraddhan
PS: Anybody reading this thread is also welcomed to answer.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, using DirectX to show the decoded images will probably help a little bit, but you won't get nowhere near 5%, as the decoding process is probably what takes most of the CPU cycles. It might be possible to use some of the later GPUs with video decoding support to decrease CPU workload, but I haven't studied the issue myself so I can't really say. HTH, Jonas
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
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 { private Vector3 _minBoundary; private Vector3 _maxBoundary;
private bool _paused = false; private bool _sorted = false;
private List _nodeList = new List(); private List _tempNodes = new List();
public event EventHandler OnNodeAdded; public event EventHandler OnNodeRemoved;
public AStarSearcher(Vector3 MinBoundary, Vector3 MaxBoundary) { _minBoundary = MinBoundary; _maxBoundary = MaxBoundary; InitializeNodes(); }
// Need to be able to add and remove nodes from the nodeList
public void InitializeNodes() { _nodeList = new List(); InitializeTempNodes(); }
public void InitializeTempNodes() { // Detatch from all relations for (int i = 0; i < _tempNodes.Count; i++) { RemoveTempNode(_tempNodes[0]); } // Don't need to reset to new node list since it's now empty // but for good measure... _tempNodes = new List(); }
public void AddNode(Vector3 Position) { AStarNode node = new AStarNode(this, Position); AddNode(node); }
public void AddNode(AStarNode Node) { if(!IsInBounds(Node.Position)) { throw new Exception("Node out of bounds"); } _nodeList.Add(Node); _sorted = false; NodeAdded(Node); }
public void AddTempNode(AStarNode Node) { if (!IsInBounds(Node.Position)) { throw new Exception("Temp node out of bounds"); } _tempNodes.Add(Node); NodeAdded(Node); // Look into setting the temp nodes availability to // that of the closest real node }
public void RemoveNode(AStarNode Node) { if (!_nodeList.Contains(Node)) { return; } _nodeList.Remove(Node); NodeRemoved(Node); }
public void RemoveTempNode(AStarNode Node) { if (!_tempNodes.Contains(Node)) { return; } _tempNodes.Remove(Node); NodeRemoved(Node); } public List GetSearchNodes() { // Search scope is the list of nodes and temp nodes together List result = new List(); result.AddRange(_nodeList); result.AddRange(_tempNodes); return result; }
public void SortNodes() { if (!_sorted) { _nodeList.Sort(AStarNode.ByPosition); _sorted = true; } }
public void UpdateCost(AStarNode Node, List Goals) { // Manhattan might be faster but the results get skewed for // diagonal results so no good double costToStart = 0; if (Node.Parent != null) { // Put an emphasis on the cost to move so best path is followed // With the right node layout this can encourage a better overall path // But there may be some pitfalls with irregularly spaced nodes double distance = Node.Position.Distance(Node.Parent.Position); costToStart = Node.Parent.CostToStart + Node.Parent.Cost + (distance * distance); } Node.CostToStart = costToStart; // Get the average distance of all goals // Might be a better way to calc this and/or allow weighting for goal priorities Node.CostToGoal = GetAverageDistance(Node, Goals); Node.TotalCost = Node.CostToStart + Node.CostToGoal + Node.Cost; }
public double GetAverageDistance(AStarNode Node, List Available) { if (Available.Count == 0) { return 0; } double result = 0; foreach (AStarNode node in Available) { result += Node.Position.Distance(node.Position); } return result / Available.Count; }
public AStarNode FindClosestNode(AStarNode Node, List Available) { if (Available.Count == 0) { return Node; } double min = Node.Position.Distance(Available[0].Position); int index = 0; for (int i = 1; i < Available.Count; i++) { double d = Node.Position.Distance(Available[i].Position); if (d < min) { min = d; index = i; } } return Available[index]; }
/// /// Tries to find the best path to the destination or goal location /// /// The end point of the path /// The result of the search and, if successful, the path public AStarSearchResult FindShortestPath(Vector3 Start, Vector3 Goal) { List goals = new List(); goals.Add(Goal); return FindShortestPath(Start, goals); }
/// /// Tries to find the shortest path to the nearest destination or goal location /// /// Available end points to choose from /// The result of the search and, if successful, the path public AStarSearchResult FindShortestPath(Vector3 Start, List Goals) { // Must have at least one goal to choose from if(Goals.Count == 0) { return new AStarSearchResult(); } // Clear any temp nodes InitializeTempNodes(); // Housekeeping lists List openNodes = new List(); List closedNodes = new List(); // Create the path container List solution = new List(); // Get the list of goal nodes to check against for success List goalNodes = ConvertGoalsToNodes(Goals); // Get the starting node AStarNode startNode = ConvertToStartNode(Start); // Add the start node to the open list openNodes.Add(startNode); // Set startNode as the current to begin with AStarNode current = startNode;
UpdateCost(startNode, goalNodes); // Keep searching until all possibilities are exhausted (no path avail) // Could be tweaked to provide best attempt if goal can't be reached while (openNodes.Count > 0) { // Find the cheapest node in the open list current = RemoveCheapestNode(openNodes); // No nodes found in the open list if (current == null) { return new AStarSearchResult(); } // Have we found a goal node yet? if (goalNodes.Contains(current)) { // Walk up the parent chain to build the path while (current != null) { solution.Insert(0, current.Position); current = current.Parent; } AStarSearchResult result = new AStarSearchResult(solution); return result; } // Haven't found it yet // Get all related nodes... from current List relatedNodes = current.RelatedNodes; foreach (AStarNode relatedNode in relatedNodes) { // Check if the related node can be traversed if (!relatedNode.Available) { continue; } // Make sure the node hasn't already been queued if (openNodes.Contains(relatedNode) || closedNodes.Contains(relatedNode)) { continue; } // Create the relationship to the current node relatedNode.Parent = current; // Update the cost of the related node to reflect the parent cost UpdateCost(relatedNode, goalNodes); // Add the new node to the list to be searched openNodes.Add(relatedNode); } closedNodes.Add(current); } // Search failed with no results return new AStarSearchResult(); }
public AStarNode GetClosestNode(AStarNode Node) { SortNodes(); // this should work if the node list is sorted by position foreach (AStarNode node in _nodeList) { if (Node.Position.X < node.Position.X && Node.Position.Y < node.Position.Y) { return node; } } // No nodes were smaller in x and y so take the greatest x, y node return _nodeList[_nodeList.Count - 1]; }
public void BindToClosestNode(AStarNode Node) { // Get the closest node from the perm list AStarNode closest = GetClosestNode(Node); // Add it as a relation Node.AddRelatedNode(closest); // Bind to the three closest to surround the new node as tight as possible // Sort the potential related nodes by distance Comparison byDistance = delegate(AStarNode One, AStarNode Two) { double distOne = Node.Position.Distance(One.Position); double distTwo = Node.Position.Distance(Two.Position); return distOne.CompareTo(distTwo); }; closest.RelatedNodes.Sort(byDistance); // Borrow it's two closest relationships Node.AddRelatedNode(closest.RelatedNodes[0]); Node.AddRelatedNode(closest.RelatedNodes[1]); }
// Can change ConvertToStartNode and ConvertGoalsToNodes to find // the closest node instead of exact matches to simulate 'snap-to' // like searching public AStarNode ConvertToStartNode(Vector3 Start) { // Look for the start in the current search nodes foreach (AStarNode node in GetSearchNodes()) { if (node.Position == Start) { return node; } } // Create a temp node for the start AStarNode startNode = new AStarNode(this, Start); BindToClosestNode(startNode); // Add it to the temp nodes to put it in the search scope AddTempNode(startNode); return startNode; }
public List ConvertGoalsToNodes(List Goals) { List goalNodes = new List(); foreach (Vector3 goal in Goals) { bool found = false; // Look at all current search nodes for a match foreach (AStarNode node in GetSearchNodes()) { // The goal is already a node if (node.Position == goal) { goalNodes.Add(node); found = true; break; } } if (!found) { // Create a temp node for the goal AStarNode goalNode = new AStarNode(this, goal); BindToClosestNode(goalNode); // Add it to the result list goalNodes.Add(goalNode); // Add it to the temp nodes to allow it to be searched for AddTempNode(goalNode); } } return goalNodes; }
public AStarNode RemoveCheapestNode(List Nodes) { if (Nodes.Count == 0) { return null; } double min = Nodes[0].TotalCost; int index = 0; for (int i = 1; i < Nodes.Count; i++) { if (Nodes[i].TotalCost < min) { min = Nodes[i].TotalCost; index = i; } } AStarNode result = Nodes[index]; Nodes.RemoveAt(index); return result; }
public bool IsAvailable(AStarNode Node) { if (!IsInBounds(Node.Position)) { return false; } if (!Node.Available) { return false; } return true; }
public bool IsInBounds(Vector3 Position) { return Position.X > _minBoundary.X && Position.Y > _minBoundary.Y && Position.X < _maxBoundary.X && Position.Y < _maxBoundary.Y; }
private void NodeAdded(AStarNode Node) { if (OnNodeAdded != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(Node); OnNodeAdded(this, args); } }
private void NodeRemoved(AStarNode Node) { if (OnNodeRemoved != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(Node); OnNodeRemoved(this, args); } } }
/***************************************** AStar Node *****************************************/
public class AStarNode : IComparable { // Provide a mechanism for an outside party to provide the node's cost private AStarSearcher _owner = null; private AStarNode _parent = null; private List _relatedNodes = new List(); private Vector3 _position; private bool _available = true; private double _cost = 0; private double _totalCost = 0; private double _costToStart = 0; private double _costToGoal = 0;
public event EventHandler OnParentChanged; public event EventHandler OnPositionChanged; public event EventHandler OnAvailableChanged; public event EventHandler OnCostChanged; public event EventHandler OnTotalCostChanged; public event EventHandler OnCostToStartChanged; public event EventHandler OnCostToGoalChanged; public event EventHandler OnRelated; public event EventHandler OnUnRelated;
public AStarNode(AStarSearcher Owner) { Initialize(Owner, new Vector3(0, 0, 0)); }
public AStarNode(AStarSearcher Owner, Vector3 Position) { Initialize(Owner, Position); }
private void Initialize(AStarSearcher Owner, Vector3 Position) { _owner = Owner; _position = Position; _owner.OnNodeRemoved += new EventHandler(_owner_OnNodeRemoved); }
void _owner_OnNodeRemoved(object sender, AStarNodeEventArgs e) { if (!_relatedNodes.Contains(e.Node)) { return; } _relatedNodes.Remove(e.Node); }
public void Dispose() { _parent = null; }
public AStarNode Parent { get { return _parent; } set { if (_parent == value) { return; } _parent = value; if (_parent == null) { CostToStart = 0; return; } ParentChanged(); } }
public List RelatedNodes { get { return _relatedNodes; } }
public Vector3 Position { get { return _position; } set { if(_position == value) { return; } _position = value; PositionChanged(); } }
public bool Available { get { return _available; } set { if (_available == value) { return; } _available = value; AvailableChanged(); } }
public double Cost { get { return _cost; } set { if (_cost == value) { return; } _cost = value; CostChanged(); } }
public double TotalCost { get { return _totalCost; } set { if (_totalCost == value) { return; } _totalCost = value; TotalCostChanged(); } }
public double CostToStart { get { return _costToStart; } set { if (_costToStart == value) { return; } _costToStart = value; CostToStartChanged(); } }
public double CostToGoal { get { return _costToGoal; } set { if (_costToGoal == value) { return; } _costToGoal = value; CostToGoalChanged(); } }
public void Reset() { Cost = 1; TotalCost = 0; CostToStart = 0; CostToGoal = 0; Parent = null; }
private void ParentChanged() { if (OnParentChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnParentChanged(this, args); } }
private void PositionChanged() { if (OnPositionChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnPositionChanged(this, args); } }
private void AvailableChanged() { if (OnAvailableChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnAvailableChanged(this, args); } }
private void CostChanged() { if (OnCostChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnCostChanged(this, args); } }
private void TotalCostChanged() { if (OnTotalCostChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnTotalCostChanged(this, args); } }
private void CostToStartChanged() { if (OnCostToStartChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnCostToStartChanged(this, args); } }
private void CostToGoalChanged() { if (OnCostToGoalChanged != null) { AStarNodeEventArgs args = new AStarNodeEventArgs(this); OnCostToGoalChanged(this, args); } } private void Related(AStarNode Other) { if (OnRelated != null) { AStarRelatedNodesEventArgs args = new AStarRelatedNodesEventArgs(true, this, Other); OnRelated(this, args); } }
private void UnRelated(AStarNode Other) { if (OnUnRelated != null) { AStarRelatedNodesEventArgs args = new AStarRelatedNodesEventArgs(false, this, Other); OnUnRelated(this, args); } }
/// /// Add this node to the target node and create the opposite relation as well /// /// The other node to create the relation to public void AddRelatedNode(AStarNode RelatedNode) { if (RelatedNode == this) { return; } if (_relatedNodes.Contains(RelatedNode)) { return; } _relatedNodes.Add(RelatedNode); // Could avoid possible repeat calls on add but related node list should // be short enough that it would only be a marginal savings RelatedNode.AddRelatedNode(this); Related(RelatedNode); }
public void RemoveRelatedNode(AStarNode RelatedNode) { if (!_relatedNodes.Contains(RelatedNode)) { return; } _relatedNodes.Remove(RelatedNode); RelatedNode.RemoveRelatedNode(RelatedNode); UnRelated(RelatedNode); }
public static Comparison ByTotalCost { get { return delegate(AStarNode one, AStarNode two) { return one.TotalCost.CompareTo(two.TotalCost); }; } }
public static Comparison ByPosition { get { return delegate(AStarNode one, AStarNode two) { // sort primarily by x int x = one.Position.X.CompareTo(two.Position.X); if (x != 0) { return x; } // if x is the same then sort by y return one.Position.Y.CompareTo(two.Position.Y);
}; } }
#region IComparable Members
public int CompareTo(AStarNode other) { return this.Position.CompareTo(other.Position); }
#endregion }
/*********************************** AStar Search Results ***********************************/
public class AStarSearchResult { private List _solution; private bool _foundSolution;
public AStarSearchResult() { _foundSolution = false; _solution = new List(); }
public AStarSearchResult(List Solution) { if (Solution.Count == 0) { _foundSolution = false; return; } _foundSolution = true; _solution = Solution; }
public bool FoundSolution { get { return _foundSolution; } }
public List Solution { get { return _solution; } } }
/*************************************** 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) { PathProvider.InitializeNodes(); AStarNode[,] primaryNodes = new AStarNode[GameBoard.TilesAcross, GameBoard.TilesDown]; AStarNode[,] secondaryNodes = new AStarNode[GameBoard.TilesAcross - 1, GameBoard.TilesDown - 1]; // Add all the tile nodes for (int x = 0; x < GameBoard.TilesAcross; x++) { for (int y = 0; y < GameBoard.TilesDown; y++) { // Get top left position for the tile // offset by half for the center points double left = GameBoard.Tiles[x, y].Left + (_width / 2); double top = GameBoard.Tiles[x, y].Top + (_height / 2); AStarNode primaryNode = new AStarNode(PathProvider, new Vector3(left, top, 0)); // Need to link the tile to the node somehow (observer) // Node is available if the tile is available primaryNode.Available = GameBoard.Tiles[x, y].Terrain.LandPassable; primaryNodes[x, y] = primaryNode; PathProvider.AddNode(primaryNode); // Get top left starting from second row & col // This give the cross point for each tile if (x > 0 && y > 0) { AStarNode secondaryNode = new AStarNode(PathProvider, new Vector3(GameBoard.Tiles[x, y].Left, GameBoard.Tiles[x, y].Top, 0)); // Node is available if all surrounding nodes are available // This avoids the issue in a square map where corners have to // be cut to make optimal paths but cannot pass through the corner // of an impassable tile secondaryNode.Available = GameBoard.Tiles[x, y].Terrain.LandPassable && GameBoard.Tiles[x - 1, y].Terrain.LandPassable && GameBoard.Tiles[x - 1, y - 1].Terrain.LandPassable && GameBoard.Tiles[x, y - 1].Terrain.LandPassable; secondaryNodes[x - 1, y - 1] = secondaryNode; PathProvider.AddNode(secondaryNode);
// Build related node lists // lines with -> means that the bi-directionality of related nodes makes this redundant // For the current primary node add the nodes above and to the left primaryNodes[x, y].AddRelatedNode(primaryNodes[x, y - 1]); primaryNodes[x, y].AddRelatedNode(primaryNodes[x - 1, y]); // Also add the corner, secondary node to it's upper left // -> primaryNodes[x, y].AddRelatedNode(secondaryNode[x - 1, y - 1]); // For node up and left add nodes to it's right and it's bottom primaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y]); primaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y - 1]); // Also add the corner, secondary node to it's lower right // -> primaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNode[x - 1, y - 1]); // For node above add the node to it's left and the current node primaryNodes[x, y - 1].AddRelatedNode(primaryNodes[x - 1, y - 1]); // -> primaryNodes[x, y - 1].AddRelatedNode(primaryNodes[x, y]); // Also add the corner, secondary node to it's lower left // -> primaryNodes[x, y - 1].AddRelatedNode(secondaryNode[x - 1, y - 1]); // For node to the left add the node above it and the current node primaryNodes[x - 1, y].AddRelatedNode(primaryNodes[x - 1, y - 1]); // -> primaryNodes[x - 1, y].AddRelatedNode(primaryNodes[x, y]); // Also add the corner, secondary node to it's upper right // -> primaryNodes[x - 1, y].AddRelatedNode(secondaryNode[x - 1, y - 1]); // For the secondary node add all four corner nodes secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y]); secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y]); secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x - 1, y - 1]); secondaryNodes[x - 1, y - 1].AddRelatedNode(primaryNodes[x, y - 1]);
if(x > 1) { // For the secondary node add the secondary node to it's left secondaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNodes[x - 2, y - 1]); } if(y > 1) { // For the secondary node add the secondary node above it secondaryNodes[x - 1, y - 1].AddRelatedNode(secondaryNodes[x - 1, y - 2]); } } } } }
/****************************************** Example of search request ******************************************/ Point start = new Point(50, 50); Point goal1 = new Point(405, 400); Point goal2 = new Point(300, 400); List goals = new List(); goals.Add(new Vector3(goal1.X, goal1.Y, 0)); goals.Add(new Vector3(goal2.X, goal2.Y, 0));
AStarSearchResult result = _pathProvider.FindShortestPath( new Vector3(start.X, start.Y, 0), goals);
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Been a while since I stopped in here to check on things. I see you had expected to have something out early '07 and here it is late '07. Still keeping the project alive here?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
One of my goals for the next release here was to have implemented some major architectural changes, such as getting rid of the Castlib/Member notions. However, since we're using it as the multimedia engine in 5 different commercial products, if I want to rewrite those portions, I'll either have to branch Endogine or adjust all those products for the new architecture. A difficult decision.
But it *has* come a very long way since the last CodeProject release, and a new version makes sense even with those flaws. Now, I just need to find the time to revise and rewrite the old demo project code...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Great work! I have been testing for a while.. Because of the intensive use of textures it would be fine that you use the compression textures feature of Directx if it is possible.
Also a bit more of documentation would be great  | | | | | |