Object Instancing & Weapons Fire – SunBurn Component

Following on from the other ramp up components; this time lets look at a component which requires object instancing – Weapons Fire.

To make this component really flexible, lets take an object that we load through the SunBurn editor and then instantiate it. Using this method allows us to use a weapons fire object that can already have a component attached to it, for example the Spin Z Slowly component previously created could be quite appealing on an object that isn’t spherical.

Create the Shell Component

Starting off with a basic Shell Component to add our logic in to; we are going to only trigger this even on each key press, not limiting the number of times it can be pressed:

[Serializable]
public class Component_Trigger_FireWepon : BaseComponentManualSerialization<ISceneEntity>
{
    bool xDown = false;

    public override void OnUpdate(GameTime gameTime)
    {
        KeyboardState keyboard = Keyboard.GetState();

        // Cast the Object the component is attached to, to a scene object SceneObject sceneobject = (SceneObject)ParentObject;

        // Move Forward if (keyboard.IsKeyDown(Keys.X) && !xDown)
        {
            xDown = true;             // Component Logic Here }
        else {
            if (keyboard.IsKeyUp(Keys.X))
            {
                xDown = false;
            }
        }
    }
}

Copy It Up

As we are creating an instance of another object in this Component we need to find it through the Object Manager. Initially we create 2 new SceneObjects, Fire – which will store the new Weapon Fire object and the copyBase which will store the result of the template object we want to use.

We reference the ActiveSceneInterface and use the Find method of the ObjectManager (there is no error handling & yes this does fail if it can’t find an Object in the Scene named Fire, you can add that easily if you want but I think it’s fairly easy to make sure you have a copy of your template in the scene some where :-)

After the object is copied we remove any current force applied to the object, just-in-case, probably not necessary but hey, I put it in there from the start.

SceneObject fire = new SceneObject();
SceneObject copyBase;
SceneInterface.ActiveSceneInterface.ObjectManager.Find("Fire", out copyBase);
fire = (SceneObject)copyBase.Clone();
fire.CollisionMove.RemoveForces();

 

A  Quaternion by any other Name

As we are using an existing object as a template, many of the properties of the object are taken care of for us. Due to this convenience, we just need to make use of the Decompose method of the World Matrix to split out the Scale, Quaternion (Rotation) & Location of the Template Object. Honestly not too interested in the rotation or the location of the template but we need the Scale for latter.

The Decompose method is very cool when you think about it, being able to break apart the Transformation Matrix and provide all of the component parts is a bit of heavy lifting in the Maths world & I for one am very glad that I didn’t need to do any of it.

Quaternion fireQ = new Quaternion();
Vector3 fireSpot = new Vector3();
Vector3 fireScale = new Vector3();
fire.World.Decompose(out fireScale, out fireQ, out fireSpot);

 

We then repeat the process for the Parent Object (where ever the Weapon fire will be triggered from, in this and probably most cases, the Weapon fire being triggered from the keyboard would relate to Weapon fire from the Player).

Quaternion rotationQ = new Quaternion();
Vector3 spot = new Vector3();
Vector3 scale = new Vector3();
sceneobject.World.Decompose(out scale, out rotationQ, out spot);

 

At the Starting Blocks

When the starters pistol is fired, we want the Weapon fire to start from the location of the ParentObject (The Player). To do this, we need to setup some Matrices that represent these locations.

Matrix rotation = Matrix.CreateFromQuaternion(rotationQ);
Matrix location = Matrix.CreateTranslation(sceneobject.World.Translation.X, sceneobject.World.Translation.Y, sceneobject.World.Translation.Z);
fire.World = rotation * location;

Notice that we don’t have any scale just yet; just trying to get to a point in space & the direction that the player is facing. Now with that information, we want the bullet to come in to existence a small distance in front of the player, so that it doesn’t get stuck in the player. For this to be successful, we need to work out the position based on the ‘fire’ location and direction & then add some space in the same direction so that it is put in front of the player; this is achieved using fire.World.Forward and multiplying it by a distance. The resulting Vector3 then needs to be added back to the World.Translation for the fire object (from where the Parent Object is). This relies on the world space of the fire object to be oriented in the direction that the bullet will travel, and then using that direction (World.Forward) & multiplying it a number of times.

Vector3 testTranslation = fire.World.Translation + (fire.World.Forward * 8);

 

Thanks here go to DjArcas of http://www.fortresscraft.com/ for spending the time to explain to me what was going on & why I wasn’t getting the right offset to begin with.

Before the final calculation (the really important one) a couple of more setup items are required. The scale of the object needs to be copied over for the Matrix multiplication, we take the fireScale which was copied out earlier from the Decompose method and create a Scale Matrix from it.

The translation we create, is slightly different, that is just so the bullets aren’t launched in to the ground & is used to just elevate the launch location of the bullets from the base of the model to something a bit higher, I just chose the 2f as it seemed to work for me, chose as you please.

// Multiple by scale first to get the right size Matrix scaleM = Matrix.CreateScale(fireScale);
// Define the offsetHight for the Bullet to start from Matrix offsetHeight = Matrix.CreateTranslation(0f, 2f, 0f);

 

SRT (Scale – Rotation – Translation)

SRT – Write it down somewhere or book mark this page, or remember it off by heart. Apparently you really need to know this when working with Matrices in 3D & especially so with XNA.

What it is, is the order that you need to multiply these matrices in, in order to get the desired result. If you multiply them in any other order the results probably aren’t going to be what you expected.

What we are going to do is work through the equation in the way it’s evaluated. So the first calculation to get is the Matrix Result for the offsetHeight and the testTranslation (the translation which is storing the offset location for where the bullet is going to come in to existence from). We do this so that we have a single Translation location, which represents the starting point and includes the height off the ground that the bullet starts life.

We then multiple that result by the rotation of the object that we have obtained from Parent Objects Matrix that was Decomposed and then turned in to a Matrix, which ensures that our fire (bullet) is pointing in the right direction itself.

Finally we take that result and multiply it by the scale of the template fire that we copied from the scene. We use this scale so that we can take a 1:1 copy of the template and not pick up some other random sizing.

The final one liner looks like this:

fire.World = scaleM * (rotation * (Matrix.CreateTranslation(testTranslation) * offsetHeight));

 

Amazing how long it took me to get that single line right for this.

With that we then just have a few finishing touches to make our bullet move onwards and outwards.

Moving and Adding

As this relates predominantly to items already covered off in my previous posts, I’ll be a little generic with the following:

Vector3 directionOfFire = new Vector3(0f, 0f, -0.7f);
fire.CollisionMove.ApplyObjectForce(directionOfFire);

fire.UpdateType = UpdateType.Automatic;
fire.Visibility = ObjectVisibility.RenderedAndCastShadows;

fire.Name = "shot";

SceneInterface.ActiveSceneInterface.ObjectManager.Submit(fire);

 

Things of note are the application of the force that we define (which is a –z number, which for our  purposes is, go forward). We ensure that the object receives update events (moves based on the physics) and that it is visible. The object is also renamed, so that the copy doesn’t pick up a copy of a copy or any such nonsense.

The last step is to then submit the newly created object to the ActiveSceneInterface so that it can take on a life of it’s own; a newly created, directed, physics obeying, instance of an object.

Obligatory Video

Here is another Obligatory Video, I’ve captured the basics of what’s happening and then the second part shows that an existing object with a component already added is also included in the copy when the object is being instanced.

A Present for You

As this post has a lot of code and it’s set out for explanation rather than sequence specifically, please find a full copy of the component pasted below. It only requires you have a template object in the scene named “Fire” & then your good to go.

[Serializable]
       public class Component_Trigger_FireWepon : BaseComponentManualSerialization<ISceneEntity> // BaseComponentAutoSerialization<ISceneEntity> {
           bool xDown = false;

