Changing Sprite and keeping the same size - c#

I'm writing a game using the 2d features of unity.
I'm designing a sort of inventory for the player character, and I have a gameobject with a number of placeholder images inside it. The intention is that when I actually load this gameobject, I'll replace the sprites of the placeholder images and I'll display what I want.
My issue is that when I change the sprite using code like this
var ren = item1.GetComponentInChildren<SpriteRenderer>();
ren.sprite = Resources.Load<Sprite>("DifferentSprite");
The image loaded is correct, however the scaling applies to the new sprite. The issue is that these sprites have all different sizes. So whilst the original placeholder image takes up a small square, the replacement might be tiny or massive enough to cover the whole screen depending on how the actual png was sized.
I basically want the sprite to replace the other and scale itself such that it has the same width and height as the placeholder image did. How can I do this?
EDIT - I've tried playing around with ratios. It's still not working perfectly, but its close.
var ren = item1.GetComponentInChildren<SpriteRenderer>();
Vector2 beforeSize = ren.transform.renderer.bounds.size;
ren.sprite = Resources.Load<Sprite>("Day0/LampOn");
Vector2 spriteSize = ren.sprite.bounds.size;
//Get the ratio between the wanted size and the sprite's size ratios
Vector3 scaleMultiplier = new Vector3 (beforeSize.x/spriteSize.x, beforeSize.y/spriteSize.y);
//Multiple the scale by this multiplier
ren.transform.localScale = new Vector3(ren.transform.localScale.x * scaleMultiplier.x,ren.transform.localScale.y * scaleMultiplier.y);

puzzle.Sprite = Sprite.Create(t, new Rect(0, 0, t.width, t.height),Vector2.one/2,256);
the last int is for the pixelperUnity, its the cause of changing size.
It's fixed in my project,
my default pixelPerUnit=256 and unity default = 100 so its bigger 2.56 times
tell me if it helps

How to modify width and height: use RectTransform.sizeDelta.
Example for this type of script
Vector3 originalDelta = imageToSwap.rectTransform.sizeDelta; //get the original Image's rectTransform's size delta and store it in a Vector called originalDelta
imageToSwap.sprite = newSprite; //swap out the new image
imageToSwap.rectTransform.sizeDelta = originalDelta; //size it as the old one was.
More about sizeDeltas here

I would expose two public variables in the script, with some default values:
public int placeholderWidth = 80;
public int placeholderHeight = 80;
Then, whenever you change sprites, set the width & height to those pre-defined values.

Related

How can I get screen position of a UI element?

I'm trying to get the global position of a UI element.
I've tried so many different ways to get the position but none of them seems to work. The problem comes with the anchors, as i'm moving them and not the UI element position itself (for resolution purposes), the position of the UI showing in the inspector is always 0,0,0. I also tried to get the anchoredPosition and also the Corners to make some calculations but it still not working, I always get (0,0,0) or some incoherent numbers.
Vector3 pointToTravel = Camera.main.WorldToScreenPoint(objectRectTransform.anchoredPosition);
This 'pointToTravel' variable should hold the screen position in pixels of the ui element.
anchoredPosition is the
position of the pivot of this RectTransform relative to the anchor reference point.
Since RectTransform inherits from Transform you can simply use
Vector3 pointToTravel = Camera.main.WorldToScreenPoint(objectRectTransform.position);
in order to get the absolute world position.
The position you see in the in the Inspector is always the
localPosition for Transform - relative to the parent GameObject.
or
anchoredPosition for RectTransform depending on the anchor settings - relative to the anchor reference point.
However in the case your Canvas is a Screen Space - Overlay all the contained UI (or better RectTransforms) already uses the screenspace coordinates in pixels so the WorldToScreenPoint is unnecessary or better said returns incorrect values.
So in that case you could instead simply use
Vector3 pointToTravel = objectRectTransform.position;
In order to get any information you could need about the dimensions and position of a UI Element from its RectTransform, I wrote this little utility function to convert a RectTransform to a Screen Space - Rect(For Canvas mode Screen Space - Camera).
public static Rect RectTransformToScreenSpace(RectTransform transform, Camera cam, bool cutDecimals = false)
{
var worldCorners = new Vector3[4];
var screenCorners = new Vector3[4];
transform.GetWorldCorners(worldCorners);
for (int i = 0; i < 4; i++)
{
screenCorners[i] = cam.WorldToScreenPoint(worldCorners[i]);
if (cutDecimals)
{
screenCorners[i].x = (int)screenCorners[i].x;
screenCorners[i].y = (int)screenCorners[i].y;
}
}
return new Rect(screenCorners[0].x,
screenCorners[0].y,
screenCorners[2].x - screenCorners[0].x,
screenCorners[2].y - screenCorners[0].y);
}
From this rect you can easily get all the information that the RectTransform does not give you that easily, such as Screen-Space-position and non-anchored size.

