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;
Related
I am creating a 2d mobile game where one of the scripts uses a joystick to move and the other script lets the player shoot an object when tapping anywhere on the screen. The issue is when using the joystick it also shoots at the same time in that direction. Is there a way to separate the touches so when you use the joystick it does not immediately shoot to that direction but the player can still move and shoot anywhere at the same time?
Move Code
private void Update()
{
Vector2 moveInput = new Vector2(joystick.Horizontal, joystick.Vertical);
moveAmount = moveInput.normalized * speed;
}
Shoot code
private void Update()
{
Vector2 direction = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
Quaternion rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);
transform.rotation = rotation;
if(Input.GetMouseButton(0))
{
if (Time.time >= shotTime)
{
Instantiate(projectile, shotPoint.position, transform.rotation);
shotTime = Time.time + timeBetweenShots;
}
}
}
Instead of using Input.mousePosition you'll have to use Input.GetTouch. You can loop through it using Input.touchCount to find the first touch that is not interacting with a ui element, than use that touch instead of Input.mousePosition to find the direction to shoot (or not shoot if there is no touch). To find out if a specific touch is over ui you need a reference to the scene's EventSystem (or use EventSystem.current), and use EventSystem.IsPointerOverGameObject with Touch.fingerId.
If the joystick is not a ui element you'll need a different way to detect if the touch is over the joystick. For example you could check the pixel position, or see if the joystick itself has an "interacting fingerId". But with the assumption that the joystick is an ui element, here's one way to do what I wrote above: (untested)
private void Update()
{
var eventSystem = EventSystem.current;
for (var i = 0; i<Input.touchCount; i++)
{
var touch = Input.GetTouch(i);
if (eventSystem.IsPointerOverGameObject(touch.fingerId))
{
continue;
}
ShootToScreenPos(Vector2 screenPos);
break;
}
}
private void ShootToScreenPos(Vector2 screenPos)
{
Vector2 direction = Camera.main.ScreenToWorldPoint(screenPos) - transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
Quaternion rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);
transform.rotation = rotation;
if (Time.time >= shotTime)
{
Instantiate(projectile, shotPoint.position, transform.rotation);
shotTime = Time.time + timeBetweenShots;
}
}
I'm new to Unity 3D and I've started to study and learn player and camera mechanics these past few weeks.Although, I have a simple character controller system with a Cinemachine free look cam following the player, I need help incorporating a camera relative player movement mechanic into my project. I therefore need help incorporating such a mechanism into my system. I've added my PlayerController Code below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] public float _playerSpeed;
[SerializeField] public Rigidbody rb;
[SerializeField] private float _jumpforce = 100 ;
[SerializeField] private float fallMultiplier;
[SerializeField] private float lowJumpMultiplier;
public bool isGrounded;
public bool jumpReady;
public float jumpcoolDownTimer = 1.5f;
public float jumpcoolDownCurrent = 0.0f;
[SerializeField] private float sensi;
private float rotX;
private float rotY;
private Vector3 rotate;
int isJumpingHash;
int isFallingHash;
int isLandingHash;
public Animator animator;
Vector3 vel;
// Start is called before the first frame update
void Start()
{
jumpcoolDownCurrent = jumpcoolDownTimer;
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
animator = GetComponent<Animator>();
//Converting string values to hash values to save memory
isJumpingHash = Animator.StringToHash("isJumping");
isFallingHash = Animator.StringToHash("isFalling");
isLandingHash = Animator.StringToHash("isGrounded");
var cam = Camera.main;
}
// Update is called once per frame
void Update()
{
Move();
Jump();
}
private void OnCollisionEnter(Collision collision){
if(collision.gameObject.tag == "Surface"){
isGrounded = true;
}
}
private void Move()
{
vel = new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical")) * _playerSpeed;
if(Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.W)){
transform.position += transform.forward * Time.deltaTime * _playerSpeed;
}
}
private void Jump(){
vel.y = rb.velocity.y;
rb.velocity = vel;
//Jump Cooldown timer
if(jumpcoolDownCurrent >= jumpcoolDownTimer){
jumpReady = true;
}
else{
jumpcoolDownCurrent += Time.deltaTime;
jumpReady = false;
}
bool jump = animator.GetBool(isJumpingHash);
bool fall = animator.GetBool(isFallingHash);
bool land = animator.GetBool(isLandingHash);
//Jump
if(Input.GetKeyDown(KeyCode.Space) && isGrounded && jumpReady){
rb.AddForce(Vector3.up * _jumpforce, ForceMode.Impulse);
isGrounded = false;
jumpcoolDownCurrent = 0.0f;
animator.SetBool(isJumpingHash, true);
jump = true;
}
//Fall
if((rb.velocity.y <= 0 && !isGrounded)){
rb.velocity += Vector3.up * Physics.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
animator.SetBool(isFallingHash, true);
fall = true;
}
//Land
if(isGrounded && fall){
fall = false;
animator.SetBool(isFallingHash, false);
animator.SetBool(isLandingHash, true);
}
//Back to 2d movement
if(isGrounded && rb.velocity.y <= 0){
animator.SetBool(isLandingHash, false);
land = false;
animator.SetBool(isJumpingHash, false);
jump = false;
animator.SetBool(isFallingHash, false);
fall = false;
}
}
}
I've referred to several different YouTube tutorials and and also surfed different forums to find a solution, but to no avail. But I did notice that all these users were using the Quaternion Rotation mechanics and cam transforms. Upon attempting to incorporate these codes into my project, the camera continuously rotates as I try to rotate the camera in one particular direction, or the A and D (strafe left and right) animations weren't functioning properly.
P.S -> For those of you who don't know, camera relative moment is the mechanic that most third person games use now, where the player while in movement, turns along the rotation and the camera and runs in the direction the camera is facing. Good examples of that would be God of War (2018), Batman Arkham Series etc.
I highly recommend that you don't use CineMachine, I have never seen anyone able to use it successfully and it is very simple to make your own camera controller after you study for a few days (or pay for someone to make one for you).
To make Camera Relative Movement in a platformer game similar to God of War, you should look into transform.Rotation, EulerAngles and Quaternions (Penny De Byl has good online classes and books on dealing with Quaternions, they're Vector4 and very difficult to wrap your head around)
Then, throughout your game, add a bunch of walls with a trigger collider attached that rotate the camera to 1 angle or another when the player crosses them.
You might even want to make the camera a child of the player object then it will always have the perfect angle and move when the player rotates.
If you ask a more specific question later, then a more specific answer can be given.
Using Camera.main.transform.forward you can get the forward direction relative to the camera. You can use Camera.main.transform.right to get the right direction. If you set the y coordinate of these to zero, you can use these to define the direction you travel in. You would then need to normalize it, and multiply it by your input.
So, here is what I would do:
get forward direction for camera
scale it so that the vertical component is zero
normalize it so that you get a normalized directional vector
multiply by your vertical movement to get your vector for movement forward and back
do the same for right and left movement
add the 2 parts together
multiply by speed to get your velocity
Like this:
vel = (Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized * Input.GetAxis("Vertical"))
+ (Vector3.Scale(Camera.main.transform.right, new Vector3(1, 0, 1)).normalized * Input.GetAxis("Horizontal")))
* playerSpeed;
Also, while it is not relevant to your issue at the moment, it is best to normalize your input vector. So do something like: Vector3 inputDirection = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0f).normalized. Otherwise, diagonal movement ends up being much faster than movement in one direction.
{
private LineRenderer lineRenderer;
public Transform LaserHit; //this all works
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
lineRenderer.useWorldSpace = true; //this works too
}
void Update()
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.up);
LaserHit.position = hit.point;
lineRenderer.SetPosition(0, transform.position); //the laser is shown like i want
lineRenderer.SetPosition(1, LaserHit.position);
}
}
I have this laser script and it works! A laser IS shown, I want my player to die to the laser i have the death animation and respawning ALREADY MADE. For The Player To Die He has TO touch a BoxCollider2D preferebly one set to trigger. I want to put the BoxCollider2D (preferebly set to trigger) for as long as the laser beam is, so that you dont die where thre isnt a laser beam shown.
I hope i made myself clear and Please anwser :D thanks
You could do something like
public Collider collider;
and then
var delta = LaserHit.position - transform.position;
collider.transform.position = (LaserHit.position + transform.position) * 0.5f;
collider.transform.right = delta;
collider.transform.localScalee = new Vector3(delta.magnitude, 0.1f, 1f);
I would like to make a smooth jump towards the nearest cube. I already have a script to detect the closest cube. I want that the X-axis is locked, so only the Y-axis and the Z-axis change when jumping. I would like to use a Jump animation when jumping. I already tried to use Vector3MoveTowards, but that didn't really work well, maybe I didn't use it properly.
Detect nearest cube where the player should jump to (C#)
void Update()
{
FindClosestCube ();
GameObject closestCube = FindClosestCube ();
Debug.Log (closestCube);
}
GameObject FindClosestCube() {
GameObject[] gos;
gos = GameObject.FindGameObjectsWithTag("cube");
GameObject closest = null;
float distance = Mathf.Infinity;
float position = transform.position.z;
foreach (GameObject go in gos) {
float diff = go.transform.position.z - position;
float curDistance = diff;
if (curDistance < distance) {
closest = go;
distance = curDistance;
}
}
return closest;
}
The tricky part is that at some cubes you have to jump up (y+1), with some cubes you jump towards the same Y (y+0) and with some cubes you jump down (y-1).
How do I do this?
Image of how it looks like:
EDIT: I have this code right now:
----------------C#-----------------
Rigidbody rb;
public int clicks = 0;
Vector3 target;
public Animation jumpAnimation;
bool jump = false;
float cubeDiffY;
bool movePlayer;
public float smoothTime = 0.3f;
public float yVelocity = 0.0f;
void Start()
{
rb = GetComponent<Rigidbody> ();
}
void Update ()
{
FindClosestCube ();
GameObject closestCube = FindClosestCube ();
Debug.Log ("Closestcube = " + closestCube);
target = closestCube.transform.position + new Vector3 (0f, 0.7f, 0f);
cubeDiffY = target.y - transform.position.y;
movePlayer = true;
Debug.Log("Cube Difference Y-axis = " + Mathf.Round(cubeDiffY));
if (Input.GetMouseButtonDown (0))
{
clicks += 1;
jump = true;
jumpAnimation = gameObject.GetComponent<Animation>();
//jumpAnimation.Play ();
}
if (jump == true)
{
Jump ();
}
}
void Jump()
{
float newPosition = Mathf.SmoothDamp (transform.position.y, target.y, ref yVelocity, smoothTime);
transform.position = new Vector3 (0, newPosition, transform.position.z);
}
I calculated the difference in Y-axis between the cube where the player is standing on and the closestCube. But the Jump() doesn't work. How do I fix that?
Okay I set up a quick version of your game and got what you wanted to work, it is not exactly a quick solution, because what your doing doesn't have built in functionality for other than using animations.
Here is the character script that has all the code you need and commented thoroughly so it should explain itself.
using UnityEngine;
public class Character : MonoBehaviour
{
//the collider for the player
private new BoxCollider collider;
//the jump box collider on a empty game object that is a child to the player object
public BoxCollider JumpBox;
//the offset of the cube so it doesn't stop inside of it
public Vector3 cubeOffset;
//how high the jump will be
public float JumpHeight;
//how fast the jump will be
public float JumpSpeed;
//holds the change in position the jump will produce
private Vector3 jumpDelta;
//holds the destination cube the jump is attempting to hit
private Cube destinationCube;
//true if a jumping animation is currently playing
private bool jumping = false;
//used to swap the jump direction from up to down
private bool jumpDirection = true;
//used to hold the position of the jump so it knows when to stop
private float jumpPosition = 0;
// Use this for initialization
void Start()
{
collider = GetComponent<BoxCollider>();
}
// Update is called once per frame
void Update()
{
if(jumping)
{
//move straight towards the cube
transform.position = transform.position + (JumpSpeed * jumpDelta);
//move up and down to simulate a jump
//check the current move direction
if (jumpDirection)
{
//add to the jump position twice product of the JumpHeight the JumpSpeed so that it will
//rise and fall the same amount of time it takes to move to the destination
jumpPosition += JumpHeight * JumpSpeed * 2;
//if it has passed the jump height reverse the jump direction
if (jumpPosition >= JumpHeight)
jumpDirection = !jumpDirection;
transform.position += transform.up * JumpHeight * JumpSpeed * 2;
}
//the jump direction is going down
else
{
jumpPosition -= JumpHeight * JumpSpeed * 2;
transform.position -= transform.up * JumpHeight * JumpSpeed * 2;
}
//check if the character collider intersects witht he cubes collider
//if it has then stop jumping and set the final position as the destination position
if (collider.bounds.Intersects(destinationCube.BoxCollider.bounds))
{
jumping = false;
transform.position = destinationCube.transform.position + cubeOffset;
}
}
//detect a jump
if (Input.GetKeyDown(KeyCode.Space))
{
//detect all hits on the jump box
Collider[] hits = Physics.OverlapBox(JumpBox.center, JumpBox.size * 0.5f);
//get the closest collider with the right tag
Collider result = GetClosestColliderWithTag(hits, "Cube");
//if we have a result then begin the jumping animation
if(result != null)
{
//gets the destination cubes cube component(the custom class you have on your cubes)
destinationCube = result.gameObject.GetComponent<Cube>();
//calculate the jump delta
jumpDelta = (result.transform.position + cubeOffset) - transform.position;
//remove the left and right components so the jumping doesnt move to the left or right of the player
Vector3 component = Vector3.Project(jumpDelta, -transform.right);
jumpDelta -= component;
component = Vector3.Project(jumpDelta, transform.right);
jumpDelta -= component;
//setup the jump animation control fields to the initial values
jumpPosition = 0;
jumpDirection = true;
jumping = true;
}
}
}
private Collider GetClosestColliderWithTag(Collider[] colliders, string tag)
{
//just gets the closest collider
float distance = float.MaxValue;
int result = -1;
for (int i = 0; i < colliders.Length; i++)
{
if (colliders[i].tag == tag)
{
float distanceTemp = Vector3.Distance(transform.position, colliders[i].transform.position);
if (distanceTemp < distance)
{
distance = distanceTemp;
result = i;
}
}
}
if (result != -1)
return colliders[result];
else return null;
}
}
And here is my cube script which has some things you will need to add
using UnityEngine;
public class Cube : MonoBehaviour {
//these arent important just fields I used to set up a quick version of your game
public GameObject StartPoint;
public GameObject EndPoint;
public float Speed;
private Vector3 directionVector;
private bool direction;
//YOU WILL NEED THIS!!
[HideInInspector]
public BoxCollider BoxCollider;
// Use this for initialization
void Start() {
//not important
directionVector = EndPoint.transform.position - StartPoint.transform.position;
directionVector.Normalize();
//DONT FORGET TO SET YOUR BOX COLLIDER
BoxCollider = GetComponent<BoxCollider>();
}
// Update is called once per frame
void Update()
{
float distance = 0;
if (direction)
{
distance = Vector3.Distance(EndPoint.transform.position, transform.position);
transform.position += directionVector * Speed;
if (distance < Vector3.Distance(EndPoint.transform.position, transform.position))
direction = !direction;
}
else
{
distance = Vector3.Distance(StartPoint.transform.position, transform.position);
transform.position -= directionVector * Speed;
if (distance < Vector3.Distance(StartPoint.transform.position, transform.position))
direction = !direction;
}
}
}
Previous Answer
I would say you need to calculate the perceived position of the object in the future.
Vector3 futurePos = cubePos + (cubeMoveDirection * cubeMoveSpeed);
Once you have the future position, even if it is not exact, you should aim your animation towards that position. To do this I would have the animation change a speed vector instead of an actual transforms position that way we can rotate this speed vector in any direction you want while keeping the orientation of the block. Otherwise you have to rotate the entire block to point towards the direction you want. If this is what you want then put your block under a empty gameobject, rotate the empty gameobject to point to where you want and do the speed calculations only.
Next your animation should have a net move vector which should be pre-calculated and scaled down or up to meet the distance to the future position. It will look something like this(note this is not tested)
//class fields
Vector3 AnimatedSpeed;
Vector3 AnimationDelta;
//basic calculation
//get the direction vector from the players current position to the future
block position
Vector3 dirVector = futurePos - transform.position;
//find the rotation from the current orientation to the direction vector
Quaternion rotation = Quaternion.FromToRotation(transform.forward, dirVector);
//calculate the distance from you to the cube and scale it with the magnitude of the AnimationDelta
float result = Vector3.Distance(transform.position, futurePos);
result = result / animationDelta.magnitude;
//finally rotate the forward vector by the rotation and multiply it by the
//animation speed and the result to get the step by step movement as
//the animation plays. NOTE: The animation should be based on forward direction
transform.position += (AnimationSpeed * rotation) * result * Time.deltaTime;
Hopefully this does it, like I said I haven't tested it at all so you may have to do some tweaking based on your particular case as this is essentially psuedo-code.
Good luck! I'm off to bed I'll check back when I wake up.
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.