2
\$\begingroup\$

So I come from C++ land where I've always done every little thing myself. Now I'm getting into Unity and C# and I'm ironically overwhelmed with the domain. I'm starting off with this simple little 2D game and I'm wondering... if someone can point me to the correct tools that would lead to a successful implementation of this game. Here is a quick mock up I did in paint:

enter image description here

These are the parts of Unity I've investigated:

  • Tilemap
  • Canvas
  • GridLayout
  • Prefabs
  • GameObjects (these seem fundamental to Unity, heh).

After researching these topics, and playing around a bit, here are my initial questions:

How do you approach Canvases in Unity? I could see this being split into 5 separate Canvases, one for each local body of UI. Or 1 Canvas for the game board, and 1 canvas that contains a single overlay with all of the other UI components. But then how do those components typically get positioned? Is it common practice to place by absolute coordinates?

I guess I'm getting hung up on.. how do I place my canvas for the game board in that position relative to all the other UI elements? I'm a C++ application developer so if I'm doing UI at all I'm used to layout systems. I'm not sure how common that is in this ecosystem.

Should I even use TileMap for this? It doesn't seem like I need that kind of power. My grid is finite and fits on screen. My gut reaction is that I should just instantiate a bunch of colored squares at first. But I'm not really sure the best approach to that either. GridLayout on a canvas? Absolute coords? TileMap?

What other tools should I be looking at? By tools I mean Unity concepts I didn't mention above. My initial thought is to just have a backing data structure that holds all the data for the grid, and then I just render... prefabs? based on that data? Maybe I just want someone to tell me I'm on the right track or "no you're doing it totally wrong use this other thing", heh.

\$\endgroup\$
5
\$\begingroup\$

How do you approach Canvases in Unity?

It's possible to use more than one screen-space canvas in Unity, but it's uncommon. There is usually no reason to do that, unless you want different UI elements to use different basic canvas settings.

If you want your game to support multiple resolutions, then you want to avoid absolute positioning. You usually anchor UI elements to the closest corner or the edge of their parent. That way those elements stay relative to their corners / edges when you resize the UI. This is easy to forget and hard to spot while you are using in a constant screen resolution. So when I design UIs, I usually undock the game window and frequently drag the lower-right corner of that window around to check if the UI handles that gracefully.

On the other hand, if you decided that your game will only run in one resolution, then you can of course ignore anchors and just place objects where you want them to. You can save a lot of work that way. But designing for a fixed resolution impacts the user experience on some platforms (like PC) and can make your game almost impossible to play on others (like mobile).

Should I even use TileMap for this?

The general rule of thumb in Unity is that you should use a canvas for the UI and non-canvas objects for the actual game. This is of course a rule you can break in either direction if you want to, but as a beginner you would be well-advised to work with the conventions of the engine instead of against them.

The Unity tilemap is more than just a way to arrange rectangles in a grid. It offers you:

  • A handy map editor
  • Collision detection between gameObjects and tiles in a very efficient way
  • A lightweight way to implement tiles with game mechanics
  • Pretty well-optimized rendering

These are all very useful when you have a game where the player navigates the grid in a physical manner (like in a platformer or a top-down action-adventure). But if I interpret your mockup correctly, then the game you seem to want to make seems more like a turn-based strategy game. This is a genre which usually uses more abstract game mechanics. It's also a genre which does not fit particularly well into the Unity architecture. I created a turn-based strategy game in Unity before, and compared to more action-oriented games it at times felt a bit like trying to fit a square peg into a round hole. You likely won't use the Unity physics engine and collision detection that much anyway. So yes, I guess you could implement this using a GridLayout on the UI if you wanted to.

But representing the actual game with sprite game-objects on a tilemap would likely be a much cleaner solution.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I see, thank you so much for taking the time to write this! I should have mentioned that my plan is to generate the grid with JSON data. This JSON data just includes each tile's type, move impact, etc. Does TileMap still fit with that? Or does the TileMap tool assume you'll be building it manually? \$\endgroup\$ – Josh Sanders Jul 30 at 17:13
  • 2
    \$\begingroup\$ @JoshSanders No, you can generate tilemaps procedurally. But I wonder why you are taking the detour via JSON when Unity provides such good map editing tools. \$\endgroup\$ – Philipp Jul 30 at 18:00
  • \$\begingroup\$ Heh, that's a good question. I have a desire to make everything data driven. JSON is just how I would do it in a world where I don't have an engine. So I think I just gravitated towards that because its what I know. I suppose, in that world, I would be either hand editing JSON or writing a tool that generates JSON based on tiles I place. But it looks like that's exactly what Unity does with the TileMap editor! \$\endgroup\$ – Josh Sanders Jul 30 at 18:44
  • \$\begingroup\$ RE the first para: examples of needing multiple canvases would be weird stuff like insets, or split-screens, or wanting menus at different angles (using several rotated world-space canvases). RE multiple resolutions: a cheapo method is a world-space menu with a same-size orthographic camera. That leaves the anchors for easier code-based placement. \$\endgroup\$ – Owen Reynolds Jul 31 at 0:14
4
\$\begingroup\$

You would usually use the canvas for UI elements. You can anchor your elements relative to positions and it should take care of your scaling for different dimensions if done correctly.

