CABAListic wrote:It's always amazing what a single person can accomplish. Great work!

Thanks! Just to clarify that, I've contracted out most of the artwork, music, and voice acting. But everything else (code, design, content, audio) is a one man (or should I say one very burnt out man) show. But even that's not true, without the help of Ogre, CEGUI, Ogitor, ParticleUniverse, PagedGeometry, Gorilla, and a few other libs, my game would look more like Atari asteroids. I think you need to be at least mid-thirties to get that joke.

JoJoBo wrote:How do you manage enemy movement and obstacle avoidance ? for example with structures or trees ?Do you use some special library or terrain map navigation or something else ?

I went old-school with my pathing. I use a path-node based approach. I'll describe this in a little more detail. But before I go on, path-node pathing is generally considered inferior to the more modern nav-mesh pathing, such as that implemented by recast detour. However, A* path-node pathing worked pretty well for me, and I was more comfortable working with this, given my limited time to devote to it.

So, first I construct my path map:1. Every quad on the landscape is assigned a path node. 2. I determine if a path node is walkable by checking the slope of the terrain at that point, and also check to see if anything (like a rock) is obstructing that path node.3. I solve paths using micropather, which is just an A* path solver, but it's very fast.

But A* path-node pathing is really just the start. It will tell you how to get from point A to point B, but it probably won't look good, as your AI characters zig-zag from path node to path node. So there are a ton of optimizations over the basic path-node chasing.Examples:

if the AI character passes a "line of sight" check to its target (using a physics raycast query). then I just run directly towards it. (It's actually a little more complicated than this, but that's the general idea).

i detect if the AI character gets stuck (say on another AI character) by checking if the AI is trying to move, but remains in the same general position for too long. If stuck, I go into a separate "get unstuck" pathing routine.

the AI troops will do other things than just chase after their target. For example, AIs are aware of cover positions, which they may try to stand behind and use to their advantage, as a player would.

you don't always want to path to your exact target's position, but sometimes somewhere nearby to shoot at it.

Code-wise, I arrange all my basic pathing functions in a single system called the PathSystem. Then, I have AI tactic code which calls into this path system, depending on what it's trying to do.My AI tactics have names like:

And just for anyone with way too much time on their hands, here's my PathSystem.h API, so you can see generally how I structure things. My game is closed source, but I don't mind talking about how I did things. No anti-singleton rants, please.

// The PathSystem is the sister system of the FlightSystem.// - The PathSystem is used for traditional pathing on a terrain.// - The FlightSystem is used for 3D maneuvering in space.// - See IndoorClassSystem.h for pathing inside a space station or other indoor structure.//// Pathing consists of 2 primary techniques://// 1. "Straight Chase" Pathing//// - When there is a direct line of walkable terrain between the AI and its // target, we will walk that direct line.//// - This is the preferred technique, because it's cheaper, and looks better.//// - If the target goes temporarily out of "straight chase" visibility, we// don't immediately give up the straight chase. Instead, we remember where // our last good "straight chase" target position, and move towards// that instead. This is a very good optimization - it often allows the AI// to catch up with the target and re-achieve "straight chase" visibility.//// 2. A* Pathing// // - we use some middleware called "MicroPather" to implement our A* solver.//// - Good things about MicroPather:// * simple: one include file, one cpp file, no external libs/dlls// * flexible: it allows you to implement your own graph data structure.// This worked out well with how I used terrain heightmap data to form a // graph out of walkable surfaces.// * focused: written and optimized specifically for real time path planning// in games. // * I also looked at the boost graph lib. It was a highly templated generic// solution for all sorts of graph problems. It would have been much more// tricky to integrate, harder to debug, and possibly slower due to the// way it was written to support many different graph problems.//// The PathSystem works very closely with:// - class PathData: Pathing data which gets cached on an AI (e.g. the path nodes leading to a target.)// - class PathMap: The graph of walkable terrain, owned by class Landscape.// See discussion in PathData.h and PathMap.h//class PathSystem : public micropather::Graph, public XML{ // // System Singleton //public: PathSystem(); ~PathSystem();

