I've been slowing grinding out programming features for my character controller, and one of the last things to implement is ladder climbing. The code I've come up with over the past 2 days mostly works, but presents some issues:
Sometimes the character bounces after climbing while holding the up arrow, at the point with which a 1-way platform meets the end of the ladder.
Once the character is all the way up, resting on the 1-way platform, it cannot go back down. This can be remedied with a longer ladder collider, but that makes issue #1 worse.
If the character is on the ground near a ladder and jumps, but presses the up arrow at the very last second, it gets flung considerably far in a certain direction.
If the character is on a rope and jumps off the rope, but holds the up arrow before doing so, it goes slightly higher, but is noticeable. Not holding the up arrow, but rather just pressing the left or right arrow and jumping, yields a regular jump. Perhaps this has something to do with the x or y input axes?
The way my classes work is that I have a Controller2D class which handles most of the raycasting stuff and movement methods, a Player class for input that calls the movement methods, and a parameters class that sets default world parameters, like gravity and jump height, but can be overridden if the character is in a certain volume, such as water or a ladder collider (this allows me to set gravity to 0 while climbing so the character can move properly). Almost all of my ladder code is contained in the OnTriggerEnter/Stay/Exit functions, and I'm just not sure if this is the best way to do it.
I tried making a ClimbUp/ClimbDown method, and shooting a raycast from the center of the player, which would allow him or her to climb up, but this was not overly successful (there were too many issues compared to using OnTriggerStay).
I'm wondering if someone who has successfully implemented ladder climbing in a raycast controller, or anybody who might have advice, would be willing to point me in the right direction for this task.
Here's some of the script:
public void ClimbLadder(float y)
{
ClimbingOnLadder = true;
SetVerticalForce(y);
}
public void JumpWhileClimbing(float x)
{
if (ClimbingOnLadder)
{
ClimbingOnLadder = false;
overrideParameters = null;
AddForce(new Vector3(x, Parameters.jumpVelocity * .70f, 0));
JumpAfterClimbing = true;
print(Parameters.jumpVelocity);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Ladder")
{
CanClimb = true;
}
}
private void OnTriggerStay2D(Collider2D other)
{
if (other.tag == "Ladder")
{
if (ClimbingOnLadder)
{
var newParameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if (newParameters == null)
{
return;
}
overrideParameters = newParameters.NewParameters;
Debug.Log("overriding");
if (ClimbingOnLadder && State.IsCollidingBelow)
{
ClimbingOnLadder = false;
overrideParameters = null;
}
}
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Ladder")
{
CanClimb = false;
ClimbingOnLadder = false;
JumpAfterClimbing = false;
}
var newParameters = other.gameObject.GetComponent<ControllerPhysicsVolume2D>();
if (newParameters == null)
{
return;
}
overrideParameters = null;
}
Related
Firstly, the code:
private bool? _hasJumped = false;
private void Update()
{
Debug.Log("Checking the _hasJumped value in Update(): " + _hasJumped);
Debug.Log("Is the player Grounded?: " + IsGrounded());
if (IsGrounded())
{
_extraJumps = _extraJumpCount;
_coyoteTimer = _coyoteTimerVal;
_hasJumped = false; //The Variable that I am having issues with.
Debug.Log("This statement only runs when the player is on ground!");
}
else
{
_coyoteTimer -= Time.deltaTime;
}
}
public bool IsGrounded()
{
return Physics2D.OverlapCircle(_feetpos.position,_feetCheckRadius,_groundLayerMask);
}
public void GetJumpInput(InputAction.CallbackContext context)
{
if (context.started)
{
_jumpBuffer = Time.time;
}
if (_coyoteTimer > 0f && (Time.time - _jumpBuffer >= 0) && context.performed)
{
if(context.interaction is TapInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce);
_whichInteraction = 0;
}
else if (context.interaction is HoldInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce);
_whichInteraction = 1;
}
_jumpBuffer = null;
_hasJumped = true; //_hasJumped set to true when I first jump
Debug.Log("The Player Has Pressed Jump!: " + _hasJumped);
}
else if (_extraJumps > 0 && context.performed && hasJumped) //Double Jump should ONLY work when I have jumped, and not when I fall off a ledge.
{
Debug.Log(_hasJumped);
_extraJumps--;
if(context.interaction is TapInteraction)
{
_myRigidBody.velocity += new Vector2 (0f, _jumpForce*_secondJumpForceMult);
_whichInteraction = 0;
}
}
else
{
_coyoteTimer = 0f;
}
}
Sorry if the the code is confusing, I've tried to add comments to the variable in focus. But the issue that I'm facing is this:
Right now, when a player falls off the edge, the player is able to jump once because of the additional "extra jump" but this should not be happening. The "extra jump" should only come into effect when the player HAS already jumped.
To stop this, I decided to use a bool "_hasJumped" which (in my head works as follows):
As long as the player is touching the ground, it is false.
When the player first jumps, it is set to true.
Only when it is true can player perform the "extra jump".
If it isn't true, the player cannot perform the "extra jump".
However, the issue that I'm facing is that although _hasJumped is set to true when I jump, it is immediately set to false in the next update (even though I'm still in the air!). Video Evidence #1
I've checked that my IsGrounded() function is working completely fine [Video Evidence #2].
The only reason I can deduce is that it may have something to do with Unity's runtime order? I'm a newbie though so I could be wrong.
I am also using the new Input System.
Thank you for taking the time to read it/give advice/help! I really appreciate it!
The problem is how fast the update actually runs in relation to the way you structured your code.
To visualize, this is what happens when the player pressed the jump button:
First frame: Player presses jump button
Second frame: Character is slightly up on the air, but the overlap collision detector is still touching the ground
Solution
Only check for IsGrounded() when the player presses the jump button, not per-update.
This also saves some computing resource as doing Physics calculation per-frame can be demanding.
Use OnCollisionEnter2D or OnTriggerEnter2D to check if the character has just landed to set canExtraJump to false.
Looks something like this:
bool canExtraJump = false;
void Update(){
// Set extrajump when player inputs jump button, and char is grounded
if (Player_Input_Jump) {
if (IsGrounded()){
Jump();
canExtraJump = true;
}
}
}
void TriggerLandedOnGround(){
canExtraJump = false;
}
// ...
/// Ideally this function should be in the character's feet (as another gameObject)
/// As you do not want this to trigger when the side of a character touches the platform.
void OnTriggerEnter2D(Collider2D other){
if (other.CompareTag("platform")){
TriggerLandedOnGround();
}
}
I am making a 2D platform game similar to Megaman X/Zero with unity as my side project, and I am trying to implement the dash function.
Following a course on Udemy, now I can make the Player jump,run and shoot bullets, but I can't make it dash.
I used InputSystem to make moves, here is my code:
void OnJump(InputValue value) //OnJump() is automatically recognized by unity, which will run when the jump button was pressed
{
//if it is pressed and also the capsulecollider of the player is touching Groundlayer(is on ground)
//then add velocity to y so it can jump ONCE
if (value.isPressed && myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Ground")))
{
myRigid.velocity += new Vector2(0f,jumpSpeed);
}
}
void OnDash(InputValue value)
{
if (value.isPressed)
{
myRigid.velocity += new Vector2(dashSpeed,0f );
}
}
The way I am thinking is: If I can successfully make the player Jump by adding velocity on y-axis, why can't I make it dash using the same way? just add velocity towards another direction?
Jumping works because the rigid body will take care of gravity and apply it for you, resulting in the character falling down, dashing is different, there is no opposite force to decrease the velocity and therefore your character will only speed up when you try dashing.
The correct implementation should disable input when dashing and to revert the added velocity when it's done.
You can implement that best using a coroutine.
The example below applies a speed boost temporarily, it does not disable the input tho:
private bool isDashing = false;
IEnumerator DashCoroutine(int dashDuration)
{
if(isDashing)
yield break;
isDashing = true;
myRigid.velocity = new Vector(dashSpeed,0);
yield return new WaitForSeconds(dashDuration);
myRigid.velocity = new Vector(0,0);
isDashing = false;
}
I'm trying to make a 2D portal in Unity that doesn't just transport the player to the other portal gameobject. But keeps the players position and movements direction and velocity after going through the portal.
I'm be no means an artist, but for example:
The player is a ball bouncing around an area, when he does through the portal his velocity is maintained, along with the fact that he entered he center of the portal.
Where here for example:
If the player enters the bottom half of the portal, he will come out the bottom half of the portal.
When this works, it works great! However, it only works 50% of the time, that 50% can have a bunch of different issues though, sometimes the ball will just not teleport. Sometimes the ball hits the first portal, teleports to the second portal, then back to the first portal, and it does this repeatedly forever. And it experiences these issues seemingly at random.
Here is my Script:
public GameObject otherPortal;
public PortalController otherPortalScript;
private BallController ballController;
public float waitTime = 0.5f;
[HideInInspector]
public bool teleporting;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
ballController = other.GetComponent<BallController>();
if(!teleporting)
{
var offset = other.transform.position - transform.position;
offset.x = 0;
other.transform.position = otherPortal.transform.position + offset;
otherPortalScript.teleporting = true;
teleporting = true;
StartCoroutine("Teleport");
}
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
teleporting = false;
otherPortalScript.teleporting = false;
}
}
IEnumerator Teleport()
{
yield return new WaitForSeconds(waitTime);
teleporting = false;
otherPortalScript.teleporting = false;
ballController.teleporting = false;
}
}
The script is attached to both portals, which are both prefabs of the same object. I set both "otherPortal", "otherPortalScript", & "waitTime" in the editor. "waitTime is something I had" to add after the fact to fix another issue I was having where sometimes "teleporting" never got set to false, I believe the the cause of the that problem is the same cause of this problem, making "waitTime" just a bandage for a larger issue. Also, anytime the Portal Script changes a variable in "ballController" such as "ballController.teleporting = false;", it's only there because the ball is add/removing points from a score system, it doesn't at all affect the ball's movement.
Consider getting rid of the teleporting property of the portals and the ball as well as the waitTime.
Now give the ball a List<PortalController> inUseControllers (note you need to add using System.Collections.Generic). Whenever it collision-enters one portal, check if the list is empty via inUseControllers.Count == 0, and if so, add both involved PortalController's to that list and handle the teleporting movement. When the ball collision-exits a PortalController, remove it from the inUseControllers list; it will thus only be emptied again once the ball leaves every portal zone.
This approach should simplify the code, yet safely protect against accidental back-and-forth circles.
In the title it is not easy to summarize this behavior in the best way.
I have an Editor and a Monobehaviour executed in [ExecuteInEditMode].
I need to move objects by selecting them with the mouse. I thought I didn't encounter any difficulties as I perform this operation with both a character and sprite. But I was wrong. When I select a 3D object (not the character) I can pick and drag it but if I hold the mouse button down without moving it it tends to slip away as if it were falling but on the Y and Z axes. If I deselect its collider the issue does not happen but obviously the collider is necessary for my operations.
I have removed all the unnecessary lines from the code, hoping to solve the problem, but it remains.
void OnGUI() {
if (moveChar) {
StartCoroutine("WaitChar");
} else if (moveSpot) {
StartCoroutine("WaitSpot");
}
Event e = Event.current;
Vector3 mousePosition = e.mousePosition;
mousePosition.y = Screen.height - mousePosition.y;
Ray ray = cam.ScreenPointToRay(mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo)) {
mousePositionWorld = hitInfo.point;
if (e.rawType == EventType.MouseDown) {
switch (e.button) {
case 0:
if (isSelectedSphere) {
moveSpot = true;
}
break;
case 1:
if (isSelectedChar) {
moveChar = true;
}
break;
}
} else if (e.rawType == EventType.MouseUp) {
switch (e.button) {
case 0:
if (moveSpot) {
moveSpot = false;
}
break;
case 1:
if (moveChar) {
moveChar = false;
}
break;
}
}
}
}
public IEnumerator WaitChar() {
character.transform.position = mousePositionWorld;
yield return new WaitForSeconds(0.1f);
}
public IEnumerator WaitSpot() {
MoveSpot.transform.position = mousePositionWorld;
MoveSpot.transform.localScale = Vector3.one * ((new Plane(cam.transform.forward,
cam.transform.position).GetDistanceToPoint(
MoveSpot.transform.position)) / radiusPoint);
yield return new WaitForSeconds(0.1f);
}
I have simplified everything with two public variables to choose the object to move.
What I do is very simple, I don't understand why I get this behavior. With the character I have no problem (not even with the sprites, which for the moment I removed from the code). When I click with the right mouse button I run the coroutine that moves the character to the cursor coordinates and continues like this until I release the button. No problem, the character moves along with the cursor without problem.
However, if I execute the same operation (left button) with a 3D object like Sphere, Cylinder, Cube it moves following the mouse but if I don't move the cursor and don't release the button the object slides away with the Y and the Z axes that increase in negative. As I said, if I deactivate the collider the object remains at the cursor position.
The problem is that the spheres cannot be selected through public variables but directly from the script so I need the collider.
I probably don't know Unity well enough to know the reason for this behavior.
Can you clarify this for me?
OnGui is bad (let me explain)
First, OnGui is part of Unity's IMGui system (stands for IMmediate Gui), and has been heavily depreciated in favor of uGui, which is a lot easier to work with for a number of reason. And one of those reasons is....
OnGui is called more than once per frame
Because OnGui is called multiple times per frame, that means that Event.current hasn't changed, meaning moveChar hasn't changed, meaning your coroutine gets started more than once.
And there's no real way around this. Its going to be almost impossible to do what you want using OnGui. It was never meant to handle this sort of logic.
Everything you have should be done inside Update() instead and replacing things like e.rawType == EventType.MouseDown with their Input relatives, eg. Input.GetMouseDown(0).
I'm kind of new to Unity. As by the title, I am having trouble getting the collisions right in my game. I am using the custom physics script from unity: https://unity3d.com/learn/tutorials/topics/2d-game-creation/scripting-gravity?playlist=17093. In my game, I am experiencing difficulties in disable collisions.
For example, when I use
Physics2D.IgnoreLayerCollision (8, 9);
It doesn't change anything and the two characters still collide. Also, for some reasons, the triggers behave strange and are still affected by collisions. Going near a character with triggers will make him float up. I've been stuck on this for a long time and I'd really appreciate the help.
It is worth noting that I am using custom physics and using a box collider 2d to detect attack ranges etc. Here is some code of that:
Collider2D attackbox = Physics2D.OverlapBox (attackPos.position, new Vector2 (attackRangeX, attackRangeY), 0, whatIsPlayer);
if (attackbox != null && !isDead && characterController.isDead == false) {
targetVelocity = Vector2.zero;
animator.SetTrigger ("isPunching");
timeBtwAtk = startTimeBtwAtk;
}
and I have individual punch triggers in the animation to detect when the character is actually being hit:
public void SetColliderIndex(int spriteNum)
{
colliders[currentColliderIndex].enabled = false;
currentColliderIndex = spriteNum;
colliders[currentColliderIndex].enabled = true;
}
public void ClearColliderIndex()
{
colliders[currentColliderIndex].enabled = false;
}
void OnTriggerEnter2D(Collider2D col)
{
if (col.tag == "Player")
{
col.GetComponent<CharacterController2D> ().TakeDamage (enemyDamage);
col.GetComponent<CharacterController2D> ().canWait = false;
col.GetComponent<CharacterController2D> ().canControl = false;
}
}
When I use Physics2D.IgnoreLayerCollision (8, 9); I want both specified layers not to interact whatsoever. I don't want any weird shifts, or floating when they pass through each other. Also when I move my player agains the enemy, I don't want him to be able to push him back. I want it to be as if he is running into a wall.
I have answered something similar to this here. Also, if you don't want the player suddenly floating upon collision, you can just set the rigidbody/rigidbody2d type to kinematic, but this mean the object will not be affected by physics. you will have to do that in code.
i think your problem is that at some point the two colliders interact and/or one of the two gets activated back, but from the code that you posted i can't clearly identify the problem if there's any