Posts Tagged ‘Game Development’

Changing the Windows Mouse Cursor in XNA

Monday, April 4th, 2011

aero_arrow

Background

For anyone who is looking to develop a mouse-based game for Windows using the XNA framework, it’s pretty critical to be able to change your mouse cursor in game. The first method is to hide the mouse cursor and draw a custom texture in place of it – but anyone who is developing a game that might not be running at a full 60 frames/sec will know that this is does not work well, as it results in a very slow and unresponsive mouse movement.

The Windows operating system is designed to give extremely responsive mouse movement no matter how slow your applications are running, so we’d like to harness this power in XNA while also giving us the power to change our cursors.

Overview

The following will allow you to change the windows cursor in XNA to any .cur or .ani file (full colour, animated, just like through the Windows Control Panel). This is done by changing the cursor of the windows forms handle for the XNA window. To load full colour and animated cursors, we will use the IntPtr LoadCursorFromFile(string) method from user32.dll, as suggested by Hans Passant.

Step 1: Add References

  • Add the “System.Windows.Forms” reference to your game project. Do this by right clicking on the References folder in the Solution Explorer for your project and select “Add Reference…”. It can be found under the “.Net” tab.
  • You will need to include the following namespaces:
using System.Windows.Forms;
// For the NativeMethods helper class:
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Reflection;

Step 2: Add your Custom Cursor

  • Add cursor to your project (let’s say it’s called “cursor.ani” and you put it in the “Content\Cursors” directory).
  • Go to the Properties of this cursor in your Solution Explorer and set the “Build Action” to “None” and “Copy to Output Directory” to “Copy if newer”.

Step 3: Loading The Cursor

You will need something like the following helper method to load your full colour animated cursor into a handle that Windows Forms can use:

// Thanks Hans Passant!
// http://stackoverflow.com/questions/4305800/using-custom-colored-cursors-in-a-c-windows-application
static class NativeMethods
{
    public static Cursor LoadCustomCursor(string path)
    {
        IntPtr hCurs = LoadCursorFromFile(path);
        if (hCurs == IntPtr.Zero) throw new Win32Exception();
        var curs = new Cursor(hCurs);
        // Note: force the cursor to own the handle so it gets released properly
        var fi = typeof(Cursor).GetField("ownHandle", BindingFlags.NonPublic | BindingFlags.Instance);
        fi.SetValue(curs, true);
        return curs;
    }
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern IntPtr LoadCursorFromFile(string path);
}

Step 4: Changing the Cursor

First, set the mouse to visible:

this.IsMouseVisible = true; // in your Game class

Next, load your cursor using the above static helper method:

Cursor myCursor = NativeMethods.LoadCustomCursor(@"Content\Cursors\cursor.ani");

Now load up the window handle that will let you change the window’s cursor:

Form winForm = (Form)Form.FromHandle(this.Window.Handle);

Simply do the following to change the cursor:

winForm.Cursor = myCursor;

That’s it! Happy game dev-ing!

Hideout! Post-mortem / Retrospective

Sunday, February 6th, 2011

It’s been a few moths since the Xbox 360 release of Hideout! on the Indie Games channel of Xbox Live. The sales were very low, but I was expecting that from this type of game. Regardless, with over 1200 trial downloads and only 45 sales, it would be good to look at why this game was not a blockbuster…

HideoutKidAbduct

1) Content, content, content!

Paraphrasing Antonio of Artech Studios, “It’s all about content nowadays.” If the player is able to experience the entire game in 5 minutes, then why would they put their money into buying it? Most players of modern video games expect a series of new experiences encapsulated in a single game, rather than just one experience per game like in the days of the Game and Watches.

This was the first problem with Hideout! – Once you’ve tried it, you know there won’t be any new and different experiences in the full version. To resolve this issue, a more refined version of the game would include new environments to unlock with new powerups, different enemies, and maybe even a variety of completely different gameplay mechanics.

This concept actually ties in a bit with the next point…

2) The trial game did not entice players to buy

The unique challenge that exists in designing games for Xbox Live and other similar platforms revolves around creating a trial game that will make the player want more. As described in the last point, it’s important to make sure that there actually is more, but that’s not enough on its own: the player needs to know about it and they need to want it.