Unity3d - Get GameObject height for a rotating object

I want to get a GameObject's height. I have tried with:
this.GetComponent<MeshRenderer>().bounds.size.y
But the problem with bounds is that it works with static objects only. When you have a moving object and if the rotation of the object is not perfectly aligned, the bounds (height) is not accurate anymore because it returns the height of the bounds that is a square and if you tilt an object like a plate you get the bounds height which is not accurate to the height of the object.
It is an Axis-Aligned Bounding Box (also known as an "AABB").
Please check the image I attached, there you can see the problem with moving objects and if you rotate them how the height is not accurate anymore.
Did anyone else have this kind of problem?
Any advice on how to get the object's height accurately?
One option is to use the Game Object's Collider.
You can relatively easily find the height and width of a collider so this is one way you can measure the dimensions of a game object. The collider, for example a box collider on the game object, will have to fully encompasses the object. You can do this be resizing the collider until it snugly wraps around your game object. Then when you want to find a certain dimension of the object instead find that dimension on the collider and multiply by the Game Object's transform.scale.
Here are some examples I have tested.
Example 1:
CapsuleCollider m_Collider = GetComponent<CapsuleCollider>();
var height = m_Collider.height * transform.localScale.y;
https://docs.unity3d.com/ScriptReference/CapsuleCollider-height.html
or
Example 2:
BoxCollider m_Collider = GetComponent<BoxCollider>();
var height = m_Collider.size.y * transform.localScale.y;
var width = m_Collider.size.x * transform.localScale.x;
var breadth = m_Collider.size.z * transform.localScale.z;
https://docs.unity3d.com/ScriptReference/BoxCollider-size.html
Hopefully one of these will work for you.
If not, then a second inconvenient way you can find the height is to create 2 proxy objects as children at the top and bottom of your object. Afterwards find the scalar distance between them.
Solution 1: Use Mesh.bounds
You can get the height using Mesh.bounds. Unlike MeshRenderer.bounds (or Renderer.bounds), this is the axis-aligned bounding box of the mesh in its local space (i.e. not affected by the transform).
You still need to account for the scale of the GameObject using transform.lossyScale, as follows:
public class Example : MonoBehaviour
{
private Mesh _mesh;
private void Awake()
{
_mesh = GetComponent<MeshFilter>().mesh;
}
private void Update()
{
float height = _mesh.bounds.size.y * transform.lossyScale.y;
Debug.Log(height);
}
}
(Note when using transform.lossyScale, the value can be slightly inaccurate. This is something to note if you need extreme accuracy. See the documentation on transform.lossyScale:)
Please note that if you have a parent transform with scale and a child
that is arbitrarily rotated, the scale will be skewed. Thus scale can
not be represented correctly in a 3 component vector but only a 3x3
matrix. Such a representation is quite inconvenient to work with
however. lossyScale is a convenience property that attempts to match
the actual world scale as much as it can. If your objects are not
skewed the value will be completely correct and most likely the value
will not be very different if it contains skew too.
Solution 2 (Workaround): Cache the unrotated MeshRenderer bounds
Alternatively, if in your context you are able to instantiate the GameObject without a rotation and you only need to rotate the GameObject after initialization, one workaround solution is to cache the bounds from MeshRenderer in the Awake method, as follows:
public class Example : MonoBehaviour
{
private Bounds _initialUnrotatedBounds;
private void Awake()
{
InitializeUnrotatedBounds();
}
private void InitializeUnrotatedBounds()
{
Assert.IsTrue(transform.rotation == Quaternion.identity);
_initialUnrotatedBounds = GetComponent<MeshRenderer>().bounds;
}
private void Update()
{
// Use the cached bounds. Now, even for moving objects,
// it doesn't matter if the rotation changes
float height = _initialUnrotatedBounds.size.y;
Debug.Log(height);
}
}

