Unity - Run a loop like sonic without falling down - c#

I am making this 2D platform game for a freelance, and I have to do a sonic mechanic, I have to make the character run through a full loop without falling down, I realised that to do this first I would have to rotate the character conform he runs through the loop, but the second part is where I am stuck.
So, basically, how can I make the character run the loop without falling down.
private void OnCollisionEnter2D(Collision2D coll)
{
Vector3 collRotation = coll.transform.rotation.eulerAngles;
if (coll.gameObject.tag == "Ground")
{
//in this part i rotate the player as he runs through the loop
transform.eulerAngles = new Vector3(transform.eulerAngles.x, transform.eulerAngles.y, collRotation.z);
//this is the part that i am stuck, trying to figure out how to make the character stay in the loop without falling, i tried collision detection,
//i tried raycasting but nothing seems to work
if (IsGrounded)
{
GetComponent<Rigidbody2D>().AddForce(Vector2.down, ForceMode2D.Impulse);
}
}
}

The trick that is most commonly used for remakes and similar is getting the speed of the player character on entry.
// Feel free to adjust this to whatever works for your project.
const float minimumAttachSpeed = 2f;
// This should be your characters current movement speed
float currentSpeed;
// You need a Rigidbody in this example, or you can just disable
// any custom gravity solution you may have created
Rigidbody2D rb;
If the speed of the character is excelling the minimum attachment speed, you can then allow them to follow a predefined path at that speed.
bool LoopAttachmentCheck()
{
return minimumAttachSpeed <= currentSpeed;
}
And now you can check to see if you're moving fast enough! I assume for this example you are using a Rigidbody2D...
(this check should only be run when you're either entering or currently on the loop)
void Update()
{
if( LoopAttachmentCheck() )
{
// We enable this to prevent the character from falling
rb.isKinematic = true;
// Here you write the code that allows the character to move
// in a circle, e.g. via a bezier curve, at the currentSpeed.
}
else
{
rb.isKinematic = false;
}
}
It's up to you to implement the actual rotational behaviour. If I were you, a good way to do this would (assuming it is a perfect circle) use RotateAround from the center of the circle.
If you have more complex shapes, you can use waypoints for movement, and just iterate through them at your speed.
The moment you fail to keep up speed, your character will fall off (so if a player decided to stop running for example) and the Rigibody2D will become kinematic.
Hope this helps!

Related

Background jitter with v-sync off in standalone build (high fps/300+ fps)

So the last 2 months I had been reworking my PlayerController to be rigidbody based for the sake of having more interactions with rigidbodies i.e. pushing objects. This went all pretty well, until I started testing my game in a standalone build. I immediately noticed strange jitter/stutter in the background (the player runs smooth) when v-sync is turned off (with v-sync is turned off my computer runs the game at 300fps+). At first I thought it had something to do with Cinemachine, I'm using CinemachineFreeLook, but to be honest as of now I'm unsure if Cinemachine is what's causing the jitter/stutter. Something worth noting is that the jitter/stutter is not constant, while the fps seems constant the jitter/stutter might go away for like 20 seconds and come back after. I have obviously done alot of research the past week and have been reading plenty of posts about FixedUpdate vs. Update, but unfortunately without any luck.
I tried to add as much information as possible to this post to give a good understanding of my project, but first the stuff I already tried;
Changing the Fixed Timestep from 0,02 to 0,01. (This kind of works,
but I would like to prevent changing the Fixed Timestep)
Turning on interpolate/extrapolate. (Now there is jitter with both
v-sync turned on and off)
Changing the CinemachineBrain Update Method to FixedUpdate/LateUpdate
instead of SmartUpdate.
Deconstructing the scene and removing some heavy on performance stuff
like Volumetric Lighting and Planar Reflections.
Creating a really barebones PlayerController with only the code to
move the player.
Removing the library folder of my project. (Read this somewhere
online, but no luck)
Turning of my Xbox One controller, to make sure there is only one
input in use.
Swithed the building from Windowed Fullscreen to Exclusive
Fullscreen.
Turned off one monitor while testing.
Move all my code to FixedUpdate.
Added these 3 scripts and changed the Script Execution Order
(Couldn't get this to work)
Put the input logic in FixedUpdate.
Below you can see both my movement scripts, one is part of my actual PlayerController, while the other was the one I wrote for the barebones Player.
void FixedUpdate() {
_isGrounded = Physics.CheckSphere(_groundChecker.position, GroundDistance, Ground, QueryTriggerInteraction.Ignore);
_inputs = Vector3.zero;
_inputs.x = Input.GetAxis("Horizontal");
_inputs.z = Input.GetAxis("Vertical");
_inputs.Normalize();
if (_inputs != Vector3.zero)
transform.forward = _inputs;
_body.MovePosition(_body.position + _inputs * Speed * Time.fixedDeltaTime);
}
This is the script I wrote for the barebones player controller.
void FixedUpdate() {
if (pushControllerScr._pushBlocker) {
input = Vector3.zero;
} else {
input = new Vector3(InputManager.MainHorizontal(), 0, InputManager.MainVertical());
}
RunAxis();
inputDir = input.normalized;
inputDir.Normalize();
// Makes the player able to move, while not landing or being dead
if (!landState && !climbManagerScr.isClimbing) {
Move(inputDir, running);
}
}
void Move(Vector3 inputDir, bool running) {
if (inputDir != Vector3.zero) {
float targetRotation = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
}
float targetSpeed = ((running) ? runSpeed : movementSpeed) * inputDir.magnitude;
stateInfoBaseLayer = _anim.GetCurrentAnimatorStateInfo(0);
if (stateInfoBaseLayer.IsTag("Climbing") || stateInfoBaseLayer.IsTag("Standing")) {
currentSpeed = 0f;
} else {
currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
}
if (stateInfoBaseLayer.IsTag("AirIdle") && currentSpeed > walkSpeed) {
currentSpeed = walkSpeed;
}
velocity = transform.forward * currentSpeed + Vector3.up * _rb.velocity.y;
if (_capsuleCol.enabled) {
_rb.MovePosition(_rb.position + velocity * Time.deltaTime);
}
}
This is what I actually use in my PlayerController Script
Below you can see some footage of the jitter/stutter. It was quite hard to capture it, since using any recording software like OBS dropped the fps in my game to a point where the jitter/stutter was disappearing. Sorry for the phone recording, but this was honestly the only way.
Footage of my game showcasing the jitter/stutter especially noticable around the yellow ship.
And finally here's an album with some relevant settings in the inspector. Imgur Album
Looking at your recording, it looks as if the player is smooth and everything else.
From my experience 99% of the time means the jitter comes from the camera not being synced up correctly to the player.
It seems you have done the right things, and usually combining the following should work fine:
CinemachineBrain should update on FixedUpdate
Use interpolate/exrapolate on your player's Rigidbody (we chose extrapolate)
Apply forces/changes to physics objects solely in FixedUpdate
If you do all 3 together, this should work smoothly regardless of vsync, frame rate, fixed delta time, etc
Obviously your specific project might have something else that's messing it up; but I've seen this issue countless times and usually these combined steps should solve it.

Unity player collision with horizontal small colliders

I know the title isnt very explanatory, but here's my problem:
I have a player (merely a cube with a rigidbody, a collider and a movement script), and I have a floor made of small 1 by 1 by 1 cubes (cubes with box colliders).
For some reason unknown to me, when my player cube falls and tries to collide horizontally with the floor, he just phases through... But want him to get blocked by the cubes just like it does vertically. Any help would be greatly appreciated ;)
heres how the scene looks like
heres a cube object
heres the player object
Here's a gif of the player going through the floor
Here's my c# player movement script (I know its very bad, but I prefer to put this here just in case its linked to my problem) :
void ApplyMovement()
{
transform.position += new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
}
If you need any more info to help me just tell me, I'll provide it as fast as I can.
The value of
new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))
is framerate-dependent. This means the faster the framerate the faster your object will move. This is usually not what you want. Instead use Time.deltaTime
// Adjust the speed in the inspector
public float speedMultiplicator = 1;
//...
new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")) * speedMultiplicator * Time.deltaTime
to get a framerate-independent movement. Also see the example in Input.GetAxis.
When dealing with RigidBody never change the transform.position (and rotation) directly!
If you would want to make the RigidBody jump to a certain position rather use
rigidBody.position = transform.position + ...;
However what you want is a smooth movement in order to keep the collision detection intact. So you should use RigidBody.MovePosition instead
rigidBody.MovePosition(transform.position + ...);
You don't have to deal with force etc.
Also add a RigidBody component to the floor objects. Even ifnthe object is not moving this improves the collision detection (at the cost of performance in the Physics engine ofcourse). And since the objects are not supposed to move set
isKinematic -> true
use Gravity -> false
you can also set freeze position and freeze rotation.
On both floor and player objects set Collision Detection -> Continous Dynamic. This improves the collision detection and looks for collisions also between frames.
Be aware, however, that dynamic detection is quite expensive so use it only if there is still trouble with too fast objects.
On the player you might want to also use interpolate as well.
Finally
Note that continuous collision detection is intended as a safety net to catch collisions in cases where objects would otherwise pass through each other, but will not deliver physically accurate collision results, so you might still consider decreasing the fixed Time step value in the TimeManager inspector
to make the simulation more precise, if you run into problems with fast moving objects.
For more information see the RigidBody manual and the Colliders manual
I recreated the scenario you described on my end. I put your "ApplyMovement" code in "Update". I was basically able to reproduce your results.
It seems to me that the issue might be Freezing Position X/Z on the Player. I think since you are doing that, you're telling the Rigidbody component that it is not allowed to modify the X/Z positions of the objects as part of it's collision resolution and physics simulation. When I turn those two off, I get results that are (I think) closer to what you're looking for.
One note: your "ApplyMovement" code is frame-locked, meaning your player will move at different speeds at different frame rates. To solve this, you'd need to multiply your Input.GetAxis values by Time.deltaTime.
Also, if your player moves too fast, it'll still be able to pass through collisions and cause odd behavior, so be sure to limit the max movement rate of the player to some reasonable value.
You should be applying a force to the Rigidbody of the character instead of directly manipulating the transform.position (this is preventing the physics engine from resolving the collisions). You're also freezing the X and Z position on the Rigidbody; you don't want that because it entirely prevents the Rigidbody from manipulating those values.
Instead of transform.postion += ... first get a reference to the Rigidbody somewhere in your script (best done in an Awake() method):
private Rigidbody _body;
private void Awake() {
_body = GetComponent<Rigidbody>();
}
Then make sure the vector built from your inputs is being applied to the object's "movement," not its position:
Vector3 inputVector = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// You can multiply this vector by a float to adjust "speed"
_body.ApplyForce(inputVector, ForceMode.VelocityChange);
Finally, you should read up on the different ForceMode options and decide which one fits your preferences: https://docs.unity3d.com/ScriptReference/ForceMode.html
Don't forget this should happen in a FixedUpdate(), not an Update() (all physics operations should be done in FixedUpdate())
As a side note, there is also the possibility your rigidbodies still up moving too quickly and passing through each-other. Even when you're using forces and velocities, this is possible so if your run into this case down the line, investigate Collision Detection Modes. TLDR; they're settings for performance vs accuracy trade-offs.

