- Home /
How to have a 2D world with 4194304 tiles or more?
This question is SOLVED :)
Using Unity 4.0 (free), C#, Windows 7 pro;
Currently, I am trying to make a game system for 2D side-scroller. If you know the game called Terraria, you might have a better idea of what I'm trying to do here.
This is a screen shot from Terraria. In this game, the player can dig, and place a block (or tile whatever you want to call it). Like Minecraft, but in very flat 2D world. The game has a world around 4000 wide x 1000 high blocks. (a small world that is) So my goal is to create a 2D side scroller with possibly 4 million blocks.
Anyhow, I've created a prototype using a free 2D framework called Orthello, which comes with a sprite class called OTSprite. It worked until the world exceeded 100x100 blocks.
The structure was very simple and inadequate. I simply created an empty gameObject and added the blocks. The framework handled the drawing calls and calls were reduced to 1 (other calls from blocks within the visible area were batched).
Above image is my prototype. I've made a noise based 2D terrain data generator so the ground is in kind of a weird shoe looking shape. In this picture, there are 881 blocks and it is working fine, but far far from my goals. So if you are planning on doing a similar idea like me, don't. It won't work.
Now, I'd like to talk about a solution that actually works. One idea is that I will build a 2D version of what is known as a voxel engine. Since the word voxel refers to a 3D box, I might call it a "Planel Engine", because we are adding bunch of planes instead of boxes. I know, there's no such word.
So far, thanks forum members who answered to my previous question. I hope this cleared some confusion...
So now my project has shifted from having bunch o' orthello sprites to procedural mesh creation.
I've read some websites and learned the basic of the mesh, vertices, triangles, UVs and I've made this image below so far. (no primitives used. code only)
This is 64 planes by 64 planes (or 4096 blocks), which is my hypothetical chunk size. (if not too large?? Not based on anything, so I'm open to any suggestions.)
This is my 3rd rev.
I've found some solution to my previous questions, and my world is getting closer to 4 mil tiles.
Here is 1 chunk of mesh drawn with textures.
In a loop where I determine the block type for my game's block coordinate, I set the UVs for each plane. I'm using a text texture to display just a few patterns(in the picture, I'm using transparent block, and a red block).
First I had an error where my transparent PNG texture became all white, but I fixed that with photoshop, adding an alpha layer. It says PNG24 instead of PNG32, so it is kind of confusing. Now, to turn ON/OFF the block, I just need to find a block in my game block coordinates, and change preset UVs from A to B.
Regarding the chunk size. 1 mesh can ONLY HOLD, up to 65000 vertices. I got this error.
Mesh.vertices is too large. A mesh may not have more than 65000 vertices.
So, from this error, my chunk is now determined as 100x100 blocks. ish. This may change. Now, my last question is, how should I make an entire world, instead of a chunk. And I think it is already given to me by @Bunnybomb7670 !
Thank you guys, I really appreciate all the comments and answers.
Rev4.
My code was getting quite messy, so I was re-factoring my code. And I added a different solution to my mesh creation regarding the empty planes.
My first approach was to ... Set a transparent texture UVs for empty space. My second approach is not adding the vertices/UVs/triangles the whole set, but add vertices count up, so it can be re-filled later on (hopefully).
This is the picture from my second approach. No mesh in the transparent area.
Now re-factoring is done, I can move on to chunks... will post soon.
Rev 5 ** Took me little longer than I expected... I implemented the chunks. Now they are all good to go! I also implemented collision for character movement test. Now each tile(or block) is ready to be walked. Thanks guys!!
Does this framework use per tile gameobjects? If your going to try and have 4194304 gameobjects, your not going to be able to do that, even having 1000 is bad, your best off learning meshes because you can create massive terrains, even 2D which use 1 draw call and you run at better framerates.
Draw calls are important but not everything. For example, drawing 1000 plane meshes with two triangles, even when batched, will be significantly slower than drawing a single mesh (with a single material) with 2000 triangles.
I don't think using multiple containers would have an impact on performance. Unity culls none visible geometry before the batching process so the amount geometry you're rendering would be the same regardless.
@$$anonymous$$, could you state what you are trying to do ?
There is a common, well-known problem on this board where the discussion goes off on an arcane technical point about an unrelated issue, when actually the real problem at hand can be simply solved. Cheers.
You could also create a mesh based off a 2D array of values, 64x64 in length and then make a single mesh, feeding the 2D array and use that to deter$$anonymous$$e which block is what by storing an integer as a block ID. if it is not 0 ( air ) then you will render it. This is the way I made this : Video which, yes it is 3D but you can convert it into 2D easily.
Answer by Bunnybomb7670 · May 18, 2013 at 09:16 AM
Well, firstly, you need to start by initializing an array of however many blocks in your "chunk" and store the ID values in an array. You may also want to store the chunk X and Y size in variables if you want to change your chunk size alot.
public int[,] voxels = new int[32,32];
You need to then assign each one a value dependant on your generation, so for example, if you wanted to use your generator, you can do :
voxels[posx,posy]=1;
which sets that value to 1. Now you will need to cycle through your entire mesh with a for loop and some mesh code:
for( int i = 0;i<32;i++){
for( int j = 0;j<32;j++){
if(voxels[i,j]!=0){ // we have a solid block
// RENDER A FACE
}
}
}
Looking back at that code there, you can see that we are looping through each value of the array and checking their ID value, if its 0, we will not render a face and therefore emptying that space.
Now, what if you want to add colours? well, its not that hard, you can set the vertex colours of the mesh and use a vertex colour shader to render the faces the colours you want. To do this, you can have an extra array of block ID references to get the colour of the block, for example,
public Color[] voxeldata = new Color[256];
this array of colours will store 256 different colours, you can have a function to assign lots of these upon start, calling a function like this at start :
public void setDataValues() {
voxeldata[0]= new Color(0.1,0.2,0.3);
voxeldata[1]= new Color(0.5,0.4,0.5);
voxeldata[2]= new Color(0.2,0.6,0.2);
//.... and so on
}
Wondering why im using decimal values? This is because a Color stores the values differently, to convert a 0-255 RGB colour into a Color you need to do R/255,G/255,B/255 or use a Color32(255,255,255,255) which goes Red Green Blue Alpha ( transparency ).
now, you can assign a meshes vertex colours easy during the mesh generation by also storing the colors in an array and calling
Mesh.colors = arrayname
Now, you can ( hopefully ) create your own 2d terraria like system in this way!
for placing and destroying a block, you will need to use this system and catch the block that was hit, compare its coordinate and set the coordinate to 0 or if placing a block, set it to the ID value and update the mesh. If your thinking that updating the whole mesh is inefficient, It is by far more efficient than trying to find the vertices that you have edited because a mesh is a lot harder to search through than you think, so your best off updating the whole mesh after editing a block.
public void addBlock (Vector2 Position,int ID) {
// Position = where the block was placed
voxels[Position.x,Position.y] = ID;
UpdateWholeMeshFunctionHere();
}
would be a possible way for this. After this, you may also want to do colliders? I do colliders by doing this function :
Mesh mesh = GetComponent<MeshFilter>().mesh;
// after the mesh is set, then it calls :
updateCollider(mesh);
// this function does this :
public void updateCollider (Mesh mesh) {
GetComponent<MeshCollider>().sharedMesh = null;
GetComponent<MeshCollider>().sharedMesh = mesh;
}
The reason why I set it to null first is because , if this is not the first meshcollider update, it will see that the sharedmesh is already the mesh and wont update it, so instead, I assign it to null and re assign it. Im not sure how your collision will be working, but this method wont really work with a flat terrain, so I guess you'll have to figure that out yourself.
I hope this code has helped you.
i just wish the voxelform guys would come back and release their product. why oh why oh why doesn't someone just post "complete utterly working $$anonymous$$ecraft copy" to the asset store?
every single $$anonymous$$iecraft copy on the app store makes a fortune. sure, everyone wants to make $$anonymous$$iecraft. why not? couldn't apple just add "CoreCraft" or something to freaking quiet this whole issue??
I have the code to do that, Look at this link :
http://www.youtube.com/watch?v=0PCpAnWFD$$anonymous$$g&list=UUtP0f1-_O-9N3eE5VImp42Q∈dex=2
$$anonymous$$y voxel engine I made in 2 days, ofcourse ive improved it since, but it shows you what you can make in a few days with unity and basic knowledge of voxels.
You should never sell anything for less than $200. It is utterly pointless.
There was a famous case where I had a huge client on the app store with a $1 product. I told them "click to change it to $2". There was a huge round of well-paid marketing expert morons etc with spreadsheets and charts. There was discussion about what the new income would be etc etc. (Newflash - how could you know?) In the end they changed to $2 ... and sold precisely the same number per week. Absolutely no change. Then I told them to change to $5. You can imagine what happened.
$$anonymous$$oney is an illusion. You don't need to be Richard Bach to understand this. Hundreds of millions of ten year old kids have $900 Samsung S5 phones. People who worry about price points, are trivially silly, they are wasting the short days of their lives. Any price you are thinking, always double it.
Anyway, if you're aim in life is to make money .. just go sell shoes. You can always make a huge amount of money - anywhere, anytime - by opening a shoe shop. It's just Not That Hard. The one and only reason anyone is on this site is for Love, because it's fun. And you want to serve people, give them the games they want, since games are the current mental paradigm for all living people.
You will never, ever, get anywhere thinking about money. Just put your voxel-whatever on the asset store for $200 (a standard price) and stop thinking about money. Think only about your voxels. I haven't thought about money since I was a child, since I was 13. It is an illusion, just don't think about it. Just randomly change the price of your voxel asset from $1 to $500 to free every day, as an exercise ......... so as to get the thought out of your head. if you are interested in voxels, think about voxels and nothing else. If you want to "make" money go open a shoe shop, or just print money. (Any fool can print money these days.)
In life if you're ever not 100% sure, about anything, just go to bed, or eat $$anonymous$$ars Bars, or whatever. Don't worry too much because within 100 years you'll be dead and all this annoying life trouble will be gone. Just try to watch TV or something until then. But setting that aside put everything you have in to the project and Start working Now. Yes, put it on the app store, whatever, yes charge $30. Great choice - waste no more time thinking about those details. Don't reply to the email because it's a waste of your time. Don't think about $$anonymous$$others' Day because that's also a waste of your time. People are literally waiting for your asset to be on sale. Just get back to your text editor. So, in the next split second you should open your text editor and work, because you'll be dead within 100 years. Note that when you're dead, they bury or burn you and that's it. So again, move your hand so that the mouse moves so that the virtual pointer moves on the LCD and open the text editor - Go!!!! You have* full permission* from the universe to make an awesome freaking product in the store. Your idea is great. Enjoy yourself, it's impossible not to.
Wow man, i never thought that i can get philosophy in Unity Answers Great Comment ^^
Answer by whydoidoit · May 18, 2013 at 07:31 AM
I'm guessing the player can never see all 4m blocks at the same time? You need to divide this up into chunks and dynamically load them as you move around, keeping less on screen at a time.
Static batching takes time when the level loads and uses a lot of memory as it has to create a single mesh from all of your scene meshes. Dynamic batching does that every frame. Just skip that whole bit. Wasting memory and processing in having a transform etc for each tile when the tiles are uniformly laid out is expensive and pointless.
You should skip using the Orthello framework for this part and generate your own meshes/uvs either in the editor or at runtime for blocks of say 100x100 tiles (depends totally on the size of the tiles) and then dynamically load those blocks when they are in view.
10,000 x 10,000 = 100,000,000 tiles. That's going to take a huge amount of memory if you have them all loaded at once! Just a transform for each of those would be GBs of memory.
You could probably use your Orthello sprite atlases to actual UV the meshes for the tile blocks.
@whydoidoit: Thank you for your comment! No, players should never see 4 mil blocks ;)
Chunks:
As I look closer to the voxel engine, I realized that I need to implement chunks. I was hoping this could be achieved by ... gameobject parent and child objects, but here it is, I was very wrong. :(
Create my own meshes/uvs:
This seems like my next challenge. I'll take a look again at the sprite atlases as well.
Yeah you could do it that way, but you will always be suffering. The best thing with voxels is that their position is implicit in the way they're laid out. So if you've got a 10x10 2d array and you know the position of any of the tiles then you can imply the position of any other tile without storing it. Obviously the same goes in 3d.
@whydoidoit: I see. So far, my terrain data is a simple [x,y] each filled with a blocktype value, so the raw data itself should be small enough and efficient.
The part I'm not so sure is creating the meshes/uvs. I've never done this part of program$$anonymous$$g before. Right now, I'm looking at ProceduralPrimitives : plane Am I on the right track here?
$$anonymous$$esh creation is just a matter of creating vertices and assigning them to triangles. Start simple with a single square. Create 4 vertex positions, assign uvs for the vertices 0,0 for 1 corner & 1,1 for the other diagonal etc. Assign normals that are perpendicular to the face and assign them to triangles so that seen from the front they are clockwise (you use the vertex index to assign 6 triangle entries for your 4 vertices)
Answer by Phong · May 26, 2013 at 05:37 PM
I have an asset in the Asset Store called MeshBaker that would be useful for combining tiles into larger chunks. You can combine the tiles in advance or dynamically at runtime. It may help with your problem.
Would'nt this cause issues when he tries to access individual chunks though, because he will need to be dynamically editing the mesh very frequently because of adding and removing blocks and so on.
Its very easy and fast to add to and remove from a combined mesh with the API. I am currently working on an LOD system built on top of meshbaker that swaps meshes in and out several times a second. With 4 million tiles though it still may be necessary to cache and load off-screen sections.