Browse Source

add follow logic

select follow target and toggle follow for a specific object. Only way to unfollow is to move the following bot to an arbitrary location
marauder2k7 1 month ago
parent
commit
3946017556

+ 9 - 11
Engine/source/T3D/AI/AIController.cpp

@@ -168,22 +168,20 @@ bool AIController::getAIMove(Move* movePtr)
             {
                obj = getAIInfo()->mObj;
             }
-            bool adjusted = false;
-            if (getNav()->avoidObstacles()) 
-            {
-               adjusted = true;
-            }
-            else if (mRandI(0, 100) < mControllerData->mFlocking.mChance && getNav()->flock())
-            {
-               adjusted = true;
-            }
+            
+            Point3F start = obj->getPosition();
+            Point3F end = start;
+            start.z = obj->getBoxCenter().z;
+            end.z -= mControllerData->mHeightTolerance;
 
+            obj->disableCollision();
             // Only repath if not already adjusted and on risky ground
             RayInfo info;
-            if (!adjusted && obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info))
+            if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info))
             {
                getNav()->repath();
             }
+            obj->enableCollision();
             getGoal()->mInRange = false;
          }
          if (getGoal()->getDist() < mControllerData->mFollowTolerance )
@@ -541,7 +539,7 @@ AIControllerData::AIControllerData()
    mAttackRadius = 2.0f;
    mMoveStuckTolerance = 0.01f;
    mMoveStuckTestDelay = 30;
-   mHeightTolerance = 0.001f;
+   mHeightTolerance = 0.1f;
    mFollowTolerance = 1.0f;
 
 #ifdef TORQUE_NAVIGATION_ENABLED

+ 36 - 32
Engine/source/T3D/AI/AINavigation.cpp

@@ -23,7 +23,7 @@
 #include "AIController.h"
 #include "T3D/shapeBase.h"
 
-static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType;
+static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType;
 
 AINavigation::AINavigation(AIController* controller)
 {
@@ -339,11 +339,11 @@ void AINavigation::repath()
    if (mPathData.path.isNull() || !mPathData.owned)
       return;
 
-   if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock())
+   if (avoidObstacles())
    {
       mPathData.path->mTo = mMoveDestination;
    }
-   else if (avoidObstacles())
+   else if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock())
    {
       mPathData.path->mTo = mMoveDestination;
    }
@@ -401,7 +401,7 @@ bool AINavigation::avoidObstacles()
    leftDir.normalizeSafe();
    rightDir.normalizeSafe();
 
-   F32 rayLength = getCtrl()->mMovement.getMoveSpeed();
+   F32 rayLength = obj->getVelocity().lenSquared() * TickSec * 2 + getCtrl()->getAIInfo()->mRadius;
    Point3F directions[3] = {
        forward,
        leftDir,
@@ -445,9 +445,10 @@ bool AINavigation::flock()
 
    obj->disableCollision();
    Point3F pos = obj->getBoxCenter();
-   Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2);
 
    F32 maxFlocksq = flockingData.mMax * flockingData.mMax;
+   Point3F searchArea = Point3F(maxFlocksq, maxFlocksq, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2);
+
    bool flocking = false;
    U32 found = 0;
    if (getCtrl()->getGoal())
@@ -471,41 +472,35 @@ bool AINavigation::flock()
          sql.mList.remove(obj);
 
          Point3F avoidanceOffset = Point3F::Zero;
+         F32 avoidanceAmtSq = 0;
 
-         //avoid objects in the way
          RayInfo info;
-         if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info))
-         {
-            Point3F blockerOffset = (info.point - dest);
-            blockerOffset.z = 0;
-            avoidanceOffset += blockerOffset;
-         }
-
          //avoid bots that are too close
          for (U32 i = 0; i < sql.mList.size(); i++)
          {
             ShapeBase* other = dynamic_cast<ShapeBase*>(sql.mList[i]);
             Point3F objectCenter = other->getBoxCenter();
 
-            F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
+            F32 sumMinRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
             F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
-            sumRad += separation;
+            separation += sumMinRad;
 
             Point3F offset = (pos - objectCenter);
             F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares
-            if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad)))
+            if ((flockingData.mMin > 0) && (offsetLensq < (sumMinRad * sumMinRad)))
             {
                other->disableCollision();
-               if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
+               if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info))
                {
                   found++;
-                  offset.normalizeSafe();
-                  offset *= sumRad + separation;
+                  offset *= separation;
                   avoidanceOffset += offset; //accumulate total group, move away from that
+                  avoidanceAmtSq += offsetLensq;
                }
                other->enableCollision();
             }
          }
