Search Unity

Procedural World Generation Tips/Advice for loading a Voxel type level

Discussion in 'Scripting' started by Abnegation, Apr 11, 2015.

  1. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Hi there Unity3D. I'm a student working on my FYP, and I decided to tackle a bit more than I set out to. As this isn't intended to be an "original" project or IP, I decided to attempt a Minecraft clone in the hopes to bring my Unity knowledge past what it is.

    As of now, I have procedural world generation created, the ability to place blocks, mine them (and pick up the mined blocks), a basic inventory with GUI and block selection, generated trees (though buggy still), and some other features. However, the biggest problem is performance.

    I have researched extensively into greedy meshing and occluding unseen cube sides, however it goes slightly over my head. What I need help with is advice on how to better optimise my engine upon world generation. I followed tutorials and open source code for my world generation since I'm completely new to it, so I'll add the code below.


    So some of the things I have tried are:
    • Occluding blocks that aren't seen using a basic culling script [link]
    • Reducing render distance of my chunk (however I really want to raise it)
    • Reducing FOV
    • Optimizing textures and cubes (using less quaded cubes, in place for cube primitives)
    • Locking frame rate
    • Some other minor improvements
    What I need help with is:
    • Potentially converting the generated cubed world into a mesh (greedy meshing)
    • Allowing the user to still mine the individual elements once the mesh is rendered
    • Hiding unseen faces
    • Any other possible performance tips when it comes to creating a voxel engine.
    The scirpts used to create the world generation are:

    [CubeGeneration.cs]

    [Chunk.cs]

    [PerlinNoise.cs]

    A lot of this code comes from tutorials and/or things I found when researching world generation. However as I say, this is all a learning experience. Any help on how to optimise this or implement a system whereby the engine is only rendering seen faces and/or some form of greedy meshing funtionality would be extremely helpful and aid me in learning more. Any help is massively appreciated, thanks guys!
     
    Last edited: Apr 11, 2015
  2. Xavior87

    Xavior87

    Joined:
    Feb 22, 2015
    Posts:
    23
    To help your performance, you can make a script to where you only render objects in your camera's view. As in, you disable everything else that isn't in your view, and when you look at them they instantly become active. Hope this helps!
     
    Abnegation likes this.
  3. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Hey there, yeah I did try creating some culling scripts to handle that sort of logic.
    One for normal culling: http://pastebin.com/VDLZyN5y
    One for culling objects with children: http://pastebin.com/6kLS8116

    Thanks for the suggestion though, it did help the FPS somewhat. But the real problem is the number of objects getting rendered despite that. I need just the faces of the objects that can be seen to be rendered, as opposed to the whole object.
     
  4. Xavior87

    Xavior87

    Joined:
    Feb 22, 2015
    Posts:
    23
    I'm not the best at that kind of thing! :p
     
  5. Juice-Tin

    Juice-Tin

    Joined:
    Jul 22, 2012
    Posts:
    244
    Here's a few things I know about performance:

    - Every time Unity draws a different texture, that's a new draw call.
    - Too many draw calls are generally bad. (It gets way more complex here, but the less the better)
    - Combining meshes does nothing but make unity render all those materials as a group. (Meaning if you have 2 brown blocks and 1 red block. Unity might render Brown>Red>Brown which is 3 draws. Combine the 2 browns and unity will render Brown>Brown>Red which is only 2 draws, since both browns share the same material)

    SO, the best way to optimize your game is to have all the block textures on 1 giant atlas. You can then set their specific block faces by using the offset/tiling on the material.

    This means your whole world will be using 1 texture and will be 1 draw call.

    Static blocks are batched and rendered Waaaaaaaaaay faster. You could potentially combine your world into 1 static mesh using the Mesh class and hiding the dynamic blocks, then when a block is mined, combine the new world into a new static mesh. So essentially you are rendering the static world, but when it has to be changed you still have the dyanmic world there, but hidden.
     
    Abnegation likes this.
  6. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I haven't read your code in depth, but here are some things I can tell you about voxel performance:

    1. Never make each cube its own GameObject. You should have your GameObject be a 'chunk' of blocks. In my own voxel engine, I use chunks of size 32x64x32 blocks (you may also want 128 height). For each chunk object, you will draw out the quads that make each block face to form the mesh. You'll have one single mesh for the entire chunk of blocks. The number of draw calls will be equal to the number of visible chunks. Generally, people use 16x16 (x, z) chunks, though I prefer 32x32 to reduce draw calls further.

    Note that you will only want to draw block faces that are adjacent either to a transparent block or to air. You simply check the adjacent blocks on all 6 sides when making the mesh for a particular block. If there's air or a transparent block adjacent in that direction, you draw that quad. Otherwise, ignore it.

    Also note that Unity has a vertex limit of around 65k. This is because Unity uses 16-bit vertex indices, and I'm really hoping they bump it up to 32-bit to solve these issues. Because you're not drawing the faces that are adjacent to solid blocks, you're likely not going to run into problems here. However, the user could come up with a design that could break it if he/she desired. The only real solution I know of is to use smaller chunks, but then you have higher draw calls. It's ugly.

    When the player modifies a block in your chunk, you'll have to re-create the mesh for the chunk (the entire chunk) to show the change.

    2. Multithreading. I recommend creating your meshes in the background, so that there's not a frame rate hit upon doing so. Depending on how you do your game, you can also put all terrain generation (using perlin noise) in the background as well. For example: you don't want the player to fall through the world on start, so generate the first few chunks where the player is in the main thread - this way the player will have to wait until they're done loading before spawning. After that, generate all surrounding chunks in the background so that they don't affect frame rate. And generate future chunks that load up when the player moves around in the background as well.

    3. Object pooling. Don't instantiate/destroy chunks all the time. I recommend using a chunk pooling system where you recycle the game objects to make the chunks and reuse them for new chunks.

    Those are a few things to consider. If you need some more in-depth help, we can talk more about it.
     
    Abnegation likes this.
  7. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Thanks for the logic behind everything, it certainly puts it into perspective. I was looking into a lot of what you suggested above, such as chunk generation (but for now, getting one to work at a decent frame rate has been a challenge).

    The next step for me right now is getting one feasible chunk to work before I work on generating more. I'm new to C# (Java software programmer here). Generating the world where only the faces show is a challenge I haven't quite been able to tackle. So if you have any further advice on the best method on generating cubes at an optimised level that would be much appreciated as generating further chunks is something I think I could potentially tackle down the line as I'll be less pressured.

    Thanks again for the explanations so far.
     
  8. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Generating a single chunk is a great place to start!

    To generate a single chunk, you're going to want a chunk GameObject with a Chunk class on it. This class is going to store the 3D array of blocks belonging to that particular chunk, of size 32x64x32, or whatever you want your size to be.

    The first thing to do is going to be use your perlin noise to fill out this voxel grid (array of blocks) with blocks. I'm assuming you know how to do this part already, since your questions are about the mesh.

    The next goal is going to be to create a mesh from this voxel grid. To do that, you're going to loop through every block in the array and draw all six quads.

    For each block, check its type. If it's an air block, you don't generate any mesh and so you ignore it and move on. If it's a solid block that should have a mesh drawn, check each adjacent block in your array (x -1, y, z), (x + 1, y, z), (x, y + 1, z), you get the idea. Determine if it's a solid block or not. If it's solid, then you don't want to draw the quad - the player can't see that quad anyway. But if it's not solid, for example air, then you will draw your quad.

    Now, you'll need a list of vertices, triangles, and UVs.

    To draw your quad, you specify the four vertices for it, the two triangles for it (three vertex indices per triangle, so six values get added to the triangles list), and the UVs - where your tile texture is on your texture atlas.

    Since I'm not sure how much you know about vertices, triangles, and UVs, I'll leave it vague for now. If none of that made any sense, I'll provide more info about it (trying not to be overwhelming here!)

    Once you have your vertex, triangle, and UV list filled with values, you will use code like this:

    Code (CSharp):
    1. visibleMesh.Clear();
    2.  
    3. visibleMesh.vertices = meshData.vertices.ToArray();
    4. visibleMesh.SetTriangles(meshData.triangles.ToArray(), 0);
    5. visibleMesh.uv = meshData.uv.ToArray();
    6.      
    7. visibleMesh.RecalculateNormals();
    to send your data to Unity's rendering engine. Note that I got 'visibleMesh' by using chunk.GetComponent<MeshFilter>().mesh; in the Awake() method.

    Edit: I forgot to mention - be careful when checking the adjacent blocks so that you don't go out of the boundaries of the chunk. If you're going to go out of the boundaries, you're going to want to redirect the check to the neighbor chunk. Since you don't have any neighbors right now, just assume there's air out there.
     
  9. Rabbito

    Rabbito

    Joined:
    Apr 5, 2015
    Posts:
    13
    hi

    Maybe this tutorial could help you

    http://alexstv.com/index.php/category/voxels

    In this Tutorial you are only creating the visible sides of Blocks. Its not perfect (creates tons of work for the Garbage Collector, setting a block needs to recreate hole Chunk...) but it is a realy good basic.
     
    Abnegation likes this.
  10. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I initially learned, a long while back, from his tutorials. It's a good place to start. When you get more experienced, you'll probably end up modifying a lot of it to make it more efficient.
     
    Abnegation likes this.
  11. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Thank you again for your help. I've started looking into using texture atlases to replace my current method of manually adding them to the models in Unity (which is naturally adding a lot of draw calls). So I've looked into the Texture2D.PackTextures API and am interested in how it works. I'm also working on the world generation script at the moment so I'll let you know how I get on on that front.

    However my question for you now is how you would go about using that API, and whether or not I should be using it. Essentially now I have a dozen or so block types. However I wish to use one texture to map to them all (naturally to improve performance). Though I've struggled to find any form of tutorial that shows you how to use Texture2D.PackTextures, and how I would be able to use one script to generate textures for multiple block types.

    I hope that makes sense, and if I'm going completely in the wrong direction here let me know!
     
  12. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    The way I do it is put the textures in a single tile sheet to begin with, and import it into Unity this way. Then what you can do is you can define a tile size:

    If you have a tile sheet with 16 by 16 tiles, then your tile size would be 1 / 16 = 0.0625. In general, 1 / number of tiles along a given axis.

    Then when you specify the UV coordinates, you use this tile value:

    Code (CSharp):
    1. meshData.uv.Add(new Vector2(tileSize * tilePos.x + tileSize, tileSize * tilePos.y));
    2. meshData.uv.Add(new Vector2(tileSize * tilePos.x + tileSize, tileSize * tilePos.y + tileSize));
    3. meshData.uv.Add(new Vector2(tileSize * tilePos.x, tileSize * tilePos.y + tileSize));
    4. meshData.uv.Add(new Vector2(tileSize * tilePos.x, tileSize * tilePos.y));
    In my code here, meshData contains my list of vertices, triangles, and UVs. When I add to my uv list, I use the tileSize to specify the four corners of the tile I'm putting on this particular face.

    tilePos is the coordinates of my tile. So if I want the tile in the bottom-left corner, then tilePos is going to be (0, 0). 3 tiles right and 3 tiles up is (3, 3).

    Edit: I should make clear that I don't have much experience with Texture2D.PackTextures since I haven't used this method, but it looks like you can get the atlas from your textures this way as well if you don't do it outside of Unity. Once you get it, you'll follow the same process I described above.

    It's generally best if you keep the number of tiles along each axis as a power of 2. 16x16, 32x32, etc.
     
  13. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Sweeet, thank you for all your help so far. I've managed to create a world that infinately generates with some great optimisation. Generating trees, caves, and multiple layers and so on. Chunks generate nicely too. I'm going to work on doing most of the chunk generating in the background tonight, and also applying my GUI work and so on that I did before rewriting the core engine.

    I do have a question however. I have all my block types drawing from the same material, however I want to change the shaders depending on block type (you know, for anything with transparency or something that's self-lit). However this is a problem since my block types inherit from their BlockBase and not MonoBehaviour. So I'm wondering how I might change my block shaders dynamically. Let's look at the code...

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. [Serializable]
    7. public class BlockOakLeaves : BlockBase
    8. {
    9.  
    10.     public Shader LeafShader;
    11.     public Renderer rend;
    12.     void Start ()
    13.     {
    14.         rend = GetComponent<Renderer> (); //this line throws a "The name 'GetComponent' does not exist in the current context" error
    15.         LeafShader = Shader.Find ("Nature/Tree Ceator Leaves");
    16.         rend.material.shader = LeafShader;
    17.     }
    18.  
    19.     public BlockOakLeaves ()
    20.         : base()
    21.     {
    22.      
    23.     }
    24.  
    25.     public override Tile TexturePosition (Direction direction)
    26.     {
    27.         Tile tile = new Tile ();
    28.      
    29.         tile.x = 1;
    30.         tile.y = 3;
    31.      
    32.         return tile;
    33.     }
    34.  
    35.     public override bool IsSolid (Direction direction)
    36.     {
    37.         return false;
    38.     }
    39.  
    40.  
    41.  
    42.  
    43. }
    44.  
    Naturally however, that won't work as neither this class, nor the one it derives from use MonoBehaviour. So how would you suggest I change the shader type without writing a whole new class to handle one part of my texture atlas?
     
  14. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Let's say I have two types of shaders I want to use: one type is a standard/cutout shader, and one is a fluid shader. You may want more, but I'll use these as examples.

    First of all, I use what's called submeshing. This is essentially creating the chunks using multiple meshes. Each mesh can have a separate shader.

    Your chunk will need a material array with both materials the chunk will use:

    Code (CSharp):
    1. public Material[] materials = { standardMaterial, fluidMaterial };
    Then, you'll need to specify the number of submeshes your mesh will have.

    Code (CSharp):
    1. mesh.subMeshCount = 2;
    For each submesh, you need a separate list of triangles. You can set them like this:

    Code (CSharp):
    1. mesh.SetTriangles(meshData.triangles.ToArray(), 0);
    2. mesh.SetTriangles(meshData.subTriangles.ToArray(), 1);
    Here, you specify the submesh index. I'm setting my triangles list at index 0 for the standard material, and my subTriangles list to submesh 1.

    What I do is, when building a fluid block, I override the AddTriangles function:

    Code (CSharp):
    1. protected override MeshData AddTriangles(MeshData meshData)
    2. {
    3.     int count = meshData.vertices.Count;
    4.      
    5.     meshData.AddSubTriangle(count - 4);
    6.     meshData.AddSubTriangle(count - 3);
    7.     meshData.AddSubTriangle(count - 2);
    8.      
    9.     meshData.AddSubTriangle(count - 4);
    10.     meshData.AddSubTriangle(count - 2);
    11.     meshData.AddSubTriangle(count - 1);
    12.      
    13.     return meshData;
    14. }
    And specify that I'm adding to my subtriangles list instead of the regular triangles list when building this block, since it will use a submesh material.

    And then you make your separate materials and put the separate shaders on them, and your chunks will build with multiple materials. You can, of course, do this with more than two. I used two for the example here.
     
  15. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    So to verify I would be doing this in my Chunk script for example? I've been working on it for a little bit, but unfortunately I failed to follow you on some accounts here! Sorry about that.

    I've added these Materials to the Chunk script
    Code (CSharp):
    1.  
    2.     public Material standardMaterial;
    3.     public Material leafMaterial;
    4.     public Material[] materials = { standardMaterial, leafMaterial };
    Then I modified my RenderMesh script to look as follows:
    Code (CSharp):
    1.  
    2.     void RenderMesh (MeshData meshData)
    3.     {
    4.         filter.mesh.Clear ();
    5.         filter.mesh.vertices = meshData.vertices.ToArray ();
    6.         filter.mesh.triangles = meshData.triangles.ToArray ();
    7.  
    8.         filter.mesh.uv = meshData.uv.ToArray ();
    9.         filter.mesh.RecalculateNormals ();
    10.  
    11.         coll.sharedMesh = null;
    12.         Mesh mesh = new Mesh ();
    13.         mesh.subMeshCount = 2;
    14.         mesh.SetTriangles (meshData.colTriangles.ToArray (), 0);
    15.         mesh.SetTriangles (meshData.subTriangles.ToArray (), 1);
    16.  
    17.         mesh.vertices = meshData.colVertices.ToArray ();
    18.         mesh.triangles = meshData.colTriangles.ToArray ();
    19.         mesh.RecalculateNormals ();
    20.  
    21.         coll.sharedMesh = mesh;
    22.     }
    23.  
    And added your "protected override MeshData AddTriangles (MeshData meshData)" function to the script, knowing it wasn't the right place to put it. For reference, I'll add my Chunk and MeshData scripts here.
    ChunkC.cs
    MeshData.cs

    Once again, thank you for your patience here. I'm learning a hell of a lot.
     
  16. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    The materials will go in the chunk class, yes.

    My render mesh function looks like this (only including relevant parts):

    Code (CSharp):
    1. private void RenderMesh(MeshData meshData)
    2. {
    3.     visibleMesh.Clear();
    4.     visibleMesh.subMeshCount = 2;
    5.  
    6.     visibleMesh.vertices = meshData.vertices.ToArray();
    7.     visibleMesh.SetTriangles(meshData.triangles.ToArray(), 0);
    8.  
    9.     if (meshData.subTriangles.Count > 0)
    10.         visibleMesh.SetTriangles(meshData.subTriangles.ToArray(), 1);
    11.  
    12.     visibleMesh.uv = meshData.uv.ToArray();
    13.     visibleMesh.RecalculateNormals();
    14. }
    Where visibleMesh is from GetComponent<MeshFilter>().mesh as I stated before.

    I created a class called LiquidBlock. All liquid blocks (water, lava, etc.) inherit from this.

    Code (CSharp):
    1. public class LiquidBlock : BlockBase
    2. {
    3.     // Add triangles to the submesh triangles list instead of the regular one.
    4.     protected override MeshData AddTriangles(MeshData meshData)
    5.     {
    6.         int count = meshData.vertices.Count;
    7.      
    8.         // Basic quad triangles by default.
    9.         meshData.AddSubTriangle(count - 4);
    10.         meshData.AddSubTriangle(count - 3);
    11.         meshData.AddSubTriangle(count - 2);
    12.      
    13.         meshData.AddSubTriangle(count - 4);
    14.         meshData.AddSubTriangle(count - 2);
    15.         meshData.AddSubTriangle(count - 1);
    16.      
    17.         return meshData;
    18.     }
    19.  
    20.     // Getting UVs from a dedicated fluid tile sheet.
    21.     protected override MeshData GetFaceUVs(MeshData meshData, Face face)
    22.     {
    23.         Tile tilePos = GetTexture(face);
    24.      
    25.         meshData.uv.Add(new Vector2(1, tileSize * tilePos.y));
    26.         meshData.uv.Add(new Vector2(1, tileSize * tilePos.y + tileSize));
    27.         meshData.uv.Add(new Vector2(0, tileSize * tilePos.y + tileSize));
    28.         meshData.uv.Add(new Vector2(0, tileSize * tilePos.y));
    29.      
    30.         return meshData;
    31.     }
    32.  
    33.     // Determines if the adjacent block's face should be drawn.
    34.     public override bool CanDrawFace(Face face, BlockBase compareTo = null)
    35.     {
    36.         if (compareTo is LiquidBlock)
    37.             return false;
    38.  
    39.         return true;
    40.     }
    41.  
    42.     // Determines if light will be blocked by this block type.
    43.     public override bool IsOpaque(Face face)
    44.     {
    45.         return false;
    46.     }
    47.  
    48.     // Determines if the player can collide with this block type.
    49.     public override bool CanCollide()
    50.     {
    51.         return false;
    52.     }
    53. }
    This inherits from BlockBase and includes my AddTriangles method, so that any liquid blocks will add the triangles to the submesh list found in MeshData.

    Code (CSharp):
    1. public class MeshData
    2. {
    3.     public List<Vector3> vertices = new List<Vector3>(1024);
    4.     public List<int> triangles = new List<int>(1024);
    5.     public List<int> subTriangles = new List<int>(1024);
    6.     public List<Vector2> uv = new List<Vector2>(1024);
    7.     public List<Color32> colors = new List<Color32>(1024);
    8.  
    9.     // Functions for adding to these lists below.
    10. }
    I'm not sure which problem you're actually having, though. If you post a more specific problem I can try to help better.

    In either case, sounds like you're making great progress so far. :)
     
    Abnegation likes this.
  17. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Sweet I'll keep working on it and see if I can get it to work. I'll let you know.

    One thing that I found to be more of an immediate problem is creating blocks with this engine. How did you go about creating blocks in place of Air Blocks in the engine? I'm struggling to get the RayCasting working properly for placing them and any advice on that matter would be a large help.

    Getting a block to position correctly is pretty difficult.
     
  18. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    What exactly is the problem you're having? If the problem is that the block isn't positioned properly, then I would suspect it to be a problem when building the mesh. When creating a block, you change a block type in the array. When you rebuild the mesh based on the array, the new block in it is used instead of the air that was there previously.

    Or is it that your ray isn't hitting the right block?
     
  19. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    I have code for destroying the block, (the default code in the tutorial) the problem is that I'm trying to build a block on the side of the block that we raycast too - and since air components don't have a collider we can't raycast onto those. Just not sure where I should start, so if you could point me in the right direction that would be great!
     
  20. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    When adding a block, do your raycast as normal. When you hit something, rather than go inward into it, you'll want to go outward to the adjacent block. That's where the new block will be placed. So use the hit.normal * 0.5f and add that to the x, y, and z components of the Vector3 where you hit. Then you can round it to block coordinates to get the exact position of that block.
     
  21. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Something like this:
    Code (Csharp):
    1.  
    2.     void CreateBlock ()
    3.     {
    4.         if (Physics.Raycast (transform.position, transform.forward, out hit, 5)) {
    5.  
    6.             var block = BlockTerrain.GetBlock(hit);
    7.  
    8.            var pos = (hit.normal*0.5f) + hit.transform.position;
    9.             var wPos = new WorldPos(r(pos.x), r(pos.y), r(pos.z));
    10.  
    11.            var chunk = hit.transform.GetComponent<ChunkC>();
    12.            if (chunk == null) return;
    13.             chunk.SetBlock(wPos.x, wPos.y, wPos.z, new BlockGlass());
    14.         }
    15.     }
    16.  
    17.     /// <summary>
    18.     /// Quickly rounds and converts a float into an integer.
    19.     /// </summary>
    20.     int r(float f)
    21.     {
    22.         return (int) (Math.Round(f));
    23.     }
    24.  
    Orrr or use the optional adjacent method in Terrain.GetBlockPos? Thanks for your amazing patience here by the way.
     
  22. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Glad to help :).

    In the tutorial that you used, that's what the purpose for the adjacent was. I believe it looked something like this. (I may have modified it a bit when I used it, but it's similar enough.)

    Code (CSharp):
    1. public static Vector3 GetBlockPos(RaycastHit hit, bool adjacent)
    2. {
    3.     float x = MoveWithinBlock(hit.point.x, hit.normal.x, adjacent);
    4.     float y = MoveWithinBlock(hit.point.y, hit.normal.y, adjacent);
    5.     float z = MoveWithinBlock(hit.point.z, hit.normal.z, adjacent);
    6.  
    7.     return GetBlockPos(new Vector3(x, y, z));
    8. }
    Code (CSharp):
    1. private static float MoveWithinBlock(float pos, float normal, bool adjacent)
    2. {
    3.     if (adjacent)
    4.         pos += (normal * 0.5f);
    5.     else
    6.         pos -= (normal * 0.5f);
    7.  
    8.     return pos;
    9. }
    If adjacent is true then what I said happens: hit.normal gets multiplied by 0.5 and added to x, y, and z. Otherwise, that value is subtracted to go inward into the block that was hit (for deletion).

    However, you can do it many different ways. As long as you move the coordinates into the adjacent block enough such that when you round it, it ends up pointing to that block position.

    Edit -> Forgot to add GetBlockPos. I was using floats here instead of ints when I was using this, for whatever reason.

    Code (CSharp):
    1. public static Vector3 GetBlockPos(Vector3 pos)
    2. {
    3.     return new Vector3(Mathf.Round(pos.x), Mathf.Round(pos.y), Mathf.Round(pos.z));
    4. }
     
    Abnegation likes this.
  23. Abnegation

    Abnegation

    Joined:
    Mar 4, 2015
    Posts:
    11
    Thank you, it's working now! Project will be submitted on Thursday, but I imagine tonight will be my last night working on it (in haste). It was an issue with the rounding.

    I'll let you know if anything else comes up.
     
    JasonBricco likes this.