Moving a RigidBody2D a specific amount of units

In my 2D side-scrolling game I move my character using the built-in physics engine by manipulating the rigidbody.velocity.
I would like to add some sort of dodge (roll) ability, where the character moves 3 units in its direction.
Here is the code I used:
void FixedUpdate() {
if (Input.GetKeyDown(KeyCode.A) ) {
Vector2 pos = rb.position;
pos.x -= 5;
rb.MovePosition (pos);
}
}
This method works but the character kind of jumps to the position rather than moving to it (Lerping?) and also doesn't detect collisions despite the body type being dynamic.
Then I tried this:
if (Input.GetKeyDown(KeyCode.A)) {
rb.AddForce(new Vector2(-50, 0));
}
I found the AddForce way isn't accurate at all.
Is there a proper way of doing this?
Maybe you could change the Transform.pos inside Vector2.Lerp to make it look smooth? (Sorry, not enough experience with 2D in unity.)
Just increase rigibody.velocity for a set time. Your chararter object could then play a fitting aniamtion. If The player should not be able to cancle midrole jsut block the controls for that time.

Moving a 2d character based on local rotation using physics in unity

I have found a solution for this if I were to be using translation as a means of movement:
http://answers.unity3d.com/questions/1035804/how-do-you-make-an-omnidirectional-2d-character-co.html
however I cannot find a solution for how to do this with my movement being physics based.
I have in my chracters controller the ability to rotate the camera around the z axis:
public Transform target;
private float cameraRot = 3;
if (Input.GetKey("q"))
{
target.transform.Rotate(0, 0, cameraRot);
}
I then have a script on all sprites in the world I am creating that rotates to always face the camera:
public Transform target;
void Start () {
target = GameObject.Find("MainCamera").GetComponent<Transform>();
}
void Update () {
if (target != null)
this.transform.rotation = Camera.main.transform.rotation;
}
That all works fine. However when I have rotated the camera, and therefore the sprites. The rigidbody movements become all skewed as they are rotating in the "global" space and not the "local".
I have tried placing the facecamera script on the child objects only to leave the rigidbody alone but this has no effect.
I hope I made this clear enough and that someone can help.
thank you very much for your time, if I find a solution I will mark the answer as correct and if I fix this myself before an answer I will update with how I fixed it.
The solution was to use:
Rigidbody.AddRelativeForce(Vector2.down * speed);
in lieu of:
Rigidbody.AddForce(Vector2.down * speed);
that adds the force in the local axis not the global.
documentation is here:
https://docs.unity3d.com/ScriptReference/Rigidbody.AddRelativeForce.html

