Custom Controller Example: How to get an Enemy to Follow a given (editable) Path

From C4 Engine Wiki

Jump to: navigation, search

Note: This tutorial has been updated to work with 2.7.1

With the code below, you can create a Path in C4 and assign a Model or Geometry to it, such that the Model or Geometry will follow along the Path. The code is contained in a class called PathController. It has simply been added to the SimpleBall tutorial code below.

To use this code, follow these steps

  • Go to your C4/GameCode folder
  • Save a copy of the SimpleBall.h and SimpleBall.cpp files there
  • Now replace the code in SimpleBall.h and Simpleball.cpp with the code below
  • Compile and then start C4
  • In the World Editor, open a tutorial world like the Water world
  • Open the Paths page from the menu and draw a path in the top viewport
  • With the path still selected, press Ctrl-I to edit the node info
  • Change the node name to EnemyPath
  • Now draw any Geometry, say a cube, again in the top viewport. Raise it slightly above the ground.
  • With the Geometry still selected, press Ctrl-I to edit the node info
  • Change the Controller for that Geometry to be the new Path Controller
  • Press Ctrl-P to save and play your world

You should now see your geometry moving along the path you defined.

--Jimbobjames

Replace the code in SimpleBall.h with this. I simply added the PathController class:

#ifndef SimpleBall_h
#define SimpleBall_h
 
 
#include "C4Application.h"
#include "C4Input.h"
#include "C4Interface.h"
#include "C4World.h"
#include "C4Particles.h"
 
 
// Every application/game module needs to declare a function called ConstructApplication()
// exactly as follows. (It must be declared extern "C", and it must include the tag C4MODULEEXPORT.)
// The engine looks for this function in the DLL and calls it to construct an instance of 
// the subclass of the Application class that the application/game module defines.
 
extern "C"
{
	C4MODULEEXPORT C4::Application *ConstructApplication(void);
}
 
 
namespace C4
{
	// These are action types used to define action bindings in the
	// Input Manager. If the four-character code for an action is
	// 'abcd', then any input control (there can be more than one)
	// bound to %abcd triggers the associated action.
 
	enum
	{
		kActionForward			= 'frwd',
		kActionBackward			= 'bkwd',
		kActionLeft				= 'left',
		kActionRight			= 'rght',
		kActionUp				= 'jump',
		kActionDown				= 'down'
	};
 
 
	// Model types are associated with a model resource using the ModelRegistration
	// class. Models are registered with the engine in the Game constructor.
 
	enum
	{
		kModelBall				= 'ball'
	};
 
 
	// New controller types are registered with the engine in the Game constructor.
 
	enum
	{
		kControllerBall			= 'ball',
		kControllerPath			= 'path'
	};
 
 
	// This is the type of our custom particle system.
 
	enum
	{
		kParticleSystemSpark	= 'sprk'
	};
 
 
	// New locator types are registered with the engine in the Game constructor.
	// The 'spec' locator is used to specify where the spectator camera should
	// be positioned when a world is loaded.
 
	enum
	{
		kLocatorSpectator		= 'spec'
	};
 
 
	// An Action object represents an input action that can be triggered by
	// some input control, such as a key on the keyboard or a button on a joystick.
	// The Begin() and End() methods are called when the button is pressed and
	// released, respectively. Actions are registered with the Input Manager when
	// the Game class is constructed.
 
	class MovementAction : public Action
	{
		private:
 
			unsigned long	movementFlag;
 
		public:
 
			MovementAction(unsigned long type, unsigned long flag);
			~MovementAction();
 
			void Begin(void);
			void End(void);
	};
 
 
	// Controllers are used to control anything that moves in the world.
	// New types of controllers defined by the application/game module are
	// registered with the engine when the Game class is constructed.
	//
	// The BallController inherits from the built-in rigid body controller, 
	// which handles the ball's motion and collision detection. We are only
	// adding a little bit of functionality that causes a particle system 
	// to be created when a ball hits another ball. 
 
	class BallController : public RigidBodyController
	{
		private:
 
			BallController(const BallController& ballController);  
 
			Controller *Replicate(void) const; 
 
		public: 
 
			BallController();
			~BallController();
 
			static bool ValidNode(const Node *node);
 
			void Preprocess(void);
 
			RigidBodyStatus HandleNewRigidBodyContact(const RigidBodyContact *contact, RigidBodyController *contactBody);
	};
 
	//Path Controller
 
	//added for Custom Path Controller
 
class PathController : public Controller 
{
   private:
 