So we need to think up a way to let the player know about the extra features and experiences while also triggering a desire to buy them. The method that should be used depends heavily on the features of the game – I would not be able to say exactly what the best implementation would be for Hideout! without strictly defining what the new features would be… Regardless, here are some methods that I’ve seen other games on similar marketplaces use:

Probably the most commonly used method is designing the trial to emphasize inaccessible features by displaying them in the game, but preventing the player from experiencing them. This works great with level-based games because it shows the player there is more and gives them a taste that will hopefully make them want more. Story-based games can simply be cut off at cliff-hangers, after the player has become involved in the story and has began to develop a relationship with the characters.

A strategy common to  Xbox Live games is including notices of achievements that would have been unlocked if the player had purchased the full game. Sadly, this ability is not available to Indie Games like Hideout!… but it would not be impossible to implement something else that would use this basic concept without having access to the Xbox Live API.

3) No strong reward for the player

This one is tricky. For some players, the reward of getting a higher score and moving to the next level is enough. But for others, they really need to see that they’re making a difference in the game world or be given something in return for their efforts. Hideout! did some of this right by giving the player a rewarding sound when saving another kid from the UFO invasion, but it was obvious during playtests that this was not enough for all players.

4) Lack of polish and “oops, I forgot.”

I could make up lots of excuses, and most of them would probably be valid, but when it comes down to sales, excuses don’t matter. Hideout! was definitely lacking in polish on the visual and performance elements. Spending extra time and effort on these elements can have an impact on sales, especially those derived from reviews, which I expect hold a large percent of total sales on this platform. On a similar note, I totally forgot to make use of the force feedback in the Xbox controller! Oops!

5) Accidental key presses in menus

This one was counterintuitive to me: I realized after making Hideout! that it is quite essential to have a slight pause between in-game menus to prevent the input intended for gameplay to be picked up by the menu system. Most major games do this nowadays with fancy animations or transitions between menus, but even a simple pause before accepting button presses in menus would have resolve this problem for Hideout!

6) Don’t distract me when I’m playing!

I’ll be honest and say that it was a bit of a surprise when I found that players were clueless about gameplay mechanics that were clearly being explained to them through in-game text. After seeing this, I learned the true power of well-constructed tutorial levels. Hideout! failed to introduce the player to the details of how to play in a safe, stress-free environment. Instead, without pausing the gameplay, the player was expected to read text while running away from UFOs and busily figuring out everything else about the game.

HideoutDontMove

In fact, a similar problem extended outside the gameplay: some players simply don’t read any text at all! Even though the reason for the game over was clearly stated at the end of the level, some players would just skip past it to continue the same mistakes over and over again in gameplay. I expect that the best solution for Hideout! would be a tutorial level that pauses the gameplay at different events, giving the player a chance to read the explanation and instructions.

7) Lack of support for player’s input preferences

I’ve played a number of 3D adventure games from Nintendo where it is convention to control the camera by rotating around the player: moving the joystick to the right rotates the the camera to the right relative to the player. This works great for Nintendo, as the majority of their platform’s games follow this convention… But a large majority of Xbox players are first-person and third-person shooter players who expect the camera to move in the reverse direction: moving the joystick to the right moves the camera left relative to the player causing the view to show what is to the right of the player’s character. This seems backwards to me, but after much playtesting, it has become obvious that on the Xbox it is critical to have invert-rotation options, even though it may not actually be necessary on Nintendo’s platforms.

That’s about all that comes to mind for now. I hope this has been a helpful insight to everyone. I’m always looking for feedback, either on my games or on these blog posts, so feel free to leave some comments!

Hideout!: Designing Lasting Arcade-Style Gameplay

Monday, August 30th, 2010

When I initially designed and implemented the gameplay of Hideout! I had created a game where the best players would consistently get to about level 5 or 6, while beginners would typically achieve level 3 or 4. This wasn’t very good – I wanted experienced players to be able to get much higher up, maybe up to around level 25.

During the update and port to the Xbox 360, I spent some time working on this problem and was successfully able to create a very effective solution that kept beginners at around level 5, but enabled skilled players (myself) to reach level 23!

The Problem

When I had initially programmed the level progression, I had designed a number of the gameplay mechanics to progress in a linear fashion, such as the UFO speed, the time it takes for a UFO to discover you, etc.

