Game Development Community

Creating a new enemy

by Vishal Bhanderi · in Torque X Platformer Kit · 09/18/2007 (12:59 am) · 12 replies

Guys i got a really simple problem. My firingAIcontroller needs to know the Position values of the actor that posses it (FiringActorComponent). Just can't find the right pointer, without creating a new variable and passing it on; when FiringActorComponent registers. Any advice on where it is?

Sorry about the really silly question :)

Thanks in Advance.

#1
09/19/2007 (6:24 pm)
This might be a silly question, but why does the controller need to know the position of anything?
#2
09/20/2007 (12:54 am)
It's so the controller knows where the actor is. Can't find the Position variables otherwise. Here's the shooting AI i've come up with.

New FiringAIController

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

using GarageGames.Torque.Core;
using GarageGames.Torque.T2D;
using GarageGames.Torque.XNA;
using GarageGames.Torque.PlatformerFramework;
using GarageGames.Torque.SceneGraph;
using GarageGames.Torque.Sim;
using GarageGames.Torque.MathUtil;

namespace GarageGames.Torque.PlatformerDemo
{
    /// <summary>
    /// This Controller defines the simple AI used by drills. They basically just turn around when they recieve an ActorHitWall notification.
    /// This happens either from hitting an actual wall, or from entering a DrillTurnAroundTriggerComponent.
    /// </summary>
    class FiringAIController : ActorAIController
    {
        //======================================================
        #region Public methods 

        public override void ActorSpawned(ActorComponent actor)
        {
            base.ActorSpawned(actor);
                
            // make sure all actors are idle when they spawn
            CurrentState = FSM.Instance.GetState(this, "idle");
        }

        public void ShootRight()
        {
            CurrentState = FSM.Instance.GetState(this, "shootRight");
        }

        public void ShootLeft()
        {
            CurrentState = FSM.Instance.GetState(this, "shootLeft");
        }

        public void Idle()
        {
            CurrentState = FSM.Instance.GetState(this, "idle");
        }
        #endregion

        //======================================================
        #region Private, protected, internal methods

        protected override void _registerAIStates()
        {
            base._registerAIStates();

            FSM.Instance.RegisterState<ShootLeftState>(this, "shootLeft");
            FSM.Instance.RegisterState<ShootRightState>(this, "shootRight");
            FSM.Instance.RegisterState<IdleState>(this, "idle");

            CurrentState = FSM.Instance.GetState(this, "idle");
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        #endregion

        //======================================================
        #region Actor AI states

        protected class ShootRightState : AIState
        {
            public override void Update(ActorAIController AI)
            {

            }

            public override string Execute(IFSMObject obj)
            {
                FiringAIController firingAI = obj as FiringAIController;

                if (firingAI == null)
                    return null;

                foreach (ActorComponent actor in firingAI.Movers)
                    actor.Actor.FlipX = false;

                return null;
            }
        }

        protected class ShootLeftState : AIState
        {
            public override void Update(ActorAIController AI){}

            public override string Execute(IFSMObject obj)
            {
                FiringAIController firingAI = obj as FiringAIController;

                if (firingAI == null)
                    return null;

                foreach (ActorComponent actor in firingAI.Movers)
                    actor.Actor.FlipX = true;

                return null;
            }
        }

        protected class IdleState : AIState
        {
            public override void Update(ActorAIController AI) { }

            public override string Execute(IFSMObject obj)
            {
                return null;
            }
        }

        #endregion
    }
}
#3
09/20/2007 (12:54 am)
FiringActorComponent

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;

using GarageGames.Torque.Core;
using GarageGames.Torque.T2D;

using GarageGames.Torque.PlatformerFramework;

namespace GarageGames.Torque.PlatformerDemo
{
    /// <summary>
    /// This component mainly just defines the how the firing component works. It also makes sure that if a component
    /// was created from a spawn point that the spawn point knows not to respawn this actor after it dies.
    /// </summary>
    [TorqueXmlSchemaType]
    class FiringActorComponent : ActorComponent
    {
        //======================================================
        #region Private, protected, internal methods

        protected override void _postUpdate(float elapsed)
        {
            base._postUpdate(elapsed);
        }

        private void Attack()
        {
            if (((T2DSceneGraph.Instance.Camera as T2DSceneCamera).Position.X) > Actor.Position.X)
            //badie are on the left
            {
                _firingAIController.ShootRight();
                Fire(1);
            }
            else
            //badie are on the right
            {
                _firingAIController.ShootLeft();
                Fire(-1);
            }            
        }