        float            pathRate;             // In radians per millisecond
        String<30>       pathName;            //path name - default is "Enemy Path"
        Transform4D        originalTransform;    // The target's original transform
        Vector3D        originalView;        // The direction turret is facing before updating each frame
        bool            lookedForEnemyPath;
        float            crashCount;
        bool            planeHealthAdded;
        long            runCount, loopCount;
        Array<Point3D>            pathPoint;
        int                pointCount;
        int                pathPointCount;
        QuadWidget        *planeHealth;//to show damage to plane
        PathController(const PathController& pathController);
        Controller *Replicate(void) const;
 
   public:
 
        int damage;//damage to Model
        PathController();
        PathController(float rate);
        ~PathController();
 
        float GetPathRate(void) const
        {
            return (pathRate);
        }
 
        void SetPathRate(float rate)
        {
            pathRate = rate;
        }
 
        static bool ValidNode(const Node *node);
 
        // Serialization functions
        unsigned long GetPackSize(unsigned long packFlags) const;
        void Pack(Packer& data, unsigned long packFlags) const;
        void Unpack(Unpacker& data, unsigned long unpackFlags);
 
        // User interface functions
        long GetSettingCount(void) const;
        Setting *GetSetting(long index) const;
        void SetSetting(const Setting *setting);
 
        void Preprocess(void);
 
        // The function that moves the target node
        void Move(void);
 
		void LookForEnemyPath(void);
};
 
	// The SparkSystem class implements a simple particle system that
	// creates a small burst of sparks.
 
	class SparkSystem : public LineParticleSystem
	{
		// This friend declaration allows the particle system registration object
		// to construct a SparkSystem object using the private default constructor.
 
		friend class ParticleSystemReg<SparkSystem>;
 
		private:
 
			enum
			{
				kMaxParticleCount	= 100
			};
 
			long				sparkCount;
 
			// This is where information about each particle is stored.
 
			ParticlePool<>		particlePool;
			Particle			particleArray[kMaxParticleCount];
 
			SparkSystem();
 
			bool CalculateBoundingSphere(BoundingSphere *sphere) const override;
 
		public:
 
			SparkSystem(long count);
			~SparkSystem();
 
			void Preprocess(void);
			bool AnimateParticles(void);
	};
 
 
	// The StartWindow class is a simple example of a window that handles a button click.
	// We add the Singleton base class so that a pointer to the window can be tracked easily.
 
	class StartWindow : public Window, public Singleton<StartWindow>
	{
		private:
 
			WidgetObserver<StartWindow>		startButtonObserver;
 
			void HandleStartButtonEvent(Widget *widget, const WidgetEventData *eventData);
 
		public:
 
			StartWindow();
			~StartWindow();
 
			void Preprocess(void);
	};
 
 
	// The application/game module will usually define a subclass of the World
	// class so that extra information can be associated with the current world.
	// In this case, an instance of the SpectatorCamera class is included
	// with the world. A new instance of this World subclass should be returned
	// when the Game::ConstructWorld() function is called (see below).
 
	class GameWorld : public World
	{
		private:
 
			SpectatorCamera		spectatorCamera;
 
		public:
 
			GameWorld(const char *name);
			~GameWorld();
 
			SpectatorCamera *GetSpectatorCamera(void)
			{
				return (&spectatorCamera);
			}
 
			WorldResult Preprocess(void);
			void Render(void);
	};
 
 
	// Every application/game module needs to define a subclass of the Application
	// class to serve as the primary interface with the engine. This subclass is
	// constructed and returned to the engine in the ConstructApplication() function.
	// There should be only one instance of this class, so it inherits from the
	// Singleton template. A pointer to the Game instance is declared below.
 
	class Game : public Application, public Singleton<Game>
	{
		private:
 
			DisplayEventHandler				displayEventHandler;
 
			ModelRegistration				ballModelReg;
			ControllerReg<BallController>	controllerReg;
			ControllerReg<PathController>	pathControllerReg;//Path Controller
			ParticleSystemReg<SparkSystem>	sparkSystemReg;
			LocatorRegistration				locatorReg;
 
			InputMgr::KeyProc				*prevEscapeProc;
			void							*prevEscapeData;
 
			MovementAction					*forwardAction;
			MovementAction					*backwardAction;
			MovementAction					*leftAction;
			MovementAction					*rightAction;
			MovementAction					*upAction;
			MovementAction					*downAction;
 
			static World *ConstructWorld(const char *name, void *data);
 
			static void HandleDisplayEvent(const DisplayEventData *eventData, void *data);
 
			static void EscapeProc(void *data);
 