how do i do something every second after Collision

I am currently working on a football game(American) and im working on a tackle mechanic. Now just having 2 things bumping into each-other did not work because the character would continue to move after being forced sideways on the ground. To make the character stop after making contact with the other rigid body, I thought the best way would be to use Time.Timescale = 0; . However the problem with this is the fact that the 2 rigid body's then just go through each-other.to solve this I think the best way would be to set time scale to 0 after 1 second of collision. How can I do this?
Feedback is always appreciated ;)
to invoke a method delayed within unity presuming it is a monobehaviour.
the Invoke method apears to be what you are after
http://docs.unity3d.com/ScriptReference/MonoBehaviour.Invoke.html
however please be aware that Time.Timescale effects everything and is not local and would more than likely not get the effect you are after. setting the Velocity of the gameObject to zero should get the desired outcome.
var rb = GetComponent<Rigidbody>();
rb.velocity = Vector3.zero;
Time.Timescale would effect your whole game.
Actually Time.Timsescale is
The scale at which the time is passing. This can be used for slow motion effects.
When timeScale is 1.0 the time is passing as fast as realtime. When timeScale is 0.5 the time is passing 2x slower than realtime.
When timeScale is set to zero the game is basically paused if all your functions are frame rate independent.
From Unity Documentation
Well, what you can do is,
In script attached to Rigidbody's GameObject you can implement OnCollisionEnter.
Rigidbody _rb;
void Start()
{
_rb = GetComponent<Rigidbody>();
}
void OnCollisionEnter(Collision col){
_rb.velocity = Vector3.zero;
}
It will stop your player even after collision with ground. :)
So you can further modify collision condition like if body strikes to some specific object, you can detect it by tag or some other properties.
void OnCollisionEnter(Collision col){
if (col.gameObject.tag == "TAG_OF_SPECIFIC_OBJECT")
_rb.velocity = Vector3.zero;
}

Categories