Monday, February 23, 2015

19: Camera Recoil Effect Test



       This is a brief video demonstration of the camera recoil effect I recently got working. It is very recent so it is by no means perfect and is still an early work-in-progress. The recoil effect increases immersion by providing a visual feedback in the form of shaking the player's camera as they shoot. This effect is used in many first-person shooters. Tactile feedback is provided as the player shoots by vibrating the player's gamepad controller. The sky-box was disabled temporarily in this video since I am working on volumetric clouds. The weapon sound effect was off some when capturing the footage, so my apologies in advance. That weird green stuff you see is the new blood-splatter particle effect I got working. It will be added to the spider soon after the first stages of the spider's AI is complete.

Saturday, January 17, 2015

18: Vehicle Physics Test



       In this latest vehicle physics test, the controls have great improved along with the overall handling of the vehicle. I used JigLibX 4.0 for the physics. I added a nifty power sliding effect and you can take tight turns by letting off the gas some.

Thursday, September 4, 2014

17. Particle System



This is the beginnings of the Cyclone Game Engine's particle system. It is mostly a 3D particle system implemented by using point sprites. It animates the particles entirely on the graphics card by using a custom vertex shader, so that it can draw large numbers of particles with minimal CPU overhead.

When displaying large numbers of particles, games can easily become bottle-necked by the amount of CPU work involved in updating everything and transferring the latest particle positions across the GPU for drawing. The game engine avoids that by animating the particles entirely on the GPU, so the CPU overhead remains low regardless of how many particles are active. By moving things to the GPU, it leaves the CPU free for many other things such as gameplay, physics and or Artificial Intelligence.

When a new particle is created, the CPU fills a vertex structure with the position, velocity, and creation time. After this vertex is uploaded to the GPU, the CPU never needs to touch it again. Every frame, the current time is set as a vertex shader parameter. When the GPU draws the particles, it can work out the age of each one by comparing the creation time (which is stored as part of the vertex data) with the current time. Knowing the starting position, velocity, and age, the shader can compute the position and draw a point sprite at this location.

New particles are always added to the end of the vertex buffer, and old ones are removed from the start. Because all particles last the same amount of time, this means the active particles will always be grouped together in a consecutive region of the buffer, and so can all be drawn in a single call. The CPU is responsible for adding new particle vertices to the end of the buffer, and for retiring old ones from the start, but it doesn't need to do anything with the active particles in the middle of the buffer. Even games with thousands of particles typically only create and retire a few each frame, so the resulting CPU workload is very low. The Cyclone Game Engine's particle system can also be used for efficient 2D particles by setting an orthographic matrix as the camera projection.

The video above is a demonstration of the particle system. The smoke is a 3D particle system where the game engine creates a giant plume of long lasting smoke. The fire is an animated 2D billboard sprite rendered in 3D, mostly done to save processing.








Thursday, June 5, 2014

16. Import multiple animations from a single .fbx file for XNA 4.0 Refresh

How to extend the XNAnimation library for XNA 4.0 Refresh. In the XNAnimation Pipeline, open up SkinnedModelImporter.cs and replace it with the following source code below.

Source Code Below:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using Microsoft.Xna.Framework.Design;


// The type to import.
using TImport = Microsoft.Xna.Framework.Content.Pipeline.Graphics.NodeContent;

// Change the namespace to suit your project
namespace SkinnedModelPipeline
{
    [ContentImporter(".fbx", DisplayName = "Multi-take FBX Importer", DefaultProcessor = "")]
    public class SkinnedModelImporter : FbxImporter
    {
        private List<string> _animfiles;
        private List<string> _fbxheader;
        private TImport _master;
        private ContentImporterContext _context;

        public override TImport Import(string filename, ContentImporterContext context)
        {
            _context = context;

            // _animfiles will contain list of new temp anim files.
            _animfiles = new List<string>();

            // Decouple header and animation data.
            ExtractAnimations(filename);

            // Process master file (this will also process the first animation)
            _master = base.Import(filename, context);

            // Process the remaining animations.
            foreach (string file in _animfiles)
            {
                TImport anim = base.Import(file, context);

                // Append animation to master NodeContent.
                AppendAnimation(_master, anim);
            }

            // Delete the temporary animation files.
            DeleteTempFiles();

            return _master;
        }

        private void AppendAnimation(NodeContent masternode, NodeContent animnode)
        {
            foreach (KeyValuePair<string, AnimationContent> anim in animnode.Animations)
            {
                if (!masternode.Animations.ContainsKey(anim.Key))
                {
                    masternode.Animations.Add(anim.Key, anim.Value);
                }
                else
                {
                    //overwrite the animation that was stored inside the
                    //master file because it is of the wrong length (except the first animation).
                    masternode.Animations[anim.Key] = anim.Value;
                }
            }

            //foreach (NodeContent child in animnode.Children) {
            //    if (child != null) {
            //        AppendAnimation(child);
            //    }
            //}

            for (int i = 0; i < masternode.Children.Count; i++)
            {
                if (animnode.Children[i] != null)
                {
                    AppendAnimation(masternode.Children[i], animnode.Children[i]);
                }
            }
        }