		public:
 
			Game();
			~Game();
	};
 
	// This is a pointer to the one instance of the Game class through which
	// any other part of the application/game module can access it.
 
	extern Game *TheGame;
 
	// This is a pointer to the start window. We only keep this around so that
	// we can delete the window before exiting if it's still on the screen.
 
	extern StartWindow *TheStartWindow;
}
 
#endif

And replace the code in SimpleBall.cpp with this. I simply added the implementation for the PathController class:

#include "SimpleBall.h"
#include "C4Configuration.h"
 
using namespace C4;
 
 
// This is the definition of the pointer to the Game class singleton.
// It should be initialized to nullptr, and its value will be set by
// the Game class constructor.
 
Game *C4::TheGame = nullptr;
 
// This is the definition of the pointer to the StartWindow class singleton.
// It should be initialized to nullptr, and its value will be set by
// the StartWindow class constructor.
 
StartWindow *C4::TheStartWindow = nullptr;
 
 
C4::Application *ConstructApplication(void)
{
	// This function should simply return a pointer to a new instance of
	// the Application class. Normally, the application/game module will
	// define a subclass of the Application class (in this case, the
	// Game class) and return a pointer to a new instance of that type.
 
	// This function is called exactly one time right after the
	// application/game module DLL is loaded by the engine. The returned
	// class is destroyed via the virtual destructor of the Application
	// class right before the application/game module DLL is unloaded.
 
	return (new Game);
}
 
 
MovementAction::MovementAction(unsigned long type, unsigned long flag) : Action(type)
{
	// Each instance of the MovementAction class represents a movement
	// in a single direction, as indicated by the flag parameter.
	// All of the MovementAction instances are constructed in the
	// Game class constructor.
 
	movementFlag = flag;
}
 
MovementAction::~MovementAction()
{
}
 
void MovementAction::Begin(void)
{
	// This function is called when the input control associated with this
	// particular action is activated (e.g., a key was pressed).
 
	GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());
	if (world)
	{
		// If there's currently a world loaded, apply the movement to
		// the spectator camera.
 
		SpectatorCamera *camera = world->GetSpectatorCamera();
		camera->SetSpectatorFlags(camera->GetSpectatorFlags() | movementFlag);
	}
}
 
void MovementAction::End(void)
{
	// This function is called when the input control associated with this
	// particular action is deactivated (e.g., a key was released).
 
	GameWorld *world = static_cast<GameWorld *>(TheWorldMgr->GetWorld());
	if (world)
	{
		// If there's currently a world loaded, remove the movement from
		// the spectator camera.
 
		SpectatorCamera *camera = world->GetSpectatorCamera();
		camera->SetSpectatorFlags(camera->GetSpectatorFlags() & ~movementFlag);
	}
}
 
 
BallController::BallController() : RigidBodyController(kControllerBall)
{
	// This constructor is only called when a new ball model is created.
}
 
BallController::BallController(const BallController& ballController) : RigidBodyController(ballController)
{
	// This constructor is called when a ball controller is cloned.
}
 
BallController::~BallController()
{
}
 
Controller *BallController::Replicate(void) const
{
	return (new BallController(*this));
}
 
bool BallController::ValidNode(const Node *node) 
{
	// This function is called by the engine to determine whether 
	// this particular type of controller can control the particular 
	// node passed in through the node parameter. This function should
	// return true if it can control the node, and otherwise it should
	// return false. In this case, the controller can only be applied
	// to model nodes.
 
	return (node->GetNodeType() == kNodeModel); 
}
 
void BallController::Preprocess(void)
{ 
	// This function is called once before the target node is ever
	// rendered or moved. The base class Preprocess() function should
	// always be called first, and then the subclass can do whatever
	// preprocessing it needs to do. In this case, we set the ball's
	// coefficient of restitution.
 
	RigidBodyController::Preprocess();
 
	SetRestitutionCoefficient(0.95F);
}
 
