So I was working on a Unity 2D game project, where you are supposed to throw stuff around using mouse and then collecting keys and unlocking doors. I got some help to be able to get the dragging, but basically whenever you rise your left click, the object would just fall to the ground. I'd like it to be affected by G-forces, that way that when you spin your mouse the object would spin around the cursor and when you make mouse movement and release left click the object would fly depending on the mouse movements.
I found one similar to this, but the code wasn't anything I could understand and all the custom stuff I have made to be able to unlock the door didn't seem to fit well with that.
{
public static GameObject HeldItem;
private bool isBeingHeld = false;
void Update()
{
if(isBeingHeld == true)
{
Vector3 mousePos;
mousePos = Input.mousePosition;
mousePos = Camera.main.ScreenToWorldPoint(mousePos);
this.gameObject.transform.localPosition = new Vector3(mousePos.x, mousePos.y, 0);
}
}
private void OnMouseDown()
{
if(Input.GetMouseButtonDown(0))
{
HeldItem = this.gameObject;
Vector3 mousePos;
mousePos = Input.mousePosition;
mousePos = Camera.main.ScreenToWorldPoint(mousePos);
isBeingHeld = true;
}
}
private void OnMouseUp()
{
isBeingHeld = false;
}
}```
Also sorry if this ain't the greatest explaining, but I have never seen my self as any how good at it.
The code you have shared here makes an object follow your mouse directly, but would not keep any of the velocity of that movement because you're directly controlling the transform's position.
For drag and drop, the most common way to make this work is what you have here.
For drag and throw, you'll need to implement this with physics in mind. There are many ways you could do this, but this sounds like the behavior you want:
Have the object "chase" your mouse around the screen in real-time using forces:
This would involve using OnMouseDrag
[RequireComponent(typeof(Collider2D))]
public class DragAndThrow : MonoBehaviour
{
Rigidbody2D rb2d;
Vector3 mousePos;
float dragIntensity = 1.0f;
bool dragging = false;
void Start()
{
rb2d = gameObject.GetComponent<Rigidbody2D>();
}
void OnMouseDrag()
{
dragging = true;
mousePos = Input.mousePosition;
mousePos.z = 0;
mousePos = Camera.main.ScreenToWorldPoint(mousePos);
}
void OnMouseUp()
{
dragging = false;
}
FixedUpdate()
{
if(!dragging)
return;
Vector3 dragVector = (mousePos - rb2d.position);
dragVector.z = 0f;
rb2d.AddForce(dragVector * dragIntensity);
//The further your mouse is from the objet, the greater the force applied
}
}
This code was written freehand, so please let me know if there are any syntax errors.
Related
In Unity 3D I'd like to create a crosshair for my top-down 2D-shooter that gradually moves to its target whenever the player has the same x-position as the target.
The problem is that I want a smooth animation when the crosshair moves to the target. I have included a small gif from another game that shows a crosshair I'd like to achieve. Have a look at it:
Crosshair video
I tried to do that with the following script but failed - the crosshair jumps forth and back when the enemies appear. It doesn't look so smooth like in the video I mentioned above.
The following script is attached to the player:
[SerializeField]
private GameObject crosshairGO;
[SerializeField]
private float speedCrosshair = 100.0f;
private Rigidbody2D crosshairRB;
private bool crosshairBegin = true;
void Start () {
crosshairRB = crosshairGO.GetComponent<Rigidbody2D>();
crosshairBegin = true;
}
void FixedUpdate() {
//Cast a ray straight up from the player
float _size = 12f;
Vector2 _direction = this.transform.up;
RaycastHit2D _hit = Physics2D.Raycast(this.transform.position, _direction, _size);
if (_hit.collider != null && _hit.collider.tag == "EnemyShipTag") {
// We touched something!
Debug.Log("we touched the enemy");
Vector2 _direction2 = (_hit.collider.gameObject.transform.position - crosshairGO.transform.position).normalized;
crosshairRB.velocity = new Vector2(this.transform.position.x, _direction2.y * speedCrosshair);
crosshairBegin = false;
} else {
// Nothing hit
Debug.Log("nothing hit");
crosshairRB.velocity = Vector2.zero;
Vector2 _pos2 = new Vector2(this.transform.position.x, 4.5f);
if (crosshairBegin) crosshairGO.transform.position = _pos2;
}
}
I think you need create a new variable call Speed translation
with
speed = distance from cross hair to enemy position / time (here is Time.fixedDeltaTime);
then multiply speed with velocity, the cross hair will move to enmey positsion in one frame.
but you can adjust speed by mitiply it with some float > 0 and < 1;
I'm trying to make simple 2D game following tutorials when I did same thing as tutorial my jump functionality not working and left and right move functionality working please help me below I attached my source code and relevant screen shot
my player class
public class Player : MonoBehaviour
{
private Rigidbody2D _rigid;
//variable for jump
[SerializeField]
private float _jumpForce = 5.0f;
[SerializeField]
private LayerMask _grondLayer;
private bool _resetJump = false;
// Start is called before the first frame update
void Start()
{
_rigid = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
Movement();
}
void Movement()
{
float move = Input.GetAxisRaw("Horizontal");
_rigid.velocity = new Vector2(move,_rigid.velocity.y);
if(Input.GetKeyDown(KeyCode.Space) && IsGrounded()==true)
{
Debug.Log("jump");
_rigid.velocity = new Vector2(_rigid.velocity.x,_jumpForce);
StartCoroutine(ResetJumpNeededRoutine());
}
}
bool IsGrounded()
{
RaycastHit2D hitInfo = Physics2D.Raycast(transform.position, Vector2.down, 0.6f, _grondLayer);
if(hitInfo.collider != null)
{
if(_resetJump==false){return true;}
}
return false;
}
IEnumerator ResetJumpNeededRoutine()
{
_resetJump = true;
yield return new WaitForSeconds(0.1f);
_resetJump = false;
}
}
Correct way to implement the jump mechanism on 2d character.
_rigid.AddForce(new Vector2(0, _jumpForce), ForceMode2D.Impulse);
The problem is probably the LayerMask you selected Ground layer to be ignored therefore IsGrounded function will return false.
What you wanna do is select the layers you'd like your Raycast to ignore (All except Ground I assume) in the unity editor then give it another go.
I have this code to drag and drop 3d objects on a world:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpyAI : MonoBehaviour {
Animator anim;
private Vector3 screenPoint;
private Vector3 offset;
// Use this for initialization
void Start () {
anim = gameObject.GetComponent<Animator>();
}
// Update is called once per frame
void Update () {
}
void OnMouseDown()
{
anim.SetBool("drag", true);
screenPoint = Camera.main.WorldToScreenPoint(transform.position);
offset = transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z));
}
void OnMouseDrag()
{
Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z);
Vector3 curPosition = Camera.main.ScreenToWorldPoint(curScreenPoint) + offset;
transform.position = curPosition;
}
void OnMouseUp()
{
anim.SetBool("drag", false);
anim.SetBool("idle", true);
}
}
The problem is:
When im dragging the object, sometimes, depending on the mouse movement it goes undergound
How can I make the object to stay above the ground while dragging it?
The most obvious thing that should change is the z-value of curScreenPoint. Using the docs as reference, it should probably be:
Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane);
Now for maybe a bit more fine-tuned behavior, you might want to make the object raise up above the ground if the ground has slopes or something with complex colliders on it like a chair or table. To do this, you likely want to do a sphere cast down towards the ground from a point somewhat above what's calculated in curPosition. Use the hitInfo to see how far down it goes, and adjust the y position of curPosition accordingly.
How can I properly make a GameObject attach (or "stick") to another GameObject after collision? The problem: I want the GameObject to attach after collision even if it is changing scale.
"Attach on collision" code:
protected Transform stuckTo = null;
protected Vector3 offset = Vector3.zero;
public void LateUpdate()
{
if (stuckTo != null)
transform.position = stuckTo.position - offset;
}
void OnCollisionEnter(Collision col)
{
rb = GetComponent<Rigidbody>();
rb.isKinematic = true;
if(stuckTo == null
|| stuckTo != col.gameObject.transform)
offset = col.gameObject.transform.position - transform.position;
stuckTo = col.gameObject.transform;
}
This code makes a GameObject attach perfectly after collision. But when that GameObject changes scale (while it's attached), it visually no longer looks attached to whatever it collided with. Basically, this code makes the GameObject stick with only the original scale at the moment of the collision. How can I make the GameObject always stick to whatever it collided with? And with whatever scale it has during the process? I would like to avoid parenting: "It's a bit unsafe though, parenting colliders can cause weird results, like random teleporting or object starting to move and rotate insanely, etc." - Samed Tarık ÇETİN : comment.
Scaling script:
public Transform object1; //this is the object that my future-scaling GameObject collided with.
public Transform object2; //another object, the same scale as object1, somewhere else
//(or vice versa)
void Update ()
{
float distance = Vector3.Distance (object1.position, object2.position);
float original_width = 10;
if (distance <= 10)
{
float scale_x = distance / original_width;
scale_x = Mathf.Min (scale_x, 3.0f);
transform.localScale = new Vector3 (scale_x * 3.0f, 3.0f / scale_x, 3.0f);
}
}
Your basic idea is right, your code can be modified slightly to support this.
Here is the trick: instead instead of sticking your object to the object it collided with, you create a dummy game object, lets call it "glue", at the collision point, and stick your object to the glue. The glue object is then parented to the object we collided with.
Since glue is just a dummy object with only components transform and some script, there is no problem with parenting.
Also, pay attention that it does not really matter at which contact point we create the glue, in case we have multiple contact points, and it is also easy to extend this to support rotations, see below.
So on collision, the only thing we do now is creating a glue. Here is the code:
void CreateGlue(Vector3 position, GameObject other) {
// Here we create a glue object programatically, but you can make a prefab if you want.
// Glue object is a simple transform with Glue.cs script attached.
var glue = (new GameObject("glue")).AddComponent<Glue>();
// We set glue position at the contact point
glue.transform.position = position;
// This also enables us to support object rotation. We initially set glue rotation to the same value
// as our game object rotation. If you don't want rotation - simply remove this.
glue.transform.rotation = transform.rotation;
// We make the object we collided with a parent of glue object
glue.transform.SetParent(other.transform);
// And now we call glue initialization
glue.AttachObject(gameObject);
}
void OnCollisionEnter(Collision col)
{
// On collision we simply create a glue object at any contact point.
CreateGlue(col.contacts[0].point, col.gameObject);
}
And here is how Glue.cs script looks, it will handle LateUpdate and modify transform.
public class Glue : MonoBehaviour {
protected Transform stuckTo = null;
protected Vector3 offset = Vector3.zero;
public void AttachObject(GameObject other)
{
// Basically - same code as yours with slight modifications
// Make rigidbody Kinematic
var rb = other.GetComponent<Rigidbody>();
rb.isKinematic = true;
// Calculate offset - pay attention the direction of the offset is now reverse
// since we attach glue to object and not object to glue. It can be modified to work
// the other way, it just seems more reasonable to set all "glueing" functionality
// at Glue object
offset = transform.position - other.transform.position;
stuckTo = other.transform;
}
public void LateUpdate()
{
if (stuckTo != null) {
// If you don't want to support rotation remove this line
stuckTo.rotation = transform.rotation;
stuckTo.position = transform.position - transform.rotation * offset;
}
}
// Just visualizing the glue point, remove if not needed
void OnDrawGizmos() {
Gizmos.color = Color.cyan;
Gizmos.DrawSphere(transform.position, 0.2f);
}
}
Also, pay attention that simply parenting the objects as it was suggested here will get you in some additional trouble, because scaling parent also scales children, so you will have to re-scale the child back to its original size. The problem is that these scaling operations are relative to different anchor points, so you will also have to make additional adjustments in objects position. Can be done though.
I also created a small sample project, see here (Unity v5.2.f3):
https://www.dropbox.com/s/whr85cmdp1tv7tv/GlueObjects.zip?dl=0
P.S. I see that you mix transform and rigidbody semantics, since it is done on Kinematic rigidbodies it is not a big deal, but just a suggestion: think whether you really need to have rigidbodies on objects that are already "stuck" to others, if not - maybe just remove or disable the rigidbody instead of making it Kinematic.
What you want to do is scale about the collision point. You can achieve this by setting the pivot point to the collision point, that way when you scale the object it will be scaled based on the pivot. The best way to simulate this is using sprites in Unity.
In the above image, the top-left crate has a pivot point in the center, so when you scale it along x, it scales about that point, increasing it's width both sides of the pivot point. But when the pivot point is set to the side, like in the bottom left image, when you scale it, it can only extend it out to the left (unless you scale negatively of course).
Now the problem is you can't easily change the pivot point of mesh, so there are a number of different work-arounds for this. One such approach is to attach the mesh to an empty new gameObject and adjust the mesh's local transform in relation to the parent, simulating moving the meshes pivot point.
What the below code does is determine the collision point. Since there can be multiple collision points, I get the average collision point of them all. I then move the mesh's parent to the collision point and adjust the meshes local position so that the side of the cube is positioned at that point, this acts as setting the mesh's pivot point to the collision point.
Now when you scale, it will scale about the collision point, just like in the above image.
public MeshRenderer _meshRenderer;
public float _moveXDirection;
public Rigidbody _rigidBody;
public Transform _meshTransform;
public bool _sticksToObjects;
public ScalingScript _scalingScript;
protected Transform _stuckTo = null;
protected Vector3 _offset = Vector3.zero;
void LateUpdate()
{
if (_stuckTo != null)
{
transform.position = _stuckTo.position - _offset;
}
}
void OnCollisionEnter(Collision collision)
{
if (!_sticksToObjects) {
return;
}
_rigidBody.isKinematic = true;
// Get the approximate collision point and normal, as there
// may be multipled collision points
Vector3 contactPoint = Vector3.zero;
Vector3 contactNormal = Vector3.zero;
for (int i = 0; i < collision.contacts.Length; i++)
{
contactPoint += collision.contacts[i].point;
contactNormal += collision.contacts[i].normal;
}
// Get the final, approximate, point and normal of collision
contactPoint /= collision.contacts.Length;
contactNormal /= collision.contacts.Length;
// Move object to the collision point
// This acts as setting the pivot point of the cube mesh to the collision point
transform.position = contactPoint;
// Adjust the local position of the cube so it is flush with the pivot point
Vector3 meshLocalPosition = Vector3.zero;
// Move the child so the side is at the collision point.
// A x local position of 0 means the child is centered on the parent,
// a value of 0.5 means it's to the right, and a value of -0.5 means it to the left
meshLocalPosition.x = (0.5f * contactNormal.x);
_meshTransform.localPosition = meshLocalPosition;
if (_stuckTo == null || _stuckTo != collision.gameObject.transform)
{
_offset = collision.gameObject.transform.position - transform.position;
}
_stuckTo = collision.gameObject.transform;
// Enable the scaling script
if (_scalingScript != null)
{
_scalingScript.enabled = true;
}
}
Here is an example project with the above code:
https://www.dropbox.com/s/i6pdlw8mjs2sxcf/CubesAttached.zip?dl=0
change global
protected Collider stuckTo = null;
///// use Collider instead of transform object. You might get better solution.Inform me if it works or gives any error since i haven't tried if it works i would like to know.
void OnCollisionEnter(Collision col)
{
rb = GetComponent<Rigidbody>();
rb.isKinematic = true;
if(stuckTo == null || stuckTo != col.gameObject.transform)
offset = col.collider.bounds.center - transform.position;
stuckTo = col.collider;
}
public void LateUpdate()
{
if (stuckTo != null)
{
Vector3 distance=stuckTo.bounds.extents + GetComponent<Collider>().bounds.extents;
transform.position = stuckTo.bounds.center + distance;
}
}
Make sure that you are scaling the stuckTo transform (the one that has the collider attached to) and not any of it's parents or this will not work.
if the stuckTo's scale is uniform:
protected Transform stuckTo = null;
protected Vector3 originalPositionOffset = Vector3.zero;
protected Vector3 positionOffset = Vector3.zero;
protected Vector3 originalScaleOfTheTarget = Vector3.zero;
public void LateUpdate()
{
if (stuckTo != null){
positionOffset *= stuckTo.localScale.x;
transform.position = stuckTo.position - positionOffset;
}
}
void OnCollisionEnter(Collision col)
{
rb = GetComponent<Rigidbody>();
rb.isKinematic = true;
if(stuckTo == null
|| stuckTo != col.gameObject.transform){
originalScaleOfTheTarget = col.gameObject.transform.localScale;
originalPositionOffset = col.gameObject.transform.position - transform.position;
originalPositionOffset /= originalScaleOfTheTarget.x;
}
stuckTo = col.gameObject.transform;
}
but if the stuckTo's scale is non-uniform:
protected Transform stuckTo = null;
protected Vector3 originalPositionOffset = Vector3.zero;
protected Vector3 positionOffset = Vector3.zero;
protected Vector3 originalScaleOfTheTarget = Vector3.zero;
public void LateUpdate()
{
if (stuckTo != null){
positionOffset.x = originalPositionOffset.x * stuckTo.localScale.x;
positionOffset.y = originalPositionOffset.y * stuckTo.localScale.y;
positionOffset.z = originalPositionOffset.z * stuckTo.localScale.z;
transform.position = stuckTo.position - positionOffset;
}
}
void OnCollisionEnter(Collision col)
{
rb = GetComponent<Rigidbody>();
rb.isKinematic = true;
if(stuckTo == null
|| stuckTo != col.gameObject.transform){
originalScaleOfTheTarget = col.gameObject.transform.localScale;
originalPositionOffset = col.gameObject.transform.position - transform.position;
originalPositionOffset.x /= originalScaleOfTheTarget.x;
originalPositionOffset.y /= originalScaleOfTheTarget.y;
originalPositionOffset.z /= originalScaleOfTheTarget.z;
}
stuckTo = col.gameObject.transform;
}
But still though - why are you following ÇETİN's advice man? It's totally safe to parent colliders and rigidbodies and literally anything as long as you know what you are doing. Just parent your sticky transform under the target and bam! if something goes wrong just remove your rigidbody component or disable your collider component.
I'm trying to make a simple drag and drop game where you have objects that can be picked up and if left above 1/2 of the screens height then it will fall down to high they will fall if you let them go below half of the screen they will stay there. Its a 2D game and what give the illusion of depth.
This is an image of the Thing i would like to make
Currently I can move the blue bucket from the bottom. If i lift the bucket above the PolygonCollider it will fall down to the Edge Collider 4. When i leave it somewhere withing the Polygone Colider i set the object to kinetic so it wont fall it will give the illusion that you placed it on the ground.
My problem is that the colliders of the bucket what I use to detect the click on it, will overlap with the PolygonCollider. And can sometimes the object is on top and sometime the polygoncollider , and if the polygoncollider is on top i can not lift the object.
Is there a way to ignore on click all layers except the PickebleObject layer that i use to identify the objects that can be picked up ?
EDIT: here is my ObjectController script
public LayerMask interactLayers;
public LayerMask ignoredColliders;
public Action<GameObject> OnDrag;
public Action<GameObject> OnLand;
private Rigidbody2D objRB2D;
private Vector2 MousePos;
private bool IsOnFloor = false;
private void Start()
{
transform.position = new Vector3 (transform.position.x, transform.position.y, transform.position.z - 0.1f);
ignoredColliders = ~ignoredColliders;
objRB2D = transform.GetComponent<Rigidbody2D>();
objRB2D.gravityScale = GameManager.Instance.GlobalFallingSpeed;
}
private void OnMouseDown()
{
objRB2D.isKinematic = true;
}
private void OnMouseDrag()
{
MousePos = Camera.main.ScreenToWorldPoint (new Vector2 (Input.mousePosition.x, Input.mousePosition.y ));
//Limit So elements cant be moved out of the scene.
float HorizontalClamp = Mathf.Clamp (MousePos.x, CameraControll.Instance.CamTopLeft.x, CameraControll.Instance.CamBottomRight.x);
transform.position = new Vector3 (HorizontalClamp, MousePos.y, transform.position.z);
//Check what is under the mouse.
RaycastHit2D IsHoveringOver = Physics2D.Raycast(transform.position, transform.TransformDirection (Vector3.down), 0 , ignoredColliders);
if (IsHoveringOver.transform != null) {
if (GameManager.Instance.GroundColliders.value == LayerMask.GetMask (LayerMask.LayerToName (IsHoveringOver.transform.gameObject.layer)))
IsOnFloor = true;
}else IsOnFloor = false;
RaycastHit2D HitColider = Physics2D.Raycast(transform.position, transform.TransformDirection (Vector3.down), Mathf.Infinity , ~ignoredColliders);
#if UNITY_EDITOR
if (HitColider.collider != null) {
Debug.DrawLine( HitColider.point, transform.position, Color.magenta);
}
#endif
}
void OnMouseUp()
{
if (!IsOnFloor) objRB2D.isKinematic = false;
}
void OnCollisionEnter2D(Collision2D coll) {
//Debug.Log(coll.gameObject.transform.name);
}
I fixed the problem by using the Z axes to define wich of the colliders to be on top.
If you are using physics raycast in your drag-n-drop logic take a look at this documentation, there you will find a topic about LayerMask and Raycasting.