           public override void OnUpdate(GameTime gameTime)
           {
               KeyboardState keyboard = Keyboard.GetState();

               // Cast the Object the component is attached to, to a scene object SceneObject sceneobject = (SceneObject)ParentObject;

               // Move Forward if (keyboard.IsKeyDown(Keys.X) && !xDown)
               {
                   xDown = true;

                   SceneObject fire = new SceneObject();
                   SceneObject copyBase;
                   SceneInterface.ActiveSceneInterface.ObjectManager.Find("Fire", out copyBase);
                   fire = (SceneObject)copyBase.Clone();
                   fire.CollisionMove.RemoveForces();

                   //Scale-Rotation-Translation Quaternion fireQ = new Quaternion();
                   Vector3 fireSpot = new Vector3();
                   Vector3 fireScale = new Vector3();
                   fire.World.Decompose(out fireScale, out fireQ, out fireSpot);

                   Quaternion rotationQ = new Quaternion();
                   Vector3 spot = new Vector3();
                   Vector3 scale = new Vector3();
                   sceneobject.World.Decompose(out scale, out rotationQ, out spot);

                   Matrix rotation = Matrix.CreateFromQuaternion(rotationQ);
                   Matrix location = Matrix.CreateTranslation(sceneobject.World.Translation.X, sceneobject.World.Translation.Y, sceneobject.World.Translation.Z);
                   //works fire.World = rotation * location;

                   // Puts the bullet in front of the player (fire.World.Forward * 8) is the distance in front of the player Vector3 testTranslation = fire.World.Translation + (fire.World.Forward * 8);

                   // Multiple by scale first to get the right size Matrix scaleM = Matrix.CreateScale(fireScale);
                   // Define the offsetHight for the Bullet to start from Matrix offsetHeight = Matrix.CreateTranslation(0f, 2f, 0f);

                   // Set the direction for the bullet (rotation) // Multiply by the result of // The multiplicatino of the offset from the world translation after the rotation has been set and the location of the player // By the offset height fire.World = scaleM * (rotation * (Matrix.CreateTranslation(testTranslation) * offsetHeight));
                   // & hey presto we have a bullet traveling from the direction the player is looking in with a small offset in front of the player Vector3 directionOfFire = new Vector3(0f, 0f, -0.7f);
                   fire.CollisionMove.ApplyObjectForce(directionOfFire);

                   fire.UpdateType = UpdateType.Automatic;
                   fire.Visibility = ObjectVisibility.RenderedAndCastShadows;

                   fire.Name = "shot";

                   SceneInterface.ActiveSceneInterface.ObjectManager.Submit(fire);

               }
               else {
                   if (keyboard.IsKeyUp(Keys.X))
                   {
                       xDown = false;
                   }
               }

           }
       }

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>