RigidBodyStatus BallController::HandleNewRigidBodyContact(const RigidBodyContact *contact, RigidBodyController *contactBody)
{
	// This function is called when the ball makes contact with another rigid body.
 
	if (contactBody->GetControllerType() == kControllerBall) 
	{
		// Add a sound effect and some sparks to the zone containing the ball.
		// Transform the contact point into the zone's local coordinate system.
 
		Node *node = GetTargetNode();
		Zone *zone = node->GetOwningZone();
		Point3D zonePosition = node->GetNodeTransform() * contact->GetContactPosition();
 
		OmniSource *source = new OmniSource("model/Ball", 50.0F);
		source->SetNodePosition(zonePosition);
		zone->AddNewSubnode(source);
 
		SparkSystem *sparks = new SparkSystem(20);
		sparks->SetNodePosition(zonePosition);
		zone->AddNewSubnode(sparks);
	}
 
	return (kRigidBodyUnchanged);
}
 
 
SparkSystem::SparkSystem(long count) :
 
		// Initialize the base class for line particles
		// and tell it where the particle pool is.
		LineParticleSystem(kParticleSystemSpark, &particlePool),
 
		// Initialize the particle pool by telling it where the array
		// of Particle structs is and how big it is.
		particlePool(kMaxParticleCount, particleArray)
{
	// The SparkEffect node creates a small burst of sparks and
	// then self-destructs when all of them have burned out.
 
	sparkCount = count;
	SetParticleSystemFlags(kParticleSystemSelfDestruct);
}
 
SparkSystem::SparkSystem() :
 
		// Initialize the base class for line particles
		// and tell it where the particle pool is.
		LineParticleSystem(kParticleSystemSpark, &particlePool),
 
		// Initialize the particle pool by telling it where the array
		// of Particle structs is and how big it is.
		particlePool(kMaxParticleCount, particleArray)
{
	// This constructor gets used when the particle effect is being loaded from
	// a saved game. In this case, we don't need to initialize anything.
}
 
SparkSystem::~SparkSystem()
{
}
 
bool SparkSystem::CalculateBoundingSphere(BoundingSphere *sphere) const
{
	// Just return a sphere that's big enough to always enclose
	// all of the particles. This is in local coordinates.
 
	sphere->SetCenter(0.0F, 0.0F, 0.0F);
	sphere->SetRadius(20.0F);
	return (true);
}
 
void SparkSystem::Preprocess(void)
{
	// This function creates the spark particles.
 
	// Always call the base class Preprocess() function.
	LineParticleSystem::Preprocess();
 
	// If there's already a particle in the system, then the effect was loaded
	// from a saved game. We only create new particles if the system is empty.
 
	if (!GetFirstParticle())
	{
		// Calculate the world-space center.
		Point3D center = GetSuperNode()->GetWorldTransform() * GetNodePosition();
		const ConstVector2D *trig = Math::GetTrigTable();
 
		long count = sparkCount;
		for (machine a = 0; a < count; a++)
		{
			// Grab a new unused particle from the pool.
			Particle *p = particlePool.NewParticle();
			if (!p) break;
 
			p->emitTime = 0;						// Particle appears right away.
			p->lifeTime = 500 + Math::Random(750);	// Particle lives 500-1250 milliseconds.
			p->radius = 0.025F;						// The radius is 25 mm.
			p->color.Set(1.0F, 1.0F, 0.1F, 1.0F);	// It's yellow.
			p->orientation = 0;						// This doesn't matter for line particles.
			p->position = center;					// It starts at the effect's center.
 
			// Calculate a random velocity in a random direction.
			float speed = Math::RandomFloat(0.004F);
			Vector2D csp = trig[Math::Random(128)] * speed;
			const Vector2D& cst = trig[Math::Random(256)];
			p->velocity.Set(cst.x * csp.y, cst.y * csp.y, csp.x);
 
			// Add the particle to the particle system.
			AddParticle(p);
		}
	}
}
 
bool SparkSystem::AnimateParticles(void)
{
	// This function is called once per frame to move the particles.
 
	// Times are in milliseconds.
	long dt = TheTimeMgr->GetDeltaTime();
	float fdt = TheTimeMgr->GetFloatDeltaTime();
 
	long particleCount = 0;
	Particle *particle = GetFirstParticle();
	while (particle)
	{
		// Get the next particle now in case the current one is removed from the system.
		Particle *next = particle->nextParticle;
 
		long life = (particle->lifeTime -= dt);
		if (life > 0)
		{
			// Update velocity with gravity.
			particle->velocity.z += K::gravity * fdt;
 
			// Move the particle and see if it hit the floor plane at z=0.
			float z1 = particle->position.z - particle->radius;
			particle->position += particle->velocity * fdt;
			float z2 = particle->position.z - particle->radius;
			if (z1 * z2 <= 0.0F)
			{
				// The particle hit the floor, so reflect its velocity and remove some energy.
				particle->position.z = 0.05F - z2;
				particle->velocity.z *= -0.5F;
			}
 
			// If the particle is nearing the end of its life, fade it out.
			if (life < 100) particle->color.alpha = (float) life * 0.01F;
			particleCount++;
		}
		else
		{
			// Particle burned out.
			FreeParticle(particle);
		}
 
		particle = next;
	}
 
	// If no particles survived, then return false to indicate that it's time to self-destruct.
	return (particleCount != 0);
}
 
 
// The StartWindow constructor initializes the Window base class with the name of the panel
// resource to load. The startButtonObserver member is initialized with the functions that
// is called when the "Start" push button in the window posts an activate event.
 
