topical media & game development

talk show tell print

lib-game-delta3d-demos-TankTarget-TankActor.cpp / cpp



  /* -*-c++-*-
   * TutorialLibrary - This source file (.h & .cpp) - Using 'The MIT License'
   * Copyright (C) 2006-2008, Alion Science and Technology Corporation
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   *
   *
author: Curtiss Murphy */
include <TankActor.h> include <TargetChanged.h> include <ActorsRegistry.h> include <KillableTargetActor.h> include <dtDAL/enginepropertytypes.h> include <dtDAL/actorproperty.h> include <dtDAL/actorproxyicon.h> include <dtCore/loadable.h> include <dtCore/isector.h> include <dtUtil/nodecollector.h> include <dtGame/gamemanager.h> include <dtGame/actorupdatemessage.h> include <dtGame/basemessages.h> include <dtABC/application.h> include <dtCore/camera.h> include <dtCore/keyboard.h> include <dtCore/particlesystem.h> include <dtUtil/matrixutil.h> include <dtUtil/mathdefines.h> include <osg/MatrixTransform> include <osgSim/DOFTransform>
/////////////////////////////////////////////////////////////////////////

const std::string TankActor::EVENT_HANDLER_NAME("HandleGameEvent"); const float MAXTANKVELOCITY = 15.0f;
/////////////////////////////////////////////////////////////////////////

TankActor::TankActor(dtGame::GameActorProxy& proxy) : dtActors::GameMeshActor(proxy) , mDust(NULL) , mCannonShot(NULL) , mVelocity(0.0f) , mAddOnVelocity(0) , mTurnRate(0.0f) , mIsEngineRunning(false) , mLastReportedVelocity(0.0f) , mIsector(new dtCore::Isector()) , mNoTargetId("NO_TARGET_ID") , mCurrentTargetId(mNoTargetId) , mPropertiesUpdated(false) { SetName("HoverTank"); }
/////////////////////////////////////////////////////////////////////////

void TankActor::OnTickLocal(const dtGame::TickMessage& tickMessage) { float deltaSimTime = tickMessage.GetDeltaSimTime(); ComputeVelocityAndTurn(deltaSimTime); if (mPropertiesUpdated) { GetGameActorProxy().NotifyFullActorUpdate(); } MoveTheTank(deltaSimTime); CheckForNewTarget(); }
/////////////////////////////////////////////////////////////////////////

