Hi guys im trying to make a tooltip window for some items in the game im making. And for the love of god i cant figure out how to setup a screen based offset for the tooltip. Atm im using this (code no.1) after 2 sec turns on the tooltip gameobject (called StatDisplay) and it works on 16x9 resolution but after changing the resolution the tooltip is too far away from the main object. I tried it with mouse position (code 2) and im running into the same problem with the offset, its decreasing on higher resolutions and in smaller ones the offset is too big. Any ideas on how to fix this ? (i was thinking about making a variable that changes based on screen size but cant figure out a way...)
Code 1:
bool hovering = false;
public GameObject StatDisplay;
IEnumerator StartCountdown()
{
RectTransform rc = StatDisplay.GetComponent<RectTransform>();
RectTransform rc2 = this.GetComponent<RectTransform>();
int time = 1;
while ((time > 0) && (hovering == true))
{
yield return new WaitForSeconds(1.0f);
time--;
}
if ((time == 0) && (hovering == true))
{
if (Input.mousePosition.x > (Screen.width / 2))
{
if (Input.mousePosition.y > (Screen.height / 2))
{
//Debug.Log("Top right");
StatDisplay.transform.position = new Vector3(-rc.rect.width / 2 + this.transform.position.x + rc2.rect.width / 2, -rc.rect.height / 2 + this.transform.position.y - rc2.rect.height / 2, 0);
}
else
{
//Debug.Log("Bottom right");
StatDisplay.transform.position = new Vector3(- rc.rect.width / 2 + this.transform.position.x + rc2.rect.width / 2, rc.rect.height / 2 + this.transform.position.y + rc2.rect.height / 2, 0);
}
}
else
{
if (Input.mousePosition.y > (Screen.height / 2))
{
//Debug.Log("Top Left");
StatDisplay.transform.position = new Vector3(rc.rect.width / 2 + this.transform.position.x - rc2.rect.width / 2, - rc.rect.height / 2 + this.transform.position.y - rc2.rect.height / 2, 0);
}
else
{
//Debug.Log("Bottom Left");
StatDisplay.transform.position = new Vector3(rc.rect.width / 2 + this.transform.position.x - rc2.rect.width / 2, rc.rect.height / 2 + this.transform.position.y + rc2.rect.height / 2, 0);
}
}
StatDisplay.SetActive(true);
}
}
public void Enter()
{
hovering = true;
StartCoroutine(StartCountdown());
}
public void Exit()
{
hovering = false;
StatDisplay.SetActive(false);
}
Code 2:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HoverAtCursorPosition : MonoBehaviour
{
RectTransform rt;
CanvasGroup cg;
float Obwidth;
float Obheight;
Vector3 MousePoz = new Vector3();
private void Start()
{
rt = GetComponent<RectTransform>();
cg = GetComponent<CanvasGroup>();
}
private void OnEnable()
{
PositionFunction();
}
private void OnDisable()
{
cg.alpha = 0;
}
void Update()
{
Obwidth = rt.rect.width + 20f;
Obheight = rt.rect.height + 20f;
//Debug.Log("X: " + Screen.width / rt.rect.width);
//Debug.Log("Y: " + Screen.height / rt.rect.height);
MousePoz = Camera.main.ScreenToViewportPoint(Input.mousePosition);
PositionFunction();
if (cg.alpha != 1)
{
if (gameObject.activeSelf == true)
{
cg.alpha += 0.1f;
}
}
}
void PositionFunction()
{
if (Input.mousePosition.x > (Screen.width / 2))
{
if (Input.mousePosition.y > (Screen.height / 2))
{
//Debug.Log("Top right");
transform.position = new Vector3(MousePoz.x * Screen.width - Obwidth / 2, MousePoz.y * Screen.height - Obheight / 2);
}
else
{
//Debug.Log("Bottom right");
transform.position = new Vector3(MousePoz.x * Screen.width - Obwidth / 2, MousePoz.y * Screen.height + Obheight / 2);
}
}
else
{
if (Input.mousePosition.y > (Screen.height / 2))
{
//Debug.Log("Top Left");
transform.position = new Vector3(MousePoz.x * Screen.width + Obwidth / 2, MousePoz.y * Screen.height - Obheight / 2);
}
else
{
//Debug.Log("Bottom Left");
transform.position = new Vector3(MousePoz.x * Screen.width + Obwidth / 2, MousePoz.y * Screen.height + Obheight / 2);
}
}
}
}
class level var:
bool isVis=false;
void OnGUI(){
if (isVis)
{
GUI.Box(new Rect(Input.mousePosition.x + 2, Screen.height - Input.mousePosition.y + 2, 128, 72), "Tool Tip Text");
}
}
private void OnMouseEnter()
{
isVis = true;
}
private void OnMouseExit()
{
isVis = false;
}
throw this on your object you want a tool tip for, then you can make some adjustments. this will give you a box with the text "Tool Tip Text" at the mouse position, while you are mouse over the object. your object will need a collider.
more to the point of your question: you are looking for this line:
new Rect(Input.mousePosition.x + 2, Screen.height - Input.mousePosition.y + 2, 128, 72)
to get the correct y position in your endeavor you need to subtract the mousePosition.y from Screen.Height
its also important to note the +2, as if you draw right at the mouse it will stutter between the tooltip and the cursor
as i said below if you store the tool tip rect in a variable, thats set in Update()
in your OnGUI you can do this:
GUI.Box(myRect, "Tool Tip Text");
GUI.DrawTexture(new Rect(myRect.x,myRect.y+20,64,64),Resources.Load("Sprites/sample.png")) ;
(the load method will grab the image, sample.png from your Resources/Sprites folder, if you dont have that folder you can make it, or change the path, but the path has to be in a folder called Resources
you can also do
GUI.Label(new Rect(myRect.x,myRect.y+84,myRect.width,24),"Item Description")) ;
the best part is, you can load a custom GUI skin, and set your own styles, and change each GUI element dynamically. this means if you want a white title, and a gold description and a border around your image, it's a simple thing. if you want some sample styles let me know!
Ok while playing around i found a way to fix my code
rt = this.GetComponent<RectTransform>();
float RealWidth = rt.rect.width / Screen.width;
float RealHeight = rt.rect.height / Screen.height;
rt is on the component thats has its anchor set to stretch on x and y (Rect transform > icon on the left > alt + icon at bottom right)
it seems like the real screen width and the width of the rect of an object are not the same even if its stretched to fit the whole screen. its always off by +-10-20% (not sure if its bc of canvas settings or what)... well whatever
new Vector3(Input.mousePosition.x * RealWidth, Input.mousePosition.y * RealHeight, 0)
this vector will give you the right position on all the resolutions. well it does for me... it might help someone in with a similar problem
Related
I am drawing lines on a canvas using the 'UIVertex' struct and I would like to be able to detect click events on the lines I have drawn.
Here is how I draw lines (largely inspired from this tutorial => https://www.youtube.com/watch?v=--LB7URk60A):
void DrawVerticesForPoint(Vector2 point, float angle, VertexHelper vh)
{
vertex = UIVertex.simpleVert;
//vertex.color = Color.red;
vertex.position = Quaternion.Euler(0, 0, angle) * new Vector3(-thickness / 2, 0);
vertex.position += new Vector3(unitWidth * point.x, unitHeight * point.y);
vh.AddVert(vertex);
vertex.position = Quaternion.Euler(0, 0, angle) * new Vector3(thickness / 2, 0);
vertex.position += new Vector3(unitWidth * point.x, unitHeight * point.y);
vh.AddVert(vertex);
}
Any idea?
Here is the solution I have found thanks to this post:
public bool PointIsOnLine(Vector3 point, UILineRenderer line)
{
Vector3 point1 = line.points[0];
Vector3 point2 = line.points[1];
var dirNorm = (point2 - point1).normalized;
var t = Vector2.Dot(point - point1, dirNorm);
var tClamped = Mathf.Clamp(t, 0, (point2 - point1).magnitude);
var closestPoint = point1 + dirNorm * tClamped;
var dist = Vector2.Distance(point, closestPoint);
if(dist < line.thickness / 2)
{
return true;
}
return false;
}
The UILineRenderer class is the class I have which represents my lines.
line.points[0] and line.points[1] contain the coordinates of the two points which determine the line length and position. line.thickness is the... thickness of the line :O
I am very new to OpenGL and am using the latest version of OpenTK with C#.
My camera class currently does the following,
public Matrix4 GetProjectionMatrix()
{
return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f);
}
public Matrix4 GetViewMatrix()
{
Vector3 lookAt = new Vector3(myObject.Pos.X, myObject.Pos.Y, myObject.Pos.Z);
return Matrix4.LookAt(Position, lookAt, _up);
}
I have a slightly weird use case, where my game window will be long, something like a 4:12 ratio, and it will present a long object. From my reading, online the best way to present this the way I want is to do a lense shift (Oblique Frustum).
I've seen articles online on how to do this, namely:
http://www.terathon.com/code/oblique.html
https://docs.unity3d.com/Manual/ObliqueFrustum.html
But I am having trouble translating this to OpenTk.
Was wondering if anyone on here has done something similar to this in OpenTK.
EDIT:
This kind of worked, but not quite what I was looking for :(
private float sgn(float a)
{
if (a > 0.0F) return (1.0F);
if (a < 0.0F) return (-1.0F);
return (0.0F);
}
public Matrix4 CreatePerspectiveFieldOfView(Matrix4 projectionMatrix)
{
Vector4 clipPlane = new Vector4(0.0f, 0.7f, 1.0f , 1.0f);
Vector4 q = new Vector4
{
X = (sgn(clipPlane.X) + projectionMatrix.M13) / projectionMatrix.M11,
Y = (sgn(clipPlane.Y) + projectionMatrix.M23) / projectionMatrix.M22,
Z = -1.0f,
W = (1.0F + projectionMatrix.M33) / projectionMatrix.M34
};
Vector4 c = clipPlane * (2.0F / Vector4.Dot(clipPlane, q));
projectionMatrix.M31 = c.X;
projectionMatrix.M32 = c.Y;
projectionMatrix.M33 = c.Z + 1.0f;
projectionMatrix.M34 = c.W;
return projectionMatrix;
}
EDIT 2:
Basically what I am looking to do, is bring the look at point closer to the edge of the frustum like so:
There are some obvious issues. OpenGL matrices are column major order. Hence, i is the column and j is the row for the Mij properties of Matrix4:
In the following columns are from the top to the bottom and rows are form the left to the right, because that is the representation of the fields of the matrix in memory and how a matrix "looks" in the debuger:
row1 row2 row3 row4 indices
column1 (M11, M12, M13, M14) ( 0, 1, 2, 3)
column2 (M21, M22, M23, M24) ( 4, 5, 6, 7)
column3 (M31, M32, M33, M34) ( 8, 9, 10, 11)
column4 (M41, M42, M43, M44) (12, 13, 14, 15)
Thus
q.x = (sgn(clipPlane.x) + matrix[8]) / matrix[0];
q.y = (sgn(clipPlane.y) + matrix[9]) / matrix[5];
q.z = -1.0F;
q.w = (1.0F + matrix[10]) / matrix[14];
has to be translated to:
Vector4 q = new Vector4
{
X = (sgn(clipPlane.X) + projectionMatrix.M31) / projectionMatrix.M11,
Y = (sgn(clipPlane.Y) + projectionMatrix.M32) / projectionMatrix.M22,
Z = -1.0f,
W = (1.0f + projectionMatrix.M33) / projectionMatrix.M43
};
and
```cpp
matrix[2] = c.x;
matrix[6] = c.y;
matrix[10] = c.z + 1.0F;
matrix[14] = c.w;
has to be
projectionMatrix.M13 = c.X;
projectionMatrix.M23 = c.Y;
projectionMatrix.M33 = c.Z + 1.0f;
projectionMatrix.M43 = c.W;
If you want an asymmetric perspective projection, then consider to to create the perojection matrix by Matrix4.CreatePerspectiveOffCenter.
public Matrix4 GetProjectionMatrix()
{
var offset_x = -0.0f;
var offset_y = -0.005f; // just for instance
var tan_fov_2 = Math.Tan(_fov / 2.0);
var near = 0.01f;
var far = 100.0f;
var left = -near * AspectRatio * tan_fov_2 + offset_x;
var right = near * AspectRatio * tan_fov_2 + offset_x;
var bottom = -near * tan_fov_2 + offset_y;
var top = near * tan_fov_2 + offset_y;
return Matrix4.CreatePerspectiveOffCenter(left, right, bottom, top, near, far);
}
I'm using Editor Window maybe that's the problem ?
The idea is when connecting two nodes is also to make an arrow at the end position that will show the connecting flow direction.
In the screenshot when I'm connecting two nodes for example Window 0 to Window 1
So there should be an arrow at the end of the line near Window 1 showing indicating that Window 0 is connected to Window 1 so the flow is from Window 0 to Window 1.
But it's not drawing any ArrowHandleCap.
I don't mind to draw another simple white arrow at the end position but it's not working at all for now. Not drawing an arrow at all.
This is my Editor Window code :
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.Graphs;
using UnityEngine.UI;
public class NodeEditor : EditorWindow
{
List<Rect> windows = new List<Rect>();
List<int> windowsToAttach = new List<int>();
List<int> attachedWindows = new List<int>();
int tab = 0;
float size = 10f;
[MenuItem("Window/Node editor")]
static void ShowEditor()
{
const int width = 600;
const int height = 600;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<NodeEditor>().position = new Rect(x, y, width, height);
}
void OnGUI()
{
Rect graphPosition = new Rect(0f, 0f, position.width, position.height);
GraphBackground.DrawGraphBackground(graphPosition, graphPosition);
int selected = 0;
string[] options = new string[]
{
"Option1", "Option2", "Option3",
};
selected = EditorGUILayout.Popup("Label", selected, options);
if (windowsToAttach.Count == 2)
{
attachedWindows.Add(windowsToAttach[0]);
attachedWindows.Add(windowsToAttach[1]);
windowsToAttach = new List<int>();
}
if (attachedWindows.Count >= 2)
{
for (int i = 0; i < attachedWindows.Count; i += 2)
{
DrawNodeCurve(windows[attachedWindows[i]], windows[attachedWindows[i + 1]]);
}
}
BeginWindows();
if (GUILayout.Button("Create Node"))
{
windows.Add(new Rect(10, 10, 200, 40));
}
for (int i = 0; i < windows.Count; i++)
{
windows[i] = GUI.Window(i, windows[i], DrawNodeWindow, "Window " + i);
}
EndWindows();
}
void DrawNodeWindow(int id)
{
if (GUILayout.Button("Attach"))
{
windowsToAttach.Add(id);
}
GUI.DragWindow();
}
void DrawNodeCurve(Rect start, Rect end)
{
Vector3 startPos = new Vector3(start.x + start.width, start.y + start.height / 2, 0);
Vector3 endPos = new Vector3(end.x, end.y + end.height / 2, 0);
Vector3 startTan = startPos + Vector3.right * 50;
Vector3 endTan = endPos + Vector3.left * 50;
Color shadowCol = new Color(255, 255, 255);
for (int i = 0; i < 3; i++)
{// Draw a shadow
//Handles.DrawBezier(startPos, endPos, startTan, endTan, shadowCol, null, (i + 1) * 5);
}
Handles.DrawBezier(startPos, endPos, startTan, endTan, Color.white, null, 5);
Handles.color = Handles.xAxisColor;
Handles.ArrowHandleCap(0, endPos, Quaternion.LookRotation(Vector3.right), size, EventType.Repaint);
}
}
The problem is that the arrow is always behind the e.g. Window 0 since you call DrawNodeWindow always after DrawNodeCurve.
It happens because the arrow is always drawen starting from the endPos to the right direction with length = size so you always overlay it with the window later ... you have to change
// move your endpos to the left by size
var endPos = new Vector3(end.x - size, end.y + end.height / 2 , 0);
in order to have it start size pixels left before the actual end.x position.
However, as you can see it is still really small since it usually is used to display the arrow in 3D space - not using Pixel coordinates .. you might have to tweak arround or use something completely different.
How about e.g. simply using a GUI.DrawTexture instead with a given Arrow sprite?
// assign this as default reference via the Inspector for that script
[SerializeField] private Texture2D aTexture;
// ...
// since the drawTexture needs a rect which is not centered on the height anymore
// you have to use endPos.y - size / 2 for the Y start position of the texture
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
like mentioned in the comment for all serialized fields in Unity you can already reference default assets for the script itself (in contrary to doing it for each instance like for MonoBehaviours) so with the NodeEditor script selected simply reference a downloaded arrow texture
If using a white arrow as texture you could then still change its color using
var color = GUI.color;
GUI.color = Handles.xAxisColor;
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
GUI.color = color;
Result
P.S.: Arrow icon usedfor the example: https://iconsplace.com/red-icons/arrow-icon-14 you can already change the color directly on that page before downloading the icon ;)
I have image which i add in my form.How can i fill part of image?
I have this
What I'm trying to achieve:
To floodfill an area you need a foodfill routine and very little else.
See this example:
It uses two pictureboxes, also a label to display the chosen color.
And two mouse click events, one to pick the color:
private void pictureBoxPalette_MouseClick(object sender, MouseEventArgs e)
{
Point sPt = scaledPoint(pictureBoxPalette, e.Location);
lbl_color.BackColor = ((Bitmap)pictureBoxPalette.Image).GetPixel(sPt.X, sPt.Y);
}
..and one to call the fill:
private void pictureBoxTgt_MouseClick(object sender, MouseEventArgs e)
{
Point sPt = scaledPoint(pictureBoxTgt, e.Location);
Bitmap bmp = (Bitmap)pictureBoxTgt.Image;
Color c0 = bmp.GetPixel(sPt.X, sPt.Y);
Fill4(bmp, sPt, c0, lbl_color.BackColor);
pictureBoxTgt.Image = bmp;
}
The Floodfill routine is taken from this post; it is basically a direct implementation of a wikipedia algorithm..:
static void Fill4(Bitmap bmp, Point pt, Color c0, Color c1)
{
Color cx = bmp.GetPixel(pt.X, pt.Y);
if (cx.GetBrightness() < 0.01f) return; // optional, to prevent filling a black grid
Rectangle bmpRect = new Rectangle(Point.Empty, bmp.Size);
Stack<Point> stack = new Stack<Point>();
int x0 = pt.X;
int y0 = pt.Y;
stack.Push(new Point(x0, y0) );
while (stack.Any() )
{
Point p = stack.Pop();
if (!bmpRect.Contains(p)) continue;
cx = bmp.GetPixel(p.X, p.Y);
if (cx.ToArgb() == c0.ToArgb()) //*
{
bmp.SetPixel(p.X, p.Y, c1);
stack.Push(new Point(p.X, p.Y + 1));
stack.Push(new Point(p.X, p.Y - 1));
stack.Push(new Point(p.X + 1, p.Y));
stack.Push(new Point(p.X - 1, p.Y));
}
}
}
Note: (*) Color equality will fail if one of the colors is a known or named color. So we need to convert to a common format..
Update
I have updated the code to include a function that will scale a mouse click location to an image pixel point; now it will work with SizeMode=StretchImage as well, so you can work on the whole image..
static Point scaledPoint(PictureBox pb, Point pt)
{
float scaleX = 1f * pb.Image.Width / pb.ClientSize.Width;
float scaleY = 1f * pb.Image.Height / pb.ClientSize.Height;
return new Point((int)(pt.X * scaleX), (int)(pt.Y * scaleY));
}
Of course you can then save the Image.
Note that your original image is 4bpp and must be converted to 24bpp or better before coloring..
Also note that for SizeMode=Zoom the calculations are a little more involved. Here is an example that should work with any SizeMode.:
static Point scaledPoint(PictureBox pbox, Point pt)
{
Size si = pbox.Image.Size;
Size sp = pbox.ClientSize;
int left = 0;
int top = 0;
if (pbox.SizeMode == PictureBoxSizeMode.Normal ||
pbox.SizeMode == PictureBoxSizeMode.AutoSize) return pt;
if (pbox.SizeMode == PictureBoxSizeMode.CenterImage)
{
left = (sp.Width - si.Width) / 2;
top = (sp.Height - si.Height) / 2;
return new Point(pt.X - left, pt.Y - top);
}
if (pbox.SizeMode == PictureBoxSizeMode.Zoom)
{
if (1f * si.Width / si.Height < 1f * sp.Width / sp.Height)
left = (sp.Width - si.Width * sp.Height / si.Height) / 2;
else
top = (sp.Height - si.Height * sp.Width / si.Width) / 2;
}
pt = new Point(pt.X - left, pt.Y - top);
float scaleX = 1f * pbox.Image.Width / (pbox.ClientSize.Width - 2 * left) ;
float scaleY = 1f * pbox.Image.Height / (pbox.ClientSize.Height - 2 * top);
return new Point((int)(pt.X * scaleX), (int)(pt.Y * scaleY));
}
I'm currently trying to create a container in Unity with multiple quads inside, and am looking for a way to stop parts of the quad that fall outside of the containers bounds from being rendered?
I just spent twenty minutes trying to fiqure out how to explain the problem properly (and failed) so created this pretty picture instead. The red line represents the bounds with the black squares representing my textured quads.
Try this:
private Rect ClipRectToScreen (Rect input)
{
// Special handling for screen reading of render texture so it always stay in visible area.
// Will throw error withoud this fix.
Rect r;
r = new Rect (input.x, (Screen.height - input.y - input.height) , input.width, input.height);
if (r.x < 0f) {
r.width = r.width - UnityEngine.Mathf.Abs (r.x);
r.x = 0f;
// Debug.Log ("x < 0");
}
if (r.y < 0f) {
r.height = r.height - UnityEngine.Mathf.Abs (r.y);
r.y = 0f;
// Debug.Log ("y < 0");
}
if (r.x > Screen.width) {
r.width = r.width - UnityEngine.Mathf.Abs (r.x);
r.x = 0f;
// Debug.Log ("x > Screen.width");
}
if (r.y > Screen.height) {
r.height = r.height - UnityEngine.Mathf.Abs (r.y);
r.y = 0f;
// Debug.Log ("y > Screen.height");
}
if ((r.x + r.width) > Screen.width) {
r.width = Screen.width - r.x;
// Debug.Log ("r.x + r.width > Screen.width");
}
if ((r.y + r.height) > Screen.height) {
r.height = Screen.height - r.y;
// Debug.Log ("r.y + r.height > Screen.height");
}
return r;
}