StartWindow::StartWindow() :
		Window("panel/SimpleBall"),
		Singleton<StartWindow>(TheStartWindow),
		startButtonObserver(this, &StartWindow::HandleStartButtonEvent)
{
}
 
StartWindow::~StartWindow()
{
}
 
void StartWindow::Preprocess(void)
{
	// We must call the Window base class Preprocess() function first to initialize the
	// internal structures that are used to search for widgets.
 
	Window::Preprocess();
 
	// Find the push button widget named "Start" and assign our observer to it.
 
	Widget *button = FindWidget("Start");
	button->SetObserver(&startButtonObserver);
}
 
void StartWindow::HandleStartButtonEvent(Widget *widget, const WidgetEventData *eventData)
{
	// This function is called whenever the "Start" push button posts an event.
 
	if (eventData->eventType == kEventWidgetActivate)
	{
		// If the widget was activated, then the user clicked the push button.
		// Delete the start window and load a world to play.
 
		delete this;
 
		TheWorldMgr->LoadWorld("world/SimpleBall");
	}
}
 
 
GameWorld::GameWorld(const char *name) :
		World(name),
		spectatorCamera(2.0F, 1.0F, 0.3F)
{
	// This constructor is called when the Game::ConstructWorld() function is
	// called to create a new world class. The world hasn't actually been loaded
	// from disk yet when we get here.
}
 
GameWorld::~GameWorld()
{
}
 
WorldResult GameWorld::Preprocess(void)
{
	// The Preprocess() function is called after the world has been constructed.
	// We must always call the base class Preprocess() function first.
 
	WorldResult result = World::Preprocess();
	if (result != kWorldOkay) return (result);
 
	// The world is now completely loaded. We search for a locator node that
	// represents the spectator camera's starting position. It will have a locator
	// type of kLocatorSpectator. We only look in the root zone of the world.
 
	Zone *zone = GetRootNode();
	if (zone)
	{
		// Iterate through all of the markers in the zone.
 
		const Marker *marker = zone->GetFirstMarker();
		while (marker)
		{
			MarkerType type = marker->GetMarkerType();
			if (type == kMarkerLocator)
			{
				if (static_cast<const LocatorMarker *>(marker)->GetLocatorType() == kLocatorSpectator)
				{
					// The marker is of the correct type for the spectator camera.
					// So set the spectator camera's initial position and orientation.
 
					const Vector3D direction = marker->GetWorldTransform()[0];
					float azimuth = Atan(direction.y, direction.x);
					float altitude = Atan(direction.z, Sqrt(direction.x * direction.x + direction.y * direction.y));
 
					spectatorCamera.SetCameraAzimuth(azimuth);
					spectatorCamera.SetCameraAltitude(altitude);
					spectatorCamera.SetNodePosition(marker->GetWorldPosition());
				}
			}
 
			// Get the next marker in the list.
 
			marker = marker->Next();
		}
	}
	else
	{
		spectatorCamera.SetNodePosition(Point3D(0.0F, 0.0F, 1.0F));
	}
 
	// Set the world's current camera to be our spectator camera.
	// The world will not render without a camera being set.
 
	SetCamera(&spectatorCamera);
 
	return (kWorldOkay);
}
 