// // Loading and Saving Game State //public: // Loads data from a savegame file. // Savegame files are XML, and we are passed an XMLNode pointing to the data for this system. // This function is called either when we begin a new game, or the user loads a saved game. void LoadGameState( XMLNode& i_node ); // Writes data for a savegame file. // Savegame files are in XML format, so we return an XMLNode containing all game state data for this system. // This function is called whenever the player saves the game. XMLNode SaveGameState( const char* i_szNodeName ) const; // Reinitializes all data back to the initial state of the system, to make sure that no data "leaks through" // (persists) from a previous game to the new game. // This should only ever be called by the LoadSaveSystem when we're loading a game. void ClearGameState();

// // ChaseTarget Helper Functions //private: // The position we should path towards isn't always the exact position of our target. // Examples: // - If the target is in an unwalkable position, we should pick a nearby walkable position // - if the target is huge, we should pick a point somewhere on its periphery. Vector3 GetChasePosition( Person* i_pChaser, const Mobile* i_pTarget ); // For small objects, this just returns i_pTarget's position. // For some large objects, such as buildings, this will return a point on i_pTarget's periphery. // // The reason for this is because when pathing towards a large target, we will not be able to path // to it's center, because that point will be blocked by the object's physics. So instead we // need to find a clear point around the edge of the target to path to. Vector3 GetLargeObjectPeripheryPoint( Person* i_pChaser, const Mobile* i_pTarget ); // Helper for GetNearestPosition bool InqWalkablePeripheryPoint( Person* i_pChaser, const Mobile* i_pTarget, float i_fRadius, Vector3& o_posNearestPeriphery ); Vector3 GetNearestUnobstructedWalkablePosition( const Vector3& i_pos );

// // Obstructions //public: // If i_pMobile has been set as an obstacle (Mobile::m_bPathMapObstacle), then // it will be added to the PathMap. void RegisterObstacle( Mobile* i_pMobile ); // This must be called when an obstacle is destroyed or removed from the scene, otherwise // AIs will keep pathing around the no longer existing obstacle. void UnregisterObstacle( Mobile* i_pMobile );

// Same as above, except for decor objects. // Currently, we are not planning on making decor objects destructable, so there is no // corresponding unregister call. void RegisterObstacle( Landscape* i_pLandscape, Decor* i_pDecor );

// General code shared between all the RegisterObstacle public API functions. // - actually we now call this directly from outside the PathSystem for our PagedGeometry entities. void RegisterObstacle( Landscape* i_pLandscape, NxActor* i_pActor, MID i_mid );

// // Inherited Graph Functions, from micropather.h //public: // Note: *** THIS IS NOT MEANT AS A PUBLIC API *** // // These are all internal helper functions that you don't need to worry about as // a user of this system. But they must be public because of callbacks made from micropather // into this system.

// PERF: Consider inlining a bunch of these pathing functions?

// From micropather.h: // Return the least possible cost between 2 states. For example, if your pathfinding // is based on distance, this is simply the straight distance between 2 points on the // map. If you pathfinding is based on minimum time, it is the minimal travel time // between 2 points given the best possible terrain. float LeastCostEstimate( void* i_pPathNodeStart, void* i_pPathNodeEnd );

// From micropather.h: // Return the exact cost from the given state to all its neighboring states. This // may be called multiple times, or cached by the solver. It *must* return the same // exact values for every call to MicroPather::Solve(). It should generally be a simple, // fast function with no callbacks into the pather. void AdjacentCost( void* i_pPathNode, std::vector< micropather::StateCost >* i_pAdjacent );

// From micropather.h: // This function is only used in DEBUG mode - it dumps output to stdout. Since void* // aren't really human readable, normally you print out some concise info (like "(1,2)") // without an ending newline. void PrintStateInfo( void* i_pPathNode );