aruco.net - How to find marker orientation

I am trying to use openCV.NET to read scanned forms. The problem is that sometimes the positions of the relevant regions of interest and the alignment may differ depending on the printer it was printed form and the way the user scanned the form.
So I thought I could use an ArUco marker as a reference point as there are libraries (ArUco.NET) already built to recognize them. I was hoping to find out how much the ArUco code is rotated and then rotate the form backwards by that amount to make sure the text is straight. Then I can use the center of the ArUco code as a reference point to use OCR on specific regions on the form.
I am using the following code to get the OpenGL modelViewMatrix. However, it always seems to be the same numbers no matter which angle the ArUco code is rotated. I only just started with all of these libraries but I thought that the modelViewMatrix would give me different values depending on the rotation of the marker. Why would it always be the same?
Mat cameraMatrix = new Mat(3, 3, Depth.F32, 1);
Mat distortion = new Mat(1, 4, Depth.F32, 1);
using (Mat image2 = OpenCV.Net.CV.LoadImageM("./image.tif", LoadImageFlags.Grayscale))
{
using (var detector = new MarkerDetector())
{
detector.ThresholdMethod = ThresholdMethod.AdaptiveThreshold;
detector.Param1 = 7.0;
detector.Param2 = 7.0;
detector.MinSize = 0.01f;
detector.MaxSize = 0.5f;
detector.CornerRefinement = CornerRefinementMethod.Lines;
var markerSize = 10;
IList<Marker> detectedMarkers = detector.Detect(image2, cameraMatrix, distortion);
foreach (Marker marker in detectedMarkers)
{
Console.WriteLine("Detected a marker top left at: " + marker[0].X + #" " + marker[0].Y);
//Upper 3x3 matrix of modelview matrix (0,4,8,1,5,9,2,6,10) is called rotation matrix.
double[] modelViewMatrix = marker.GetGLModelViewMatrix();
}
}
}
It looks like you have not initialized your camera parameters.
cameraMatrix and distortion are the intrinsic parameters of your camera. You can use OpenCV to find them.
This is vor OpenCV 2.4 but will help you to understand the basics:
http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html
If you have found them you should be able to get the parameters.

Wobbling texture using source rectangle

I'm having a little trouble with a texture2d I'm trying to draw in XNA. Basically, I have a power-up "cooldown fill-effect" going on. I have a texture that I'm trying to draw partially as the cooldown decreases. So, for example, at 10% cooldown done I'm drawing only 10% cooldown of of the texture (the bottom), 20% done only 20% of the bottom of the texture, and so on.
The problem I'm having is, when drawing the texture, it keeps wobbling as it fills up.
Note that below, ActiveSkillTexture is my preloaded fill texture. It's size is the size of the fully filled graphic.
InterfaceDrawer.Draw is a method that calls SpriteBatch.Draw, but does some extra stuff beforehand. For all intents and purposes, it's the same as SpriteBatch.Draw.
Scale is my scale factor, it's just a float between 0 and 1.
MyDest is a pre-calculated position for where this texture should draw (from the top-left, as usual).
Here's a snippet of code:
Rectangle NewBounds = ActiveSkillTexture.Bounds;
float cooldown = GetCooldown(ActiveSkillId);
if (cooldown > 0) //cooldown timer
{
//Code that calculated cooldown percent which I'm leaving out
if (percentdone != 1) //the percentage the cooldown is done
{
//code for fill-from bottom --
float SubHeight = ActiveSkillTexture.Height * percentremaining;
float NewHeight = ActiveSkillTexture.Height * percentdone;
NewBounds.Y += (int) SubHeight;
NewBounds.Height = (int) NewHeight;
MyDest.Y += SubHeight * Scale;
}
}
if (ActiveSkillTexture != null)
InterfaceDrawer.Draw(SpriteBatch, ActiveSkillTexture, MyDest, NewBounds, Color, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.0f);
I know you can't see it, but it's basically wobbling up and done as it fills. I tried printing out the values for the destination, the newbounds rectangle, etc. and they all seemed to consistently increase and not "sway", so I'm not sure what's going on. Interestingly enough, if I fill it from the top, it doesn't happen. But that's probably because I don't have to do math to alter the destination position each time I draw it (because it should draw from the top-left corner each time).
Any help would be greatly appreciated. Thanks!
I think this would be easier if you set the Vector2.Origin parameter of your spriteBatch.Draw as the bottom-left of your texture.
In this way you simply increase your sourceRectangle.Height, with something like this:
sourceRectangle.Height = ActiveSkillTexture.Height * percentdone;
without doing that useless math to find the destination position.

Prevent image clipping during rotation Windows Phone

I am using the WriteableBitmapEx extension method to rotate a WriteableBitmap. bi in the code is a WritableBitmap. The RotateFree method rotates the bitmap in any degree and returns a new rotated WritableBitmap. My code:
private void rotate()
{
degree += 1;
var rotate = bi.RotateFree(degree);
ImageControl.Source = rotate;
}
My problem is since the ImageControl size is fixed, it causes the rotated bitmap to be clipped. So what is the best way to prevent this? I guess I am looking for a way to resize the ImageControl during rotation to prevent clipping. Any suggestions?
UPDATE
Based on this useful info Calculate rotated rectangle size from known bounding box coordinates I think I managed to calculate the bounding box width (bx) and height(by) and resize it accordingly during the rotation
double radian = (degree / 180.0) * Math.PI;
double bx = rotate.PixelWidth * Math.Cos(radian) + rotate.PixelHeight * Math.Sin(radian);
double by = rotate.PixelWidth * Math.Sin(radian) + rotate.PixelHeight * Math.Cos(radian);
While it appears that the ImageControl width and height increases/decreases during rotation, the image is still being clipped.
UPDATE 2
Based on #Rene suggestion, I managed to prevent the clipping. Combined with the ImageControl Width/Height calculation, the image size is retained during rotation by also setting its stretch property to NONE.
The issue now is to make sure the ImageControl resize from its center so that it does not appear moving. I can include a sample project if anyone interested
UPDATE 3
For those who might be interested the final solution. This is how I do it. The result is, the image is rotated without clipping and its size is retained during rotation. In addition, the rotation appears to originate from the center.
To adjust the ImageControl position as it's resizing so that the rotation appears to originated from center, I use this code.
var translationDelta = new Point((ImageControl.ActualWidth - bx) / 2.0, (ImageControl.ActualHeight - by) / 2.0);
UpdateImagePosition(translationDelta);
ApplyPosition();
// This code update the ImageControl position on the canvas
public void UpdateImagePosition(Point delta)
{
var newPosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);
ImagePosition = newPosition;
}
//This apply the new position to make the ImageControl rotate from center
public void ApplyPosition()
{
ObjComposite.TranslateX = ImagePosition.X;
ObjComposite.TranslateY = ImagePosition.Y;
}
Use RotateFree with the crop parameter set to false: RotateFree(degree, false). Also set the ImageControl Stretch property to Uniform: Stretch="Uniform".
rene

Categories

Resources