1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-18 16:32:32 +02:00

Initial shift to AIController controlling

This commit is contained in:
Daniel Evans 2014-05-29 09:10:08 +01:00
parent 34400e19ab
commit 532738077e
11 changed files with 244 additions and 179 deletions

View File

@ -11,22 +11,60 @@ struct GTACharacter;
*/
class GTAAIController
{
public:
enum Activity {
Idle,
GoTo,
};
struct ActivityParameter {
glm::vec3 position;
ActivityParameter( ) {}
ActivityParameter( const glm::vec3& position ) :
position( position ) {}
};
protected:
/**
* The character being controlled.
*/
GTACharacter* character;
Activity _currentActivity;
Activity _nextActivity;
ActivityParameter _currentParameter;
ActivityParameter _nextParameter;
bool updateActivity();
void setActivity(Activity activity, const ActivityParameter& _parameter );
public:
GTAAIController(GTACharacter* character);
Activity getCurrentActivity() const { return _currentActivity; }
const ActivityParameter& getCurrentActivityParameter() const { return _currentParameter; }
Activity getNextActivity() const { return _nextActivity; }
const ActivityParameter& getNextActivityParameter() const { return _nextParameter; }
/**
* @brief setNextActivity Sets the next Activity with a parameter.
* @param activity
* @param position
*/
void setNextActivity( Activity activity, const ActivityParameter& parameter );
/**
* @brief update Updates the controller.
* @param dt
*/
virtual void update(float dt) = 0;
virtual void update(float dt);
virtual glm::vec3 getTargetPosition() = 0;
};

View File

@ -7,30 +7,11 @@
struct GTAAINode;
class GTADefaultAIController : public GTAAIController
{
enum Action
{
Wander
};
Action action;
GTAAINode* targetNode;
GTAAINode* lastNode;
float nodeMargin; /// Minimum distance away to "reach" node.
float getUpTime; /// Time to wait before getting up.
public:
GTADefaultAIController(GTACharacter* character)
: GTAAIController(character),
action(Wander),
targetNode(nullptr),
lastNode(nullptr),
nodeMargin(0.f), getUpTime(-1.f) {}
void update(float dt);
: GTAAIController(character) {}
glm::vec3 getTargetPosition();
};

View File

