- Home /
True 3D Selection for RTS
Hi!
Im wondering how one would implement a REAL 3D selection. It's well documented how to make a 2D Rect with the GUI canvas system. But what if one wants the selection plane to appear ontop of the terrain? This would also mean that the selection rect has to compensate for height differences on the map....
I thought about using a projector, but I'm not able to figure out how to set an exact projcetion "rect" rather than an orthographic square.
A first idea is using a projector that is big enough to project on the entire terrain (meh). Then, when the user "draws" a selection, dynamically paint on a texture thats as big as the terrain (more meh) and then set this texture for the projector. This seeems like a heavy task and doesn't really feel "right".
To clarify: I'm not looking for a finished code script, I'm rather curious how a Unity - pro would solve this task.
What I want is similar to the zoning mechanism in Cities Skylines: https://www.skylinescity.com/wp-content/uploads/2014/08/cities-skylines.png
Answer by theLittleSettler · Oct 20, 2017 at 09:27 AM
Is the selection still in camera space? I'm not exactly sure what you mean by real 3d, but no matter where the plain is, the solution would be similar, because a 2D plane in camera space isn't so different from a 2D plane in an arbitrary space.
So anyway... for finding what is selected, I would have a script with a list of all possible selectable Transforms. Next, I'd implement an IsWithin method. Something with a signature like
public static bool IsWithin(this Transform transform, Vector3 dragPoint0, Vector3 dragPoint1)
If the plain is created from camera space, then its pretty simple (camera.WorldToScreen; then 2D check). Otherwise, I'd write a WorldToPlane method.
Next, to draw it. A projector does sound like the "correct" solution. They should be efficient for such a task (?). But I haven't used them yet, so I'll offer another option.
I'd use shaders. I'd edit the terrain shader to include it directly (because exactly matching a mesh to the terrain's shape with an overlay effect would be more difficult).
For the selectable objects, I'd overlay a semitransparent unlit shader. It would be seperate to the main material of the objects, so that I could still use whatever material I'd like, for convenience. So I'd only need one selection shader for general meshes.
Hi, thanks for your response. To be clear on what I want: http://answers.unity3d.com/questions/442581/how-to-draw-a-grid-over-parts-of-the-terrain.html
This is a similar question, although I dont want to draw a fixed sized grid but a rectangle which is specified by mouse input. The provided solutions mostly use a mesh which is overlayed on the terrain. As you correctly point out, this is a. more difficult and b. not as "beautiful".
$$anonymous$$odifying the terrain shader could be a good idea, but I'm quite new to shader program$$anonymous$$g. How would you modify that shader? I would need several inputs, such as mouse position and if the mouse button is clicked.
well, first you'll need to copy all the relevent files for the terrain shaders. Its not too complicated, but there are a few. First, there's three actual terrain shaders 1 - the first pass terrain shader that draws the first four textures 2 - the add pass shader, drawing each successive four textures 3 - a simple distant shader
And a couple of include files but the one you might need is TerrainSplatmapCommon.
So first, copy the relevent files...I guess just add pass and first pass, along with TerrainSplatmapCommon.
Then I'd edit the SplatmapFinalColor function in TerrainSplatmapCommon, using IN.tc_Control (that's the uv coordinates to the splatmap). You could edit it later in the process, to make it occur after lighting, fog, etc. You can always move when it occurs though...have a look at the finalcolor function in the unity docs.
Anyway, you'd have to pass the two positions defining the rectangle to the shader (Shader.SetGlobalFloat, or material.SetFloat), relative to the terrain position, scaled to the 0..1 range.
The shader code would be something like:
fixed4 selectionColor; // rgb, a is blend amount. This'd be a property (add to shaders property blocks), for convenience.
float2 dragPosition0, dragPosition1; // x, z position. Not properties, only applied through code. There used to be a thing where unity wouldn't set the values through code if something is a property, not sure if its still the case.
void SplatmapFinalColor(Input IN, TERRAIN_SURFACE_OUTPUT o, inout fixed4 color)
{
<snip>
float2 $$anonymous$$ = ...;
float2 max = ...;
if (IN.tc_Control.x < $$anonymous$$.x && ... etc)
{
// mix, because if you just added colour you might be adding it beyond the colour range
color.rgb = color.rgb * (1.0 - selectionColor.a) + selectionColor.rgb * selectionColor.a;
}
}
Hope that's enough to get you going. Oh, and after I'd finished I'd rename the shaders (and change related stuff, which is the #include statements, and the menu path for each shader) so I could still use the unedited terrain shaders if I wanted.
It works!
Only problem I have is, that if i dont set the "selection color" value of the shader in each frame, the selection is not visible. Further, if i dont CHANGE the color in each frame, no selection is visible. This is the shader code:
//FirstPass.shader:
Properties {
......
_SelectionColor("Selection Color", Color) = (1,1,1,1)
}
//SplatmapCOmmon.cginc:
....
fixed4 _SelectionColor;
...
void SplatmapFinalColor(Input IN, TERRAIN_SURFACE_OUTPUT o, inout fixed4 color)
{
......
float xBegin = $$anonymous$$(_$$anonymous$$ousePosStart.x, _$$anonymous$$ousePosEnd.x);
float xEnd = max(_$$anonymous$$ousePosStart.x, _$$anonymous$$ousePosEnd.x);
float yBegin = $$anonymous$$(_$$anonymous$$ousePosStart.y, _$$anonymous$$ousePosEnd.y);
float yEnd = max(_$$anonymous$$ousePosStart.y, _$$anonymous$$ousePosEnd.y);
if (IN.worldPos.x > xBegin && IN.worldPos.x < xEnd)
{
if (IN.worldPos.z > yBegin && IN.worldPos.z < yEnd)
{
color.rgb = color.rgb * (1.0 - _SelectionColor.a) + _SelectionColor.rgb * _SelectionColor.a;
}
}
}
Well, that's not something I've seen before, but my guess is _SelectionColor is being used by something else, globally. Or maybe something is assigning the terrain material per frame.
I changed the name, still nothing. Could it be the ordering in the shader code? I have no idea how and when the suf and SplatmapFinalColor functions are drawn, but ofcourse my SplatmapFinalColor should be the last one, right? How can i enforce that?
It works now! For the sake of readability, I will add the entire process as seperate answer. Thanks again for your help!
Answer by Hafnernuss · Oct 23, 2017 at 03:24 PM
Ok, this is basically what you need:
First, download and modify the built in terrain shaders for your unity version: https://unity3d.com/de/get-unity/download/archive
The ones you are interested in are:
1. CGIncludes/TerrainSplatmapCommon.cginc
2. DefaultResourcesExtra/TerrainShaders/Splats/Standard-FirstPass.shader
3. DefaultResourcesExtra/TerrainShaders/Splats/Standard-AddPass.shader
Copy these Shaders to your unity project. To achieve something like i wanted, just look at the files attached
Note that i put all variables that i modify into the properties panel of the FirstPass.shader. I dont know why, but you cannot set the variables from your code if you dont. In your script, just set the positions like that:
Terrain.activeTerrain.materialTemplate.SetFloat("_MousePosStartX", DragStart.x);
Dont forget to create a new material and set the FirstPass as shader. (Change the name of your FirstPass at the top of the file to something like "Custom/Terrain"
Set this material as the material for your terrain:
Your answer
Follow this Question
Related Questions
Multiple Cars not working 1 Answer
need help regarding rotating arm 0 Answers
Creating A Seesaw Lever 0 Answers
Help 3D Enemy Detection Visual Feedback 3 Answers