I wanted the experienced players to be able to get to a “high” level, maybe in the twenties or thirties. This essentially resulted in the following scenario:

problem1

Interestingly, this implementation presented a huge problem: Players couldn’t tell that the levels were actually getting harder. Said differently, the difficulty steps were too small at the beginning levels. This meant that the players would easily become bored of the game because they assumed that it was the same thing over and over, with no difference in challenge from level to level.

To resolve this problem, I simply made the gameplay mechanics become harder much more faster than in the first design. This resulted in the first version of the game:

problem2

This caused a second problem, which I described at the beginning of the article: Even advanced players were not able to get very far in the game, compared to the beginners. It became impossibly hard to get past the low-to-mid range levels.

The Solution: Using Asymptotes!

After looking back at the game and performing this analysis, it was quite easy to come up with a solution to the problems: Instead of using a linear progression of gameplay mechanics, I simply implemented a function with an asymptote. This asymptote was positioned approximately around where I had determined that the gameplay mechanic was essentially impossible for humans. I also scaled out the function to make the curve start levelling out around the level 100 mark:

solution

This new approach enabled the players to both feel that the game was getting harder and allow them to slowly creep their way up to the highest level they could, anywhere between levels 10 and 25. This gave players a feeling of accomplishment as they played the game, because they could really see themselves getting better and better the more they played, but also understand that with each level the game was actually getting harder.

It’s nice to use a graphing calculator to help you make your function just right (Microsoft Math is one example). In the end, I feel that this approach has really made the Hideout! experience all that it could be and I’ve seen lots of players really get hooked on it and have a lot of fun.

Make sure to check out the new Hideout! to see what level you can get to! (And, of course, brag to your friends.)

720p Title Safe GUI Template

Saturday, June 19th, 2010

I am currently in the process of porting Hideout! to the Xbox 360 with XNA. As a part of this process, I needed to create a Photoshop GUI template for my GUI artist to ensure that our game would work well on all TVs. I thought this template may be useful to other developers as well:

Title-Safe-GUI-Template-PSD Title-Safe-GUI-Template-PNG

This file is essentially a compilation of notes from Best Practices for Indie Games and this topic. The guides (cyan lines) that you see are markings for 10% and 20% of total width and height. I chose these markings based on some comments by Shawn Hargreaves concerning development of professional games for the Xbox:

Native Xbox games have two different safe areas. They are strongly recommended to keep everything within 80%, and strictly required to keep everything within 90%. A single UI pixel outside the 90% region is an instant cert fail. UI outside the 80% region is going to get mentioned in the cert report, and they’ll most likely be asked to fix it, but if a big commercial developer pushes back and decides they don’t want to do that, it’s not a totally rigid requirement.

For indie games, there is no official cert and thus no rigid fail threshold. Our recommendation for indie games is exactly the same as for commercial titles: Microsoft thinks all games should keep all UI within the 80% region, and would love it if every developer would do this.

I have included notes about font size, as well as how much space is needed if you would like to make a 4:3 alternative.

4:3 Alternative

This 4:3 alternative can be achieved by simply changing the BackBuffer width in XNA to 960px instead of 1280px and letting XNA do the rest of the scaling. To stay within the 20% to 10% title safe area, you will need between 256 (20%) and 288 (10%) pixels in between left, center, and right aligned GUI elements. When you have shrunk the width to 960px, your GUI elements should still just fit within the title safe area after being moved closer together so long as you have left this extra space.

Concluding Thoughts

As a designer, I do understand the challenge of creating a well balanced and pleasing layout without being able to use 20% of your screen space… But that said, I must say that if you are looking to create a game that everyone can enjoy (which I hope you are), you should take on the challenge of keeping all critical elements within that 80% area. The minimum font size of 14 points is also a bit on the small size: Do not use this for common gameplay text, but instead lean towards around 20 point at minimum.

Simple, fast, GPU-driven multi-textured terrain

Thursday, May 6th, 2010

In this article, I will be outlining the method of multi-textured terrain that I used in my recently completed XNA Real Time Strategy (RTS) project, Toy Factory. I have also expanded this tutorial to cover organic (blended) and deformed terrain. This method enables you to produce a similar result to Riemer’s XNA Tutorial on Multitexturing, but allows for more flexible optimization of your terrain by separating the multi-texturing from the surface geometry.

