Home > TutorialsTechniques
4.2. Particle System - Rain 
In this tutorial you will further use the effects framework of BetaCell; in this case, the effect is rain.



Click here to go to the forum discussion of this tutorial


 

4.2.1. Prerequisites
default.jpg
Before you start this tutorial make sure that you have:
  • Installed XNA Game Studio 3.1
  • Downloaded the starter project from the downloads section
  • Successfully run the starter project
  • Finish tutorial 4.1.
 

4.2.2. Game Assets 
Game assets [2.3.2] for this tutorial:

 

4.2.3. The using statements
Besides the using statements from the starter project, this tutorial needs the following using statements:

using BetaCell.Environment.Particles;
using BetaCell.Environment.Texture;
using BetaCell.Environment.Particles.Initializers;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;
using BetaCell.Environment.Particles.Parts;


 

4.2.4. Game Attributes 
The attributes that come with the starter project are explained in tutorial section 1.1.4.

For this tutorial we are going to be needing the following attributes:

BCParticleSystem particleSystem;
BCCeilingInitializer ceilingInitializer;


 

Advertisement
 

4.2.5. The Initialize method 

The parts of the method that we haven't already discussed start by creating a ceiling initializer:

ceilingInitializer = new BCCeilingInitializer(
    2.5f, new Color(192, 192, 192, 64),
    10, 15,
    0.01f, 0.1f,
    new Vector4(-0.1f, 0, 0, 0), new Vector4(0.1f, 0, 0, 0),
    new Vector4(0, 0, 0, 1), 20, 40, 40
);

The parameters are the same as the circle initializer up until the sixth one. The seventh parameter is the minimum initial vector velocity of the particles, the eight is the maximum initial velocity of the particles, the ninth is the center of the plane where the particles are emitted, the tenth is the height of the plane and the final two are the width and depth of the plane.

default.jpg
This initializer creates particles at a random location of a plane as shown in the upper part of the figure.

The other part not already discussed is the creation of an acceleration part:

BCParticleAccelerationPart acceleration =
    new BCParticleAccelerationPart(
        "Particles.Parts.Acceleration"
        new Vector4(0, -9.8f, 0, 0));


This part is shown in the lower part of the figure, notice that the particle goes faster over time. The acceleration of the particles is a vector, in this case is the earth's gravity (9.8m/s^2 in the negative Y axis).

The last part that's worth mentioning is that a different render strategy is used:

new BCInvSrcAlphaRenderStrategy()

What this does is to make the water transparent.

 

