Jump to content

Chris Paulson

Members
  • Posts

    134
  • Joined

  • Last visited

Everything posted by Chris Paulson

  1. I've done this before, one thing to look out for is the animation may rotate but you model isn't. This means at the end of the rotation anim you have to rotate the model the correct amount to match the animation otherwise the model will "snap" back the rotation.
  2. Thanks for the comments, you anim system sounds better than mine. I'd be interested in how you do this and how this works out, I get a feeling it something that needs to be tackled if you don't want "floating" NPCs. Check out:- http://udn.epicgames.com/Three/UsingSkeletalControllers.html
  3. +1 one for this. It would be nice if they had switchable viewmode (wireframe etc) as well. Big ask... if you did CSG in the editor too you'd be closing the gap on UDK .
  4. When your NPC moves it may move at different speeds, especially if you are using some sort of steering. This presents the problem of what speed should you play your walk/run animations. My solution is to analyse the animation and see how far the foot travels from start frame to end frame and when animating play the correct amount of frame(s) for the distance. Here’s the code:- #include "anim.h" void animItem::calcAnimDist( TEntity e, TEntity bone ) { TVec3 startPos; TVec3 newPos; TVec3 prevPos; TVec3 rot; float totalZ = 0; float totalX= 0; if (!bone) return; rot = EntityRotation(e,1); RotateEntity(e,Vec3(0),1); startPos = EntityPosition(bone,1); for(int i=startFrame; i<endFrame; i++) { prevPos = EntityPosition(bone,1); Animate( e, i ); newPos = EntityPosition(bone,1); totalZ += abs(newPos.Z - prevPos.Z); totalX += abs(newPos.X - prevPos.X); } RotateEntity(e,rot,1); distanceZ = totalZ*2; distanceX = totalX*2; perFrameZ = totalZ / frameCount; perFrameX = totalX / frameCount; maxSpeed = perFrameZ / 1.8; minSpeed = perFrameZ / 3.6; } Here’s it’s header file, it has loads more stuff in that I will touch on in later blog entries. #ifndef ANIM_H #define ANIM_H #include <string> #include <map> #include <vector> #include <algorithm> #include "engine.h" #include "actor/include/animbody.h" using namespace std; typedef enum animDirection { ANIM_OTHER }; class keyFrame { int parentFrame; int childFrame; }; typedef enum animType { ANIM_NONE = 0, ANIM_IDLE = 1, ANIM_WALK = 2, ANIM_RUN = 4, ANIM_CROUCH = 8, ANIM_TURN = 16, ANIM_STRAFE = 32, ANIM_RIFLE = 64, ANIM_PISTOL = 128, ANIM_DEATH = 256, ANIM_FORWARD = 512, ANIM_BACKWARD = 1024, ANIM_PAIN = 2048, ANIM_LEFT = 4096, ANIM_RIGHT = 8192, ANIM_JUMP = 16384 }; typedef enum animStatus { ANIM_DEFAULT, ANIM_STARTING, ANIM_PLAYING, ANIM_STOPPING, ANIM_STOPPED }; class animItem { protected: float distanceZ; float distanceX; float rotationAngle; map<std::string,keyFrame> keyFrames; public: animType animType; std::string name; int sequence; int startFrame; int endFrame; int frameCount; float perFrameX; float perFrameZ; bool speedDepdent; float maxSpeed; float minSpeed; animDirection direction; animStatus m_status; animItem() {}; animItem( string pName, int pStart, int pEnd, animDirection pDirection = ANIM_OTHER ); ~animItem() {}; inline int getFrameCount() { return frameCount; } //void calcAnimDist( TEntity e, string boneName ); void calcAnimDist( TEntity e, TEntity bone ); }; typedef enum animPlayType { PLAY_REPEAT, PLAY_ONCE, PLAY_ONCE_RESET }; class playingAnim { protected: public: animItem* myAnimItem; float currentFrame; // Zero based float speed; animStatus status; animPlayType animType; TEntity entity; float transitionTime; float startTime; float stopTime; int blendTime; playingAnim() {}; playingAnim( animItem* pAnim, float pTransitionTime = 0, float pSpeed = 0, animPlayType pAnimType = PLAY_REPEAT): currentFrame(0), blendTime(0), stopTime(0), startTime(0) { myAnimItem = pAnim; speed = pSpeed; animType = pAnimType; transitionTime = pTransitionTime; status = ANIM_STARTING; } inline void setStartTime( float pTime ) { startTime = pTime; } inline void setStopTime( float pTime ) { stopTime = pTime; } inline void setEntity( TEntity ent ) { entity = ent; } inline void setCurrentFrame( float pFrame ) { currentFrame = pFrame; } inline float getStopTime() { return stopTime; } inline float getStartTime() { return startTime; } inline float getTransitionTime() { return transitionTime; } inline animStatus getStatus() { return status; } inline animPlayType getAnimType() { return animType; } }; class entityAnimation { protected: map<string,animItem*> items; map<string,playingAnim*> animQueue; playingAnim* activeItem; TEntity entity; public: entityAnimation( TEntity pEnt ); //~entityAnimation(); animItem* addItem( string pName, int pStart, int pEnd ); playingAnim* start( string pName, float pTranTime = 0, float animSpeed = 1, animPlayType pAnimType = PLAY_REPEAT, string entityName = ""); void stop( playingAnim* anim, float transition); void stopAll( float time); void update( float dist = 0); bool isPlayingAnimName( string pName ); bool animationActive( string pName); bool animationStopped( string pName ); inline float startBlend( playingAnim* play ); inline float stopBlend( playingAnim* play ); }; class locomotion: public entityAnimation { public: animType currentAnimType; bool crouching; bool holdingPistol; bool holdingRifle; bool movingForward; bodyParts* m_bodyParts; bodyControl* m_bodyControl; float swapTime; void update( float dist = 0); animItem* locomotion::findItemForSpeed( float dist ); animItem* locomotion::findItemForType( animType pAnimType ); bool readAnimFile( string pFile ); void setCrouching( bool pCrouch = true ); void setHoldingPistol( bool pPistol = true ); void setHoldingRifle( bool pRifle = true ); void setMovingForward( bool pForward = true ); locomotion( TEntity e); void render( TCamera cam); }; #endif
  5. For NPC AI to work they have to have simulated vision. This is done by using raycast/linepick. My vision system does the following:- Takes into account head position - using head bone Takes into account rotation of NPC spine bone (used for aiming) Build list of enemies in view range Takes into account FOV It looks through each of the ememies bones to see if any part of the enemy is visible. It returns location of the first bone found. This is to make sure enemies are still seen even if they are partially obscured. It has a render function to help with debugging the AI. Here's the code: - #ifndef VISION_H #define VISION_H #include "gamelib/include/gamelib.h" #include "gamelib/include/scene.h" #include "actor/include/animBody.h" typedef enum SeenState { SEEN_IDLE, SEEN_ALERT, SEEN_ACTIVE }; typedef enum visionMode { VISION_BONE, VISION_BOUNDING, VISION_NAVMESH }; // // Implement actor vision // class Vision { protected: TScene *m_scene; bodyControl *m_body; float m_lastCheckTime; float m_lastSeenTime; float m_notSeenTime; float m_lockTime; float m_updateRate; float m_idleTime; bool m_seen; float m_viewRange; float m_viewAngle; int m_cnt; public: SeenState m_state; map<float,TEntity> m_nearbyEnemy; TEntity m_entity, m_lastTarget, m_pivot, m_head, m_target; string m_team; TPick m_pick; TVec3 m_lastSeenPos, m_targetPos; Vision(TScene *s, TEntity e, TEntity head, bodyControl *pbody); bool canSeeEnemy(visionMode mode= VISION_BONE); bool canSeeEntity(TEntity target, visionMode mode = VISION_BONE); bool canSeeBoundBox(TVec3 pos, TEntity target); bool canSeeBone( TVec3 pos, TEntity target); TVec3 getAimAtLocation(); bool navMeshCanSeeEntity(TEntity target); inline TEntity getLastTarget() { return m_lastTarget; } inline SeenState Vision::getSeenState() { return m_state; } float distanceToEnemy(); void Render(TCamera cam); }; #endif #include "actor/include/vision.h" #include "gamelib/include/gamemath.h" //#define DEBUG_VISION Vision::Vision( TScene *s, TEntity e, TEntity h, bodyControl *pBody ) : m_lastCheckTime(0), m_updateRate(100), m_lastTarget(NULL), m_seen(false), m_viewRange(50), m_viewAngle(180), m_cnt(0), m_lockTime(5000), m_idleTime(60000) { m_entity = e; m_scene = s; m_cnt = 0; m_lastSeenTime = 0; m_seen = false; m_state = SEEN_IDLE; m_pivot = CreatePivot(); EntityParent( m_pivot, m_entity ); m_body = pBody; m_head = h; m_notSeenTime = AppTime(); m_team = GetEntityKey(e,"team"); } bool Vision::navMeshCanSeeEntity(TEntity target) { static const int MAX_POLYS = 256; dtStatPolyRef m_startRef; dtStatPolyRef m_endRef; dtStatPolyRef m_polys[MAX_POLYS]; dtStatPolyRef m_parent[MAX_POLYS]; float m_spos[3], m_epos[3]; int m_npolys; float m_polyPickExt[3]; float wallDist, navhit; float elapse = AppTime() - m_lastCheckTime; if((target == m_lastTarget) && (elapse < m_updateRate)) return m_seen; m_targetPos = EntityPosition( target, 1); m_lastCheckTime = AppTime(); m_lastTarget = target; m_cnt += 1; m_seen = false; float dist = EntityDistance(m_entity, target); // check If the target is within viewrange if (dist<=m_viewRange) { // observer vector PositionEntity( m_pivot, EntityPosition( m_head,1),1); TVec3 tform = TFormVector( Vec3(0,0,1), m_body->pivot, NULL); AlignToVector(m_pivot, tform, 3, 1); TVec3 observerPos = EntityPosition( m_entity, 1); // pick vector float angle = angleToDest( m_pivot, m_targetPos ); if(angle <= (m_viewAngle/2.0)) { // check If something is blocking the view m_spos[0] = observerPos.X*-1; m_spos[1] = observerPos.Y; m_spos[2] = observerPos.Z; m_epos[0] = m_targetPos.X*-1; m_epos[1] = m_targetPos.Y; m_epos[2] = m_targetPos.Z; m_polyPickExt[0] = 2; m_polyPickExt[1] = 4; m_polyPickExt[2] = 2; m_startRef = m_scene->navmesh->m_navMesh->findNearestPoly(m_spos, m_polyPickExt); m_npolys = m_scene->navmesh->m_navMesh->raycast(m_startRef, m_spos, m_epos, navhit, m_polys, MAX_POLYS); if (navhit >=1) { m_seen = true; m_lastTarget = target; m_lastSeenPos = m_targetPos; m_lastSeenTime = AppTime(); m_state = SEEN_ACTIVE; } } } // observer cannot see target if (!m_seen) { if ((AppTime() - m_lastSeenTime) < this->m_lockTime) m_lastSeenPos = EntityPosition( target, 1); m_lastTarget = NULL; if ((AppTime() - m_lastSeenTime) > m_idleTime) m_state = SEEN_IDLE; else m_state = SEEN_ACTIVE; } else if (m_seen) // Cheat if it's with locked on time { m_lastTarget = target; m_lastSeenPos = EntityPosition( target, 1); } return m_seen; } TEntity pickSource, pickDest; int _stdcall filter( TEntity entity ) { if ( isParent(pickSource, entity) ) return 0; return 1; } bool Vision::canSeeBoundBox( TVec3 pos, TEntity target) { TVec6 box; float margin = 0.3; box = GetEntityAABB( target ); box.X0 += margin; box.Y0 += margin; box.Z0 += margin; box.X1 -= margin; box.Y1 -= margin; box.Z1 -= margin; if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y0, box.Z0), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y1, box.Z0), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y0, box.Z0), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y1, box.Z0), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y0, box.Z1), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y1, box.Z1), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y0, box.Z1), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y1, box.Z1), 0.1,0, (BP)filter )) return true; if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true; return false; } bool Vision::canSeeBone( TVec3 pos, TEntity target) { int i; for(i = 1; i <= CountChildren(target); i++) { TEntity e = GetChild(target, i); string name=GetEntityKey(e,"name"); if (LinePick( &m_pick, pos, EntityPosition(e,1), 0,0, (BP)filter )) if (isParent(m_target, m_pick.entity )) return true; if (canSeeBone(pos, e )) return true; } return false; } int _stdcall checkIfEnemy( TEntity e, byte* extra ) { Vision *vis = (Vision*)extra; if (GetEntityType(e) != 3) return 1; // Not a NPC/Player string team = GetEntityKey(e,"team"); if (team == vis->m_team) return 1; // On same team vis->m_nearbyEnemy[ EntityDistance(vis->m_entity,e) ] = e; return 1; } bool Vision::canSeeEnemy(visionMode mode) { if (m_lastTarget) { if (GetEntityType(m_lastTarget) == 3) { if ((AppTime() - m_lastSeenTime) < this->m_lockTime) return canSeeEntity(m_lastTarget, mode ); } else { m_lastTarget = NULL; m_state = SEEN_IDLE; } } m_nearbyEnemy.clear(); TVec6 box; TVec3 pos; pos = EntityPosition( m_entity, 1); box.X0 = pos.X - m_viewRange; box.Y0 = pos.Y - m_viewRange; box.Z0 = pos.Z - m_viewRange; box.X1 = pos.X + m_viewRange; box.Y1 = pos.Y + m_viewRange; box.Z1 = pos.Z + m_viewRange; ForEachEntityInAABBDo( box, (BP)checkIfEnemy, (byte*)this, ENTITY_MODEL ); map<float,TEntity>::iterator enemies; enemies=m_nearbyEnemy.begin(); while(enemies!=m_nearbyEnemy.end()) { if (canSeeEntity(enemies->second, mode)) return true; enemies++; } return false; } bool Vision::canSeeEntity(TEntity target, visionMode mode) { BOOL seen; float elapse = AppTime() - m_lastCheckTime; if((target == m_lastTarget) && (elapse < m_updateRate)) return m_seen; m_targetPos = EntityPosition( target, 1); m_lastCheckTime = AppTime(); m_lastTarget = target; m_cnt += 1; seen = false; float dist = EntityDistance(m_entity, target); // check If the target is within viewrange if (dist<=m_viewRange) { // observer vector TVec3 pos = EntityPosition( m_head,1); PositionEntity( m_pivot, pos,1); TVec3 tform = TFormVector( Vec3(0,0,1), m_body->pivot, NULL); AlignToVector(m_pivot, tform, 3, 1); TVec3 observerPos = EntityPosition( m_entity, 1); // pick vector float angle = angleToDest( m_pivot, m_targetPos ); if(angle <= (m_viewAngle/2.0)) { m_target = target; pickSource = m_entity; if (mode == VISION_BONE) seen = canSeeBone(pos, GetChild(GetChild(target,1),1)); else if (mode == VISION_BOUNDING) seen = canSeeBoundBox(pos, GetChild(target,1)); else // todo seen = navMeshCanSeeEntity(GetChild(target,1)); if (seen) { m_lastTarget = target; m_lastSeenPos = m_targetPos; m_lastSeenTime = AppTime(); m_state = SEEN_ACTIVE; } } } // observer cannot see target if (!seen) { m_notSeenTime = AppTime(); if ((AppTime() - m_lastSeenTime) < this->m_lockTime) m_lastSeenPos = EntityPosition( target, 1); if ((AppTime() - m_lastSeenTime) > m_idleTime) m_state = SEEN_IDLE; else m_state = SEEN_ACTIVE; if ((AppTime() - m_lastSeenTime) < 0) // 0.25 sec delay time - to stop twitchy ai m_seen = true; else m_seen = false; } else if (seen) // Cheat if it's with locked on time { m_lastTarget = target; m_lastSeenPos = EntityPosition( target, 1); if ((AppTime() - m_notSeenTime) < 0) // 0.25 sec delay time - to stop twitchy ai m_seen = false; else m_seen = true; } return m_seen; } TVec3 Vision::getAimAtLocation() { if (m_pick.entity) return Vec3(m_pick.X, m_pick.Y, m_pick.Z ); TEntity target = getLastTarget(); target = GetChild( target, 1 ); TVec6 ab = GetEntityAABB( target ); TVec3 pos = EntityPosition( target, 1 ); pos.Y = ab.Y0 + ((ab.Y1 - ab.Y0) * 0.5); return pos; } float Vision::distanceToEnemy() { if (!m_seen) return MAX_DEC; return EntityDistance(m_entity, m_lastTarget); } void Vision::Render(TCamera cam) { #ifdef DEBUG_VISION TVec3 pos,pos1; float elapse = AppTime() - m_lastCheckTime; char buf[150]; string from, to; string stateString[3]; stateString[0] = "idle"; stateString[1] = "alert"; stateString[2] = "active"; pos = EntityPosition(m_pivot,1); pos1 = TFormVector( Vec3( 0,0,-2), m_pivot, NULL); SetColor( Vec4(0,1,0,1) ); // green pos1.X += pos.X; pos1.Y += pos.Y; pos1.Z += pos.Z; tdDraw( cam, pos, pos1 ); tdDrawText( cam, pos, "s" ); tdDrawText( cam, pos1, "e" ); if (m_seen) { SetColor( Vec4(1,1,1,1) ); // White tdDraw( cam, pos, m_lastSeenPos ); from = GetEntityKey(m_lastTarget,"name","none"); to = GetEntityKey(m_entity,"name","none"); sprintf(buf,"See (%f) (%d) %s %s Angle (%f)", elapse, m_cnt, from.c_str(), to.c_str(), angleToDest(m_pivot, m_targetPos) ); tdDrawText( cam, pos, buf ); } else { SetColor( Vec4(1,0,0,1) ); // red sprintf(buf,"No See %s (%f) (%d) Angle (%f)", stateString[m_state].c_str(), elapse, m_cnt, angleToDest(m_pivot, m_targetPos) ); tdDrawText( cam, pos, buf ); if (m_pick.entity) { SetColor( Vec4(0,0,1,1) ); // blue tdDraw( cam, pos, Vec3(m_pick.X, m_pick.Y, m_pick.Z) ); } } if (m_lastTarget) { SetColor( Vec4(0,1,1,1) ); // yellow tdDraw( cam, pos, EntityPosition(m_lastTarget,1) ); } #endif } #include "gamemath.h" #include "pathsteer.h" #include <assert.h> // // Convert radians to degress // float Rad2Deg (double Angle) { static double ratio = 180.0 / 3.141592653589793238; return Angle * ratio; } float calcAngle( TVec3 pOrigin, TVec3 pDest ) { float a; float b; a = pDest.X - pOrigin.X; b = pDest.Z - pOrigin.Z; if (a == 0 && b == 0) return 0; if (a < 0) return Rad2Deg( acos( b / sqrt( (a * a) + (b * b ))) ); else return Rad2Deg( -acos( b / sqrt( (a * a) + (b * b ))) ); } // // Calc distance to destination // float distanceTo(TVec3 spos, TVec3 epos, distanceType distanceFunction ) { float dx= abs(epos.X - spos.X); float dy = abs(epos.Y - spos.Y); float dz= abs(epos.Z - spos.Z); switch(distanceFunction) { case distanceEuclidean: return sqrt( (dx * dx) + (dz * dz) + (dy * dy)); case distancePseudoEuclidean: return (dx * dx) + (dz * dz) + (dy * dy); case distanceManhatten: return dx + dz + dy; case distanceDiagonalShortcut: if( dx > dz) return 1.4*dz + (dx-dz); else return 1.4*dx + (dz-dx); default: assert( 0,"Bad distance function"); } return 0; } // // Calc angle from src to dst // float angleToDest( TEntity observer, TVec3 targetPos ) { //observer vector TVec3 tform = TFormVector( Vec3(0,0,1), observer, NULL); TVec3 observerPos = EntityPosition( observer,1 ); float dist = distanceTo( observerPos, targetPos ); //pick vector float dx = (observerPos.X - targetPos.X) / dist; float dy = (observerPos.Y - targetPos.Y) / dist; float dz = (observerPos.Z - targetPos.Z) / dist; //dot product float dot = (tform.X*dx) + (tform.Y*dy) + (tform.Z*dz); float ang = Rad2Deg( acos( dot ) ); return ang; }
  6. I'll put in a zip file all my code so far and put on my blog. I just need to tidy the VC++ project a little to strip out the third party libraries into LIB files. It's a bit WIP rats nest a the moment so I may have give lots of explanations. I did attempt to get recast to compile from with blitzmax but failed, that's why I moved onto C++. However I now realise I could have just compiled recast with VC++ into a LIB file and than use it from within blitzmax. I believe the failure was probably to do with compiler differences in the minGW and how into inits variables.
  7. pick.X Y Z are the position pick.NX NY NZ are the normals
  8. In a previous blog entry I showed a code sample of the behaviour tree. In the code below is the code that is fired for each behaviour. The return status mean: - RUNNING - Keep on running the behaviour FAILED - Exit this behaviour with error (has different effect depending on sequeance/selector/parallel node. COMPLETE - Exit this behaviour with complete (has different effect depending on sequeance/selector/parallel node. #include "actor/include/action.h" #include <alive/TreeBuilder.h> #include <alive/engine/ConstantNode.h> #include <alive/tree/Parallel.h> #include <alive/tree/Repeat.h> #include <alive/tree/ErrorHandler.h> void SpatialAction::setup(Pedestrian& ped, Vision& vis, ActorGun& rifle) { m_pedestrian = &ped; m_vision = &vis; m_rifle = &rifle; } void SpatialAction::init() { m_pedestrian = NULL; m_Observer.BIND(SpatialAction, this, stop); } // Moves the actor to the nearest enemy alive::Status ActionMoveToEnemy::execute() { if (m_pedestrian == 0) { return FAILED; } if (m_vision == 0) { return FAILED; } m_pedestrian->currentAction += "ActionMoveToEnemy,"; if (m_pedestrian->moveToPosition( m_vision->m_lastSeenPos )) return RUNNING; else return FAILED; } void ActionMoveToEnemy::stop(alive::Status) { m_pedestrian->stopMove(); } alive::Status ActionStopMove::execute() { m_pedestrian->stop(); if(m_pedestrian->moveStatus == MOVE_IDLE) return COMPLETED; return RUNNING; } alive::Status hasGun::execute() { if( (m_rifle != NULL) == m_Settings.getEquals()) { m_pedestrian->currentAction += "hasGun,"; return COMPLETED; } return FAILED; } alive::Status WithinFiringRange::execute() { if(!m_rifle) { // If no rifle I'm not in firing range if (!m_Settings.getEquals()) return RUNNING; else return RUNNING; } if( m_rifle->withinRange( m_vision->distanceToEnemy() ) == m_Settings.getEquals()) { m_pedestrian->currentAction += "WithinFiringRange,"; return RUNNING; } return FAILED; } // Moves the actor to the next waypoint alive::Status ActionMoveToWaypoint::execute() { if (m_pedestrian == 0) { return FAILED; } m_pedestrian->currentAction += "ActionMoveToWaypoint,"; if (m_pedestrian->moveToNextWaypoint()) return RUNNING; else return COMPLETED; } void ActionMoveToWaypoint::stop(alive::Status) { m_pedestrian->stopMove(); } alive::Status ActionCurrentMode::execute() { m_pedestrian->currentMode = m_Settings.getmode(); return COMPLETED; } void AimAtEnemy::init() { } alive::Status LookForward::execute() { m_pedestrian->currentAction += "LookForward,"; if (m_pedestrian->anim->m_bodyControl->needToReset( BONE_SPINE ) ) { m_pedestrian->anim->m_bodyControl->reset( BONE_SPINE ); return RUNNING; } if(m_Settings.getParallel()) return RUNNING; else return COMPLETED; } This isn't the complete code but it might help people get a feel.
  9. Here's the code that loads in the verticies to recast:- Include:- #ifndef MESHLOADER_SBX #define MESHLOADER_SBX #include "leo.h" #include "MeshLoaderObj.h" class rcMeshLoaderSBX : public rcMeshLoaderObj { public: TEntity scene; int vcap; int tcap; int nidx; rcMeshLoaderSBX() { vcap = 0; tcap = 0; scene = NULL;}; ~rcMeshLoaderSBX(); void addVertex(float x, float y, float z, int& cap) { rcMeshLoaderObj::addVertex( x, y, z, cap); } void addTriangle(int a, int b, int c, int& cap) { rcMeshLoaderObj::addTriangle(a, b, c, cap); } bool load(const char* filename); private: }; #endif Source:- #include "leo.h" #include "recast.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #define _USE_MATH_DEFINES #include <math.h> #include "ledcast.h" #include "ledcast_statmeshSimple.h" #include "rcMeshLoaderSBX.h" using namespace LEO; rcMeshLoaderSBX *currentLoader; bool processMesh( TEntity entity ) { TMesh mesh = (TMesh)entity; int SurfCnt = CountSurfaces( mesh ); TSurface surface; TVec3 vec; int vertIdx; float factor=1; vertIdx = currentLoader->getVertCount(); for( int s = 1; s <= SurfCnt; s++ ) { surface = GetSurface( mesh, s ); if (surface) { for( int t = 0; t < CountTriangles( surface ); t++) { for( int v = 0; v < 3; v++) { vec = GetVertexPosition( surface, TriangleVertex( surface, t, v )); vec=TFormPoint(Vec3(vec.X,vec.Y,vec.Z),entity,NULL); currentLoader->addVertex( (vec.X)*-factor, (vec.Y)*factor, (vec.Z)*factor, currentLoader->vcap ); } currentLoader->addTriangle( vertIdx+1, vertIdx, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; } } else break; } return true; } int _stdcall recastLoadMesh( TEntity entity, byte* extra ) { TMesh mesh = (TMesh)entity; std::string entityClass, cn, en; int type, type1; // Only bother with collision type 1 cn=GetEntityKey(entity,"classname"); en=GetEntityKey(entity,"name"); entityClass = GetEntityKey(entity, "class",""); type = atoi( GetEntityKey(entity, "collisiontype","1") ); if (type >0) EntityType(entity, type, 1); type1 = GetEntityType(entity); //if (EntityHidden(entity)) return true; if (entityClass != "Model") return true; if( (type1 != 1)|| (cn=="water")|| (cn=="physics_prop")|| (cn=="physics_pivot")|| (cn=="joint_motor")|| (cn=="info_waypoint")|| (cn=="info_playerstart")|| (cn=="player")|| (cn=="light_directional")|| (cn=="light_spot")|| (cn=="light_point")|| (cn=="env_emitter")|| (cn=="env_sound")) { HideEntity(entity); return true; } int child = CountChildren(entity); for(int i=1; i<=child;i++) { processMesh( GetChild(entity, i) ); }; return true; } int _stdcall recastLoadTerrain( TEntity terrain, byte* extra ) { std::string entityClass; entityClass = GetEntityKey(terrain, "class",""); if (entityClass != "Terrain") return false; float mapSize = 1024; float x; float z; float mapHalfSize = 512; float height; int vertIdx; float minx = 0, minz =0, maxx=0, maxz=0; if (!terrain) return false; vertIdx = currentLoader->getVertCount(); for(x = -mapHalfSize; x<mapHalfSize - 1; x++) { for(z = -mapHalfSize; z<mapHalfSize - 1; z++) { height = TerrainElevation( terrain, x, z); if ((height != 0) || ((x>=minx && x<=maxx) && (z>=minz && z <= maxz))) { minx = __min(minx, x); minz = __min(minz, z); maxx = __max(maxx, x); maxz = __max(maxz, z); //br height = TerrainElevation( terrain, (float)x+1, (float)z); currentLoader->addVertex( (x+1)*-1, height, z, currentLoader->vcap ); //tr height = TerrainElevation( terrain, (float)x+1, (float)z+1); currentLoader->addVertex( (x+1)*-1, height, z+1, currentLoader->vcap ); //bl height = TerrainElevation( terrain, x, z); currentLoader->addVertex( x*-1, height, z, currentLoader->vcap ); currentLoader->addTriangle( vertIdx, vertIdx+1, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; //Trianlge 2 //tl height = TerrainElevation( terrain, (float)x, (float)z+1); currentLoader->addVertex( x*-1, height, z+1, currentLoader->vcap ); //bl height = TerrainElevation( terrain, (float)x, (float)z); currentLoader->addVertex( x*-1, height, z, currentLoader->vcap ); //tr height = TerrainElevation( terrain, (float)x+1, (float)z+1); currentLoader->addVertex( (x+1)*-1, height, z+1, currentLoader->vcap ); currentLoader->addTriangle( vertIdx, vertIdx+1, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; } else height = -1; } } return true; } int _stdcall recastLoadMeshNormals( TEntity entity, byte* extra ) { TMesh mesh = (TMesh)entity; std::string entityClass; entityClass = GetEntityKey(entity, "class",""); if (entityClass != "Mesh") return false; TSurface surface; int SurfCnt = CountSurfaces( mesh ); float *normals; normals = (float *)(currentLoader->getNormals()); TVec3 vnorm; for( int s = 1; s <= SurfCnt; s++ ) { surface = GetSurface( mesh, s ); for( int t = 0; t < CountTriangles( surface ); t++) { vnorm = GetVertexNormal( surface, TriangleVertex( surface, t, 1 )); normals[currentLoader->nidx] = vnorm.X; normals[currentLoader->nidx+1] = vnorm.Y; normals[currentLoader->nidx+2] = vnorm.Z; currentLoader->nidx=currentLoader->nidx+3; } } return true; } bool rcMeshLoaderSBX::load(const char* filename) { TWorld world; TWorld cur; cur = CurrentWorld(); std::string f = filename; if (f.find("sbx")) scene = LoadScene((str)filename); else if (f.find("gmf")) scene = LoadModel((str)filename); if (!scene) return false; currentLoader = this; ForEachEntityDo( (BP)recastLoadMesh,NULL, ENTITY_MESH|ENTITY_MODEL ); ForEachEntityDo( (BP)recastLoadTerrain,NULL, ENTITY_TERRAIN ); setNormals( new float[getTriCount()*3] ); nidx = 0; calcNormals(); SetWorld(cur); return true; } rcMeshLoaderSBX::~rcMeshLoaderSBX() { if (scene) FreeEntity(scene); }
  10. That would be brill! It took me quite a long time, but only because I'm not very bright and I do all this between my wife nagging and finding me DIY to do. Every game AI has it failure point see: - http://aigamedev.com/open/articles/bugs-caught-on-tape/ For what it's worth I'll help anyway I can, but that would be like me trying to give Stephen Hawkins tips on black holes.
  11. Youtube video of my AI. Red line is path found by recast. White text is current behaviours running in behaviour tree. Here's what the BT looks like (yes it is C++) alive::Node* moveToEnemy(std::string type = "walk") { return alive::TreeBuilder() .execute<ActionMoveToEnemy>() .type(type) .end(); } alive::Node* resetToStart() { return alive::TreeBuilder() .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("resetToStart") .end() .execute<ActionAnimate>() .Speed(1) .Name("rifle_idle") .AnimType(PLAY_REPEAT) .end() .composite<alive::ParallelNode>() .execute<LookForward>() .Parallel(true) .end() .execute<ActionStopMove>() .end() .end() .end(); } alive::Node* gunAttack() { return alive::TreeBuilder() .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("gunAttack") .end() .execute<hasGun>() .Equals(true) .end() .execute<CanSeeEnemy>() .Equals(true) .Parallel(false) .end() .execute<ActionStopMove>() .end() .execute<ActionAnimate>() .Speed(1) .TranTime(1) .Name("rifle_idle") .AnimType(PLAY_REPEAT) .end() .composite<alive::ParallelNode>() .execute<CanSeeEnemy>() .Equals(true) .end() .execute<WithinFiringRange>() .Equals(true) .end() .execute<AimAtEnemy>() .end() .composite<alive::SequenceNode>() .decorator<alive::RepeatNode>() .composite<alive::SelectorNode>() .execute<NeedToAim>() .Equals(true) .end() .execute<FireAtEnemy>() .end() .execute<ActionTimer>() .time(0.01) .end() .end() .end() .end() .end() .end(); } alive::Node* moveToAttackPosition() { return alive::TreeBuilder() .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("moveToAttackPosition") .end() .execute<hasGun>() .Equals(true) .end() .add(resetToStart()).end() .composite<alive::ParallelNode>() .execute<IsSeenIdle>() .Equals(false) .end() .execute<WithinFiringRange>() .Equals(true) .end() .execute<hasGoodAimAtEnemy>() .Equals(false) .end() .add(moveToEnemy()).end() .end() .end(); } alive::Node* moveToWithinRifleRange() { return alive::TreeBuilder() .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("moveToWithinRifleRange") .end() .execute<hasGun>() .Equals(true) .end() .composite<alive::ParallelNode>() .execute<LookForward>() .Parallel(true) .end() .execute<IsSeenIdle>() .Equals(false) .end() .execute<WithinFiringRange>() .Equals(false) .end() .add(moveToEnemy()).end() .end() .end(); } alive::Node* hunt() { return alive::TreeBuilder() .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("hunt") .end() .composite<alive::ParallelNode>() .execute<LookForward>() .Parallel(true) .end() .execute<IsSeenIdle>() .Equals(false) .end() .execute<CanSeeEnemy>() .Equals(false) .end() .add(moveToEnemy()).end() .end() .end(); } void Crawler::init(Pedestrian* pPedestrian, Vision *pvision, ActorGun *pRifle) { // .decorator<alive::RepeatNode>() // .decorator<alive::ErrorHandlerNode>() alive::Node* behave = alive::TreeBuilder() .decorator<alive::RepeatNode>() .add(resetToStart()).end() .composite<alive::SelectorNode>() // Reset to default // Attack with gun .add(gunAttack()).end() // Move to a aim position .add(moveToAttackPosition()).end() // Move to attack (to melle or to get in firing range) .add(moveToWithinRifleRange()).end() // Hunt .add(hunt()).end() // Move to waypoint .composite<alive::ParallelNode>() .execute<CanSeeEnemy>() .Equals(false) .end() .composite<alive::SequenceNode>() .add(resetToStart()).end() .execute< CrawlerCompare<bool> >() .Variable(&Crawler::hasWaypoint) .Reference(true) .end() .execute<ActionMoveToWaypoint>() .end() .end() .end() // Idle .composite<alive::SequenceNode>() .execute<ActionCurrentMode>() .mode("idle") .end() .composite<alive::ParallelNode>() .execute<CanSeeEnemy>() .Equals(false) .end() .composite<alive::SequenceNode>() .add(resetToStart()).end() .execute<ActionAnimate>() .Speed(1) .Name("rifle_idle") .AnimType(PLAY_ONCE) .end() .end() .end() .end() .end() .end(); rifle = pRifle; pedestrian = pPedestrian; vision = pvision; // alive::InitializeWithBlackboard<Crawler> initialize(*this); alive::InitializeWithBlackboard<Crawler> *initialize = new alive::InitializeWithBlackboard<Crawler>(*this); m_Brain.registry.setAddObserver(*initialize); m_Brain.registry.setAddObserver(*new InitializeAction(*this)); //m_Brain.registry.setAddObserver(*new InitializeAction(*this)); m_Brain.add(*behave); } void Crawler::Update( float gameLoopTime ) { pedestrian->currentAction = pedestrian->currentMode + ","; m_Brain.tick(); pedestrian->Update( gameLoopTime ); } void Crawler::Render( float gameLoopTime) { pedestrian->Render( gameLoopTime ); vision->Render( pedestrian->scene->cam ); }
  12. Thanks that did the trick. Thnaks Lumooja, I'll convert over.
  13. My function: - void tdDraw( TCamera cam, TVec3 s, TVec3 e ) { TVec3 p1; TVec3 p2; p1 = CameraUnproject( cam, s ); p2 = CameraUnproject( cam, e ); if (p1.Z < 0 || p2.Z < 0) return; DrawLine( p1.X, p1.Y, p2.X-p1.X, p2.Y-p1.Y ); } Seems not to work correctly anymore, one end of the line seems to be in the correct place but the other is always top left. Has it changed?
  14. Both. I have done a lot with it. I’ve got it to process levels created from the sandbox and I have used to for my NPCs to move around, going from A to B. The path finding is really fast, enough to handle many requests per frame. The generation of the static mesh can take a while, for example the island level as I remember took about 10 minutes. However when I wrote my own navmesh stuff it took in excess of 24hours to do the island level. For a level the size of the island it would take a lot longer to do manually lacing AI nodes I guess. The generation can be refined with different config such as voxel size, slope angle, agent size etc. A Larger voxel size would speed the mesh generation. Generation of a tile using the tile mesh method depends on the size of tile, you could probably only get away with doing 1 -2 tiles per frame. I guess this where threading would be really useful. I’ll try and do a video of it working, however my AI stuff is still very flawed so I wasn’t proud enough of it yet to show it off. I’ll also publish all my code for anybody who wants to use it.
  15. Word of warning, I spent six months working on my navmesh generation before I chucked my stuff for recast. You might be more successful as your probably brighter (thats not hard), however recast is done and working... I'll share all my C++ code happily.
  16. Sorry for the delay, I didn't have access to the this forum. Recast is NOT my work, I've intergrated the library with Leadwerks. Recast is the work of chap who did the AI for crysis, so fairly good pedagree.
  17. In the config it's an on/off option. What I was after was the intensity, size etc.
  18. Not quite sure what you mean but this is what is in my sbx file: - Model { path="environment_atmosphere.gmf" position=-2.00000000,0.000000000,1.00000000 rotation=-0.000000000,-0.000000000,0.000000000 scale=1.00000000,1.00000000,1.00000000 id=416742776 "backgroundcolor"="127,127,127,255" "camerarange"="0.100000001,1000.00000" "class"="Model" "fogangle"="0.000000000,15.0000000" "fogcolor"="234,233,215,255" "fogrange"="0.000000000,200.000000" "gravityforce"="0.000000000,-20.0000000,0.000000000" "intensity"="1" "name"="atmosphere_1" "range"="10" "reloadafterscript"="1" "skymaterial"="FullskiesBlueClear0016_2_L.mat" "volume"="1" } it has a "class", it has a "name" which is not fixed, it doesn't have a "classname".
  19. It would be nice if the sbx file had a classname in for atmosphere and ambient light otherwise I haven't got anything to look for against the model.
  20. Just need to find out how to make my skybox appear now. Guess I need to process the atmosphere model.
  21. Correct solved my problems: 1) Line Pick - if you specify the radius param other than zero it is very very slow. Set param to zero and FPS returned to 120. Param didn't do anything pre 2.3. 2) val = (flt)atof(GetEntityKey(e,"mass","60")); Was setting mass to zero not 60? Guess the default on entity key is broken. Editted my .sbx file and added mass and controller started working. I guess these are things for Josh to fix in the future.
  22. I have the following problems in my project since converting to 2.3. 1) Frame rate droppped from 120fps to 2fps - if I comment out my AI it goes back to 120fps. What should I look for? Has any params changed to anything? 2) Controller will not move, have the params changed for this (I was on 2.28). Thanks for any help.
  23. Chris Paulson

    Week 2

    From the process of doing more high level stuff and demos you might find you want to add features to facilitate what you’re doing.
  24. I haven't been blogging since I started with Leadwerks so this blog will be a bit of a catchup until now. Unlike most people working with Leadwerks I thought I'd ignore all the graphic eye candy as icing on the cake and concentrate on the importing matter of making things happen ie. AI The bacis of AI is:- Physics ------- Knowing whats bumped into what. This is already taken care of in Leadwerks. Sight ----- NPC's need to have a sense of sight, this is mimicked by doing Line of sight tests using raycasts. In leadwerks you use LinePick, the knack is though where to linepick. I post example code latter. Pathfinding ----------- NPCs need to know how to get from A to B. This is a big one and quite complicated. Older games used AI Nodes bit this is now seen as outdated, the thing to use in a modern game is a NAVMESH. I wrote Blitzmax stuff to generate my own navmeshes by analysing the level with linepicks however this proved to be too slow. I dumped Blitz and went onto C++ and intergrated in recast ( http://code.google.com/p/recastnavigation/ ). This library is really good and fast. My NPCs knows now how to get from A to B. See http://www.ai-blog.net/archives/000152.html My blitzmax navmesh Locomotion ---------- Once a NPC knows how to get from A to B they need to do the actual physical move. Moving the character controller along will not derive a quality appearance as it will bump into things, other NPC dynamic moving objects etc. To get the actual controller to move nicely I have implemented OpenSteer on top of the pathfinding. Using combined steering forces allows collision avoidence of moving objects. See http://opensteer.sourceforge.net/ I am only 80% satisfied with this solution an may abandon it for something better in the future. A dark video example of navmesh and opensteer working Animation --------- To avoid foot skating/moonwalking I play the movement animation the correct proportion of frames to match the distance travelled by the locomotion. I pre analyis the animation sequence to calculate how for a sequence my travel. Descesion making ---------------- Another big one. A lot of people implement FPS games using finite state machines - a posh was of saying lots of if then else ifs.. I wrote my own FSM machine again in Blitz but decided this did not have the quality I was looking for. I have dropped this for behavoir trees and I am using a library called A++. See http://forums.aigamedev.com for more info on BTs and A++ I can't comment any further than as this is as far as I've got, I am currently implementing all the different behaviours for my AI.
×
×
  • Create New...