Left: Toy Factory multi-texturing | Right: Deformed, organic multi-texturing

Left: Toy Factory multi-texturing | Right: Deformed, organic multi-texturing

If you are OK with having a flat terrain, this method will achieve extremely high framerates no matter how large your surface is. If you want deformed terrain, this method will still provide a strong starting point that allows for easy optimization of the geometry.

Part 1: Drawing some simple geometry

We’ll start with a large, square, two-triangle plane and draw a “ground” texture onto it using our own custom HLSL effect file. This ground texture will describe the type of terrain that we want to draw. In this case, hardwood is red, carpet is blue, and tile is green. This is the ground texture that I am going to use:

ToyFactory Multi-texturing Ground Texture

So our current goal is to simply draw that texture onto two very large triangles.

The C# Side

These variables need to be accessible to your initialization and drawing methods:

Effect terrainEffect;
VertexPositionTexture[] vertices;
VertexDeclaration vertexDeclaration;

Inside of your initialize method, let’s load the effect (we’ll put the HLSL code in “Content\Effects\Terrain.fx”) and set up the 6 vertices that we will be drawing:

            // Load the effect:
            terrainEffect = game.Content.Load<Effect>(@"Effects\Terrain");
            // Load the ground texture:
            Texture2D ground = game.Content.Load<Texture2D>(@"Ground");
            // Set the texture parameter of our effect to the ground:
            terrainEffect.Parameters["Ground"].SetValue(ground);

            // Initialize our verticies:
            vertices = new VertexPositionTexture[6];
            for (int i = 0; i < vertices.Length; i++)
                vertices[i] = new VertexPositionTexture();

            // Initialize our vertex declaration:
            vertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements);

            Vector3 topLeft = new Vector3(0f, groundHeight, 0f);
            Vector3 topRight = new Vector3(width, groundHeight, 0f);
            Vector3 bottomLeft = new Vector3(0f, groundHeight, height);
            Vector3 bottomRight = new Vector3(width, groundHeight, height);

            Vector2 topLeftTex = new Vector2(0f, 0f);
            Vector2 topRightTex = new Vector2(1f, 0f);
            Vector2 bottomLeftTex = new Vector2(0f, 1f);
            Vector2 bottomRightTex = new Vector2(1f, 1f);

            vertices[0].Position = topLeft;
            vertices[0].TextureCoordinate = topLeftTex;
            vertices[1].Position = bottomRight;
            vertices[1].TextureCoordinate = bottomRightTex;
            vertices[2].Position = bottomLeft;
            vertices[2].TextureCoordinate = bottomLeftTex;
            vertices[3].Position = topLeft;
            vertices[3].TextureCoordinate = topLeftTex;
            vertices[4].Position = topRight;
            vertices[4].TextureCoordinate = topRightTex;
            vertices[5].Position = bottomRight;
            vertices[5].TextureCoordinate = bottomRightTex;

The following code will go in your draw method to draw the vertices to the screen using the effect that we will create:

                terrainEffect.CurrentTechnique = terrainEffect.Techniques["Terrain"];
                terrainEffect.Parameters["View"].SetValue(camera.ViewMatrix);
                terrainEffect.Parameters["Projection"].SetValue(camera.ProjectionMatrix);

                terrainEffect.Begin();
                terrainEffect.CurrentTechnique.Passes[0].Begin();

                device.VertexDeclaration = vertexDeclaration;
                device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, numTriangles);

                terrainEffect.CurrentTechnique.Passes[0].End();
                terrainEffect.End();

The HLSL Side

Let’s call this file “Terrain.fx”. We’ve already referenced to the parameters and technique in this effect file in the above C# code. Note lines 12-14 where I ensure that no smoothing from the sampled texture happens by setting the min, mag, and mip filters to “None”. If you want to smooth between terrain types, you could try setting those values to “Linear” instead (but this will require modification of some code that we will look at latter in this tutorial).

// HLSL to simply sample from a texture

// Input parameters.
float4x4 View;
float4x4 Projection;

texture Ground;
sampler GroundSampler = sampler_state
{
    Texture = (Ground);

    MinFilter = None;
    MagFilter = None;
    MipFilter = None;
    AddressU = clamp;
    AddressV = clamp;
};

