I have trouble with PhysicMaterial2D for the ground and my character. When the character doesn't move, it works as I need (both materials have friction = 1.0). But due to high friction value the character can't move.
Of course, I could use more force (AddForce()) to move the character but then it will move too quickly on other grounds that uses other materials.
How is this problem resolved in other games (i.e. many platformers)? Should I set a friction of the character's material to zero when it moves (I haven't another ideas)? Or has it more pretty solution?
P.S. I tested with higher value of the friction also but it led to the character begun to rotate on the moving.
ADDED
I tried to set boxCollider.sharedMaterial.friction = 0.0f on the movement and return back to 1.0f after the user released button BUT the character continues to move as with zero friction although in the logger I can see it's 1.0f.
private void Update()
{
if (!paused)
{
if (Input.GetMouseButtonDown(0) && !IsOverPauseButton())
{
isPressed = true;
endMovePoint = Input.mousePosition;
isWalk = GetTouchDistance() > jumpTriggerRadius;
if (!(isWalk || isJump))
{
StartCoroutine(Jump());
}
}
else if (Input.GetMouseButtonUp(0) && isPressed)
{
boxCollider.sharedMaterial.friction = 1.0f;
isPressed = false;
isWalk = false;
canFlip = true;
}
if (isWalk)
{
if (!isJump)
{
boxCollider.sharedMaterial.friction = 0.0f;
}
Walk();
}
animator.SetBool(WalkingFlag, isWalk && !isJump);
}
if (isWalk && !isJump)
{
FixSpeed();
}
Debug.Log("Friction=" + boxCollider.sharedMaterial.friction);
}
Rather than using friction, you can dampen the player's movement.
Say your player has a velocity of 10, you want the player to stop moving of it's own accord because that's what's natural.
What you would want to do is subtract an amount from your player's velocity every frame e.g.
player.velocity.x *= 0.9f;
This would set the player's x velocity to 90% of it's original velocity. Do this every frame and soon your player will stop all by itself.
If the above method does not produce the effect you desire, you can always just subtract an arbitrary value from the player's velocity (of course you must account for if the player's velocity is negative):
player.vel.x -= 1;
Both of the methods above can factor in friction if need be, simply multiply the percentage or arbitrary value by the friction coefficient plus or minus a bit to suit your needs.
Related
Brand new to Unity/C# so this might be a stupid error. I have a player and a push-able box that when pushed towards another object (that usually the player cannot walk through) causes the box to stop moving and the player to get stuck and be unable to move but stuck mid-animation.
They basically just all get stuck in eachother
https://i.stack.imgur.com/rkNtu.png
I followed tutorials for a lot of these things but couldn't manage to find one for pushing the box so I did it by myself, which is what I'm thinking caused these issues.
The player has a 2D circle collider and a 2D rigidbody (with a dynamic body type and discrete collision detection).
It also has all of its code to do with walking that looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
//Movement speed variable
public float moveSpeed;
//Giving information of where the collisions layer is (edited in actual unity)
public LayerMask collisionsLayer;
//Variable to check if the character is moving
private bool isMoving;
private Vector2 input;
//Animation SetUp
private Animator animator;
private void Awake()
{
animator = GetComponent<Animator>();
}
//End of Animation SetUp
private void Update()
{
//If the player is NOT moving
if (!isMoving)
{
//'GetAxisRaw' gives 1 or -1 --> right = 1, left = -1
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
//Making it so that the player cannot move diagonally
if (input.x != 0) input.y = 0;
if (input != Vector2.zero)
{
//Animation section
animator.SetFloat("moveX",input.x);
animator.SetFloat("moveY",input.y);
//End of animation section
var targetPos = transform.position;
//Adds 1 or -1 depending on the key input (GetAxisRaw)
targetPos.x += input.x;
targetPos.y += input.y;
if (IsWalkable(targetPos))
//Calls coroutine 'IEnumerator Move'
StartCoroutine(Move(targetPos));
}
}
//for animation
animator.SetBool("isMoving",isMoving);
//Special type of function w/ 'IEnumerator' as return type. Used to do something over a period of time.
IEnumerator Move(Vector3 targetPos)
{
isMoving = true;
//Checks if the diff between the target position & the players current position is greater than a vry small value
while ((targetPos - transform.position).sqrMagnitude > Mathf.Epsilon)
{
//If there is a diff btwn targetpos & the current position --> we will move the player towards the target position by a very small amount
transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
//Stops execution of the coroutine, resumes it in the next update function
yield return null;
}
//Sets the players current position to the target position
transform.position = targetPos;
isMoving = false;
}
}
//Checking if the next available tile is actually able to be walked on / if the next tile is blocked
private bool IsWalkable(Vector3 targetPos)
{
if(Physics2D.OverlapCircle(targetPos, 0.1f, collisionsLayer) != null)
{
return false;
}
return true;
}
}
The box has a box collider 2D and a 2D rigidbody (dynamic bodytype; discrete collision detection)
And the collisions tilemap (which is set to be used by the composite) has a tilemap collider 2D, rigidbody 2D (static) and a composite collider 2D
If anyone actually knows how to make it so that the entire game doesn't break when I try to move the box past objects, that'd be really helpful because I've been working on this for ages and haven't figured anything out at all
You should try running the debugger. I think you're finding yourself in an interlocked condition, where your character can't get to the target position (because it's blocked), but it hasn't reached the target position (because it's blocked), so it keeps trying to move, which prevents you from giving it a new command.
I think the easiest way to work around this is to cache your target directions - should be in positive x, positive y, or negative x, or negative y. Then, if you detect the user trying to move in the opposite direction you cancel the move and start a new one.
There are lots of ways to work around this, though, and I do see you are trying to check the target before moving to it, but you're checking the raw input, which may overshoot an obstacle. For example, if the tree is at 0.5 meters, but you're looking at a target of 1 meter, then the target is clear. You get blocked by the tree at 0.5 meters, and because you never reach 1 meter you never exit the coroutine.
You've got to run a debugger and step through the code and see what specifically isn't responding.
I have a player on XY position. After player's swipe position must be increased or decreased on 1 for x-area. Tryed to do this by addForce and velocity, but these ways are increasing x-area pos to infinity.
So - How i can stop increasing after then position will be (1,0) after (0,0)? or mb anything else method can help me?
void FixedUpdate()
{
if (Input.touchCount > 0)
{
theTouch = Input.GetTouch(0);
if (theTouch.phase == TouchPhase.Began)
{
theTouchStartPos = theTouch.position;
}
if (theTouch.phase == TouchPhase.Ended)
{
theTouchEndPos = theTouch.position;
if (theTouchEndPos.x <= theTouchStartPos.x)
{
Debug.Log("swipe left");
Move(Vector2.left);
}
if (theTouchEndPos.x >= theTouchStartPos.x)
{
Debug.Log("swipe right");
Move(Vector2.right);
}
}
}
}
void Move(Vector2 direction)
{
//playerRb.velocity = new Vector2(direction.x, direction.y) * speed;
float step = speed * Time.deltaTime;
playerPos = Vector2.Lerp(playerPos, playerPos + direction, step);
}
Do not take single frame inputs such as TouchPhase.Began or GetKeyDown in FixedUpdate(). It may work on your machine but it will not register the input every time on a faster machine.
"FixedUpdate is often called more frequently than Update. It can be called multiple times per frame, if the frame rate is low and it may not be called between frames at all if the frame rate is high." - https://docs.unity3d.com/Manual/ExecutionOrder.html
What this means is that FixedUpdate is not guaranteed to run on the frame when you pressed down, so inputs that only last for one frame such as TouchPhase.Began or GetKeyDown may not register if the framerate is high enough.
It's surprising to me how noone else noticed this.
So it sounds like what you are trying to achieve is: Each swipe moves your player one "step" to left or right on the X-axis but smoothly.
In your current approach you call the Lerp only in one single frame when the touch ends so the object will barely move at all.
I would rather set the target x-axis position and let the playeroventowards that with the given speed in every physics update call.
As SpicyCatGames pointed out correctly, you should always separate the handling of User Input (Update) from the physics (FixedUpdate).
private float targetX;
private void Awake ()
{
targetX = playerRb.position.x;
}
void FixedUpdate()
{
// Move the player towards the target position but only on X
// -> keep the Y velocity
var position = playerRb.position;
position.x = Mathf.MoveTowards(position.x, targetX, speed * Time.deltaTime);
playerRb.MovePosition(position);
}
private void Update ()
{
if (Input.touchCount > 0)
{
theTouch = Input.GetTouch(0);
if (theTouch.phase == TouchPhase.Began)
{
theTouchStartPos = theTouch.position;
}
else if (theTouch.phase == TouchPhase.Ended)
{
theTouchEndPos = theTouch.position;
if (theTouchEndPos.x < theTouchStartPos.x)
{
Debug.Log("swipe left");
Move(-1);
}
else if (theTouchEndPos.x > theTouchStartPos.x)
{
Debug.Log("swipe right");
Move(1);
}
}
}
}
void Move(float direction)
{
targetX += direction;
}
This seems to get discussed a lot.
FixedUpdate is for physics "calculations".
If you are animating something - that is to, say, you want to draw something each time the scene is drawn (perhaps changing the position) - you very much use Update, not FixedUpdate.
It's both pointless (and inaccurate) to change the position of on object on the FixedUpdate calls.
Say this represents time and the F are fixed update events and the D (draw) are update events
F F F F F F F F F F F F F F F F F
D D D D D D
You (of course, obviously) only need to set the position of the object when you are about to draw it.
And indeed, when you're about to draw it: you (obviously) want the position it should be in at that actual time.
(The script given here shows "F" and "D" running .. https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html )
As it explains in the documentation, FixedUpdate is for physics >>calculations<<. So, if you are extending the physics engine, simulating something, applying the precise rules of a bullet game, or whatever. (I've rarely had to use it.) Note that interestingly, if for some reason you were calculating a planet or something and doing that every FixedUpdate, you'd still have to actually set the position of the planet (or whatever it is) at the exact moment when you're about to draw it! (ie in Update.)
It's another good example of the famously touchy Unity doco. In dozens of places they show something being moved (ie, using Update, when the draw happens). On that FixedUpdate page it very confusingly just mentions physics "calculations" which is very vague. If they had added a couple more words ("for behind the scenes physics calculations where you are not actually getting the position to draw something that's about to be drawn") it would all be clearer!
I'm new to Unity and C# in general, so excuse my terminology(or lack thereof)...
I have succeeded -with the help of a friend, to credit where its due- in making a platform go up a certain amount after one frame of collision with the player! The only problem is that, now, I can't seem to get it moving down again... Said friend challenged me to make it go up, stay airborne for a certain amount of time, then go back down.
Here is a snippet of my working code that makes it go up.
bool HaveCollided = false; //Collision checker boolean
void Update()
{
Vector3 up = lifter * Time.deltaTime;
if (HaveCollided != true || transform.position.y >= 5)
{
return;
}
}
void OnCollisionEnter(Collision collision) //Makes HaveCollided true at collision event
{
if (collision.gameObject.GetComponent<Playertag>() != null) //don't use GetComponent in Update()
{
HaveCollided = true;
}
So if my logic is right, I'd need to nest another if statement inside the one where the condition is: HaveCollided != true || transform.position.y >= 5 which should be:
if (newTime == Time.deltaTime * CertainAmount && transform.position.y >= 1) //make platform go down until y position is equal to 1
{
//make platform go down
Debug.Log("reached");
transform.position = transform.position - up;
}
But it's not working. It doesn't even seem to reach the part that would make the platform descend. I literally do not know where to go from here...
Based on your comment I made a few revisions to make the platform movement a bit smoother. I also made it so the same function can be used for both the upward and downward motion.
using UnityEngine;
using System.Collections;
[SerializeField] private float timeToMovePlatform = 0.0f; // time it takes the platform to get from 1 point to another
[SerializeField] private float timeToRestAtPeak = 0.0f; // time that the platform rests at the top of its movement
[SerializeField] private float platformYOffset = 0.0f; // offset the platform moves from its origin point to float upward
private Coroutine movingPlatform = null; // determines if the platform is currently moving or not
private void OnCollisionEnter(Collision col)
{
// add a tag to your player object as checking a tag at runtime vs. a GetComponent is faster
if(col.gameObject.tag == "Player")
{
// only trigger when we are not currently moving
if(movingPlatform == null)
{
// start our coroutine so the platform can move
movingPlatform = StartCoroutine(MovePlatform(true));
}
}
}
/// <summary>
/// Moves this object up or down depending on the parameter passed in
/// </summary>
/// <param name="moveUpward">Determines if this object is currently moving upward or not</param>
private IEnumerator MovePlatform(bool moveUpward)
{
// store our start position
Vector3 startPos = transform.position;
// build our goal position based on which direction we are moving
Vector3 goalPos = new Vector3(startPos.x, startPos + (moveUpward ? platformYOffset : -platformYOffset), startPos.z);
// set our current time
float currentTime = 0.0f;
while(currentTime <= timeToMovePlatform)
{
// lerp the position over the current time compared to our total duration
transform.position = Vector3.Lerp(startPos, goalPos, currentTime / timeToMovePlatform);
// update our timer
currentTime += Time.deltaTime;
// need to yield out of our coroutine so it does not get stuck here
yield return null;
}
// just in case there are any floating point issues, set our position directly
transform.position = goalPosition;
// when we are moving upward, make sure to stop at the peak for the set duration
if(moveUpwards)
{
yield return WaitForSeconds(timeToRestAtPeak);
// once we are done waiting, we need to move back downward
StartCoroutine(MovePlatform(false));
}
else
{
// we finished moving downward, so set our coroutine reference to false
// so the player can set the platform to move again
movingPlatform = null;
}
}
Fair warning, I did not test this code it is more a direction I would take for your situation. Before doing anything, add a tag to your player. If you are unsure what tags are, check out the docs. Be sure to give the player the tag Player so that the code will work as expected.
You will notice the first three variables are Serialized but are private. By doing this, it allows for Unity to show them in the inspector but not allow other scripts to touch them.
To learn more about Coroutines, check out the docs and for Lerps, I enjoy this blog post. Let me know if this works for you and/or you have any questions.
Your best bet is to add colliders and use isTrigger. Seed a time and if it's not triggered within that time period raise an event.
I'm trying to make jumping functionality in my Movement test. My character jumps and comes back down, but it's very choppy and not smooth at all.
What happens is he juts up to his max height, then comes down smoothly.
I can spot the problem, the for loop doesn't want to play nicely with the code. However, I don't know how to circumvent this. Is there any way to keep the button press and have him jump up nicely?
Code:
if (leftStick.Y > 0.2f && sprite.Position.Y == position.Y || isPressed(Keys.Up) == true && sprite.Position.Y == position.Y)
{
if (wasLeft == true)
{
sprite.CurrentAnimation = "JumpLeft";
}
else if (wasLeft == false)
{
sprite.CurrentAnimation = "JumpRight";
}
//This for loop is my issue, it works but it's jumpy and not smooth.
for (movement.PlayerHeight = 0; movement.PlayerHeight < movement.PlayerMaxHeight; movement.PlayerJump())
{
sprite.Position.Y -= movement.PlayerJump();
}
}
sprite.StartAnimation();
}
else
{
leftStick = NoInput(leftStick);
}
private Vector2 NoInput(Vector2 leftstick)
{
if (sprite.Position.Y < position.Y) //(movement.PlayerSpeed > 0)
{
sprite.Position.Y += movement.PlayerHeight;
movement.PlayerHeight -= movement.Player_Gravity;
//sprite.Position.Y += movement.PlayerSpeed;
//movement.PlayerSpeed -= movement.Player_Decel;
}
else
{
sprite.Position.Y = position.Y;
}
}
Movement class:
public float PlayerMaxHeight = 15f;
public float PlayerHeight = 0;
public float Player_Gravity = 0.01f;
private const float Player_Jump = 0.35f;
public float PlayerJump()
{
PlayerHeight += Player_Jump + Player_Gravity;
if (PlayerHeight > PlayerMaxHeight)
{
PlayerHeight = PlayerMaxHeight;
}
return PlayerHeight;
}
The best way to do jumping I found is to implement a property that will deal with acceleration.
A brief list of what to do:
Create a property that stores the current Y velocity.
Increment the Y velocity by a set amount each step - generally represented by a gravity property somewhere.
Increment1 the Y position by the Y velocity each step.
When you jump, simply subtract1 a said amount from the Y velocity - which will cause your player to jump up in an easing-out motion (start fast and slow down as he reaches the high of the jump). Because you're always incrementing the Y velocity, you will eventually reverse direction and return back to the surface.
When touching a surface, reset the Y velocity to zero.
1 Pretty sure that the Y axis is inverted in XNA (I work in Flash), so where I say increment the Y velocity you may need to decrement it instead - same deal for subtracting from it to jump.
My general approach to get a jump really quickly is to use a bleed off value to make slightly smoother looking movement. I can't look at any code/xna right now but my first thought would be something like below.
Define variables:
float bleedOff = 1.0f;
bool jumping = false;
Input update:
if(input.JumpKey())
{
jumping = true;
}
Jumping update:
if(jumping)
{
//Modify our y value based on a bleedoff
//Eventually this value will be minus so we will start falling.
position.Y += bleedOff;
bleedOff -= 0.03f;
//We should probably stop falling at some point, preferably when we reach the ground.
if(position.Y <= ground.Y)
{
jumping = false;
}
}
bleedOff = MathHelper.Clamp(bleedOff, -1f, 1f);
Obviously the bleedOff value should be calculated with a bit more randomness, probably using a gravity value, to it to make it look right but this will give the illusion of acceleration/decceleration with the jump as they rise and fall.
Rising very fast to begin with and slowing down and eventually starting to fall again and that will speed up. The clamp at the bottom will be your maximum vertical velocities.
I just wrote this off the top of my head at work so apologies if it's not quite what your looking for but I tried to keep it a bit more general. Hope it helps.
I am trying to simulate gravity in my first xna 2d game. I have the following
//Used for Jumping
double elapsedAirTime = 0.0;
double maxAirTime = 8.35;
//End Jumping
So I am trying to move the sprite up by a certain amount while the elapsedAirTime < maxAirTime
However, there is some issue where my code only seems to move the sprite up once and not multiple times during this segment of time. here is the code in my "player.cs" class, or the update method of the class.
if (newState.IsKeyDown(Keys.Space))
{
if(oldState.IsKeyUp(Keys.Space))
{
//if we are standing or jumping then change the velocity
if (playerState == PlayerStates.Standing)
{
playerState = PlayerStates.Jumping;
this.position.Y -= (float)(30.0 + ((1.2)*elapsedAirTime)*elapsedAirTime);
}
}
}
//if we are jumping give it some time
if (playerState == PlayerStates.Jumping)
{
if ((elapsedAirTime < maxAirTime) && position.Y < 3)
{
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
elapsedAirTime += gameTime.ElapsedGameTime.TotalSeconds;
}
//otherwise time to fall
else
{
playerState = PlayerStates.Falling;
}
}
//add gravity to falling objects
if (playerState == PlayerStates.Falling || playerState == PlayerStates.Standing)
{
//if we are above the ground
if (this.position.Y < windowBot - 110)
{
//chnage state to falling
playerState = PlayerStates.Falling;
this.position.Y += 3.0f + ((float)(gameTime.ElapsedGameTime.TotalSeconds));
}
else
{
playerState = PlayerStates.Standing;
elapsedAirTime = 0.0f;
}
}
Any help is much appreciated, please and thank you!
To give your sprite the feel of gravity, you should add velocity and acceleration to your Sprite class. Then, create an Update method for the Sprite, and have acceleration be added to your velocity every update, and velocity added to position every update. Position should not be based on the amount of elapsed air time. You can set the acceleration to a constant gravitational value, and then add to the velocity of the Sprite whenever you jump. This will create a flowing parabolic jump that looks nice. If you want to include timing, you can pass the GameTime into the Sprite's Update method, and use it as a modifier on the velocity. Here is an example Update method:
void Update(GameTime gt)
{
int updateTime = gt.ElapsedGameTime.TotalMilliseconds - oldgt.ElapsedGameTime.TotalMilliseconds;
float timeScalar = updateTime / AVG_FRAME_TIME;
this.velocity += this.acceleration * timeScalar;
this.position += this.velocity;
oldgt = gt;
}
If you use timing, this method is a little complicated. You have to keep track of how much time the update took, then divide it by the average amount of time an update or frame should take to get the amount you should adjust your velocity by. Without timing, the method is very simple:
void Update()
{
this.velocity += this.acceleration;
this.position += this.velocity;
}
I would suggest using the simpler method until you understand exactly how timing works and why you need to implement it.
It looks like this line is at fault:
this.position.Y -= (float)(30.0 + ((1.2) * elapsedAirTime)*elapsedAirTime);
I think you will find that this updates the sprites position quicker than you imagine, the sprite will move 330 pixels up the screen in 10 updates (assuming Game.IsFixedTimeStep == true) that is 1 tenth of a second realtime
It is likely that this is just updating so quickly that you don't get a change to see it rise before the && position.Y < 3 condition kicks in and changes the playerState.
It looks like you are trying to say - jump at a rate of x pixels per second for upto 8.5 seconds so long as space is held.
What you need for that is to change the calculation to this.position.y -= (float) (30 * gameTime.ElapsedGameTime.TotalSeconds), this will give a very liner movement to the jump action but it will mean that the sprite jumps at exactly 30 pixels per second.
If Game.IsFixedTimeStep == true - which is the default - the update gets called 60 times per second so gameTime.ElapsedGameTime.TotalSeconds is going to be about 0.1 every update. If something happens to cause an update to skip (rendering issues for example) then update will get delayed and gameTime.ElapsedGameTime.TotalSeconds may be 0.3 (the 2 updates skipped) but the formular still works out the correct jump rate.