        protected float _getDistanceToPlayer()
        {
            // get the distance of this firing guy to from the player
            return Vector2.Distance(Actor.Position, (T2DSceneGraph.Instance.Camera as T2DSceneCamera).Position);
        }

        protected override void _preUpdate(float elapsed)
        {
 	        base._preUpdate(elapsed);
            _delayRemaining -= 1f;

            if ((_getDistanceToPlayer() < 50)
                && Math.Abs((T2DSceneGraph.Instance.Camera as T2DSceneCamera).Position.Y - Actor.Position.Y)
                < 20)
            {
                _attack = true;
                Attack();
            }
            else
            {
                _attack = false;
                _firingAIController.Idle();
            }
        }

        protected void Fire(float dir)
        {
            if (_delayRemaining <= 0 && _attack)
            {
                // Clone a particular projectile from the template.
                T2DSceneObject projectileObj = TorqueObjectDatabase.Instance.CloneObject<T2DSceneObject>("ProjectileTemplate");
                T2DSceneObject SceneObject = Actor;
                // Set the position based on the position of the object that's shooting.
                projectileObj.Position = SceneObject.Position;

                projectileObj.Layer = SceneObject.Layer + 1;

                // Get the projectile moving -- remember, Y coordinates are higher, the further down you go,
                //  so we'll send it off at -80 (-80 screen units per second).
                projectileObj.Physics.Velocity = new Vector2(80.0f * dir, 0.0f);

                // set up the collisions so that we can hit our target!
                projectileObj.Collision.CollidesWith = TorqueObjectDatabase.Instance.GetObjectType("Player");
                projectileObj.CollisionsEnabled = true;
                projectileObj.Collision.ResolveCollision = T2DPhysicsComponent.KillCollision;
                //projectileObj.Collision.OnCollision.

                TorqueObjectDatabase.Instance.Register(projectileObj);

                // limit the rate-of-fire.
                _delayRemaining = 10f;
            }
        }

        protected override void _die(float damage, T2DSceneObject sourceObject)
        {
            base._die(damage, sourceObject);

            // if this object was spawned, tell it's spawned object component not to recover
            if (_actor.TestObjectType(PlatformerData.SpawnedObjectType))
            {
                CheckpointSystemSpawnedObjectComponent spawned = _actor.Components.FindComponent<CheckpointSystemSpawnedObjectComponent>();

                if (spawned != null)
                    spawned.Recover = false;
            }
        }

        protected override bool _OnRegister(GarageGames.Torque.Core.TorqueObject owner)
        {
            if (!base._OnRegister(owner))
                return false;

            // give them a big enough ground Y threshold so they dont jitter
            _groundCheckYThreshold = 2.0f;

            // create a FiringAIController and possess this actor with it
            _firingAIController = new FiringAIController();
            _firingAIController.PossessMover(this);
            return true;
        }

        protected override void _resetActor()
        {
            base._resetActor();

            // reset the AI controller to run right
            if (Controller != null && Controller is FiringAIController)
                FSM.Instance.SetState(Controller as FiringAIController, "idle");
        }

        #endregion

        //======================================================
        #region Private, protected, internal fields

        protected bool _attack;
        protected FiringAIController _firingAIController;
        protected float _delayRemaining = 15f;

        #endregion
    }
}

It find's out where the FiringActorComponent is to determine which side it should look to fire.
#4
09/20/2007 (5:17 pm)
I see what you're saying. Originally the controllers were meant to just send commands one way with the possibility of receiving notifications from their actors. This allows them to be swapped out and lets you control more than one actor with a single controller all at once. The controller is meant to be the mind of the actor, so it should only ever need to respond to notifications from the actors themselves, which are meant to be the physical bodies that interact with the game world. In short, the actor should send a notification to its controller when it detects a target and the controller can then choose to shoot or move or whatever it wants to do.

You can see an example of an actor notifying a controller of worldly events on ActorController with the public notification methods (ActorSpawned, ActorLaneded, ActorDamaged, etc.). In this instance, your firing actor would have to be able to detect targets somehow and then call some sort of notification on it's controller, which would then handle any changes to it's behavior.

If you still want to have the controller find the position of it's possessed actors, there is a public property that stores a list of all the controlled MoveComponents on a MoveController called "Movers". You can iterate over this list for all the controlled movers and find the positions of their owners, if applicable. Like I said, though, this isn't the intended way to use controllers, but it should work fine anyway.
#5
09/21/2007 (12:33 pm)
O.k. Edited posts above to show my second attempt. Now I think it follows the controller rules. But my ActorAIstates looks a bit bare!
#6
09/21/2007 (3:09 pm)
Well it should, right? The actual behavioral logic is dead simple: whenever you see an enemy, shoot. Though I would probably change it to notify the controller that it detected an enemy with a ref to the enemy and a ref to itself. Then the controller would tell the actor whether to aim right or left, run, jump, or not to pursue/attack that enemy versus ignore the enemy or keep attacking another, previously existing target. The difference is pretty much contextual and it doesn't really matter all that much for a game. If it works, I'd move on to something else. :)
#7
09/22/2007 (1:11 am)
It works fine. Hopefully it doesn't cause any problems later on though. That's my main concern, coding methods, when it comes to explaining problems to others they may not get why come code is here and there. That's all. And if I'm lucky enough to submit to XBL then it needs to look professional. Yeh right! Doubt it will make it.