+
          //if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up
          if (found == 0)
          {
@@ -514,20 +509,20 @@ bool AINavigation::flock()
                ShapeBase* other = static_cast<ShapeBase*>(sql.mList[i]);
                Point3F objectCenter = other->getBoxCenter();
 
-               F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin;
+               F32 sumMaxRad = flockingData.mMax + other->getAIController()->mControllerData->mFlocking.mMax;
                F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius;
-               sumRad += separation;
+               separation += sumMaxRad;
 
                Point3F offset = (pos - objectCenter);
-               if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq)))
+               F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares
+               if ((flockingData.mMax > 0) && (offsetLensq < (sumMaxRad * sumMaxRad)))
                {
                   other->disableCollision();
-                  if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info))
+                  if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info))
                   {
                      found++;
-                     offset.normalizeSafe();
-                     offset *= sumRad + separation;
                      avoidanceOffset -= offset; // subtract total group, move toward it
+                     avoidanceAmtSq -= offsetLensq;
                   }
                   other->enableCollision();
                }
@@ -535,27 +530,36 @@ bool AINavigation::flock()
          }
          if (found > 0)
          {
+            //ephasize the *side* portion of sidestep to better avoid clumps
+            if (avoidanceOffset.x < avoidanceOffset.y)
+               avoidanceOffset.x *= 2.0;
+            else
+               avoidanceOffset.y *= 2.0;
+
+            //add fuzz to sidestepping
             avoidanceOffset.z = 0;
             avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75;
             avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75;
-            if (avoidanceOffset.lenSquared() < (maxFlocksq))
+
+            avoidanceOffset.normalizeSafe();
+            avoidanceOffset *= avoidanceAmtSq;
+
+            if ((avoidanceAmtSq) > flockingData.mMin * flockingData.mMin)
             {
-               dest += avoidanceOffset;
+               dest = obj->getPosition()+avoidanceOffset;
             }
 
             //if we're not jumping...
             if (mJump == None)
             {
                dest.z = obj->getPosition().z;
+
                //make sure we don't run off a cliff
                Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance);
                if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info))
                {
-                  if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance)
-                  {
-                     mMoveDestination = dest;
-                     flocking = true;
-                  }
+                  mMoveDestination = dest;
+                  flocking = true;
                }
             }
          }

+ 65 - 4
Engine/source/navigation/navMeshTools/navMeshTestTool.cpp

@@ -112,6 +112,9 @@ NavMeshTestTool::NavMeshTestTool()
    mPlayer = NULL;
    mCurPlayer = NULL;
 
+   mFollowObject = NULL;
+   mCurFollowObject = NULL;
+
    mPathStart = Point3F::Max;
    mPathEnd = Point3F::Max;
 
@@ -120,10 +123,12 @@ NavMeshTestTool::NavMeshTestTool()
    mLinkTypes = LinkData(AllFlags);
    mFilter.setIncludeFlags(mLinkTypes.getFlags());
    mFilter.setExcludeFlags(0);
+   mSelectFollow = false;
 }
 
 void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt)
 {
+   mSelectFollow = false;
    Con::executef(this, "onActivated");
 }
 
@@ -140,6 +145,8 @@ void NavMeshTestTool::onDeactivated()
       Con::executef(this, "onPlayerDeselected");
    }
 
+   mSelectFollow = false;
+
    Con::executef(this, "onDeactivated");
 }
 
