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;
}
}
Related
I want to move my player character(human) on a curved surface. But at the same time character shall stay perpendicular to the surface normals and it should face in the movement direction and can handle collisions(if there is a wall ahead, shall not be able to go through it).
I tried to make a parent stay over normals and change the child local rotation towards direction of motion of its parent. But it has several limitations as of now.
Here is the code what i was using:
[SerializeField] float raycastLength = 1f;
bool canPlayerMove = true;
public float speed = 2f;
public Vector3 offset; //object's position offset to ground / surface
public Quaternion childDirection;
private void Update()
{
float moveHorizontal = SimpleInput.GetAxis("Horizontal");
float moveVertical = SimpleInput.GetAxis("Vertical");
Ray ray = new Ray(transform.position, -transform.up);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo, raycastLength))
{
transform.rotation = Quaternion.LookRotation(Vector3.up, hitInfo.normal);
transform.position = hitInfo.point + offset;
Debug.DrawLine(ray.origin, hitInfo.point, Color.red);
}
if (canPlayerMove)
{
Vector3 movement = new Vector3(moveHorizontal, 0, moveVertical);
if (movement != Vector3.zero)
{
childDirection = Quaternion.Slerp(transform.GetChild(0).localRotation, Quaternion.LookRotation(movement), 0.15F);
transform.GetChild(0).localRotation = childDirection;
}
transform.Translate(movement * speed * Time.deltaTime, Space.Self);
}
}
first to not make your player go thru walls you want to add a collider to your walls and not set it as trigger, you will also need a rigidbody on your player and this will help in the next steps.
Secondly you will need to acces the rigidBody in code using this: (if you Check Use Gravity it will also stay on your terrain that you made)
private Rigidbody rb;
private float speed = 7.5f;
private void Start()
{
//this gets the rigidbody on the gameObject the script is currently on.
rb = this.GetComponent<Rigidbody>();
}
private void Update()
{
float hor = Input.GetAxis("Horizontal");
float vert = Input.GetAxis("Vertical");
//this will move your player frame independent.
rb.MovePosition(this.transform.position + new Vector3(hor, 0, vert) * speed *
Time.deltaTime);
}
Also make sure that you have a rigidBody on your player, else it will throw an error.
After my player object touches the wall it sometimes starts to move and rotate on it's own. I tried to increase player object weight and it helps, but i don't think it's a good approach as tiny movement never dissapears.
Player is rigidbody with box collider attached.
isKinematic - false;
useGravity - true;
XYZ rotation is fixed and Y coordinate position is fixed.
Walls have box collider but have no rigidbody.
Ground has no collider as Y position is fixed and player object doesn't touch the ground.
Game is network based so i have photon rigidbody view component attached to player as well.
Code (C#) which moves player:
public void Update()
{
if (!photonView.IsMine || !controllable)
{
return;
}
if (shootingTimer > 0.0)
{
shootingTimer -= Time.deltaTime;
}
m_MovementInputValue = Input.GetAxis(m_MovementAxisName);
if (m_MovementInputValue == 0.0f)
{
m_MovementInputValue = joystick.Vertical;
}
m_TurnInputValue = Input.GetAxis(m_TurnAxisName);
if (m_TurnInputValue == 0.0f)
{
m_TurnInputValue = joystick.Horizontal;
}
Vector3 vector = new Vector3(
m_TurnInputValue,
rigidbody.velocity.y,
m_MovementInputValue
);
MovementVector = Quaternion.AngleAxis(60, Vector3.up) * vector;
EngineAudio();
}
public void FixedUpdate()
{
if (!photonView.IsMine || !controllable)
{
return;
}
Move();
Turn();
}
private void Move()
{
// Adjust the position of the tank based on the player's input.
// Vector3 movement = transform.forward * m_MovementInputValue * m_Speed * Time.deltaTime;
// rigidbody.MovePosition(rigidbody.position + movement);
Vector3 movement = MovementVector * m_Speed * Time.deltaTime;
rigidbody.MovePosition(rigidbody.position + movement);
}
private void Turn()
{
// Adjust the rotation of the tank based on the player's input.
// float turn = m_TurnInputValue * m_TurnSpeed * Time.deltaTime;
// Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);
// rigidbody.MoveRotation(rigidbody.rotation * turnRotation);
if (m_TurnInputValue != 0.0f || m_MovementInputValue != 0.0f)
{
rigidbody.rotation = Quaternion.LookRotation(MovementVector);
}
}
Check to make sure the component (such as a rigidbody) is attached to the right object (gameobject - parent vs. child objects). Also, the fact that it is constantly rotating is curious... it makes me think it has something to do with the Time.DeltaTime, but I see that's only being applied to your Vector3 movement, and not in your rotation function... Just curious, what happens when you remove the Time.DeltaTime part from your movement Vector3?
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 have colliders where i can turn to the sides and i want camera to rotate as a player turn to the side. I'm using Mathf.LerpAngle for that, but when I press the key for turn to side, camera is rotating in loop. How can I make rotation stop?
The problem is that everytime I turn player should go -90 degrees to the left +90 to the right and there will be more turns so i can't use functions for setting rotation.
I was already trying to make it stop by that if statement with (lAngle > 90f)
float lAngle = Mathf.LerpAngle(minAngle, lMaxAngle, Time.deltaTime);
float rAngle = Mathf.LerpAngle(minAngle, rMaxAngle, Time.deltaTime);
Quaternion leftRotation = Quaternion.Euler(new Vector3 (0, lAngle, 0));
Quaternion rightRotation = Quaternion.Euler(new Vector3 (0, rAngle, 0));
transform.position = player.transform.position + offSet;
transform.LookAt (player.transform);
if (Input.GetKeyDown (KeyCode.LeftArrow) && GameObject.Find("Player").GetComponent<PlayerMovement>().turn) {
turnLeft = true;
} else if (Input.GetKeyDown (KeyCode.RightArrow) && GameObject.Find("Player").GetComponent<PlayerMovement>().turn) {
turnRight = true;
}
if(turnLeft) {
offSet = leftRotation * offSet;
transform.position = player.transform.position + offSet;
transform.LookAt (player.transform);
if (lAngle > 90f)
turnLeft = false;
}
if(turnRight) {
offSet = rightRotation * offSet;
transform.position = player.transform.position + offSet;
transform.LookAt (player.transform);
if (rAngle < -90f)
turnRight = false;
}
An alternative would be to use Quaternion.RotateTowards, as this allows rotations to negative values without the GameObject infinitely rotating (as it can't approach a negative value). Below you can see I'm storing the initial value of the GameObject's rotation, as a Vector3, before modifying this value whenever A or D keys are pressed. The rotation of the transform is then set to the result of Quaternion.RotateTowards.
public class FixedRotate : MonoBehaviour
{
[SerializeField]
private float m_rotationAngle;
[SerializeField]
private float m_rotationSpeed;
private Vector3 m_targetRotation;
public void Start()
{
m_targetRotation = transform.eulerAngles;
}
public void Update()
{
//Left
if(Input.GetKeyDown(KeyCode.A))
m_targetRotation.y -= m_rotationAngle;
//Right
if (Input.GetKeyDown(KeyCode.D))
m_targetRotation.y += m_rotationAngle;
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(m_targetRotation), m_rotationSpeed);
}
}
The unfortunate thing about this approach is that you can't specify the amount of time a rotation takes, only the incremental value of which the object will rotate by. An alternative approach would be to use Mathf.Clamp to add Time.deltaTime / rotationTime to the existing y rotation of the GameObject and stop once it's reached +/- 90. Hope this helps.
I'm wanting to have a script that when I click in my scene, my player will rotate and have a force added to it and will travel until it has reached the clicked point in my scene.
Right now I have it working using Vectors and having my player lerp from one point to another. But I want to amend it so I can use physics and get a better sense of my player moving. Like have him speed up to start moving and slowing down as he reaches my target loction
My script now looks like this
public GameObject isActive;
public float speed;
public Ray ray;
public Rigidbody rigidBody;
public Vector3 targetPoint;
// Use this for initialization
void Start ()
{
targetPoint = transform.position;
}
// Update is called once per frame
void Update ()
{
}
void FixedUpdate()
{
if (Input.GetMouseButton(0))
{
targetPoint = Camera.main.ScreenToWorldPoint (Input.mousePosition);
ChangeRotationTarget ();
}
Quaternion targetRotation = Quaternion.LookRotation (targetPoint - transform.position);
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, speed * Time.deltaTime);
rigidbody.position = Vector3.Lerp(transform.position, targetPoint, speed * Time.fixedDeltaTime);
}
void ChangeRotationTarget ()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane playerPlane = new Plane (Vector3.up, transform.position);
float hitdist = 0.0f;
if (playerPlane.Raycast (ray, out hitdist))
{
targetPoint = ray.GetPoint (hitdist);
}
}
However, when I run this, he just slides from one point to another. Regardless of the drag or mass I put in my rigidbody.
Can someone help me make my changes? Or point me in the right direction
I didn't had time to test this, but it should almost be what you are looking for. Just apply your Quaternion and Rotation codes to it.
void FixedUpdate() {
if (Input.GetMouseButton(0))
{
targetPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (rigidBody.position != targetPoint)
{
reachedTargetPoint = false;
if (reachedTargetPoint == false)
{
GetComponent<Rigidbody>().AddForce(speed);
}
}
//zoneBeforeReachingTP should be how much units before reaching targetPoint you want to start to decrease your rigidbody velocity
if (rigidBody.position == (targetPoint - zoneBeforeReachingTP))
{
//speedReductionRate should be how much of speed you want to take off your rigidBody at the FixedUpdate time rate
rigidBody.velocity = speedReductionRate;
}
if(rigidBody.position == targetPoint)
{
rigidBody.velocity = new Vector3(0, 0, 0);
reachedTargetPoint = true;
}
ChangeRotationTarget();
}
}
That's because you modify the rigidbody.position, and your logic overrides the physics. What you have to do instead is to ApplyForce every physics frame, probably with ForceMode.Force or ForceMode.Acceleration.