A gameobject and prefab are almost the same. A gameobject basically is a container of something. It can hold multiple scripts, your sprites, colliders, etc. Take a look at your (3), one of them would be basically a gameobject. A prefab is a copy template of it. You would not create 3 times the same thing, just instantiate it and assign the values to it (like image, number values, etc)

Now a tilemap just makes your life easier but you can achieve the same with a grid or even just placing multiple sprites on the board. You can find some more examples for 2D from Unity at https://github.com/Unity-Technologies/2d-extras

Additional things you might want to take a look at are scenes but basically you are good and just need to get a feel for things. If you have any problem implementing a specific part, don't hesitate to describe the problem - best with what you tried and how it failed.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Disclaimer

I myself am a noob working on my first Unity game. However, like you, mine is a 2D game with no aspirations for a 3rd dimension (a typical puzzle game).

Use the UI

Like you, I struggled to understand whether it is better to use the world space or the UI space. Everyone said to use the world, so I started out with that. After playing around with things a fair amount, especially trying to make things pixel-perfect, I realized that the Unity world is really 3D, whether you pretend it is 2D or not, and this is simply more firepower than you need for purely 2D apps. On the one hand, setting the game scale to world units and setting the world unit to be the size of your tile makes for very nice, very convenient coordinates. On the other hand, if you want things pixel-perfect, this route will make you pull your hair out. That's because if you end up drawing a line between two pixels, Unity will very helpfully interpolate for you, drawing the line two pixels wide and adjusting the color accordingly (I want to call it "anti-aliasing", but this effect happens even if you explicitly turn anti-aliasing off, so I think that refers more to curves than simple straight rectilinear lines).

What I did is switch entirely to UI elements: Panels, Buttons, ScrollView, etc. This means that everything you do is now in screen coordinates, which is both good and bad. The good thing is, it is trivial to make things pixel-perfect. The bad thing is, screen resolutions. If you only want to target a single resolution, life is easy. Of course, if you were going to do that, why are you using Unity? So, the best way to go is to create a Canvas set to "Screen Space - Overlay" and set the Scaler to "Scale With Screen Size". I also set the scaling to 0.5, so it accounts for both horizontal and vertical changes evenly. This will mostly make your game scale appropriately from tiny phones up to massive 4K TV displays.

Custom Scaling

My situation is unique in that I want the puzzle board to remain close to a fixed physical size (so zoomed in on tiny displays and zoomed out on big ones), which implies "Constant Physical Size". However, I also want to be able to zoom in and out under user control. So I removed the Canvas Scaler entirely and I simply resize the main Panel to zoom. Note that the Camera is almost completely irrelevant with this style, and you should basically ignore it.

The next issue is that I have menus which I do want to scale to the screen size. So I created a second Canvas which has the Scaler set as mentioned above. This causes my menus to always be visible at an appropriate size for the display, while letting the puzzle board expand and contract as necessary.

In this style, there are no "world coordinates". There are just screen coordinates, determined by your Canvas Scaler or your Canvas. Layering is sometimes achieved by setting the Z order, but mostly by ordering your GameObjects (rendering goes from top to bottom, so the last object drawn will always be "on top"). Of course, my Canvases also render in list order, so the Menu Canvas is drawn last, and overlays everything else.

Widgets

I suspect you can get far by using Panels for most of your controls. A button provides a very convenient OnClick() event to hook into, but I use OnPointerClick() from the EventSystem, and find out which panel the mouse is over via eventData.pointerEntered. This way, you have a single entry point for click handling, and you can get at the object clicked without having to compute its coordinates on the screen. If you are generating your tiles programmatically, I don't see a need for a GridLayout (although, I haven't used it, so take that with a grain of salt).

It looks like Area 1 on your drawing is a "game map", and Area 2-5 are what I would call the "UI" proper. If you want the game map to be scrollable, you can probably just create a ScrollRect and lay down the entire map at once, using the ScrollRect to mask all but the visible area. You should be able to lay out all the UI elements statically in the designer, so you can get the controls pixel-perfect. Note that "Pixel Perfect" is actually an option on your Canvas, which will snap objects to integer screen coordinates so you don't get the anti-aliasing problem I mentioned above. Definitely check this box if you want crisp edges.

Every widget that you create more than 1 of, you should create as a Prefab first. Make the Prefab have the properties which will be most common, and then create all the instances from the Prefab by dragging the Prefab onto your object tree. This will make all your tiles have the same size, anchors, etc. However, note that once you override a Prefab, it will stop auto-updating when you change the Prefab properties. So, you can make each object constructed from a Prefab different from the Prefab (reflected as overrides), but there is the cost to doing so, which is that it disconnects the object from Prefab updates.

Scripts

Yes, all your game logic should live in scripts. There should be [at least] one for your map, one for your characters, one for game items, one for menu actions, etc. The scripts can use dependency injection by declaring public data members which you bind in the Unity Editor (drag a game object onto the field in the property page). This makes it easy to refer to other Unity objects inside your script without making global references to the object hierarchy.

You'll notice that every script gives you a Start() and Update() method for free. This is a reminder to NOT USE THE CONSTRUCTOR. The c'tor will get called multiple times because of serialization. You want initialization to be done in Awake() or Start(). This is unfriendly to #nullable enabled code, unfortunately, but such is life.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.