@@ -175,8 +182,17 @@ void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt)
    {
       if (!ri.object)
          return;
-
-      mPlayer = ri.object;
+      if (mSelectFollow)
+      {
+         mFollowObject = ri.object;
+         Con::executef(this, "onFollowSelected");
+         mSelectFollow = false;
+         return;
+      }
+      else
+      {
+         mPlayer = ri.object;
+      }
 
 #ifdef TORQUE_NAVIGATION_ENABLED
       AIPlayer* asAIPlayer = dynamic_cast<AIPlayer*>(mPlayer.getPointer());
@@ -277,10 +293,17 @@ void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt)
    RayInfo ri;
 
    if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri))
-      mCurPlayer = ri.object;
+   {
+      if (mSelectFollow)
+         mCurFollowObject = ri.object;
+      else
+         mCurPlayer = ri.object;
+   }
    else
+   {
+      mCurFollowObject = NULL;
       mCurPlayer = NULL;
-
+   }
 }
 
 void NavMeshTestTool::onRender3D()
@@ -310,10 +333,15 @@ void NavMeshTestTool::onRender3D()
 
    dd.immediateRender();
 
+   if (!mCurFollowObject.isNull())
+      renderBoxOutline(mCurFollowObject->getWorldBox(), ColorI::LIGHT);
    if (!mCurPlayer.isNull())
       renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE);
    if (!mPlayer.isNull())
       renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN);
+   if (!mFollowObject.isNull())
+      renderBoxOutline(mFollowObject->getWorldBox(), ColorI::WHITE);
+   
 }
 
 bool NavMeshTestTool::updateGuiInfo()
@@ -326,10 +354,25 @@ bool NavMeshTestTool::updateGuiInfo()
 
    String text;
 
+   if (mPlayer)
+      text = "LMB To Select move Destination. LSHIFT+LMB To Deselect Current Bot.";
+   if (mCurPlayer != NULL && mCurPlayer != mPlayer)
+      text = "LMB To select Bot.";
+
+   if (mPlayer == NULL)
+   {
+      text = "LMB To place start/end for test path.";
+   }
+
+   if (mSpawnClass != String::EmptyString && mSpawnDatablock != String::EmptyString)
+      text += " CTRL+LMB To spawn a new Bot.";
+
    if (statusbar)
       Con::executef(statusbar, "setInfo", text.c_str());
 
    text = "";
+   if (mPlayer)
+      text = String::ToString("Bot Selected: %d", mPlayer->getId());
 
    if (selectionBar)
       selectionBar->setText(text);
@@ -342,12 +385,24 @@ S32 NavMeshTestTool::getPlayerId()
    return mPlayer.isNull() ? 0 : mPlayer->getId();
 }
 
+S32 NavMeshTestTool::getFollowObjectId()
+{
+   return mFollowObject.isNull() ? 0 : mFollowObject->getId();
+}
+
+
 DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), ,
    "@brief Return the current player id.")
 {
    return object->getPlayerId();
 }
 
+DefineEngineMethod(NavMeshTestTool, getFollowObject, S32, (), ,
+   "@brief Return the current follow object id.")
+{
+   return object->getFollowObjectId();
+}
+
 DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "")
 {
    object->setSpawnClass(className);
@@ -358,3 +413,9 @@ DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), ,
    object->setSpawnDatablock(dbName);
 }
 
+DefineEngineMethod(NavMeshTestTool, followSelectMode, void, (), ,
+   "@brief Set NavMeshTool to select a follow object.")
+{
+   return object->followSelectMode();
+}
+

+ 6 - 0
Engine/source/navigation/navMeshTools/navMeshTestTool.h

@@ -18,11 +18,15 @@ protected:
    String mSpawnDatablock;
    SimObjectPtr<SceneObject> mPlayer;
    SimObjectPtr<SceneObject> mCurPlayer;
+   SimObjectPtr<SceneObject> mFollowObject;
+   SimObjectPtr<SceneObject> mCurFollowObject;
    Point3F mPathStart;
    Point3F mPathEnd;
    NavPath* mTestPath;
    LinkData mLinkTypes;
    dtQueryFilter mFilter;
+   bool mSelectFollow;
+
 public:
    DECLARE_CONOBJECT(NavMeshTestTool);
 
