Unity 2D Rigidbody.velocity and transform.position causes random 'ghost' frame - unity3d

I am creating a 2D platform / adventure game that uses flip screen to manage changing rooms.
My character moves using RigidBody.velocity, this works as I need it to, when my character reaches one of the screen's left or right extremes I use transform.position to move (effectively teleporting) the character to the other side of the screen, I then change the room. This gives the effect of moving to the next room along.
My flip screen code works fine, the changing room effect works without a problem, in fact almost everything works just as I like it, except that I get 1 frame where the character appears at a random point in between where the character moved from and where he moved to.
For example, my room is 16 tiles across, if my character moves left and passes x position '0' (the very left of the screen) I make a transform.position to x position 16 (keeping the y position constant) and then change rooms. I have logged the movement and what happens is (pseudo code)..
x < 0 (the change room code is run)
translate.position to x=16 (the far right)
x = 16 (this is correct)
The room changes
x = some float somewhere between 0 and 16 (this is NOT correct)
x = 16 (back to the correct position again)
This is my player code:
using UnityEngine;
using System.Collections;
public class MKManager : MonoBehaviour {
public float maxSpeed = 0.10f;
bool isFacingRight = false;
Rigidbody2D myBody;
bool grounded = true;
public static int liftdirection = 0;
void Start (){
myBody = GetComponent<Rigidbody2D> ();
}
void Update () {
checkRoomBounds ();
}
void FixedUpdate(){
float h = Input.GetAxisRaw ("Horizontal");
if (grounded) {
myBody.velocity = new Vector2 (h * maxSpeed, myBody.velocity.y);
if (h < 0) { //Moving Left
if (isFacingRight) {
FlipAnimation ();
}
} else if (h > 0) { //Moving Right
if (!isFacingRight) {
FlipAnimation ();
}
}
}
}
void checkRoomBounds(){
if (transform.position.x < 0) {
transform.position = new Vector3 (16.0f, transform.position.y, 0.0f);
GameManager.instance.moveRoom (-1);
} else if (transform.position.x > 16) {
transform.position = new Vector3 (0.0f, transform.position.y, 0.0f);
GameManager.instance.moveRoom (1);
}
}
void FlipAnimation(){
isFacingRight = !isFacingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
I have tried calling checkRoomBounds() in update, fixed update and lateupdate, all give the same result. If I do not call the moveRoom() function the same happens but the room is not changed, but the moveRoom() code is...
public void moveRoom(int direction){
if (room == 0) {
room = (10*currentFloor) + 1;
} else {
room += direction;
if (room == -1) {
room = 68;
} else if (room == 69) {
room = 0;
} else if (room % 10 == 9 || room % 10 == 0) {
//room += (direction * 2);
room = 0;
}
}
boardScript.SetupScene (room);
}
I've run out of ideas and don't know where I'm going wrong. Any help would be much appreciated.
Many thanks in advance

Related

Unity RotateAround Clamp Dead Zone Issue

For the past 3 days, I have tried numerous methods and read dozens of questions on forums in order to achieve the desired effect of rotating a camera on the Y axis using mouse rotations, and clamping the rotation within a certain range. The amount of rotation is dependent on how close the mouse is to the edge of the screen.
I was unable to find a solution, but I was able to learn enough to have my own honest attempt at it, and I came quite close. The prominent issue I faced was being able to properly clamp the RotateAround() inside of the desired range. This is because of eulerAngles being 0 - 360, and having the min and max of the clamp transition to the other side of the spectrum.
By clamping the rotation degrees before calling RotateAround, I was able to get the clamping to work, though I had to use Booleans to get them to work properly.
My current issue is that when the initial Y rotation(anchor) is slightly below 360, the camera fails to clamp on the left and will clamp back around to the right side of the range.
In all other situations, the clamping works just fine:
Initial Y rotation slightly above 0
Not within the dead zone
While debugging, I find that this is because rightOverFlag is true. When I set this bool to true manually in situations clamping normally works fine, the clamping will no longer work on the left.
I can't seem to find how this bool being true causes something like this to happen, so I am hoping some fresh eyes and seasoned advice can help me learn from this. Thank you.
public gameState gameState;
public openCams openCams;
private GameObject GameStateManager;
[SerializeField]
private GameObject Player;
[SerializeField]
private Camera cam;
// Rotation Variables
private Quaternion initialRotation;
private float initialYRotation = 0f;
[SerializeField]
private float angleRange;
private float anchorY = 0f;
public bool leftOverFlag = false;
public bool rightOverFlag = false;
void Start()
{
cam = Camera.main;
Cursor.lockState = CursorLockMode.None;
}
public void OrientControls()
{
initialRotation = Player.transform.rotation;
initialYRotation = Player.transform.eulerAngles.y;
anchorY = initialYRotation;
if (anchorY > 360)
{
anchorY -= 360;
}
rightOverFlag = false;
leftOverFlag = false;
if ((anchorY + angleRange) > 360)
{
rightOverFlag = true;
}
else if ((anchorY - angleRange) <= 0)
{
leftOverFlag = true;
}
}
void Update()
{
if (openCams.GetMonitorState() == false)
{
mousePos = Input.mousePosition;
float rotateDegrees = 0f;
//Debug.Log(Player.transform.eulerAngles.y);
// THERE IS CODE HERE THAT INCREASES ROTATE DEGREES BASED ON MOUSE POSITION. I removed it for the sake of readability, because it was quite long and unrelated to my issue.
float angleFromInitial = Quaternion.Angle(initialRotation, Player.transform.rotation);
float currentPlayerYRot = Player.transform.eulerAngles.y;
if (currentPlayerYRot > 360)
{
currentPlayerYRot -= 360;
}
bool currentRightOverageFlag = false;
bool currentLeftOverageFlag = false;
if (rightOverFlag)
{
if ((currentPlayerYRot) < (anchorY - (angleRange - 5))) //
{
currentRightOverageFlag = true;
}
}
if (leftOverFlag)
{
if ((currentPlayerYRot) > (anchorY + (angleRange + 5)))
{
currentLeftOverageFlag = true;
}
}
// !!! - For some reason, when rightOverFlag is enabled, the clamp does not work on the left side. In all other situations, the clamp works perfectly.- !!!
if (!currentLeftOverageFlag && !currentRightOverageFlag)
{
if (currentPlayerYRot < anchorY) // Regular
{
angleFromInitial *= -1;
}
}
else if (currentLeftOverageFlag && !currentRightOverageFlag)
{
if (currentPlayerYRot > anchorY) // If over the left line
{
angleFromInitial *= -1;
}
}
else if (!currentLeftOverageFlag && currentRightOverageFlag)
{
if (currentPlayerYRot > anchorY) // If over the right line
{
angleFromInitial *= -1;
}
}
else
{
Debug.Log("staticPersonController: ERROR => Cannot have current left and right overage flags enabled at the same time.");
}
currentLeftOverageFlag = false;
currentRightOverageFlag = false;
float newAngle = Mathf.Clamp(angleFromInitial + rotateDegrees, -angleRange, angleRange);
rotateDegrees = newAngle - angleFromInitial;
Player.transform.RotateAround(Player.transform.position, Vector3.up, rotateDegrees);
}
}
}
Assume you have these two variables set.
// This is the initial value of the player's Y rotation. Set only once.
float initialRotY = Player.transform.eulerAngles.y;
// This is the offset of the player's Y rotation based on the cursor position
// e.g. -90 when the cursor is on the left edge and 90 on the right
float mouseBasedRotY;
In the Update method, just do this.
Player.transform.rotation = Quaternion.Euler(0f, initialRotY + mouseBasedRotY, 0f);
You don't have to clamp the value, because the degree is always limited in [initialRotY-90, initialRotY+90]

unity update skipping calculation in a frame

I'm Instantiating tiles as a path, on every four instantiating's tiles, they should start instantiating towards right, after four instantiating's, they go straight again.
to check this logic I, made a variable spawnOffset and incrementing +1 each frame.
if spawnOffset % 4 == 0 change direction
but, i'm not getting a change in direction in regular intervals, when i debug, the frame skips and so is the logic
public GameObject go;
public Transform Playertransform;
public Vector3 tileSpawnOffset = Vector3.zero;
private Vector3 direction = Vector3.forward;
public int SpawnOffset = -3;
private bool turnRight = false;
void Update()
{
SpawnPath();
ChangeDirection();
}
void SpawnPath()
{
if (Playertransform.position.z > SpawnOffset)
{
tileSpawnOffset += direction;
Instantiate(go, this.transform.position + tileSpawnOffset, this.transform.rotation, this.transform);
SpawnOffset++;
}
}
void ChangeDirection()
{
if (SpawnOffset % 4 == 0)
{
turnRight = !turnRight;
}
direction = turnRight == true ? Vector3.right : Vector3.forward;
}
and but when i tried with time instead
float time = 0f;
void ChangeDirection()
{
time += Time.deltaTime;
if (time > 1)
{
turnRight = !turnRight;
time = 0;
}
direction = turnRight == true ? Vector3.right : Vector3.forward;
}
it works perfectly fine. so, how could i fix it. I, don't want use time, i want to change direction exactly after 4 tiles spawned
Since your always calling both SpawnPath(); ChangeDirection(); everyframe if (Playertransform.position.z > SpawnOffset) is ever false twice in a row your turnright bool gets flipped regardless every frame your on a multiple of 4. You don't need to call ChangeDirection if you didn't spawn a new tile. if you just remove that call from update and add it at right after your increment SpawnOffset its probably going to fixe it.

unity moving an object to right/left side

I am new to unity and I am trying to make a simple task: touch an object and then release your touch. when you release, I want to check in which side of the screen you released your touch and then move the object to that side of the screen.
So if I am pressing on my object, then swiping my finger to thr giht side, the object will move left and same for the right side...
This is my code, attached to the game object, and for some reason the object is just going to the right side of the screen. and it do it immidietly even though I used Lerp.
void OnMouseUp()
{
Vector3 pos = Input.mousePosition;
Debug.Log("press off");
if (pos.x < Screen.width / 2)
{
transform.position = Vector3.Lerp(transform.position, new Vector3(0,0,0), 2f * Time.deltaTime);
}
else
{
transform.position = Vector3.Lerp(transform.position, new Vector3(Screen.width, 0, 0), 2f * Time.deltaTime);
}
}
thank you!
So After a lot of trying this worked out for me:
public float smoothing = 7f;
IEnumerator MoveCoroutine(Vector3 target)
{
while (Vector3.Distance(transform.position, target) > 0.05f)
{
transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
yield return null;
}
}
void OnMouseUp()
{
Plane p = new Plane(Camera.main.transform.forward, transform.position);
Ray r = Camera.main.ScreenPointToRay(Input.mousePosition);
float d;
if (p.Raycast(r, out d))
{
Vector3 target = r.GetPoint(d);
if (target.x > 0)
{
Debug.Log("right:" + target.x + " total: " + Screen.width);
target.x = 5;
target.y = 0;
}
else
{
Debug.Log("left:" + target.x + " total: " + Screen.width);
target.x = -5;
target.y = 0;
}
StartCoroutine(MoveCoroutine(target));
}
}
not sure what the Ray casting does, I would be glad if someone can explain.
You code is almost right. You just need to define target positions and have Lerp called each time in update function.
A simple solution is to define two empty objects as position targets and pass them as parameters to the function.
using UnityEngine;
using System.Collections;
public class ClickTest : MonoBehaviour {
public Transform posLeft;
public Transform posRight;
private Vector3 destPos;
void Setup()
{
// default target is init position
destPos = transform.position;
}
// Update is called once per frame
void Update () {
// Define target position
if (Input.GetMouseButtonUp (0)) {
Vector3 pos = Input.mousePosition;
Debug.Log("press off : "+pos+ " scren : "+Screen.width);
if (pos.x < Screen.width / 2)
{
Debug.Log("left");
destPos = posLeft.position;
}
else
{
Debug.Log("right");
destPos = posRight.position;
}
}
// update position to target
transform.position = Vector3.Lerp(transform.position, destPos, 2f * Time.deltaTime);
}
}
Screenshot with empty objects as parameters

Lifting platforms is not working as it should

I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.

Character is not colliding with other other objects in Unity3D

I'm working on a game right now (first 3D styled game), and I have a problem with my character colliding. I have a Player object, which has another object(s) as the real moveable characters (now I have only one). I have rigidbody and box collider too attached to my character. I have made a level (with hand-placed platforms), and I would like avoid my character of falling of the platforms, while the user control it. I have tried to place a cube on the side of the platform(s), with box collider attached to it, but the character not detect the colliding for some reasons. I would like my character to be stopped by the collider "cube", but it doesn't happen.
I use this script to move my object (attached to my character Player object) :
public class Bounce : MonoBehaviour {
float lerpTime;
float currentLerpTime;
float perc = 1;
Vector3 startPos;
Vector3 endPos;
bool firstInput;
public bool justJump;
public GameObject player;
// Update is called once per frame
void Update () {
if (Input.GetButtonDown("up") || Input.GetButtonDown("down") || Input.GetButtonDown("left") || Input.GetButtonDown("right")) {
if (perc == 1) {
lerpTime = 1;
currentLerpTime = 0;
firstInput = true;
justJump = true;
}
}
startPos = gameObject.transform.position;
if (Input.GetButtonDown("up") && gameObject.transform.position == endPos) {
endPos = transform.position + player.transform.rotation * (new Vector3(0, 0, 1f));
}
if (Input.GetButtonDown("down") && gameObject.transform.position == endPos) {
endPos = transform.position + player.transform.rotation * (new Vector3(0, 0, -1f));
}
if (firstInput == true) {
currentLerpTime += Time.deltaTime * 5;
perc = currentLerpTime / lerpTime;
gameObject.transform.position = Vector3.Lerp(startPos, endPos, perc);
if (perc > 0.8f) {
perc = 1;
}
if (Mathf.Round(perc) == 1) {
justJump = false;
}
}
}
void OnCollisionEnter(Collision collision) {
Debug.Log("!!!!!!!");
}
}
And I use this script on the character itself: (to rotate and animate it)
Code (csharp):
public class AnimationController : MonoBehaviour {
Animator anim;
public GameObject thePlayer;
// Use this for initialization
void Start () {
anim = gameObject.GetComponent<Animator>();
}
// Update is called once per frame
void Update () {
Bounce bounceScript = thePlayer.GetComponent<Bounce>();
if (bounceScript.justJump == true) {
anim.SetBool("Jump", true);
}
else {
anim.SetBool("Jump", false);
}
if (Input.GetButtonDown("right")) {
//transform.rotation *= Quaternion.Euler(0,30,0);
transform.RotateAround(transform.position, Vector3.up, 90);
}
if (Input.GetButtonDown("left")) {
transform.Rotate (0, -90, 0, 0);
}
}
}
It's only colliding when the cube not isKinematic, but it doesn't stop my player from getting through the cube for some reason.
I have read some sites about problems like this, but nothing helped yet.
It would be great if you can give me any code improvements :)
EDIT1:
Sorry, for answering so late, I couldn't do anything with my game in the last few days. Today I have tried to do something with raycasts, you can see my code, for my first try just in the update method:
void Update () {
if (Input.GetButtonDown("up") || Input.GetButtonDown("down") || Input.GetButtonDown("left") || Input.GetButtonDown("right")) {
if (perc == 1) {
lerpTime = 1;
currentLerpTime = 0;
firstInput = true;
justJump = true;
}
}
startPos = gameObject.transform.position;
/* if (Input.GetButtonDown("right") && gameObject.transform.position == endPos) {
//endPos = new Vector3(transform.position.x + 0.5f, transform.position.y, transform.position.z);
}
if (Input.GetButtonDown("left") && gameObject.transform.position == endPos) {
endPos = new Vector3(transform.position.x - 0.5f, transform.position.y, transform.position.z);
}*/
Vector3 fwd = player.transform.TransformDirection(Vector3.forward);
RaycastHit objectHit;
if (Physics.Raycast(player.transform.position, fwd, out objectHit, 2)) {
if (objectHit.collider.tag == "Wall") {
Debug.Log("WALL RAYCAST HIT!");
}
}
if (Input.GetButtonDown("up") && gameObject.transform.position == endPos) {
endPos = transform.position + player.transform.rotation * (new Vector3(0, 0, 1f));
}
if (Input.GetButtonDown("down") && gameObject.transform.position == endPos) {
//endPos = transform.position + player.transform.rotation * (new Vector3(0, 0, -1f));
}
if (firstInput == true) {
currentLerpTime += Time.deltaTime * 5;
perc = currentLerpTime / lerpTime;
gameObject.transform.position = Vector3.Lerp(startPos, endPos, perc);
if (perc > 0.8f) {
perc = 1;
}
if (Mathf.Round(perc) == 1) {
justJump = false;
}
}
}
With this I can get the Debug Log WALL RAYCAST HIT 2-3 times at a time, but only once. As I move the character it won't appear again, for some reason (I think it should because the update method is called in every frame).
Although when I place it in the Input.GetButtonDown("up") method, it won't log anything:
if (Input.GetButtonDown("up") && gameObject.transform.position == endPos) {
fwd = player.transform.TransformDirection(Vector3.forward);
if (Physics.Raycast(player.transform.position, fwd, out objectHit, 2)) {
if (objectHit.collider.tag == "Wall") {
Debug.Log("WALL RAYCAST HIT!");
}
}
endPos = transform.position + player.transform.rotation * (new Vector3(0, 0, 1f));
}
When you update the transform.position you actually "teleport" yout object to the new position. Even when you do this smoothly, using something like the Lerp function, as you do on your move script, Unity understands that you are teleporting your object to a new coordinate.
This is not wrong, All games work like that. Functions like Lerp just create the ilusion of movement, so the player fell like the character actually moved from one point to another.
What is happening is that your script keeps telling the character to move to a new position, despite any objects on its path. The collision happens, but your Update function still places the character on ints new position.
I can think of two possible ways for you to solve this:
Create an OnCollisionEnter() function for your Move script that somehow stops your movement. By creating a flag like you did with the firstInput you could achieve this.
Instead of updating your transform.position you could use a rigidbody to handle the movement and collisions for your. This way, everytime you need to move your character you will need to add some velocity to the character rigidbody, instead of directly updating the transform.position.
Since your character doesn't move in a constant way, but in a "one step at a time" way, I think the first solution is a better suitted for you.
The code will be something like this
void OnCollisionenter(Collision collision)
{
collided = true;
}
and the end of your Move script should be updated to
if(!collided)
{
if (firstInput == true) {
currentLerpTime += Time.deltaTime * 5;
perc = currentLerpTime / lerpTime;
transform.position = Vector3.Lerp(startPos, endPos, perc);
if (perc > 0.8f) {
perc = 1;
}
if (Mathf.Round(perc) == 1) {
justJump = false;
}
}
}
else
{
// Remember to reset this when you stop your character
endPos = transform.position;
collided = false;
}
This time I couldn't test the code before writting it here, so you may need to do some changes before it works.
ps: I don't think you need your firstInput variable. Once it is set to true, you never set it to false again. Also, the Input.GetButtonDown() function will only return true on the first frame a button is pressed wich is what I guess you intended when you created firstInput.
ps2: My solution added a collided boolean attribute to your class, so don't forget to add a line private bool collided; with your other class attributes. Also, read that chapter about the state machine pattern I recommended you on your other question. Creating this kind of control attribute suach as the collided boolean is not a good practice and if you had implemented a state machine this could be avoided.
Hey I had a similar problem i was using an animated model from Mixamo.com and I notices that for some strange reason the character didn't collide with boundaries despite having collider, for some reason it didn't work until i added a CharacterController to my model instead of a collider just search for the CharacterController and add it as you would add any script or rigid body to your game object.
Hope it Works XD Greetings