void GameWorld::Render(void)
{
	// This function is called once per frame to render the world.
	// The subclass may do whatever it needs to before or after rendering,
	// but at some point must call World::Render().
 
	World::Render();
}
 
 
Game::Game() :
 
		// This is the constructor for the main application/game module class.
		// This class is constructed by the ConstructApplication() function,
		// which is called right after the application/game DLL is loaded by
		// the engine. There is only one instance of this class, so it inherits
		// from the Singleton template, which we initialize first.
 
		Singleton<Game>(TheGame),
 
		// The display event handler encapsulates a function that gets called
		// when the Display Manager changes something like the screen resolution.
 
		displayEventHandler(&HandleDisplayEvent),
 
		// A model registration represents a model that can be instanced.
		// This particular declaration associates the kModelBall type with the
		// model named "model/Ball.mdl". The fourth parameter tells the engine
		// to pre-cache the model resource, and the last parameter specifies
		// the default controller type to assign to models of type kModelBall.
 
		ballModelReg(kModelBall, "Bouncing Ball", "model/Ball", kModelPrecache, kControllerBall),
 
		// A controller registration tells the engine about an application-defined
		// type of controller and registers its name and validation function.
		// In this case, the name "Bouncing Ball" will appear in the list of
		// available controllers in the World Editor for any node that is determined
		// to be a valid target node by the BallController::ValidNode() function.
 
		controllerReg(kControllerBall, "Bouncing Ball"),
 
		pathControllerReg(kControllerPath, "Path"),
 
		// A particle system registration tells the engine about an application-defined
		// type of particle system and registers its name.
 
		sparkSystemReg(kParticleSystemSpark, "Sparks"),
 
		// Locator markers are registered so that the World Editor
		// can display their names in the Get Info dialog box.
 
		locatorReg(kLocatorSpectator, "Spectator Camera")
{
	// This installs an event handler for display events. This is only
	// necessary if we need to perform some action in response to
	// display events for some reason.
 
	TheDisplayMgr->InstallDisplayEventHandler(&displayEventHandler);
 
	// This sets the function that is called when the user hits the
	// escape key during gameplay. We save the old function so that
	// it can be restored when the game DLL is unloaded.
 
	prevEscapeProc = TheInputMgr->GetEscapeProc();
	prevEscapeData = TheInputMgr->GetEscapeData();
	TheInputMgr->SetEscapeProc(&EscapeProc, this);
 
	// This registers our world class constructor with the World Manager.
	// We only need to do this if we have defined a subclass of the World
	// class that holds extra information.
 
	TheWorldMgr->SetWorldConstructor(&ConstructWorld);
 
	// These create the movement actions that are used to fly the
	// spectator camera around.
 
	forwardAction = new MovementAction(kActionForward, kSpectatorMoveForward);
	backwardAction = new MovementAction(kActionBackward, kSpectatorMoveBackward);
	leftAction = new MovementAction(kActionLeft, kSpectatorMoveLeft);
	rightAction = new MovementAction(kActionRight, kSpectatorMoveRight);
	upAction = new MovementAction(kActionUp, kSpectatorMoveUp);
	downAction = new MovementAction(kActionDown, kSpectatorMoveDown);
 
	// These register our new actions with the Input Manager.
 
	TheInputMgr->AddAction(forwardAction);
	TheInputMgr->AddAction(backwardAction);
	TheInputMgr->AddAction(leftAction);
	TheInputMgr->AddAction(rightAction);
	TheInputMgr->AddAction(upAction);
	TheInputMgr->AddAction(downAction);
 
	// Let the Interface Manager determine when to change input devices to gameplay mode.
 
	TheInterfaceMgr->SetInputManagementMode(kInputManagementAutomatic);
 
	// Put the Message Manager in single-player mode.
 
	TheMessageMgr->BeginSinglePlayerGame();
 
	// Create the start window and tell the Interface Manager to display it.
 
	//TheInterfaceMgr->AddWidget(new StartWindow);
}
 
Game::~Game()
{
	// When the game DLL is about to be unloaded, this destructor is called.
 
	TheWorldMgr->UnloadWorld();
	TheWorldMgr->SetWorldConstructor(nullptr);
 
	// If the start window exists, delete it. We need to do this here, instead of letting the
	// Interface Manager clean it up, because the destructor code for the window is in this DLL.
 
	delete TheStartWindow;
 
	// Tell the Message Manager to clean up.
 
	TheMessageMgr->EndGame();
 
	delete downAction;
	delete upAction;
	delete rightAction;
	delete leftAction;
	delete backwardAction;
	delete forwardAction;
 
	// Restore the previous escape key handling function.
 
	TheInputMgr->SetEscapeProc(prevEscapeProc, prevEscapeData);
}
 
World *Game::ConstructWorld(const char *name, void *data)
{
	// This function is called when a new world is being loaded. It should
	// return a pointer to a newly constructed subclass of the World class.
 
	return (new GameWorld(name));
}
 
void Game::HandleDisplayEvent(const DisplayEventData *eventData, void *data)
{
	// This function is called when a display event occurs (because we
	// registered it in the Game constructor).
 
	if (eventData->eventType == kEventDisplayChange)
	{
		// The screen resolution has changed. Handle accordingly.
	}
}
 
void Game::EscapeProc(void *data)
{
	// This function is called when the user hits the escape key in gameplay
	// mode because we registered it using the InputMgr::SetEscapeProc() function.
}
 
