tag:blogger.com,1999:blog-78075848305876213282024-03-13T12:03:05.837-07:00XBox One Indie DevelopmentCharles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.comBlogger19125tag:blogger.com,1999:blog-7807584830587621328.post-53268523143503784732016-01-04T18:01:00.001-08:002016-01-04T18:01:45.902-08:00Serializing and Saving/Loading Data in Unity3D for UWP 10My reason for this post has come from me wanting to both port existing game
projects and create new projects to be released on the Windows 10 Store. To do
this is pretty simple and the wonderful <a href="https://twitter.com/shahedC">Shahed</a> has already done a great <a href="http://wakeupandcode.com/publish-a-windows-10-game-with-unity-5/">blog
post</a> on the basics of getting your Unity3D projects ported to UWP 10<br />
<br />
This is a great place to start, but, if like me, you like to serialize your
game data and write it to a file, then you will probably come across the issues
I had.<br />
<br />
My issues were namely around the restrictions on the namespaces that can be
used in the UWP 10 build. To my knowledge, we can’t use binary serializers for
UWP and I think the use of <a href="https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer(v=vs.110).aspx">DataContractSerializer</a>DataContractSerializers
are recommended (I like using them anyway)<br />
<br />
So, I decided, after having all sorts of issues porting an existing project
to start from the beginning and create a game project for UWP 10 from the get
go.<br />
<br />
I created a class called Dude, this was going to store my player data,
position, name score, etc.. I made this class a <a href="https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractattribute%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396">DataContract</a>
and the properties I wanted to save <a href="https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datamemberattribute(v=vs.110).aspx">DataMemeber</a>s,
all good so far…<br />
<br />
<br />
I then created a simple Serializer class that I can use to serialize any
class that is a DataContract.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-lzPpa-C2Jz0/VosiWUOAooI/AAAAAAAACHc/CWf9IhsAWy8/s1600/Blog1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="396" src="http://1.bp.blogspot.com/-lzPpa-C2Jz0/VosiWUOAooI/AAAAAAAACHc/CWf9IhsAWy8/s400/Blog1.jpg" width="400" /></a></div>
<br />
As you can see it’s pretty simple.<br />
<br />
Within my Dude class I created simple load and save methods that would use
the serializer<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-7doMYvLUgDs/Vosi2dpCBMI/AAAAAAAACHk/HHij-Tx9NkY/s1600/Blog2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="http://1.bp.blogspot.com/-7doMYvLUgDs/Vosi2dpCBMI/AAAAAAAACHk/HHij-Tx9NkY/s400/Blog2.jpg" width="400" /></a></div>
<br />
<br />
Again, pretty simple stuff right?<br />
<br />
So, running this as a regular PC game all works fine, it builds, my game
runs, I can save and re load player data perfectly.<br />
<br />
Switch to a Universal 10 build and it all went a bit wrong lol<br />
<br />
In the serializer class I got one compile error reported by the Unity
editor<br />
<br />
<strong>Assets\Scripts\Utilities\Serializer.cs(40,23): error CS1061:
'MemoryStream' does not contain a definition for 'Close' and no extension method
'Close' accepting a first argument of type 'MemoryStream' could be found (are
you missing a using directive or an assembly reference?)</strong>
<br />
<br />
<br />
It seemed that under UWP 10, the Close method on the MemoryStream is no
longer there, this was simply fixed by wrapping the Close method in a #if for
the appropriate build type. It may well be better to just host the MemoryStream
in a using (){} as I am not sure if doing it like this could result in memory
issues on UWP 10 due to the stream not getting closed.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-6gmTHoC1F5I/VosjObO3TqI/AAAAAAAACHs/cQCyEEaveXs/s1600/Blog3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="http://1.bp.blogspot.com/-6gmTHoC1F5I/VosjObO3TqI/AAAAAAAACHs/cQCyEEaveXs/s400/Blog3.jpg" width="372" /></a></div>
<br />
Now, my dude class had more issues… All the issues in my save and load methods
were due to the System.IO.Stream and System.Text.Encoding, this took me a little
longer to sort out, but after some Google/Bing searches I discovered that
Unity3D provides a UnityEngine.Windows namespace that holds a new <a href="http://docs.unity3d.com/ScriptReference/Windows.Directory.html">Dictionary</a>
and <a href="http://docs.unity3d.com/ScriptReference/Windows.File.html">File</a>
objects that can be used for saving the data, once I found that out, fixing the
code was as simple as it was in the serializer class.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-GHiJNqGNDtU/VosjnhUPChI/AAAAAAAACH0/t8aPO9dD5ew/s1600/Blog4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="http://4.bp.blogspot.com/-GHiJNqGNDtU/VosjnhUPChI/AAAAAAAACH0/t8aPO9dD5ew/s400/Blog4.jpg" width="400" /></a></div>
<br />
<br />
I hope this will help if you come across these issues, as I pulled out a fair
bit of my hair solving this, and I have to tell you, I have little hair to start
with :P<br />
<br />
<br />
<br />Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com2tag:blogger.com,1999:blog-7807584830587621328.post-72779084534693077012015-02-24T04:25:00.001-08:002015-02-24T04:25:56.826-08:00Unity3D Shaders–Multi Pass Surface Shader<p align="center"><a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-lighting.html"><< Previous</a> Next >></p> <p><a href="http://lh6.ggpht.com/-_kygbhVC42o/VOxtq9u-wWI/AAAAAAAACEI/dZnWhoG3fTE/s1600-h/image13.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh5.ggpht.com/-VydaSlOtaBw/VOxtruCis_I/AAAAAAAACEQ/ffXvR1Mf0Do/image_thumb5.png?imgmax=800" width="511" height="287"></a></p> <p>In this post we are going to be looking at making a multi pass shader, and I can hear you say</p> <blockquote> <p>“What exactly is a multi pass shader?”</p></blockquote> <p>So, up until now we have been looking at single pass shaders, so the vertices are passed into the render pipeline, and the shader applies one vertex (we don’t have to write this with a surface shader) and one surface shader to the vertex data passed.</p> <p>With a multi pass shader, the shader resends the vertex data to the render pipeline and applies the next pass of functions to the vertex data.</p> <p>In the image above, in the first pass we call the same methods we did for our diffuse shader, so a simple lighting model and a simple surface shader, in the second pass we execute the code to draw the toon like outline you can see.</p> <p>Creating multiple passes in a surface shader is as simple as adding another CGPROGRAM section, the order you create the passes is the order of execution. There is a little more to it for vertex & fragment shaders, but we will cover those in another post.</p> <p>Let’s create another shader file in the Unity editor, just as we have before, also create a material for it and assign our new shader to it. Call the shader SSOutline, the material SSOutlineMaterial. Apply the material to your model as we have over the past few posts. </p> <p>For simplicity I am going to create a Capsule and apply my material to that.</p> <p>First thing we will do it take the code from our diffuse shader we did earlier and put it in our new SSOutline shader, it should look something like this now</p> <div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:92e6a1e7-570c-4fc4-b97a-a040eb07b891" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">Shader "Randomchaos/Tutorial/SSOutline" <br />{<br /> Properties <br /> {<br /> _MainTex ("Diffuse", 2D) = "white" {}<br /><br /> }<br /> SubShader <br /> {<br /> Tags { "RenderType"="Opaque"}<br /> LOD 200<br /> <br /><br /> // Geom pass<br /> CGPROGRAM<br /> #pragma surface surf SimpleDiffuse <br /><br /> sampler2D _MainTex;<br /> <br /> half4 LightingSimpleDiffuse (inout SurfaceOutput s, half3 lightDir, half atten)<br /> {<br /> half4 c;<br /><br /> half NdotL = saturate(dot(s.Normal, lightDir));<br /><br /> c.rgb = s.Albedo * _LightColor0.rgb * NdotL;<br /> c.a = s.Alpha;<br /> return c;<br /> } <br /><br /> struct Input <br /> {<br /> float2 uv_MainTex;<br /> float2 uv_Bump;<br /> };<br /><br /> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> half4 c = tex2D (_MainTex, IN.uv_MainTex);<br /> o.Albedo = c.rgb;<br /> o.Alpha = c.a;<br /> }<br /> ENDCG<br /> }<br /> FallBack "Diffuse"<br />}</pre></div><br /><p>Pretty much the same thing we had before, I can now pass this shader a texture and it will be applied to my mesh like this.</p><br /><p><a href="http://lh3.ggpht.com/-Hh1s4fHQ7Lc/VOxtsG2ueNI/AAAAAAAACEY/H1-_-y4BQn8/s1600-h/image%25255B3%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh4.ggpht.com/-zcmJxNC7Dys/VOxts8EMVsI/AAAAAAAACEg/nuU_HH3dJFM/image_thumb%25255B1%25255D.png?imgmax=800" width="479" height="271"></a></p><br /><p>Now, we want to be able to render an outline around this mesh, so we will need to be able to give it at least two values to do this, the colour of the line we want and the thickness. All we need to do, to enable the shader to take these two new parameters is to add them to the properties at the top of the shader.</p><br /><p>I am going to have _OutlineColor to denote the colour of the outline (forgive me mixed spelling of colour, one is the English spelling, colour, the other is the American, color, the later is used in code) and I am going to have _OutlineSize to denote how thick I want the line to be.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:5a18b34a-5034-4711-96ed-490804613483" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> Properties <br /> {<br /> _MainTex ("Diffuse", 2D) = "white" {}<br /><br /> _OutlineColor ("Outline Color",Color) = (0,0,0,1)<br /> _OutlineSize ("Outline Size", float) = .05<br /> }</pre></div><br /><p>I have given them the default values of black and .05 respectively. At this point we can still use and run our shader like this, but no matter what we do with those parameters it is not going to change what is rendered, this is where our second pass it used.</p><br /><p>The second pass is effectively going to render the vertices again, only this time we are going to manipulate the vertex data in the vertex shader so that it draws the mesh slightly larger than the original mesh rendered, using the _OutlineSize parameter. </p><br /><p>Then in the second pass surface shader, we are going to render the mesh using the _OutlineColor provided and have a lighting calculation that just render’s the line with no lighting calculations applied to it.</p><br /><p>This is what this pass will look like, but I will go over the components and describe what is going on in each section.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b382c4a3-c724-4f51-ad50-1ea09924ca40" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> // Outline pass<br /><br /> CGPROGRAM<br /> #pragma surface surf LineLit vertex:vert<br /><br /> float4 _OutlineColor;<br /> float _OutlineSize;<br /><br /> void vert (inout appdata_full v) <br /> {<br /> v.vertex.xyz += v.normal * _OutlineSize;<br /> }<br /><br /> half4 LightingLineLit (inout SurfaceOutput s, half3 lightDir, half3 viewDir) <br /> { <br /> return float4(s.Albedo,s.Alpha);<br /> }<br /><br /> struct Input <br /> {<br /> float2 uv_MainTex;<br /> float2 uv_Bump;<br /> };<br /><br /> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> o.Albedo = _OutlineColor;<br /> o.Alpha = _OutlineColor.a;<br /> }<br /> ENDCG</pre></div><br /><p>First thing we do is let the render pipeline know we are starting a new CGPROGRAM, like before we set up the shaders to be used with a #pragma, but there are some differences here, what are they?</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:8de7fb15-e0e9-4083-971b-09dd54f9e0d6" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">#pragma surface surf LineLit vertex:vert</pre></div><br /><p>As before, we are telling it our surface shader is called surf and that we will be using our own lighting calculation called LineLit, but we are also telling it that we are also using our own vertex shader called vert, rather than use the one Unity supplies for us.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2a1323a6-f2d6-4436-bdd7-fcbb1a0097fb" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> void vert (inout appdata_full v) <br /> {<br /> v.vertex.xyz += v.normal * _OutlineSize;<br /> }</pre></div><br /><p>Again, remember back to the first post we did when creating the data stored in the vertex?</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2cf543f2-f601-459b-895c-197b8203fda4" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">// Set up position data.<br />positions.Add(new Vector3(-.5f, .5f, 0)); // Top left corner<br />positions.Add(new Vector3(-.5f, -.5f, 0)); // Bottom left corner<br />positions.Add(new Vector3(.5f, -.5f, 0)); // Bottom right corner<br />positions.Add(new Vector3(.5f, .5f, 0)); // Top right corner</pre></div><br /><p>This position data is stored in the v.vertex elements, remember this is a vertex shader, so each vertex on the mesh is set up ready for the screen in here and this data is no interpolated like in the pixel and surface shaders, it is the raw data in the vertex as it was created.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d0630129-d7c5-4ecd-acc0-da658e376932" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> // Create our own normals<br /> normals.Add(Vector3.back);<br /> normals.Add(Vector3.back);<br /> normals.Add(Vector3.back);<br /> normals.Add(Vector3.back);</pre></div><br /><p>Again, this normal data is stored in the v.normal for this vertex, and as with the position, this is the raw un interpolated data that is used to render the triangles on the screen.</p><br /><p>In this vertex shader v is an instance of a predefined Unity vertex structure that will get populated by our mesh’s data. In this case it is using appdata_full, there are a fwe pre defined structures, but appdata_full has all the vertex data elements you are most likely to need. If you would like more information on these structures check out <a href="http://docs.unity3d.com/Manual/SL-BuiltinIncludes.html">this Unity document</a>.</p><br /><p>So, in the vertex shader we are saying, move the position of this vertex in the direction of the normal (remember a range of 0-1 for each axis) by a multiple of _OutlineSize. This will result in the mesh being drawn again, but scaled by _OutlineSize.</p><br /><p>The surface shader can then just set the Albedo and Alpha to the colour we have chosen to render the line in and the lighting calculation just returns the colour without any lighting calculations on it.</p><br /><p>BUT, if we run this shader now, all we will get is a slightly bigger version of our mesh rendered over the first pass like this</p><br /><p align="center"><a href="http://lh3.ggpht.com/-qiiwOtD4KAg/VOxttokIIVI/AAAAAAAACEo/cWAzT1kqtNA/s1600-h/image%25255B7%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://lh4.ggpht.com/-YxOIqSvr-Qw/VOxtuJJDEeI/AAAAAAAACEs/zChcoT2rMBU/image_thumb%25255B3%25255D.png?imgmax=800" width="463" height="262"></a></p><br /><p>How do we get this to be an outline of our mesh as opposed to just blocking it out?</p><br /><p>Well, when the render pipeline draws triangles, it has a culling calculation so that any triangles that are facing away from it are removed from the render pipeline and so don’t need to be drawn. In the case of Unity this is what’s known as a backside cull, that is to say, any triangles facing away are removed from the pipeline, but we want front a side cull, how do we tell the shader, that for this pass we want to only see the back of the triangles?</p><br /><p>Just before we start the CGPROGRAMM block for the second pass we can change some of the pipeline settings, so in the lines between my comment and the CGPROGRAM block we can set the cull mode to forward like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:400f66c1-ffc4-4cc8-842d-ce6eb86ff537" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> // Outline pass<br /> Cull Front<br /><br /> CGPROGRAM<br /> #pragma surface surf LineLit vertex:vert<br /><br /> float4 _OutlineColor;<br /> float _OutlineSize;<br /></pre></div><br /><p>Cull has three settings, Back, Front and None. Back is the default, and None means it will render both sides of your triangles, so if you had something like a flag or a window and wanted to see both sides of the thin mesh you would use None.</p><br /><p>Our render now looks like this</p><br /><p><a href="http://lh3.ggpht.com/-ASP0WM8hTWU/VOxtuh2QeQI/AAAAAAAACE4/eWwX56pekwE/s1600-h/image%25255B11%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-mriJ-UuAo90/VOxtvL1VDkI/AAAAAAAACE8/PccAZCa5wlQ/image_thumb%25255B5%25255D.png?imgmax=800" width="474" height="268"></a></p><br /><p>We can now adjust the thickness and colour of the outline to suit the look we want.</p><br /><p><a href="http://lh5.ggpht.com/-qqjG49Vl4aM/VOxtvzPd_3I/AAAAAAAACFE/_toaxQJigTU/s1600-h/image%25255B21%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh5.ggpht.com/-oWYv2gEH138/VOxtwXwzUPI/AAAAAAAACFM/Kh_x035aaXs/image_thumb%25255B9%25255D.png?imgmax=800" width="475" height="269"></a></p><br /><p> <a href="http://lh5.ggpht.com/--WahRm45J8o/VOxtxDFIhhI/AAAAAAAACFY/aVwNjnfTxIQ/s1600-h/image%25255B22%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-H9wRdv1rrPQ/VOxtx0_WhdI/AAAAAAAACFc/C-_rRRCMT2A/image_thumb%25255B10%25255D.png?imgmax=800" width="477" height="270"></a></p><br /><p><a href="http://lh6.ggpht.com/-aP5CHj-RlBQ/VOxtyixzZqI/AAAAAAAACFo/F7riXFZkY_Y/s1600-h/image%25255B23%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh5.ggpht.com/-nhX-AExwNeQ/VOxtzcv3CoI/AAAAAAAACFs/q7Xi-MScuZo/image_thumb%25255B11%25255D.png?imgmax=800" width="477" height="270"></a></p><br /><p>Or on a more complex mesh like this</p><br /><p><a href="http://lh5.ggpht.com/-sRVxjz8tupY/VOxt0OS4NKI/AAAAAAAACF4/BWX6EdH6klA/s1600-h/image%25255B27%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-tQUZNaxLR1A/VOxt0trDEFI/AAAAAAAACGA/wyXsoO9doqU/image_thumb%25255B13%25255D.png?imgmax=800" width="496" height="281"></a></p><br /><p>The whole SSOutline shader looks like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ac92bf5e-51a4-4a94-b59a-e25e3fc2e1b4" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c">Shader "Randomchaos/Tutorial/SSOutline" <br />{<br /> Properties <br /> {<br /> _MainTex ("Diffuse", 2D) = "white" {}<br /><br /> _OutlineColor ("Outline Color",Color) = (0,0,0,1)<br /> _OutlineSize ("Outline Size", float) = .05<br /> }<br /><br /> SubShader <br /> {<br /> Tags { "RenderType"="Opaque"}<br /> LOD 200<br /> <br /><br /> // Geom pass<br /> CGPROGRAM<br /> #pragma surface surf SimpleDiffuse <br /><br /> sampler2D _MainTex;<br /> <br /> half4 LightingSimpleDiffuse (inout SurfaceOutput s, half3 lightDir, half atten)<br /> {<br /> half4 c;<br /><br /> half NdotL = saturate(dot(s.Normal, lightDir));<br /><br /> c.rgb = s.Albedo * _LightColor0.rgb * NdotL;<br /> c.a = s.Alpha;<br /> return c;<br /> } <br /><br /> struct Input <br /> {<br /> float2 uv_MainTex;<br /> float2 uv_Bump;<br /> };<br /><br /> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> half4 c = tex2D (_MainTex, IN.uv_MainTex);<br /> o.Albedo = c.rgb;<br /> o.Alpha = c.a;<br /> }<br /> ENDCG<br /><br /> // Outline pass<br /> Cull Front<br /><br /> CGPROGRAM<br /> #pragma surface surf LineLit vertex:vert<br /><br /> float4 _OutlineColor;<br /> float _OutlineSize;<br /><br /> void vert (inout appdata_full v) <br /> {<br /> v.vertex.xyz += v.normal * _OutlineSize;<br /> }<br /><br /> half4 LightingLineLit (inout SurfaceOutput s, half3 lightDir, half3 viewDir) <br /> { <br /> return float4(s.Albedo,s.Alpha);<br /> }<br /><br /> struct Input <br /> {<br /> float2 uv_MainTex;<br /> float2 uv_Bump;<br /> };<br /><br /> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> o.Albedo = _OutlineColor;<br /> o.Alpha = _OutlineColor.a;<br /> }<br /> ENDCG<br /> }<br /> FallBack "Diffuse"<br />}</pre></div><br /><p>As ever, hope you have found this post useful and if you have any questions or critique, please post them in the comments section :)</p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><p><a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-lighting.html"><< Previous</a> Next >></p> Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com3tag:blogger.com,1999:blog-7807584830587621328.post-10525558863269134862015-02-19T15:00:00.001-08:002015-02-24T04:26:59.958-08:00Unity3D Shaders–Surface Shaders : Lighting Models<div align="center">
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-continued.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shadersmulti-pass-surface-shader.html">Next >></a></div>
Continuing with surface shaders, in this post I am going to look at how you can create your own lighting models.<br />
Now, lighting models are just the ability to calculate how the light(s) bounces off the surface of the geometry. It does not the ability to do your own shadow mapping. <br />
So, as I said in an earlier post, we declare the lighting model to be used in the shader, in the #pragma section, in all our other surface shaders we have used the Lambert lighting model, we can also use BlinnPhong.<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d51b63bb-7f1b-4121-babe-6a21f2ca95ef" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> #pragma surface surf Lambert</pre>
</div>
<br />
Lighting models can be written for both forward and deferred rendering pipelines, you can alter the render mode in Unity, by default it uses forward rendering and I think deferred is a pro feature. <br />
<br />
Forward rendering is when each light is calculated against each mesh, so the more lights you have the slower things are going to get. Normally a forward render will not have more than 3 lights in it as after that light calculations start to lag.<br />
<br />
Deferred rendering is done once ALL the mesh’s are rendered and they are applied to the whole scene in one go, so with deferred you can have lots and lots of lights will very little slow down.<br />
<br />
As Unity uses forward render and deferred it a pro feature I am going to focus on forward surface shader lighting models.<br />
<br />
Lets create our own lighting model. To do this we need to give it a name. For this first lighting model we will go with a simple diffuse, so all we want to do in the lighting model is find the angle of the light source bouncing off the surface. As it is a surface shader this done on each pixel of each triangle on the mesh being rendered.<br />
<br />
Add a shader to your Shaders asset folder and name is SSDiffuse, create a new material called SSDiffuseMaterial, associate the shdaer with the material as we have done in the previous posts.<br />
<br />
It should looks something like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6066f4b4-931c-4afa-b3b9-7ca58ddfba02" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">Shader "Randomchaos/Tutorial/SSDiffuse"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
float _Ramp =1;
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}</pre>
</div>
<br />
Put a mesh into your scene and apply this shader to it, I have used Dude from the old MS XNA Demos, and with the above shader, he looks like this<br />
<br />
<a href="http://lh5.ggpht.com/-1M-Ci9i9Omc/VOZq0yUfzbI/AAAAAAAACCA/cJCc2io-KIk/s1600-h/image%25255B3%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-ZtTeuem9fyA/VOZq1h5y3sI/AAAAAAAACCE/HDDJq6PblIs/image_thumb%25255B1%25255D.png?imgmax=800" height="288" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="513" /></a><br />
<br />
Lets set up our own lighting model in this shader. First thing we need to do is tell the shader to use our new lighting model, we well call it SimpleDiffuse and replace Lambert with our new model in the #pragma like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:fed9aef6-dfe6-4c4a-938c-35da20fc681a" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> #pragma surface surf SimpleDiffuse</pre>
</div>
<br />
Now we need to provide a function that gets called to do our lighting calcs, in forward rendering, there are two function signatures we can use, this first is for lighting models that we don’t need to know the cameras view direction, like a diffuse shader, the other is for shaders that do, so things like specular calculations (thanks Unity docs).<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:eb45758a-07fc-418b-b8a1-7fbaedb4883c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half atten);</pre>
</div>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bde282dd-81ae-44b0-b861-0c8656c1dd2e" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">half4 Lighting<Name> (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten); </pre>
</div>
<br />
As we can see, the method name needs to start with Lighting followed by the name of our model, in our case SimpleDiffuse, so our function will look like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2dc8b2ba-a321-44eb-820b-2dbba72319ec" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> half4 LightingSimpleDiffuse (SurfaceOutput s, half3 lightDir, half atten)
{
return 1;
}</pre>
</div>
<br />
If se save our shader now and check out our rendered mesh, it will look like this<br />
<br />
<a href="http://lh5.ggpht.com/-cTAgyWx3gns/VOZq2ERmAfI/AAAAAAAACCM/kxck9tdIcF8/s1600-h/image%25255B7%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-LFsdzYN5bxc/VOZq2o68TaI/AAAAAAAACCU/BsWDz3LXzIs/image_thumb%25255B3%25255D.png?imgmax=800" height="296" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="523" /></a><br />
<br />
The whole thing is white! That’s because, we are returning the value of 1, but the shader is expecting a half4 (half4 is a small float3 effectively), why is there no error thrown?<br />
<br />
It’s because when we set a float<n> or half<n> or any other vector to a single value like this, all the elements of the vector are set to that value, so, in this case 1,1,1,1 is returned. <br />
<br />
So in our lighting model we are saying that the surface is being lit at every angle with the colour white. If we change our lighting calculation to look like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b72bf7a1-8f69-4ce4-aba3-365a31d73b73" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> half4 LightingSimpleDiffuse (SurfaceOutput s, half3 lightDir, half atten)
{
return _LightColor0;
}</pre>
</div>
<br />
_LightColor0 is pre loaded by Unity for us, this value is set each time the light calculation is done for another light, so this will also handle multiple lights in a forward rendered scene.<br />
<br />
We can then play with the lights colour in the scene or game window and we can see that the texture is still being rendered, but when we set the colour to white in the lighting model, that’s all we see, so the surf shader is being called then the lighting model is being applied to it.<br />
<br />
<a href="http://lh4.ggpht.com/-yjjiNqk2TvM/VOZq3Kr99xI/AAAAAAAACCg/pEi-bqt7RGg/s1600-h/image%25255B11%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-YhCvND2tb8k/VOZq3_4qi5I/AAAAAAAACCk/gauL97iLSj4/image_thumb%25255B5%25255D.png?imgmax=800" height="280" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="495" /></a><br />
<br />
OK, lets start to make this shader start to look like a diffuse effect. We can see in our function signature, we are getting passed a structure called SurfaceOutput. This structure gives us a few elements we can use<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b73de9a1-1304-4811-868d-9f5c2041ee55" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> struct SurfaceOutput
{
half3 Albedo;
half3 Normal;
half3 Emission;
half Specular;
half Gloss;
half Alpha;
};</pre>
</div>
<br />
At the moment in our surface shader we are only setting the Albedoo and the Alpha, so lets use these in conjunction with the light colour like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ac3cf017-5033-4bbe-97c9-ac5b999a175d" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> half4 LightingSimpleDiffuse (SurfaceOutput s, half3 lightDir, half atten)
{
half4 c;
c.rgb = s.Albedo * _LightColor0.rgb;
c.a = s.Alpha;
return c;
}</pre>
</div>
<br />
So we set up a half4 variable called ‘c’, this is going to be the value we return, we then set the rgb (Red, Green, Blue) elements of the return variable with the given Albedo multiplied by the light colour, why multiplied? Because if we added it and the light was black, we would just get the Albedo color, which would be wrong, if there is no light, no colours should be bouncing off the surface.<br />
<br />
We then set the Alpha as it’s passed in, and return what we have calculated.<br />
<br />
The effect of this shader now looks like this<br />
<br />
<a href="http://lh6.ggpht.com/-dh2hsgsw7Z0/VOZq4W9OIxI/AAAAAAAACCw/39xT1LIZHwA/s1600-h/image%25255B18%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-NErCtxv35IQ/VOZq5DYbrPI/AAAAAAAACC4/pYivwY75zQQ/image_thumb%25255B8%25255D.png?imgmax=800" height="272" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="484" /></a><br />
<br />
So a little better. By the way, if you change your light to black, there is still some light applied to the mesh, this is the ambient light that is applied to the model. We can switch this off, by default, the ambient colour (in Unity 5) is set to that of the skybox, open the Lighting Window<br />
<br />
<a href="http://lh5.ggpht.com/-aIZTJsbkRx8/VOZq5pIA0FI/AAAAAAAACC8/-JrOvTKLlPg/s1600-h/image%25255B22%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-28FWL_uuP74/VOZq6Ouvp9I/AAAAAAAACDE/gSOKEokl76o/image_thumb%25255B10%25255D.png?imgmax=800" height="364" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="490" /></a><br />
<br />
You can now either alter the Ambient Source or set it’s intensity to 0<br />
<br />
<a href="http://lh5.ggpht.com/-eAHHqlGFN-Q/VOZq6gr0OJI/AAAAAAAACDQ/qpWOFcXxXt0/s1600-h/image%25255B26%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-DYOHD0T-BvA/VOZq7nqLUqI/AAAAAAAACDU/6wyZOgKpuqc/image_thumb%25255B12%25255D.png?imgmax=800" height="640" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="256" /></a><br />
<br />
Now, our shader is not taking into account the angle of the surface with that of the light, so we get an un natural flat looking render. How do we calculate the angle between the surface and the light, and how do we then modify the colour to reflect (forgive the pun) that?<br />
<br />
Well, there is an awesome method we can use called <a href="http://http.developer.nvidia.com/Cg/dot.html">dot</a>. dot(float4, float4) takes two vectors and returns the dot product, of the two vectors. In other words it will return a value between –1 and 1, where 1 would be that the light is directly over the surface, so 90 degrees over it and –1 would effectively be at 90 degrees again but on the other side of the surface, so behind it. <br />
<br />
So, how do we do that, how do we know the direction the surface is facing, and the direction of the light hitting that surface? <br />
<br />
Well, the light direction we can get easy as Unity hands it to us in our function as a parameter, so that’s that bit solved, but what about the direction the surface is facing….. Remember back in the <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/coming-from-shaders-in-xna-to-shaders.html">first post</a>, we covered what a vertex is and the data that is in it?<br />
<br />
One of the data elements was called the normal that describes the direction the vertex is facing, so each triangle is made of 3 vertices, each vertex has a normal describing its direction of facing, so, just like with the texture coordinates (described in the <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaders.html">second post</a>) these too are interpolated, and if you look at the SurfaceOutput structure, we get given the current pixels “Normal” :) How cool is that!! (I know, I’m a geek) <br />
<br />
So, now we can find the angle the light is hitting this pixel of the surface like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:4e1b30f1-828a-406d-8d25-443007667d15" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">dot(s.Normal, lightDir)</pre>
</div>
<br />
Now, this will return a value, as I said above, in the range of –1 and 1, but I don’t care if it’s behind the surface, I just want to know how much light to reflect, so I am going to use a function called <a href="http://http.developer.nvidia.com/Cg/saturate.html">saturate</a> that will clamp the value between 0 and 1 and store it in a variable like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bd4174d2-a22e-4cb2-acbc-292396b961a9" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> half NdotL = saturate(dot(s.Normal, lightDir));</pre>
</div>
<br />
We can now use this value to multiply our output colour and our lighting calculation now looks like this<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3de266d5-0a1a-4345-88d3-9224e8b957e5" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> half4 LightingSimpleDiffuse (SurfaceOutput s, half3 lightDir, half atten)
{
half4 c;
half NdotL = saturate(dot(s.Normal, lightDir));
c.rgb = s.Albedo * _LightColor0.rgb * NdotL;
c.a = s.Alpha;
return c;
}</pre>
</div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Which then means we get a decent diffuse render like this<br />
<br />
<a href="http://lh5.ggpht.com/-GNhDF-YRq6Q/VOZq8PAOofI/AAAAAAAACDg/RPDZXgY41tY/s1600-h/image%25255B31%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-zTFltI7LF_o/VOZq8q1FqWI/AAAAAAAACDk/pbMCZsbxPOk/image_thumb%25255B15%25255D.png?imgmax=800" height="285" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="502" /></a><br />
<br />
So, lets have a look at this with two lights rendered and a floor…<br />
<br />
<a href="http://lh5.ggpht.com/-5nkNfMgkE8k/VOZq9esNrtI/AAAAAAAACDw/Yp0qiv0bsss/s1600-h/image%25255B36%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-vHuvzVCvoHY/VOZq-LieEaI/AAAAAAAACD0/BQQyAZpIM7s/image_thumb%25255B18%25255D.png?imgmax=800" height="279" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="500" /></a><br />
<br />
As you can see the surface shader is handling all the lighting passes for us, we don’t have to worry about how many lights. In this scene are two directional lights, one green, one red and a blue point light at the back of the mesh.<br />
<br />
I know what you are thinking, <br />
<br />
<blockquote>
<br />
“Why on earth have we just created this shader when I can just use the standard Diffuse shader that ships with Unity?!!!” </blockquote>
<br />
Well, I wanted to show you, with the simplest shader I could how we can create our own lighting calculations, and to give you an idea of all the bits Unity is doing for us with a Surface Shader. <br />
<br />
I was going to cover more lighting model types and look at some other surface shaders, but I am sure with the small bit of information I have tried to arm you with in these first few posts you can take a look at the examples in the Unity Docs and start experimenting with them your self. You can find the surface shader examples <a href="http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html">here</a>, and the lighting model examples <a href="http://docs.unity3d.com/Manual/SL-SurfaceShaderLightingExamples.html">here</a>.<br />
<br />
In my next post, I am going to show how you can combine a surface shader with a vertex shader, using the vertex shader to alter the geometry and also look at a multiple pass surface shader.<br />
<br />
As ever, your comments and critique are always welcome, feel free to post :) <br />
<br />
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-continued.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shadersmulti-pass-surface-shader.html">Next >></a>Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-42672372906940499432015-02-16T15:28:00.001-08:002015-02-19T15:05:11.955-08:00Unity3D Shaders–Surface Shaders Continued<div align="center">
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaders.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-lighting.html"> Next >></a></div>
<div align="left">
In the last post we started to look at surface shaders and we even applied it to a sprite to show that we can apply these shaders to simple 2D objects, but we had not set the shader up for alpha blending so we had a few artefacts. In this post, we will look making a surface shader work with alpha blending and we will set both our quad and sprite to fade in and out on command.</div>
<div align="left">
Like before, we will create a new shader, call it SSFadeShader, also a new material like before and name it SSFadeMaterial</div>
<h1 align="left">
SSFadeShader</h1>
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7d978361-80ad-4336-ba86-ccfee7f7ad4e" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">Shader "Randomchaos/Tutorial/SSFadeShader"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_FadeOut("Fade out value", Range(0,1)) = 1
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert alpha
sampler2D _MainTex;
float _FadeOut;
struct Input
{
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a * _FadeOut;
}
ENDCG
}
Fallback "Diffuse"
}</pre>
</div>
<br />
<div align="left">
A couple of things here, we have a fade parameter called _FadeOut as a range from 0 to 1, and an associated variable, we have also appended “alpha” to the #pragma, as well as setting the queue and rendertype to “Transparent” in the Tag line. This tells Unity that this surface shader uses transparency.</div>
<br />
<div align="left">
We can now apply this shader to our new material and then put this material on our sprite and quad. I have a nice flower png with transparency I am going to use for the quad.</div>
<br />
<div align="left">
<a href="http://lh3.ggpht.com/-M-epm4DICGI/VOJ9Hxb9CbI/AAAAAAAACBI/QxZEhtomMzs/s1600-h/image%25255B6%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-RcvA-qgAiTE/VOJ9IS7XnVI/AAAAAAAACBM/UY-TvvSiwo8/image_thumb%25255B2%25255D.png?imgmax=800" height="240" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="488" /></a></div>
<br />
<div align="left">
As you can see, the artefacts from before have been fixed with this new shader.</div>
<br />
<div align="left">
You can test the shader by moving the Fate Out Slider, as you reduce the value, the texture fades out, as you increase it, it fades back in.</div>
<br />
<div align="left">
We can now create a script that will control the fade level, create a script and call it FadeMaterialScript.cs</div>
<br />
<h1 align="left">
FadeMaterialScript.cs</h1>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e970f326-d082-4323-8cea-a1753f5bd07c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="xml" name="code">using UnityEngine;
using System.Collections;
public class FadeMaterialScript : MonoBehaviour
{
public float FadeSpeed = .01f;
float currentFade;
bool fade;
float fadeValue = 1;
Renderer renderer;
// Use this for initialization
void Start ()
{
currentFade = FadeSpeed ;
renderer = GetComponent<Renderer>();
}
// Update is called once per frame
void Update ()
{
if (fade)
{
fadeValue += currentFade;
fadeValue = Mathf.Clamp01(fadeValue);
if (fadeValue == 0 || fadeValue == 1)
fade = false;
renderer.material.SetFloat("_FadeOut", fadeValue);
}
}
public void Fade()
{
currentFade *= -1;
fade = true;
}
}</pre>
</div>
<br />
<div align="left">
This is a pretty simple script, we set up five variables, one public float to hold the speed of the fade we want to implement, one to hold the current fade (this is also the current fade direction, in or out) a bool to indicate if we are actually fading or not, another float to store the current fade value and a Renderer so we can get at the material.</div>
<br />
<div align="left">
In the Start method we set the currentFade and get the objects Renderer.</div>
<br />
<div align="left">
In the Update method we have an if to see if we are fading or not, if we are then we alter the value by the current fade direction/speed, we then clamp the fade value as we only want values in the range of 0 and 1. We then switch the fade flag off if we have fully faded, either in or out. Then we pass the fade value to the shader via</div>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bce32dfb-c37a-488e-8b47-38e5cbb4cdfc" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> renderer.material.SetFloat("_FadeOut", fadeValue);</pre>
</div>
<br />
<div align="left">
We then have a public function that can be called externally to kick off the fade, it first flips the current fade direction and then sets the fade flag to true, the Update will then pick it up from here.</div>
<br />
<div align="left">
The next thing I am going to do is add two UI.Buttons to my scene, I rename the text, one to “Fade Flower” and one to “Fade Sprite”. With the UI.Button we can hand an object to the OnClick event, and from that tell it what script and what method to execute when the button is clicked, so I set the buttons up with each of my objects</div>
<br />
<div align="left">
<a href="http://lh5.ggpht.com/-LrGBmWHzDfA/VOJ9ImPwtZI/AAAAAAAACBU/2Xfkba-gOoY/s1600-h/image%25255B14%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-ISRo3lxopY0/VOJ9JFxInfI/AAAAAAAACBg/cFD0mKN8bvg/image_thumb%25255B6%25255D.png?imgmax=800" height="456" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="448" /></a></div>
<br />
<div align="left">
We can now run the code, and I can now fade out and back in the game objects by clicking the UI buttons.</div>
<br />
<div align="left">
<a href="http://lh3.ggpht.com/-c4R81QuHm3k/VOJ9JxvFMnI/AAAAAAAACBo/i8l8XG7ry-8/s1600-h/image%25255B7%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-Kx1ZR15S71E/VOJ9KLePRJI/AAAAAAAACBs/by5sMhW6LLk/image_thumb%25255B3%25255D.png?imgmax=800" height="294" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="520" /></a></div>
<br />
<div align="left">
I have found with surface shaders there are a few idiosyncrasies, with transparency, I can’t control the blend type, it seems only alpha blend are available, so no additive or multiplication blends. Also the shadow pass takes all of the geom into account, it ignores the level of alpha. I find that you have much more control in a vertex/fragment shader.</div>
<br />
<div align="left">
This has been a bit of a quick post, partly as I have a pretty busy week, also someone requested a practical example of the surface shader I described in the last post, so thought this might be something to show quick that can be done with a shader. I do plan on doing some shader examples towards the end of the posts, but really do want to get the basics covered before I dig too deep into that. I hope this gives you an idea of how you can use something as simple as a surface shader, in my next post I am going to stick with the surface shader and cover creating our own lighting equations. </div>
<br />
<div align="left">
As ever comments and critique is more than welcome, even suggest an effect you would like to apply and Ill look at adding at the end of the series.</div>
<br />
<div align="left">
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaders.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-lighting.html">Next >></a></div>
Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-30166452308487651252015-02-15T16:08:00.001-08:002015-02-16T15:29:53.315-08:00Unity3D Shaders<p align="center"><a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/coming-from-shaders-in-xna-to-shaders.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-continued.html">Next>></a></p> <p align="left">So, I hope, prior to reading this tutorial you have read my previous post laying down some fundamental ideas we sort of need before we start to look at creating shaders. If you haven't, then please, hop back and have a read of it <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/coming-from-shaders-in-xna-to-shaders.html">here</a>, or simply click the << Previous links at the top and bottom of this post.</p> <p align="left">Ill repeat my caveat here, I am no shader or Unity expert, these posts are based on what I have found through my own exploration of shaders in Unity, but I hope what you find here gives you some pointers and ideas for your own shaders and adventures creating shaders :)</p> <p align="left">Unity gives us a few shader types that we can write, <a href="http://docs.unity3d.com/Manual/SL-SurfaceShaders.html">surface shaders</a>, as well as <a href="http://docs.unity3d.com/Manual/SL-ShaderPrograms.html">vertex and fragment</a> shaders.</p> <p align="left">If you are coming from XNA/DX vertex shaders are the same as DX9 vertex shaders and fragment shaders are the analogue of pixel shaders. In XNA there is no analogue with surface shaders.</p> <p align="left">If you have no experience of shaders at all, then I had best describe to you the difference of each of these shaders.</p> <h1 align="left">Vertex Shader</h1> <p align="left">A vertex shader is a function that runs on the GPU, it takes a single vertex (as described in the last post) and places it on the screen, that is to say if we have a vertex position of 0,0,0 it will be drawn in the centre of out mesh, if the mesh then has a position of 0,0,10 the vertex will be drawn at that position in the game world. This function returns this position data, as well as other data back to the graphics pipeline and this data is then passed, after some manipulation, to the pixel shader, or in the case of Unity, it may be passed to a surface shader, depending how we have written it.</p> <p align="left">A vertex shaders calculations are done on each vertex, so in the case of our quad from earlier, this would be executed 6 times, once for each point of the two triangles the quad is made up of.</p> <h1 align="left">Pixel Shader</h1> <p align="left">The pixel shader is given each and every pixel that lies with in each of the triangles in the rendered mesh and returns a colour. As you can imagine, depending on how much of the screen your mesh takes up, this could be called once for every pixel on your screen!! Needless to say pixel shaders are more expensive that vertex shaders.</p> <h1 align="left">Surface Shaders</h1> <p align="left">These are a strange halfway house between the vertex and pixel shader. You do not need to create a vertex shader with a surface shader as Unity will use it’s default one, but you can if you need one, we will cover that later. It comes with a set of predefined elements that will automatically use the Unity lighting model. You can write you own lighting calculations too which is quite cool, but, like anything else, you don’t get something for nothing, they will have an overhead, but if I am honest, I have not had too many issues with them.</p> <p align="left">I think the first shader we should look at is the Unity Surface shader. These shaders are a great way to start as they do a lot of work for you, you don’t have to worry about all the lighting calculations, if forward rendering or deferred rendering is being used, all we have to do is worry about the colour out put.</p> <h1 align="left">Create our first Surface Shader</h1> <p align="left">From within your Assets folder, create a few new folders called Shaders, Materials and Scripts.</p> <p align="left">In the Shaders folder, we will create out first surface shader, right click in or on the shaders folder, select Create, then Shader</p> <p align="left"><a href="http://lh5.ggpht.com/-1I33FhevOHs/VOE06S8yFJI/AAAAAAAAB_A/YZ9_7fBk_1M/s1600-h/image%25255B3%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-TcWoLPlEIfM/VOE06_HdOCI/AAAAAAAAB_E/GTqvDvJWdK8/image_thumb%25255B1%25255D.png?imgmax=800" width="488" height="252"></a></p> <p align="left">Call this first shader SSOne. Lets take a look at what we get just by creating this shader file.</p> <div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:8dcc5ed1-e22f-49e3-a29c-b605687220d5" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">Shader "Custom/SSOne" <br />{<br /> Properties <br /> {<br /> _MainTex ("Base (RGB)", 2D) = "white" {}<br /> }<br /><br /> SubShader <br /> {<br /> Tags { "RenderType"="Opaque" }<br /> LOD 200<br /> <br /> CGPROGRAM<br /> #pragma surface surf Lambert<br /><br /> sampler2D _MainTex;<br /><br /> struct Input <br /> {<br /> float2 uv_MainTex;<br /> };<br /><br /> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> half4 c = tex2D (_MainTex, IN.uv_MainTex);<br /> o.Albedo = c.rgb;<br /> o.Alpha = c.a;<br /> }<br /> ENDCG<br /> } <br /> FallBack "Diffuse"<br />}</pre></div><br /><p align="left">First things first, I removed the silly java style brackets, the ones that have the opening ‘{‘ bracket on the same line as the condition or definition, they should always be on the next line in my opinion ;)</p><br /><p align="left">So, we have a few things going on in this file already, we will look at them in turn moving down through the file.</p><br /><h2 align="left">Shader Name</h2><br /><p align="left">At the top we have the Shader name in quotes, with this field we are also able to change how unity displays it in the list of shaders when we go to add it to a material.</p><br /><p align="left">Lets create a material to add this new shader to, in the Material folder, right click on or in it, select create then Material like this</p><br /><p align="left"><a href="http://lh3.ggpht.com/-8iODMKvXJGE/VOE07NKh9JI/AAAAAAAAB_Q/-Rv6pMJFb_0/s1600-h/image%25255B7%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-iNrIAeluM4I/VOE075tZ88I/AAAAAAAAB_U/2iBiiwBp0wQ/image_thumb%25255B3%25255D.png?imgmax=800" width="464" height="290"></a></p><br /><p align="left">Name this material SSOneMaterial, we can now add our new shader to it. Click the shader combo box, our shader is in Custom select that option and you will be able to see the shader in the list</p><br /><p align="left"><a href="http://lh4.ggpht.com/--sDlBL8FbL0/VOE08GBmxAI/AAAAAAAAB_c/jZHf2ZrYNQE/s1600-h/image%25255B15%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh5.ggpht.com/-9p2WBvI3cig/VOE08r91IzI/AAAAAAAAB_o/6WwQyx6fPzc/image_thumb%25255B7%25255D.png?imgmax=800" width="426" height="471"></a></p><br /><p align="left">Lets change the name and location of our shader, so change the header from</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3958f9f9-89aa-4f51-bf3a-2ca3ab737ebd" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">Shader "Custom/SSOne" <br />{</pre></div><br /><p align="left">To</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e4b4dfed-43dc-4fbf-9b23-e1e252a42d21" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">Shader "Randomchaos/Tutorial/SSOne" <br />{</pre></div><br /><p align="left">If we go to add it now, it looks like this</p><br /><p align="left"><a href="http://lh5.ggpht.com/-VmxSd-bbepI/VOE09a-O1jI/AAAAAAAAB_s/dpclcLOcysE/s1600-h/image%25255B19%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh3.ggpht.com/-pXAGiKa-BME/VOE090M60vI/AAAAAAAAB_4/R7sqF8vMuAU/image_thumb%25255B9%25255D.png?imgmax=800" width="405" height="500"></a></p><br /><h2 align="left">Shader Properties</h2><br /><p align="left">The properties section defines any variables we want to pass to the shader. At the moment, in the file created by unity, we only have one variable and it’s a texture to be used in this shader. There are a few different types of parameters that can be passed. Before we look at the types, lest look at the definition of the parameter we currently have.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ee387d11-2ea4-499a-87d2-7ddbcdbbb5c9" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> _MainTex ("Base (RGB)", 2D) = "white" {}</pre></div><br /><p align="left">I guess the “signature” of these parameters is as follows</p><br /><p align="left"><param name> (“UI Description”, type) = <default value></p><br /><p align="left">So, for our current parameter it has the name _MainTex, the description that will be shown in the UI is “Base (RGB)” and the type is a 2D texture, it’s default value is white, lets look at that in the UI.</p><br /><p align="center"><a href="http://lh6.ggpht.com/-csnUq8JwXVA/VOE0-anH_NI/AAAAAAAAB_8/zYIBZWpbIks/s1600-h/image%25255B24%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="http://lh4.ggpht.com/-mFXRxb-_jQg/VOE0-3_PizI/AAAAAAAACAI/wVuEfyM1tjQ/image_thumb%25255B12%25255D.png?imgmax=800" width="268" height="700"></a></p><br /><p align="left">We have a few options when it comes to parameters</p><br /><table cellspacing="0" cellpadding="2" width="511" border="1"><br /><tbody><br /><tr><br /><td valign="top" width="199"><br /><p align="center"><font style="background-color: #ffffff" color="#ffffff"></font><strong>Property Type</strong></p></td><br /><td valign="top" width="310"><br /><p align="center"><strong>Description</strong><font style="background-color: #ffffff" color="#ffffff"></font></p></td></tr><br /><tr><br /><td valign="top" width="199">Range(min,max)</td><br /><td valign="top" width="310">Defines a float property, represented as a slider from <em>min</em> to <em>max</em> in the inspector.</td></tr><br /><tr><br /><td valign="top" width="199">Color</td><br /><td valign="top" width="310">Defines a color property. (r,g,b,a)</td></tr><br /><tr><br /><td valign="top" width="199">2D</td><br /><td valign="top" width="310">Defines a 2D texture property.</td></tr><br /><tr><br /><td valign="top" width="199">3D</td><br /><td valign="top" width="310">Defines a 3D texture property.</td></tr><br /><tr><br /><td valign="top" width="199">Rectangle</td><br /><td valign="top" width="310">Defines a rectangle (non power of 2) texture property.</td></tr><br /><tr><br /><td valign="top" width="199">Cubemap</td><br /><td valign="top" width="310">Defines a cubemap texture property.</td></tr><br /><tr><br /><td valign="top" width="199">Float</td><br /><td valign="top" width="310">Defines a float property.</td></tr><br /><tr><br /><td valign="top" width="199">Vector</td><br /><td valign="top" width="310">Defines a four-component vector property.</td></tr></tbody></table><br /><h2 align="left">SubShader</h2><br /><p align="left">The next bit of this file is where all the work is done. </p><br /><p align="left">The first thing we see is the Tag, we have a few elements to this part of the Unity Shader file, we can control quite a few things from here</p><br /><p align="left">At the moment it looks like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:83fdfb4d-09db-413e-9bab-e7ef93e84267" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> Tags { "RenderType"="Opaque" }</pre></div><br /><p align="left">So, as you can see we are using one tag type in there called RenderType and it’s set to Opaque, so no transparency.</p><br /><p align="left">So what tag types are there?</p><br /><table cellspacing="0" cellpadding="2" width="509" border="1"><br /><tbody><br /><tr><br /><td valign="top" width="198"><br /><p align="center"><strong>Tag Type</strong></p></td><br /><td valign="top" width="309"><br /><p align="center"><strong>Description</strong></p></td></tr><br /><tr><br /><td valign="top" width="198"><br /><h5>Queue</h5></td><br /><td valign="top" width="309">With this tag type you can set what order your objects are drawn in.</td></tr><br /><tr><br /><td valign="top" width="198">RenderType</td><br /><td valign="top" width="309">We can use this to put our shader into different render groups that help Unity render them in the right way.</td></tr><br /><tr><br /><td valign="top" width="198">ForceNoShadowCasting</td><br /><td valign="top" width="309">If you use this and set it to true then your object will not cast any shadows, this is great if you have a transparent object.</td></tr><br /><tr><br /><td valign="top" width="198">IgnoreProjector</td><br /><td valign="top" width="309">If used and set to true, this will ignore <a href="http://docs.unity3d.com/Manual/class-Projector.html">Projectors</a>, now until I decided to write this tutorial I have never heard of these, but they do look useful :D</td></tr></tbody></table><br /><p align="left">Lets look at the available values that can be used for Queue and RenderType</p><br /><p align="left">*Descriptions ripped from the Unity docs ;)</p><br /><p align="left">We wont use the queue tag, but it’s nice to know how we can use them, you never know, later on, a few posts down the line we might use it.</p><br /><table cellspacing="0" cellpadding="2" width="509" border="1"><br /><tbody><br /><tr><br /><td valign="top" width="200"><br /><p align="center"><strong>Queue Value</strong></p></td><br /><td valign="top" width="307"><br /><p align="center"><strong>Description</strong></p></td></tr><br /><tr><br /><td valign="top" width="200">Background</td><br /><td valign="top" width="307">This render queue is rendered before any others. It is used for skyboxes and the like.</td></tr><br /><tr><br /><td valign="top" width="200">Geometry (default)</td><br /><td valign="top" width="307">This is used for most objects. Opaque geometry uses this queue.</td></tr><br /><tr><br /><td valign="top" width="200">AlphaTest</td><br /><td valign="top" width="307">!lpha tested geometry uses this queue. It’s a separate queue from Geometry one since it’s more efficient to render alpha-tested objects after all solid ones are drawn.</td></tr><br /><tr><br /><td valign="top" width="200">Transparent</td><br /><td valign="top" width="307">This render queue is rendered after Geometry and AlphaTest, in back-to-front order. Anything alpha-blended (i.e. shaders that don’t write to depth buffer) should go here (glass, particle effects).</td></tr><br /><tr><br /><td valign="top" width="200">Overlay</td><br /><td valign="top" width="307">This render queue is meant for overlay effects. Anything rendered last should go here (e.g. lens flares).</td></tr></tbody></table><br /><p align="left">Again, at the moment we are not too concerned with these tags, but it’s good to be aware of them.</p><br /><table cellspacing="0" cellpadding="2" width="512" border="1"><br /><tbody><br /><tr><br /><td valign="top" width="201"><br /><p align="center"><strong>RenderType Value</strong></p></td><br /><td valign="top" width="309"><br /><p align="center"><strong>Description</strong></p></td></tr><br /><tr><br /><td valign="top" width="201">Opaque</td><br /><td valign="top" width="309">Most of the shaders (<a href="http://docs.unity3d.com/Manual/shader-NormalFamily.html">Normal</a>, <a href="http://docs.unity3d.com/Manual/shader-SelfIllumFamily.html">Self Illuminated</a>, <a href="http://docs.unity3d.com/Manual/shader-ReflectiveFamily.html">Reflective</a>, terrain shaders).</td></tr><br /><tr><br /><td valign="top" width="201">Transparent</td><br /><td valign="top" width="309">Most semitransparent shaders (<a href="http://docs.unity3d.com/Manual/shader-TransparentFamily.html">Transparent</a>, Particle, Font, terrain additive pass shaders).</td></tr><br /><tr><br /><td valign="top" width="201">TransparentCutout</td><br /><td valign="top" width="309">Masked transparency shaders (<a href="http://docs.unity3d.com/Manual/shader-TransparentCutoutFamily.html">Transparent Cutout</a>, two pass vegetation shaders)</td></tr><br /><tr><br /><td valign="top" width="201">Background</td><br /><td valign="top" width="309">Skybox shaders</td></tr><br /><tr><br /><td valign="top" width="201">Overlay</td><br /><td valign="top" width="309">GUITexture, Halo, Flare shaders</td></tr><br /><tr><br /><td valign="top" width="201">TreeOpaque</td><br /><td valign="top" width="309">Terrain engine tree bark</td></tr><br /><tr><br /><td valign="top" width="201">TreeTransparentCutout</td><br /><td valign="top" width="309">Terrain engine tree leaves.</td></tr><br /><tr><br /><td valign="top" width="201">TreeBillboard</td><br /><td valign="top" width="309">Terrain engine billboarded trees</td></tr><br /><tr><br /><td valign="top" width="201">Grass</td><br /><td valign="top" width="309">Terrain engine grass.</td></tr><br /><tr><br /><td valign="top" width="201">GrassBillboard</td><br /><td valign="top" width="309">Terrain engine billboarded grass.</td></tr></tbody></table><br /><h2 align="left"> </h2><br /><h2 align="left">LOD</h2><br /><p align="left">Our shader currently has the value 200, this is used when you want to restrict your shaders, so when deploying to certain platforms you may want to disable shaders > LOD 200. You can check out the the settings that the default shaders come with <a href="http://docs.unity3d.com/Manual/SL-ShaderLOD.html">here</a>. Again, this just for information, we won’t be playing with this value, in fact, we could probably remove it.</p><br /><h2 align="left">CGPROGRAM</h2><br /><p align="left">This is where our shader code starts.</p><br /><h2 align="left">#pragama</h2><br /><p align="left">The pragma is telling the unity compiler what shaders we are using. In this pre generated shader, this is a surface shader, it is then followed by the function name for the shader, in this case surf and if a surface shader the followed by the type of lighting algorithm to use, in this case Lambert. There are a few lighting algorithms, in hlsl I have written a few, Unity comes withe Lambert and BlinnPhong. What I do like about surface shaders is we can write our own lighting algorithms too, and we will come to that later.</p><br /><h2 align="left">Shader Variable Declarations</h2><br /><p align="left">We have a parameter defined at the top, but we then need to create a variable for in inside the CGPROGRAM section, and parameters set that we want to use in the shader need to be duplicated here. We can also declare variables here that are not passed as parameters if we want to too. In this shader we have just the one</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b57852d9-f4ba-44fa-b593-57944aa74f03" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> sampler2D _MainTex;</pre></div><br /><h2 align="left">Shader Structures</h2><br /><p align="left">This is where we define the structures to be used that describe the vertex data we are passing in and the data we are passing to other shaders. In this surface shader we are just getting data for the pixels inside each triangle, like a pixel shader, so Unity will have already ran a vertex shader for us and then it will pass the data we require as laid out in the structures defined here.</p><br /><p align="left">Defined here is a structure called Input</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7271cb7d-51ae-4350-aefb-b10407d1a29c" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> struct Input <br /> {<br /> float2 uv_MainTex;<br /> };</pre></div><br /><p align="left">It is going to pass us the texture coord for the given pixel so we can then use that to get a texel (not pixel) off the texture passed in for the given coordinate.</p><br /><h3 align="left">How on earth does it do that and what is a texel!?!?!?!</h3><br /><p align="left">OK, this bit is a bit funky :) Remember we set up those texCoords in our earlier runtime quad code</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:50e442ce-daa6-4c19-91ab-213b7d9bc48a" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> // Set up text coords<br /> texCoords.Add(new Vector2(0, 1));<br /> texCoords.Add(new Vector2(0, 0));<br /> texCoords.Add(new Vector2(1, 0));<br /> texCoords.Add(new Vector2(1, 1));</pre></div><br /><p align="left">We said that the top left corner was 0,0 and the top right was 1,0, well when this information is given to the surface or pixel shader it is interpolated, that is to say, when we get given the pixel(s) between the top left corner and top right corner they will be in the range of 0,0 and 1,0. So if the pixel being sent to the surface shader is slap in the middle and at the very top of the mesh, it would have the uv_MainTex value od .5,0. If it was the pixel in the very centre of our rendered quad then uv_MainTex value would be .5,.5. So by using this mapping we can get the right texel from the texture passed in.</p><br /><p align="left">Texel = TEXt ELement, the GPU can’t work on a pixel to pixel mapping, the texture you pass could be 32x32 pixels and the area your mesh covers could be, well, the whole screen or 16x16, so the GPU needs to use the texture coordinates to pull out the colour at that point. To do that on a pixel by pixel basis, just would not work…</p><br /><h2 align="left">Surf</h2><br /><p align="left">And now, the part you have all been waiting for, the actual shader it’s self…</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2cd54b53-e7ed-4bb9-8e51-d804b7685539" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> half4 c = tex2D (_MainTex, IN.uv_MainTex);<br /> o.Albedo = c.rgb;<br /> o.Alpha = c.a;<br /> }</pre></div><br /><p align="left">This is being call for each and every pixel that your mesh is showing to the camera. The function is being passed the Input structure, as I have explained above this will have the interpolated data for the given pixel with regards to the mesh. There is also an inout parameter, SurfaceOutput o. This structure will be populated by this function then handed onto the rest of the Unity surface pipeline to have the lighting applied to it.</p><br /><p align="left">The SurfaceOutput structure is pre defined in Unity and looks like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:fb4ad443-45cc-4c83-9365-8487362f545f" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">struct SurfaceOutput {<br /> half3 Albedo;<br /> half3 Normal;<br /> half3 Emission;<br /> half Specular;<br /> half Gloss;<br /> half Alpha;<br />};</pre></div><br /><p align="left">All that is getting populated here is the Albedo and the Alpha. The Albedo is the colour of the surface to be returned for this particular pixel and the Alpha is the corresponding alpha.</p><br /><h3 align="left">ENDCG</h3><br /><p align="left">Denotes the end of your shader code</p><br /><h3 align="left">Fallback</h3><br /><p align="left">This is the default shader to use should this shader be unable to run on the hardware.</p><br /><h1 align="left">Play Time</h1><br /><p align="left">So, that was a lot of waffle, lets look at altering this shader so we can see how some of it works. First we need to use our new material on our rendered object, now you can use our runtimequad or add a new model to the scene and set it’s material to our new SSOneMaterial.</p><br /><p align="left">Once you have assigned the new material, give it a texture.</p><br /><p align="left"><a href="http://lh5.ggpht.com/-XBMtXi8TNxU/VOE0_VQSw-I/AAAAAAAACAM/hjO0uVG75J0/s1600-h/image%25255B28%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh4.ggpht.com/-0A2xPcA7aYA/VOE0_9UvBPI/AAAAAAAACAU/lpibN_J3tSk/image_thumb%25255B14%25255D.png?imgmax=800" width="481" height="272"></a></p><br /><p align="left">Run this and we can see (or you should) that it runs as it did before with the other shader.</p><br /><p align="left">Lets add a new parameter to the shader. In the Properties section add a new property _Tint like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:76947ac7-e54f-4648-a19e-1d1aeed5926d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> Properties <br /> {<br /> _MainTex ("Base (RGB)", 2D) = "white" {}<br /> _Tint ("Color Tint", Color) = (1,1,1,1)<br /> }</pre></div><br /><p align="left">We need to also set up a variable for this, so under sampler2D _MainTex; add a float4 (a colour is a float4)</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:54bb6b2c-282a-434b-bbfd-c336972b7a94" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> sampler2D _MainTex;<br /> float4 _Tint;</pre></div><br /><p align="left">We can now use this value to tint the image that we out put like this</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:789b2599-bf78-4ed9-bd7b-533657682553" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> void surf (Input IN, inout SurfaceOutput o) <br /> {<br /> half4 c = tex2D (_MainTex, IN.uv_MainTex);<br /> o.Albedo = c.rgb * _Tint;<br /> o.Alpha = c.a;<br /> }</pre></div><br /><p align="left">Now, go back to Unity, run the scene, select your object, pick the Tint colour picker and you will see the image get tinted at run time, this can be done in the scene screen when not running too.</p><br /><p align="left"><a href="http://lh3.ggpht.com/--I2WrQQPTG8/VOE1AV_Q57I/AAAAAAAACAg/_vyyU0soAVQ/s1600-h/image%25255B32%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh6.ggpht.com/-o1p0PMtqiss/VOE1AxghZYI/AAAAAAAACAo/6dzJPpqav50/image_thumb%25255B16%25255D.png?imgmax=800" width="493" height="279"></a></p><br /><p align="left">Oh, and just to show you can use these 3D shaders with 2D graphics, here is an image of the sahder being used on both a 3D quad and a sprite at the same time.</p><br /><p align="left"><a href="http://lh3.ggpht.com/-fh0UL33QGCw/VOE1BzCm9UI/AAAAAAAACAw/CCdVbZpsb0Y/s1600-h/image%25255B36%25255D.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; display: block; padding-right: 0px; border-top-width: 0px; margin-right: auto" border="0" alt="image" src="http://lh3.ggpht.com/-3YxwtIi1pb4/VOE1CWZ0TDI/AAAAAAAACA0/UaBYkAV4ZGY/image_thumb%25255B18%25255D.png?imgmax=800" width="486" height="275"></a></p><br /><p align="left">Naturally, our shader at the moment is not taking into account the alpha for the sprite, but we can sort that out later, for now, just be clear, what ever we render in 3D with shaders we can do in 2D as well, they are the same thing…</p><br /><p align="left">As ever comments and critique are more than welcome. The next post we will look at a few more tricks we can do in the surface shader and if I have time take a look at writing out own lighting model for our shader.</p><br /><p align="center"><a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/coming-from-shaders-in-xna-to-shaders.html"><< Previous</a> <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaderssurface-shaders-continued.html">Next>></a></p> Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-75442163095800137892015-02-13T14:58:00.001-08:002015-02-15T16:09:43.358-08:00Coming from Shaders in XNA to Shaders in Unity3D<br />
<div align="right">
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaders.html">Next>></a></div>
This is the first post in an intended series of tutorials covering shader in Unity3D. Now, I am no expert in this area, this is just my experience of working with shaders in Unity3D. I came to Unity3D shaders pre armed I guess, with knowledge gained from writing DX9 shaders in XNA. I have written a lengthy set of tutorials for that as well as creating my own deferred lighting engine in XNA with a post processing pipeline as well as hardware instanced mesh’s and particle systems, all with accompanying shaders, so as far as the GPU goes I do have some experience and I think a good understanding of HLSL and the graphics pipeline. <br />
So what has got me wanting to write this post, especially as there are loads of tutorials out there on Unity3D shaders as well as the excellent Unity3D Documentation? Well, I remember starting out with shaders in XNA, and, well it’s tough to get the concepts and ideas under your belt at first and from what I have seen (I may not have looked very hard) most of them assume existing knowledge. Also, for a lot of developers all these sources are very 3D driven, and a lot of devs don’t seem to then see that these shaders can also be used in their 2D projects.<br />
In the past I have heard people say that 3D is much harder than 2D, but what I don’t think a lot of people realize is when you were working in SpriteBatch (XNA) and now Sprites (Unity3D) you are still working in 3D, it’s just that everything is a quad and always facing the camera.<br />
So I am going to start from the very, very beginning. The first thing we will do will be to create a mesh ourselves in code so we can actually understand what gets passed to the shader, we may not even get to look at a shader in this post :S<br />
A friend of mine, Jay from <a href="https://www.facebook.com/DropDeadInteractive">Drop Dead Interactive</a>, sent me a great youtube link to a series of <a href="https://www.youtube.com/watch?v=zJxxXjoZE30&list=PLV4HCa5XqFT02gZOZ_Jb_A66wqDhZMCkN">Unity Shader tutorials</a>, you might want to check this out to. I liked it, worth a look if you have time.<br />
<h1>
<span style="font-weight: normal;">The Vertex</span></h1>
Please forgive me if I am teaching you how to suck eggs (showing you something you already know), but if people don’t understand the very root of the pipeline, then it’s easy to get confused. <br />
<br />
<h2>
So what is the Vertex (plural, Vertices)?</h2>
OK, the Vertex is a structure that holds information for a given point on a mesh. So at the very least this would be a Vector3 for the point position on the mesh, not in the world, but on the mesh. <br />
For the GPU to draw anything on the screen it needs at least 3 vertices, and to draw a quad (a flat square, billboard or sprite), at least 4 vertices (you could use 6 to draw 3 triangles, with indexing the GPU sort of does this anyway). All meshs are made up of triangles, so you can imagine the more curved your mesh is, the more triangles are needed. <br />
<a href="http://lh3.ggpht.com/-p6NHgx4Na7I/VN6Bb91JXGI/AAAAAAAAB8k/0r7bilAv9TE/s1600-h/image%25255B70%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-akCfDKFLPXw/VN6BcfjeV3I/AAAAAAAAB8s/sJ4nq5jw-h8/image_thumb%25255B36%25255D.png?imgmax=800" height="354" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="406" /></a> <br />
Each one of the triangles in the image above is built from 3 vertices.<br />
The Vertex also has other information with it, it also has the direction this point is facing, this is also known as the “Normal”, it will also have texture coordinates (textcoord), that describe how a texture would be applied to it. For now, we will focus on these there elements, position, normal and texcoord. The texcoord are also referred to as UV, I am sure you have heard artists talk about UV mapping in modeling tools and this is the process of setting up the vertices so that the right bits of the textures are mapped to the right parts of the mesh. I am terrible at UV mapping, it’s a real skill and I just don’t have the patients… <br />
<h3>
<b>Unity is Left Handed – XNA was Right Handed</b></h3>
So, why does this matter? Well, you can get your self in a bit of a pickle if you are not aware of it, and I get in a pickle all the time as I am so use to working right handed. But what does it mean for a graphics system to be left or right handed? Well, it’s about how the coordinate system works, I could describe it here, but found a great link explaining handedness <a href="https://msdn.microsoft.com/en-gb/library/windows/desktop/bb204853(v=vs.85).aspx">here</a>. <br />
<h1>
RuntimeQuadScript.cs</h1>
To help me show you the sort of data that gets sent to the shader we are going to create a script that will generate a Quad for us at run time. Now, for those of you that don’t like working in 3D, see this quad as a sprite, not as 3D geometry, the sprites and GUI.Image elements you have been working with, even the 3D text are all quads, so think of this as a sprite, ill even show you how we can use our shaders on sprites and even text in our games.<br />
What do we need then, we need a list of position data, Vector3, 4 in total, an index list used to draw the positions in the correct order (winding order) , 6 in total, a list of Vector2’s for texture coordinates, again 4 in total as well as a list of Vector3’s for the normals, again, 4 in total. We also need a Mesh to put it all in and we can apply a material and so in turn a shader to.<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:b4278d71-79c1-4590-b084-d9f2f1c897e8" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[ExecuteInEditMode]
public class RuntimeQuadScript : MonoBehaviour
{
/// <summary>
/// List to store all our vertex positions
/// </summary>
List<Vector3> positions = new List<Vector3>();
/// <summary>
/// List to store the order we want the positions rendered in.
/// </summary>
List<int> index = new List<int>();
/// <summary>
/// List to hold the text coorinates we want for the mesh.
/// </summary>
List<Vector2> texCoords = new List<Vector2>();
/// <summary>
/// List to hold the normals we wish to create.
/// </summary>
List<Vector3> normals = new List<Vector3>();
/// <summary>
/// The mesh that our variables will create.
/// </summary>
Mesh thisMesh;</pre>
</div>
<br />Now we can use these elements create our quad. First thing we will do is initialize our mesh and load up the vertex positions we need.<br />
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:531b05cd-eb0e-4a17-965a-d2a8907cfad9" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> // Use this for initialization
void Start()
{
// Initialize the mesh object
thisMesh = new Mesh();
// Set up position data.
positions.Add(new Vector3(-.5f, .5f, 0)); // Top left corner
positions.Add(new Vector3(-.5f, -.5f, 0)); // Bottom left corner
positions.Add(new Vector3(.5f, -.5f, 0)); // Bottom right corner
positions.Add(new Vector3(.5f, .5f, 0)); // Top right corner</pre>
</div>
<br />We can then set the draw order of these positions with a list of indicies, so each value in this next list corresponds to a Vector3 in out position list, so 0 would be the first Vector3 and 3 would be the last.<br />
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7a63bab8-fc43-42f1-8642-288c9d803624" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> // Set up the draw index
index.Add(0); // Draw from top left corner
index.Add(3); // to bottom left
index.Add(2); // then to bottom right
// Next triangle
index.Add(2); // Draw from bottom right
index.Add(1); // to top right
index.Add(0); // then to top left</pre>
</div>
<br />We can then give these values to the mesh and set it in the filter like this, remember we are rendering the mesh to face our camera at 0,0,-10.<br />
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:5d120615-9b74-4713-bf9c-82f9035be76c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> // Now give our mesh this data
thisMesh.vertices = positions.ToArray();
thisMesh.triangles = index.ToArray();
// Now put this in the mesh filter so the renderer apply a material to it.
GetComponent<MeshFilter>().sharedMesh = thisMesh;</pre>
</div>
<br />We can now create an empty game object, rename it RuntimeQuad and add our script to it like this<br />
<br /><div align="center">
<a href="http://lh6.ggpht.com/-cz3Z2s25a-0/VN6BdP-rS1I/AAAAAAAAB8w/OSAuAHlKCM8/s1600-h/image%25255B54%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-oQtcl2VvR7g/VN6Bdo1sytI/AAAAAAAAB84/I3QsGMxfwd0/image_thumb%25255B28%25255D.png?imgmax=800" height="650" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="413" /></a></div>
<br />The quad<br />
<br />As we had the required attributes at the top of our class, the MeshFilter and MeshRenderer have been added automatically for us. You will also see that I have set the MeshRenderer to use the Default-Diffuse material, before you do that you will see your quad rendered as a magenta square like this<br />
<br /><a href="http://lh3.ggpht.com/-iHcKIIBeRSY/VN6Bd_U9_1I/AAAAAAAAB9A/8kzObPqWjtA/s1600-h/image%25255B8%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-vCMg9oyQNZ0/VN6BeQEdzbI/AAAAAAAAB9I/stEwyf8Zgn4/image_thumb%25255B4%25255D.png?imgmax=800" height="271" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="482" /></a><br />
<br />Once you have set the material it will look like this<br />
<br /><a href="http://lh4.ggpht.com/-Z0478J2V8nY/VN6Be4MGuCI/AAAAAAAAB9Q/3378yPezoIo/s1600-h/image%25255B13%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-goVirPGb80U/VN6BfFOn2DI/AAAAAAAAB9Y/cvYzrGWoqDo/image_thumb%25255B7%25255D.png?imgmax=800" height="271" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="480" /></a><br />
<br />You can see that it’s being shaded in an odd way with that shadow on the bottom, this is because we have not set up the normals, so the default shader does not know which direction the mesh is facing, we can set the normals in a couple of ways, first we will do it by hand<br />
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:1d245df1-9cd5-47a2-ab08-67d74363f939" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> // Create our own normals
normals.Add(Vector3.back);
normals.Add(Vector3.back);
normals.Add(Vector3.back);
normals.Add(Vector3.back);
// Now give our mesh this data
thisMesh.vertices = positions.ToArray();
thisMesh.triangles = index.ToArray();
thisMesh.normals = normals.ToArray();</pre>
</div>
<br />So I am setting the normals to Vector3.back, so they will be 0,0,-1 , so pointing in the direction of the camera, the quad now renders like this<br />
<br /><a href="http://lh3.ggpht.com/--VXnRD3HGck/VN6BfmG6wvI/AAAAAAAAB9k/ObMTRLMWhn4/s1600-h/image%25255B17%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-8AC-KOu8zPE/VN6BgJWywtI/AAAAAAAAB9o/Nmlt56uUdLY/image_thumb%25255B9%25255D.png?imgmax=800" height="281" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="497" /></a><br />
<br />Unity3D’s Mesh has a lovely intrinsic method called RecalculateNormals, so, we can do away with the normals list, but I had it in here just to illustrate how they look and what they are referring to.<br />
<br />We can do that like this<br />
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:e90fa1a7-c5fb-4c5d-b237-7ce132cf8585" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code"> // Now give our mesh this data
thisMesh.vertices = positions.ToArray();
thisMesh.triangles = index.ToArray();
//thisMesh.normals = normals.ToArray();
// Thankfully, Unity provides a method to calculate the normals of the mesh
thisMesh.RecalculateNormals();
// Now put this in the mesh filter so the renderer apply a material to it.
GetComponent<MeshFilter>().sharedMesh = thisMesh;</pre>
</div>
<br /><div align="left">
OK, so lets now set up a new material, call it IntrinsicDiffuse, as we are going to use the Diffuse shader provided by Unity like this</div>
<br /><div align="left">
<a href="http://lh4.ggpht.com/-pCLftBidLLA/VN6Bgts79oI/AAAAAAAAB9w/nIHOrSEKFYo/s1600-h/image%25255B22%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-dQyJytNi8vE/VN6BhFv5xHI/AAAAAAAAB94/ODw20TPLbr4/image_thumb%25255B12%25255D.png?imgmax=800" height="809" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="313" /></a></div>
<br /><div align="left">
So, now we can set MeshRenderer to the new material, change the color, say to red and it will render red, thanks to the material.</div>
<br /><div align="left">
<a href="http://lh4.ggpht.com/-WtOdGOUWjBU/VN6BhumjWtI/AAAAAAAAB-E/smwHPgH_U1E/s1600-h/image%25255B62%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-eZTubNcyNmk/VN6BiKA6tHI/AAAAAAAAB-I/M0WoSeuB4Mk/image_thumb%25255B32%25255D.png?imgmax=800" height="196" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="474" /></a></div>
<br /><div align="left">
But, what happens if we pass it a texture?</div>
<br /><div align="left">
Lets give it my handsome face to render</div>
<br /><div align="center">
<a href="http://lh4.ggpht.com/-ZyODxHFtjjQ/VN6BiuuzOgI/AAAAAAAAB-U/zZsxspVKwvs/s1600-h/image%25255B58%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-UA-yrOONYgU/VN6BjJXY5XI/AAAAAAAAB-Y/uzDjI6hNORM/image_thumb%25255B30%25255D.png?imgmax=800" height="202" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="495" /></a></div>
<br /><div align="left">
As you can see, it’s not rendered it right, it’s because we have not set the texture coordinates, so before we can set them we sort of need to know how they work, how does the texture coordinate relate to the texture that gets passed in.</div>
<br /><div align="left">
The texture coordinate system is a Vector2, the top left corner of the image is 0,0, the bottom right is 1,1, making the top right 1,0 and the bottom left 0,1 ergo, the centre of the texture would be .5f,.5f </div>
<br /><div align="left">
BUT Unity pulls a few tricks, as you can read <a href="http://docs.unity3d.com/Manual/SL-PlatformDifferences.html">here</a> depending on what framework it is using for the render, the above applies to Direct3D, so keep in mind that this can get flipped..</div>
<br /><div align="left">
I think due to the way the default shader is working, it’s using OpenGL as it’s flipping the texture ccords, so with the texture coordinates set the class looks like this</div>
<br /><div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:6ac3fe65-8091-43ba-bd82-3dde645f1c8c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#" name="code">using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[ExecuteInEditMode]
public class RuntimeQuadScript : MonoBehaviour
{
/// <summary>
/// List to store all our vertex positions
/// </summary>
List<Vector3> positions = new List<Vector3>();
/// <summary>
/// List to store the order we want the positions rendered in.
/// </summary>
List<int> index = new List<int>();
/// <summary>
/// List to hold the text coorinates we want for the mesh.
/// </summary>
List<Vector2> texCoords = new List<Vector2>();
/// <summary>
/// List to hold the normals we wish to create.
/// </summary>
List<Vector3> normals = new List<Vector3>();
/// <summary>
/// The mesh that our variables will create.
/// </summary>
Mesh thisMesh;
// Use this for initialization
void Start()
{
// Initialize the mesh object
thisMesh = new Mesh();
// Set up position data.
positions.Add(new Vector3(-.5f, .5f, 0)); // Top left corner
positions.Add(new Vector3(-.5f, -.5f, 0)); // Bottom left corner
positions.Add(new Vector3(.5f, -.5f, 0)); // Bottom right corner
positions.Add(new Vector3(.5f, .5f, 0)); // Top right corner
// Set up the draw index
index.Add(0); // Draw from top left corner
index.Add(3); // to bottom left
index.Add(2); // then to bottom right
// Next triangle
index.Add(2); // Draw from bottom right
index.Add(1); // to top right
index.Add(0); // then to top left
// Set up text coords
texCoords.Add(new Vector2(0, 1));
texCoords.Add(new Vector2(0, 0));
texCoords.Add(new Vector2(1, 0));
texCoords.Add(new Vector2(1, 1));
// Create our own normals
normals.Add(Vector3.back);
normals.Add(Vector3.back);
normals.Add(Vector3.back);
normals.Add(Vector3.back);
// Now give our mesh this data
thisMesh.vertices = positions.ToArray();
thisMesh.triangles = index.ToArray();
thisMesh.uv = texCoords.ToArray();
//thisMesh.normals = normals.ToArray();
// Thankfully, Unity provides a method to calculate the normals of the mesh
thisMesh.RecalculateNormals();
// Now put this in the mesh filter so the renderer apply a material to it.
GetComponent<MeshFilter>().sharedMesh = thisMesh;
}
// Update is called once per frame
void Update ()
{
}
}</pre>
</div>
<br />And it now renders like this<br />
<br /><a href="http://lh4.ggpht.com/-SO3SV8SPktw/VN6BjtnrVoI/AAAAAAAAB-g/BzjbjaU58Q4/s1600-h/image%25255B66%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-18mlKviakZM/VN6Bj6G1X6I/AAAAAAAAB-o/qEQ-HQBXhXo/image_thumb%25255B34%25255D.png?imgmax=800" height="206" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="503" /></a><br />
<br />I know, I know, you are wondering when we are going to start looking at actually writing a shader, and we will, but first, PLEASE spend some time to understand the values used to create the mesh we just created. Have a play around with the values, set one of the textcoords to .5f,.5f or one of the vertex positions to 1,2,0 or go back to manual normals and set one to Vector3.forward, so you have a clear idea of what all these elements are doing, because if you don’t get these fundamentals clear in your mind, shaders are going to cause you nothing but pain.<br />
<br />As ever if you spot anything in my post(s) that is incorrect or misleading, then please let me know, just post a comment bellow and Ill sort it out, same with any questions you might have, feel free to fire off a comment below.<br />
<br />In the next post we are going to look at creating our first shader, honest :P<br />
<br /><br />
<br /><div align="right">
<a href="http://xboxoneindiedevelopment.blogspot.co.uk/2015/02/unity3d-shaders.html">Next>></a></div>
<br />
<br />
<br />
<br />
<br />
Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com5tag:blogger.com,1999:blog-7807584830587621328.post-86864623517343560752015-02-08T14:57:00.001-08:002015-02-08T15:15:58.966-08:00A Inventory System for Unity<p>So, ages a go, well before Xmas I think (2014) I spotted a fellow indie developer, and now MS Evangelist <a href="http://davevoyles.azurewebsites.net/">Dave Voyles</a> post about an Inventory <a href="http://www.davevoyles.com/creating-inventory-system-unity-using-c/">system he had written for a SMUP</a> he was working on, and thought I would have a go at creating an Inventory system in Unity of my own. By the time I got around to doing this Unity 4.6 was released, so all the code snippets are from that version of Unity, so it’s using the new Unity GUI, which I have to say is a huge improvement.</p> <p>So, the first thing I did was write a simple shooter, so, you have a ship and some asteroids, so we can get damaged by hitting asteroids, destroying asteroids yields gold and silver coins, both of which you can collect. So, we need to pick up coins, first aid kits, and ammo, and that’s where the inventory system comes in.</p> <p><a href="http://lh5.ggpht.com/-FiWr9repeAI/VNfpvCLdGSI/AAAAAAAAB6M/iDqXwEGn1yc/s1600-h/image6.png"><img title="image" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="image" src="http://lh3.ggpht.com/-W9OGo8eqwoA/VNfpvy7w3qI/AAAAAAAAB6Q/SPVeXMb6n-Q/image_thumb2.png?imgmax=800" width="502" height="255"></a></p> <p>As you can see in the screen shot, we are storing the inventory items along the bottom of the screen. So, what do these inventory items look like in code?</p> <p>First thing we need to do is set up some rules, a way to define the different types of items so we know what to do with them, we can also then bunch like types together in the system. I have done this with an Enum</p> <h1>InventoryItemTypesEnum</h1> <p> <div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3efce3e1-8712-4eb5-b79a-64af85980d3d" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">public enum InventoryItemTypesEnum : int<br />{<br /> DeadSpace = 0,<br /> Bullet,<br /> MissileSeeker,<br /> GoldCoins,<br /> SilverCoins,<br /> MediPack,<br />}</pre></div></p><br /><br /><br /><br /><p>In this Enum we have DeadSpace, not used, but wanted to have something like this in case I need to hold blank spaces. The other types are pretty self descriptive I think.</p><br /><h1>InventoryItemScript</h1><br /><p>Using the above Enum I can now create a script that can be used to create each and every inventory item. Using this script I can then create prefabs for each of the different types and have them used in both the game and the inventory system.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:d2b7728c-ca2b-4337-8e0d-7a7a35777196" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#">using UnityEngine;<br />using System.Collections;<br /><br />public class InventoryItemScript : MonoBehaviour <br />{<br /> public string ItemName;<br /> public int Quantity = 0;<br /> public InventoryItemTypesEnum InventoryItemType = InventoryItemTypesEnum.DeadSpace;<br /><br /> public int ScoreModifier = 0;<br /><br /> // Use this for initialization<br /> void Start () <br /> {<br /> <br /> }<br /> <br /> // Update is called once per frame<br /> void Update () <br /> {<br /> <br /> }<br /><br /> void OnTriggerEnter2D(Collider2D collider)<br /> {<br /> if (collider.gameObject.tag == "Player")<br /> {<br /> // Add it to the Inventory..<br /> InventoryScript Inventory = collider.gameObject.GetComponent<InventoryScript>();<br /> collider.gameObject.GetComponent<ShipScript>().Score += ScoreModifier;<br /> Inventory.AddItemToInventory(this);<br /> <br /> Destroy(gameObject);<br /> }<br /> }<br />}</pre></div><br /><p>In here we have the name of the item, a quantity for it and it’s type dictated by the Enum, set initially to DeadSpace as well as if the item has a score modifier, again you could add other properties in here that are pertinent to you game.</p><br /><p>I am using a trigger because I don’t want the collection of the object to impact the craft, when the “Player” collides with the inventory item we get the InventoryScript, this script has the actual inventory system in it, we apply the score modifier and then add this item to the inventory system before we then destroy it.</p><br /><p>We can now create prefabs for each of the inventory types:-</p><br /><h2>Bullet</h2><br /><p><a href="http://lh5.ggpht.com/-kf8uNUXtYkE/VNfpwVbcuwI/AAAAAAAAB6Y/_2clAiilW-Q/s1600-h/image%25255B4%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh4.ggpht.com/-nGPHMzi1XoI/VNfpwnaaRvI/AAAAAAAAB6g/ubPhNaVOsaU/image_thumb%25255B2%25255D.png?imgmax=800" width="246" height="514"></a></p><br /><h2>MissileSeeker</h2><br /><p><a href="http://lh3.ggpht.com/-2rXVLIBQC50/VNfpxCDPZ3I/AAAAAAAAB6o/HMklx85fHs4/s1600-h/image%25255B8%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh3.ggpht.com/-8t8NhWrEKaE/VNfpxmm5yBI/AAAAAAAAB6w/g3d3gV8mp_U/image_thumb%25255B4%25255D.png?imgmax=800" width="243" height="577"></a></p><br /><h2>MediPack</h2><br /><p><a href="http://lh5.ggpht.com/-ZowS9IGtfb8/VNfpyKDBGAI/AAAAAAAAB64/FVEvGFgr928/s1600-h/image%25255B12%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh5.ggpht.com/-l144gs8SScs/VNfpypW_X4I/AAAAAAAAB7A/EP-gswAmwXs/image_thumb%25255B6%25255D.png?imgmax=800" width="252" height="598"></a></p><br /><h2>GoldCoins</h2><br /><p><a href="http://lh4.ggpht.com/-rYuyT5zQsDM/VNfpzGd0ptI/AAAAAAAAB7I/cOapullzcDw/s1600-h/image%25255B17%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh3.ggpht.com/-s6bRZgLAgXY/VNfpznGBSxI/AAAAAAAAB7Q/JuFqFdXBDgE/image_thumb%25255B9%25255D.png?imgmax=800" width="255" height="606"></a></p><br /><h2>SilverCoins</h2><br /><p><a href="http://lh4.ggpht.com/-_sTirtOXsAA/VNfp0M5IjBI/AAAAAAAAB7c/eCUpL80AOWg/s1600-h/image%25255B21%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh3.ggpht.com/-mRhWQr3oDXI/VNfp0ln9Y7I/AAAAAAAAB7g/RIyzRHM-03Y/image_thumb%25255B11%25255D.png?imgmax=800" width="257" height="612"></a></p><br /><h1>InventoryScript</h1><br /><p>Here is where all the magic happens :) First thing I do is set up three public GameObjects. InventoryItem is the part of the HUD that will be used to display the inventory items in, a UI.Image</p><br /><p align="center"><a href="http://lh6.ggpht.com/-x1oj4GccTlA/VNfp1Pui15I/AAAAAAAAB7s/LkDHiEOKYyM/s1600-h/image%25255B25%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://lh6.ggpht.com/-sdFuvKBYQLo/VNfp1opUxUI/AAAAAAAAB7w/Od-XmeT-Unw/image_thumb%25255B13%25255D.png?imgmax=800" width="472" height="267"></a></p><br /><p>The InventoryItemPrefab is a prefab used to instance images in the InventoryItem GamObject</p><br /><p><a href="http://lh4.ggpht.com/-nGzYgSYG_e8/VNfp2LAMuLI/AAAAAAAAB74/7UiyniQBArQ/s1600-h/image%25255B29%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; float: none; padding-top: 0px; padding-left: 0px; margin-left: auto; border-left: 0px; display: block; padding-right: 0px; margin-right: auto" border="0" alt="image" src="http://lh4.ggpht.com/-U8nzv03ZFAA/VNfp2ktiR1I/AAAAAAAAB8A/XcaRrqAQuoQ/image_thumb%25255B15%25255D.png?imgmax=800" width="241" height="568"></a></p><br /><p> </p><br /><p>We then have the Selector GameObject, again this is an object in the HUD and I use that to indicate which inventory is currently selected.</p><br /><p align="center"><a href="http://lh6.ggpht.com/-9EW7R1bS6PI/VNfp3KhqanI/AAAAAAAAB8M/oIYAtZVFOp8/s1600-h/image%25255B33%25255D.png"><img title="image" style="border-top: 0px; border-right: 0px; background-image: none; border-bottom: 0px; padding-top: 0px; padding-left: 0px; border-left: 0px; display: inline; padding-right: 0px" border="0" alt="image" src="http://lh3.ggpht.com/-J2RAATGJRd0/VNfp3sGZG5I/AAAAAAAAB8Q/C1gjwA22VT0/image_thumb%25255B17%25255D.png?imgmax=800" width="476" height="270"></a></p><br /><p>We then have a Dictionary of InventoryItemScript with a string key for the name of the item that is stored in this location of the inventory. There is also a list of GameObjects called ItemUI, these are used to render the representation of the inventory item in the HUD and given the relative name for the inventory item.</p><br /><p>We then have a variable for the spacer, for padding the UI, a count for the number of items types in our system, and finally a variable to store the index of the current selected item in the system.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ded467d7-074a-4cfe-b733-71a7bc797e74" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> public GameObject InventoryDisplay;<br /> public GameObject InventoryItemPrefab;<br /> public GameObject Selector;<br /><br /> Dictionary<string,InventoryItemScript> Items = new Dictionary<string,InventoryItemScript>();<br /><br /> List<GameObject> ItemUI = new List<GameObject>();<br /><br /> float spacer = 16;<br /><br /> int ItemTypesCount = 0;<br /><br /> int SelectedIndex = -1;</pre></div><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><p>Lets start with the method that adds items to the inventory system, the same method we saw earlier in the InventoryItemScript, AddItemToInventory</p><br /><h3>public void AddItemToInventory(InventoryItemScript item)</h3><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:426a39c6-3506-4e58-a41c-70580be05766" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> public void AddItemToInventory(InventoryItemScript item)<br /> {<br /> if (!Items.ContainsKey(item.ItemName))<br /> {<br /> Items.Add(item.ItemName, item);<br /> GameObject ii = (GameObject)Instantiate(InventoryItemPrefab);<br /><br /> ii.name = item.ItemName;<br /> ii.transform.parent = InventoryDisplay.transform;<br /><br /> SetUIItemText(item, ii);<br /><br /> ii.GetComponent<Image>().sprite = item.GetComponent<SpriteRenderer>().sprite;<br /><br /> RectTransform srt = InventoryItemPrefab.GetComponent<RectTransform>();<br /> RectTransform trt = ii.GetComponent<RectTransform>();<br /><br /> int x = (int)(srt.anchoredPosition.x + ((srt.sizeDelta.x + spacer) * ItemTypesCount));<br /> trt.anchoredPosition = new Vector2(x, srt.anchoredPosition.y);<br /><br /> ItemTypesCount = Items.Keys.Count;<br /> ItemUI.Add(ii);<br /> }<br /> else<br /> {<br /> Items[item.ItemName].Quantity += item.Quantity;<br /><br /> GameObject ii = ItemUI.SingleOrDefault(uii => uii.name == item.ItemName);<br /><br /> if (ii != null)<br /> SetUIItemText(Items[item.ItemName], ii); <br /> }<br /><br /> }</pre></div><br /><p>The parameter for this method is the InventoryItemScript item to add to the system. First thing we do is check if we already have an item of this type, if we do, we simply find it in the system and increment it’s quantity, then update the UI text for this item. If we don’t then we add it to our Dictionary then create it’s UI counterpart to go in the list by instancing a new InventoryItemPrefab, we set it’s name, make it a child of InventoryDisplay, set it’s text value, then pull out the sprite so we can then position it in the UI correctly then add it to out ItemsUI list.</p><br /><h3>void Update()</h3><br /><p>Using the left and right arrow keys, the user can select the inventory item they want to use, and by pressing Enter, use it. This is all managed in Update, in here we also manage the SeelctedItemIndex. The Selector is rendered by getting the RectTransfer of the item we want it to appear over and setting it’s anchor position to the RectTransform anchor position. I did this with a new Vector2, but this should be a regular one for one swap.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:74415c76-8fef-40c3-a54c-96eaaa006779" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> void Update()<br /> {<br /> if (ItemTypesCount == 0)<br /> Selector.SetActive(false);<br /> else<br /> {<br /> if(!Selector.activeInHierarchy)<br /> {<br /> SelectedIndex = 0;<br /> Selector.SetActive(true);<br /> }<br /><br /> if (Input.GetKeyDown(KeyCode.RightArrow))<br /> {<br /> SelectedIndex++;<br /> if (SelectedIndex > ItemUI.Count - 1)<br /> SelectedIndex = 0;<br /> }<br /><br /> if (Input.GetKeyDown(KeyCode.LeftArrow))<br /> {<br /> SelectedIndex--;<br /> if (SelectedIndex < 0)<br /> SelectedIndex = ItemUI.Count - 1;<br /> }<br /><br /> RectTransform trt = ItemUI[SelectedIndex].GetComponent<RectTransform>();<br /> Selector.GetComponent<RectTransform>().anchoredPosition = new Vector2(trt.anchoredPosition.x, trt.anchoredPosition.y);<br /><br /> if (Input.GetKeyDown(KeyCode.Return))<br /> UseItem(Items[ItemUI[SelectedIndex].name]);<br /> }<br /> }</pre></div><br /><h3>public void UseItem(InventoryItemScript item)</h3><br /><p>When the player hits Enter, they then “Use” the selected item. This method is a simple switch statement, in here put what ever you want your items to to to or for the player, in this sample I am only doing this for MediPack types, and increasing the players health. This in turn calls RemoveItemFromInventory.</p><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:a971e3c6-03ec-42c1-baec-4619cae10d70" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> public void UseItem(InventoryItemScript item)<br /> {<br /> switch (item.InventoryItemType)<br /> {<br /> case InventoryItemTypesEnum.MediPack:<br /> GetComponent<ShipScript>().Health += .1f;<br /> <br /> RemoveItemFromInventory(item.ItemName, 1);<br /> break;<br /> default:<br /> break;<br /> }<br /><br /> if (SelectedIndex > ItemUI.Count - 1)<br /> SelectedIndex = ItemUI.Count - 1;<br /> }</pre></div><br /><h3>public void RemoveItemFromInventory(string ItemName, int qty)</h3><br /><p>This will only actually remove an item from the system if it’s quantity is less than or equal to zero, otherwise it just subtracts the given quantity from the inventory items quantity and update the UI text for it. If it removed the item from the system it has to then shuffle all the other items in the UI back so there is no gap.</p><br /><br /><br /><div id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:4f6ea807-6a2a-4a92-9d2b-0901e737127c" class="wlWriterEditableSmartContent" style="float: none; padding-bottom: 0px; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px"><pre name="code" class="c#"> public void RemoveItemFromInventory(string ItemName, int qty)<br /> {<br /> if (Items.ContainsKey(ItemName))<br /> {<br /> Items[ItemName].Quantity -= qty;<br /><br /> GameObject ii = ItemUI.SingleOrDefault(uii => uii.name == ItemName);<br /><br /> if (Items[ItemName].Quantity <= 0)<br /> {<br /> RectTransform srt = ii.GetComponent<RectTransform>();<br /> // Move them all back one from this one on..<br /> foreach (GameObject goi in ItemUI)<br /> {<br /> RectTransform trt = goi.GetComponent<RectTransform>();<br /> if (trt.anchoredPosition.x > srt.anchoredPosition.x)<br /> trt.anchoredPosition = new Vector2(trt.anchoredPosition.x - (srt.sizeDelta.x + spacer), trt.anchoredPosition.y);<br /> }<br /><br /> ItemUI.Remove(ii);<br /> Destroy(ii);<br /> Items.Remove(ItemName);<br /> ItemTypesCount = Items.Keys.Count;<br /><br /><br /> }<br /> else<br /> SetUIItemText(Items[ItemName], ii);<br /> }<br /> }</pre></div><br /><p>And well, that’s about it, quite simple really, and I hope you can see how easy it is to extend :)</p><br /><p>If you want to play with the whole project, then the lovely <a href="http://davevoyles.azurewebsites.net/">Dave Voyles</a> as it on his sky drive. If however you find this link is broken, then please let me know and I can host the code from my server. Grab a copy <a href="https://www.facebook.com/l.php?u=https%3A%2F%2Fonedrive.live.com%2Fredir%3Fresid%3D51CCFDB424CB429E%2521263844%26authkey%3D%2521ANrsBS3Wx4QgQi4%26ithint%3Dfolder%252Csln&h=uAQFOjP3R">here</a> :)</p><br /><p>As ever, if you have any questions or suggestions on this post, or any of my posts, please feel free to post here and let me know what you think. </p> Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-66577122177527883142014-12-12T13:40:00.001-08:002014-12-12T13:41:28.349-08:00Object Pooling–Unity3DI know, I know, there are already lots of solutions out there for this in Unity3D, and you have probably already got your own mechanism in place to handle this, but I thought I would post how I have object pooling in my current game project.<br />
Ill start with an apology, I have anew laptop, have not blogged in a while and don’t seem to be able to find the Windows Live Plugin I use to use for pasting code from Visual Studio.<br />
So, what is object pooling, well the usual application of this is for things like bullets, I am also using it for bombs, missiles as well as blast fragments. <br />
As you can imagine you could end up having to create a lot of these sorts of items on the fly using the Unity3D Intrinsic “Instantiate” which will create a totally new object when called. So, a way around this is to have a group of pre instantiated objects, a pool of objects :)<br />
<hr />
<h1>
Object Pooler</h1>
Lest look at my pool class, we will start with the variables we are going to use. It’s derived from MonoBehavior so we can attach it to an empty game object in the editor.<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:3814e10c-8ffb-46c7-a9c9-cfa129f213d3" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols" name="code"> public int StartingPoolSize = 25;
public GameObject ObjectType;
List<GameObject> Queue = new List<GameObject>();
List<GameObject> Live = new List<GameObject>();
public int TotalObjects = 0;
public int QueuedObjects = 0;
public int LiveObjects = 0;</pre>
</div>
<br />
So, we have a starting pool size, I have a default of 25 for this, but in the editor you can set this to be what ever you like.<br />
<br />
We then have an ObjectType, this is the GameObject that is going to be pooled.<br />
<br />
I then have two lists, one is the list of items that are not yet in use, the Queue list and a list of those in use, the Live list.<br />
<br />
I then have a few counters, these are just so I can see what’s going on while I am debugging the pool.<br />
<br />
<hr />
<br />
<br />
<h2>
void Start()</h2>
<br />
Now, the Start method looks like this:<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7caec971-7e03-4d91-8ede-5d12e82f071f" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols" name="code"> void Start()
{
for (int o = 0; o < StartingPoolSize; o++)
AddObject();
}</pre>
</div>
<br />
So, all we are doing here is initializing the Queue list to the size of our default Queue size, so lets look at what the AddObject method is doing.<br />
<br />
<hr />
<br />
<br />
<h2>
void AddObject()</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2b323054-a831-479c-b616-b891dd3a3179" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public void AddObject()
{
Queue.Add((GameObject)Instantiate(ObjectType));
Queue[Queue.Count - 1].transform.parent = transform;
Queue[Queue.Count - 1].gameObject.SetActive(false);
TotalObjects++;
}</pre>
</div>
<br />
All that this method is doing is instantiating an object and adding it to the Queue list, for now making it a child of this game object, and ensuring it is not active. I then increment the total objects count.<br />
<br />
<hr />
<br />
<br />
<h2>
GameObject InstanciateObject()</h2>
<br />
So, now we need a method to get a new object from the Queue and put it in the game world, this is where this method comes in.<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:864e31aa-003a-44da-8ff2-57f228714187" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public GameObject InstanciateObject()
{
if (Queue.Count == 0)
{
// Create 3 more...
for (int i = 0; i < 3; i++)
AddObject();
}
AddLive(Queue[Queue.Count - 1]);
Queue.RemoveAt(Queue.Count - 1);
return Live[Live.Count - 1];
}</pre>
</div>
<br />
The first thing I do is see if there are any object left in the Queue list, the list is reduced each time we take an object and put it in the game world, so if they are all used, we need to pad the list out a little, so if it is empty I create another 3 objects. I guess I could add another public property for growth, so rather than a hard coded 3 the list will grow by this property as it’s set in the editor.<br />
<br />
I then call my AddLive method, this moved the object over to the Live list, makes sure it’s ready for the world and then is activated. I then remove this object from the Queue and return the new live object.<br />
<br />
<hr />
<br />
<br />
<h2>
void AddLive(GameObject o)</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:2ad59aba-1415-484f-b32c-bc51d8579f1d" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> void AddLive(GameObject o)
{
Live.Add(o);
if (Live[Live.Count - 1].GetComponent<GameObjectDeath>() != null)
{
Live[Live.Count - 1].GetComponent<GameObjectDeath>().ReSet();
Live[Live.Count - 1].GetComponent<GameObjectDeath>().IsPooled = true;
}
Live[Live.Count - 1].gameObject.SetActive(true);
}</pre>
</div>
<br />
As you can see, it adds the object to the Live list, then there is a bit of code that makes sure that if this object is being used for the <em>n</em>th time, by that I mean, it’s not the first time it’s been used, that I reset my object. I have a GameObjectDeath script, this script will “Destroy” an object if it collides with another object, or it can be given a life span, this bit of code just makes sure that this gets set. <br />
<br />
Finally it makes the object active.<br />
<br />
<hr />
<br />
<br />
<h2>
void DestroyObject(GameObject o)</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:177a4b1b-7de7-48b9-8cac-f85a33f7292e" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public void DestroyObject(GameObject o)
{
o.SetActive(false);
Queue.Add(o);
Live.Remove(o);
}</pre>
</div>
<br />
And so, when an object is destroyed, rather than calling the intrinsic Unity3D method, we use this method. It deactivates the object and moves it from the Live list back into the Queue.<br />
<br />
To use this now, we can create an empty game object in the editor and attach the script, we can then set these variables in the editor like this:<br />
<br />
<a href="http://lh5.ggpht.com/-r2VJxl4iuZE/VItgvRWYMEI/AAAAAAAAB5c/0bPjOjDr28w/s1600-h/image%25255B38%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-zajrl0a0zMA/VItgvxS4XpI/AAAAAAAAB5k/MQNah1jQhsQ/image_thumb%25255B24%25255D.png?imgmax=800" height="243" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="485" /></a><br />
<br />
As you can see this pool is managing bullets. The bullet object is a prefabricated game object I have already created.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
OK, so now we can pool an object, but wouldn't it be great if we could do this for lots of types of object?<br />
<br />
<hr />
<br />
<br />
<h1>
ObjectPoolManager</h1>
<br />
And that’s where this bit of code comes in :D<br />
<br />
All we need in here are two variables<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:bd5a824f-15dc-41eb-b7d1-a0a09173c4af" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public static ObjectPoolManager thisObjectPoolManager;
List<ObjectPooler> pools;</pre>
</div>
<br />
I have a static so I can get at the class and it’s methods from any other script and a list of ObjectPoller objects. These will be child game objects held in this game object.<br />
<br />
<hr />
<br />
<br />
<h2>
void Start()</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:14dbc662-9d85-4ef9-abf9-f939953ebb1a" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> void Start ()
{
thisObjectPoolManager = this;
pools = GetComponentsInChildren<ObjectPooler>().ToList();
}</pre>
</div>
<br />
<br />
In the Start method we set the static to this, then get all the child ObjectPooler game objects.<br />
<br />
<hr />
<br />
<br />
<h2>
GameObject InstanciateObject(GameObject gameObj)</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:ca616bc5-9be1-4940-b5b8-4ea03e2e1362" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public GameObject InstanciateObject(GameObject gameObj)
{
ObjectPooler pl = pools.First(p => p.ObjectType.name == gameObj.name);
if (pl != null)
return pl.InstanciateObject();
else
{
print("Can't find pool for " + gameObj.name);
return null;
}
}</pre>
</div>
<br />
So, now when another script want’s to instantiate a new game object, it will call the manager, the manger in turn then gets the pool related to the object they want to instantiate, by comparing the pool’s ObjectType.name property with the name property of the object to be instantiated, if found it then calls the pools InstanciateObject method and returns the world ready object to the caller.<br />
<br />
If it can’t find it (and this will be because you have not set up a pool for the required object) then it will return null.<br />
<br />
<hr />
<br />
<br />
<h2>
GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)</h2>
<br />
Sometimes you want p instantiate an object at a position, with a rotation, and this overloaded method will do just that.<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:7f33b636-1ed6-4eb8-857f-819952be84ef" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)
{
GameObject go = InstanciateObject(gameObj);
go.transform.position = Position;
go.transform.rotation = rotation;
return go;
}</pre>
</div>
<br />
<br />
<hr />
<br />
<br />
<h2>
void DestroyObject(GameObject gameObj)</h2>
<br />
<div class="wlWriterEditableSmartContent" id="scid:812469c5-0cb0-4c63-8c15-c81123a09de7:f131f86c-3099-40cd-a964-7d265a43f3e1" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<pre class="c#:nogutter:nocontrols:collapse" name="code"> public void DestroyObject(GameObject gameObj)
{
pools.First(p => p.ObjectType.name+"(Clone)" == gameObj.name).DestroyObject(gameObj);
}</pre>
</div>
<br />
Again, when we want to destroy an object, we call this method, like the InstanciateObject method(s) it finds the required pool, this time it post fixes the ObjectType.name property with “(Clone)” as when you instance an object it has this at the end. I guess you could replace this with a Contains, rather than getting an exact match. Upon finding it, it calls the pool sDestroyObject method passing in the object to be destroyed.<br />
<br />
<br />
<hr />
<br />
<br />
So, in the editor, you can create an empty game object add the ObjectPoolManager script to it, then put all your pool game objects in it like this.<br />
<br />
<a href="http://lh3.ggpht.com/-QDHAtLapE4c/VItgwZ02MuI/AAAAAAAAB5o/9w3rYlcCE10/s1600-h/image%25255B43%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-frUv-qSR2tw/VItgxJAsGwI/AAAAAAAAB50/xuMvEpIF0kI/image_thumb%25255B27%25255D.png?imgmax=800" height="215" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="213" /></a><br />
<br />
Well, that’s my current approach to object pooling, and for the limited way I am using it it’s working out quite well. If you have any questions or suggestions, then please feel free to post here.<br />
<br />
I am also a member of the <a href="https://www.facebook.com/groups/UnityIndieDevs/">Unity Indie Devs on Facebook</a>, if you are not already a member, join up, join the chatter :)Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-48647623971503898392014-06-19T02:48:00.001-07:002014-06-19T02:53:17.589-07:00Ball Shooter Script–Unity3DThis blog is becoming more and more Unity3D focused, the XB1 program is so quiet I have not had anything to post about, maybe I should just recycle the ID game releases that are happening….<br />
So, was catching up with events on FaceBook and I spotted a <a href="https://www.facebook.com/groups/UnityIndieDevs/1433111070288012">post in a group that I am an admin for</a>, asking if anyone knew how to script shooting a ball, as the code they had written was not working as they expected. So, being the kind of person that likes to help people out, I wrote this.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:dc4b38b2-f4ff-48e4-a532-6c07f2dc8b4d" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">using</span> UnityEngine;<br />
<span style="color: blue;">using</span> System.Collections;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">BallShooter</span> : <span style="color: #2b91af;">MonoBehaviour</span> <br />
{<br />
<br />
<span style="color: blue;">bool</span> mousehold = <span style="color: blue;">false</span>;<br />
<span style="color: blue;">bool</span> shoot = <span style="color: blue;">false</span>;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> PowerBuild = .1f;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> MaxVelocity = 2;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> power = 0;<br />
<br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">GameObject</span> ball;<br />
<br />
<span style="color: green;">// Update is called once per frame</span><br />
<span style="color: blue;">void</span> Update () <br />
{<br />
<span style="color: green;">// Player has clicked the left mouse button...</span><br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetMouseButtonDown(0))<br />
{<br />
mousehold = <span style="color: blue;">true</span>;<br />
shoot = <span style="color: blue;">false</span>; <br />
}<br />
<br />
<span style="color: green;">// Player has released the left mouse button..</span><br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetMouseButtonUp(0))<br />
{<br />
<span style="color: blue;">if</span> (mousehold)<br />
shoot = <span style="color: blue;">true</span>;<br />
<br />
mousehold = <span style="color: blue;">false</span>;<br />
}<br />
<br />
<span style="color: green;">// While the player has the left mouse button pressed power up the shot..</span><br />
<span style="color: blue;">if</span> (mousehold && power < 1)<br />
{<br />
power += PowerBuild;<br />
<span style="color: blue;">if</span> (power > 1)<br />
power = 1;<br />
}<br />
<br />
<span style="color: green;">// Shoot the ball!!!</span><br />
<span style="color: blue;">if</span> (shoot)<br />
{<br />
shoot = <span style="color: blue;">false</span>;<br />
<br />
<span style="color: green;">// Get mouse pos in the view.</span><br />
<span style="color: #2b91af;">Vector3</span> mp = <span style="color: #2b91af;">Camera</span>.main.ScreenToViewportPoint(<span style="color: #2b91af;">Input</span>.mousePosition);<br />
<br />
<span style="color: green;">// (.5,.5) is center, so we can elevate and pan the shot angle based on this while creating the velocity.</span><br />
<span style="color: #2b91af;">Vector3</span> velocity = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(MaxVelocity * (mp.x - .5f), MaxVelocity * (mp.y - .5f), MaxVelocity * power);<br />
<br />
<span style="color: green;">// Create the ball.</span><br />
<span style="color: #2b91af;">GameObject</span> shot = (<span style="color: #2b91af;">GameObject</span>)Instantiate(ball, <span style="color: #2b91af;">Camera</span>.main.transform.position + <span style="color: #2b91af;">Vector3</span>.forward, <span style="color: #2b91af;">Quaternion</span>.identity);<br />
<br />
<span style="color: green;">// Give it some velocity.</span><br />
shot.rigidbody.velocity = velocity;<br />
<br />
<span style="color: green;">// Reset the power ready for the next shot.</span><br />
power = 0;<br />
}<br />
}<br />
}</div>
</div>
</div>
As you can see, it’s a pretty simple script, check if the player has the left mouse button pressed, if they do then set the hold and shoot variables, if the player releases the left mouse button, then set the shoot flag and un set the mouse hold flag.<br />
While the left mouse button is down, build up power for the shot. Once released, set the shoot flag.<br />
If the shoot flag is set, calculate the velocity, create an instance of the ball and apply the velocity to it, and that’s about it…<br />
<a href="http://lh6.ggpht.com/-rJagmgj8Ftg/U6Kx3CoHj1I/AAAAAAAAByk/xUkzj80t7j0/s1600-h/QuickPic%25255B7%25255D.jpg"><img alt="QuickPic" border="0" src="http://lh5.ggpht.com/-9vyDbjhczkE/U6Kx30XfoII/AAAAAAAAByo/KlEqflhBj1Q/QuickPic_thumb%25255B4%25255D.jpg?imgmax=800" height="320" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="QuickPic" width="384" /></a><br />
If you want to get the whole unity scene, then you can download it off my server <a href="http://www.randomchaos.co.uk/tutorials/unity3d/BallShooter.zip">here</a>.Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-51064968215963230272014-03-20T12:59:00.000-07:002014-03-20T12:59:45.062-07:00Unity Network Game SampleSo, a few weeks ago I started looking at network gaming using Unity and got something simple working and posted a picture on the Unity Indie Devs FB group.<br />
<a href="http://lh6.ggpht.com/-W_0ms-qWLiw/UysShNjSRSI/AAAAAAAABws/speoyodLpMk/s1600-h/UnityNetworking5.jpg"><img alt="UnityNetworking" border="0" src="http://lh3.ggpht.com/-Z9RMTInFyaY/UysSh-F0zEI/AAAAAAAABw0/BT774IGXoLE/UnityNetworking_thumb2.jpg?imgmax=800" height="191" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="UnityNetworking" width="270" /></a><br />
Now, as you know this is really a XBox One Indie Development blog, how is this relevant? Well, as I am sure you know, if you are part of the <a href="http://www.xbox.com/en-GB/Developers/id">ID@XBox</a> program, then it has been announced that we will get an XB1 license for free for developing on the XB1 :D<br />
So, I have embellished on this work in progress a little , and it now looks like this<br />
<a href="http://lh4.ggpht.com/-E9OdyR4Bwo8/UysSipPiafI/AAAAAAAABw8/42nr-LYn3ZI/s1600-h/UnityNetworkGameSample5.jpg"><img alt="UnityNetworkGameSample" border="0" src="http://lh3.ggpht.com/-bHTcqfDoqsU/UysSjKh_ozI/AAAAAAAABxA/UA3rTDxk7P4/UnityNetworkGameSample_thumb2.jpg?imgmax=800" height="232" style="background-image: none; border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="UnityNetworkGameSample" width="278" /></a><br />
As you can see, there are a few things we can go over, from the star field (it’s animated by the way, I know you can’t see that) to the ship trails as well as the main thread of this post, networking.<br />
I am going to focus on the networking as I am going to put all the source up in a zip at the end for you to download, so you can have a look at how I have done it all, I dare say, Ill explain some of it as I go.<br />
<h2>
<span style="color: red;">Caveat:-</span></h2>
All that you read here is just how I found I could get it working, the only tutorials I have used have been for the basic network connection stuff, the rest is all my own work. So, with that being said, this may not be the best way to go about writing this in Unity 4.x, but it’s the way I have found (so far) I just hope that this gives those of you that have never done it before somewhere to start, and for those of you that this is all old hat for, please let me know how I can improve on it.<br />
<h1>
NetworkManager</h1>
The first thing we are going to do is create a network manager to handle the setting up of the server and the connection’s between it and the client. I have written my network manager so that if there are no games being hosted, it will become the server, if there is 1, then it will attempt to connect. In a real game, I/we would create a lobby and have the option to either be a server or look for a server to join, but from this code I am sure you can figure out how easy that would be to do.<br />
I went about creating my network manager by creating an empty GameObject in the scene, to that I had it host a GUIText GameObject like this:<br />
<a href="http://lh4.ggpht.com/-7-4FTYPK4Z8/UysSjvYCpbI/AAAAAAAABxM/WgWQow7t9r8/s1600-h/image%25255B7%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-QBCtFTQxBY0/UysSkINOX_I/AAAAAAAABxU/mXgC8GN3LTs/image_thumb%25255B3%25255D.png?imgmax=800" height="155" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="344" /></a><br />
I then added a C# script to the project and called it NetworkManager and added it to the empty GameObject.<br />
<a href="http://lh6.ggpht.com/-smR5LCgvr4I/UysSk077J-I/AAAAAAAABxc/Ko8qsbzpl_o/s1600-h/image%25255B12%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-5v6Hu8g1QoE/UysSlednhEI/AAAAAAAABxk/QG2w0xDbDNU/image_thumb%25255B6%25255D.png?imgmax=800" height="376" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="261" /></a><br />
<h2>
NetworkManager.cs</h2>
There is a far bit going on in here, lets have a look…<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:83806000-9801-49b9-922f-c6222cd99273" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">using</span> UnityEngine;<br />
<span style="color: blue;">using</span> System.Collections;<br />
<span style="color: blue;">using</span> System;<br />
<span style="color: blue;">using</span> System.Linq;<br />
<span style="color: blue;">using</span> System.Collections.Generic;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">NetworkManager</span> : <span style="color: #2b91af;">MonoBehaviour</span> <br />
{<br />
<span style="color: green;">// Intrinsic .NET Random number generator class, Unity has one to, but I still use this.</span><br />
System.<span style="color: #2b91af;">Random</span> rnd = <span style="color: blue;">new</span> System.<span style="color: #2b91af;">Random</span>(<span style="color: #2b91af;">DateTime</span>.Now.Millisecond);<br />
<br />
<span style="color: green;">// The Prefab that will be used to create the player in the game.</span><br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">GameObject</span> PlayerPrefab;<br />
<br />
<span style="color: green;">// A List of available games being hosted, if there are none, then</span><br />
<span style="color: green;">// we will become the server and clients will connect to us</span><br />
<span style="color: blue;">private</span> <span style="color: #2b91af;">HostData</span>[] hostList = <span style="color: blue;">new</span> <span style="color: #2b91af;">HostData</span>[] { };<br />
<br />
<span style="color: green;">// The maximum number of players that can connect to the server</span><br />
<span style="color: blue;">public</span> <span style="color: blue;">int</span> MaxPlayers = 32;<br />
<br />
<span style="color: green;">// Status text used to display network status messages.</span><br />
<span style="color: #2b91af;">GUIText</span> StatusText;<br />
<br />
<span style="color: green;">// Use this for initialization</span><br />
<span style="color: blue;">void</span> Start () <br />
{<br />
<span style="color: green;">// Setup the connection.</span><br />
<span style="color: green;">// MasterServer.RequestHostList will result in a call to OnMasterServerEvent</span><br />
<span style="color: #2b91af;">MasterServer</span>.RequestHostList(<span style="color: #a31515;">"RandomChaosNetworkGameSample"</span>);<br />
<br />
<span style="color: green;">// Set the current status.</span><br />
StatusText = GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>();<br />
StatusText.transform.position = <span style="color: #2b91af;">Camera</span>.main.ScreenToViewportPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(8, 4, 0));<br />
SetStatus(<span style="color: #a31515;">"Checking Network Status"</span>);<br />
}<br />
<br />
<span style="color: blue;"> #region</span> Intrinsic Unity Network methods<br />
<span style="color: blue;">void</span> OnMasterServerEvent(<span style="color: #2b91af;">MasterServerEvent</span> msEvent)<br />
{<br />
<span style="color: green;">// This function handles lots of events, we just want to know about HostListReceived</span><br />
<span style="color: blue;">if</span> (msEvent == <span style="color: #2b91af;">MasterServerEvent</span>.HostListReceived)<br />
{<br />
<span style="color: green;">// Poll the list</span><br />
hostList = <span style="color: #2b91af;">MasterServer</span>.PollHostList();<br />
<br />
<span style="color: green;">// Is a game already beeing hoste?</span><br />
<span style="color: blue;">if</span> (hostList.Length == 0)<br />
{<br />
<span style="color: green;">// Nope? Then host one :)</span><br />
SetStatus(<span style="color: #a31515;">"Initializing Server"</span>);<br />
<span style="color: green;">// This will result in OnServerInitialized being called</span><br />
<span style="color: #2b91af;">Network</span>.InitializeServer(MaxPlayers, 8080, !<span style="color: #2b91af;">Network</span>.HavePublicAddress());<br />
<span style="color: #2b91af;">Network</span>.incomingPassword = <span style="color: #a31515;">"oxopots"</span>;<br />
<span style="color: #2b91af;">MasterServer</span>.RegisterHost(<span style="color: #a31515;">"RandomChaosNetworkGameSample"</span>, <span style="color: #a31515;">"This Game"</span>, <span style="color: #a31515;">"This is a sample"</span>);<br />
}<br />
<span style="color: blue;">else</span><br />
{<br />
<span style="color: green;">// Yes, so connect to it.</span><br />
SetStatus(<span style="color: #a31515;">"Connecting to Server"</span>);<br />
<span style="color: green;">// This will result in OnConnectedToServer being called</span><br />
<span style="color: #2b91af;">Network</span>.Connect(hostList[0],<span style="color: #a31515;">"oxopots"</span>);<br />
}<br />
<br />
}<br />
}<br />
<br />
<span style="color: green;">// This method gets called once we have set up the server.</span><br />
<span style="color: blue;">void</span> OnServerInitialized()<br />
{<br />
SetStatus(<span style="color: #a31515;">"Server Initializied"</span>);<br />
SpawnPlayer(<span style="color: #2b91af;">Vector3</span>.zero);<br />
}<br />
<br />
<span style="color: green;">// Method to change the displayed network status message</span><br />
<span style="color: blue;">void</span> SetStatus(<span style="color: blue;">string</span> status)<br />
{<br />
StatusText.text = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"[{0}]"</span>, status);<br />
}<br />
<br />
<span style="color: green;">// The server has this method called each time a connection is made</span><br />
<span style="color: blue;">void</span> OnPlayerConnected(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"New Player Connected. {0}/{1} total"</span>, <span style="color: #2b91af;">Network</span>.connections.Length + 1, <span style="color: #2b91af;">Network</span>.maxConnections));<br />
}<br />
<br />
<span style="color: green;">// Clients connecting to the server will have this called once the client has connected.</span><br />
<span style="color: blue;">void</span> OnConnectedToServer()<br />
{<br />
SetStatus(<span style="color: #a31515;">"Connected to Server."</span>);<br />
SpawnClientPlayer();<br />
}<br />
<br />
<span style="color: green;">// Method to spawn the client player, we don't want to have every one</span><br />
<span style="color: green;">// start in the same spot, so we randomize it a little</span><br />
<span style="color: blue;">public</span> <span style="color: blue;">void</span> SpawnClientPlayer(<span style="color: blue;">string</span> name = <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: green;">// Random position..</span><br />
<span style="color: blue;">float</span> x, y;<br />
x = <span style="color: #2b91af;">Mathf</span>.Lerp(-10, 10, (<span style="color: blue;">float</span>)rnd.NextDouble());<br />
y = <span style="color: #2b91af;">Mathf</span>.Lerp(-10, 10, (<span style="color: blue;">float</span>)rnd.NextDouble());<br />
SpawnPlayer(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(x, y, 0), name);<br />
}<br />
<br />
<span style="color: green;">// Method called when disconnecting</span><br />
<span style="color: blue;">void</span> OnDisconnectedFromServer(<span style="color: #2b91af;">NetworkDisconnection</span> info)<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Network</span>.isServer) {<br />
SetStatus(<span style="color: #a31515;">"Local server connection disconnected"</span>);<br />
}<br />
<span style="color: blue;">else</span> <br />
{<br />
<span style="color: blue;">if</span> (info == <span style="color: #2b91af;">NetworkDisconnection</span>.LostConnection)<br />
SetStatus(<span style="color: #a31515;">"Lost connection to the server"</span>);<br />
<span style="color: blue;">else</span><br />
SetStatus(<span style="color: #a31515;">"Successfully diconnected from the server"</span>);<br />
}<br />
}<br />
<br />
<span style="color: green;">// Method calld if there is a connection issue</span><br />
<span style="color: blue;">void</span> OnFailedToConnect(<span style="color: #2b91af;">NetworkConnectionError</span> error)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Could not connect to server: {0}"</span>, error));<br />
}<br />
<br />
<span style="color: green;">// Method called on the server when a client disconnects</span><br />
<span style="color: blue;">void</span> OnPlayerDisconnected(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
SetStatus(<span style="color: #a31515;">"player has disconnected"</span>);<br />
<br />
<span style="color: #2b91af;">PlayerControler</span> thisPlayer = GetNetworkPlayerControler(player);<br />
<span style="color: blue;">if</span> (thisPlayer != <span style="color: blue;">null</span>)<br />
DestroyPlayer(thisPlayer.networkView.viewID.ToString(), thisPlayer.DisplayName);<br />
<br />
<span style="color: #2b91af;">Network</span>.RemoveRPCs(player);<br />
<span style="color: #2b91af;">Network</span>.DestroyPlayerObjects(player);<br />
}<br />
<br />
<span style="color: blue;"> #endregion</span><br />
<br />
<span style="color: blue;"> #region</span> My Helper Methods<br />
<br />
<span style="color: green;">// Method used to get a player based on NetworkPlayer</span><br />
<span style="color: #2b91af;">PlayerControler</span> GetNetworkPlayerControler(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>> players = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>>(FindObjectsOfType<<span style="color: #2b91af;">PlayerControler</span>>());<br />
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">PlayerControler</span> go <span style="color: blue;">in</span> players)<br />
{<br />
<span style="color: blue;">if</span> (go.gameObject.networkView.owner == player)<br />
<span style="color: blue;">return</span> go; <br />
}<br />
<br />
<span style="color: blue;">return</span> <span style="color: blue;">null</span>;<br />
}<br />
<br />
<span style="color: green;">// Method to spawn a player</span><br />
<span style="color: blue;">void</span> SpawnPlayer(<span style="color: #2b91af;">Vector3</span> position, <span style="color: blue;">string</span> name = <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: #2b91af;">GameObject</span> thisPlayer = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(PlayerPrefab, position, <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Network</span>.isServer)<br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = <span style="color: #a31515;">"Server Plr"</span>;<br />
<br />
<span style="color: blue;">if</span> (name != <span style="color: blue;">null</span>) <br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = name;<br />
<br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().UpdateNetworkPlayer();<br />
}<br />
<br />
<br />
<span style="color: green;">// MEthod to update a given player</span><br />
<span style="color: blue;">public</span> <span style="color: blue;">void</span> UpdatePlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> dispName, <span style="color: blue;">string</span> text)<br />
{<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">GameObject</span>> objs = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">GameObject</span>>(FindObjectsOfType<<span style="color: #2b91af;">GameObject</span>>());<br />
<br />
<span style="color: #2b91af;">GameObject</span> updOb = objs.SingleOrDefault(e => e.networkView != <span style="color: blue;">null</span> && e.networkView.viewID.ToString() == id);<br />
<span style="color: blue;">if</span> (updOb != <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: blue;">if</span> (text.Contains(<span style="color: #a31515;">"Score"</span>))<br />
{<br />
<span style="color: blue;">int</span> s = text.IndexOf(<span style="color: #a31515;">"Score:"</span>) + 6;<br />
<span style="color: blue;">int</span> l = text.IndexOf(<span style="color: #a31515;">"\n"</span>, s) - s;<br />
<span style="color: blue;">int</span> score = 0;<br />
<span style="color: blue;">int</span> kills = 0;<br />
<span style="color: blue;">float</span> hits = 1;<br />
<br />
<span style="color: blue;">int</span>.TryParse(text.Substring(s, l), <span style="color: blue;">out</span> score);<br />
<br />
s = text.IndexOf(<span style="color: #a31515;">"Kills:"</span>) + 6;<br />
l = text.IndexOf(<span style="color: #a31515;">"\n"</span>, s) - s;<br />
<span style="color: blue;">int</span>.TryParse(text.Substring(s,l), <span style="color: blue;">out</span> kills);<br />
<br />
<span style="color: blue;">float</span>.TryParse(text.Substring(s + l), <span style="color: blue;">out</span> hits);<br />
<br />
text = text.Substring(0, s+l);<br />
<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().Score = score;<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().Kills = kills;<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().hits = hits;<br />
}<br />
<br />
<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = dispName;<br />
updOb.GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>().text = text;<br />
}<br />
}<br />
<br />
<span style="color: green;">// Method used to update the network status for a disconnected player.</span><br />
<span style="color: blue;">public</span> <span style="color: blue;">void</span> DestroyPlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> playerName)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} disconnected"</span>, playerName));<br />
}<br />
<span style="color: blue;"> #endregion</span><br />
<br />
}</div>
</div>
</div>
Lets start from the top and work our way down the code. There are five fields in this class two are public the rest are private, the two public ones as you can see in the editor screen shot above are the PlayerPrefab and MaxPlayers.<br />
The PlayerPrefab is the prefab that will be used to instance the player(s) in the game, we will come to the creation of that in a little while, the MaxPlayers is where you can set the maximum number of players a server can host.<br />
Lets get into these methods now:-<br />
<h4>
void Start()</h4>
Like most MonoBehavior’s we have a Start method, in here we are going to set up the network.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:b21dc9bf-18e0-461b-9628-924331ee1a26" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: green;">// Use this for initialization</span><br />
<span style="color: blue;">void</span> Start () <br />
{<br />
<span style="color: green;">// Setup the connection.</span><br />
<span style="color: green;">// MasterServer.RequestHostList will result in a call to OnMasterServerEvent</span><br />
<span style="color: #2b91af;">MasterServer</span>.RequestHostList(<span style="color: #a31515;">"RandomChaosNetworkGameSample"</span>);<br />
<br />
<span style="color: green;">// Set the current status.</span><br />
StatusText = GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>();<br />
StatusText.transform.position = <span style="color: #2b91af;">Camera</span>.main.ScreenToViewportPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(8, 4, 0));<br />
SetStatus(<span style="color: #a31515;">"Checking Network Status"</span>);<br />
}</div>
</div>
</div>
So, as I say, we want to know if there are any games already being played, so we use the Unity MasterServer class and request a list of hosts, this method will return in the function OnMasterServerEvent, we will look at that next. Once we have made this asynchronous call, we then set the StatusText up, we know it’s a child component, so we can get it with GetComponentInChildren and bind it to our StatusText variable and we can then use my method of SetStatus to update it on the screen for the user.<br />
<h4>
void OnMasterServerEvent(MasterServerEvent msEvent)</h4>
This method is provided by Unity, and is called as a response to MasterServer.RequestHostList, we can now decide if we need to connect to an existing game or create one of our own.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:732bd67f-61c5-4f7f-a390-b9e399d72f50" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnMasterServerEvent(<span style="color: #2b91af;">MasterServerEvent</span> msEvent)<br />
{<br />
<span style="color: green;">// This function handles lots of events, we just want to know about HostListReceived</span><br />
<span style="color: blue;">if</span> (msEvent == <span style="color: #2b91af;">MasterServerEvent</span>.HostListReceived)<br />
{<br />
<span style="color: green;">// Poll the list</span><br />
hostList = <span style="color: #2b91af;">MasterServer</span>.PollHostList();<br />
<br />
<span style="color: green;">// Is a game already beeing hoste?</span><br />
<span style="color: blue;">if</span> (hostList.Length == 0)<br />
{<br />
<span style="color: green;">// Nope? Then host one :)</span><br />
SetStatus(<span style="color: #a31515;">"Initializing Server"</span>);<br />
<span style="color: green;">// This will result in OnServerInitialized being called</span><br />
<span style="color: #2b91af;">Network</span>.InitializeServer(MaxPlayers, 8080, !<span style="color: #2b91af;">Network</span>.HavePublicAddress());<br />
<span style="color: #2b91af;">Network</span>.incomingPassword = <span style="color: #a31515;">"oxopots"</span>;<br />
<span style="color: #2b91af;">MasterServer</span>.RegisterHost(<span style="color: #a31515;">"RandomChaosNetworkGameSample"</span>, <span style="color: #a31515;">"This Game"</span>, <span style="color: #a31515;">"This is a sample"</span>);<br />
}<br />
<span style="color: blue;">else</span><br />
{<br />
<span style="color: green;">// Yes, so connect to it.</span><br />
SetStatus(<span style="color: #a31515;">"Connecting to Server"</span>);<br />
<span style="color: green;">// This will result in OnConnectedToServer being called</span><br />
<span style="color: #2b91af;">Network</span>.Connect(hostList[0],<span style="color: #a31515;">"oxopots"</span>);<br />
}<br />
<br />
}<br />
}</div>
</div>
</div>
If no hosts are present then we will call Network.InitializeServer, this will, if all goes well, result in OnServerInitialized being called so we know the server has been set up and is waiting for players to join. If there are games already hosted, then we will connect to the first one in the list with Network.Connect, as I say, in an actual game, we would be able to connect to any we liked or serve our own.<br />
<h4>
void OnServerInitialized()</h4>
So, we have initialized the server, let the user know with SetStatus and spawn him/her a player to play the game with using SpawnPlayer and put them in the center of the game world.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:0728e919-3da7-4799-b0ca-d6ddefdf80f4" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnServerInitialized()<br />
{<br />
SetStatus(<span style="color: #a31515;">"Server Initializied"</span>);<br />
SpawnPlayer(<span style="color: #2b91af;">Vector3</span>.zero);<br />
}</div>
</div>
</div>
<h4>
void SetStatus(string status)</h4>
This is my method to keep the user informed on what’s going on with the network, all it does it update the StatusText GUText text with what we want to show.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:a87c4f44-47cb-4935-b2d4-5b2dcb4b4a48" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> SetStatus(<span style="color: blue;">string</span> status)<br />
{<br />
StatusText.text = <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"[{0}]"</span>, status);<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnPlayerConnected(NetworkPlayer player)</h4>
A player has connected to the server, so, again, we inform the server player.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:6c8f8722-0fa8-4f16-91bf-a3b22a515c9b" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnPlayerConnected(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"New Player Connected. {0}/{1} total"</span>, <span style="color: #2b91af;">Network</span>.connections.Length + 1, <span style="color: #2b91af;">Network</span>.maxConnections));<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnConnectedToServer()</h4>
This method is called by the client network after a successful Network.Connect, so in here we are going to tell the client player he has connected and create him/her a player to use.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:fae2ecfb-06a8-42f6-bdbe-7d40b3d5b778" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnConnectedToServer()<br />
{<br />
SetStatus(<span style="color: #a31515;">"Connected to Server."</span>);<br />
SpawnClientPlayer();<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
public void SpawnClientPlayer(string name = null)</h4>
This met5hod spawns a player, it differs as it will place the player at a random location and if it’s a re spawn, assign the new player with the current players name.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:c5f04c1d-84a1-43a1-8292-8606ab1aff6c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> SpawnClientPlayer(<span style="color: blue;">string</span> name = <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: green;">// Random position..</span><br />
<span style="color: blue;">float</span> x, y;<br />
x = <span style="color: #2b91af;">Mathf</span>.Lerp(-10, 10, (<span style="color: blue;">float</span>)rnd.NextDouble());<br />
y = <span style="color: #2b91af;">Mathf</span>.Lerp(-10, 10, (<span style="color: blue;">float</span>)rnd.NextDouble());<br />
SpawnPlayer(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(x, y, 0), name);<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnDisconnectedFromServer(NetworkDisconnection info)</h4>
This method is called by both client and server when they disconnect, it’s used here just to inform the user.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:efeca688-093c-4906-a22c-7d8e9e4e7718" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnDisconnectedFromServer(<span style="color: #2b91af;">NetworkDisconnection</span> info)<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Network</span>.isServer) {<br />
SetStatus(<span style="color: #a31515;">"Local server connection disconnected"</span>);<br />
}<br />
<span style="color: blue;">else</span> <br />
{<br />
<span style="color: blue;">if</span> (info == <span style="color: #2b91af;">NetworkDisconnection</span>.LostConnection)<br />
SetStatus(<span style="color: #a31515;">"Lost connection to the server"</span>);<br />
<span style="color: blue;">else</span><br />
SetStatus(<span style="color: #a31515;">"Successfully diconnected from the server"</span>);<br />
}<br />
}</div>
</div>
</div>
<h4>
void OnFailedToConnect(NetworkConnectionError error)</h4>
Again, another informative method should the connection fail.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:03172b72-c1e4-45cc-bf48-aee7a4684252" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnFailedToConnect(<span style="color: #2b91af;">NetworkConnectionError</span> error)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"Could not connect to server: {0}"</span>, error));<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnPlayerDisconnected(NetworkPlayer player)</h4>
This method is called on the server when a player drops off, we inform the user then clear up the disconnected player. Using the provided NetworkPlayer object we need to find the player GameObject in the scene, then, if found destroy it, in this case we just inform the user with this method. We then clear our the players RPC and PlayerObjects over the network, this will remove it from all clients.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:d014f636-ce2a-407c-91ef-ca1233d2625b" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnPlayerDisconnected(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
SetStatus(<span style="color: #a31515;">"player has disconnected"</span>);<br />
<br />
<span style="color: #2b91af;">PlayerControler</span> thisPlayer = GetNetworkPlayerControler(player);<br />
<span style="color: blue;">if</span> (thisPlayer != <span style="color: blue;">null</span>)<br />
DestroyPlayer(thisPlayer.networkView.viewID.ToString(), thisPlayer.DisplayName);<br />
<br />
<span style="color: #2b91af;">Network</span>.RemoveRPCs(player);<br />
<span style="color: #2b91af;">Network</span>.DestroyPlayerObjects(player);<br />
}</div>
</div>
</div>
<h4>
PlayerControler GetNetworkPlayerControler(NetworkPlayer player)</h4>
This is a method of my own creation to get a player GameObject based on a given NetworkPlayer object.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:a7175987-77e8-4c63-bb5a-61d821050a08" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: #2b91af;">PlayerControler</span> GetNetworkPlayerControler(<span style="color: #2b91af;">NetworkPlayer</span> player)<br />
{<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>> players = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>>(FindObjectsOfType<<span style="color: #2b91af;">PlayerControler</span>>());<br />
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">PlayerControler</span> go <span style="color: blue;">in</span> players)<br />
{<br />
<span style="color: blue;">if</span> (go.gameObject.networkView.owner == player)<br />
<span style="color: blue;">return</span> go; <br />
}<br />
<br />
<span style="color: blue;">return</span> <span style="color: blue;">null</span>;<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void SpawnPlayer(Vector3 position, string name = null)</h4>
This is my method to spawn a player GameObject in the scene. By using Network.Instantiate rather than Instantiate, we create the object on all the client machines too :) We then can set the players name and update it, both of these will be covered in the PlayerControler section.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:cb0ae15c-0739-461b-b0e3-6c4c49da8a3d" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> SpawnPlayer(<span style="color: #2b91af;">Vector3</span> position, <span style="color: blue;">string</span> name = <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: #2b91af;">GameObject</span> thisPlayer = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(PlayerPrefab, position, <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Network</span>.isServer)<br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = <span style="color: #a31515;">"Server Plr"</span>;<br />
<br />
<span style="color: blue;">if</span> (name != <span style="color: blue;">null</span>) <br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = name;<br />
<br />
thisPlayer.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().UpdateNetworkPlayer();<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
public void UpdatePlayer(string id, string dispName, string text)</h4>
This is my method to update a given player in the scene based on an ID, this again will be covered in the PlayerControler section as it is called from there.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:6bb662f1-e43c-49c6-8521-e76eb9662f89" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> UpdatePlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> dispName, <span style="color: blue;">string</span> text)<br />
{<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">GameObject</span>> objs = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">GameObject</span>>(FindObjectsOfType<<span style="color: #2b91af;">GameObject</span>>());<br />
<br />
<span style="color: #2b91af;">GameObject</span> updOb = objs.SingleOrDefault(e => e.networkView != <span style="color: blue;">null</span> && e.networkView.viewID.ToString() == id);<br />
<span style="color: blue;">if</span> (updOb != <span style="color: blue;">null</span>)<br />
{<br />
<span style="color: blue;">if</span> (text.Contains(<span style="color: #a31515;">"Score"</span>))<br />
{<br />
<span style="color: blue;">int</span> s = text.IndexOf(<span style="color: #a31515;">"Score:"</span>) + 6;<br />
<span style="color: blue;">int</span> l = text.IndexOf(<span style="color: #a31515;">"\n"</span>, s) - s;<br />
<span style="color: blue;">int</span> score = 0;<br />
<span style="color: blue;">int</span> kills = 0;<br />
<span style="color: blue;">float</span> hits = 1;<br />
<br />
<span style="color: blue;">int</span>.TryParse(text.Substring(s, l), <span style="color: blue;">out</span> score);<br />
<br />
s = text.IndexOf(<span style="color: #a31515;">"Kills:"</span>) + 6;<br />
l = text.IndexOf(<span style="color: #a31515;">"\n"</span>, s) - s;<br />
<span style="color: blue;">int</span>.TryParse(text.Substring(s,l), <span style="color: blue;">out</span> kills);<br />
<br />
<span style="color: blue;">float</span>.TryParse(text.Substring(s + l), <span style="color: blue;">out</span> hits);<br />
<br />
text = text.Substring(0, s+l);<br />
<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().Score = score;<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().Kills = kills;<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().hits = hits;<br />
}<br />
<br />
<br />
updOb.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>().DisplayName = dispName;<br />
updOb.GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>().text = text;<br />
}<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
public void DestroyPlayer(string id, string playerName)</h4>
A method to update the network status to inform the user a player has been destroyed.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:040c7105-2ba5-4fb7-86ea-9a63cdc23311" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> DestroyPlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> playerName)<br />
{<br />
SetStatus(<span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} disconnected"</span>, playerName));<br />
}</div>
</div>
</div>
<h1>
</h1>
<h1>
Player Prefab</h1>
So, we have a network manager, now we need a player to play the game with. I started off creating a Sprite GameObject in the scene, then, like with the Nework Manager gave it a child of GUText so it’s stats can be displayed. I then created a C# script called PlayerControler and attached this to the Sprite as well as giving it a RigidBody2D, a TrailRenderer a NewtorkView (most important) and a PollygonCollider2D. I then made this a prefab by dragging it into my Prefabs folder and deleted the one in the scene.<br />
<a href="http://lh3.ggpht.com/-2BiFFx0u3O0/UysSmaY-NJI/AAAAAAAABxs/YbIa1ikKuks/s1600-h/image%25255B17%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-bLcqeTD1bIo/UysSnG9HYtI/AAAAAAAABxw/Js2Hj7JAcp8/image_thumb%25255B9%25255D.png?imgmax=800" height="364" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="317" /></a><br />
<h2>
PlayerControler.cs</h2>
I guess this is where all the gameplay and gameplay networking sits, and to be honest, there is not a lot as the NetworkView does most of it for me. Here is the class in full, we can then take a look at what is going on here.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:c0f2772c-36b1-4a4f-a7b8-0c5ba785136c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">using</span> UnityEngine;<br />
<span style="color: blue;">using</span> System.Collections;<br />
<span style="color: blue;">using</span> System.Collections.Generic;<br />
<span style="color: blue;">using</span> System.Linq;<br />
<span style="color: blue;">using</span> System;<br />
<br />
[<span style="color: #2b91af;">RequireComponent</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">Rigidbody2D</span>))]<br />
[<span style="color: #2b91af;">RequireComponent</span>(<span style="color: blue;">typeof</span>(<span style="color: #2b91af;">NetworkView</span>))]<br />
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">PlayerControler</span> : <span style="color: #2b91af;">MonoBehaviour</span><br />
{<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> ShipRotationSpeed = 1;<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> ShipThrust = 2;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">string</span> DisplayName = <span style="color: #a31515;">"New Player"</span>;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> Score = 0;<br />
<span style="color: blue;">public</span> <span style="color: blue;">int</span> Kills = 0;<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> hits = 1;<br />
<br />
<span style="color: blue;">bool</span> canShoot;<br />
<span style="color: #2b91af;">TimeSpan</span> lastShot = <span style="color: #2b91af;">TimeSpan</span>.Zero;<br />
<span style="color: #2b91af;">TimeSpan</span> shootDelay = <span style="color: blue;">new</span> <span style="color: #2b91af;">TimeSpan</span>(0, 0, 0, 0, 250);<br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">GameObject</span> BulletPrefab;<br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">GameObject</span> BoomPrefab;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">string</span> DisplayStatus<br />
{<br />
<span style="color: blue;">get</span> { <span style="color: blue;">return</span> <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0}\nScore:{1:00000}\nKills:{2:0000}"</span>, DisplayName, Score, Kills); }<br />
}<br />
<br />
<span style="color: blue;">void</span> Awake()<br />
{<br />
<span style="color: green;">// assign me to the camera if this is my network session..</span><br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
<span style="color: #2b91af;">Camera</span>.main.GetComponent<<span style="color: #2b91af;">CameraControler</span>>().Player = gameObject;<br />
}<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">void</span> Shoot()<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">DateTime</span>.Now.TimeOfDay - lastShot >= shootDelay)<br />
{<br />
lastShot = <span style="color: #2b91af;">DateTime</span>.Now.TimeOfDay;<br />
<span style="color: #2b91af;">GameObject</span> bullet = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(BulletPrefab, transform.TransformPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(.5f, .125f, 0)), <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
bullet.GetComponent<<span style="color: #2b91af;">BulletControler</span>>().owner = gameObject;<br />
bullet.transform.rotation = transform.rotation;<br />
bullet.rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector3</span>.right) * 8;<br />
<br />
bullet = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(BulletPrefab, transform.TransformPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(.5f, -.125f, 0)), <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
bullet.GetComponent<<span style="color: #2b91af;">BulletControler</span>>().owner = gameObject;<br />
bullet.transform.rotation = transform.rotation;<br />
bullet.rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector3</span>.right) * 8; <br />
}<br />
}<br />
<br />
<span style="color: blue;">void</span> OnTriggerEnter2D(<span style="color: #2b91af;">Collider2D</span> collider)<br />
{<br />
<span style="color: #2b91af;">BulletControler</span> bullet = collider.gameObject.GetComponent<<span style="color: #2b91af;">BulletControler</span>>();<br />
<br />
<span style="color: blue;">if</span> (bullet)<br />
{<br />
<span style="color: #2b91af;">PlayerControler</span> plr = bullet.owner.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>();<br />
<br />
hits -= .1f;<br />
<br />
<span style="color: blue;">if</span> (hits <= 0)<br />
{<br />
Instantiate(BoomPrefab, transform.position, <span style="color: #2b91af;">Quaternion</span>.identity);<br />
plr.Kills++;<br />
plr.Score += 1000;<br />
<br />
<span style="color: green;">// Spawn client player</span><br />
<span style="color: blue;">if</span>(networkView.isMine)<br />
FindObjectOfType<<span style="color: #2b91af;">NetworkManager</span>>().SpawnClientPlayer(DisplayName);<br />
<br />
<span style="color: #2b91af;">Network</span>.Destroy(gameObject);<br />
}<br />
<br />
plr.UpdateNetworkPlayer();<br />
UpdateNetworkPlayer();<br />
<br />
plr.Score += 100;<br />
}<br />
}<br />
<br />
<span style="color: green;">// Update is called once per frame</span><br />
<span style="color: blue;">void</span> Update()<br />
{<br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.LeftArrow))<br />
transform.Rotate(<span style="color: #2b91af;">Vector3</span>.forward, ShipRotationSpeed);<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.RightArrow))<br />
transform.Rotate(<span style="color: #2b91af;">Vector3</span>.back, ShipRotationSpeed);<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.LeftArrow) || <span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.RightArrow))<br />
{<br />
<span style="color: blue;">if</span> (transform.InverseTransformDirection(rigidbody2D.velocity).x > 0)<br />
rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * rigidbody2D.velocity.magnitude);<br />
<span style="color: blue;">else</span><br />
rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * -rigidbody2D.velocity.magnitude);<br />
}<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.UpArrow))<br />
rigidbody2D.AddForce(transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * ShipThrust));<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.DownArrow))<br />
rigidbody2D.AddForce(transform.TransformDirection(-<span style="color: #2b91af;">Vector2</span>.right * ShipThrust));<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.Space))<br />
Shoot();<br />
}<br />
<br />
<span style="color: green;">// healing..</span><br />
<span style="color: blue;">if</span> (hits < 1)<br />
{<br />
hits += .005f;<br />
<span style="color: blue;">if</span> (hits > 1)<br />
hits = 1;<br />
<br />
<span style="color: blue;">float</span> c = <span style="color: #2b91af;">Mathf</span>.Lerp(0, 1, hits);<br />
gameObject.renderer.material.color = <span style="color: blue;">new</span> <span style="color: #2b91af;">Color</span>(1, c, c + .25f, 1);<br />
<br />
<span style="color: blue;">if</span>(networkView.isMine)<br />
UpdateNetworkPlayer();<br />
}<br />
<br />
GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>().transform.position = <span style="color: #2b91af;">Camera</span>.main.WorldToViewportPoint(transform.position + (<span style="color: #2b91af;">Vector3</span>.down * 1));<br />
}<br />
<br />
<span style="color: green;">// Method to set an RPC call to all the players on the network to update it's data for this player.</span><br />
<span style="color: blue;">public</span> <span style="color: blue;">void</span> UpdateNetworkPlayer()<br />
{<br />
networkView.RPC(<span style="color: #a31515;">"UpdatePlayer"</span>, <span style="color: #2b91af;">RPCMode</span>.AllBuffered, networkView.viewID.ToString(), DisplayName, DisplayStatus + <span style="color: #a31515;">"\n"</span> + hits.ToString());<br />
}<br />
<br />
[<span style="color: #2b91af;">RPC</span>]<br />
<span style="color: blue;">void</span> UpdatePlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> playerName, <span style="color: blue;">string</span> status)<br />
{<br />
FindObjectOfType<<span style="color: #2b91af;">NetworkManager</span>>().UpdatePlayer(id, playerName, status);<br />
}<br />
<br />
<span style="color: blue;">void</span> OnGUI()<br />
{ <br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
{<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(8, 8, 100, 24), <span style="color: #a31515;">"Your Name:"</span>);<br />
DisplayName = <span style="color: #2b91af;">GUI</span>.TextArea(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(80, 8, 110, 20), DisplayName, 13);<br />
DisplayName = DisplayName.Replace(<span style="color: #a31515;">"\n"</span>, <span style="color: #a31515;">""</span>).Replace(<span style="color: #a31515;">"\r"</span>, <span style="color: #a31515;">""</span>);<br />
UpdateNetworkPlayer();<br />
}<br />
<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>> otherPlayers = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>>(FindObjectsOfType<<span style="color: #2b91af;">PlayerControler</span>>());<br />
<br />
<span style="color: #2b91af;">GUI</span>.Box(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(8, 32, 300, 24 * (otherPlayers.Count + 1)), <span style="color: #a31515;">"Players"</span>);<br />
<br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(12, 56, 300, 24), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} [{1}]"</span>, DisplayStatus.Replace(<span style="color: #a31515;">"\n"</span>,<span style="color: #a31515;">" : "</span>), <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(transform.position.x,transform.position.y)));<br />
<br />
<span style="color: green;">// Display the others..</span><br />
<br />
<br />
<span style="color: blue;">int</span> line = 1;<br />
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">PlayerControler</span> plr <span style="color: blue;">in</span> otherPlayers)<br />
{<br />
<span style="color: blue;">if</span> (!plr.networkView.isMine)<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(12, 56 + (24 * line++), 300, 24), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} [{1}]"</span>, plr.DisplayStatus.Replace(<span style="color: #a31515;">"\n"</span>, <span style="color: #a31515;">" : "</span>), <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(plr.transform.position.x, plr.transform.position.y)));<br />
}<br />
}<br />
<br />
}</div>
</div>
</div>
We have a number of fields and properties here and they are all only relevant to the game play rather than the networking, so how fast the ship can rotate or translate, it’s score, kills, variable used for shooting and some prefabs for the bullets and sound. Lets work our way through the methods…<br />
<h4>
void Awake()</h4>
In here, we check if this is running as the players instance or another players instance, this is done by looking at the networkview and it’s flag isMine, if it is we set the camera so it is looking at this game object, we don’t want to be looking at other players do we…<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:e25d0960-a76d-4222-8f33-444d19359d9c" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> Awake()<br />
{<br />
<span style="color: green;">// assign me to the camera if this is my network session..</span><br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
<span style="color: #2b91af;">Camera</span>.main.GetComponent<<span style="color: #2b91af;">CameraControler</span>>().Player = gameObject;<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
public void Shoot()</h4>
This method is used to create bullets and shoot them, two per shot.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:6a24febe-216a-47d2-8441-d47488130749" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> Shoot()<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">DateTime</span>.Now.TimeOfDay - lastShot >= shootDelay)<br />
{<br />
lastShot = <span style="color: #2b91af;">DateTime</span>.Now.TimeOfDay;<br />
<span style="color: #2b91af;">GameObject</span> bullet = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(BulletPrefab, transform.TransformPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(.5f, .125f, 0)), <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
bullet.GetComponent<<span style="color: #2b91af;">BulletControler</span>>().owner = gameObject;<br />
bullet.transform.rotation = transform.rotation;<br />
bullet.rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector3</span>.right) * 8;<br />
<br />
bullet = (<span style="color: #2b91af;">GameObject</span>)<span style="color: #2b91af;">Network</span>.Instantiate(BulletPrefab, transform.TransformPoint(<span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(.5f, -.125f, 0)), <span style="color: #2b91af;">Quaternion</span>.identity, 0);<br />
bullet.GetComponent<<span style="color: #2b91af;">BulletControler</span>>().owner = gameObject;<br />
bullet.transform.rotation = transform.rotation;<br />
bullet.rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector3</span>.right) * 8; <br />
}<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnTriggerEnter2D(Collider2D collider)</h4>
This is our trigger, if we get hit by a bullet we deal with it as we need to, taking damage, increasing the shooters score and if we die, instantiate a sound effect, re spawn and remove us from the scene. <br />
<em><strong>N.B need to change the re spawn code to re connect rather than just try and add a new player object.</strong></em><br />
<h4>
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:ab4625b5-da03-4ebd-b44b-72674ddfb28d" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnTriggerEnter2D(<span style="color: #2b91af;">Collider2D</span> collider)<br />
{<br />
<span style="color: #2b91af;">BulletControler</span> bullet = collider.gameObject.GetComponent<<span style="color: #2b91af;">BulletControler</span>>();<br />
<br />
<span style="color: blue;">if</span> (bullet)<br />
{<br />
<span style="color: #2b91af;">PlayerControler</span> plr = bullet.owner.GetComponent<<span style="color: #2b91af;">PlayerControler</span>>();<br />
<br />
hits -= .1f;<br />
<br />
<span style="color: blue;">if</span> (hits <= 0)<br />
{<br />
Instantiate(BoomPrefab, transform.position, <span style="color: #2b91af;">Quaternion</span>.identity);<br />
plr.Kills++;<br />
plr.Score += 1000;<br />
<br />
<span style="color: green;">// Spawn client player</span><br />
<span style="color: blue;">if</span>(networkView.isMine)<br />
FindObjectOfType<<span style="color: #2b91af;">NetworkManager</span>>().SpawnClientPlayer(DisplayName);<br />
<br />
<span style="color: #2b91af;">Network</span>.Destroy(gameObject);<br />
}<br />
<br />
plr.UpdateNetworkPlayer();<br />
UpdateNetworkPlayer();<br />
<br />
plr.Score += 100;<br />
}<br />
}</div>
</div>
</div>
</h4>
<h4>
<span style="font-weight: normal;"></span> </h4>
<h4>
void Update()</h4>
In here we have the player controls, note, you really should not write them like this, use the Unity configurable keys, not like this :S Note the use of networkView.isMine so we only move our player not all in the scene. Also, I keep the text for the player bound to it by using <br />
<span style="font-size: xx-small;">GetComponentInChildren<GUIText>().transform.position = Camera.main.WorldToViewportPoint(transform.position + (Vector3.down * 1));</span><br />
Also notice, I don’t have to send anything down the network, the transform is sent by the networkView :)<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:f9d577b5-7f42-4fae-8365-1d995705eea4" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> Update()<br />
{<br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
{<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.LeftArrow))<br />
transform.Rotate(<span style="color: #2b91af;">Vector3</span>.forward, ShipRotationSpeed);<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.RightArrow))<br />
transform.Rotate(<span style="color: #2b91af;">Vector3</span>.back, ShipRotationSpeed);<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.LeftArrow) || <span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.RightArrow))<br />
{<br />
<span style="color: blue;">if</span> (transform.InverseTransformDirection(rigidbody2D.velocity).x > 0)<br />
rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * rigidbody2D.velocity.magnitude);<br />
<span style="color: blue;">else</span><br />
rigidbody2D.velocity = transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * -rigidbody2D.velocity.magnitude);<br />
}<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.UpArrow))<br />
rigidbody2D.AddForce(transform.TransformDirection(<span style="color: #2b91af;">Vector2</span>.right * ShipThrust));<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.DownArrow))<br />
rigidbody2D.AddForce(transform.TransformDirection(-<span style="color: #2b91af;">Vector2</span>.right * ShipThrust));<br />
<br />
<span style="color: blue;">if</span> (<span style="color: #2b91af;">Input</span>.GetKey(<span style="color: #2b91af;">KeyCode</span>.Space))<br />
Shoot();<br />
}<br />
<br />
<span style="color: green;">// healing..</span><br />
<span style="color: blue;">if</span> (hits < 1)<br />
{<br />
hits += .005f;<br />
<span style="color: blue;">if</span> (hits > 1)<br />
hits = 1;<br />
<br />
<span style="color: blue;">float</span> c = <span style="color: #2b91af;">Mathf</span>.Lerp(0, 1, hits);<br />
gameObject.renderer.material.color = <span style="color: blue;">new</span> <span style="color: #2b91af;">Color</span>(1, c, c + .25f, 1);<br />
<br />
<span style="color: blue;">if</span>(networkView.isMine)<br />
UpdateNetworkPlayer();<br />
}<br />
<br />
GetComponentInChildren<<span style="color: #2b91af;">GUIText</span>>().transform.position = <span style="color: #2b91af;">Camera</span>.main.WorldToViewportPoint(transform.position + (<span style="color: #2b91af;">Vector3</span>.down * 1));<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
public void UpdateNetworkPlayer()</h4>
This method is the only one that does ANY network calls, and all it is doing is calling the UpdatePlayer RPC method I have set up in the PlayerControler which we will look at next.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:eb0ffad9-b3af-4acb-9045-861c3e50ac49" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">void</span> UpdateNetworkPlayer()<br />
{<br />
networkView.RPC(<span style="color: #a31515;">"UpdatePlayer"</span>, <span style="color: #2b91af;">RPCMode</span>.AllBuffered, networkView.viewID.ToString(), DisplayName, DisplayStatus);<br />
}</div>
</div>
</div>
As you can see, this RPC call is being sent to ALL clients in this session. You have a few options on how you make RPC calls, the whole enum looks like this<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:d88d1b91-8cf4-43ec-8d6e-a61f9c1b6197" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">namespace</span> UnityEngine<br />
{<br />
<span style="color: blue;">public</span> <span style="color: blue;">enum</span> <span style="color: #2b91af;">RPCMode</span><br />
{<br />
Server = 0,<br />
Others = 1,<br />
All = 2,<br />
OthersBuffered = 5,<br />
AllBuffered = 6,<br />
}<br />
}</div>
</div>
</div>
So, Server, would send to just the server player and none of the others, Others is to everyone except myself, All, is, you guessed it, to everyone. the buffered options mean that the server will store them and when players join later it will forward them on to them. I have chosen buffered so I don’t have to send the update all the time, just when I make changes to the data, then when new players join they will have the latest data on each players status.<br />
I could have gone with OthersBuffered, but I wanted to have the code to update the status in one place and this lets me do this or I would have to make a second call to update myself as well as the other players on the network.<br />
<h4>
[RPC]</h4>
<h4>
void UpdatePlayer(string id, string playerName, string status)</h4>
And so we come to the actual RPC call it’s self, again keeping the code in one place I decided the best place to put this was in the network manager code, so I pass on all the prams to it’s method and it goes through all the players in the scene and if it finds a matching ID, updates them. <br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:fd81ef90-052e-4780-b3ae-97192ea200d3" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
[<span style="color: #2b91af;">RPC</span>]<br />
<span style="color: blue;">void</span> UpdatePlayer(<span style="color: blue;">string</span> id, <span style="color: blue;">string</span> playerName, <span style="color: blue;">string</span> status)<br />
{<br />
FindObjectOfType<<span style="color: #2b91af;">NetworkManager</span>>().UpdatePlayer(id, playerName, status);<br />
}</div>
</div>
</div>
<h4>
</h4>
<h4>
void OnGUI()</h4>
And here is where I set up the GUI, so a field for you to enter your player name and also show all the known players and their stats that are currently in play.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:75073239-85ae-4d89-beeb-e81c216b6c33" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> OnGUI()<br />
{ <br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
{<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(8, 8, 100, 24), <span style="color: #a31515;">"Your Name:"</span>);<br />
DisplayName = <span style="color: #2b91af;">GUI</span>.TextArea(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(80, 8, 110, 20), DisplayName, 13);<br />
DisplayName = DisplayName.Replace(<span style="color: #a31515;">"\n"</span>, <span style="color: #a31515;">""</span>).Replace(<span style="color: #a31515;">"\r"</span>, <span style="color: #a31515;">""</span>);<br />
UpdateNetworkPlayer();<br />
}<br />
<br />
<span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>> otherPlayers = <span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">PlayerControler</span>>(FindObjectsOfType<<span style="color: #2b91af;">PlayerControler</span>>());<br />
<br />
<span style="color: #2b91af;">GUI</span>.Box(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(8, 32, 300, 24 * (otherPlayers.Count + 1)), <span style="color: #a31515;">"Players"</span>);<br />
<br />
<span style="color: blue;">if</span> (networkView.isMine)<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(12, 56, 300, 24), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} [{1}]"</span>, DisplayStatus.Replace(<span style="color: #a31515;">"\n"</span>,<span style="color: #a31515;">" : "</span>), <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(transform.position.x,transform.position.y)));<br />
<br />
<span style="color: green;">// Display the others..</span><br />
<br />
<br />
<span style="color: blue;">int</span> line = 1;<br />
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">PlayerControler</span> plr <span style="color: blue;">in</span> otherPlayers)<br />
{<br />
<span style="color: blue;">if</span> (!plr.networkView.isMine)<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(12, 56 + (24 * line++), 300, 24), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} [{1}]"</span>, plr.DisplayStatus.Replace(<span style="color: #a31515;">"\n"</span>, <span style="color: #a31515;">" : "</span>), <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(plr.transform.position.x, plr.transform.position.y)));<br />
}<br />
}</div>
</div>
</div>
As I mentioned earlier in this post, there are other bits in here too, like the star field, the sound and the bullets, but as I am giving you all the code anyway, I don’t see the point to showing these here as this was really about how I set up the networking, and as you can see it’s pretty simple really.<br />
<a href="http://lh5.ggpht.com/-W6gjde7oiqE/UysSnijHlxI/AAAAAAAABx4/1mpkw-bYO4g/s1600-h/image%25255B22%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-vKFXXsmL7qA/UysSoMp9ZGI/AAAAAAAAByA/hdR4Ameq9AY/image_thumb%25255B12%25255D.png?imgmax=800" height="236" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="334" /></a><br />
Now, naturally there are things in here I want to change, but, it works, well enough for a start and I would have never gotten around to writing this post if I kept adding stuff so forgive me if this post does not go far enough for you.<br />
As ever, all comments and criticisms are welcome, all you have to do is post them.<br />
If you want to get all the source for this you can find it all in a zip file <a href="http://www.randomchaos.co.uk/tutorials/unity3d/NetworkGameSample/NetworkGameSample.zip">here</a>. <br />
If you want to play the game as it currently stands then have a look <a href="http://www.randomchaos.co.uk/tutorials/unity3d/NetworkGameSample/">here</a>.Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-73927949950061829822014-02-25T13:51:00.001-08:002014-02-25T14:14:27.816-08:00Unity3D: Scrolling 2D BackgroundI came across a Facebook Unity3D user group post where someone was having an issue with a scrolling 2D background. The OP was doing the scrolling programmatically and was ending up having a gap appear in the background. I did exactly the same thing a while back, but found that a better way of doing this in Unity3D was to use Animators and Animation Clips.<br />
So, here is a tutorial on how to do this, Ill start off creating a 2D project in Unity3D<br />
<a href="http://lh4.ggpht.com/-J3uMBOhG-04/Uw0P3FCSzAI/AAAAAAAABqQ/u0mUQ1xvwyA/s1600-h/image%25255B4%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-1Z5DSu-5zBc/Uw0P3la6UrI/AAAAAAAABqY/onucw2c47_k/image_thumb%25255B2%25255D.png?imgmax=800" height="288" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="337" /></a><br />
We are now going to create a Texture\Backgrounds folder<br />
<a href="http://lh4.ggpht.com/-ysd6gZ0LiHg/Uw0P4PBGlhI/AAAAAAAABqg/1um0TgVf4cQ/s1600-h/image%25255B9%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-3N6jVHh_atc/Uw0P4yGeSeI/AAAAAAAABqk/s4U9BSgW670/image_thumb%25255B5%25255D.png?imgmax=800" height="260" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="344" /></a><br />
I have a number of background textures, that I am going to use:<br />
<a href="http://lh4.ggpht.com/-Ic6FFcSJP50/Uw0P5CtKL6I/AAAAAAAABqs/zhrdP0WXTWk/s1600-h/BGForeGround_1_1%25255B3%25255D.png"><img alt="BGForeGround_1_1" border="0" src="http://lh3.ggpht.com/-yb1OOMGGAas/Uw0P54L6MBI/AAAAAAAABq4/QjpOadVazHg/BGForeGround_1_1_thumb%25255B1%25255D.png?imgmax=800" height="84" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BGForeGround_1_1" width="240" /></a><a href="http://lh5.ggpht.com/-b3Q8Dl-FJVE/Uw0P6SBqxpI/AAAAAAAABrA/JgJ8mbueC-Y/s1600-h/BGForeGround_1_2%25255B3%25255D.png"><img alt="BGForeGround_1_2" border="0" src="http://lh3.ggpht.com/-CzarYndSLlI/Uw0P69Hyv0I/AAAAAAAABrE/ArPtmCZ8lgg/BGForeGround_1_2_thumb%25255B1%25255D.png?imgmax=800" height="84" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BGForeGround_1_2" width="240" /></a><a href="http://lh3.ggpht.com/-6txdBB3M78A/Uw0P7Dfhw3I/AAAAAAAABrQ/z_XNIFiqj7k/s1600-h/BGForeGround_1_3%25255B3%25255D.png"><img alt="BGForeGround_1_3" border="0" src="http://lh6.ggpht.com/-zI4v7s3AUwE/Uw0P78iJnBI/AAAAAAAABrU/V6XNyJ7RGT0/BGForeGround_1_3_thumb%25255B1%25255D.png?imgmax=800" height="84" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: inline; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="BGForeGround_1_3" width="240" /></a><br />
But, for now we will work with the first texture, we will get to the others in a little while. First thing we need to do is make sure that out images are imported as sprites.<br />
<a href="http://lh6.ggpht.com/-cw310m8RdIc/Uw0P8NYEFrI/AAAAAAAABrg/BvPOguWINAg/s1600-h/image%25255B14%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-rN4-BYjpRiU/Uw0P80Y5J9I/AAAAAAAABro/2oruXuvSsf8/image_thumb%25255B8%25255D.png?imgmax=800" height="411" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="227" /></a><br />
Next thing we are going to do is set up the sprite on the screen in relation to the camera, so just drag and drop the first image into the scene.<br />
<a href="http://lh4.ggpht.com/-dNC-oZ69Qms/Uw0P9Ti922I/AAAAAAAABrw/F3cCsqII0rM/s1600-h/image%25255B19%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-6tAB7BVmtcI/Uw0P9xBNY3I/AAAAAAAABr0/pkra7blLTOQ/image_thumb%25255B11%25255D.png?imgmax=800" height="217" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="315" /></a><br />
Now, we are going to set the textures position to 0,0,1 and stretch it to fit the full view of the camera, so we will scale it to 1.2, 2.4,1<br />
<a href="http://lh4.ggpht.com/-qhgpEVS19kU/Uw0P-XG1k3I/AAAAAAAABsA/wA3QX058W64/s1600-h/image%25255B24%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-3Fhz963p8J0/Uw0P-5hiDDI/AAAAAAAABsE/1vU7k3k54Po/image_thumb%25255B14%25255D.png?imgmax=800" height="199" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="288" /></a><br />
Now, make a copy of it, and paste it into the scene, line it up with the edge of the first texture by setting the position to 19.125,0,0<br />
<a href="http://lh5.ggpht.com/-rD9MYTmyIgs/Uw0P_VoCztI/AAAAAAAABsM/YVQiHHpZPX8/s1600-h/image%25255B29%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-634SAuhqU1c/Uw0P__RUxpI/AAAAAAAABsU/mWlvxOf4sk8/image_thumb%25255B17%25255D.png?imgmax=800" height="205" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="300" /></a><br />
Create an empty GameObject, name it Background and in the Hierarchy drag the two images we have just set up in the scene into it.<br />
<a href="http://lh6.ggpht.com/-SPMERCkfNUc/Uw0QAlhSbGI/AAAAAAAABsg/qZXxebUlJuk/s1600-h/image%25255B34%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-LzbeqL3CYuI/Uw0QBAHgIhI/AAAAAAAABso/NsRb6SlIFnM/image_thumb%25255B20%25255D.png?imgmax=800" height="275" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="313" /></a><br />
In our container GameObject, add an Animator component, so Add Component, Miscilanious and select Animator.<br />
<a href="http://lh4.ggpht.com/-Yz36T_nJFFU/Uw0QBjPYZfI/AAAAAAAABsw/tYcT1Ak88Aw/s1600-h/image%25255B59%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-pMpdurGYOaw/Uw0QCACJyJI/AAAAAAAABs4/uuxFME-YLmA/image_thumb%25255B35%25255D.png?imgmax=800" height="480" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="249" /></a><br />
We are now going to create our Animator, first create a folder called Animation<br />
<a href="http://lh5.ggpht.com/-6_ghJAf2ZGw/Uw0QCoB9FMI/AAAAAAAABtA/iOlkfISNFVY/s1600-h/image%25255B39%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-sDJ6smDtXDo/Uw0QDDiC1dI/AAAAAAAABtI/6mtGOLrVcxE/image_thumb%25255B23%25255D.png?imgmax=800" height="235" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="320" /></a><br />
In here we will add another two folders Clips and Controllers<br />
<a href="http://lh5.ggpht.com/-N7ILLmj-kvM/Uw0QD_NkP4I/AAAAAAAABtQ/J_vkybwPjdo/s1600-h/image%25255B44%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-CJ7yx6YJf4Y/Uw0QEfi8XuI/AAAAAAAABtY/pKJDFHLAjdk/image_thumb%25255B26%25255D.png?imgmax=800" height="215" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="322" /></a><br />
Now create an Animator Controller <br />
<a href="http://lh6.ggpht.com/-fZFC2jUadF0/Uw0QFPQY0yI/AAAAAAAABtg/soxOppDvgmU/s1600-h/image%25255B49%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-2_C-M2i9nk4/Uw0QFqaGj3I/AAAAAAAABto/mrmamcpyIvI/image_thumb%25255B29%25255D.png?imgmax=800" height="280" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="329" /></a><br />
Name it BackgroundAnimation, double click it and we will see the Animator window. Now in clips, create a new Animation.<br />
<a href="http://lh4.ggpht.com/-B5IL80pvmWg/Uw0QGKsSxSI/AAAAAAAABtw/HtYwNMWOCpc/s1600-h/image%25255B54%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-olLtpNDVNSk/Uw0QGhR93PI/AAAAAAAABt4/vjQ_810bPs8/image_thumb%25255B32%25255D.png?imgmax=800" height="352" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="325" /></a><br />
Name is BGScroller. Add this Animation to the BackgroundAnimation Animator Controller by dragging and dropping it in and set the speed to .125<br />
<a href="http://lh5.ggpht.com/-pcvkn9prGBc/Uw0QHGDjpvI/AAAAAAAABuA/OGBRp3cNhpg/s1600-h/image%25255B64%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-mxAbvbR6Sak/Uw0QHyyeEYI/AAAAAAAABuI/Dr7rp6ouDbM/image_thumb%25255B38%25255D.png?imgmax=800" height="211" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="329" /></a><br />
<a href="http://lh4.ggpht.com/-aPW7At9epcY/Uw0QIX3dCwI/AAAAAAAABuQ/xDSHjM8T_cM/s1600-h/image%25255B69%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-fNOZs8nu0NE/Uw0QI_NNsJI/AAAAAAAABuY/6C420xn6qPg/image_thumb%25255B41%25255D.png?imgmax=800" height="363" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="315" /></a><br />
Now, in our Background GameObject (the one that is holding our sprites) set the Animator to use the new BackgroundAnimator we just set up.<br />
<a href="http://lh4.ggpht.com/-jALb9vBau8Q/Uw0QJd2dEOI/AAAAAAAABug/KcFwHlx77Po/s1600-h/image%25255B74%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-TViVVXjajQs/Uw0QJ6o3KyI/AAAAAAAABuo/DOWEUEVEAng/image_thumb%25255B44%25255D.png?imgmax=800" height="205" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="359" /></a><br />
With the Background GameObject selected open up the Animation Window<br />
<a href="http://lh5.ggpht.com/-I6bo1pEDMkI/Uw0QKQFepWI/AAAAAAAABuw/MeKQoc8f3dw/s1600-h/image%25255B79%25255D.png"><img alt="image" border="0" src="http://lh3.ggpht.com/-vbm2nzTyCgM/Uw0QLCDxurI/AAAAAAAABu4/_0uix7FtO3c/image_thumb%25255B47%25255D.png?imgmax=800" height="344" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="350" /></a><br />
Select Add Curve, select Transform and Position<br />
<a href="http://lh6.ggpht.com/-HioCCC107Yw/Uw0QLm4iGxI/AAAAAAAABu8/MhM8R-rrPc0/s1600-h/image%25255B84%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-J324DulR7ag/Uw0QMAcHZNI/AAAAAAAABvI/nk3FxJSPrSk/image_thumb%25255B50%25255D.png?imgmax=800" height="315" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="356" /></a><br />
Expand this out, to show the elements of the position element, drag the far right set of dots out to the 10 second mark and set the x element to -19.125.<br />
<a href="http://lh5.ggpht.com/-BqtoeH0RWig/Uw0QMgmpcMI/AAAAAAAABvQ/5scVZWKtKKs/s1600-h/image%25255B89%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-uGsamBRvHwU/Uw0QNIrSuUI/AAAAAAAABvY/aP0vB1j94IU/image_thumb%25255B53%25255D.png?imgmax=800" height="119" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="367" /></a><br />
Now run it, it will now smoothly scroll our textures from right to left :D, BUT, after 10 seconds it stops (if played at the speed of 1) :S not what we want, so, go into the BGScroller Animation and set the Loop Time to true.<br />
<a href="http://lh3.ggpht.com/-DKke0aL9uCY/Uw0QNg6L47I/AAAAAAAABvg/Rcrt4gLWl88/s1600-h/image%25255B94%25255D.png"><img alt="image" border="0" src="http://lh6.ggpht.com/-UzLvrkJw65Q/Uw0QO8AxJ3I/AAAAAAAABvo/HN3P3WeY8bg/image_thumb%25255B56%25255D.png?imgmax=800" height="583" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="290" /></a><br />
Run it now, and it will scroll on and on, looping lovely with no issue :)<br />
<h2>
Parallax</h2>
So, what if we want some parallax scrolling background?<br />
Well, we just do what we did before, but we will create a different Animator Controller for each layer. First thing ill do is set the first to background z positions to 0, but set their container, Background", z to 1, this was we can set the layer order.<br />
So, for the next layer, we add the next texture like before.<br />
<a href="http://lh4.ggpht.com/-LvbpOfkMxEk/Uw0QPWUtUfI/AAAAAAAABvw/YORfYdwU1-Y/s1600-h/image%25255B99%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-6kXeshSGDAw/Uw0QP280AHI/AAAAAAAABv4/5ZR3NFO_4ME/image_thumb%25255B59%25255D.png?imgmax=800" height="171" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="379" /></a><br />
As before, create an empty GameObject to put them in and call it Background2 and add an Animator component to it.<br />
<a href="http://lh6.ggpht.com/-Hrm_dM3IwQI/Uw0QQZh_qiI/AAAAAAAABwA/cKLzKiiuPM0/s1600-h/image%25255B104%25255D.png"><img alt="image" border="0" src="http://lh5.ggpht.com/-UpW9COVCSk8/Uw0QQ0tgkBI/AAAAAAAABwE/nJT0d13kLgM/image_thumb%25255B62%25255D.png?imgmax=800" height="143" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="401" /></a><br />
Now in the controller folder create a new Animator Controller and call it Backgrouhd2Animation, drag and drop the same Animation clip into it, BGScroller, but we will set the speed to .25 and then set Background2’s Animator to the new controller Background2Animation.<br />
Run it and the two sets of backgrounds scroll at different speeds :D<br />
Now do the same for the another set of backgrounds, this time set the animation speed to .5, then we will create another empty GameObject called Backgrounds, and put all three of our container GameObjects in it.<br />
<a href="http://lh5.ggpht.com/-WbFfggngnAo/Uw0QR-NsVeI/AAAAAAAABwQ/6NF9R2Fo01U/s1600-h/image%25255B114%25255D.png"><img alt="image" border="0" src="http://lh4.ggpht.com/-MnGbIj9dQSM/Uw0QSXDl15I/AAAAAAAABwY/U_GM8veVH0o/image_thumb%25255B68%25255D.png?imgmax=800" height="308" style="background-image: none; border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto; padding-left: 0px; padding-right: 0px; padding-top: 0px;" title="image" width="370" /></a><br />
So, this is what it will look like<br />
<div class="wlWriterEditableSmartContent" id="scid:53357c8b-5919-4e32-8c25-305d27c17a37:307760c8-543b-44a7-b089-21c1cde73102" style="display: block; float: none; margin-left: auto; margin-right: auto; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; width: 425px;">
<embed height="350" src="http://www.youtube.com/v/qvRs2Rex9XI" type="application/x-shockwave-flash" width="425" wmode="transparent"></embed></div>
You can get the project for this <a href="http://www.randomchaos.co.uk/tutorials/unity3D/Scrolling2DBackground.zip">here</a>Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-39500638475355600272014-02-11T11:51:00.001-08:002014-02-11T11:51:27.253-08:00Direct3D Rendering Cookbook By Justin Stenning<p><a href="https://www.packtpub.com/">Packt Publishing</a> have asked me to review another book, the <a href="http://www.packtpub.com/direct3d-rendering-cookbook/book">Direct3d Rendering Cookbook</a></p> <p>I didn’t know where to post this, my many blogs are either XNA (C#), DX11 C++ or Unity3D, or a combination of the three lol, anyway, decided as this is my most recent blog, to post it here, hope it’s not too off topic :P</p> <p><a href="http://lh4.ggpht.com/-tmafItixFaY/Uvp_OtUfDQI/AAAAAAAABp4/ys1KkbcsR7Q/s1600-h/7101OT_Direct3D-Rendering-Cookbook4.jpg"><img title="7101OT_Direct3D Rendering Cookbook" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; float: right; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="7101OT_Direct3D Rendering Cookbook" align="right" src="http://lh3.ggpht.com/-ljWTMtFLNGU/Uvp_PK0Y7xI/AAAAAAAABqA/Tdo1CF0xGpQ/7101OT_Direct3D-Rendering-Cookbook_t.jpg?imgmax=800" width="173" height="284"></a></p> <p>One of the first things I noticed and liked as a .NET developer is that it’s for C# developers using SharpDX. I have never used SharpDX before, have played a little with SlimDX when I was helping out on the <a href="http://stexcalibur.com/">ST: Excalubur</a> project (which you should check out, the guys are doing an amazing job with it, check out the <a href="http://www.youtube.com/user/ExcaliburGame">Youtube channel too</a>)</p> <h2>Chapter 1</h2> <p>The first chapter lays out the fundamental’s for getting up and running, like most books of this ilk as well as going into the graphics pipeline, and it’s pretty in depth I have to say. The chapter then covers creating your first project using the SharpDX library with great detail and a fair bit of depth. </p> <h2>Chapter 2</h2> <p>Goes into great detail again on setting up your code to render 3D, again, this is quite in depth and a lot of info is given. What I do find odd is that the creation of the renderer code is described as a recipe, I know there are a number of ways to do this, but I would not have had this as a recipe as it’s pretty fundamental to you rendering full stop, it’s something you should have in place, and this chapter should really just be a way of doing it. To me, its more of a preparation step than a recipe…None the less, there is great detail again here. </p> <h2>Chapter 3</h2> <p>We are then given recipes on rendering meshes, we start of creating the mesh in code, then look into some simple lighting techniques and texturing and thankfully (as I think this tends to get missed out a fait bit) it gives and example of loading a mesh from file, and what I do like is that <a href="http://www.blender.org/">Blender3D</a> is used :D Great stuff..</p> <h4>Chapter 4</h4> <p>Then moves on to skinned meshes, again, this is a great chapter and full of detail :)</p> <h2>Chapter 5 </h2> <p>Looks at Hardware Tessellation, some great detail here too, the diagrams are great at for describing the pipeline. It goes on to describe tessellating bicubic Bezier surfaces, refining a mesh with Phong tessellation and loads more.</p> <h2>Chapter 6</h2> <p>Covers normal and displacement mapping, and again, this goes into masses and masses of detail, with some great screen shots</p> <h2>Chapter 7</h2> <p>We take a break from geometry and move on to image processing. There is some awesome stuff in here too, from desaturation, contrast and brightness to implementing box blur, gaussian blur and Sobel edge detection and luminance histograms.</p> <h2>Chapter 8</h2> <p>Covers using a physics engine, and again, I am glad to see BulletSharp being used as I have used it in my own engines, including my <a href="https://illuminatiengine.codeplex.com/">XNA Illuminati deferred lighting engine</a>. This chapter also covers rendering waves (personally prefer this being done as a post process) and instanced particles, again another topic close to my heart as again I use them in my own engine.</p> <h2>Chapter 9</h2> <p>Now goes even deeper with rendering on multiple threads and deferred contexts, now, due to me coming from a DX9 background, this was very interesting. </p> <h2>Chapter 10 </h2> <p>Moves onto Deferred Rendering, another topic I love having implemented it in XNA :) Again, masses and masses of detail here, this book has really got me wanting to start writing my own engine again lol</p> <h2>Chapter 11 </h2> <p>Covers integrating Direct3D into Windows 8.1 and the use of XAML, this chapter will be of more use to me once I finally get a decent development machine. Hoping to sort that out some time this year :) Again though, lots and lost of details in here.</p> <h2>In Summary…</h2> <p>I loved this book, even if you are not a C# developer, you can port/include these techniques to/in your C++ engine. If you are just starting out, this may not be the book for you, if you have been working with earlier versions of DX and looking for an interesting read, then I don’t think you can go wrong with this book.</p> <p>I have reviewed a few books by Packt Publishing, and I have to say, they have tended to turn up short, however, the last two books I have reviewed have been truly excellent, keep up the good work :) Check out my last review <a href="http://xboxoneindiedevelopment.blogspot.co.uk/2013/12/learning-windows-8-game-development.html">here</a>.</p> <h2>Where can I get this book?</h2> <p><a href="http://www.packtpub.com/direct3d-rendering-cookbook/book">here</a>.</p> Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-4110664232998566722013-12-22T16:32:00.001-08:002014-01-07T07:56:12.417-08:00Learning Windows 8 Game Development Written by Michael Quandt<h2>
Packt</h2>
I have reviewed one or two books that have been published by <a href="https://www.packtpub.com/">Packt</a> publishing, and I have to be honest, the content, while normally accurate, has been a little thin on the ground. I am pleased to say, not with this book! <br />
I am pleased to say this as I have known the author for a number of years. As is the case with on line communities, I have never met him physically, but I have known Michael for many years. <br />
<h2>
Chr0n1x</h2>
I first encountered him when I started to get into XNA on a well know community board called the <a href="http://www.thehazymind.com/">Hazy Mind</a>, he went by the name Chr0n1x then, board was ran by another XNA/DX guru Micael Schuld. Chr0n1x was one of the main go to guys on that board after the admin, and he helped me a great deal, not just in the early days but even today with various GPU related issues I have. At that time I don’t think Michael was even in college/Uni then but his understanding and knowledge of the GPU and it’s pipeline was hard to beat.<br />
<h2>
The Book</h2>
So, enough of all that, what about this book, we know it’s rich in content, but is it any good? I think it is, it takes you right from the basics of setting up your project and creating a simple game loop along with a graphics device, drawing sprites and using input devices and accelerometers to live tiles, networking publishing to the store and monetization. Now, I know, this all sounds a bit 2D, but in the appendix Michael quickly covers some of the basics of 3D too, but to be honest, you really need the first lot of tools and can create a great 2D game, but you still need the skills from the previous chapters to get your game together whether its 2D or 3D.<br />
<h2>
Should I buy this book?</h2>
If you are not new to C++, but new to DirectX and/or game development and want a great book to get you into it, and take you to an intermediate level then, yes, this is the book for you. If you know C++ and have done some game development, then yes, there is some great stuff in here for building games for Windows 8, personally I am going to find it very useful for padding out a lot of holes in my C++ and DX knowledge.<br />
<h2>
Anything Missing?</h2>
The only thing I would have liked to have seen in this book was audio, it gets a mention, but there is no implementation shown, which is a shame, but you know what, the amount of stuff that’s in here, you can find this out somewhere else anyway.<br />
<h2>
Where Is It?</h2>
<div>
You can get a copy of the book <a href="http://www.packtpub.com/learning-windows-8-game-development/book" target="_blank">here</a>.</div>
Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-42058764316210102512013-12-13T10:21:00.000-08:002013-12-16T04:57:23.582-08:00Reinventing some of Unity’s Wheels: Terrain<h2>
Why?</h2>
First of all, why? Why create this when there is a perfectly good terrain package that comes with Unity? Well, I do like to have a mess about and find out what I can do, no other reason than that really, why? Because I can :) I dare say that the Unity Terrain asset is a much better way of creating your terrain, having not really played with it I don’t know, but I would like to implement my version of Geo Clip mapping see here: <br />
<div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:9ee24b8c-2501-4044-a633-3ce63e4936c8" style="display: block; float: none; margin-left: auto; margin-right: auto; padding: 0px; width: 425px;">
<div id="c78d2515-003d-4744-af54-34ef21247acc" style="display: inline; margin: 0px; padding: 0px;">
<div>
<a href="http://www.youtube.com/watch?v=szgHZ0IL6To&feature=youtube_gdata_player" target="_new"><img alt="" galleryimg="no" onload="var downlevelDiv = document.getElementById('c78d2515-003d-4744-af54-34ef21247acc'); downlevelDiv.innerHTML = "<div><object width=\"425\" height=\"355\"><param name=\"movie\" value=\"http://www.youtube.com/v/szgHZ0IL6To&hl=en\"><\/param><embed src=\"http://www.youtube.com/v/szgHZ0IL6To&hl=en\" type=\"application/x-shockwave-flash\" width=\"425\" height=\"355\"><\/embed><\/object><\/div>";" src="http://lh6.ggpht.com/-qGmwXMaX9vc/Uqs_19_rAfI/AAAAAAAABos/a8gx5TfmF2c/video43edaaa71de3%25255B8%25255D.jpg?imgmax=800" /></a></div>
</div>
</div>
And to do that I am going to need to implement height maps and generate the geometry as I need to. Also, later on I may want to procedurally generate my terrain, and I am guessing Ill need something like this.<br />
<h2>
How?</h2>
I started off trying to find out if I could generate my own geometry in Unity, and it turns out you <a href="http://docs.unity3d.com/Documentation/Manual/Example-CreatingaBillboardPlane.html">can</a>, this was great news on my quest as it meant I can pass my script a height map and using that to generate the mesh needed to render the terrain.<br />
I started off creating a script called TerrainGenerator in my Unity project, in here I added a number of public parameters so they can be set in the editor and I can pass in the height map and the min and max height of my terrain, the txMod is just a variable I can use to modify the texcoords in the vertex, it’s a throw back to the version I did in XNA, and as you can change an assets tiling in the editor, probably not needed now.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:63707f97-d8a1-4af2-8907-8fc8500cc446" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">TerrainGenerator</span> : <span style="color: #2b91af;">MonoBehaviour</span> <br />
{<br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">Texture2D</span> HeightMap;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> min = 0;<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> max = 30;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> txMod = 4.25f;</div>
</div>
</div>
I then add an <a href="http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.Awake.html">Awake</a> method, this gets called when the script is instanced, in here is where I read the height map and turn it into a mesh, this mesh, if a mesh collider is provided can then be used for that mesh collider, naturally you can’t set this in the editor as the mesh has not been created yet :)<br />
Naturally you will need a mesh filter to store the mesh in, if you don’t Unity will throw an error when you go to set the mesh in it. I then check to see if a height map has been given and if not generate a plane and render that instead.<br />
Here is the script in it’s entirety:-<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:08ae5120-6f4b-4de7-9495-a988758e3ecb" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">class</span> <span style="color: #2b91af;">TerrainGenerator</span> : <span style="color: #2b91af;">MonoBehaviour</span> <br />
{<br />
<span style="color: blue;">public</span> <span style="color: #2b91af;">Texture2D</span> HeightMap;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> min = 0;<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> max = 30;<br />
<br />
<span style="color: blue;">public</span> <span style="color: blue;">float</span> txMod = 4.25f;<br />
<br />
<span style="color: blue;">int</span> myWidth;<br />
<span style="color: blue;">int</span> myHeight;<br />
<span style="color: blue;">float</span>[,] height;<br />
<br />
<br />
<br />
<span style="color: blue;">void</span> Awake()<br />
{<br />
<span style="color: #2b91af;">MeshFilter</span> meshFilter = GetComponent<<span style="color: #2b91af;">MeshFilter</span>>();<br />
<br />
meshFilter.mesh = <span style="color: blue;">new</span> <span style="color: #2b91af;">Mesh</span>();<br />
<br />
<span style="color: blue;">if</span> (HeightMap != <span style="color: blue;">null</span>)<br />
{<br />
myWidth = HeightMap.width;<br />
myHeight = HeightMap.height;<br />
<br />
transform.position = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(transform.position.x - (myWidth / 2), transform.position.y, transform.position.z - (myHeight / 2));<br />
<br />
<span style="color: #2b91af;">Color</span>[] heightMapCol = <span style="color: blue;">new</span> <span style="color: #2b91af;">Color</span>[myWidth * myHeight];<br />
<br />
<span style="color: #2b91af;">Vector3</span>[] verts = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>[myWidth * myHeight];<br />
<span style="color: #2b91af;">Vector3</span>[] normals = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>[myWidth * myHeight];<br />
<span style="color: #2b91af;">Vector2</span>[] uv = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>[myWidth * myHeight];<br />
<span style="color: #2b91af;">Vector4</span>[] tan = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector4</span>[myWidth * myHeight];<br />
<span style="color: #2b91af;">Color</span>[] col = <span style="color: blue;">new</span> <span style="color: #2b91af;">Color</span>[myWidth * myHeight];<br />
<br />
height = <span style="color: blue;">new</span> <span style="color: blue;">float</span>[myWidth, myHeight];<br />
<br />
heightMapCol = HeightMap.GetPixels();<br />
<br />
<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> x = 0; x < myWidth; x++)<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> y = 0; y < myHeight; y++)<br />
height[x, y] = <span style="color: #2b91af;">Mathf</span>.Lerp(min, max, heightMapCol[x + y * myWidth].r);<br />
<br />
<br />
<span style="color: green;">// Verts</span><br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> x = 0; x < myWidth; x++)<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> y = 0; y < myHeight; y++)<br />
{<br />
verts[x + y * myWidth] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(y, height[x, y], x);<br />
normals[x + y * myWidth] = <span style="color: #2b91af;">Vector3</span>.up;<br />
uv[x + y * myWidth] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>((<span style="color: blue;">float</span>)x / (myWidth / txMod), (<span style="color: blue;">float</span>)y / (myHeight / txMod));<br />
<br />
<span style="color: green;">// blend</span><br />
col[x + y * myWidth].r = <span style="color: #2b91af;">Mathf</span>.Clamp(1.0f - <span style="color: #2b91af;">Mathf</span>.Abs(height[x, y] - 0) / 8, 0, 1);<br />
col[x + y * myWidth].g = <span style="color: #2b91af;">Mathf</span>.Clamp(1.0f - <span style="color: #2b91af;">Mathf</span>.Abs(height[x, y] - 12) / 6, 0, 1);<br />
col[x + y * myWidth].b = <span style="color: #2b91af;">Mathf</span>.Clamp(1.0f - <span style="color: #2b91af;">Mathf</span>.Abs(height[x, y] - 20) / 6, 0, 1);<br />
col[x + y * myWidth].a = <span style="color: #2b91af;">Mathf</span>.Clamp(1.0f - <span style="color: #2b91af;">Mathf</span>.Abs(height[x, y] - 30) / 6, 0, 1);<br />
<br />
<span style="color: blue;">float</span> totalWeight = col[x + y * myWidth].r;<br />
totalWeight += col[x + y * myWidth].g;<br />
totalWeight += col[x + y * myWidth].b;<br />
totalWeight += col[x + y * myWidth].a;<br />
<br />
col[x + y * myWidth].r /= totalWeight;<br />
col[x + y * myWidth].g /= totalWeight;<br />
col[x + y * myWidth].b /= totalWeight;<br />
col[x + y * myWidth].a /= totalWeight;<br />
}<br />
<br />
<br />
<span style="color: green;">// Calc normals</span><br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> x = 0; x < myWidth; x++)<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> y = 0; y < myHeight; y++)<br />
{<br />
<span style="color: green;">// Tangent Data.</span><br />
<span style="color: blue;">if</span> (x != 0 && x < myWidth - 1)<br />
tan[x + y * myWidth] = verts[x + 1 + y * myWidth] - verts[x - 1 + y * myWidth];<br />
<span style="color: blue;">else</span><br />
<span style="color: blue;">if</span> (x == 0)<br />
tan[x + y * myWidth] = verts[x + 1 + y * myWidth] - verts[x + y * myWidth];<br />
<span style="color: blue;">else</span><br />
tan[x + y * myWidth] = verts[x + y * myWidth] - verts[x - 1 + y * myWidth];<br />
<br />
tan[x + y * myWidth].Normalize();<br />
<br />
<span style="color: green;">// Normals</span><br />
<span style="color: #2b91af;">Vector3</span> normX = <span style="color: #2b91af;">Vector3</span>.one;<br />
<span style="color: #2b91af;">Vector3</span> normZ = <span style="color: #2b91af;">Vector3</span>.one;<br />
<br />
<span style="color: blue;">if</span> (x != 0 && x < myWidth - 1)<br />
normX = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>((verts[x - 1 + y * myWidth].y - verts[x + 1 + y * myWidth].y) / 2, 1, 0);<br />
<span style="color: blue;">else</span><br />
<span style="color: blue;">if</span> (x == 0)<br />
normX = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>((verts[x + y * myWidth].y - verts[x + 1 + y * myWidth].y) / 2, 1, 0);<br />
<span style="color: blue;">else</span><br />
normX = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>((verts[x - 1 + y * myWidth].y - verts[x + y * myWidth].y) / 2, 1, 0);<br />
<br />
<span style="color: blue;">if</span> (y != 0 && y < myHeight - 1)<br />
normZ = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(0, 1, (verts[x + (y - 1) * myWidth].y - verts[x + (y + 1) * myWidth].y) / 2);<br />
<span style="color: blue;">else</span><br />
<span style="color: blue;">if</span> (y == 0)<br />
normZ = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(0, 1, (verts[x + y * myWidth].y - verts[x + (y + 1) * myWidth].y) / 2);<br />
<span style="color: blue;">else</span><br />
normZ = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(0, 1, (verts[x + (y - 1) * myWidth].y - verts[x + (y) * myWidth].y) / 2);<br />
<br />
normals[x + y * myWidth] = normX + normZ;<br />
normals[x + y * myWidth].Normalize();<br />
}<br />
<br />
<br />
meshFilter.mesh.vertices = verts;<br />
meshFilter.mesh.tangents = tan;<br />
meshFilter.mesh.normals = normals;<br />
meshFilter.mesh.uv = uv;<br />
meshFilter.mesh.colors = col;<br />
<br />
<span style="color: green;">// Index</span><br />
<span style="color: blue;">int</span>[] terrainIndices = <span style="color: blue;">new</span> <span style="color: blue;">int</span>[(myWidth - 1) * (myHeight - 1) * 6];<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> x = 0; x < myWidth - 1; x++)<br />
{<br />
<span style="color: blue;">for</span> (<span style="color: blue;">int</span> y = 0; y < myHeight - 1; y++)<br />
{<br />
terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = ((x + 1) + (y + 1) * myWidth);<br />
terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = ((x + 1) + y * myWidth);<br />
terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (x + y * myWidth);<br />
<br />
terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = ((x + 1) + (y + 1) * myWidth);<br />
terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (x + y * myWidth);<br />
terrainIndices[(x + y * (myWidth - 1)) * 6] = (x + (y + 1) * myWidth);<br />
}<br />
}<br />
<br />
meshFilter.mesh.triangles = terrainIndices;<br />
}<br />
<span style="color: blue;">else</span><br />
{<br />
<br />
<span style="color: green;">// Verts</span><br />
<span style="color: #2b91af;">Vector3</span>[] verts = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>[4];<br />
<br />
verts[0] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(-1, 0, -1);<br />
verts[1] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(1, 0, -1);<br />
verts[2] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(-1, 0, 1);<br />
verts[3] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(1, 0, 1);<br />
<br />
meshFilter.mesh.vertices = verts;<br />
<br />
<span style="color: green;">// Index</span><br />
<span style="color: blue;">int</span>[] index = <span style="color: blue;">new</span> <span style="color: blue;">int</span>[6];<br />
<br />
index[0] = 0;<br />
index[1] = 2;<br />
index[2] = 1;<br />
<br />
index[3] = 2;<br />
index[4] = 3;<br />
index[5] = 1;<br />
<br />
meshFilter.mesh.triangles = index;<br />
<br />
<span style="color: green;">// Normals</span><br />
<span style="color: #2b91af;">Vector3</span>[] normals = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>[4];<br />
<br />
normals[0] = <span style="color: #2b91af;">Vector3</span>.up;<br />
normals[1] = <span style="color: #2b91af;">Vector3</span>.up;<br />
normals[2] = <span style="color: #2b91af;">Vector3</span>.up;<br />
normals[3] = <span style="color: #2b91af;">Vector3</span>.up;<br />
<br />
meshFilter.mesh.normals = normals;<br />
<br />
<span style="color: green;">// UV</span><br />
<span style="color: #2b91af;">Vector2</span>[] uv = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>[4];<br />
<br />
uv[0] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(0, 0);<br />
uv[1] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(1, 0);<br />
uv[2] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(0, 1);<br />
uv[3] = <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>(1, 1);<br />
<br />
uv[0] = <span style="color: #2b91af;">Vector3</span>.zero;<br />
uv[1] = <span style="color: #2b91af;">Vector3</span>.zero;<br />
uv[2] = <span style="color: #2b91af;">Vector3</span>.zero;<br />
uv[3] = <span style="color: #2b91af;">Vector3</span>.zero;<br />
<br />
meshFilter.mesh.uv = uv;<br />
}<br />
<br />
<span style="color: #2b91af;">MeshCollider</span> mc = GetComponent<<span style="color: #2b91af;">MeshCollider</span>>();<br />
<br />
<span style="color: blue;">if</span> (mc != <span style="color: blue;">null</span>)<br />
mc.sharedMesh = meshFilter.mesh;<br />
}<br />
<br />
}</div>
</div>
</div>
Now, a word of warning, in order for this to work you MUST set your heightmap textures in Unity to be readable, by default the texture importer locks it, simply select your height map, choose the “Advanced” texture type and set the “Read\Write” enabled to true. If you don’t do this you will get an error on the line calling GetPixels()<br />
I had a bit of a shock when I first ported this code from XNA, nothing rendered, but I had no errors, then I found out that Unity is left handed, so I just had to reverse the winding order of the index and it worked :) something to be mindful of if you are porting code from XNA.<br />
So, that will generate your mesh AND if you give it a mesh collider, will add it to the Unity Physics system too, it was quite cool to then just add some sphere’s and cubes, give them a rigid body and watch them roll and tumble down the hills :D<br />
So, you can now render the terrain and with a diffuse shader will look something like this:<br />
<a href="http://lh4.ggpht.com/-anLF-0jnHfw/Uqs_3FjUPKI/AAAAAAAABo0/Xc0yptJQ-s4/s1600-h/885017_10151806984342218_1902863551_o%25255B1%25255D%25255B3%25255D.jpg"><img alt="885017_10151806984342218_1902863551_o[1]" border="0" height="255" src="http://lh5.ggpht.com/-Ye5mttpJRt8/Uqs_3yui5ZI/AAAAAAAABo8/iTSyN1Gcu1U/885017_10151806984342218_1902863551_o%25255B1%25255D_thumb%25255B1%25255D.jpg?imgmax=800" style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="885017_10151806984342218_1902863551_o[1]" width="406" /></a><br />
If you look at the code above, you can see I am setting the vertex colour, I am actually not using this as a colour, but a blend weight used to blend the textures as the height of the terrain alters, my custom shader will now use that value to determine what textures should be used where. Also note that Unity (well there may be a way around this, but I am new to Unity so forgive me) will not render large height maps with this technique, if I go for a big height map texture I get this error:<br />
Mesh.vertices is too large. A mesh may not have more than 65000 vertices.<br />
UnityEngine.Mesh:set_vertices(Vector3[]) <br />
<h2>
Custom Terrain Shader</h2>
Again, this was a pretty simple port from my XNA sample to Unity, main issue I had was working out how to extend the vertex structure, but finding you can’t and having to use the Color element of the vertx to store my blend values in. I guess I could calculate them in the shader, but that might be another post ;) Again, the online Unity <a href="http://docs.unity3d.com/Documentation/Components/SL-Reference.html">docs</a> were a massive help with this.<br />
My shader properties are pretty simple, 4 textures along with their bump maps:<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:37014133-ae25-4819-aace-9f860c94070b" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
Properties <br />
{<br />
_MainTex ("Dirt", 2D) = "red" {}<br />
_BumpMap ("Dirt Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex2 ("Grass", 2D) = "green" {}<br />
_BumpMap2 ("Grass Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex3 ("Stone", 2D) = "grey" {}<br />
_BumpMap3 ("Stone Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex4 ("Snow", 2D) = "white" {}<br />
_BumpMap4 ("Snow Bumpmap", 2D) = "bump" {}<br />
}</div>
</div>
</div>
I did run out of arithmetic instruction so had to set the SM to 3.0 as well as add a vertex fragment to the shader to push on my blend data like this<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:cf84e4bb-c25d-4490-ac63-ab9fa2247b66" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
#pragma surface surf Lambert vertex:vert<br />
#pragma target 3.0</div>
</div>
</div>
Then we just calculate the texture and bump map values based on the blend settings in the surface shader :)<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:e6113c61-831b-4128-bfd9-826ae29ac5d7" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
void vert (inout appdata_full v, out Input o) <br />
{<br />
UNITY_INITIALIZE_OUTPUT(Input,o);<br />
o.customColor = abs(v.color);<br />
}<br />
<br />
void surf (Input IN, inout SurfaceOutput o) <br />
{<br />
<br />
float3 col = 0;<br />
col = tex2D(_MainTex,IN.uv_MainTex) * IN.customColor.x;<br />
col += tex2D(_MainTex2,IN.uv_MainTex) * IN.customColor.y;<br />
col += tex2D(_MainTex3,IN.uv_MainTex) * IN.customColor.z;<br />
col += tex2D(_MainTex4,IN.uv_MainTex) * IN.customColor.w;<br />
<br />
o.Albedo = pow(col * .5,1.25);<br />
<br />
float3 n = 0;<br />
n = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap) * IN.customColor.x);<br />
n += UnpackNormal (tex2D (_BumpMap2, IN.uv_BumpMap) * IN.customColor.y);<br />
n += UnpackNormal (tex2D (_BumpMap3, IN.uv_BumpMap) * IN.customColor.z);<br />
n += UnpackNormal (tex2D (_BumpMap4, IN.uv_BumpMap) * IN.customColor.w);<br />
o.Normal = n;<br />
}</div>
</div>
</div>
And shazam! The blighter only worked :D <br />
<a href="http://lh5.ggpht.com/-jvJ4TuAzAuQ/Uqs_5AUZFrI/AAAAAAAABpE/XgXxEo9Pnss/s1600-h/1425267_10151810425112218_1951188246_o%25255B1%25255D%25255B3%25255D.jpg"><img alt="1425267_10151810425112218_1951188246_o[1]" border="0" height="236" src="http://lh4.ggpht.com/-nzPXfsF2wdM/Uqs_54d1ErI/AAAAAAAABpM/k6wUvwIChMk/1425267_10151810425112218_1951188246_o%25255B1%25255D_thumb%25255B1%25255D.jpg?imgmax=800" style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="1425267_10151810425112218_1951188246_o[1]" width="376" /></a><br />
To implement it, I created an empty game object, gave it the terrain script, a mesh filter, a mesh renderer and a mesh collider, I then changed the shader to be my custom\terrainshader set the params on script and shader as I wanted. <br />
There are a few other bits and bobs in there to iron out and polish, but think I am now ready to move on to that geo clip map implementation :) I hope it’s going to be as easy as I think it will be….<br />
Here is my shader in full<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:2fb01cd1-3050-4176-9255-08935fd7c12a" style="display: inline; float: none; margin: 0px; padding: 0px;">
<div style="border: 1px solid rgb(0, 0, 128); color: black; font-family: "Courier New", Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
Shader "Custom/TerrainShader" <br />
{<br />
Properties <br />
{<br />
_MainTex ("Dirt", 2D) = "red" {}<br />
_BumpMap ("Dirt Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex2 ("Grass", 2D) = "green" {}<br />
_BumpMap2 ("Grass Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex3 ("Stone", 2D) = "grey" {}<br />
_BumpMap3 ("Stone Bumpmap", 2D) = "bump" {}<br />
<br />
_MainTex4 ("Snow", 2D) = "white" {}<br />
_BumpMap4 ("Snow Bumpmap", 2D) = "bump" {}<br />
}<br />
SubShader <br />
{<br />
Tags { "RenderType" = "Opaque" }<br />
CGPROGRAM<br />
<br />
#pragma surface surf Lambert vertex:vert<br />
#pragma target 3.0<br />
<br />
struct Input <br />
{<br />
float2 uv_MainTex;<br />
float2 uv_BumpMap;<br />
<br />
float4 customColor;<br />
};<br />
<br />
sampler2D _MainTex;<br />
sampler2D _BumpMap;<br />
<br />
sampler2D _MainTex2;<br />
sampler2D _BumpMap2;<br />
<br />
sampler2D _MainTex3;<br />
sampler2D _BumpMap3;<br />
<br />
sampler2D _MainTex4;<br />
sampler2D _BumpMap4;<br />
<br />
void vert (inout appdata_full v, out Input o) <br />
{<br />
UNITY_INITIALIZE_OUTPUT(Input,o);<br />
o.customColor = abs(v.color);<br />
}<br />
<br />
void surf (Input IN, inout SurfaceOutput o) <br />
{<br />
<br />
float3 col = 0;<br />
col = tex2D(_MainTex,IN.uv_MainTex) * IN.customColor.x;<br />
col += tex2D(_MainTex2,IN.uv_MainTex) * IN.customColor.y;<br />
col += tex2D(_MainTex3,IN.uv_MainTex) * IN.customColor.z;<br />
col += tex2D(_MainTex4,IN.uv_MainTex) * IN.customColor.w;<br />
<br />
o.Albedo = pow(col * .5,1.25);<br />
<br />
float3 n = 0;<br />
n = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap) * IN.customColor.x);<br />
n += UnpackNormal (tex2D (_BumpMap2, IN.uv_BumpMap) * IN.customColor.y);<br />
n += UnpackNormal (tex2D (_BumpMap3, IN.uv_BumpMap) * IN.customColor.z);<br />
n += UnpackNormal (tex2D (_BumpMap4, IN.uv_BumpMap) * IN.customColor.w);<br />
o.Normal = n;<br />
}<br />
ENDCG<br />
} <br />
Fallback "Diffuse"<br />
}</div>
</div>
</div>
As ever, comments are more than welcome.<br />
<br />
[Edit]<br />
The discovery of the class attribute <a href="http://docs.unity3d.com/Documentation/ScriptReference/ExecuteInEditMode.html"><span style="background-color: #eeeeee; color: #222222; font-family: Verdana, Arial; font-size: 8pt; line-height: 17px;">[</span><span style="color: #145d7b; font-family: Verdana, Arial; font-size: xx-small;"><span style="line-height: 17px;">ExecuteInEditMode</span></span><span style="background-color: #eeeeee; color: #222222; font-family: Verdana, Arial; font-size: 8pt; line-height: 17px;">]</span></a> means that my height map terrain can now be viewed in the editor too :D Awesome !!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-8EmEF6NZG5M/Uq74g7-HUCI/AAAAAAAABpc/_TnHGXXpgBY/s1600/UnityHMTerrainEditor.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="201" src="http://1.bp.blogspot.com/-8EmEF6NZG5M/Uq74g7-HUCI/AAAAAAAABpc/_TnHGXXpgBY/s320/UnityHMTerrainEditor.jpg" width="320" /></a></div>
<br />
<br />
[/Edit]<br />
<br />
<br />Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-69480909162346771082013-12-02T14:27:00.001-08:002013-12-03T04:00:57.833-08:00Unity3D 4.3 and WCF Web ServiceThis post is about how I got Unity3D to talk to my WCF web service. I don’t know if this will be of any use in the future for development on the XB1 as I have no idea what MS are going to allow on their network, I am guessing it will be quite locked down, just as it was for XNA on the 360, but felt it was worth looking at anyway, as I am using Unity3D I may want to deploy to other platforms anyway, so this could still be useful.<br />
<h2>
So, why can’t I just add a service reference and use that?</h2>
Well, you can, and in VS it will build, but, Unity3D won’t have a clue what you are trying to reference..<br />
<h2>
What helped me find the solution I came to?</h2>
As you do, I had a mooch on google, and came across a number of post and cross posts that pointed to <a href="http://randomrnd.com/2011/08/21/webservices-in-unity/">this</a> method of accessing a web service in Unity. For some reason, when I used this method, Unity3D would complain about the System.ServiceModel namespace, so, I thought, after finding <a href="http://docs.unity3d.com/Documentation/Manual/UsingDLL.html">this</a> link saying I can just drop an assembly in and it gets picked up by Unity3D, I dragged and drop System.ServiceModel and System.Runtime.Serialization into my assets script folder. All this seemed to do was throw up another issue, with the HTTP binding. The assemblies I was adding were .NET 3.0, as I now know, Unity3D is really only compliant with 2.0, though I am sure I read somewhere that it’s a bit of a mix between 2.0 and 3.5, what ever the case, it didn’t like me assembles. In the original post I found it mentioned moving the assemblies to the mono folder, naturally I ignored this, I was not using mono, but, it gave me the idea to start looking around the Unity3D mono installation, and I found a set of C# assemblies in there, two of which are the ones I wanted, so, in an act of desperation, I copied those two assemblies over into my assets script folder….and it only bloody worked!<br />
<h2>
How am I doing it now?</h2>
So, create your WCF service as you would normally, I am using .NET4.0 for mine. Before you do anything else, create a WebClient folder in your script folder, I do this just to keep it separate, you could just drop it anywhere in your assets folder I guess.<br />
As in the original post describing how to do this I use svcutil to build the proxy class for my Unity3d scripts to use, I created a very simple cmd file that I can run for this, before you run it, make sure your service is running, or it wont be able to build it.<br />
<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:4d9ab4d1-672a-4582-a0aa-87909e6e9ca8" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="background: #ffffff; color: black;">cd "f:\development\unity\killercore\webservice"</span><br />
<span style="background: #ffffff; color: black;">cls</span><br />
<span style="background: #ffffff; color: black;">svcutil -out:F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\KillerCoreWebService.cs http://localhost:9997/KillerCoreWebService.svc?wsdl </span><br />
<br />
<span style="background: #ffffff; color: black;">copy "C:\Program Files (x86)\Unity\Editor\Data\Mono\lib\mono\2.0\System.Runtime.Serialization.dll" "F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\System.Runtime.Serialization.dll"</span><br />
<span style="background: #ffffff; color: black;">copy "C:\Program Files (x86)\Unity\Editor\Data\Mono\lib\mono\2.0\System.ServiceModel.dll" "F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\System.ServiceModel.dll"</span></div>
</div>
</div>
<br />
<strong>IMPORTANT: </strong>Only run this cmd in a Visual Studio Command prompt. <br />
As you can see it copies my resulting code into my script folder, it then also copies the mono assemblies I need into that folder too. Switch back to Unity3D and it will load the new cs script and dll’s<br />
<a href="http://lh4.ggpht.com/-tewhJcLkIbU/Up0JZiKQTGI/AAAAAAAABoE/XD1AbWgBczA/s1600-h/KCUWSRef%25255B6%25255D.jpg"><img alt="KCUWSRef" border="0" height="231" src="http://lh3.ggpht.com/-uLSyTXokO-s/Up0JaP_1PWI/AAAAAAAABoM/aKS3WJUsTBk/KCUWSRef_thumb%25255B4%25255D.jpg?imgmax=800" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="KCUWSRef" width="492" /></a> <br />
And that is pretty much that, I can now access my WCF web service from with in my Unity3D game.<br />
<h3>
KillerCoreWebService Call</h3>
My very simple service has one method at the moment called GetHighScoreTable, and for the sake of testing looks like this:<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:7ec9a998-2a40-4ac5-bb9c-dba4681002de" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="background: #ffffff; color: black;"></span><span style="background: #ffffff; color: blue;">public</span><span style="background: #ffffff; color: black;"> </span><span style="background: #ffffff; color: #2b91af;">HighScoreResponse</span><span style="background: #ffffff; color: black;"> GetHighScoreTable()</span><br />
<span style="background: #ffffff; color: black;">{</span><br />
<span style="background: #ffffff; color: black;"></span><span style="background: #ffffff; color: #2b91af;">HighScoreResponse</span><span style="background: #ffffff; color: black;"> hst = </span><span style="background: #ffffff; color: blue;">new</span><span style="background: #ffffff; color: black;"> </span><span style="background: #ffffff; color: #2b91af;">HighScoreResponse</span><span style="background: #ffffff; color: black;">();</span><br />
<br />
<span style="background: #ffffff; color: black;"></span><span style="background: #ffffff; color: blue;">for</span><span style="background: #ffffff; color: black;"> (</span><span style="background: #ffffff; color: blue;">int</span><span style="background: #ffffff; color: black;"> x = 0; x < 10; x++)</span><br />
<span style="background: #ffffff; color: black;">hst.HighScoreTable.Add(</span><span style="background: #ffffff; color: blue;">new</span><span style="background: #ffffff; color: black;"> </span><span style="background: #ffffff; color: #2b91af;">HighScoreEntity</span><span style="background: #ffffff; color: black;">(</span><span style="background: #ffffff; color: blue;">string</span><span style="background: #ffffff; color: black;">.Format(</span><span style="background: #ffffff; color: #a31515;">"High Score {0}"</span><span style="background: #ffffff; color: black;">, x + 1), (x + 1 * 501).ToString()));</span><br />
<br />
<span style="background: #ffffff; color: black;"></span><span style="background: #ffffff; color: blue;">return</span><span style="background: #ffffff; color: black;"> hst;</span><br />
<span style="background: #ffffff; color: black;">}</span></div>
</div>
</div>
So, my response class holds a list or array of HighScoreEntity, this “entity” just holds a name and a value for a high score, naturally I would wire this up to a database on my server, but this will do for my test, just passing back 10 values.<br />
<h3>
Client side use of the Web Service</h3>
I have a globals script that I use to hold stuff I want to use all over the place, so, the first thing I need is a client object to connect to the service, like this<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:25799712-93b6-4b32-992c-b38689b8fc00" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px;">
<span style="color: blue;">static</span> <span style="color: #2b91af;">KillerCoreWebServiceClient</span> client = <span style="color: blue;">new</span> <span style="color: #2b91af;">KillerCoreWebServiceClient</span>(<span style="color: blue;">new</span> <span style="color: #2b91af;">BasicHttpBinding</span>(<span style="color: #2b91af;">BasicHttpSecurityMode</span>.None), <span style="color: blue;">new</span> <span style="color: #2b91af;">EndpointAddress</span>(<span style="color: #a31515;">"http://localhost:9997/KillerCoreWebService.svc"</span>));</div>
</div>
</div>
This client is connecting to the service running on my system, so you would alter the url to point at the service on your server. I then create a static method I can use to call a method on the service.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:65b4d6f0-036b-4780-86b8-2e880ad9fb4f" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">HighScoreEntity</span>> GetHighScores()<br />
{<br />
<span style="color: #2b91af;">HighScoreResponse</span> response = client.GetHighScoreTable();<br />
<br />
<span style="color: blue;">return</span> response.HighScoreTable.ToList();<br />
}</div>
</div>
</div>
So, this simply goes off to the web service and gets the high score table.. Now in my test UI I wire it up under a button click.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:b42456ab-8c92-4331-843e-5f5823cab0a8" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">if</span> (<span style="color: #2b91af;">GUI</span>.Button(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(0, 0, 100, 50), <span style="color: #a31515;">"Get Highscores"</span>))<br />
{<br />
scores = <span style="color: #2b91af;">Globals</span>.GetHighScores();<br />
}<br />
<br />
<span style="color: blue;">if</span> (scores.Count > 0)<br />
{<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(10, 110, 100, 20), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} Records"</span>, scores.Count));<br />
<span style="color: blue;">int</span> cnt = 0;<br />
<span style="color: blue;">foreach</span> (<span style="color: #2b91af;">HighScoreEntity</span> score <span style="color: blue;">in</span> scores)<br />
{<br />
cnt++;<br />
<span style="color: #2b91af;">GUI</span>.Label(<span style="color: blue;">new</span> <span style="color: #2b91af;">Rect</span>(10, 110 + (cnt * 20), 100, 20), <span style="color: blue;">string</span>.Format(<span style="color: #a31515;">"{0} - {1}"</span>, score.Name, score.Score));<br />
}<br />
}</div>
</div>
</div>
scores is just a list of HighScoreEntity, once populated I display how many records have been returned and the records returned.<br />
<a href="http://lh4.ggpht.com/-sxoo6K3siXs/Up0JaqowsRI/AAAAAAAABoU/raddW89GeRo/s1600-h/1398370_10151784623107218_659661404_o%25255B1%25255D%25255B5%25255D.jpg"><img alt="1398370_10151784623107218_659661404_o[1]" border="0" height="347" src="http://lh5.ggpht.com/-fKnGv53ZczM/Up0JbZyOsZI/AAAAAAAABoc/waECiGi_PZo/1398370_10151784623107218_659661404_o%25255B1%25255D_thumb%25255B3%25255D.jpg?imgmax=800" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="1398370_10151784623107218_659661404_o[1]" width="552" /></a> <br />
So, after all my moaning and whinging, it turned out not to be that difficult to set up Unity3D so it can access a WCF web service. I hope this helps make your life a bit easier integrating WCF services into your Unity3D projects. If you find a better way, or have issues with this method, then let me know.<br />
<br />
[UPDATE]<br />
Tested this for the Web player and it does not work, I guess just the full executable version, if I do need WS ill end up having to write a post/get class to pull stuff back from a server, unless Unity3D gets an update to the .NET framework I guess.<br />
[/UPDATE]Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com4tag:blogger.com,1999:blog-7807584830587621328.post-14010565664323574172013-11-17T15:48:00.001-08:002013-11-22T08:21:52.064-08:00My First Unity3D Project<h1>
Killer Core</h1>
Those of you that have followed my old XNA blogs will know that I have had the idea for Killer Core rolling around for an age, have had it in pretty much every release of XNA and of each of the engines I have created with XNA. With the announcement that Unity3D will be a valid development platform for the new XBox One I thought I might as well start work on porting what I have to Unity3D.<br />
I am using the freeware version of Unity3D rather than the Pro, firstly, I am a hobbyist and so have a budget of zero for my projects, but once we can develop for the new XB1 those of us that have registered with the ID@XBox program will get Unity3D Pro for XB1 for free :D So, when that becomes available, I will upgrade my projects to that. Also, in the mean time I can deploy my games in the internet and you guys can tell me just how much it sucks :P<br />
So, I thought I would show you where I am with the project after about a week of development with Unity3D. I don’t get a lot of personal development time, it’s done in my lunch and when I have a free evening or two. This always leaves me thinking “What could I do if I was able to dedicate 7 hours a day to this stuff…” If I win the lotto I’ll find out I guess.<br />
<h2>
Where was I last week with it?</h2>
Last week I had got to the stage where I could create simple scripts and get models on the screen, so I started off by getting the models I had already created in the XNA version(s) and get them into Unity3D. I also have some simple physics and started to play with the basic particle systems, and it looked like this:-<br />
<div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:32397620-f6a9-4501-8b4f-c8f68a1b92d0" style="display: block; float: none; margin-left: auto; margin-right: auto; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; width: 425px;">
<div id="9b1f077a-d709-4ebb-a3a4-fdb2ebd504c1" style="display: inline; margin: 0px; padding: 0px;">
<div>
<a href="http://www.youtube.com/watch?v=9GFQVQhtUxY&feature=youtube_gdata_player" target="_new"><img alt="" galleryimg="no" onload="var downlevelDiv = document.getElementById('9b1f077a-d709-4ebb-a3a4-fdb2ebd504c1'); downlevelDiv.innerHTML = "<div><object width=\"425\" height=\"355\"><param name=\"movie\" value=\"http://www.youtube.com/v/9GFQVQhtUxY&hl=en\"><\/param><embed src=\"http://www.youtube.com/v/9GFQVQhtUxY&hl=en\" type=\"application/x-shockwave-flash\" width=\"425\" height=\"355\"><\/embed><\/object><\/div>";" src="http://lh4.ggpht.com/-7n38C3Qcdp0/UolVzKDK4DI/AAAAAAAABm4/3KILFQ1C10w/video8fac08f4e7d2%25255B11%25255D.jpg?imgmax=800" style="border-style: none;" /></a></div>
</div>
</div>
As you can see, my modelling skills are as bad as ever, but, I am trying to focus more on the game mechanic. You will also notice the glitch that occurs when I shoot my first shot, and that the particle systems are destroyed along with the parent object. Both of those things are now resolved and Ill show you what I did to remedy them after I show you what it currently looks at.<br />
<h2>
Where am I with it now?</h2>
<div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:9f3a9c03-f9b1-4240-9334-29db0b727389" style="display: block; float: none; margin-left: auto; margin-right: auto; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; width: 425px;">
<div id="cb3c107d-e1f1-4f31-a4cb-5b0c7ab5bae8" style="display: inline; margin: 0px; padding: 0px;">
<div>
<a href="http://www.youtube.com/watch?v=bW5K4I45lz8&feature=youtube_gdata_player" target="_new"><img alt="" galleryimg="no" onload="var downlevelDiv = document.getElementById('cb3c107d-e1f1-4f31-a4cb-5b0c7ab5bae8'); downlevelDiv.innerHTML = "<div><object width=\"425\" height=\"355\"><param name=\"movie\" value=\"http://www.youtube.com/v/bW5K4I45lz8&hl=en\"><\/param><embed src=\"http://www.youtube.com/v/bW5K4I45lz8&hl=en\" type=\"application/x-shockwave-flash\" width=\"425\" height=\"355\"><\/embed><\/object><\/div>";" src="http://lh6.ggpht.com/-TsSSPLQMcik/UolVzWaPiLI/AAAAAAAABm8/FgmVDSkwRuE/video7593bbccaa0b%25255B3%25255D.jpg?imgmax=800" style="border-style: none;" /></a></div>
</div>
</div>
As I say, I have resolved the initial creation issue for the shells, I did this in my Shellshooter C# script. I added a void Awake() function, and in there I simply create an instance of the shell, and destroy it there and then.<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:40bad5b0-1022-4a36-aca7-b4047ed24a07" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">void</span> Awake()<br />
{<br />
<span style="color: #2b91af;">Rigidbody</span> t = (<span style="color: #2b91af;">Rigidbody</span>)Instantiate(Block, <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector3</span>(10000, 10000, 10000), <span style="color: #2b91af;">Quaternion</span>.identity);<br />
Destroy(t.gameObject);<br />
<br />
}</div>
</div>
</div>
As you can see I place it a great distance from the scene too just in case my destroy call is not called in a timely manor, not that I can see why it wouldn’t but cant hurt can it :)<br />
So, how about the particle issue, well I had a global static method in my Globals script that I wrote to handle the destruction of objects, so when the object is killed I call Globals.ObjectDeath(gameObject, HideOnDeath); The hide on death parameter is used so if set, the object is hidden first so it’s particle emitter can carry on and the object is then destroyed after the HideOnDeath value in seconds :)<br />
<div class="wlWriterEditableSmartContent" id="scid:9ce6104f-a9aa-4a17-a79f-3a39532ebf7c:928b6b75-d1d3-4c79-9abc-416948438b48" style="display: inline; float: none; margin: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;">
<div style="border: #000080 1px solid; color: black; font-family: 'Courier New', Courier, Monospace; font-size: 10pt;">
<div style="background-color: white; max-height: 300px; overflow: auto; padding: 2px 5px; white-space: nowrap;">
<span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">void</span> ObjectDeath(<span style="color: #2b91af;">GameObject</span> gameObject, <span style="color: blue;">float</span> hideTime = 0)<br />
{<br />
<br />
<span style="color: blue;">if</span> (hideTime != 0)<br />
{<br />
gameObject.renderer.enabled = <span style="color: blue;">false</span>;<br />
<br />
<span style="color: blue;">if</span> (gameObject.rigidbody != <span style="color: blue;">null</span>)<br />
gameObject.rigidbody.isKinematic = <span style="color: blue;">true</span>;<br />
<br />
<span style="color: blue;">if</span> (gameObject.collider != <span style="color: blue;">null</span>)<br />
gameObject.collider.enabled = <span style="color: blue;">false</span>; <br />
}<br />
<br />
Destroy(gameObject, hideTime);<br />
<br />
}</div>
</div>
</div>
As you can see, if the hideTime is not 0, i disable the objects renderer, then it’s physics components so they don’t hinder the scene either, the intrinsic Destroy call then handles the destruction of the object after a given time.<br />
So, not the greatest clip in the world, but it’s progress, and I think I have managed to do quite a bit in the short time I have been working in it. Unity3D really has surprised me by how much I have enjoyed using it so far.<br />
<h2>
What next?</h2>
Lots of stuff lol, get a blast effect in there, I would like to get my old XNA flames working in there, so will have to read up on how to create my own shaders next. I am going to try and make a blog post each month if I can. Really need to sort out my terrain/platfom mesh’s they really suck. I can use Blender, I just don’t have the patients for creating decent UV maps, maybe I should :S<br />
Hoping that by the time we can develop the game will almost ready to go into test/release, fingers crossed eh…<br />
Ohh did I mention that the XBox One launch date is the same date as my birthday, how cool is that lol, it will be a new born, but alas, I will be a creaky old 42 :(Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-68723388867841317032013-11-12T08:30:00.000-08:002013-11-22T08:21:38.777-08:00Unity 4.3 Released!So, what’s all the fuss about? Well, as you know, if you follow this blog, I am new to Unity, but from what I have gleaned over the past few weeks or so, Unity lacked a fair bit in the area of 2D. They have also has an inbuilt animation tool called Mecanim now, so lots of new stuff in this release making it even more exciting to see what is going to be created for the XBox One platform using this tool :D<br />
The full run down on this new version can be found <a href="http://unity3d.com/unity/whats-new">here</a>.<br />
I am currently downloading it now, looking forward to investigating all the new bits, as well as still learning all the old bits :PCharles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-64665089114055335912013-11-07T14:14:00.000-08:002013-11-22T08:21:25.953-08:00Unity for ID@Xbox Developers!I have heard about Unity3D for a long time now, and while I was writing samples and learning all I could about games and graphics programming in XNA I never really gave it a second thought. I think this was because it looked like a simple drag and drop interface for creating simple games, granted across many platforms, but at a price, which as a bedroom coder/hobbyist is not an attractive thing to be.<br />
But, with the announcement that the Unity Pro version would be FREE to <a href="http://www.xbox.com/en-us/Developers/id">ID@Xbox</a> developers, I had to check it out. I first heard about this via the awesome Facebook group I am a member of, <a href="https://www.facebook.com/groups/XboxOneIndieDevs/">Xbox Indie Devs</a>, where one of the guys on there reported that Unity had posted an <a href="http://blogs.unity3d.com/2013/11/05/big-xbox-one-news-for-unity-developers/">article</a> on their blog about it, which then also took me to the <a href="http://news.xbox.com/2013/11/xbox-one-unity-id">Xbox news</a> for the same thing, so I had to go and <a href="http://unity3d.com/unity/download">get me a copy</a> to try it out, and I have to say, I am quite impressed with what I was able to do in just a few short hours.<br />
This is the culmination of a couple of lunch hours and an evening with Unity and the Unity <a href="http://unity3d.com/learn/tutorials/modules">online beginners help</a>.<br />
<div class="wlWriterEditableSmartContent" id="scid:5737277B-5D6D-4f48-ABFC-DD9C333F4C5D:fcee4b21-b61e-499b-adaf-290d36027157" style="display: block; float: none; margin-left: auto; margin-right: auto; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; width: 425px;">
<div id="6ec2f6c8-3783-4258-a078-397eaa63c199" style="display: inline; margin: 0px; padding: 0px;">
<div>
<a href="http://www.youtube.com/watch?v=0L6DFL_6sho&feature=youtube_gdata_player" target="_new"><img alt="" galleryimg="no" onload="var downlevelDiv = document.getElementById('6ec2f6c8-3783-4258-a078-397eaa63c199'); downlevelDiv.innerHTML = "<div><object width=\"425\" height=\"355\"><param name=\"movie\" value=\"http://www.youtube.com/v/0L6DFL_6sho&hl=en\"><\/param><embed src=\"http://www.youtube.com/v/0L6DFL_6sho&hl=en\" type=\"application/x-shockwave-flash\" width=\"425\" height=\"355\"><\/embed><\/object><\/div>";" src="http://lh6.ggpht.com/-m1SptMbe_lA/Uny5k7fa6GI/AAAAAAAABlo/WZqv9rHtkvQ/video321e1d83bb39%25255B9%25255D.jpg?imgmax=800" style="border-style: none;" /></a></div>
</div>
<div style="clear: both; font-size: .8em;">
My First Look at Unity</div>
</div>
In this scene are three animated mesh’s (you must recognise Dude!) as well as three point lights a number of cubes, and a cavern mesh, I have some physics in there too for the box’s and the dudes.<br />
I am really impressed with Unity, and how easy it was to create something so quickly, even if you are not part of the ID@Xbox program, then I would suggest you check it out anyway. I am just hoping I am going to be able to write my own shaders for it, as well as do Post Processing and all the other great stuff I have learnt from XNA. I dare say I will, I just have lots to learn, again…<br />
So, with that I am hoping to start posting my adventures in the world of Unity, and hope it helps others as they come to use it.Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0tag:blogger.com,1999:blog-7807584830587621328.post-63656760543579889232013-08-22T01:29:00.001-07:002013-08-22T01:29:24.384-07:00XBox One Indie Development – Welcome<p>So, what has got me blogging again? Well, the news that EVERYONE will be able to write games for the new XBox One, that’s what!!</p> <p>If I am honest, the past year or so have not been great for me in terms of how Microsoft have been. I don’t think they have meant it, but they have given the impression that indie developers didn’t mean a lot to them. After the awesome work they did with XNA, to then abandon that framework without giving people who have invested time and money into it was in my view just plain wrong. Also, what I have found disheartening is that Microsoft, in the area of games development seem to be climbing down the technology tree to C++. I don’t deny that C++ was, and still is, a very powerful language (started out in 95 as a C/C++ developer), I also get that all the big entities in the games industry have all there development aligned with C++, but, and I hope I am not the only one that can see this, the present is .NET, and when I say .NET I really mean C#.</p> <p>Microsoft has started the ball rolling with <a href="mailto:ID@Xbox">ID@Xbox</a>, where if you want early access to a “development kit”, this is because on the launch of the new console we wont immediately be able to use them as development kits, but we will later in the year. This early access is open to everyone, and if you are accepted you will get an XBox One to start developing on. In this early stage though, Microsoft will probably be more likely to give you access if you have already written and released games in the past, so if you don’t get accepted, don’t worry, we will all have dev kits at some stage just be buying the console.</p> <p>I am hoping that we will be able to use C# on the new XBox One, but I suspect to start with it will be C++ if you want to get access to all the lovely things that the new console will have.</p> <p>I guess this is the only thing worrying me at the moment, what languages will we be able to use, if it’s not just C++, will the other languages have the same level of access that C++ will have??</p> <p>Either way, I see this as a great step in the right direction, and I am hopeful for how this could pan out, it’s good to feel excited about something Microsoft is doing again.</p> <p>You can follow the <a href="https://twitter.com/ID_Xbox">ID@XBox</a> on twitter too [<a href="https://twitter.com/ID_Xbox">https://twitter.com/ID_Xbox</a>] oh, and if you have not found me on there yet, you can follow me <a href="https://twitter.com/NemoKrad">here</a>.</p> <p>I am going to blog my journey through this new XBox One world, write articles on others in the same situation as me, and once I can write something for the console, start putting some samples up again :D</p> <p>See you again soon, fellow XBox One Indie adventurer ;)</p> Charles Humphreyhttp://www.blogger.com/profile/10935746329039730399noreply@blogger.com0