Search Unity

Sandbox 'terraria'

Discussion in '2D' started by steg90, Jan 28, 2016.

  1. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Hi,

    Is it possible to create a game like terraria using unity? Would it be best to use 2d or 3d project if so?

    I've already done this in Java with OpenGL, just wondering if it is feasible to do in unity.

    Thanks
     
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Absolutely. Terraria is a tile-based game, so search for Unity tile map assets/tutorials. But if you want to go beyond Terraria and make something even cooler, please see PixelSurface, which lets you create/modify/destroy the world at the pixel level (forum thread here).
     
  3. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Many thanks.

    I was hoping to code something myself a long the lines of using Mesh object and creating it procedurally:

    Code (CSharp):
    1. public List<Vector3> newVertices = new List<Vector3>();
    2. public List<int> newTriangles = new List<int>();
    3. public List<Vector2> newUV = new List<Vector2>();
    4.  
    5. void Start () {
    6.  
    7.   mesh = GetComponent<MeshFilter> ().mesh;
    8.  
    9.   float x = transform.position.x;
    10.   float y = transform.position.y;
    11.   float z = transform.position.z;
    12.  
    13.  
    14.   newVertices.Add( new Vector3 (x  , y  , z ));
    15.   newVertices.Add( new Vector3 (x + 1 , y  , z ));
    16.   newVertices.Add( new Vector3 (x + 1 , y-1  , z ));
    17.   newVertices.Add( new Vector3 (x  , y-1  , z ));
    18.  
    19.  
    20.   newTriangles.AddRange(new int[]{
    21.   0, 1, 3, // Triangle 1
    22.   1, 2, 3 // Triangle 2
    23.   });
    24.  
    25.   mesh.Clear ();
    26.   mesh.vertices = newVertices.ToArray();
    27.   mesh.triangles = newTriangles.ToArray();
    28.   mesh.Optimize ();
    29.   mesh.RecalculateNormals ();
    30. }
    31.  
    This just simply draws a quad, this would be the tile. Now I would add these to a static array, does this seem a way to go? The above could be used in 3d voxel situations also.

    Thanks
     
    Last edited: Jan 28, 2016
    TeramonGame likes this.
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes, of course if you just want to make a quad, there is a built-in primitive for that.

    But by extending the code above, you can create many quads in the same mesh, and set their UV values to appropriate locations in a texture map, to display the desired texture on each one.

    That's the basic idea behind tiling... a bit too advanced for most beginners, but I can see that it will not be much trouble for you!
     
  5. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Great :)

    So, would I use the built in quad primitive instead or keep using the code I've done above?

    What I'm planning on doing is storing these 'quads/tiles' in a big static 2d array then displaying this array to the screen. Of course, would only render what the camera can see - convert camera space to this '2d array' space.

    Thanks
     
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Well, sure, that's one way to do it. But it won't be as efficient as making a single mesh that has all the quads in it at once (i.e., if you have R rows and C columns, you'd make a mesh with R*C*2 triangles, and R*C*4 vertices).
     
  7. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Can you not add the quad primitive to this 'mesh'?

    Thanks
     
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    No. And no need to put "mesh" in quotes; I'm not using it metaphorically. :) I'm talking about the Mesh class that you already demonstrated use of above.

    Your code generated a mesh with 4 vertices and 2 triangles. Now, just put in some for-loops, and generate a mesh with (say) 400 vertices and 200 triangles.

    Or you can just throw a bunch of quad primitives up on the screen. But that won't perform very well — it's an awful lot of overhead compared to using a single mesh.
     
  9. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Thank you.

    So, let me get this, the mesh object consists of one to many of the quads I've coded up, the 'terraria' style game would be made up of many of these mesh objects - kind of like 2d chunks?

    Code (CSharp):
    1.        
    2.         Mesh mesh = new Mesh();
    3.         GetComponent<MeshFilter>().mesh = mesh;
    4.         mesh.vertices = newVertices;
    5.         mesh.uv = newUV;
    6.         mesh.triangles = newTriangles;
    7.  
     
  10. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Hmm. In a 3D Minecraft-style world, yes, that's how you would do it, because the camera can potentially turn to see quite a long distance away.

    But in a 2D world, the camera can only show so many rows and columns at once, and that's it. So you could really get by with only one mesh. There's no point in even having meshes for all the stuff that's offscreen; there is no way that stuff could ever be drawn.

    So, I would have just one mesh and simply change the textures according to where the camera is.

    But I don't want to discourage you — remember, if you feel you're getting bogged down in the details and you just want to get on with creating your game, there are several assets in the asset store that already do all this for you. (And there may be some free open-source code out there to do it too; I haven't looked.)
     
  11. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Hi, thanks again.

    One mesh, hmmmm, interesting, so, when these tiles scroll to the left for instance (Camera moves), we just update the mesh textures?

    This is what I want (This is my sandbox terrarria type game I did in Java:)

     
  12. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes, I think you've got the idea.
     
  13. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Ok, so, each one of those quads in the texture would have some way of knowing what texture to apply to them, guess would need some big 2d array to hold texture type?
     
  14. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yep! (You'll need that information anyway for your game logic.)
     
  15. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Thanks again :)

    Would it be possible to have one big mesh with all these quads or would unity render the whole of this mesh? I'm also taking it I set the project up as a 2D one :)

    If it does render the whole lot, obviously performance would be bad so have:

    1. One mesh the size of the screen (say screen width / tile size, screen height / tile size) and fill this mesh with quads.
    2. When move to the right, screen scrolls and thus mesh gets updated

    Does this sound correct?
     
    Last edited: Feb 1, 2016
  16. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    You mean, rather than having the mesh about the size of the screen, make it the size of the whole world? Well that's possible, if your world is small enough. Meshes are limited to about 65,000 vertices, so you can do the math to figure out how big that would be.

    Probably. The only difference between a 2D project and a 3D project is the default for how textures are imported, and the default scene view mode. So it really doesn't matter much.

    Yes, that's how I'd do it.
     
  17. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Have you looked for a tutorial showing how to do this? It's a pretty common need; I'd be shocked if there isn't something already out that that lays it out clearly.

    A quick google search turned up quite a few things that sound promising, but most of them are YouTube videos, which I don't have time for.

    If there really isn't anything out there, let me know. Perhaps I'll write it up for my blog.
     
  18. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Many thanks for your reply.

    One big mesh covering the whole world would work out to 16000 quads so not really that big.

    Want something along the lines of 4096x2048 :) So looks like one Mesh the size of the screen area and
    update this every frame. Guess when camera has moved tile width amount update the mesh so as to get smooth scrolling? Thus move right, if done 32 pixels, then time to update the mesh, as without this, it would just scroll tile width amount of times on each scroll, or am I missing something here?!

    Thanks again
     
  19. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Hi,

    I've looked up on the web, did find this: http://catlikecoding.com/unity/tutorials/procedural-grid/

    I guess my next concern is how to scroll the mesh.

    Thanks
     
  20. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    That's a good one, though note that the IEnumerator thing isn't needed — he's only using that so he can slow it way down, so you can actually watch the mesh being created. A neat trick, but in a real app you could just generate your mesh all at once in Start (or some method called from Start).

    I suggest you get that much working first. Then deal with scrolling an entire tile at a time, by updating the UVs. Then worry about sub-tile scrolling. It won't be that hard — you'll just shift the mesh over a few pixels (the mesh should be one tile bigger than the screen in each direction). But take it one step at a time.
     
  21. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Oh, and one more thing: I think that Catlike tutorial shares the vertices with all the adjacent quads. You'll need to use separate vertices on each quad, so that you can set different UV coordinates.

    Still surprised nobody has written (or YouTubed) this up anywhere. I know there are a half-dozen assets out there that would do it for you, but I totally get that you want to understand how to do it yourself.
     
  22. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Hi, thanks.

    Is the scrolling not done by moving the camera then? Guess it can't be really as the mesh is only so big.

    Yes, catlike tut does share the vertices on adjacent quads, good point!

    Weird really, as I did this in Java quite easily, well, the cave generation was a little harder (used cellular automata for that and also flood fill to identify the caves).

    Wanting to do it in Unity as get some nice touches, i.e. collision, lighting, physics...
     
  23. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    So how'd you do it in Java?
     
  24. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    I had a 2d array of a class called BlankEntity, array was 2000x2000, tile objects derived from this - so grassEntity, stoneEntity etc - each with their own draw method, example:

    Code (JavaScript):
    1. public void draw(Batch batch, TextureRegion[][] t) {
    2.         batch.draw(t[1][0], x << size, y << size); // 0,0
    3.     }
    I had a drawMap method which took the cameras position and drew the map (map comes from the big 2d array and only draws from where the camera is, so draws just 32 tiles wide x 20 tiles high). Scrolling, the camera basically pans left/right/up/down. All drawing is done in batches when using LibGdx so guess why I easily get 60fps.

    Q. So, scrolling needs to be done by moving the texture coordinates? Struggling to visualise this if honest!

    My quad code is as follows now (again, still one quad, need loops to generate next ones along with indexes and uv coordinates)

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [RequireComponent(typeof(MeshRenderer)),
    4. RequireComponent(typeof(MeshFilter))]
    5.  
    6. public class MeshScript : MonoBehaviour
    7. {
    8.  
    9.     public float x, y, z;
    10.  
    11.     public Mesh MainMesh { get; set; }
    12.  
    13.     private GameObject MainObject;
    14.  
    15.     Vector3[] vertices = new Vector3[4];
    16.     Vector2[] uv = new Vector2[4];
    17.     int[] triangles = new int[6];
    18.  
    19.     void Start()
    20.     {
    21.         x = 0f; y = 0f;
    22.  
    23.         vertices[0] = new Vector3(x, y, 0);             // 0
    24.         vertices[1] = new Vector3(x+1, y, 0);           // 1
    25.         vertices[2] = new Vector3(x+1, y-1, 0);         // 2
    26.         vertices[3] = new Vector3(x, y-1, 0);           // 3
    27.  
    28.         uv[0] = new Vector2(0, 0);      
    29.         uv[1] = new Vector2(1, 0);      
    30.         uv[2] = new Vector2(1, 1);
    31.         uv[3] = new Vector2(0, 1);
    32.  
    33.         triangles[0] = 0;          // 0,0
    34.         triangles[1] = 1;          // 1,0
    35.         triangles[2] = 3;          // 0,-1
    36.         triangles[3] = 1;          // 1,0
    37.         triangles[4] = 2;          // 1,-1
    38.         triangles[5] = 3;          // 0,-1
    39.  
    40.         SetupMesh();
    41.  
    42.         GetComponent<MeshFilter>().mesh = MainMesh;
    43.  
    44.     }
    45.  
    46.     void Update()
    47.     {
    48.     }
    49.  
    50.     void SetupMesh()
    51.     {
    52.         MainMesh = new Mesh();
    53.  
    54.         MainMesh.vertices = vertices;
    55.         MainMesh.uv = uv;
    56.         MainMesh.triangles = triangles;
    57.         MainMesh.RecalculateNormals();
    58.     }
    59. }
     
  25. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    OK, you say you only drew where the camera was. It sounds to me like you could only move by whole tiles then, right? That being the case, you should do the same in Unity, so you're comparing apples to apples.

    The other reason the Java code seems easier is that you were using immediate-mode drawing, which is often easier to wrap your head around than deferred drawing like what Unity uses. It is not as efficient though. In Unity (or any scene-graph API), once you have your mesh set up, it's pushed out to the video card and stays there, consuming no CPU at all until something changes. That's a big win over having the CPU drawing stuff every frame.

    And it's really not much harder. You just have to shift your thinking: instead of "what do I need to draw each frame," you need to think "what do I need to set up at the start, so I don't have to draw much each frame?"

    And yes, scrolling will be done by changing the textures. Look, you have a 2D array of tile types, right? Keeps track of the fact that there's grass at row 5 column 3, but dirt in row 5 column 4? So, you'll have a function that takes that array, and the camera position, and stuffs UV values into your mesh accordingly. To "scroll" you just change the camera position, and call this function again. That's it.

    (And remember, you're choosing to do this the hard way, because you want to learn... the easy way would be to pick one of the assets or open-source tile map implementations, and get on with making your game!)
     
  26. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Your code is off to a good start. Try this version to make a whole bunch of quads.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using System.Collections.Generic;
    4.  
    5. [RequireComponent(typeof(MeshFilter))]
    6. [RequireComponent(typeof(MeshRenderer))]
    7.  
    8. public class TileMapMesh : MonoBehaviour {
    9.     void Start() {
    10.         RebuildMesh();
    11.     }
    12.    
    13.     void Update() {
    14.    
    15.     }
    16.  
    17.     public void RebuildMesh() {
    18.         Mesh mesh = GetComponent<MeshFilter>().mesh;
    19.        
    20.         var vertices = new List<Vector3>();
    21.         var triangles = new List<int>();
    22.         var uvs = new List<Vector2>();
    23.        
    24.         for (int x=0; x<10; x++) {
    25.             for (int y=0; y<6; y++) {
    26.                 AddQuad(x, y, vertices, triangles, uvs);              
    27.             }
    28.         }
    29.        
    30.         mesh.Clear ();
    31.         mesh.vertices = vertices.ToArray();
    32.         mesh.triangles = triangles.ToArray();
    33.         mesh.SetUVs(0, uvs);
    34.         mesh.Optimize();
    35.         mesh.RecalculateNormals();
    36.         mesh.RecalculateBounds();
    37.     }
    38.    
    39.     void AddQuad(float x, float y, List<Vector3> vertices, List<int> triangles, List<Vector2> uvs) {
    40.         int vertNum = vertices.Count;
    41.         vertices.Add( new Vector3 (x,   y,   0 ));    uvs.Add(new Vector2(0, 0));
    42.         vertices.Add( new Vector3 (x+1, y,   0 ));    uvs.Add(new Vector2(1, 0));
    43.         vertices.Add( new Vector3 (x+1, y+1, 0 ));    uvs.Add(new Vector2(1, 1));
    44.         vertices.Add( new Vector3 (x  , y+1, 0 ));    uvs.Add(new Vector2(0, 1));
    45.        
    46.         triangles.AddRange(new int[]{
    47.             vertNum+0, vertNum+3, vertNum+1, // Triangle 1
    48.             vertNum+1, vertNum+3, vertNum+2 // Triangle 2
    49.         });
    50.     }
    51. }
    52.  
     
  27. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Many thanks for this.

    With the UV stuff, if I have grass tile and a stone tile next to each other, but the stone UV coordinates aren't next to the grass tile in the texture how do you map this?
     
  28. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    UV values go from 0 to 1 across the full range of the texture. So you just do the math and assign the correct U and V values for each vertex. Here's an example to get you started.

    Code (CSharp):
    1.     /// <summary>
    2.     /// Set the UV coordinates for the quad at the given row/column so
    3.     /// that it shows the given portion of the tile map texture
    4.     /// </summary>
    5.     /// <param name="row">row (y) of quad to set</param>
    6.     /// <param name="col">column (x) of quad to set</param>
    7.     /// <param name="tileMapRow">row (y) of texture map tile to use</param>
    8.     /// <param name="tileMapCol">column (x) of texture map tile to use</param>
    9.     public void SetTile(int row, int col, int tileMapRow, int tileMapCol) {
    10.         int quadNum = row * cols + col;
    11.         int vertNum = quadNum * 4;
    12.         Debug.Log("Setting " +row + "," + col + " at quad " + quadNum + ", vert " + vertNum);
    13.         float du = 1f / tileMapCols;
    14.         float dv = 1f / tileMapRows;
    15.         float u = tileMapCol * du;
    16.         float v = 1 - tileMapRow * dv;
    17.         uvs[vertNum] = new Vector2(u, v);
    18.         uvs[vertNum+1] = new Vector2(u+du, v);
    19.         uvs[vertNum+2] = new Vector2(u+du, v-dv);
    20.         uvs[vertNum+3] = new Vector2(u, v-dv);
    21.     }
     
  29. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    Thanks for that, this is what I'm trying to achieve in this image (also, I've shown what I think you do for mapping UV coordinates, this correct?)

     
    Last edited: Feb 2, 2016
  30. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes, I understand what you're trying to achieve perfectly. You can do it!
     
  31. steg90

    steg90

    Joined:
    Jan 26, 2016
    Posts:
    93
    And is this correct how mapping of UV's works? Many thanks.

    TileTextureU = 1 / AmountOfTilesOnX * ( TileNo - 1 )...
    TileTextureV = 1 / AmountOfTilesOnY * ( TileNo - 1 )...

     
    Last edited: Feb 2, 2016
  32. SCPrototype

    SCPrototype

    Joined:
    Nov 4, 2013
    Posts:
    6
    Bit of a late response, but leaving it here for people who are interested!
    Terraria was one of the main inspirations for my procedural 2D world generation tool TerraTiler2D.
    Even if you don't plan on using the tool, feel free to ask any questions about how to create Terraria-like worlds in the Discord server :)
    No need to purchase the tool. I really enjoy talking about the subject, so I would be happy to help!
     
  33. TeramonGame

    TeramonGame

    Joined:
    Oct 8, 2017
    Posts:
    21
    How would you manage interactable/animated objects? E.g. fireplace, anvil, torch...

    Option 1: Treat objects like tiles. I thought of using a single mesh texture for all objects on a chunk (similar to placing the tiles), but objects will need custom animations. I'm worried about the cost of refreshing the chunk mesh (containing objects) at 60fps for the object animations.

    Option 2: The alternative I came up with was: Each chunk has an object layer. When chunks are loaded, a pool manager reuses GameObjects from out-of-zone chunks and places them in the newly loaded chunk layer. Then GPU instancing of identical objects. However, if chunks are 64x64 the worst case scenario becomes a player placing over 4000 GameObjects on every chunk with over 6 chunks loaded on screen. Eeep! And that's not including the cache of thousands of other various GameObjects types in the pool.

    Now that I type it option 1 is correct, but how does Terraria animate trees and vines? Are they recording tree vertices and applying mesh deformation to them?

    Are animated objects just tiles on a shared mesh and they update tiles and refresh the mesh 60 times a second (or what ever the animation speed is)? This sounds correct because then the objects don't have to have individual logic. A world manager would track tile types (aka objects) in each position and run the logic when an event occurs (onload:make particles on fire, onclick:animate, onHPZero: explode).

    I think I'm answering my own questions, but I'd love to know what everyone else is going to do!
     
    Last edited: Feb 14, 2024
  34. sildeflask

    sildeflask

    Joined:
    Aug 16, 2023
    Posts:
    329
    first you dont make chunks square on a 2d game, it makes no sense because everyone use widescreen 16:9, because of that your chunks should be 16:9. For example 32x18 chunks

    you should make your objects very abstract so that they can all come from the same pool, the same object should be able to both become either an anvil or a fireplace.

    with this in mind you should go for option 2
     
    TeramonGame likes this.
  35. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,514
    Probably shader based. Much more efficient that way.



     
    TeramonGame likes this.