        private void ExtractAnimations(string filename)
        {
            List<string> masterFile = File.ReadAllLines(filename).ToList();
            string path = Path.GetDirectoryName(filename);
            int open_idx = 0,
                length,
                num_open = -1,
                filenum = 0;
            bool foundTake = false;

            int idx = masterFile.IndexOf("Takes:  {") + 1;
            _fbxheader = masterFile.Take(idx).ToList();
            List<string> anims = masterFile.Skip(idx).ToList();

            // Extract each animation and create a temporary anim file.
            for (int i = 0; i < anims.Count; i++)
            {
                if (anims[i].Contains("Take: "))
                {
                    open_idx = i;
                    num_open = 0;
                    foundTake = true;
                }

                if (anims[i].Contains("{") &&
                    foundTake)
                {
                    num_open++;
                }

                if (anims[i].Contains("}") &&
                    foundTake)
                {
                    num_open--;
                }

                if (num_open == 0 &&
                    foundTake)
                {
                    // Skip first animation since this is processed in the master
                    // fbx file.
                    if (filenum > 0)
                    {
                        length = i - open_idx + 1;

                        // Create temp file from header + anim data.
                        CreateTempFile(Path.Combine(path, "tmp.anim." + filenum + ".fbx"),
                                       anims.Skip(open_idx).Take(length).ToArray());
                    }
                    filenum++;
                    foundTake = false;
                }
            }
        }

        private void CreateTempFile(string filename, string[] data)
        {
            List<string> completefile = new List<string>();
            completefile.AddRange(_fbxheader);
            completefile.AddRange(data);

            try
            {
                // Write data to new temp file.
                File.WriteAllLines(filename, completefile.ToArray());

                // Store temp file name for processing.
                _animfiles.Add(filename);
            }
            catch
            {
                // Error while creating temp file.
                _context.Logger.LogWarning(null, null, "Error creating temp file: {0}", filename);
            }
        }

        private void DeleteTempFiles()
        {
            foreach (string file in _animfiles)
            {
                File.Delete(file);
            }
        }
    }
}

Monday, May 26, 2014

15: Water Shader




Water is one of the most complex shaders to render in games. I am pleased to announce that I was able to implement an ocean shader with wave ripples and light reflecting on the surface.

Friday, May 2, 2014

#14: Attaching Non Skinned Models to an Animated Model








       Models can now be attached to animated characters and they don't necessarily have to be rigged. I am using the XNAnimation Library. I got it to work in XNA 4.0 by creating a merge content processor. In the future, this will work great for character customization and vehicle customization. I will soon make the entire menu system into a 3D scene. The camera will transition to different areas of the 3D scene after the player make certain selections.

Example source code below:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using XNAnimation;
using XNAnimation.Controllers;
using System;

namespace ModelAttachment
{

public class ModelAttachment : Microsoft.Xna.Framework.Game
{
        int RIGHT_HAND_BONE_ID;
        /// <summary>
        /// The AnimationController class handles the animation playback
        /// </summary>
        AnimationController animationController;

        SkinnedModel marine;
        Matrix view;
        Matrix projection;

        Model gun;
        
        public override void LoadContent()
        {
              RIGHT_HAND_BONE_ID = 15;
              // Calculate camera view and projection matrices.
              view = Matrix.CreateLookAt(new Vector3(-5, 5, -30),
              new Vector3(1, 3, 0), Vector3.Up);
              projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,GraphicsDevice.Viewport.AspectRatio, 10, 10000); 

           LoadMarineModel();                                                    
        }

        /// <summary>
        /// Unloads graphics content for this screen.
        /// </summary>
        public override void UnloadContent()
        {
           content.Unload();
        }

        public string idle = "Idle";

        private void LoadMarineModel()
        {
            gun = content.Load<Model>("Models/weapon");
            marine = content.Load<SkinnedModel>("Models/PlayerMarine");
            animationController = new AnimationController(marine.SkeletonBones);
            animationController.StartClip(marine.AnimationClips["Idle"]);
  }
        public override void Update(GameTime gameTime)
        {
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            // Update the animation according to the elapsed time         
            animationController.Update(gameTime.ElapsedGameTime, Matrix.Identity);

            base.Update(gameTime);
        }


        /// <summary>
        /// Draws the background screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
            Drawmarine(marine, view, projection);
DrawGun(gun,view, projection, animationController.SkinnedBoneTransforms[RIGHT_HAND_BONE_ID];

        } 

        /// <summary>
        /// Draws the marine model
        /// </summary>       
        private void Drawmarine(SkinnedModel marine, Matrix view, Matrix projection)
        {
            foreach (ModelMesh mesh in marine.Model.Meshes)
            {
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {
                    foreach (SkinnedEffect effect in mesh.Effects)
                     {               
                     effect.SetBoneTransforms(animationController.SkinnedBoneTransforms);
                        effect.World = Matrix.Identity;

                        // OPTIONAL - Configure lights
                        effect.EnableDefaultLighting();
                        effect.AmbientLightColor = new Vector3(0.1f);
                        effect.DiffuseColor = new Vector3(0.8f);
                        effect.SpecularColor = new Vector3(0.3f);
                        effect.SpecularPower = 8;

                     // Camera Properties
                        effect.View = view;
                        effect.Projection = projection;
                     }
                }
                mesh.Draw();
            }
        }
     

        public void DrawGun(Model model, Matrix view, Matrix projection, Matrix world)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);
            foreach (ModelMesh mm in model.Meshes)
            {
                foreach (BasicEffect be in mm.Effects)
                {
                    be.View = view;
                    be.Projection = projection;
                    be.World = transforms[mm.ParentBone.Index] * world;
                    be.EnableDefaultLighting();
                    be.AmbientLightColor = new Vector3(0.1f);   
                    be.SpecularPower = 5;
                }
                mm.Draw();
          }
        }
      }
    }