@ -44,9 +44,13 @@ private:
void createActor(const glm::vec3& size = glm::vec3(0.35f, 0.35f, 1.3f));
void destroyActor();
// Incredibly hacky "move in this direction".
bool _hasTargetPosition;
glm::vec3 _targetPosition;
public:
enum Activity {
enum Action {
None,
Idle,
Walk,
@ -84,16 +88,16 @@ public:
Type type() { return Character; }
Activity currentActivity;
Action currentActivity;
void enterActivity(Activity act);
void enterAction(Action act);
void tick(float dt);
/**
* @brief updateCharacter updates internall bullet Character.
*/
void updateCharacter();
void updateCharacter(float dt);
virtual void setPosition(const glm::vec3& pos);
@ -114,6 +118,8 @@ public:
* (taking into account the current vehicle)
*/
void resetToAINode();
void setTargetPosition( const glm::vec3& target );
};
#endif

View File

@ -1,8 +1,59 @@
#include <ai/GTAAIController.hpp>
#include <objects/GTACharacter.hpp>
bool GTAAIController::updateActivity()
{
switch( _currentActivity ) {
default: break;
case GoTo: {
/* TODO: Use the ai nodes to navigate to the position */
glm::vec3 targetDirection = _currentParameter.position - character->getPosition();
if( glm::length(targetDirection) < 0.01f ) {
character->enterAction(GTACharacter::Idle);
return true;
}
character->setTargetPosition( _currentParameter.position );
glm::quat r( glm::vec3{ 0.f, 0.f, atan2(targetDirection.y, targetDirection.x) - glm::half_pi<float>() } );
character->rotation = r;
character->enterAction(GTACharacter::Walk);
} break;
}
return false;
}
void GTAAIController::setActivity(GTAAIController::Activity activity, const ActivityParameter &parameter)
{
_currentActivity = activity;
_currentParameter = parameter;
}
GTAAIController::GTAAIController(GTACharacter* character)
: character(character)
: character(character), _currentActivity(Idle), _nextActivity(Idle)
{
character->controller = this;
}
void GTAAIController::setNextActivity(GTAAIController::Activity activity, const ActivityParameter &parameter)
{
if( _currentActivity == Idle ) {
setActivity(activity, parameter);
_nextActivity = Idle;
}
else {
_nextParameter = parameter;
_nextActivity = activity;
}
}
void GTAAIController::update(float dt)
{
if(updateActivity()) {
setActivity(Idle, {});
}
}

View File

@ -1,142 +1,16 @@
#include "ai/GTADefaultAIController.hpp"
#include <objects/GTACharacter.hpp>
#include <engine/GameWorld.hpp>
#include <engine/Animator.hpp>
#include <objects/GTAVehicle.hpp>
void GTADefaultAIController::update(float dt)
{
if( character->currentActivity == GTACharacter::KnockedDown ) {
if( getUpTime < 0.f ) {
getUpTime = 1.5f;
}
else {
getUpTime -= dt;
if(getUpTime < 0.f) {
character->enterActivity(GTACharacter::GettingUp);
getUpTime = -1.f;
}
}
}
else if (character->currentActivity == GTACharacter::GettingUp ) {
if( character->animator->isCompleted() ) {
character->enterActivity(GTACharacter::Idle);
}
}
else {
if( action == Wander ) {
if( targetNode == nullptr ) {
GTAAINode::NodeType currentType = character->getCurrentVehicle()
? GTAAINode::Vehicle : GTAAINode::Pedestrian;
float d = std::numeric_limits< float >::max();
for( auto n = character->engine->aigraph.nodes.begin();
n != character->engine->aigraph.nodes.end();
++n ) {
if( (*n)->type != currentType ) continue;
if( (*n) == lastNode ) continue;
float ld = glm::length( (*n)->position - character->getPosition() );
if( ld > nodeMargin && ld < d ) {
d = ld;
targetNode = (*n);
}
}
if( targetNode == nullptr ) {
character->enterActivity(GTACharacter::Idle);
}
else {
character->enterActivity(GTACharacter::Walk);
// Choose a new random margin
if(character->getCurrentVehicle()) {
std::uniform_real_distribution<float> dist(2.f, 2.5f);
nodeMargin = dist(character->engine->randomEngine);
}
else {
std::uniform_real_distribution<float> dist(1.f, 1.5f);
nodeMargin = dist(character->engine->randomEngine);
}
}
}
else if( glm::length(getTargetPosition() - character->getPosition()) < nodeMargin ) {
if(targetNode->connections.size() > 0) {
std::uniform_int_distribution<int> uniform(0, targetNode->connections.size()-1);
GTAAINode* nextNode = nullptr; size_t i = 0;
do
{
nextNode = targetNode->connections[uniform(character->engine->randomEngine)];
} while(i++ < targetNode->connections.size() && nextNode == lastNode);
lastNode = targetNode;
targetNode = nextNode;
}
else {
targetNode = nullptr;
character->enterActivity(GTACharacter::Idle);
}
}
else
{
// Ensure the AI doesn't get stuck in idle with a target node.
if(character->currentActivity == GTACharacter::Idle) {
character->enterActivity(GTACharacter::Walk);
}
}
}
if( action == Wander && targetNode != nullptr ) {
auto d = targetNode->position - character->getPosition();
if(lastNode && character->getCurrentVehicle()) {
auto nDir = glm::normalize(targetNode->position - lastNode->position);
auto right = glm::cross(nDir, glm::vec3(0.f, 0.f, 1.f));
d += right * 2.2f;
}
if(character->getCurrentVehicle()) {
auto vd = glm::inverse(character->getCurrentVehicle()->getRotation()) * d;
float va = -atan2( vd.x, vd.y );
if(glm::abs(va) > (3.141f/2.f)) {
va = -va;
}
character->getCurrentVehicle()->setSteeringAngle(va);
}
else {
float a = -atan2( d.x, d.y );
character->rotation = glm::quat(glm::vec3(0.f, 0.f, a));
}
}
if(character->getCurrentVehicle()) {
if(targetNode == nullptr) {
character->getCurrentVehicle()->setThrottle(0.f);
}
else {
float targetVelocity = 5.f;
float perc = (targetVelocity - character->getCurrentVehicle()->physBody->getLinearVelocity().length())/targetVelocity;
perc = std::min(1.f, std::max(0.f, perc));
// Determine if the vehicle should reverse instead.
auto td = getTargetPosition() - character->getPosition();
auto vd = character->getCurrentVehicle()->getRotation() * glm::vec3(0.f, 1.f, 0.f);
if(glm::dot(td, vd) < -0.25f) {
perc *= -1.f;
}
character->getCurrentVehicle()->setThrottle(perc);
}
}
}
}
glm::vec3 GTADefaultAIController::getTargetPosition()
{
if(targetNode) {
/*if(targetNode) {
if(lastNode && character->getCurrentVehicle()) {
auto nDir = glm::normalize(targetNode->position - lastNode->position);
auto right = glm::cross(nDir, glm::vec3(0.f, 0.f, 1.f));
return targetNode->position + right * 2.2f;
}
return targetNode->position;
}
}*/
return glm::vec3();
}

View File

@ -65,10 +65,10 @@ void GTAPlayerAIController::update(float dt)
if( character->currentActivity != GTACharacter::Jump )
{
if( glm::length(direction) > 0.001f ) {
character->enterActivity(running ? GTACharacter::Run : GTACharacter::Walk);
character->enterAction(running ? GTACharacter::Run : GTACharacter::Walk);
}
else {
character->enterActivity(GTACharacter::Idle);
character->enterAction(GTACharacter::Idle);
}
if( character->getCurrentVehicle() ) {

View File

@ -7,7 +7,9 @@
GTACharacter::GTACharacter(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, Model* model, std::shared_ptr<CharacterData> data)
: GameObject(engine, pos, rot, model),
currentVehicle(nullptr), currentSeat(0), ped(data), physCharacter(nullptr),
currentVehicle(nullptr), currentSeat(0),
_hasTargetPosition(false),
ped(data), physCharacter(nullptr),
controller(nullptr), currentActivity(None)
{
mHealth = 100.f;
@ -30,7 +32,7 @@ GTACharacter::GTACharacter(GameWorld* engine, const glm::vec3& pos, const glm::q
animator->setModel(model);
createActor();
enterActivity(Idle);
enterAction(Idle);
}
}
@ -39,7 +41,7 @@ GTACharacter::~GTACharacter()
destroyActor();
}
void GTACharacter::enterActivity(GTACharacter::Activity act)
void GTACharacter::enterAction(GTACharacter::Action act)
{
if(currentActivity != act) {
currentActivity = act;
@ -66,7 +68,8 @@ void GTACharacter::createActor(const glm::vec3& size)
physObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
physCharacter = new btKinematicCharacterController(physObject, physShape, 0.2f, 2);
physCharacter->setVelocityForTimeInterval(btVector3(1.f, 1.f, 0.f), 1.f);
physCharacter->setGravity(engine->dynamicsWorld->getGravity().length());
engine->dynamicsWorld->addCollisionObject(physObject, btBroadphaseProxy::KinematicFilter, btBroadphaseProxy::StaticFilter);
engine->dynamicsWorld->addAction(physCharacter);
}
@ -92,7 +95,7 @@ void GTACharacter::tick(float dt)
}
if(currentVehicle) {
enterActivity(VehicleSit);
enterAction(VehicleSit);
}
switch(currentActivity) {
@ -136,7 +139,7 @@ void GTACharacter::tick(float dt)
animator->tick(dt);
updateCharacter();
updateCharacter(dt);
// Ensure the character doesn't need to be reset
if(getPosition().z < -100.f) {
@ -144,7 +147,7 @@ void GTACharacter::tick(float dt)
}
}
void GTACharacter::updateCharacter()
void GTACharacter::updateCharacter(float dt)
{
if(physCharacter) {
// Check to see if the character should be knocked down.
@ -181,7 +184,7 @@ void GTACharacter::updateCharacter()
if(object->type() == Vehicle) {
GTAVehicle* vehicle = static_cast<GTAVehicle*>(object);
if(vehicle->physBody->getLinearVelocity().length() > 0.1f) {
enterActivity(KnockedDown);
enterAction(KnockedDown);
}
}
}
@ -194,16 +197,44 @@ void GTACharacter::updateCharacter()
{
if(physCharacter->onGround())
{
enterActivity(GTACharacter::Idle);
enterAction(GTACharacter::Idle);
}
}
else
{
glm::vec3 direction = rotation * animator->getRootTranslation();
physCharacter->setWalkDirection(btVector3(direction.x, direction.y, direction.z));
glm::vec3 walkDir;
glm::vec3 animTranslate = animator->getRootTranslation();
btVector3 Pos = physCharacter->getGhostObject()->getWorldTransform().getOrigin();
position = glm::vec3(Pos.x(), Pos.y(), Pos.z());
if( _hasTargetPosition ) {
auto beforedelta = _targetPosition - position;
glm::quat faceDir( glm::vec3( 0.f, 0.f, atan2(beforedelta.y, beforedelta.x) - glm::half_pi<float>() ) );
glm::vec3 direction = faceDir * animTranslate;
auto positiondelta = _targetPosition - (position + direction);
if( glm::length(beforedelta) < glm::length(positiondelta) ) {
// Warp the character to the target position if we are about to overstep.
physObject->getWorldTransform().setOrigin(btVector3(
_targetPosition.x,
_targetPosition.y,
_targetPosition.z));
_hasTargetPosition = false;
}
else {
walkDir = direction;
}
}
else {
walkDir = rotation * animTranslate;
}
physCharacter->setWalkDirection(btVector3(walkDir.x, walkDir.y, walkDir.z));
Pos = physCharacter->getGhostObject()->getWorldTransform().getOrigin();
position = glm::vec3(Pos.x(), Pos.y(), Pos.z());
}
}
}
@ -239,7 +270,7 @@ bool GTACharacter::enterVehicle(GTAVehicle* vehicle, size_t seat)
enterVehicle(nullptr, 0);
vehicle->setOccupant(seat, this);
setCurrentVehicle(vehicle, seat);
enterActivity(VehicleSit);
enterAction(VehicleSit);
return true;
}
}
@ -286,7 +317,7 @@ bool GTACharacter::takeDamage(const GameObject::DamageInfo& dmg)
void GTACharacter::jump()
{
physCharacter->jump();
enterActivity(GTACharacter::Jump);
enterAction(GTACharacter::Jump);
}
void GTACharacter::resetToAINode()
@ -319,3 +350,9 @@ void GTACharacter::resetToAINode()
}
}
void GTACharacter::setTargetPosition(const glm::vec3 &target)
{
_targetPosition = target;
_hasTargetPosition = true;
}

