XNA Game Engine教程系列9:ComponentPredicate和Materials类
在这个教程中我们将创建一个component predicate类,它用来决定一个组件是否被用于一个行为。这对特殊的绘制过程很有用,例如在创建阴影映射绘制阴影时,我们只需要shadow caster。
另外:很抱歉这个教程不会有很酷的东西,在本章最后你会实现和前面相同的结果,但工作地更好。
// 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;
}
}
下面更新GameScreen使用它:
// 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();
}
最后更新Engine使用它:
// 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));
}
现在运行程序,你会发现没什么变化。这个教程的第二部分关于材质。一个材质定义了对象如何绘制。它们定义了象specularity(高光),texture(纹理),color(颜色),emmisive light(自发光),bump map(凹凸贴图)等属性。你接下去实现的材质只定义了纹理,但以后会扩展这个类,下面是材质的基类:
// 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);
}
}
接着扩展这个基类实现支持一个叫做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);
}
}
最后制作的材质类叫做MeshPartMaterial。这个类被用于如Actor等类,处理包含在模型中的ModelMeshPart的材质属性。
// 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;
}
}
现在我们可以更新Actor类使用这个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();
}
现在运行程序你可以发射一个盒子,没什么新的东西,但至少工作正常,以后我们会实现更高级的东西。
现在对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;
}
}
这个类所做的就是跟踪纹理并创建effect,以后我们会扩展为多纹理,现在改变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;
}
现在再次运行程序,和以前一样没什么变化,我们会在以后做出更酷的效果。
发布时间:2009/2/10 下午1:45:13 阅读次数:7749