void TankActor::OnTickRemote(const dtGame::TickMessage& tickMessage) { float deltaSimTime = tickMessage.GetDeltaSimTime(); // do NOT recompute velocity and turn rate since we don't own this tank! MoveTheTank(deltaSimTime); }
/////////////////////////////////////////////////////////////////////////

void TankActor::ProcessMessage(const dtGame::Message& message) { if (message.GetMessageType() == dtGame::MessageType::INFO_GAME_EVENT) { const dtGame::GameEventMessage& eventMsg = static_cast<const dtGame::GameEventMessage&>(message); // Note, we are using strings which aren't constants. In a real application, these // event names should be stored in some sort of shared place and should be constants... if (eventMsg.GetGameEvent() != NULL) { // Handle "ToggleEngine" Game Event if (eventMsg.GetGameEvent()->GetName() == "ToggleEngine") { mIsEngineRunning = !mIsEngineRunning; mDust->SetEnabled(mIsEngineRunning); printf("Toggling Engines to the [\%s] state.\r\n", (mIsEngineRunning ? "ON" : "OFF")); } // Handle "SpeedBoost" Game Event else if (eventMsg.GetGameEvent()->GetName() == "SpeedBoost") { SetVelocity(mVelocity + -5.0f); } // Handle 'reset' else if (eventMsg.GetGameEvent()->GetName() == "ResetStuff") { // put the tank bank SetTransform(mOriginalPosition); mIsEngineRunning = false; mTurnRate = 0.0f; mVelocity = 0.0f; mDust->SetEnabled(false); // put our camera back - first to tank's position, and then offset it. dtCore::Transform tx(0.0f,0.7f,2.2f,0.0f,0.0f,0.0f); dtCore::Camera* camera = GetGameActorProxy().GetGameManager()->GetApplication().GetCamera(); //camera->SetTransform(mOriginalPosition, dtCore::Transformable::ABS_CS); camera->SetTransform(tx, dtCore::Transformable::REL_CS); } } } }
/////////////////////////////////////////////////////////////////////////

void TankActor::ComputeVelocityAndTurn(float deltaSimTime) { osg::Vec3 turnTurret; // calculate current velocity float decelDirection = (mVelocity >= 0.0) ? -1.0f : 1.0f; float accelDirection = 0.0f; float acceleration = 0.0; dtCore::Keyboard* keyboard = GetGameActorProxy().GetGameManager()->GetApplication().GetKeyboard(); // which way is the user trying to go? if (keyboard->GetKeyState('i')) { accelDirection = -1.0f; } else if (keyboard->GetKeyState('k')) { accelDirection = 1.0f; } // speed up based on user and current speed (ie, too fast) if (mIsEngineRunning && accelDirection != 0.0f) { // boosted too fast, slow down if ((accelDirection > 0 && mVelocity > MAXTANKVELOCITY) || (accelDirection < 0 && mVelocity < -MAXTANKVELOCITY)) { acceleration = deltaSimTime*(MAXTANKVELOCITY/3.0f)*decelDirection; } // hold speed else if (mVelocity == accelDirection * MAXTANKVELOCITY) { acceleration = 0; } // speed up normally - woot! else { acceleration = accelDirection*deltaSimTime*(MAXTANKVELOCITY/2.0f); } } else if (mVelocity > -0.1 && mVelocity < 0.1) { acceleration = -mVelocity; // close enough to 0, so just stop } else // coast to stop { acceleration = deltaSimTime*(MAXTANKVELOCITY/6.0f)*decelDirection; } //std::cerr << "Ticking - deltaTime[" << deltaSimTime << "], acceleration [" << acceleration << "]" << std::endl; SetVelocity(mVelocity + acceleration); if (mIsEngineRunning && keyboard->GetKeyState('l')) { SetTurnRate(-0.1f); } else if (mIsEngineRunning && keyboard->GetKeyState('j')) { SetTurnRate(0.1f); } else { SetTurnRate(0.0f); } if (keyboard->GetKeyState('o')) { turnTurret.set(-0.008, 0.0, 0.0); turnTurret = mDOFTran->getCurrentHPR() + turnTurret; mDOFTran->setCurrentHPR(turnTurret); } else if (keyboard->GetKeyState('u')) { turnTurret.set(0.008, 0.0, 0.0); turnTurret = mDOFTran->getCurrentHPR() + turnTurret; mDOFTran->setCurrentHPR(turnTurret); } if (keyboard->GetKeyState('f')) { mCannonShot->SetEnabled(true); } }
/////////////////////////////////////////////////////////////////////////

void TankActor::MoveTheTank(float deltaSimTime) { dtCore::Transform tx; osg::Matrix mat; osg::Quat q; osg::Vec3 viewDir; GetTransform(tx); tx.GetRotation(mat); mat.get(q); viewDir = q * osg::Vec3(0,-1,0); // translate the player along its current view direction based on current velocity osg::Vec3 pos; tx.GetTranslation(pos); pos = pos + (viewDir*(mVelocity*deltaSimTime)); //particle fun if (mDust.valid() && mIsEngineRunning && mVelocity != 0) { // Get the layer we want dtCore::ParticleLayer& pLayerToSet = *mDust->GetSingleLayer("Layer 0"); // make a temp var for changing particle default template. osgParticle::Particle& defaultParticle = pLayerToSet.GetParticleSystem().getDefaultParticleTemplate(); // do our funky changes float lifetime = dtUtil::Max(2.0f, dtUtil::Abs(mVelocity+1) * 0.4f); defaultParticle.setLifeTime(lifetime); } // attempt to ground clamp the actor so that he doesn't go through mountains. osg::Vec3 intersection; mIsector->Reset(); mIsector->SetStartPosition(osg::Vec3(pos.x(),pos.y(),-10000)); mIsector->SetDirection(osg::Vec3(0,0,1)); if (mIsector->Update()) { osgUtil::IntersectVisitor& iv = mIsector->GetIntersectVisitor(); const dtCore::DeltaDrawable* hitActor = mIsector->GetClosestDeltaDrawable(); if (hitActor != this) { osg::Vec3 p = iv.getHitList(mIsector->GetLineSegment())[0].getWorldIntersectPoint(); // make it hover pos.z() = p.z() + 2.0f; } } osg::Vec3 xyz = GetGameActorProxy().GetRotation(); xyz[2] += 360.0f * mTurnRate * deltaSimTime; tx.SetTranslation(pos); SetTransform(tx); GetGameActorProxy().SetRotation(xyz); }
/////////////////////////////////////////////////////////////////////////

void TankActor::CheckForNewTarget() { bool foundTarget = false; // To determine if a KillableActor is targeted, we are going to use old-skool // Doom-style aiming. Recall in the original Doom if the player's heading // matches up with the position of the enemy, you can shoot it. In other words, // the Z value doesn't matter. // // To calculate this, we are going to create a plane that intersects the tank's // position and is normal to our RIGHT vector( imagine a plane going through your // body vertically). And then, to see if we have hit our target, we are just going // to check if the distance the target to the plane is less than the radius of the // bounding sphere of the target (imagine the bounding sphere intersecting the plane). osg::Matrix absMat; osg::NodePathList nodePathList = mDOFTran->getParentalNodePaths(); if (!nodePathList.empty()) { absMat.set( osg::computeLocalToWorld(nodePathList[0]) ); } // Find out some info from our current matrix osg::Vec3 rightVector( dtUtil::MatrixUtil::GetRow3( absMat, 0) ); osg::Vec3 forwardVector( dtUtil::MatrixUtil::GetRow3( absMat, 1) ); osg::Vec3 tankPosition( dtUtil::MatrixUtil::GetRow3( absMat, 3 ) ); // Next, calculate the plane. osg::Plane plane(rightVector, tankPosition); float closestDistance(0.0f); dtCore::UniqueId closestId(mNoTargetId); // Find all the KillableActors and iterator over them, looking for a target... typedef std::vector<dtDAL::ActorProxy*> ActorProxyVector; ActorProxyVector killableActorProxies; GetGameActorProxy().GetGameManager()->FindActorsByType(*ActorsRegistry::KILLABLE_ACTOR_TYPE, killableActorProxies); for (ActorProxyVector::iterator iter = killableActorProxies.begin(); iter != killableActorProxies.end(); ++iter ) { // Find the position of the target we are querying. dtCore::RefPtr<dtDAL::ActorProperty> translationProp((*iter)->GetProperty(dtDAL::TransformableActorProxy::PROPERTY_TRANSLATION)); dtCore::RefPtr<dtDAL::Vec3ActorProperty> vec3prop(static_cast<dtDAL::Vec3ActorProperty*>(translationProp.get())); osg::Vec3 targetPosition(vec3prop->GetValue()); // Find the dtUtil::Absolute distance from the center of the target to the plane. float distance(dtUtil::Abs(plane.distance(targetPosition))); // Find the radius of the target's bounding sphere. float radius((*iter)->GetActor()->GetOSGNode()->getBound().radius()); // However, at this point we do not know if the target is in front of the tank // or behind the tank. We'll check for this by seeing if the dot product between // the tank's forward vector and the vector from the tank to the target is // positive. osg::Vec3 tankToTarget(targetPosition - tankPosition); float dot(tankToTarget * forwardVector); // So the final check.. if (distance < radius && // Is the plane intersecting the bounding sphere? dot > 0.0f ) // Is the target in front of us? { if (closestId == mNoTargetId || // Is this the first pass? distance < closestDistance ) // Or is it the cloest target yet? { foundTarget = true; closestDistance = distance; closestId = (*iter)->GetActor()->GetUniqueId(); } } } dtCore::UniqueId id; if (foundTarget) { // ... then use the cloest one id = closestId; } else { // else use no target. id = mNoTargetId; } // If we are already targeted this on a previous frame, then // don't send out a new message. if (mCurrentTargetId != id) { mCurrentTargetId = id; FireTargetChangedMessage(); } }
/////////////////////////////////////////////////////////////////////////

void TankActor::FireTargetChangedMessage() { dtCore::RefPtr<TargetChangedMessage> targetChangedMessage; GetGameActorProxy().GetGameManager()->GetMessageFactory(). CreateMessage(TutorialMessageType::TANK_TARGET_CHANGED, targetChangedMessage); targetChangedMessage->SetNewTargetUniqueId(mCurrentTargetId); targetChangedMessage->SetAboutActorId(GetUniqueId()); GetGameActorProxy().GetGameManager()->SendMessage(*targetChangedMessage); }
/////////////////////////////////////////////////////////////////////////

void TankActor::SetVelocity(float velocity) { mVelocity = velocity; // Notify the world that our velocity changed, if there is enough difference // In a more sophisticated app, you would track acceleration, not just velocity // And then you wouldn't have to send velocity but every so often, since acceleration // would allow you to dead reckon the position without a network update. if (!IsRemote()) { if ((dtUtil::Abs(dtUtil::Abs(mLastReportedVelocity) - dtUtil::Abs(mVelocity)) > 0.5) || (mLastReportedVelocity != mVelocity && (mVelocity == MAXTANKVELOCITY || mVelocity == 0.0f || mVelocity == -MAXTANKVELOCITY))) { mLastReportedVelocity = mVelocity; mPropertiesUpdated = true; } } }
/////////////////////////////////////////////////////////////////////////

void TankActor::SetTurnRate(float rate) { if (mTurnRate != rate) { mTurnRate = rate; if (!IsRemote()) { // Notify the world that our turn rate changed. Only changes on keypress mPropertiesUpdated = true; } } }
/////////////////////////////////////////////////////////////////////////

void TankActor::OnEnteredWorld() { dtActors::GameMeshActor::OnEnteredWorld(); // add our dust particle mDust = new dtCore::ParticleSystem(); mDust->LoadFile("Particles/dust.osg",true); mDust->SetEnabled(false); AddChild(mDust.get()); mCannonShot = new dtCore::ParticleSystem(); mCannonShot->LoadFile("Particles/smoke.osg",true); mCannonShot->SetEnabled(false); GetTransform(mOriginalPosition); // put our camera - first to tank's position, and then offset it. dtCore::Transform tx(0.0f,0.7f,2.2f,0.0f,0.0f,0.0f); dtCore::Camera* camera = GetGameActorProxy().GetGameManager()->GetApplication().GetCamera(); AddChild(camera); camera->SetTransform(tx, dtCore::Transformable::REL_CS); mIsector->SetScene(&(GetGameActorProxy().GetGameManager()->GetScene())); //Collect all of the Transform Nodes off of the Model dtCore::RefPtr<dtUtil::NodeCollector> mOSGCollector = new dtUtil::NodeCollector(GetGameActorProxy().GetGameActor().GetOSGNode(), dtUtil::NodeCollector::DOFTransformFlag); mDOFTran = mOSGCollector->GetDOFTransform("dof_turret_01"); mDOFTran->addChild(mCannonShot.get()->GetOSGNode()); if (mDOFTran == NULL) { LOG_ERROR ("DOF TURRET WAS NOT FOUND"); throw dtUtil::Exception(dtUtil::BaseExceptionType::GENERAL_EXCEPTION, "Could Not Find Turret", __FILE__, __LINE__); } }
/////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////

TankActorProxy::TankActorProxy() { SetClassName("HoverTank"); }
/////////////////////////////////////////////////////////////////////////

void TankActorProxy::BuildPropertyMap() { const std::string GROUP = "HoverTank"; dtActors::GameMeshActorProxy::BuildPropertyMap(); TankActor& actor = dynamic_cast<TankActor&>(GetGameActor()); // "Velocity" property AddProperty(new dtDAL::FloatActorProperty("Velocity","Velocity", dtDAL::MakeFunctor(actor, &TankActor::SetVelocity), dtDAL::MakeFunctorRet(actor, &TankActor::GetVelocity), "Sets/gets the hover tank's velocity.", GROUP)); // "Turnrate" property AddProperty(new dtDAL::FloatActorProperty("Turnrate","Turn Rate", dtDAL::MakeFunctor(actor, &TankActor::SetTurnRate), dtDAL::MakeFunctorRet(actor, &TankActor::GetTurnRate), "Sets/gets the hover tank's turn rate in degrees per second.", GROUP)); }
/////////////////////////////////////////////////////////////////////////

void TankActorProxy::CreateActor() { SetActor(*new TankActor(*this)); }
/////////////////////////////////////////////////////////////////////////

void TankActorProxy::OnEnteredWorld() { // Note we did not create any of these Invokables. ProcessMessage(), TickLocal(), // and TickRemote() are created for us in GameActorProxy::BuildInvokables(). //Register an invokable for Game Events... RegisterForMessages(dtGame::MessageType::INFO_GAME_EVENT); // Register an invokable for tick messages. Local or Remote only, not both! if (IsRemote()) { RegisterForMessages(dtGame::MessageType::TICK_REMOTE, dtGame::GameActorProxy::TICK_REMOTE_INVOKABLE); } else { RegisterForMessages(dtGame::MessageType::TICK_LOCAL, dtGame::GameActorProxy::TICK_LOCAL_INVOKABLE); } dtActors::GameMeshActorProxy::OnEnteredWorld(); }


(C) Æliens 04/09/2009

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.