//Adding Custom Path Controller
 
PathController::PathController() : Controller(kControllerPath)
{
	// Set a default value for the Path rate
	pathRate = 1.0f;//Corresponds to the speed of object along the path
	pathName = "EnemyPath";//the default name of the path this object should follow
} 
 
PathController::~PathController()
{
	if(planeHealth != nullptr)
		delete planeHealth;
}
 
PathController::PathController(const PathController& pathController) : Controller(pathController)
{
	pathRate = pathController.pathRate;
	pathName = "EnemyPath";
}
 
Controller *PathController::Replicate(void) const
{
	return (new PathController(*this));
}
 
unsigned long PathController::GetPackSize(unsigned long packFlags) const
{
	unsigned long size = Controller::GetPackSize(packFlags);
	// Add enough space to hold a float for the path rate, the path name as well as
	// the target node's original transform
	unsigned long length = size + 8 + sizeof(pathName) + sizeof(Transform4D) - sizeof(float);
	return (length);
}
 
void PathController::Pack(Packer& data, unsigned long packFlags) const
{
	Controller::Pack(data, packFlags);
 
	// Write the Path rate
	data << pathRate;
 
	//Write the Path Name
	data << pathName;
 
	// Write the original transform
	data << originalTransform;
}
 
void PathController::Unpack(Unpacker& data, unsigned long unpackFlags)
{
	Controller::Unpack(data, unpackFlags);
 
	// Read the Path rate
	data >> pathRate;
 
	//read the Path Name
	data >> pathName;
 
	// Read the original transform
	data >> originalTransform;
}
 
long PathController::GetSettingCount(void) const
{
	// There are two settings
	return (2);
}
 
Setting *PathController::GetSetting(long index) const
{
	// Is it asking for the first setting?
	if (index == 0)
	{
		// Yes, return a new text setting and set its value
		return (new TextSetting('rate', Text::FloatToString(pathRate),
			"Speed on Path", 7, &EditTextWidget::FloatNumberFilter));
	} // Or is it asking for the second setting?
	else if (index == 1)
	{
		// Yes, return a new text setting and set its value
		return (new TextSetting('pnme', pathName,
			"Path Name", 20));
	}
 
	return (nullptr);
}
 
void PathController::SetSetting(const Setting *setting)
{
	// Are we setting the Path rate?
	if (setting->GetSettingIdentifier() == 'rate')
	{
		// Yes, grab the value from the setting
		const char *text = static_cast<const TextSetting *>(setting)->GetText();
		pathRate = Text::StringToFloat(text);
	} //or are we setting the path name?
	else if (setting->GetSettingIdentifier() == 'pnme')
	{
		// Yes, grab the value from the setting
		const char *text = static_cast<const TextSetting *>(setting)->GetText();
		pathName = text;
	}
}
 
bool PathController::ValidNode(const Node *node)
{
	return (node->GetNodeType() == kNodeModel || node->GetNodeType() == kNodeGeometry);
}
 
void PathController::Preprocess(void)
{
	Controller::Preprocess();
 
	planeHealth = nullptr;
	damage = 0;
	crashCount = 0.0F;
	planeHealthAdded = false;
	// Grab the original transform of the target node
	const Node *target = GetTargetNode();
	originalTransform = target->GetNodeTransform();
	pointCount = 0;
	pathPointCount = 0;
	lookedForEnemyPath = false;
	Point3D originalDirection = Point3D(-originalTransform[1].x,-originalTransform[1].y,-originalTransform[1].z);
	originalView = (originalDirection).Normalize();
	// Set the kGeometryDynamic flag for any geometry nodes
	const Node *node = target;
	do
	{
		if (node->GetNodeType() == kNodeGeometry)
		{
			// Node is a geometry, so grab its object
			GeometryObject *object = static_cast<const Geometry *>(node)->GetObject();
 
			// Set the kGeometryDynamic flag
			object->SetGeometryFlags(object->GetGeometryFlags() | kGeometryDynamic);
		}
		// Iterate through entire subtree
		node = target->GetNextNode(node);
	} while (node);
}
 