@@ -44,9 +48,11 @@ public:
    bool updateGuiInfo() override;
 
    S32 getPlayerId();
+   S32 getFollowObjectId();
 
    void setSpawnClass(String className) { mSpawnClass = className; }
    void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; }
+   void followSelectMode() { mSelectFollow = true; }
 
 };
 

+ 21 - 7
Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui

@@ -294,7 +294,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
          {
             internalName = "SelectActions";
             position = "7 21";
-            extent = "190 64";
+            extent = "190 136";
             
             new GuiButtonCtrl() {
                Profile = "ToolsGuiButtonProfile";
@@ -370,7 +370,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
          {
             internalName = "LinkActions";
             position = "7 21";
-            extent = "190 64";
+            extent = "190 136";
             
             new GuiButtonCtrl() {
                Profile = "ToolsGuiButtonProfile";
@@ -386,7 +386,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
          {
             internalName = "CoverActions";
             position = "7 21";
-            extent = "190 64";
+            extent = "190 136";
             
             new GuiButtonCtrl() {
                Profile = "ToolsGuiButtonProfile";
@@ -411,7 +411,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
          {
             internalName = "TileActions";
             position = "7 21";
-            extent = "190 64";
+            extent = "190 136";
             
             new GuiButtonCtrl() {
                Profile = "ToolsGuiButtonProfile";
@@ -427,7 +427,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
          {
             internalName = "TestActions";
             position = "7 21";
-            extent = "190 64";
+            extent = "190 136";
             new GuiControl() {
                profile = "GuiDefaultProfile";
                Extent = "190 20";
@@ -481,7 +481,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
                   Extent = "90 18";
                   text = "Delete";
                   tooltipProfile = "GuiToolTipProfile";
-                  tooltip = "Delete Selected.";
+                  tooltip = "Delete Selected Bot.";
                   command = "NavMeshTools->TestTool.getPlayer().delete();NavInspector.inspect();";
                };
                new GuiButtonCtrl() {
@@ -505,7 +505,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
                   HorizSizing = "right";
                   VertSizing = "bottom";
                   Extent = "90 18";
-                  text = "Follow";
+                  text = "Select Follow";
                   command = "NavMeshTools->TestTool.followObject();";
                };
                new GuiButtonCtrl() {
@@ -519,6 +519,20 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) {
                   command = "NavMeshTools->TestTool.stop();";
                };
             };
+            new GuiControl() {
+               profile = "GuiDefaultProfile";
+               Extent = "190 18";
+
+               new GuiButtonCtrl() {
+                  Profile = "ToolsGuiButtonProfile";
+                  buttonType = "PushButton";
+                  HorizSizing = "right";
+                  VertSizing = "bottom";
+                  Extent = "90 18";
+                  text = "Toggle Follow";
+                  command = "NavMeshTools->TestTool.toggleFollow();";
+               };
+            };
          };
       };
       new GuiContainer(NavEditorInspector){

+ 18 - 1
Templates/BaseGame/game/tools/navEditor/navEditor.tscript

@@ -431,7 +431,7 @@ function NavMeshTestTool::onActivated(%this)
    %properties->TestProperties.setVisible(false);
 
    %classList =  enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle");
-   echo(%classList);
+   //echo(%classList);
 
    SpawnClassSelector.clear();
    foreach$(%class in %classList)
@@ -498,6 +498,23 @@ function NavMeshTestTool::stop(%this)
     }
 }
 
+function NavMeshTestTool::toggleFollow(%this)
+{
+   
+   if(isObject(%this.getFollowObject()) && isObject(%this.getPlayer()))
+   {
+      if(%this.getPlayer().isMemberOfClass("AIPlayer"))
+         %this.getPlayer().followObject(%this.getFollowObject(), "2.0");
+      else
+         %this.getPlayer().getAIController().followObject(%this.getFollowObject(), %this.getPlayer().getDatablock().aiControllerData.mFollowTolerance);
+   }
+}
+
+function NavMeshTestTool::followObject(%this)
+{
+   %this.followSelectMode();
+}
+
 function SpawnClassSelector::onSelect(%this, %id)
 {
    %className = %this.getTextById(%id);