4.2.6. The Update method
The only new part of the update method updates the center of the emission plane to always be above the camera, this creates the effect that is raining everywhere (like having a cloud on top of the user's head).

ceilingInitializer.center.X = camera.getPosition().X;
ceilingInitializer.center.Y = camera.getPosition().Y;
ceilingInitializer.center.Z = camera.getPosition().Z;
ceilingInitializer.center.W = 1;


 

4.2.7. The Draw method
The new part of this method disables the cull mode, draws the particle system and enables back the cull:

CullMode currentCullMode = device.RenderState.CullMode;
device.RenderState.CullMode = CullMode.None;
particleSystem.Draw(gameTime);
device.RenderState.CullMode = currentCullMode;


 

4.2.8. Conclusion
Look how with a very little change, you achieved a different particle effect, try adding other parts to see the behavior.

Click here to go to the forum discussion of this tutorial

 

Advertisement
 

4.2.9. Complete source code
using System;
using System.Collections.Generic;
using System.Threading;

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.Net;
using Microsoft.Xna.Framework.Storage;

using BetaCell.Debug;
using BetaCell.Util.GlobalInfo.Content;
using BetaCell.Environment.Camera;
using BetaCell.Util.GlobalInfo;

using BetaCell.Environment.Particles;
using BetaCell.Environment.Texture;
using BetaCell.Environment.Particles.Initializers;
using BetaCell.Util.RenderStates;
using BetaCell.Environment.Texture.Feeder;
using BetaCell.Environment.Particles.Parts;

namespace Starter
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Game attributes
        GraphicsDeviceManager graphics;
        GraphicsDevice device;
        ContentManager content;

        BCFirstPersonHumanCamera camera;
        //end game attributes

        BCParticleSystem particleSystem;
        BCCeilingInitializer ceilingInitializer;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8;
            content = new ContentManager(Services);

#if(XBOX360)
            graphics.PreferredBackBufferWidth = 1280;
            graphics.PreferredBackBufferHeight = 768;
#else
            graphics.PreferredBackBufferWidth = 852;
            graphics.PreferredBackBufferHeight = 480;
#endif
        }

        protected override void Initialize()
        {
            device = graphics.GraphicsDevice;
            BCLogger.instance.init(device, content);
            BCInitializationManager.initialize(content);

            OnActivated(null, null);

            BCLogger.instance.message.color = Color.White;

            //-----
            //Particle system
            //-----

            BCMemoryTextureStrategy raindrop = new BCMemoryTextureStrategy(
                content.Load<Texture2D>("Content/gameAssets/raindrop"));

            ceilingInitializer = new BCCeilingInitializer(
                2.5f, new Color(192, 192, 192, 64),
                10, 15,
                0.01f, 0.1f,
                new Vector4(-0.1f, 0, 0, 0), new Vector4(0.1f, 0, 0, 0),
                new Vector4(0, 0, 0, 1), 20, 40, 40
            );

            particleSystem = new BCParticleSystem(
                device,
                raindrop,
                0,
                null,
                20000,
                0.0005f,
                "Particles.Base",
                ceilingInitializer,
                new BCInvSrcAlphaRenderStrategy()
            );

            BCSampler sampler = new BCSampler(
                "LINEAR", "LINEAR", "POINT", "4",
                "CLAMP", "CLAMP", "0xffffffff");

            BCParticleAccelerationPart acceleration =
                new BCParticleAccelerationPart("Particles.Parts.Acceleration", new Vector4(0, -9.8f, 0, 0));

            particleSystem.initEffect(device, "Shader.Texture.Sprite", sampler, acceleration);

            base.Initialize();
        }

        protected override void OnActivated(object sender, EventArgs args)
        {
            buildViewMatrix();
            base.OnActivated(sender, args);
        }

        void buildViewMatrix()
        {
            Vector3 pos = new Vector3(0, 0, -4);
            Vector3 look = new Vector3(0, 0, 1);
            look.Normalize();

            camera = new BCFirstPersonHumanCamera(look, pos,
                MathHelper.PiOver4,
                (float)this.Window.ClientBounds.Width / (float)this.Window.ClientBounds.Height,
                1f, 200);

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.VIEW_INDEX,
                camera.getViewMatrix(-1)
            );

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.PROJECTION_INDEX,
                camera.getProjectionMatrix(-1)
            );
        }

        protected override void Update(GameTime gameTime)
        {
            particleSystem.update((float)gameTime.TotalGameTime.TotalSeconds);

            moveCamera(gameTime);

            ceilingInitializer.center.X = camera.getPosition().X;
            ceilingInitializer.center.Y = camera.getPosition().Y;
            ceilingInitializer.center.Z = camera.getPosition().Z;
            ceilingInitializer.center.W = 1;

            BCGlobalInfo.instance.setMatrix(
                BCGlobalInfo.VIEW_INDEX,
                camera.getViewMatrix(-1)
            );
        }

        private void moveCamera(GameTime gameTime)
        {

            float df = 0;
            float ds = 0;
            float dy = 0;
            float pitch = 0;
            float angleY = 0;

            float speed = 5f;
#if (XBOX360)
            GamePadState gamepad = GamePad.GetState(PlayerIndex.One);

            df = gamepad.ThumbSticks.Left.Y;
            ds = gamepad.ThumbSticks.Left.X;
            pitch = -gamepad.ThumbSticks.Right.Y * 0.05f;
            angleY = gamepad.ThumbSticks.Right.X * 0.05f;

            if (gamepad.IsButtonDown(Buttons.RightShoulder))
            {
                dy += 0.25f;
            }
            if (gamepad.IsButtonDown(Buttons.LeftShoulder))
            {
                dy -= 0.25f;
            }

            speed = new Vector3(df, ds, dy).Length() * 10;
#else

            KeyboardState keys = Keyboard.GetState();
            MouseState mouse = Mouse.GetState();

            if (keys.IsKeyDown(Keys.W))
            {
                df += 1;
            }
            if (keys.IsKeyDown(Keys.S))
            {
                df -= 1;
            }
            if (keys.IsKeyDown(Keys.D))
            {
                ds += 1;
            }
            if (keys.IsKeyDown(Keys.A))
            {
                ds -= 1;
            }
            if (keys.IsKeyDown(Keys.Q))
            {
                dy += 1;
            }
            if (keys.IsKeyDown(Keys.E))
            {
                dy -= 1;
            }
            speed = 5f;
            if (keys.IsKeyDown(Keys.LeftControl))
            {
                speed = 10f;
            }

            angleY = ((float)this.Window.ClientBounds.Width / 2.0f - (float)mouse.X) * -0.05f;
            pitch = ((float)this.Window.ClientBounds.Height / 2.0f - (float)mouse.Y) * -0.05f;

            Mouse.SetPosition(this.Window.ClientBounds.Width / 2, this.Window.ClientBounds.Height / 2);

#endif
            camera.update(df, dy, ds, pitch, angleY, speed, (float)gameTime.ElapsedGameTime.TotalSeconds);

            BCGlobalInfo.instance.setMatrix(BCGlobalInfo.VIEW_INDEX, camera.getViewMatrix(-1));
        }

        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

            CullMode currentCullMode = device.RenderState.CullMode;
            device.RenderState.CullMode = CullMode.None;
            particleSystem.Draw(gameTime);
            device.RenderState.CullMode = currentCullMode;

            BCLogger.instance.printFPS(gameTime);
            BCLogger.instance.flush();
            base.Draw(gameTime);
        }
    }
}