void PathController::Move(void)
{
	Node *target = GetTargetNode();
	Transform4D translation;
	Point3D myTranslation;
 
	//Look for the enemy path once only
	if(!lookedForEnemyPath)
		LookForEnemyPath();
 
	myTranslation.x = pathPoint[pathPointCount].x;
	myTranslation.y = pathPoint[pathPointCount].y;
	myTranslation.z = pathPoint[pathPointCount].z - crashCount;//plane falls down if crashing
	pathPointCount++;
	translation.SetIdentity();
	translation.SetTranslation(myTranslation);
	if (pathPointCount >= pointCount)
		pathPointCount = 0;
	target->SetNodeTransform(originalTransform * translation);
 
	//Orientation - face in the direction of motion
	if (pathPointCount > 0)
	{
		Vector3D view = (pathPoint[pathPointCount] - pathPoint[pathPointCount-1]);
		//view.z -= crashCount/20.0f;//nose dives down if crashing
		view = view.Normalize();
		float x = view.x;
		float y = view.y;
		float f = InverseSqrt(x * x + y * y);
		Vector3D right(y * f, -x * f, 0.0F);
		Vector3D down = view % right;
		float bankAngle = 0.0f;//temp - replace by variance with path curvature
		right = right.RotateAboutAxis(bankAngle + crashCount, view);//spins if crashing
		down = down.RotateAboutAxis(bankAngle + crashCount, view);//spins if crashing
		target->SetNodeMatrix3D(view, -right, -down);
	}
 
	if ((GetTargetNode()->GetNodePosition()).z <= 0.0f)
		delete target;
	else
		target->Invalidate();
}
 
void PathController::LookForEnemyPath(void) {
 
	float x=0.0f, y=0.0f, z=0.0f, distance = 0.0f;
	const float EPSILON = 0.1f;
 
	if (pathRate < EPSILON)
		pathRate = EPSILON;
 
	const int MAX_ITERATIONS = 10;
	const char * stringOutput = " ";
 
	if (pathRate < EPSILON)
		pathRate = EPSILON + 0.1;
 
	Node *root = TheWorldMgr->GetWorld()->GetRootNode(); // start at the root
	Node *thisnode = root;
	PathMarker *pathMarker = nullptr;
	do
	{
		stringOutput = " ";
		if (thisnode->GetNodeName() != NULL)
			stringOutput = thisnode->GetNodeName();
 
		if (pathName == stringOutput) { //Call the Path Node "EnemyPath" in the Editor
			Marker *marker = static_cast<Marker *>(thisnode);
			if (marker->GetMarkerType() == kMarkerPath)
				pathMarker = static_cast<PathMarker *>(marker);
			const Path *path = pathMarker->GetPath();
			const PathComponent *component = path->GetFirstPathComponent();
			while (component)
			{
				pathPoint.AddElement(component->GetPosition(0));
				pointCount++;
				float t = 0.5f, t_old = 0.0f, speed = 0.0f, distance, x, y, z;//0.03125F;
				while (t < 1.0f && pointCount < 100000)//limit max length of path
				{
					float diff = 0.0f;
					bool found = false;
					int iterations = 0;
					pathPoint.AddElement(component->GetPosition(t));
					//tangent.AddElement(Vector3D(pathPoint[pointCount] - pathPoint[pointCount-1]));
					do {
						pathPoint[pointCount] = component->GetPosition(t);
						Vector3D tangent = (pathPoint[pointCount] - pathPoint[pointCount-1]);
						//tangent[pointCount-1] = Vector3D((pathPoint[pointCount] - pathPoint[pointCount-1]));
						x = tangent.x;
						y = tangent.y;
						z = tangent.z;
						distance = Sqrt(x * x + y * y + z * z);
						speed = distance / (t - t_old);
						//length += distance;
						diff = distance - pathRate;//targetDistance;
						//Using Newton's method to converge on t for target distance along path
						if (fabs(diff) > EPSILON && iterations++ < MAX_ITERATIONS)
						{
							t -= diff/speed;
							if (fabs(t) >= 1.0f)
								t = t_old + 0.05f;
						}
						else
						{
							if (iterations >= MAX_ITERATIONS) //did'nt converge
							{
								t = t_old + t_old/(float)pointCount;
								if (fabs(t) > 1.0f)
									t = 1.0f;
								pathPoint[pointCount] = component->GetPosition(t);
								//tangent[pointCount-1] = Vector3D(pathPoint[pointCount] - pathPoint[pointCount-1]);
							}
							iterations = 0;
							found = true;
							t_old = t;
							t += t/(float)pointCount;//2.0f;
							//length += distance;
							pointCount++;
						}
					} while (!found);
				}
				component = component->Next();
				lookedForEnemyPath = true;//found enemyPath at this point
			}
		}
		thisnode = root->GetNextNode(thisnode);
	} while (thisnode && !lookedForEnemyPath);
	lookedForEnemyPath = true;//After checking all node set to true to avoid checking each frame!!
}
Personal tools