Do check the Width option of Child Controls Size. This option is poorly named and documented. When unchecked, the size of each child is set by its RectTransform. When checked, the size of each child is determined by its Layout Element component. If this option is checked and the children are without Layout Elements, the children will collapse to their minimum sizes. So, check the option and add Layout Elements to the children.
On each child’s Layout Element, set the Flexible Width property accordingly. Widgets with equal weights will expand at the same rate if the parent has leftover space.
If any child has a weight of 0 or doesn’t have a Flexible Width checked, it will be assigned its minimum size. If you don’t like the default minimum size, assign it on the Layout Element.
It seems like you should be able to set the minimum size in other ways—like with a Content Size Fitter on the child—but I haven’t found this to work. This would be especially nice on buttons, whose minimum size should snugly fit around their text label. I don’t see why the minimum size can’t be automatically calculated.
]]>These are the steps we followed:
using UnityEngine;
using System.IO;
public class Screenshotter : MonoBehaviour {
public RenderTexture renderTexture;
void Update() {
// Here we take screenshots when the player hits the S key, but it could
// just as well have been a button click, time elapsing, or some other
// condition.
if (Input.GetKeyDown(KeyCode.S)) {
TakeScreenshot();
}
}
public void TakeScreenshot() {
// Force a render to the target texture.
Camera.main.targetTexture = renderTexture;
Camera.main.Render();
// Texture.ReadPixels reads from whatever texture is active. Ours needs to
// be active. But let's remember the old one so we can restore it later.
RenderTexture oldRenderTexture = RenderTexture.active;
RenderTexture.active = renderTexture;
// Grab ALL of the pixels.
Texture2D raster = new Texture2D(renderTexture.width, renderTexture.height);
raster.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
raster.Apply();
// Write them to disk. Change the path and type as you see fit.
File.WriteAllBytes("screenshot.png", raster.EncodeToPNG());
// Restore previous settings.
Camera.main.targetTexture = null;
RenderTexture.active = oldRenderTexture;
Debug.Log("Screenshot saved.");
}
}
This will capture normal GameObjects visible to the camera. But it will not capture any Canvas that is a screen-space overlay. I don’t know if this is a bug or technical impossibility. Hopefully you can switch to camera or world space.
I assure you, the poster looked sharp.
]]>RectTransform can be used to achieve a couple of common multi-column UI patterns. I describe here how to achieve these using only anchors, offsets, and pivots—and not any layout groups.
If your layout requires multiple columns and each column is apportioned a percentage of the total display, set each column’s X anchors according to the percentages allotted to each. If the columns consume 50%, 30%, and 20% of the parent, then the X anchors should be set to [0, 0.5], [0.5, 0.8], and [0.8, 1].
Fill the parent vertically by setting the Y anchors of all columns to [0, 1].
If the child elements of each column should fill the column, then set the left, top, right, and bottom properties to 0.
If your layout requires two columns and one column is apportioned a fixed number of pixels W and the other column should fill the remaining space, you will need to adjust both the anchors and pivots.
First, fill the parent vertically by setting the Y anchors of both columns to [0, 1].
If the fixed-width column is on the right, set its X anchors to [1, 1]. This makes the reference point the right edge of the parent. We want to align the right edge of the column with the right edge of the parent, so set the pivot to [1, ?]. Finally, set the width of the fixed column to W pixels. Set the X position, top, and bottom to 0.
On the variable-width column, have it fill the parent horizontally by setting the X anchors to [0, 1]. The value of the pivot is irrelevant—but changing it after the other properties have been set will likely undesirably alter them. Set left, top, bottom to 0, and right to W.
If the fixed-width column is on the left, set its X anchors to [0, 0]. This makes the reference point the left edge of the parent. We want to align the left edge of the column with the left edge of the parent, so set the pivot to [0, ?]. Finally, set the width of the fixed column to W pixels. Set the X position, top, and bottom to 0.
On the variable-width column, have it fill the parent horizontally by setting the X anchors to [0, 1]. The value of the pivot is irrelevant—but changing it after the other properties have been set will likely undesirably alter them. Set top, right, and bottom to 0, and left to W.
If your layout requires three columns, with the fixed width columns on the left and right and a variable-width column in the middle, you can combine the fixed right and fixed left patterns described above.
If you want a fixed amount of padding within a column, then set left, top, right, and bottom to the desired number of pixels. If you want a percentage-based amount of padding, then add the proportion to the minimum X anchor and subtract it from the maximum X anchor.
]]>RectTransform layout system has confused me for a very long time. Not that there’s anything wrong with it. Confusion is what naturally happens when you approach a sophisticated system and just start pressing buttons. But in order to move past this stage, I’m documenting what I’ve learned after many hours of pressing.
Each widget with a RectTransform has a handful of properties that will determine how it is positioned on screen. The properties fall into two groups, which I’ll call anchors and offsets. The anchors facilitate percentage-based positioning relative to the parent container. The offsets facilitate pixel-based positioning relative to the anchors. There’s also pivot, which may bridge between the two groups.
The properties to consider first are the anchors. The anchors establish reference points within the widget’s parent container. For example, we might anchor the widget to the bottom of the parent, or have it fill the parent, or have it fill the top-right quadrant of the parent. Anchors are expressed as proportions within the parent. An anchor of [0, 0] refers to the parent’s bottom-left corner, and an anchor of [1, 1] refers to parent’s top-right corner.
The widget’s bounding box is situated relative to the anchors using the offsets.
When the anchors form a rectangle of non-zero width and height—which we’ll call the anchor box—the offsets are named left, top, right, and bottom. These values position the edges of the widget’s bounding box relative to the corresponding edge of the anchor box. A positive number insets an edge the given number of pixels. A negative number extends the widget outside of the anchor box.
When the anchors do not form a proper rectangle but rather a vertical anchor line, left becomes pos x. It still represents a horizontal offset, but “inset” is no longer a fitting term, as lines have no “in.” The right property is replaced by width, which is the distance in pixels from the widget’s left edge to its right edge. It must be positive.
When the anchors do not form a proper rectangle but rather a horizontal anchor line, top becomes pos y. It still represents a vertical offset. The bottom property is replaced by height, which is the distance in pixels from the widget’s top edge to its bottom edge. It must be positive.
When the anchors form neither a box nor a line but rather an anchor point, left becomes pos x, top becomes pos y, right becomes width, and bottom becomes height. These behave just as they do with anchor lines.
The widget’s pivot is a proportion representing the origin of the widget’s bounding box. A pivot of [0, 1] puts the origin at the widget’s top-left corner. A pivot of [0.5, 0.5] puts the origin at the center of widget.
Scaling and rotation are applied relative to the pivot. To rotate something about its own centroid, then, the pivot must be at [0.5, 0.5].
For anchor lines and anchor points, the offsets specified by pos x and pos y are not between corresponding edges as they are with anchor boxes. Rather, they are the offsets between the anchor and the widget’s pivot. The pivot plays no role when an anchor box is used.
]]>We first need a place on GitHub’s servers to store our project. This is the central location to which you will push your changes and pull your teammates’ changes. Follow these steps to create your so-called remote repository:
Now let’s jump to the local machine. You should have already created a Unity project that you want to get under version control. Please follow these steps to prepare that Unity project for hosting on GitHub:
.gitignore file with these contents:
/[Ll]ibrary/ /[Tt]emp/ /[Oo]bj/ /[Bb]uild/ /[Bb]uilds/ # Autogenerated VS/MD solution and project files *.csproj *.unityproj *.sln *.suo *.tmp *.user *.userprefs *.pidb *.booproj # Unity3D generated meta files *.pidb.meta # Unity3D Generated File On Crash Reports sysinfo.txt
Next you’ll need a Git client. I recommend GitHub Desktop for macOS and Windows. Let’s have it preside over the wedding between our remote repository and our local project. Only one team member completes the following steps:
.gitignore. Type a message like "Prime repository" in the Summary box and click Commit to master.git@github.com:username/repo.git. username and repo, of course, will be your actual username and repository name.Afterward, the rest of the team follows these steps:
Now, everyone should have a clone or checkout of the remote repository.
Henceforth, one should follow this workflow to keep files in sync:
Steady adherence to this workflow will minimize headache.
]]>One simple workaround to this problem is to duplicate your one-sided model and flip its normals to point the reverse direction. Facing one way, the viewer will see one model. Facing the other, the viewer will see the other model. Of course this duplicates geometry and leaves you in a brittle state. If you change the model, you have to change it’s counterpart. Oh, well.
Here’s how you might duplicate and flip in Blender:
Solution: To expire a game object or component based merely on time, call Destroy(thingToDestroy, nSeconds). After nSeconds elapses, thingToDestroy will disappear.
]]>Solution: Add a 3-D text game object. Add colliders to projectiles and the text. The collider on the 3-D text will automatically size itself around the bounds of the text. Implement OnTriggerEnter and update the textMesh.text on the 3-D text game object.
Problem: Suppose the number starts off at 1,000,000. When it gets down to 1, all the leading white space will still cause a trigger event. That’s dumb.
Solution: The collider doesn’t resize when the text changes; we have to do that manually.
Problem: I don’t know what numbers to use when resizing the collider.
Solution: Yeah, me neither. There’s no way to query the extent of the text, as far as I can tell.
Problem: That’s not a solution.
Solution: You’re right. A workaround is to remove the collider from the 3-D text and add a new one on. It’ll be sized appropriately.
// decrement and update label
value = value - 1;
textMesh.text = "" + value;
// remove stalely-sized collider
Destroy(GetComponent<BoxCollider>());
// add freshly-sized collider
gameObject.AddComponent<BoxCollider>();
Problem: Hey, cool! Wait. It doesn’t work. I get this error: “Can’t add component ‘BoxCollider’ to <GAME-OBJECT-NAME> because such a component is already added to the game object!”
Solution: Yeah, that problem stinks too. Destroyed objects aren’t destroyed right away. Unity waits till the end of the frame to really destroy things, or something. So, when you try to AddComponent, the previously destroyed component is still sitting there. I don’t know a good fix, but I offer another workaround. After you destroy the collider, set an isStale flag to true. In Update, if we’re stale, add your component and set the flag to false.
]]>