XNA Game Engine Tutorial Series #9: ComponentPredicate and Materials
In this tutorial we are going to create a component predicate, a class used to determine whether or not a component will be used for an action. This is useful for things like special drawing, for example when drawing shadows we only want shadow casters to draw when creating the shadow map.
Side note: I apologize ahead of time but nothing cool is going to happen during this tutorial. By the end of this tutorial, you will have exactly the same results. But they will be better.
// Provides a way for the engine to let the game tell it what to
// draw, instead of the somewhat limited ComponentType
public abstract class ComponentPredicate
{
// Decides whether the specified component is eligible for the
// action being done
public abstract bool IsComponentEligible(Component Component);
}
// Basic ComponentPredicate that uses ComponentType to determine
// what components to use
public class ComponentTypePredicate : ComponentPredicate
{
// The type of component to accept
ComponentType Type;
// Accepts a ComponentType to use to determine what
// components will be accepted
public ComponentTypePredicate(ComponentType Type)
{
this.Type = Type;
}
public override bool IsComponentEligible(Component Component)
{
if (Type == ComponentType.Both)
{
// If the render type is both, we will use all 2D or
// 3D components
if (Component is I2DComponent || Component is I3DComponent)
return true;
}
else if (Type == ComponentType.Component2D)
{
// If the render type is 2D, we will only use 2D
// components
if (Component is I2DComponent)
return true;
}
else if (Type == ComponentType.Component3D)
{
// If the render type is 3D, we will only use 3D
// components
if (Component is I3DComponent)
return true;
}
else
{
// Otherwise, we will use every component regardless of type
return true;
}
// If the conditions have not been met, we will not use the
// component
return false;
}
}
Now we can update GameScreen to use it:
// Draw the screen and its components. Accepts a ComponentPredicate
// to tell us what kind of components to draw.
public virtual void Draw(ComponentPredicate DrawPredicate)
{
// Temporary list of components to draw
List<Component> drawing = new List<Component>();
foreach (Component component in Components.InDrawOrder)
if (DrawPredicate.IsComponentEligible(component))
drawing.Add(component);
// Keep a list of components that are 2D so we can draw them on top
// of the 3D components
List<Component> defer2D = new List<Component>();
foreach (Component component in drawing)
if (component.Visible && component.Initialized)
// If the component is visible
if (component is I2DComponent)
// If it is 2D, wait to draw
defer2D.Add(component);
else
// otherwise, draw immediately
component.Draw();
// Draw 2D components
foreach (Component component in defer2D)
component.Draw();
}
And finally update Engine to use it:
// ComponentType to render
public static void Draw(GameTime gameTime, ComponentPredicate DrawPredicate)
{
// Update the time, create a temp list
Engine.GameTime = gameTime;
List<GameScreen> drawing = new List<GameScreen>();
// Clear the back buffer
GraphicsDevice.Clear(Color.CornflowerBlue);
// Populate the temp list if the screen is visible
foreach (GameScreen screen in GameScreens)
if (screen.Visible)
drawing.Add(screen);
// BlocksDraw and OverrideDrawBlocked logic
for (int i = GameScreens.Count - 1; i >= 0; i--)
if (GameScreens[i].BlocksDraw)
{
if (i > 0)
for (int j = i - 1; j >= 0; j--)
{
if (!GameScreens[j].OverrideDrawBlocked)
drawing.Remove(GameScreens[j]);
}
break;
}
// Draw the remaining screens
foreach (GameScreen screen in drawing)
if (screen.Initialized)
screen.Draw(DrawPredicate);
}
public static void Draw(GameTime gameTime, ComponentType DrawType)
{
Draw(gameTime, new ComponentTypePredicate(DrawType));
}
Now run it and you should see that nothing changed at all. At least, hopefully you do. If not, something went wrong.
The second part of this tutorial is on materials. A material defines how an object should be drawn. They define properties like specularity, texture, color, emmisive light, bump map, etc. The materials we will make right now only define texture, but they can be expanded in the future. Here is the base Material class:
// Base Material class keeps track of an effect and handles basic
// properties for it
public class Material : Component
{
// The effect to handle
Effect effect;
public virtual Effect Effect
{
get { return effect; }
set { effect = value; }
}
// Sets up the material from the values embedded in a MeshPart
public virtual Material CreateFromMeshPart(ModelMeshPart MeshPart)
{
this.effect = MeshPart.Effect;
return this;
}
// Set World, View, and Projection values
public virtual void Prepare3DDraw(Matrix World)
{
effect.Parameters["World"].SetValue(World);
effect.Parameters["View"].SetValue(
Engine.Services.GetService<Camera>().View);
effect.Parameters["Projection"].SetValue(
Engine.Services.GetService<Camera>().Projection);
}
}
Now we will expand this to make a material that supports texturing called TexturedMaterial:
// Material that handles a texture
public class TexturedMaterial : Material
{
// The texture to keep track of
Texture2D texture;
// Set accesor sets the texture to the underlying effect
public virtual Texture2D Texture
{
get { return texture; }
set { texture = value; SetTextureToEffect(Effect, texture); }
}
public override Effect Effect
{
get { return base.Effect; }
set
{
SetTextureToEffect(value, GetTextureFromEffect(base.Effect));
base.Effect = value;
}
}
// Override gets the texture from the MeshPart
public override Material CreateFromMeshPart(ModelMeshPart MeshPart)
{
base.CreateFromMeshPart(MeshPart);
this.texture = GetTextureFromEffect(MeshPart.Effect);
return this;
}
// Method to get texture from various types of effect
internal static Texture2D GetTextureFromEffect(Effect Effect)
{
if (Effect == null)
return null;
if (Effect is BasicEffect)
return ((BasicEffect)Effect).Texture;
else if (Effect.Parameters["Texture"] != null)
return Effect.Parameters["Texture"].GetValueTexture2D();
return null;
}
// Method to set the texture to various types of effect
internal static void SetTextureToEffect(Effect Effect, Texture2D Texture)
{
if (Effect == null)
return;
if (Effect is BasicEffect)
((BasicEffect)Effect).Texture = Texture;
else if (Effect.Parameters["Texture"] != null)
Effect.Parameters["Texture"].SetValue(Texture);
}
}
The final material we will make is called MeshPartMaterial. This class can be used by classes like Actor to handle the material properties of a ModelMeshPart, contained in a Model:
// Handles setting effect and texture to a MeshPart
public class MeshPartMaterial : TexturedMaterial
{
ModelMeshPart meshPart;
public ModelMeshPart MeshPart
{
get { return meshPart; }
set { meshPart = value; value.Effect = Effect; }
}
public override Effect Effect
{
get { return base.Effect; }
set { SetTextureToEffect(value, GetTextureFromEffect(base.Effect)); base.Effect = value; MeshPart.Effect = value; }
}
public override Material CreateFromMeshPart(ModelMeshPart MeshPart)
{
base.CreateFromMeshPart(MeshPart);
this.meshPart = MeshPart;
return this;
}
}
Now we can update the Actor class to use MeshPartMaterial:
// In the top of the class: public Dictionary<ModelMeshPart, Material> Materials = new Dictionary<ModelMeshPart, Material>();
// In Setup()
foreach (ModelMesh mesh in Model.Meshes)
foreach (ModelMeshPart part in mesh.MeshParts)
Materials.Add(part, new MeshPartMaterial().CreateFromMeshPart(part));
// In the Draw() method:
// Loop through meshes and effects and set them up to draw
foreach (ModelMesh mesh in model.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
Materials[part].Prepare3DDraw(world);
// Draw the mesh
mesh.Draw();
}
Now if you hit F5 and spawn a box (left click if you have been following the tutorials in order), you should see.. exactly the same thing. I know, not very exciting, but at least it’s working! We’ll get to more advanced stuff later.
Now lets do the same thing for Terrain:
// Material for a Terrain object
public class TerrainMaterial : TexturedMaterial
{
public TerrainMaterial(Texture2D Texture)
: base()
{
this.Effect = new BasicEffect(Engine.GraphicsDevice, null);
this.Texture = Texture;
SetupEffect();
}
// Setup BasicEffect
private void SetupEffect()
{
BasicEffect basicEffect = (BasicEffect)this.Effect;
basicEffect.Texture = Texture;
basicEffect.TextureEnabled = true;
basicEffect.EnableDefaultLighting();
basicEffect.DirectionalLight0.Direction = new Vector3(1, -1, 1);
basicEffect.DirectionalLight0.Enabled = true;
basicEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
basicEffect.DirectionalLight1.Enabled = false;
basicEffect.DirectionalLight2.Enabled = false;
basicEffect.SpecularColor = new Vector3(0, 0, 0);
}
public override void Prepare3DDraw(Matrix World)
{
base.Prepare3DDraw(World);
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
}
}
All this does right now is keep track of the texture and create the effect, but we will extend it later on for effect like multitexturing. Now change terrain:
// Replaces Effect and Texture properties // Material public TerrainMaterial Material;
// Remove the following from Setup() basicEffect = new BasicEffect(Engine.GraphicsDevice, null); SetupEffect(); // Add to Setup() Material = new TerrainMaterial(Texture);
private float[,] CreateTerrain(Texture2D heightMap, Texture2D texture) // Becomes private float[,] CreateTerrain(Texture2D heightMap) // Remove from CreateTerrain() this.texture = texture;
// Update the Draw() method
// Draw the terrain
public override void Draw()
{
// Require the camera
Camera camera = Engine.Services.GetService<Camera>();
if (camera == null)
throw new Exception("The engine services does not contain a "
+ "camera service. The terrain requires a camera to draw.");
// Set effect values
Material.Prepare3DDraw(MathUtil.CreateWorldMatrix(
Position, Rotation, Scale));
// Get width and height
int width = heightData.GetLength(0);
int height = heightData.GetLength(1);
// Terrain uses different vertex winding than normal models,
// so set the new one
Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
// Start the effect
Material.Effect.Begin();
// For each pass..
foreach (EffectPass pass in Material.Effect.CurrentTechnique.Passes)
{
// Begin the pass
pass.Begin();
// Draw the terrain vertices and indices
Engine.GraphicsDevice.Vertices[0].SetSource(terrainVertexBuffer, 0,
VertexPositionNormalTexture.SizeInBytes);
Engine.GraphicsDevice.Indices = terrainIndexBuffer;
Engine.GraphicsDevice.VertexDeclaration = myVertexDeclaration;
Engine.GraphicsDevice.DrawIndexedPrimitives(
Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleStrip,
0, 0, width * height, 0, width * 2 * (height - 1) - 2);
// End the pass
pass.End();
}
// End the effect
Material.Effect.End();
// Set the vertex winding back
Engine.GraphicsDevice.RenderState.CullMode =
CullMode.CullCounterClockwiseFace;
}
Now, run it, and again you should see the same thing you did before. Once, again, thrilling, but we’ll make it cooler in the future.
Thats it for this tutorial!
Download the Code for this Chapter
Back to Game Engine Tutorial Series