// Vertex shader input structure.
struct VS_INPUT
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

// Vertex shader output structure.
struct VS_OUTPUT
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};

// Vertex shader program.
VS_OUTPUT VertexShader(VS_INPUT input)
{
    VS_OUTPUT output;

    //generate the view-projection matrix
    float4x4 vp = mul(View, Projection);
    output.Position = mul(input.Position, vp);

    output.TexCoord = input.TexCoord;

    return output;
}

float4 PixelShader(VS_OUTPUT input) : COLOR
{
	float4 colour =	tex2D(GroundSampler, input.TexCoord);
	return colour;
}

technique Terrain
{
    pass Main
    {
        VertexShader = compile vs_2_0 VertexShader();
        PixelShader = compile ps_2_0 PixelShader();
    }
}

This should result in the following, which is already recognizable as a ground plane that follows the pattern we described in our “ground” texture (zoomed in on the top right corner of the texture):

ToyFactory Multi-texturing Ground - Drawn

ToyFactory Multi-texturing Ground Texture - Drawn in 3D

Part 2: Using the graphics card to decide which texture to draw

Now, time for some magic! Let’s pass in three more textures to our effect:

We can include these new textures the same way we did with the ground texture, but we’ll use them slightly differently:

The C# Side

In your initialize method, add lines 6-12 to load the textures:

            // Load the ground texture:
            Texture2D ground = game.Content.Load<Texture2D>(@"Ground");
            // Set the texture parameter of our effect to the ground:
            terrainEffect.Parameters["Ground"].SetValue(ground);

            // Load the terrain textures:
            Texture2D hardwood = game.Content.Load<Texture2D>(@"Hardwood");
            terrainEffect.Parameters["GroundText0"].SetValue(hardwood);
            Texture2D tile = game.Content.Load<Texture2D>(@"Tile");
            terrainEffect.Parameters["GroundText1"].SetValue(tile);
            Texture2D carpet = game.Content.Load<Texture2D>(@"Carpet");
            terrainEffect.Parameters["GroundText2"].SetValue(carpet);

            // Now let's set some new parameters that we will be using latter in our HLSL code:
            terrainEffect.Parameters["GroundText0Scale"].SetValue(terrainHardwoodDensity);
            terrainEffect.Parameters["GroundText1Scale"].SetValue(terrainTileDensity);
            terrainEffect.Parameters["GroundText2Scale"].SetValue(terrainCarpetDensity);

The HLSL Side

Now we have the different terrain types loaded into the effect as parameters. With these new terrain textures and their respective “scale” parameters, we can now draw them repeating across the entire ground plane — to do this, we use the “AddressU = wrap” and “AddressV = wrap” parameters in the sampler code for these new textures (see lines 13-14, 25-26, 37-38). Here’s what our new samplers in the HLSL should look like:

float GroundText0Scale;
float GroundText1Scale;
float GroundText2Scale;

texture GroundText0;
sampler GroundText0Sampler = sampler_state
{
    Texture = (GroundText0);

    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
};

texture GroundText1;
sampler GroundText1Sampler = sampler_state
{
    Texture = (GroundText1);

    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
};

texture GroundText2;
sampler GroundText2Sampler = sampler_state
{
    Texture = (GroundText2);

    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
};

Next, we will put some logic in our pixel shader that will sample from only one of the three terrain textures, depending on what colour we receive from the “ground” texture:

float4 PixelShader(VS_OUTPUT input) : COLOR
{
	float4 colour = tex2D(GroundSampler, input.TexCoord);

	if(colour.r == 1)
	{
		colour = tex2D(GroundText0Sampler, input.TexCoord * GroundText0Scale);
	}
	else if(colour.g == 1)
	{
		colour = tex2D(GroundText1Sampler, input.TexCoord * GroundText1Scale);
	}
	else
	{
		colour = tex2D(GroundText2Sampler, input.TexCoord * GroundText2Scale);
	}

	return colour;
}

This will result in Toy Factory-style multitexturing that is super-fast, no matter what the size of your ground is. You can adjust the scaling of each type of terrain by using the “Scale” parameters that are passed into the effect.

Toy Factory Multi-texturing

Toy Factory Multi-texturing

Part 3: Organic terrain blending