View File

@ -124,13 +124,6 @@ std::map<std::string, std::function<void (std::string)>> Commands = {
}
}
},
{"knock-down",
[&](std::string) {
for(auto it = gta->pedestrians.begin(); it != gta->pedestrians.end(); ++it) {
(*it)->enterActivity(GTACharacter::KnockedDown);
}
}
},
{"vehicle-test",
[&](std::string) {
glm::vec3 hit, normal;

View File

@ -1,2 +1,8 @@
#define BOOST_TEST_MODULE gtfw
#include <boost/test/included/unit_test.hpp>
#include "test_globals.hpp"
std::ostream& operator<<( std::ostream& stream, const glm::vec3& v ) {
stream << v.x << " " << v.y << " " << v.z;
return stream;
}

64
tests/test_character.cpp Normal file
View File

@ -0,0 +1,64 @@
#include <boost/test/unit_test.hpp>
#include <objects/GTACharacter.hpp>
#include <ai/GTADefaultAIController.hpp>
#include "test_globals.hpp"
BOOST_AUTO_TEST_SUITE(CharacterTests)
BOOST_AUTO_TEST_CASE(test_create)
{
{
auto character = Global::get().e->createPedestrian(1, {100.f, 100.f, 50.f});
BOOST_REQUIRE( character != nullptr );
auto controller = new GTADefaultAIController(character);
// Check the initial activity is Idle.
BOOST_CHECK_EQUAL( controller->getCurrentActivity(), GTAAIController::Idle );
// Check that Idle activities are instantly displaced.
controller->setNextActivity(GTAAIController::GoTo, glm::vec3{ 1000.f, 0.f, 0.f });
BOOST_CHECK_EQUAL( controller->getCurrentActivity(), GTAAIController::GoTo );
BOOST_CHECK_EQUAL( controller->getNextActivity(), GTAAIController::Idle );
Global::get().e->destroyObject(character);
delete controller;
}
{
GTAAIController::ActivityParameter ap { glm::vec3 { 1.f, 2.f, 3.f } };
BOOST_CHECK_EQUAL( ap.position, glm::vec3( 1.f, 2.f, 3.f ) );
}
}
BOOST_AUTO_TEST_CASE(test_activities)
{
{
auto character = Global::get().e->createPedestrian(1, {0.f, 0.f, 0.f});
BOOST_REQUIRE( character != nullptr );
auto controller = new GTADefaultAIController(character);
controller->setNextActivity( GTAAIController::GoTo, { glm::vec3{ 10.f, 0.f, 0.f } } );
BOOST_CHECK_EQUAL( controller->getCurrentActivity(), GTAAIController::GoTo );
BOOST_CHECK_EQUAL( controller->getCurrentActivityParameter().position, glm::vec3( 10.f, 0.f, 0.f ) );
for(float t = 0.f; t < 11.f; t+=(1.f/60.f)) {
controller->update(1.f/60.f);
character->tick(1.f/60.f);
Global::get().e->dynamicsWorld->stepSimulation(1.f/60.f);
}
// This check will undoubtably break in the future, please improve.
BOOST_CHECK_CLOSE( glm::distance(character->getPosition(), {10.f, 0.f, 0.f}), 0.038f, 1.0f );
Global::get().e->destroyObject(character);
delete controller;
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -3,9 +3,22 @@
#include <SFML/Window.hpp>
#include <engine/GameWorld.hpp>
#include <glm/gtx/string_cast.hpp>
#define ENV_GAME_PATH_NAME ("OPENRW_GAME_PATH")
std::ostream& operator<<( std::ostream& stream, glm::vec3 const& v );
namespace boost { namespace test_tools {
template<>
struct print_log_value<glm::vec3> {
void operator()( std::ostream& s , glm::vec3 const& v )
{
s << glm::to_string(v);
}
};
}}
class Global
{
public:
@ -25,6 +38,8 @@ public:
++it) {
e->defineItems(it->second);
}
e->dynamicsWorld->setGravity(btVector3(0.f, 0.f, 0.f));
}
~Global() {