I have a character (moved with the keyboard's arrows). I have several walls on my house.
The thing is, I wanted to detect collisions when I go against a wall (so it won't go through). It's ok. But now, when my character goes against a wall, he does weird things, he moves alone like he was in a gravity mode (I don't know if understandable).
So, I'd like that, when I go against a wall, character stops moving ? I've tried a lot of things and I'm kinda lost atm so if you have any idea I'll take it and try ! Just for the record,started using unity a few months ago, so there might errors in my script ( I mean, it compiles but maybe its not written the best way).
Here's my script :
public class ScriptCharacter : MonoBehaviour
{
private Animator m_animator;
private Rigidbody m_rigidBody;
private void Start()
{
m_animator = gameObject.GetComponent<Animator>();
m_animator.SetFloat("Speed", 1);
}
// Update is called once per frame
private void FixedUpdate()
{
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
transform.Translate(transform.forward * v * Time.deltaTime, Space.World);
transform.Rotate(0, h * Time.deltaTime * 30, 0);
m_animator.SetFloat("Speed", v);
}
void OnCollisionEnter(Collision collision)
{
Debug.Log("here");
if (collision.gameObject.name == "Wall")
{
m_rigidBody.velocity = Vector3.zero;
m_rigidBody.angularVelocity = Vector3.zero;
m_animator.SetFloat("Speed", 0);
}
}
}
As Chestera mentioned in the comments:
Set the rigidbody to kinematic in onCollisionEnter() and set it back to dynamic in onCollisionExit().
From the Unity docs, "If isKinematic is enabled, Forces, collisions or joints will not affect the rigidbody anymore."
https://docs.unity3d.com/ScriptReference/Rigidbody-isKinematic.html
Related
I'm programming a top-down survival type game, and I added slimes that have a simple AI, which just follows the player when in range. I want to make it so when the slimes touch the player, you will take damage, and the slime will get knocked back a little. This is the code I have to add force to the slime. I've tried reversing the kbDirection, the player and slime are both not kinematic, the "KB" debug does show up when they collide, and the player still loses health.
public void AddKnockback()
{
Vector2 kbDirection = transform.position - player.transform.position;
kbDirection.Normalize();
slimeRb.AddForce(kbDirection * 100, ForceMode2D.Impulse);
Debug.Log("KB");
}
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Player")
{
player.GetComponent<PlayerActions>().playerHealth--;
AddKnockback();
}
}
I suspect that it might have something to do with the way the slime's movement is calculated. This is what I have for the movement:
public void AddKnockback()
{
Vector2 kbDirection = transform.position - player.transform.position;
kbDirection.Normalize();
slimeRb.AddForce(kbDirection * 100, ForceMode2D.Impulse);
Debug.Log("KB");
}
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Player")
{
player.GetComponent<PlayerActions>().playerHealth--;
AddKnockback();
}
}
Most prob your slime follows the target by overriding its position. like slime.transform.position = targetPosition; That is why the force effect can't change the position of the slime. That's only an assumption since I don't know how you move them. But sounds possible to me that is why I shared it with you.
So i made an ball(player) which moves forward on it's own with script. I want to make that ball act like a normal ball. when it riches the edge of platform it won't fall off. Basicaly it stops on the edge. Here's my image:
Here's my controller script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SwerveInputSystem : MonoBehaviour
{
private float _lastFrameFingerPositionX;
private float _moveFactorX;
public float MoveFactorX => _moveFactorX;
void Start(){
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
_lastFrameFingerPositionX = Input.mousePosition.x;
}
else if (Input.GetMouseButton(0))
{
_moveFactorX = Input.mousePosition.x - _lastFrameFingerPositionX;
_lastFrameFingerPositionX = Input.mousePosition.x;
}
else if (Input.GetMouseButtonUp(0))
{
_moveFactorX = 0f;
}
}
}
This is Second script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour{
private SwerveInputSystem _swerveInputSystem;
[SerializeField] private float swerveSpeed = 5f;
[SerializeField] private float maxSwerveAmount = 1f;
[SerializeField] private float verticalSpeed;
void Start(){
_swerveInputSystem = GetComponent<SwerveInputSystem>();
}
void Update(){
float swerveAmount = Time.deltaTime * swerveSpeed * _swerveInputSystem.MoveFactorX;
swerveAmount = Mathf.Clamp(swerveAmount, -maxSwerveAmount, maxSwerveAmount);
transform.Translate(swerveAmount, 0, 0);
float verticalDelta = verticalSpeed * Time.deltaTime;
transform.Translate(swerveAmount, verticalDelta, 0.1f);
}
}
EDITED: Adjusted the answer since we now have some source code.
You are positioning the player directly (using its transform) which will mess up the physics. The purpose of a rigidbody is to let Unity calculate forces, gravity, and so on for you. When you are using physics, and you want to move an object you have three main options:
Teleporting the object to a new position, ignoring colliders and forces like gravity. In this case use the rigidbody's position property.
_ourRigidbody.position = new Vector3(x, y, z);
Moving the object to the new position, similar to teleporting but the movement can be interrupted by other colliders. So, if there is a wall between the object and the new position, the movement will be halted at the wall. Use MovePosition().
_ourRigidbody.MovePosition(new Vector3(x, y, z));
Adding some force to the object and letting the physics engine calculate how the object is moved. There are several options like AddForce() and AddExplostionForce(), etc... see the Rigidbody component for more information.
_ourRigidbody.AddRelativeForce(new Vector3(x, y, z));
In your case you can simply remove the transsform.Translate() calls and instead add some force like this:
//transform.Translate(swerveAmount, 0, 0);
//transform.Translate(swerveAmount, verticalDelta, 0.1f);
Vector3 force = new Vector3(swerveAmount, verticalDelta, 0);
_ourRigidbody.AddForce(force);
We can get the _ourRigidbody variable in the Awake() or Start() method as normal. As you can see I like the Assert checks just to be safe, one day someone will remove the rigidbody by mistake, and then it is good to know about it...
private SwerveInputSystem _swerveInputSystem;
private Rigidbody _ourRigidbody;
void Start()
{
_swerveInputSystem = GetComponent<SwerveInputSystem>();
Assert.IsNotNull(_swerveInputSystem);
_ourRigidbody = GetComponent<Rigidbody>();
Assert.IsNotNull(_ourRigidbody);
}
One likely reason your Rigidbody is not being affected by gravity is due to having the field isKinematic checked to on. From the Rigidbody docs, when toggling on isKinematic,
Forces, collisions or joints will not affect the rigidbody anymore.
The rigidbody will be under full control of animation or script
control by changing transform.position
As gravity is a force and no forces act on the object when this setting is checked, your object will not fall when it is no longer on the platform. A simple solution is to uncheck this box.
I have a SMALL a problem. In Unity 3D 2020 Beta, I've put a player with a sphere collider on it and some cubes (walls) with box colliders. I've added a player controller script to the player object.
I've put the camera above the plane where the player and the walls are on, and I've made that the player should rotate to face the mouse position. I used rigidbody.AddForce for movement in a FixedUpdate function.
The player controller script is attached below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Keys")]
public KeyCode forward;
public KeyCode backward;
public KeyCode left;
public KeyCode right;
public KeyCode fire;
[Header("Health")]
public int hitpoints = 3;
[Header("Movement")]
public float speed;
public float turningSpeed;
[Header("Shooting")]
public GameObject bulletPrefab;
public Transform bulletSpawner;
public float bulletSpeed;
public float reloadTime;
private float currentReload;
private Rigidbody rb;
private Quaternion targetRotation;
void Start()
{
rb = GetComponent<Rigidbody>();
currentReload = reloadTime;
}
void LateUpdate()
{
if (hitpoints == 0)
Die();
// Rotation
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
targetRotation = Quaternion.LookRotation(hit.point - transform.position);
Debug.DrawLine(transform.position, hit.point, Color.white);
}
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turningSpeed * Time.deltaTime);
transform.eulerAngles = new Vector3(0, transform.rotation.eulerAngles.y, 0);
currentReload += Time.deltaTime;
// Shooting
if (Input.GetKeyDown(fire) && currentReload >= reloadTime)
{
currentReload = 0f;
GameObject bulletGO = Instantiate(bulletPrefab, bulletSpawner.position, transform.rotation);
bulletGO.transform.position = bulletSpawner.position;
Bullet bulletScript = bulletGO.GetComponent<Bullet>();
bulletScript.speed = bulletSpeed;
Destroy(bulletGO, 5f);
}
}
void FixedUpdate()
{
// Movement
if (Input.GetKey(forward))
{
rb.AddForce(Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(backward))
{
rb.AddForce(-Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(left))
{
rb.AddForce(Vector3.left * speed, ForceMode.Force);
}
if (Input.GetKey(right))
{
rb.AddForce(Vector3.right * speed, ForceMode.Force);
}
//transform.position = new Vector3(transform.position.x, 10, transform.position.z);
// ON RIGIDBODY I HAVE CONSTRAINS:
// POSITION: Y (thats why I commented the line above)
// ROTATION: X, Z (topdown -> so I want only rotation on Y)
}
private void Die()
{
Destroy(gameObject);
}
}
But the problem is when the player hits very hard a wall, the sphere collider starts shaking and the player does not look at the mouse position exactly (it is somewhere 10 degrees away most of the times - it depends on how hard do I hit the walls).
I can record if it helps. If you want any information, feel free to ask! Any help will be appreciated! :)
The problem here seems to be you're directly altering the transform of your object despite having a Rigidbody component. Generally you should avoid altering a transform directly when you have a Rigidbody attached, especially a non-kinematic one, as by attaching one you are signalling that the object is to be controlled by the physics simulation.
Solutions I would explore:
If you don't need a rigidbody, don't use one
If you can avoid altering the transform directly, then do not do so. You can rotate objects by applying torque and the likes
Try setting your object to kinematic if you don't need collisions to affect the rigidbody's physics
Manually set the torque and velocity of your object to 0 each fixed update
My character's jump is working fine in a straight line collider, then I noticed that when every-time my character jumps in a curve shape collider, my jump always turns too strong abnormaly. How can I fix this? I'll provide my initial code for jump movement below.
Here is the img :
Here is jump code:
float checkgroundradius = 0.50f;
public bool isgrounded2;
public Transform grouncheckslot;
public LayerMask LM;
public float JumpPower;
public float movespeed;
Rigidbody2d RB;
void Update () {
if (isgrounded && Input.GetKeyDown(KeyCode.UpArrow))
{
isgrounded = false;
jumping = true;
myanim.SetBool("groundedanim", isgrounded);
myrb.AddForce(new Vector2(0, jumpower));
}
}
void FixedUpdate()
{
isgrounded = Physics2D.OverlapCircle(checkslot.position, groundradius, LM);
myanim.SetBool("groundedanim", isgrounded);
myanim.SetFloat("verticalspeed", myrb.velocity.y);
float move = Input.GetAxis ("Horizontal");
RB.velocity = new Vector 2 (move * movespeed, RB.velocity.y);
}
When jumping up the slope, your Physics2D.OverlapCircle intersects with the slope, thus making isgrounded=true, making you apply more force if the UpArrow is pressed.
Once solution could be to check that your character is not already moving upwards before jumping
if (isgrounded && Input.GetKeyDown(KeyCode.UpArrow) && myrb.velocity.y <= 0.0f)
this way you wont jump again if you are already heading upwards.
Maybe you could play around with a smaller groundradius and/or try moving the checkslot.position in a way that it doesnt intersect with the slope in the same manner.
For jumping people use Rigidbody2D.AddForce with Forcemode.Impulse.
Also, change
if (isgrounded && Input.GetKeyDown(KeyCode.UpArrow))
to
if (Input.GetKeyDown(KeyCode.UpArrow) && myrb.velocity.y == 0)
So you don't need to bool isgrounded
I hope it helps you :)
I have a ball standing on a platform and i have written code so that every time i swipe up the ball jumps from one platform to another depending on the force of the swipe. At the moment my platforms are just placed in position by myself and i dont have a script for their random generation. The only script i have is on the player for swiping and moving forward.
Currently im making this motion by adding force in two directions, up and forward to create the projectile motion. Its working like its supposed too, but the motion is too slow. I want it to sort of move faster. Iwe tried playing with the forces as well as the mass of the ball. They do make a difference, but I still want the ball to move some what faster.
Is adding force the best way to do this? Or would you recommend a different way?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SwipeScript : MonoBehaviour {
public float maxTime;
public float minSwipeDist;
float startTime;
float endTime;
Vector3 startPos;
Vector3 endPos;
float swipeDistance;
float swipeTime;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
startTime = Time.time;
startPos = touch.position;
}
else if (touch.phase == TouchPhase.Ended)
{
endTime = Time.time;
endPos = touch.position;
swipeDistance = (endPos - startPos).magnitude;
swipeTime = endTime - startTime;
if (swipeTime < maxTime && swipeDistance > minSwipeDist)
{
swipe();
}
}
}
}
public void swipe()
{
Vector2 distance = endPos - startPos;
if (Mathf.Abs(distance.y) > Mathf.Abs(distance.x))
{
Debug.Log("Swipe up detected");
jump();
}
}
private void jump()
{
Vector2 distance = endPos - startPos;
GetComponent<Rigidbody>().AddForce(new Vector3(0, Mathf.Abs(distance.y/5), Mathf.Abs(distance.y/5)));
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "Cube (1)") {
Debug.Log("collision!");
GetComponent<Rigidbody>().velocity = Vector3.zero;
GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
}
}
}
I was writing my answer when I noticed #Fredrik comment: pretty much all he said was in what I wrote so I'll just skip it ! (I also do not recommend increasing Time.timeScale)
The other way you could move your ball would be using ballistic equations and setting your RigidBody to Kinematic. This way you will be able to control the ball speed through code using RigidBody.MovePosition() and still get OnCollision[...] events.
Also as a side note I do not recommend using collision.gameObject.name for collision object checking but rather tagging your cubes or even setting their layer to a specific one (but I guess this may be temporary code of yours ;) ).
Hope this helps,
Either pass ForceMode.VelocityChange as a second parameter to AddForce, or make sure you divide your vector by Time.fixedDeltaTime (which has the effect of multiplying it because Time.fixedDeltaTime will be less than 1).