I am working on a simple game where you click on square sprites before they disappear. I decided to get fancy and make the squares rotate. Now, when I click on the squares, they don't always respond to the click. I think that I need to rotate the click position around the center of the rectangle(square) but I am not sure how to do this. Here is my code for the mouse click:
if ((mouse.LeftButton == ButtonState.Pressed) &&
(currentSquare.Contains(mouse.X , mouse.Y )))
And here is the rotation logic:
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
RotationAngle += elapsed;
float circle = MathHelper.Pi * 2;
RotationAngle = RotationAngle % circle;
I am new to Xna and programming in general, so any help is appreciated.
Thanks a lot,
Bill
So you're trying to determine if a point is in a rectangle, but when the rectangle is rotated?
The Contains() method will only work if the current rotation is 0 (I guess currentSquare is a rectangle representing the image position without rotation?).
What you will have to do is do the opposite rotation of the image on the mouse coordinates (the mouse coordinates should rotate around the origin of your image), then calculate if the new position is within currentSquare. You should be able to do all of this using vectors.
(Untested)
bool MouseWithinRotatedRectangle(Rectangle area, Vector2 tmp_mousePosition, float angleRotation)
{
Vector2 mousePosition = tmp_mousePosition - currentSquare.Origin;
float mouseOriginalAngle = (float)Math.Atan(mousePosition.Y / mousePosition.X);
mousePosition = new Vector2((float)(Math.Cos(-angleRotation + mouseOriginalAngle) * mousePosition.Length()),
(float)(Math.Sin(-angleRotation + mouseOriginalAngle) * , mousePosition.Length()));
return area.Contains(mousePosition);
}
If you dont need pixel pefect detection you can create bounding sphere for each piece like this.
var PieceSphere = new BoundingSphere()
{
Center =new Vector3(new Vector2(Position.X + Width/2, Position.Y + Height/2), 0f),
Radius = Width / 2
};
Then create another bounding sphere around mouse pointer.For position use mouse coordinates and for radius 1f. Because mouse pointer will be moving it will change its coordinates so you have to also update the sphere's center on each update.
Checking for clicks would be realy simple then.
foreach( Piece p in AllPieces )
{
if ((mouse.LeftButton == ButtonState.Pressed) && p.BoundingSphere.Intersects(MouseBoundingSphere))
{
//Do stuff
}
}
If you are lazy like me you could just do a circular distance check.
Assuming mouse and box.center are Vector2
#gets us C^2 according to the pythagorean Theorem
var radius = (box.width / 2).squared() + (box.height / 2).square
#distance check
(mouse - box.center).LengthSquared() < radius
Not perfectly accurate but the user would have a hard time noticing and inaccuracies that leave a hitbox slightly too large are always forgiven. Not to mention the check is incredibly fast just calculate the radius when the square is created.
Related
I'm new to programming. I am writing a simple game (I think many people know it under the name "Ping Pong") for smartphones. A game for two people. I need the screen to be divided into two areas. Everyone within their area should be able to control the object with their finger along the x axis. Accordingly, the object changes its position only along the x axis.
here's my game :)
I was able to write this script myself:
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) * new Vector2(1, 0);
Vector2 mousePosition2 = mousePosition + new Vector2(1, -4.74f);
transformPosition1.transform.position = mousePosition2;
This script changes the position of the object relative to the x-axis of the mouse. But I do not know how to limit its area and divide it into two players. Thank you in advance for your help. I apologize for my English. Please do not use complex language constructions in the response.
One way would be putting one image to top half of the screen and other to bottom half and assign EventSystem to handle dragging.
Other way is to calculate the mouse position to see if it is within the top half or bottom half.
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
float y = mousePosition .y;
if (y > Screen.height / 2 && y < Screen.height)// top half {
// move the top side player
}
if (y > 0 && y < Screen.height / 2)// bottom half {
// move the bottom side player
}
I know this question is a duplicate but I’ve tried all the other solutions and can’t find a way to work this out. I have a bunch of cubes set up in unity in a circle, and my goal is to rotate them collectively when someone holds the mouse down and then snap to a certain position. Since there are 20 cubes, I need it to go to the nearest 18 degrees (360/20) so that there is a cube in the same place as in the original circle, just that the whole circle is turned.
I have gotten the cubes to rotate together but when I try to snap them to the nearest 18 degrees it snaps to a seemingly random spot. I have tried using different methods and I don’t know where I am going wrong! Please help!
This is my code controlling the rotation and then trying to snap it after the user has let go (the circle is the parent of all the cubes):
GameObject circle;
void Update(){
if (circle != null) { //if the circle has been assigned, rotate it
circle.transform.Rotate(cam.up, -Input.GetAxis("Mouse X") * rotationSpeed, Space.World);
}
else if (Input.GetMouseButtonDown(0)) { //if clicked, assign the circle
circle = this.GameObject.transform.parent;
}
if (circle != null && Input.GetMouseButtonUp(0)) { //supposed to snap to the right spot, this is the part that isn't working
var rotate = circle.transform.rotation.y;
var newRotate = Mathf.Round((rotate * 360) / 18) * 18;
if (newRotate > 360) { newRotate -= 360; }
else if (newRotate < 0) { newRotate += 360; }
circle.transform.eulerAngles = new Vector3(0, newRotate, 0);
}
}
Many things seems weird.
In the code you show, you assign circle but you never use it. why do you need it?
Are you rotating the parent of these 20 cubes, because that is what you should do but it is not clear in your code
You use the y value of a quaternion when you do layer.transform.rotation.y. Try layer.transform.eulerAngles.y.
I have a class below that I attach to a object in order to make it rotate around its pivot. I sent the pivot of the sprite via the inspector.
This works exactly how I want it too, BUT the issue I am having is that whenever I touch and drag it, and then touch and drag it again, it snaps to a new position.
What I would like for it to do is, when it is rotated and then rotated again, the sprite stays in its same rotation and not snap to a new position and I would like the angle of the sprite to be reset to 0. The next then is that I want the angle to continually rotate. So if I rotate it in the positive direction, the angle should keep increasing in the positive direction and not change..Such as 0---> 360 ----> 720 -----> etc, etc. And then when the mouse is released, the sprite stays in the same position but the angle is now set back to 0. And then when clicked again to rotate, it rotates from that exact position.
Here is my code thus far which works well for rotating, but I would like to modify it to achieve the above scenario. Any help with this?
public class Steering : MonoBehaviour {
float prevAngle,wheelAngle,wheelNewAngle = 0;
public SpriteRenderer sprite;
void Start () {
}
void Update () {
}
public float GetAngle(){
return wheelAngle;
}
void OnMouseDrag(){
Vector3 mouse_pos = Input.mousePosition;
Vector3 player_pos = Camera.main.WorldToScreenPoint(this.transform.position);
mouse_pos.x = mouse_pos.x - player_pos.x;
mouse_pos.y = mouse_pos.y - player_pos.y;
wheelNewAngle = Mathf.Atan2 (mouse_pos.y, mouse_pos.x) * Mathf.Rad2Deg;
if (Input.mousePosition.x > sprite.bounds.center.x) {
wheelAngle += wheelNewAngle - prevAngle;
} else {
wheelAngle -= wheelNewAngle - prevAngle;
}
this.transform.rotation = Quaternion.Euler (new Vector3(0, 0, wheelAngle));
Debug.Log (wheelAngle);
prevAngle = wheelNewAngle;
}
void OnMouseUp(){
prevAngle = wheelNewAngle;
wheelAngle = 0;
}
}
By angle of the sprite, do you mean the rotation? I'm not sure how the position is changing if there's nothing in your code doing that. Does it always move to the same position? I'm having a little trouble visualizing how your system is supposed to look but I hope this helps.
It looks like you might want to store the previous mouse position so you can get the relative amount to rotate each frame.
At the top:
Vector3 prevMousePos = Vector3.zero;
This method will help get the position when the player pressed:
void OnMouseDown(){
prevMousePos = Input.mousePosition;
}
Then in OnMouseDrag() get the difference between the two mouse positions to get the relative position (if you moved the mouse left, right, up, or down since pressing):
Vector3 mouseDiff = Input.mousePosition - prevMousePos;
With this it will use the relative mouse position after pressing instead of the current one, which should smooth things out.
for my programm im programming a "clock like" behaviour. Meaning i
order some pictures in a circle (works)
if i click and drag on any item all items should rotate with the mouse (works)
But i get a weird bug. The first time i click and hold the mouse my images "jump" to different positions. if i hold the mouse down i can rotate my clock ust fine.
When i MouseUp and start dragin from the same image it works well. if i go to another image i get this "Jump" again.
When i only have a few images on my clock i see that it doesnt jump. but the start position seems to bee off.
When i only have one item, i can rotate it in a circle, but the moment i start to rotate it jumps away from my mouse and than i can rotate it as desired.
For me it seems to be a wrong "starting point" when first dragging an item. SInce it works fine when i than drag the same item again and again.
Unfortunately i cant find the damn bug, and im searching the whole day already.
#
public void SetLayoutHorizontal ()
{
Debug.Log ("LAYOUT");
for (var i =0; i < Rect.childCount; i++)
{
var PanelPrefab = Rect.GetChild (i) as RectTransform;
Transform ImageObject = PanelPrefab.GetComponentInChildren<Transform>().Find("Image");
if (PanelPrefab == null)
continue;
PanelPrefab.sizeDelta = CellSize;
PanelPrefab.anchoredPosition = new Vector2(radius * Mathf.Sin( CalculateCircleAngle(i) - deltaRadian),radius * Mathf.Cos(CalculateCircleAngle(i) - deltaRadian));
}
}
private float CalculateCircleAngle(int parts)
{
//parts == Number of parts the whole circle is to be cut into
return parts * (360/Rect.childCount) * (Mathf.PI/180);
}
public void OnDrag (PointerEventData eventData)
{
var diffX = eventData.position.x - _rect.rect.width/2; // MouseX - CenterScreenX
var diffY = eventData.position.y - _rect.rect.height/2; // MouseY - CenterScreenY
deltaRadian = Mathf.Atan2(diffY,diffX);
SetDirty();
}
Edit:
Ok i Edited the code but it still is not working.
I added the following method:
public void OnBeginDrag(PointerEventData eventData)
{
originalX = eventData.position.x;
originalY = eventData.position.y;
}
and i changed the drag method acordingly
public void OnDrag (PointerEventData eventData)
{
var diffX = eventData.position.x - originalX;
var diffY = eventData.position.y - originalY;
deltaRadian = Mathf.Atan2(diffY,diffX);
SetDirty();
}
The "Jumping" at the beginning of my drag event is gone, but the speed of the draggin is not on par with my mouse.
The closer i am to my starting point of the drag, the faster it moves, the further away i am the slower it gets.
I dont know if this brought me closer to a solution or further away :(
Edit2:
Ok i think the problem might be that my calculations were all done from the center point of view as 0,0 point.
Unity has the bottom left point as 0,0. So i somehow have to translate all those coordiantes first...
All that was needed was a transformation to kartesian coordinates
//Convert to kartesian coordinates with 0,0 in center of screen
var diffX = (eventData.position.x - _rect.rect.width / 2) % (_rect.rect.width / 2);
var diffY = (eventData.position.y - _rect.rect.height / 2) % (_rect.rect.height / 2);
and an addition of the delta instead of the subtraction
PanelPrefab.anchoredPosition = new Vector2(radius * Mathf.Sin(CalculateCircleAngle(i) + deltaRadian),radius * Mathf.Cos(CalculateCircleAngle(i) + deltaRadian));
Your mouse coordinates are relative to the control you click in. You could re-calculate them using the control's position within its container or you use absolute coordinates.
I don't think you are displaying all the relevant code, but your OnDrag function, specifically diffX, and diffY probably provide the answer for you.
I believe you're taking the absolute coordinates of the location you pick, and using them to generate your angle - which means that almost anywhere you click is a big jump from however the current angle is set. Your initial click handler should save off a starting coordinate, and your OnDrag should compare itself against that initial coordinate, based on how far you've dragged from that saved location.
I'm trying to figure out how to create an accurate pinch zoom for my camera in Unity3D/C#. It must be based on the physical points on the terrain. The image below illustrates the effect I want to achieve.
The Camera is a child of a null which scales (between 0,1 and 1) to "zoom" as not to mess with the perspective of the camera.
So what I've come up with so far is that each finger must use a raycast to get the A & B points as well as the current scale of the camera parent.
EG: A (10,0,2), B (14,0,4), S (0.8,0.8,0.8) >> A (10,0,2), B (14,0,4), S (0.3,0.3,0.3)
The positions of the fingers will change but the hit.point values should remain the same by changing the scale.
BONUS: As a bonus, it would be great to have the camera zoom into a point between the fingers, not just the center.
Thanks so much for any help or reference.
EDIT:
I've come up with this below so far but it's not accurate the way I want. It incorporates some of the ideas I had above and I think that the problem is that it shouldn't be /1000 but an equation including the current scale somehow.
if (Input.touchCount == 2) {
if (!CamZoom) {
CamZoom = true;
var rayA = Camera.main.ScreenPointToRay (Input.GetTouch (0).position);
var rayB = Camera.main.ScreenPointToRay (Input.GetTouch (1).position);
int layerMask = (1 << 8);
if (Physics.Raycast (rayA, out hit, 1500, layerMask)) {
PrevA = new Vector3 (hit.point.x, 0, hit.point.z);
Debug.Log ("PrevA: " + PrevA);
}
if (Physics.Raycast (rayB, out hit, 1500, layerMask)) {
PrevB = new Vector3 (hit.point.x, 0, hit.point.z);
Debug.Log ("PrevB: " + PrevB);
}
PrevDis = Vector3.Distance (PrevB, PrevA);
Debug.Log ("PrevDis: " + PrevDis);
PrevScaleV = new Vector3 (PrevScale, PrevScale, PrevScale);
PrevScale = this.transform.localScale.x;
Debug.Log ("PrevScale: " + PrevScale);
}
if (CamZoom) {
var rayA = Camera.main.ScreenPointToRay (Input.GetTouch (0).position);
var rayB = Camera.main.ScreenPointToRay (Input.GetTouch (1).position);
int layerMask = (1 << 8);
if (Physics.Raycast (rayA, out hit, 1500, layerMask)) {
NewA = new Vector3 (hit.point.x, 0, hit.point.z);
}
if (Physics.Raycast (rayB, out hit, 1500, layerMask)) {
NewB = new Vector3 (hit.point.x, 0, hit.point.z);
}
DeltaDis = PrevDis - (Vector3.Distance (NewB, NewA));
Debug.Log ("Delta: " + DeltaDis);
NewScale = PrevScale + (DeltaDis / 1000);
Debug.Log ("NewScale: " + NewScale);
NewScaleV = new Vector3 (NewScale, NewScale, NewScale);
this.transform.localScale = Vector3.Lerp(PrevScaleV,NewScaleV,Time.deltaTime);
PrevScaleV = NewScaleV;
CamAngle();
}
}
Intro
I had to solve this same problem recently and started off with the same approach as you, which is to think of it as though the user is interacting with the scene and we need to figure out where in the scene their fingers are and how they're moving and then invert those actions to reflect them in our camera.
However, what we're really trying to achieve is much simpler. We simply want the to user feel like the area of the screen that they are pinching changes size with the same ratio as their pinch changes.
Aim
First let's summarise our goal and constraints:
Goal: When a user pinches, the pinched area should appear to scale to match the pinch.
Constraint: We do not want to change the scale of any objects.
Constraint: Our camera is a perspective camera.
Constraint: We do not want to change the field of view on the camera.
Constraint: Our solution should be resolution/device independent.
With all that in mind, and given that we know that with a perspective camera objects appear larger when they're closer and smaller when they're further, it seems that the only solution for scaling what the user sees is to move the camera in/out from the scene.
Solution
In order to make the scene look larger at our focal point, we need to position the camera so that a cross-section of the camera's frustum at the focal point is equivalently smaller.
Here's a diagram to better explain:
The top half of the image is the "illusion" we want to achieve of making the area the user expands twice as big on screen. The bottom half of the image is how we need to move the camera to position the frustum in a way that gives that impression.
The question then becomes how far do I move the camera to achieve the desired cross-section?
For this we can take advantage of the relationship between the frustum's height h at a distance d from the camera when the camera's field of view angle in degrees is θ.
Since our field of view angle θ is constant per our agreed constraints, we can see that h and d are linearly proportional.
This is useful to know because it means that any multiplication/division of h is equally reflected in d. Meaning we can just apply our multipliers directly to the distance, no extra calculation to convert height to distance required!
Implementation
So we finally get to the code.
First, we take the user's desired size change as a multiple of the previous distance between their fingers:
Touch touch0 = Input.GetTouch(0);
Touch touch1 = Input.GetTouch(1);
Vector2 prevTouchPosition0 = touch0.position - touch0.deltaPosition;
Vector2 prevTouchPosition1 = touch1.position - touch1.deltaPosition;
float touchDistance = (touch1.position - touch0.position).magnitude;
float prevTouchDistance = (prevTouchPosition1 - prevTouchPosition1).magnitude;
float touchChangeMultiplier = touchDistance / prevTouchDistance;
Now we know by how much the user wants to scale the area they're pinching, we can scale the camera's distance from its focal point by the opposite amount.
The focal point is the intersection of the camera's forward ray and the thing you're zooming in on. For the sake of a simple example, I'll just be using the origin as my focal point.
Vector3 focalPoint = Vector3.zero;
Vector3 direction = camera.transform.position - focalPoint;
float newDistance = direction.magnitude / touchChangeMultiplier;
camera.transform.position = newDistance * direction.normalized;
camera.transform.LookAt(focalPoint);
That's all there is to it.
Bonus
This answer is already very long. So to briefly answer your question about making the camera focus on where you're pinching:
When you first detect a 2 finger touch, store the screen position and related world position.
When zooming, move the camera to put the world position back at the same screen position.
This is a small example:
if (_Touches.Length == 2)
{
Vector2 _CameraViewsize = new Vector2(_Camera.pixelWidth, _Camera.pixelHeight);
Touch _TouchOne = _Touches[0];
Touch _TouchTwo = _Touches[1];
Vector2 _TouchOnePrevPos = _TouchOne.position - _TouchOne.deltaPosition;
Vector2 _TouchTwoPrevPos = _TouchTwo.position - _TouchTwo.deltaPosition;
float _PrevTouchDeltaMag = (_TouchOnePrevPos - _TouchTwoPrevPos).magnitude;
float _TouchDeltaMag = (_TouchOne.position - _TouchTwo.position).magnitude;
float _DeltaMagDiff = _PrevTouchDeltaMag - _TouchDeltaMag;
_Camera.transform.position += _Camera.transform.TransformDirection((_TouchOnePrevPos + _TouchTwoPrevPos - _CameraViewsize) * _Camera.orthographicSize / _CameraViewsize.y);
_Camera.orthographicSize += _DeltaMagDiff * _OrthoZoomSpeed;
_Camera.orthographicSize = Mathf.Clamp(_Camera.orthographicSize, _MinZoom, _MaxZoom) - 0.001f;
_Camera.transform.position -= _Camera.transform.TransformDirection((_TouchOne.position + _TouchTwo.position - _CameraViewsize) * _Camera.orthographicSize / _CameraViewsize.y);
}
In the second video of this tutorial explains it