Anyone who wants a shooting AI, feel free to use it as the basis. Just let me know when the game comes out!
#8
09/24/2007 (1:05 pm)
O.K Thomas I have another problem. (probably a silly one). But i've copied the drill component's to kill my firingActor. It all works fine. I can jump on him and he does his kill animation. But the strange thing is that he continues to shoot, an invisible man. I can't see him, nor can he hurt me but the bullets keep on coming. This also happens with an unmodified version of the platformDemo. When you jump on the drill, the AI code will still be running. Is this a bug, part of the framework or do i need to modify my AI ?
#9
09/27/2007 (4:25 pm)
I think it didn't matter so I just ignored that. You can add an AI state called "dead" where the AI does nothing and then notify the AI when the actor dies (there's already a method in place for that). Or you could just detach the AI when the actor dies. A better solution would be to just not let the actor shoot if it's being killed. It makes the most sense from a logical standpoint: the brain wants to shoot, but the body can't at the moment.

Edit: By "didn't matter" I mean for the drills specifically.
#10
09/28/2007 (11:50 am)
I'm going to and an (if _alive) in the the fire method, but doesn't the AIactor, ActorCompnonent in the background cause a slow down. I'm worried that if I have loads of AI, the computer would be processing unnecessary code.
#11
09/28/2007 (3:07 pm)
If you're done with the actor you can delete it. Since the Actor should be the only thing with a reference to it's controller, it will be garbage collected as well when the actor is deleted. If you have other references to it you'll want to null them so the GC knows you're done with the controller.
#12
10/02/2007 (12:52 am)
Thanks, it works now. But I got another problem ( I got plenty of problems). Creating Objects on the fly. When I create a projectileComponent(now a separate object), I cannot get it work. It says: System.MissingMethodException was unhandled.

namespace GarageGames.Torque.PlatformerDemo
{
    [TorqueXmlSchemaType]
    public class ProjectileComponent : TorqueComponent, ITickObject
    {
         public ProjectileComponent(T2DSceneObject owner, T2DSceneObject target, int style)
        {
            _OnRegister(owner);
            creator = owner;
            SceneObject.Position = (T2DSceneGraph.Instance.Camera as T2DSceneCamera).Position;
            SceneObject.Layer = creator.Layer;
            SceneObject.Layer = creator.Layer + 1;
         }
        
         protected override bool _OnRegister(TorqueObject owner)
        {
            if (!base._OnRegister(owner) || !(owner is T2DSceneObject))
                return false;
            SceneObject.SetObjectType(PlatformerData.Projectile, true);
            SceneObject.Collision.CollidesWith = TorqueObjectDatabase.Instance.GetObjectType("Player");
            SceneObject.CollisionsEnabled = true;
            SceneObject.Collision.ResolveCollision = T2DPhysicsComponent.KillCollision;

            return true;
        }

    }
Thats the main part of the projectile. I was trying it so you create a bullet using:

ProjectileComponent projectile = new ProjectileComponent((T2DSceneObject)Actor, (T2DSceneGraph.Instance.Camera as T2DSceneCamera), 0);

I've tried using the other method shown everywhere else. Creating a shallow copy of the object (object.Clone()). But it doesn't allow for a constructor (as far as i know). Are they any other ways i can create objects ?

EDIT: I don't even know how the object can modify it's own properties. Owner doesn't have the Position values and stuff. Do i need to call the component of the owner ?

I got it to pass values now.