Though these hard cut edges worked well for our indoor environment, many games will require a smoother blending between terrain types to create the illusion of an organic and natural terrain. To achieve this effect, simply change the ground sampler’s min, mag, and mip filters and change the logic of the pixel shader:

texture Ground;
sampler GroundSampler = sampler_state
{
    Texture = (Ground);

    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    // use "clamp" to avoid unwanted wrapping problems at the edges due to smoothing:
    AddressU = clamp;
    AddressV = clamp;
};
float4 PixelShader(VS_OUTPUT input) : COLOR
{
 float4 groundSample = tex2D(GroundSampler, input.TexCoord);

 float4 colour = float4(0,0,0,1);
 colour += tex2D(GroundText0Sampler, input.TexCoord * GroundText0Scale) * groundSample.r;
 colour += tex2D(GroundText1Sampler, input.TexCoord * GroundText1Scale) * groundSample.g;
 colour += tex2D(GroundText2Sampler, input.TexCoord * GroundText2Scale) * groundSample.b;

 return colour;
}

Let’s use these three textures instead:

This will result in the following, more organic looking terrain:

Toy Factory Multi-texturing - Organic

Toy Factory Multi-texturing - Organic

You can also modify the “ground” texture to include softer edges between values to have a smoother blending of terrain types.

Part 4: Mapping the terrain to complex geometry (deformable terrain)

When developing the fog of war for Toy Factory, I discovered a great trick that Catalin Zima used in his fog of war sample. In his sample he used world coordinates to determine the colour/alpha value for a given pixel in the pixel shader. This concept can also be applied in our sample by simply using the X and Y world coordinates to determine the texture coordinate that will be passed to the sampler.

The C# Side

Our C# code can change a bit now because we are no longer using texture coordinates. Instead, we will purely be using world coordinates. At this point you can change the vertex declaration to use VertexPositionColor, which is a little faster than the old VertexPositionTexture due to less data being sent to the graphics card. Or, you can scrap the old vertex buffer altogether and draw your own imported model, a custom mesh that has been optimized using a quadtree, or anything else you want!

Add the following to your initialize method:

            terrainEffect.Parameters["MapWidth"].SetValue(mapWidth);
            terrainEffect.Parameters["MapHeight"].SetValue(mapHeight);

The following code will draw an existing model with the effect, but you could also use a heightmap-generated mesh as well.

                foreach (ModelMesh mesh in terrainModel.Meshes)
                {
                    foreach (Effect effect in mesh.Effects)
                    {
                        effect.CurrentTechnique = terrainEffect.Techniques["Terrain"];
                        effect.Parameters["View"].SetValue(camera.ViewMatrix);
                        effect.Parameters["Projection"].SetValue(camera.ProjectionMatrix);
                        mesh.Draw();
                    }
                }

The HLSL Side:

Add the following two parameters:

float MapWidth;
float MapHeight;

And change your vertex and pixel shaders and input/output structures:

// Vertex shader input structure.
struct VS_INPUT
{
    float4 Position : POSITION0;
};

// Vertex shader output structure.
struct VS_OUTPUT
{
    float4 Position : POSITION0;
    float4 WorldPos : TEXCOORD0;
};

// Vertex shader program.
VS_OUTPUT VertexShader(VS_INPUT input)
{
    VS_OUTPUT output;

    //generate the view-projection matrix
    float4x4 vp = mul(View, Projection);
    output.Position = mul(input.Position, vp);
    output.WorldPos = input.Position;

    return output;
}

float4 PixelShader(VS_OUTPUT input) : COLOR
{
	float2 mapPosition = float2(input.WorldPos.x / MapWidth, input.WorldPos.z / MapHeight);
	float4 groundSample = tex2D(GroundSampler, mapPosition);

	float4 colour = float4(0,0,0,1);
	colour += tex2D(GroundText0Sampler, mapPosition * GroundText0Scale) * groundSample.r;
	colour += tex2D(GroundText1Sampler, mapPosition * GroundText1Scale) * groundSample.g;
	colour += tex2D(GroundText2Sampler, mapPosition * GroundText2Scale) * groundSample.b;

	return colour;
}

And here’s the result — It needs a bit of artistic work, but all the functionality that you need should be at your fingertips!

Toy Factory Multi-texturing - Organic & Deformed

Toy Factory Multi-texturing - Organic & Deformed