Hmmm, it seems as though something is wrong with the grass texture. I actually had this in the previous tutorial also, but I thought maybe it would be fixed in this one.
http://i44.tinypic.com/2expl4i.png
That’s what happens. Only one corner is shown, the rest is some colorful-lines on the side as seen, and the rest is that yucky green color. Any ideas what could wrong?
addendum to the previous: What I meant was the terrain itself actually appears, however only one corner of the grass is rendered properly. The green scanline things only appear on the two sides in the image see, and the yucky green color fills the rest.
Addendum to the addendum:
I tried it with a bitmap texture, and the outcome is the same. One corner gets rendered right, the rest are exactly like the previous. Any ideas?
Addendum to the addendum of the addendum: I just reran it and it seems to work? I did screw around with some lighting, so maybe that fixed it.
Great tuts!
It sounds like you had some texture wrap settings messed up. On certain texture wrap modes, if a texture is outside the range of [0,1] (like the entire terrain except for one corner), it will just stretch the outermost pixel of the texture to cover the rest of the terrain (ie, the ugly yellow). If it’s set to Wrap, it will wrap the texture over the whole terrain:
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
If you used a SpriteBatch or possibly BasicEffect, this would have been fiddled with.
It keeps giving me an error in the TexturedMaterial class that value.Parameters["Texture"].SetValue(texture); is not assigned to. I even downloaded your source code.
value.Parameters["Texture"].SetValue(texture); Will not work with the provided code as it tries to set a Parameter of the effect which is null.
if you chuck
if (value.Parameters["Texture"] != null)
before that line it will fix your problem.
Or you could use SetTextureToEffect(), and make sure the Effect has a parameter called Texture.
hello sean
first, say sorry, my engish is poor.
I adding a simple class Font to engine, inherit Component and I2DComponent, called SpriteBatch.DrawString() to drawing font in override Draw().
in test game, in LoadContent() ,
…
Font font = new Font();
Engine.Services.AddService(typeof(Font), font);
…
but color parameter of SpriteBatch.DrawString() causative BasicEffect of SetupEffect() in TerrainTexture bring bug.
same with Ilya Ostrovskiy say
please help me, thank you.
I add code
…
Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
Engine.GraphicsDevice.RenderState.AlphaBlendEnable = true;
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
Engine.GraphicsDevice.RenderState.AlphaBlendEnable = true;
…
remove bug,hehe
is
this.graphics.GraphicsDevice.RenderState.AlphaTestEnable = true;
I m fool, sorry ,is
Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
Engine.GraphicsDevice.RenderState.AlphaTestEnable = true;
Engine.GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
Engine.GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
Engine.GraphicsDevice.RenderState.AlphaBlendEnable = true;
my engish is poor,hehe
I get an error when trying to add the Box we used earlier. In MeshPartMaterial it says: “if (value.Parameters["Texture"] != null)” in line
“set { SetTextureToEffect(value, GetTextureFromEffect(base.Effect)); base.Effect = value; MeshPart.Effect = value; }”
Specifically its the “MeshPart.Effect = value” that causes problems. It seems that the Meshpart isnt set to anything before we try and assign it an effect… Am i missing something?
[...] http://innovativegames.net/blog/2008/12/19/engine-tutorial-9/ [...]
If you ask me this :
Materials[part].Prepare3DDraw(world);
should be :
Materials[part].Prepare3DDraw(transforms[mesh.ParentBone.Index] * world);
when you dont all model positions are at zero
how to add animation library to this engine.when i tried to add it, it was not displaying the animation models.But it is displaying the static models. Am i missing something?
Thanks
@Ram
See the comment before you- I think that’s what you want. ie. The world matrix needs to be combined with the bone transformations…
I created a FrameRateDisplay component that inherits from Component and I2DComponent and overrides the Update and Draw methods to calculate and print the framerate on the screen.
The problem I’m having is that the terrain is not being drawn correctly when I draw text to the screen to display the framerate. If I comment out the DrawString method, everything is fine. It’s only when I draw the text on the screen that I see anything wrong.
Could you possibly post the correct code for displaying text (or any other sprite) on the screen with the terrain rendering correctly behind it?
Thank you.
Update: I read through these comments again and it appears I was having the same issue as Johnson was. All is well now. Thanks for the great tuts!
Any problem rewriting the draw code like this?
public virtual void Draw(ComponentPredicate DrawPredicate)
{
//Temporary lists
List drawing = new List();
List defer2d = new List();
foreach (Component Component in Components.InDrawOrder)
{
if (Component.Visible && Component.Initialized)
{
if (DrawPredicate.IsComponentEligible(Component)
{
if (Component is I2dComponent)
defer2d.Add(Component);
else
drawing.Add(Component);
}
}
}
//Now Draw
foreach (Component Component in drawing)
Component.Draw();
foreach (Component Component in defer2d)
Component.Draw();
}