I'm making a 3D Side-Scroll Platformer Game,
I have trouble with my character when it steps on the moving platform it will not come along on the platform. I want my character to stay on the moving platform so I think converting my Character Controller into Rigibody will help me,
I need help to give me ideas on how I can reuse my Character Controller Script in Rigibody. This is my code, how can I reuse this in Rigibody script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public CharacterController controller;
private Vector3 direction;
public float speed = 8;
public float jumpForce = 30;
public float gravity = -20;
public Transform groundCheck;
public LayerMask groundLayer;
public bool ableToMakeADoubleJump = true;
public Animator animator;
public Transform model;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (PlayerManager.gameOver)
{
//play death animation
animator.SetTrigger("die");
//disable the script
this.enabled = false;
}
float hInput = Input.GetAxis("Horizontal");
direction.x = hInput * speed;
animator.SetFloat("speed", Mathf.Abs(hInput));
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.15f, groundLayer);
animator.SetBool("isGrounded", isGrounded);
if (isGrounded)
{
//Jump Codes
ableToMakeADoubleJump = true;
if (Input.GetButtonDown("Jump"))
{
Jump();
}
}
else
{
direction.y += gravity * Time.deltaTime;
if (ableToMakeADoubleJump & Input.GetButtonDown("Jump"))
{
DoubleJump();
}
}
if (hInput != 0)
{
Quaternion newRoattion = Quaternion.LookRotation(new Vector3(hInput, 0, 0));
model.rotation = newRoattion;
}
//Move the player using the character controller
controller.Move(direction * Time.deltaTime);
}
private void DoubleJump()
{
//Double Jump Codes
animator.SetTrigger("doubleJump");
direction.y = jumpForce;
ableToMakeADoubleJump = false;
}
private void Jump()
{
direction.y = jumpForce;
}
}
I would not recommend switching between the two. It would get tricky, and think about it, you are alternating between two very different things. One is movement and one is physics.
However, I would reccomend adding to your current script so that the player would move with the moving platform.
There is a lot of stuff in this answer, so read the whole thing.
Btw, when I talk about velocity, in your case it is direction.
Since it seems like you know how to code pretty well, I won’t write out the script, rather tell you some physics ideas to get you going in the right direction.
The reason people can stand on a moving platform and not fall off is because of friction.
If you are standing on a gameObject with enough friction (you could add a physics material the gameObject you stand on and change friction there. Note that physics materials only work with rigidbodies, but you might want to use it to just read the value)
First of all, you are going to want to raycast down to obtain the object you are standing on. From there you can get the physics material from hit.collider.sharedMaterial (or any other hit. to obtain data about what object you are standing on.
If they friction is too low, just make the character slip off, like it was before (I assume)
If the friction is above a threshold, get the velocity from the object you are standing on. If it was a rigidbody, hit.rigidbody.velocity. If it is controlled by script, use hit.collider.gameObject.GetComponent<scriptname>().velocityvariablename This part is continued later on
This is not necessary but useful: You can think of this as grabbing on a rope. When you are grabbing on a slippery rope, and someone pulls it (Like tug of war), You won’t move because the rope will slide through your hands. If the rope had grip tape on it and someone pulled it, you would come with it because it has more friction. You can think of the platform the same way. Now on to the more complex part: When you grip a rope that is stationary, and someone pulls it, you come with it as its velocity changes. When the rope is already being pulled, so its velocity is not stationary and it is already something. You grab onto it and a similar thing happens. It is like you are becoming a part of that rope. Similar to how if you are running, the arms and legs and head is a part of you. If you lose grip, you are no longer a part of that body, like your arms falling off when running. In other words, you become part of the body when you attach yourself to it.
Bottom line:
Get the velocity of the platform and set platformVel to it, do not add that to velocity, rather do a seperate controller.Move(platformVel).
A small customization:
Vector3.Lerp the platformVel to 0, so it doesn’t change while on the platform, but gradually goes to (0,0,0) when you get off. This way, there is a little momentum maintained from standing on the platform.
Feel free to ask anything in the comments.
Related
I am a beginner in Unity developing a 2D top-down mobile game. I am trying to create an enemy movement script that mimics the pattern of the leech enemy below:
This enemy is constantly trying to move towards the player but even though it can move quite quickly, due to its momentum, you are able to kite it as it cannot make a sharp turn without first taking some time to build speed in another direction.
I have created a script for enemies to constantly be targeting the player based on the player's current position but it is too difficult to dodge my enemies as they are able to turn instantly when the player does and maintain a constant speed. I would like to balance them to be more like this leech enemy so the player can dodge them by taking advantage of the enemy's current momentum with proper timing. How can I create this momentum effect for my enemies?
If you're using Unity's physics here's a way to do this nicely:
Walkable.cs
Create a modular component that all walkable game objects will use. The purpose of the component is to keep the object moving in the specified direction while stabilising the forces. It uses the values you configure in the inspector for speed and force. It does the movement inside of FixedUpdate as physics movement require it.
public class Walkable : MonoBehaviour {
private const float ForcePower = 10f;
public new Rigidbody2D rigidbody;
public float speed = 2f;
public float force = 2f;
private Vector2 direction;
public void MoveTo (Vector2 direction) {
this.direction = direction;
}
public void Stop() {
MoveTo(Vector2.zero);
}
private void FixedUpdate() {
var desiredVelocity = direction * speed;
var deltaVelocity = desiredVelocity - rigidbody.velocity;
Vector3 moveForce = deltaVelocity * (force * ForcePower * Time.fixedDeltaTime);
rigidbody.AddForce(moveForce);
}
}
Character.cs
This is a simple example of character that will follow a target. Notice how all it's doing is passing the direction to the walkable from inside an Update function.
public class Character : MonoBehaviour {
public Transform target;
public Walkable walkable;
private void Update() {
var directionTowardsTarget = (target.position - this.transform.position).normalized;
walkable.MoveTo(directionTowardsTarget);
}
}
Configurations
By configuring move and force variables you can get a variety of movement styles, some that can move fast but take a long time to ramp up, some that ramp up fast but move slowly overall.
You can also play around with with mass and linear drag on the Rigidbody2D to get even more control over the movement style.
I'm starting to learn C# with Unity and I guess my code present some issues, the movement of my player works but not that good. Let me explain, I have a player at the bottom of the screen, moving only from left to right by itself, using transform.translate (I didn't used rigidbody) because on collision with the sides it stops, and is not a constant movement, the idea is that, once collides with the sides, it changes of direction, but same velocity.
The issue happens when the player colides with the walls (removed the mesh renderer to keep them transparent) but if at the same time you press the key to trigger also the change of direction the player gets stuck at the corner, as shown on this gif example:
Wait for the Player to get stuck at the corners, the gif takes like 15 secs.
And here's my actual Script for the Player
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PMovement : MonoBehaviour
{
public float playerSpeed = 8.0f;
void Update()
{
float moveX = playerSpeed * Time.deltaTime;
transform.Translate(moveX, 0, 0);
if (Input.GetKeyUp(KeyCode.Space))
{
playerSpeed = playerSpeed * -1;
}
}
void OnCollisionEnter(Collision target)
{
if (target.gameObject.tag.Equals("SideWalls") == true)
{
playerSpeed = playerSpeed * -1;
}
}
}
Your code looks ok for what you want to achieve. Unfortunately, in Unity it may not be enough to pin point the problem, as the code heavily depends on the setting from the editor. One possible solution to this or similar problems:
If you still have blocking colliders, (The collision functions need some sort of rigidbody.), or if you clamp your player's position, to stop overflow, the following can happen:
Collider on right side is hit
Player direction change to left
Space is hit while colliders are still touching
Player direction change to right again
There is no collider on right side left to start the colliderEnter event again.
In this case block your space recognition while the player is touching one side.
If you change direction when you hit the wall, but you press Space at just the right time, you'd end up inside the wall a bit most likely. Then it'd keep thinking it was entering the wall collision and reversing direction over and over. Here's what I'd do.
public class PMovement : MonoBehaviour
{
private List<GameObject> Collisions = new List<GameObject>();
public float playerSpeed = 8.0f;
void Update()
{
float moveX = playerSpeed * Time.deltaTime;
transform.Translate(moveX, 0, 0);
if (Collisions.Count <= 0 && Input.GetKeyUp(KeyCode.Space))
{
playerSpeed = playerSpeed * -1;
}
}
void OnCollisionEnter(Collision target)
{
if (!Collisions.Contains(target.gameObject) && target.gameObject.tag.Equals("SideWalls") == true)
{
Collisions.Add(target.gameObject);
playerSpeed = playerSpeed * -1;
}
}
void OnCollisionExit(Collision target)
{
if (Collisions.Contains(target.gameObject)) {
Collisions.Remove(target.gameObject);
}
}
}
By keeping a list of wall objects you're inside, you can disallow the space bar if you're inside a wall. Keep in mind there's probably better ways to do the things you're doing, but this should fix the issue you're having for now.
I am currently creating a game in Unity, in which you move a ball around using OnMouseDrag(), a CircleCollider2D and a RigidBody2D. This is how I set the position of the ball:
private void OnMouseDrag()
{
Vector2 mouseInWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition);
playerRb.position = new Vector3(mouseInWorld.x, mouseInWorld.y, 0);
}
I still want the ball to slide on collision while the mouse moves around. Is there a way to do this?
I have tried RigidBody2D.MovePosition(), but the ball jumped around from one point to another, and Raycasts but couldn't get that to work either.
EDIT:
This is what I've got now:
playerRb.velocity = new Vector3(mouseInWorld.x - playerRb.position.x, mouseInWorld.y - playerRb.position.y, 0);
Now the problem is, that the ball lags behind the mousePosition.
When you use RigidBody.MovePosition, you don't call the physics engine and so it ignores collisions. If you want collisions to happen you need to use RigidBody.Velocity instead.
Doing this change will require you to make some change to your code though because what you give to RigidBody.Velocity is a velocity and not a position so you will need to calculate the velocity required in x,y (and z if you are in 3d) to reach your destination.
I invite you to read the Unity page about velocity for more info
https://docs.unity3d.com/ScriptReference/Rigidbody-velocity.html
Note: This will make the player/ball stick to collisions.
Modifying the velocity could cause the ball to bounce around unexpectedly when the ball collides with the wall. I would use a CircleCast for this, check if it hit anything, then use MovePosition accordingly:
float cursorDepth;
Rigidbody2D playerRb;
CircleCollider cc;
void Awake()
{
playerRb = GetComponent<Rigidbody2D>();
cc = GetComponent<CircleCollider>();
}
private void OnMouseDrag()
{
Vector2 mouseInWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 posToMouse = mouseInWorld - playerRb.position;
RaycastHit2D hit = Physics2D.CircleCast(playerRb.position,
cc.radius * transform.lossyScale.x, posToMouse, posToMouse.magnitude);
if (hit.collider != null)
{
mouseInWorld = hit.centroid;
}
playerRb.MovePosition(mouseInWorld);
}
But notice that if the ball can't move all the way to the mouse, it might cause the drag to end. So, plan accordingly.
So, my issue is as follows:
I'm developing a very simple 2D game as I'm quite new to Unity. Basically, there is a knight, and there is a troll. I'm developing my custom AI for the troll and got stumped at the very beginning. I tried to make the troll sprite correctly rotate and walk towards the player (in Update()), but then it turned out that the troll game object doesn't move as much as a millionth of a unit (although it rotates correctly)!
Now, what is special about my problem is the following: it's not about using localPosition instead of position (the troll has no parents – pun intended), it's not about referencing the transform in the script via "[SerializeField]", it's not about working with static objects or messed-up prefab coordinates or any of the other issues I've found in my desperate 10-hour search on the Internet. No, the issue is that I HAVE the troll transform in my code, I modify it successfully, but then it fails to be applied to the in-game troll object. Let that sink in – I am correctly modifying the correct transform (checked multiple times via debugging) but at the beginning of the next frame, all changes get magically reverted and the transform doesn't move the slightest.
I've made almost twenty modifications to my code in order to try and make it work (including separating stuff in different files as was suggested in a particular forum topic) but it just doesn't, and I can't think of anything else. Any help is very, very much appreciated.
public class EnemyController : MonoBehaviour
{
[SerializeField]
private Animator animator;
[SerialiseField]
private float walkingSpeed;
[SerializeField]
[Header("The aggro range of the enemy in Unity units.")]
private float aggroRange;
private float dir;
private Transform playerTransform;
void Update()
{
if (Aggro(playerTransform, aggroRange))
{
//WALKING:
WalkTowards(playerTransform, walkingSpeed);
}
}
private void WalkTowards(Transform targetTransform, float walkingSpeed)
{
animator.SetBool("IsRunning", false);
dir = 0.0f;
//Get the direction (left or right)
if (targetTransform.position.x < transform.position.x) //Left
{
dir = -1.0f;
//Flip the sprite to the left
transform.rotation = Quaternion.Euler(0.0f, 180.0f, 0.0f);
}
else //Right
{
dir = 1.0f;
//Flip the sprite to the right
transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
}
transform.position = new Vector3(
transform.position.x + (walkingSpeed * dir * Time.deltaTime),
transform.position.y,
transform.position.z
);
Debug.Log(
string.Format("Troll 1 position:\nx: {0:#.##}", transform.position.x) +
string.Format(" y: {0:#.##}", transform.position.y) +
string.Format(" z: {0:#.##}", transform.position.z)
);
}
}
(NOTE: Aggro() is a custom method that determines when the enemy aggros; not included in the code as it is irrelevant to the issue)
So basically, transform.position gets changed correctly in the script and stays that way until the end of the method, but at the beginning of the next frame – i.e. when WalkTowards() gets called again, the transform.position remains the same as it was at the beginning of the last frame – as if no calculations or changes were ever made.
#Ruzihm,
You were right!!! Turns out the Animator component of the troll was causing the issue – "Apply root motion" was off, I turned it on and the position gets correctly updated now. Thank you ever so much for the help!
Example:
I tryed to make an elevator and i came around something that i could not explain.
So here is the scenario:
Once i put my object(in this case a cube) on the elevator it will go up and down.
If the object sits out the ride without moving and waits for the next ride, the colliding will stop when the elevator comes back through the ground.
If the object moves while the elevator come's back everything is fine and the object go's for another ride.
Could someone explain to my why this is happening and is there a fix for this ?
The platform is expected to go through the ground and pick up the object once it comes back above ground. (Imagine a not all to sectret elevator)
I alredy tried to add the script DontGoThroughThings.cs. It didnt work either.
Here is a screenshot of the inspectors.
Here is the script i made for the elevator to go up and down.
Elevator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Elevator: MonoBehaviour {
public float speed;
public float distance;
private bool goingUp;
void Start () {
goingUp = true;
}
void Update () {
var currentPosition = transform.position;
currentPosition.y = goingUpDown(currentPosition.y);
transform.position = currentPosition;
}
private float goingUpDown(float currentPosition)
{
if (goingUp)
currentPosition += speed;
else
currentPosition -= speed;
if (currentPosition > distance / 2)
goingUp = false;
if (currentPosition < -distance / 2)
goingUp = true;
return currentPosition;
}
}
If you need any more information leave a comment. Thank you
You need to add a RigidBody to your elevator object, and then check the "Is Kinematic" option on it.
From the Unity Documentation RigidBody Manual:
Is Kinematic : If enabled, the object will not be driven by the physics engine, and can only be manipulated by its Transform. This is useful for moving platforms or if you want to animate a Rigidbody that has a HingeJoint attached.