I'm making a simple project in Unity where there is a Ball attached to a SpringJoint2d component the ball is on an angled slope, like in the image below:
I simply want the user to be able to drag the ball backward along the edge of the slope only,in other words I don't want the user to be able to move the ball away from the slope or into it.
I'v been trying several ways I thought could do the job hers the script of the dragging with what I tried:
(This Is the updated version)
public class ball : MonoBehaviour
{
public Rigidbody2D rb;
public Transform spring;
public Transform calcpoint;
private Vector3 start;
private Vector3 end;
private bool isPressed = false;
RaycastHit2D[] hits = new RaycastHit2D[2];
RaycastHit2D[] hits2 = new RaycastHit2D[2];
float factor = 0;
private void OnMouseDown()
{
if (!isPressed)
{
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
/// <summary>
/// release the ball from the spring joint after a small amount of time
/// </summary>
/// <returns></returns>
IEnumerator release()
{
yield return new WaitForSeconds(0.1f);
rb.GetComponent<SpringJoint2D>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (isPressed)
{
if (Vector3.Distance(spring.position, rb.position) > 3f || spring.position.x < (rb.position.x - 1)) return;//restrict the dragging of the ball to not go beyond the spring point and not too far back
float angle = 0;
if (checkGround() > 1)//if we hit the slope with the ray cast downward from the mouse/Tap position
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
factor = (float)(((45 - angle) * 0.02) + 1) * (angle / 45);//an inaccurate formula to offset the ball to be on top of the slope that works just fine with some glitches
rb.position = hits[1].point + new Vector2(0, factor * 1f);//position the ball at the point were the ray cast downward from the mouse hit
//(that puts the ball center on the line of the slope) so I offset it usinf the formula above
}
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
}
here are the settings on the ball:
and the slope setup:
The dragging of the ball works fine ,at one point the player could drag it in the air or into the slope, I managed to fix that with the new code so now the player could only drag it on the edge, though my calculations are still a bit flawed and when the slopes angle is changed the ball would dip a bit inside the slope and that causes some problems at release.
The method used to try to solve the problem is simple, when the player start dragging the ball I cast a ray from the mouse downward and pit the ball on the point of impact with the slope ,offsetting it to sit on top of it,right ow the problem is that the offsetting part is not accurate enough.
I hope I explained myself a bit better this time Thanks:)
After lots of trial and error I did manage to come up with a perfect solution to the problem so I thought I might as well share the answer maybe it will help someone.
here is the updated code I changed my method of restricting the movement completely now I use simple linear line equation as shown below:
private void OnMouseDown()
{
if (!isPressed)
{
//cast a ray on the slope
if (checkGround() > 1)
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
slope = Mathf.Tan(angle * Mathf.Deg2Rad);//get the slope steepiness
}
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
void Update() {
if (isPressed)
{
xMove = Camera.main.ScreenToWorldPoint(Input.mousePosition).x - spring.position.x;//get how much the mouse moved backward or forward
if (xMove < -3f ) xMove = -3f; //restrict the drag range to 3 backward
if (xMove > 0.3f) xMove = 0.3f;//restrict the drag range to 0.3 forward
xpos = spring.position.x+xMove;//since the ball and the spring start at exactly the same position the new ball's x position would be the spring x + the x movement we calculated above
ypos = (xMove * slope)- spring.position.y; //the y posistion would be y=mx+b so the the x movement * the slop steepiness - the starting y position
rb.position = new Vector2(xpos, -ypos);//set the new position of the ball
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
Related
I want to rotate the bar based on my finger touch draw related to the pivot point. Following test structure implementation, I have created it for testing purposes.
Currently, I can able to write this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestRotateController : MonoBehaviour
{
float rotateSpeed = 10f;
Vector2 touchStartPos;
Transform touchItem;
//
[SerializeField] LayerMask touchItemsMask;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);
RaycastHit2D hit = Physics2D.Raycast(mousePos2D, Vector2.zero, 0f, touchItemsMask);
if (hit.collider != null && hit.transform.CompareTag(GameConstants.TAG_RELEASE_ANGLE_BAR))
{
touchItem = hit.transform;
touchStartPos = mousePos2D;
}
}
else if (Input.GetMouseButton(0))
{
if (touchItem != null && touchItem.CompareTag(GameConstants.TAG_RELEASE_ANGLE_BAR))
{
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);
RotateReleaseAngleBar(touchStartPos, mousePos2D);
}
}
else if (Input.GetMouseButtonUp(0))
{
touchItem = null;
}
}
// rotate pivot parent
public void RotateReleaseAngleBar(Vector2 touchStartPosition, Vector2 touchPosition)
{
if (touchStartPosition.x > touchPosition.x)
{
transform.parent.Rotate(Vector3.forward, rotateSpeed * Time.deltaTime);
}
else if (touchStartPosition.x < touchPosition.x)
{
transform.parent.Rotate(Vector3.forward, -rotateSpeed * Time.deltaTime);
}
}
}
Now with this code, I can't able to rotate the bar as my finger is moving.
Selected direction will remain proper because I have used X value to decide this but when I stop dragging my finger then also rotation remain continue in the same direction.
I want to stop this, I want to rotate the bar based on finger drag amount. It is a kind of experience, I want a person is rotating the bar with his finger.
I think you don't really want to rotate the bar by a certain amount each frame, but instead update its rotation at each frame with respect to the finger.
You are currently rotating the bar not considering the exact (x,y) mouse2D position of current frame, which is what you need.
This script works for me, assuming you assign it to the child of the pivot (as I assume you did from the screenshot)
public class TestRotateController : MonoBehaviour
{
//member variables
void Update() {
//[...] take the input as you did...
//else
if (Input.GetMouseButton(0)) {
//if(touchItem != null...) {
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);
RotateFollowPoint(mousePos2D);
//}
}
//[else...]
}
//sets the angle of the bar to point in the direction of targetPoint2D
void RotateFollowPoint(Vector2 targetPoint2D) {
Vector2 position2D = new Vector2(transform.parent.position.x, transform.parent.position.y);
float angle = Vector2.Angle(Vector2.right, targetPoint2D - position2D);
if (targetPoint2D.y < position2D.y)
angle *= -1;
transform.parent.eulerAngles = new Vector3(0, 0, angle);
}
}
First you take the position 2D of the pivot point, then you compute the acute angle between the vector pointing from the pivot to the clicked point, which is targetPoint2D - position2D, and the Vector2.right vector (which is (1,0), so a vector pointing right), which means that the bar must be oriented to the right on its default position (when rotation on Z of the pivot = 0).
Then since Vector2.Angle gives the acute angle, set it negative if it should be more than 180°, and finally just update the angle accordingly
I have this code:
public class MoveCard : MonoBehaviour
{
public float speed = 1f;
public Rigidbody2D rb;
public Vector2 pos = new Vector2(6.8f,0);
public bool move = false;
void FixedUpdate(){
if (move){
//Stops Rigidbody
if (rb.position == pos){
move = false;
}
rb.transform.position += -rb.transform.right * speed * Time.fixedDeltaTime;
}
}
public void CardMovement(){
move = true;
}
}
I have it set as so when a button is pressed, CardMovement() initiates and in FixedUpdate I have a if statement that turns move off when the Rigidbody reaches a certain position. The rb moves but it doesn't stop when it reaches the Vector2. I am new to Unity so I don't know if this is the way to do it.
Well your Rb doesn't pass exactly for each point between the initial position and Vector2.
It's prtty unlikely to have rb.position == pos because one frame it will not enough, and the next one will be too much :)
Try with MoveTowards. Some like this:
rb.position = Vector3.MoveTowards(rb.position, pos, speed * Time.fixedDeltaTime);
You dont need a statement to stop it because it will do it when reaches pos.
PD: You can do this with transform instead of rigidbody if u are not going to use physics and you only want a movement.
Don't compare the 2 vector2D values like this:
if(rb.position == pos)
Rather compare the distance between them with a value that is very small, like this:
if(Vector2.Distance(rb.position,pos) <= 0.01)
Additionally, you can set the position like this rb.postion = pos; if it is close enough so that it snaps to the right location.
First of all thank you for your time. I'm quite new in this so I'm struggling a bit. I'm trying to make a drag and release shooter which doesn't really depend on colliders or raycasts but solely depends on mouse delta and camera position. The way I'm trying to have it done is I'm mapping mouse position (x,y) to velocity direction such as new Vector3(mouseX, 0f, mouseY) then rotating it about Y axis to match the visual drag on the screen.
void OnMouseUp()
{
releasePos = Input.mousePosition;
Vector2 m_direction = releasePos - initialPos;
Vector3 direction = new Vector3(m_direction.x, 0, m_direction.y);
float userDrag = Mathf.Clamp(direction.magnitude, 0f, maxVelocity);
direction = Translation(direction);
direction = Vector3.Normalize(direction);
rigidbody.velocity = (direction * userDrag) * force;
}
private Vector3 Translation(Vector3 direction)
{
Vector3 camPos = Camera.main.transform.position;
camPos.y = 0;
float angle = Vector3.Angle(new Vector3(0f, 0f, 1f), camPos);
Quaternion translatedAngle = Quaternion.AngleAxis(angle, Vector3.up);
direction = translatedAngle * direction;
But as the angle changes it kinda fails to deliver what I'm asking for. Is there a way I can avoid bunch of if, else statements for the angle value or a shorter way of doing this?
Example
OnMouseUp is fundamentally a collider method.
Meaning, the mouse MUST be over the GameObject's collider for the GameObject this code is attached to. If you want to move away from using colliders, then you need to move away from this method.
The generic works-anywhere way of saying "is the mouse button up or down?" is the static methods available in the Input class:
Input.GetMouseButtonDown() True on the single frame after the mouse button is depressed
Input.GetMouseButtonUp() True on the single frame after the mouse button is released
Input.GetMouseButton() True any frame the mouse button is depressed (otherwise false)
So I think I have a solution finally and I thought I'd share it for people who might be interested.
The challenge was to rotate mouse delta's direction according to camera position so it gives a natural vibe to the player when dragging and releasing the ball for putting. I did some tests to see where my code had problems rotating delta direction properly(so it matches the screen) and realized when I'm "looking from" (0,0,1) for comparison while the camera's x position is positive it works fine. But when x is negative it doesn't because Vector3.Angle doesn't return a negative value as far as I'm concerned so I just multiplied the result with minus. Seems to work.
Here is the final code:
private void Start()
{
rigidbody = GetComponent<Rigidbody>();
}
void OnMouseDown()
{
ballPos = Input.mousePosition;
}
void OnMouseUp()
{
Vector2 m_direction = Input.mousePosition - ballPos;
Vector3 direction = new Vector3(m_direction.x, 0, m_direction.y);
float userDrag = Mathf.Clamp(direction.magnitude, 0f, maxVelocity);
direction = Translation(direction);
direction = Vector3.Normalize(direction);
rigidbody.velocity = (direction * userDrag) * force;
}
private Vector3 Translation(Vector3 direction)
{
float angle = GetAngle();
Quaternion translatedAngle = Quaternion.AngleAxis(angle, Vector3.up);
direction = translatedAngle * direction;
return direction;
}
private float GetAngle()
{
Vector3 camPos = Camera.main.transform.position;
camPos.y = 0;
if (camPos.x > 0)
{
angle = Vector3.Angle(Vector3.forward, camPos);
}
else if (camPos.x <0)
{ angle = -Vector3.Angle(Vector3.forward, camPos); }
return angle;
}
please do share if you have suggestions regarding the code.
Im thinking about it two days. I still did not made any progress.
I wonder how to do that objects fly away from mouse click position in 2D view?
I tried like that:
pos = Input.mousePosition;
Vector3 realWorldPos = Camera.main.ScreenToViewportPoint(pos);
print("MOuse pos: " + realWorldPos);
//print(realWorldPos);
Vector3 velo = GetComponent<Rigidbody2D>().velocity;
if (realWorldPos.x < 0.5)
{
velo = new Vector3((realWorldPos.x * speed), velo.y);
}
else if(realWorldPos.x > 0.5)
{
velo = new Vector3((realWorldPos.x * speed) * (-1), velo.y);
}
if (realWorldPos.y < 0.5)
{
velo = new Vector3(velo.x, realWorldPos.y * speed);
}
else if (realWorldPos.y > 0.5)
{
velo = new Vector3(velo.x, (realWorldPos.y * speed) * (-1));
}
GetComponent<Rigidbody2D>().velocity = velo;
But it doesnt work as I want.
Is it possible to do this?
For this to work your Rigidbody2D must have Gravity Scale set to 0.
This is a simple test code that works for me, is placed on a sprite object:
public class PushPlayer : MonoBehaviour
{
public float pushPower = 50.0f;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
Vector3 dir = transform.position - Camera.main.ScreenToWorldPoint(Input.mousePosition);
dir = dir.normalized;
rb.AddForce(dir * pushPower, ForceMode2D.Force);
// as alternative:
rb.velocity = dir * pushPower;
}
}
}
You need to adjust the values a bit, also in the regidbody (like drag) to get it the way you want.
Edit:
transform.position - Camera.main.ScreenToWorldPoint(Input.mousePosition): calculate the directional vector from the mouse position to the player position (have a look a vector algebra if you are not familiar with this) which is the direction away from the click (in a straight line).
dir.normalized: this shortens the vector to a length (= magnitude) of 1 (again have a look at vectors) so it really is just a direction. You could omit this and reduce the factor, but doing it this way your factor equals the force you use.
I'm having an issue with the cameras in Unity. When the camera moves through any means it seems to cut my FPS in half if not more. It's not really noticeable on PC, unless I'm looking at the from from 800fps to about 150fps, however on mobile it'll cut the smooth 60fps I'm getting to 20fps on a Nexus 4. It's absolutely devastating.
Here's the properties for the camera I'm using & the script HOWEVER this issue still happens without ANY of these components and a completely reset camera component:
public class ViewDrag : MonoBehaviour
{
public Vector3 hit_position = Vector3.zero;
public Vector3 current_position = Vector3.zero;
public Vector3 camera_position = Vector3.zero;
public Vector2 min_position;
public Vector2 max_position;
float z = 0.0f;
MouseHolder holder;
hider sidebarHide;
GameObject gameStructure;
// Use this for initialization
void Start()
{
gameStructure = GameObject.FindGameObjectWithTag("GameStructure");
holder = gameStructure.GetComponent<MouseHolder>();
sidebarHide = GameObject.FindGameObjectWithTag("SidebarBG").GetComponent<hider>();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
hit_position = Input.mousePosition;
camera_position = transform.position;
}
if (Input.GetMouseButton(0))
{
current_position = Input.mousePosition;
LeftMouseDrag();
}
if (!sidebarHide.isHidden)
{
//GetComponent<Camera2DFollow>().enabled = true;
}
if(gameStructure.GetComponent<ExecuteMovement2>().isExecuted)
{
//GetComponent<Camera2DFollow>().enabled = true;
}
}
void LeftMouseDrag()
{
// From the Unity3D docs: "The z position is in world units from the camera." In my case I'm using the y-axis as height
// with my camera facing back down the y-axis. You can ignore this when the camera is orthograhic.
//current_position.z = hit_position.z = camera_position.y;
// Get direction of movement. (Note: Don't normalize, the magnitude of change is going to be Vector3.Distance(current_position-hit_position)
// anyways.
Vector3 direction = Camera.main.ScreenToWorldPoint(current_position) - Camera.main.ScreenToWorldPoint(hit_position);
// Invert direction to that terrain appears to move with the mouse.
direction = direction * -1;
Vector3 position = camera_position + direction;
if (position.x < max_position.x && position.x > min_position.x && position.y < max_position.y && position.y > min_position.y)
{
if (!EventSystem.current.IsPointerOverGameObject() && holder.fromSlot == null )
{
if (sidebarHide.isHidden)
{
if (!gameStructure.GetComponent<ExecuteMovement2>().isExecuted)
{
//GetComponent<Camera2DFollow>().enabled = false;
transform.position = position;
}
}
}
}
}
}
Has anyone got an idea why this happens and how I can work around it if not fix it?
Through closer inspection I think it has something to do with the Canvas being screen space.. But it's kind of needed that way. Again, any workaround?
Check comments for profiler screenshot.
Problem Solved:
In the profiler I found that Canvas.SendWillRenderCanvases() was causing huge spikes. I solved the issue completely by turning off Pixel Perfect in the Canvas. Smooth as butter now.