// To convert from a position to a micropather "state" (i.e. PathNode). // See the micropather docs for more info on states. A state is basically just a unique representation of a position. void* PositionToPathNode( const Vector3& i_pos ) const;

// To convert from a micropather "state" to a position. // See the micropather docs for more info on states. A state is basically just a unique representation of a position. // // Note: This only returns a 2D position because the state is a 2D x,y coordinate on the heightmap. // We could return a 3D position if needed. To achieve this, we'd want to cache the absolute heights of all the // path nodes within Landscape::m_vecPathMap Vector2 PathNodeToPosition( const void* i_pPathNode ) const;

private: // We need to record who the chaser is before we call micropather::Solve. // This is because the graph functions called by micropather::Solve (such as AdjacentCost) // need to take into account the GetMaxWalkableHeightDistance of the chaser. But, since // the graph APIs are defined by micropather, we can't pass in the chaser. // So, we'll just record it in a system variable, then query it from the graph functions. void SetCurrentChaser( const Person* i_pChaser ); const Person* GetCurrentChaser() const; const Person* m_pCurrentChaser;

// // Micropather solver //public: // Not meant as a public API, but leaving this function public, so the ConsoleSystem can access it for testing. // // parameter: o_path is an array of pathnode indexes, outlining the path from start to finish. // Unfortunately, you will have to cast the void* vector values to integer pathnode indexes. // This is a rough edge with the micropather API. // See Landscape.h for more info on pathnode indexes. PathResultType SolvePath( const Person* i_pChaser, const Vector3& i_posStart, const Vector3& i_posEnd, std::vector< void* >& o_vecPathNode, float& o_fTotalCost );

// If we change the PathMap in any way (i.e. costs between path nodes change), // we need to reset the cached data stored by m_pPathSolver. // Example: when we add obstacles to the path map. // See the MicroPather docs for more info on resetting the cache. void ResetPathCache();

private: // This gets set by SetActiveLandscape. // It will be deleted and re-newed as different landscapes are activated. // // TODO: It's not going to work to have only one solver, with multiple // creatures of different PathData:;GetMaxWalkableHeightDifference(). The cache // will get all messed up. // This is yet another reason to revisit this cache, and potentially disable it completely. // Or maybe maintain multiple path solvers for creatures of different height ranges. micropather::MicroPather* m_pPathSolver;

private: // Cache a pointer to the current Landscape Scene. // This prevents us having to constantly do a dynamic_cast<Landscape*> operation inside the performance critical // A* pathing functions. Landscape* m_pActiveLandscape;

@Jabberwocky : Thanks for the overall description I'm not skilled programmer, but i'll perhaps incorporate some ways and ideas you use in your game.

Some last question for target cover, how do you determine if the monster is covering well ? Do you use the bouding box ?Caus if you use only the physic ray intersection going from player weapon laser or shotgun to ennemy body center,the ennemy will cover but you will be able t ohit the arms for example that won't be covered ?

Or is ray collision enough when in game finally ?

Crysis game uses some sort of covert points that are placed on models or in the scene editor, they are just 3D positions around a 3D model like 4 positions for example, and the AI will use them to cover.that's predifined cover positions, but it works pretty well, specially when playing the game.

Hey Jabberwocky, can you recommend me/us some resources on gameplay programming, and the basic concepts of gameplay development, since I have basically not idea how to put the gameplay mechanics (fighting, world interaction, etc...) together, at all...

This means that any of these 5 meshes are possible things an NPC can find cover behind.If each mesh was placed on the landscape 20 times, that would make 5 * 20 = 100 potential cover objects.

2. Pick one of these cover objects

If an NPC wants to go into cover, I query what cover objects are nearby, and pick one.

3. Assess the threat direction

I look at where the enemies are relative to the cover object.Example, let'say the cover object is at position (100,10,100) and is 2 meters wide.And there are three nearby enemies at positions (200,10,100), (250,10,50), (150,10,150)Then:

Possible improvements:- rank each nearby cover position to see which one provides the best cover from all nearby enemies based on the calculated cover_position, and choose that one.- only consider enemies who are actively shooting at you.

Sorry I didn't get back to you on this sooner. It looks like you already got some good feedback in this thread. I browsed through some of the content on amazon, and they look pretty good.

dudeabot wrote:how are you doing the transitions from world to the another?

Yep, zones. Seamless transitions are very cool, but are a lot of hard work. A person could easily spend years working on this stuff, and I wanted to make a game instead of new technology. Not that there's anything wrong with creating new tech, it just wasn't my goal.

Thanks a lot , i'm inspired now, to try making some similar system !I think precise cover is needed on "ultra realistic" FPS games where we don't want to be able to kill ennemies throught somecover having holes and see the ennemy not reacting caus the AI is limited ; but for your game style it's perfect (as this is not ultrarealistic FPS game) .For the Zones , i'm also in this state of thinking , i want more to make a game than waste years on technology without delivering any game.

I 've seen old versions of your game and the progress in 3D assets is really big, they are now very polished, but the old versionwas not that bad and showed already gameplay.Have you taken a 3D artist to make the 3D assets like interior rooms and stuff ? or it's you that have refined the models and texwtures ? Caus the progression is amazing between new and old version.

Agreed. This was written very specifically for the kind of game I am creating.

JoJoBo wrote:Have you taken a 3D artist to make the 3D assets like interior rooms and stuff ? (...) Cause the progression is amazing between new and old version.

Yes, most of what you see now is contracted artwork. Although I have gotten very good at modeling rocks myself.

When I started the project, I bought a few basic textures and models. I also learned the basics of 3ds max, and built a few things myself.

This gave me enough art to implement things like character control, animations, combat, physics, shaders, and my content pipeline for building planets and stations. So when I started buying art later on, I knew exactly what I needed. And I was also able to integrate artwork immediately, and detect if there were any problems. Plus, the artists got feedback for seeing their stuff in-game more quickly.

JoJoBo wrote:I think you will be successfull with your game

Thanks! I hope so.I'm a little worried about being finished at approximately the same time as Star Wars: The Old Republic. You know, just one of the most anticipated Science Fiction games of all time. But since Salvation Prophecy is single player, and SW:TOR is an MMO, maybe we appeal to different crowds.

I'm a little worried about being finished at approximately the same time as Star Wars: The Old Republic

Well you can delay your game about 2 or 3 months after the game this mmo is arriving ? perhaps you could polish some things or add new extra content ?Anyway if your price is not 60 Euros and more indie price this is not a problem, and your game have is own universe and design, it's not somethingwe all know, it's original and polished, no need to pay each month to play, so you don't need to worry.What will matter is the popularity the game will encounter and some advertising

Personally, I loathe everything "MMO-ish", and I am (probably) not alone.Well, maybe that's an exaggeration, but I read books and play games and really appreciate that experience - single-player engrossment (if there is such a word).

Thanks, I hope you're right. But I agree, there's definitely a lot of folks who won't go anywhere near an MMO.It'll cost significantly less than a regular full priced game, although I haven't settled on an exact price yet.

I'm really impressed. Your detailed descriptions are really interesting, thanks for sharing them with us.Hats off for the massive amount of work you did on your own, the result is something to behold.How long is this project in development?

I see what you mean, and have a feeling that a lot of people on these forums are in the same boat in some way. Even though the time I put into my project is still far less (about two years on and off in my spare time), these things really take their time.Sometimes I think you need to be a little nuts to keep going at it, or be really devoted.

Nice job on the Gamasutra article, Jabberwocky, it should help anyone who decides to make their first game a space simulation.

Maybe this is not a thing you'd want to share, so I might sound a bit nosy or rude, but I'm going to ask it anyway.

Can you please share which libraries you used in combination with OGRE, and if possible post the list of the source and header file names (stupid question, but it's going to help anyone like me who's lost at how to structure a game source code) ?