I'm working on a third person controller and I'm trying to prevent movement when a raycast in front of the player hits something. Currently when the player collides with a block the blend tree is set to 0 so that it shows the Idle animation. In this case the player appears to be not moving, which it obviously isn't because it's colliding with something, but the input of the user is still there.
This is visible when the player presses the jump button, because the player moves in the direction the user was initially trying to move. In the player controller there is a part that prevents the user from moving after the jump button has been pressed during the length of the jump (Until the player is grounded again). What I'm trying to achieve is that when the user is moving against something and presses the jump button the player will just jump up and won't go in the direction the user was initially trying to go, even if there is no more collider or obstruction.
Something like if the raycast hits something the user won't be able to add movement in that direction, but is still able to turn the player away from the object and move in that direction as long as the raycast doesn't hit a collider.
I'm no c# wizard, so most of the code below comes from a tutorial. I can write a raycast myself, but have no clue how to write the rest of what I'm trying to achieve.
Edit: Current script
public RaycastHit _Hit;
public LayerMask _RaycastCollidableLayers; //Set this in inspector, makes you able to say which layers should be collided with and which not.
public float _CheckDistance = 5f;
void Update()
{
PushStates();
// Input
Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
Vector2 inputDir = input.normalized;
bool running = Input.GetButton("Run");
if (IsNotBlocked())
{
Move(inputDir, running);
}
float animationSpeedPercent = ((running) ? currentSpeed / runSpeed : currentSpeed / walkSpeed * .5f);
anim.SetFloat("speedPercent", animationSpeedPercent, speedSmoothTime, Time.deltaTime);
anim.SetFloat("speedPercentPush", animationSpeedPercent, speedSmoothTime, Time.deltaTime);
// Check if walking or running
if (animationSpeedPercent < 0.51f)
{
if (ShortJumpRaycast())
{
if (Input.GetButtonDown("Jump") && Time.time > canJump)
{
ShortJump();
}
}
else
{
if (Input.GetButtonDown("Jump") && Time.time > canJump)
{
JumpWalk();
}
}
}
else
{
//Debug.Log("You are Running");
if (Input.GetButtonDown("Jump") && Time.time > canJump)
{
JumpRun();
}
}
JumpCheck();
}
void Move(Vector2 inputDir, bool running)
{
if (inputDir != Vector2.zero)
{
float targetRotation = Mathf.Atan2(inputDir.x, inputDir.y) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
}
float targetSpeed = ((running) ? runSpeed : walkSpeed) * inputDir.magnitude;
currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
velocityY += Time.deltaTime * gravity;
Vector3 velocity = transform.forward * currentSpeed + Vector3.up * velocityY;
controller.Move(velocity * Time.deltaTime);
currentSpeed = new Vector2(controller.velocity.x, controller.velocity.z).magnitude;
// Checks if player is not grounded and is falling
if (GroundCheck())
{
velocityY = 0;
anim.SetBool("onAir", false);
}
else
{
anim.SetBool("onAir", true);
}
}
bool IsNotBlocked()
{
Vector3 forward = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(transform.position, forward, out _Hit, _CheckDistance + 0.1f, _RaycastCollidableLayers))
if (_Hit.collider == null)
{
Debug.Log("Raycast hit nothing");
return true;
}
GameObject go = _Hit.collider.gameObject;
if (go == null) //If no object hit, nothing is blocked.
return true;
else //An object was hit.
return false;
}
Directly before move you can raycast in the direction of the movement and if it hits something you can cancel movement.
Something like:
Vector3 velocity = transform.forward * currentSpeed + Vector3.up * velocityY;
if(!Physics.Raycast(transform.position, transform.forward, distance)
{
controller.Move(velocity * Time.deltaTime);
}
else
{
controller.Move(Vector3.up * velocityY * Time.deltaTime);
}
Something like if the raycast hits something the user won't be able to add movement in that direction
Here is an example of how you can perform a raycast to check whether something was hit or not.
//Example raycast code
//Variables
public RayCastHit _Hit;
public LayerMask _RaycastCollidableLayers; //Set this in inspector, makes you able to say which layers should be collided with and which not.
public float _CheckDistance = 5f;
//Method
bool IsNotBlocked(){
Vector3 forward = transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(transform.position, forward, out _Hit, _CheckDistance + 0.1f, _RaycastCollidableLayers))
if (_Hit.collider == null)
{
Debug.Log("Raycast hit nothing");
return true;
}
GameObject go = _Hit.collider.gameObject;
if (go == null) //If no object hit, nothing is blocked.
return true;
else //An object was hit.
return false;
}
Basically,
The length of the ray is CheckDistance.
RayCastCollidableLayers determines which layers an object can be in for it to be collidable with the ray we create.
The code sets a direction stored in "forward", a raycast is then performed from the transform.position (position of object this script is attached to) in direction Vector3.Forward.
_Hit saves whatever the raycast hits. An object can then be accessed through it and stored as a GameObject. I call this object 'go'.
Feel free to ask questions.
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 would like my character to jump diagonally but when it hits the ground it slides.
private void Update()
{
if (Input.GetMouseButtonUp(0) && isGrounded())
{
//jump
timeHeld = 0;
Debug.Log("MouseButtonUp = true");
rb.AddForce(jumpDirection, ForceMode2D.Impulse);
}
else if (isGrounded())
{
rb.velocity = Vector2.zero;
}
}
this code somehow works but it glitches when the player hits the ground. I need to put some amount of force to get him out of isGrounded() or he will just get some pixels up and be instantly pushed down again.
This is my isGrounded() function
private bool isGrounded()
{
RaycastHit2D raycastHit = Physics2D.BoxCast(boxCollider2d.bounds.center, boxCollider2d.bounds.size, 0f, Vector2.down, 0.1f, platformLayerMask);
return raycastHit.collider != null;
}
the problem is obviously my distance which is 0.1f in Boxcast(). Any ideas how to fix that?
No need to check the ground by yourself.
Add a unity event called OnCollisionEnter
Inside, check if the collider is the ground
If true, then velocity = zero
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've made my own controller for the game I'm trying to make but I've ran into some issues with it. Whenever the enemy is approaching me and I don't stop my character in time, he teleports on top of the enemy's head.
Push away:
void OnControllerColliderHit(ControllerColliderHit hit)
{
if (hit.gameObject.GetComponent<EnemyController>())
{
Debug.Log("!");
float pushPower = 0.05f;
Vector3 pushDir = hit.transform.forward;
characterController.Move(pushDir * pushPower);
}
}
Movement:
void FixedUpdate()
{
RaycastHit hitInfo;
Physics.SphereCast(transform.position, characterController.radius, Vector3.down, out hitInfo, characterController.height / 2f);
desiredMove = Vector3.ProjectOnPlane(desiredMove, hitInfo.normal).normalized;
desiredMove = (transform.forward * Input.GetAxis("Vertical") + transform.right * Input.GetAxis("Horizontal")).normalized;
if(characterController.isGrounded)
{
moveDir.x = desiredMove.x * speed;
moveDir.z = desiredMove.z * speed;
isJumping = false;
if(Input.GetButtonDown("Jump"))
{
moveDir.y = jumpPower;
isWalking = false;
isJumping = true;
canRun = false;
}
if(Input.GetButton("Sprint") && canRun)
{
isWalking = false;
isRunning = true;
moveDir.x = desiredMove.x * runSpeed;
moveDir.z = desiredMove.z * runSpeed;
}
else
{
isRunning = false;
}
}
else
{
Falling();
}
characterController.Move(moveDir * Time.fixedDeltaTime);
}
I've decided to add a function to push player away if he's too close to the enemy but I can't really get it to work right. Right now it pushes player in the forward direction of the enemy so it works fine when you're approaching the enemy from the front, in other cases my player just teleports in front of the enemy. I wanted to push the player away to the direction he came from but I can't really think of any way to do it. Could anyone help me out with it?
To push away from the enemy, you would do something like:
Vector3 direction = (enemy.transform.position - player.transform.position).normalized;
player.transform.position += direction * DISTANCE_YOU_WANT_TO_PUSH;
In your case, since you're already dealing with a collision inside of OnControllerColliderHit, it would be more like:
Vector3 direction = (hit.point - transform.position).normalized
In both cases, you might want to set direction.y = 0 before normalizing it (if you only want to be pushed away along the ground plane, for example).
I am currently having trouble shooting a ball and adding velocity to it. The idea is that the longer you hold down "SPACE" the longer the ball will travel.
What I have so far is this for the player control:
public class PlayerControl : MonoBehaviour {
public float speed = 0f;
Vector3 enVector = new Vector3(10,0,0);
public bool laserDirection = false;
public Transform firePoint;
public GameObject RedBall;
public PlayerControl player;
// Use this for initialization
void Start () { }
// Update is called once per frame
void Update ()
{
// Press CTRL to move the platform under the ball and shoot a laser (NOT FINISHED)
if (Input.GetKey (KeyCode.LeftControl) && laserDirection == false)
{
transform.Translate(enVector * -speed * Time.deltaTime);
}
else if(Input.GetKey (KeyCode.LeftControl) && laserDirection == true)
{
transform.Translate(enVector * speed * Time.deltaTime);
}
// Sets the direction the platform will travel if pressed
if(Input.GetKey (KeyCode.LeftArrow))
{
laserDirection = false;
}
// Sets the direction the platform will travel if pressed
if(Input.GetKey (KeyCode.RightArrow))
{
laserDirection = true;
}
// Shoots a ball the longer you hold down
if(Input.GetKey (KeyCode.Space))
{
GetComponent<Rigidbody2D> ().velocity = new Vector2 (speed, GetComponent<Rigidbody2D> ().velocity.y);
Instantiate(RedBall, firePoint.position, firePoint.rotation);
}
}
// Destroy the ball
void OnTriggerEnter2D(Collider2D other)
{
Destroy (gameObject);
}
}
If you look where the get space button is you see the code I've made for the shooting. This only makes the ball travel to the right at a set speed I've chosen.
This is for changing the direction of the ball (It moves around the big black ball):
// THE DIRECTION OF THE BALL SCRIPT
public class RedBall : MonoBehaviour
{
public Transform target;
void Update()
{
// Moves the ball launcher to the left
if (Input.GetKey (KeyCode.LeftArrow))
{
transform.RotateAround (target.position, transform.forward, Time.deltaTime * 90f);
}
// Moves the ball launcher to the right
if (Input.GetKey (KeyCode.RightArrow))
{
transform.RotateAround (target.position, -transform.forward, Time.deltaTime * 90f);
}
}
}
I am struggling with how to figure out how to make the ball shoot in the direction it is facing, which depends on how it has been translated in the ball script. Additionally, how to make the ball react to the gravity when shot and how to shoot further the longer I hold hold the "SPACE" button.
If anyone knows how to do at least one of these things it would help a lot! Thank you.
(Instead of using Rigidbody2D.velocity try to use Rigidbody2D.AddForce. For turning gravity on and off use Rigidbody2D-gravityScale.
I'm not quite sure what you mean with "the longer I hold the "SPACE button". Do you want to make it a "Spring" and release it when the Space button was released?
edit:
maybe do it like this. make initial force a variable and "Load it" while the button is pressed, when it is release, instantiate the Ball and add the force to it.
i justed typed this out of the head, without testing so maybe it wont run out of the box, but the direction should be clear
if (Input.GetKey(KeyCode.Space))
{
initialForce += 0.1f;
GetComponent<Rigidbody2D>().velocity = new Vector2(speed, GetComponent<Rigidbody2D>().velocity.y);
Instantiate(RedBall, firePoint.position, firePoint.rotation);
}
else
{
if (initialForce > 0)
{
var ball = (GameObject)Instantiate(RedBall, firePoint.position, firePoint.rotation);
ball.GetComponent<Rigidbody2D>.AddForce(firePoint.rotation * Vector2.one * initialForce);
}
initialForce = 0f;
}
So I am struggling with how to figure out how to make the ball shoot
in the direction it is facing depending on how it have been translated
in the ball script.
Multiply speed by the direction you want to shoot in:
// Shoots a ball the longer you hold down
if(Input.GetKey (KeyCode.Space)) {
GetComponent<Rigidbody2D> ().AddForce(transform.forward * speed);
Instantiate(RedBall, firePoint.position, firePoint.rotation);
}
Also look into object pooling, you really shouldnt be instantiating projectiles it takes alot of memory to instantiate and destroy all the time during execution.
Also how to make the ball have the gravity when shot
protected float gravity = 1f;
protected bool isShot = false;
// Update is called once per frame
void Update ()
{
if(isShot)
rigidBody.velocity.z += gravity * Time.deltaTime;
}
shoot longer the longer I hold hold the "SPACE" button.
public float rate = 1.0f;
protected float power = 0f;
// Update is called once per frame
void Update ()
{
if (Input.GetKeyUp (KeyCode.Space))
{
UsePower(power);
power = 0f;
}
if (Input.GetKey (KeyCode.Space))
{
power += rate * Time.deltaTime;
}
}
void UsePower (float _power)
{
// Use power here
}