I am trying to get my 'player' to change directions with my below script
however, it is contently staying stuck in the first 2 if statements of my 'void Update()'
I am trying to use these two scripts (1. https://pastebin.com/AGLatvUD (I wrote this one) and 2. https://pastebin.com/2XA3w04w)
I am attempting to use CharacterController2D to move my player with specified points and actions
Don't know if this is the right place to ask but I figured I'd try!!
void Update() // not sure if should have more in 'FixedUpdate' or others (maybe?)
{
if (isRight && transform.position.x > currentPoint.position.x) // flipping the character -- I'm pretty sure I can use TestCharacterController2D to do this for me, this is comparing the player's 'transform'
{
moveSpeed = -0.25f; // tells controller to head in the left direction
isRight = false; // no longer facing right
}
if (!isRight && transform.position.x < currentPoint.position.x) // reverse of above
{
moveSpeed = -0.25f; // tells controller to head in the right direction
isRight = true; // no longer facing left
}
if (transform.position == currentPoint.position) // checks to see if player is at 'currentPoint'
{
pause = true; // starts the pause sequenece
if (pause) // when the movement is pause do the the following
{
animator.SetFloat("Speed", 0); // player stops moving -- works!
if (maxPause <= 100) // checks to see if still paused
{
Debug.Log(maxPause);
maxPause--; // reduce pause amount (working way out of loop)
if (maxPause < 0) // found 'maxPause' was going to far below zero
maxPause = 0;
}
if (maxPause == 0) // when 'maxPause' timer has finished
{
pointsSelection++; // move to netx point
maxPause = 100; // reset 'maxPause' timer
pause = false; // resume 'transform.position == currentPoint.position' process
}
}
if (pointsSelection == points.Length) // makes sure 'pointsSelection' doesn't go out of bounds
pointsSelection = 0; // start the player's movement process over again
}
else // not sure if requried
Debug.Log("removed pause = false");
any help would be appreciated!!!
Thank you very much!!
I am an amateur (obviously :))
littlejiver
There's nothing that sets the character's position exactly to currentPoint. There are a number of ways to solve this. One way is to check if the character is near the currentPoint instead of exactly on it.
if (Vector2.Distance(transform.position, currentPoint.position) < 0.1f) {
pause = true; // starts the pause sequenece
...
Related
Overview
I'm making an endless runner game. In this game, I have 5 lines, I want to player smoothly switch lines something like this Ref Link
In my case, I have everything the same but instead of a car, I have a player with PlayerController attached to it.
I'm changing the player line on Button click and also on IPointerDownHandler & IPointerUpHandler
Code
Full Code
[SerializeField] private List<Vector3> lines; // 5 lines in my case. Vector3 (0,0,0) and so on ...
private int flag;
Vector3 currLine;
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
//I used two approaches to moving but both are not working as indented
// 1 _ using DoTween
// 2 _ using Vector3.Lerp ()
private void MoveRight ()
{
// some input delay for Ipointers
if (inputDelay > 0) return;
if (currLine == lines [lines.Count - 1]) return; // can't move right anymore
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f); // rotate player toward target
transform.DoMoveX (currLine.X, 0.3f) // 0.3f is coming from inspector
.SetEase (Ease.Linear) // i almost tried all Ease
.OnComplete ( ()=> DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f));
// using Lerp
LookAt (new Vector3 (currLine.x,Y,Z));
transform.position = Vector3.Lerp(transform.position, new Vector3(currLine.x, ..,..), lineChangeCurve
.Evaluate(Time.deltaTime * lineChangeSpeed));
}
private void MoveLeft ()
{
// same code as MoveRight
}
Problem
The code I wrote is prettier much working. the player is changing lines and also rotating towards the line but I'm unable to figure out what should i need to do to make this effect look like a reference.
Can you tell me how can I achieve the same smoother effect as the reference for my player?
Here is the link that I made so far
3D Assets link
Player lines distance :
new Vector3 (-8, 0,0)
new Vector3 (-4, 0,0)
new Vector3 (0, 0,0)
new Vector3 (4, 0,0)
new Vector3 (8, 0,0)
Thanks in Advance
You seem to be mixing two different animation techniques.
You do
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
...
which starts a tween animation
and then you also do Evaluate on the lerp which seems to be a bit redundant.
Your issue with the Lerp and Evaluate is
a) that you pass in
Time.deltaTime * lineChangeSpeed
which basically means
lineChangeSpeed / frame rate (e.g. 60)
=> This is a way to small value and will most probably basically mean you don't move at all (depending on your used curve)
You want to call this every frame and pass in a value increasing from 0 to 1 here (e.g. in a Coroutine)
b) you call it only exactly once. This will not result in any movement at all .. or at least only in a single step in the first frame => which probably causes a little "hiccup"
=> get rid of the lerp line all together. At best it interferes with the tween animation and is probably the cause of it looking somewhat off
To the question about keeping the button pressed.
You have nothing to prevent this in
private void ChangeLines ()
{
// Getting Inputs
if (Input.GetKey(KeyCode.LeftArrow)) { flag = -1; }
else if (Input.GetKey(KeyCode.RightArrow)) { flag = 1; }
else flag = 0;
if (flag > 0) MoveRight ();
else if (flag < 0) MoveLeft ();
}
First of all the flag seems quite redundant. And then you would
Either change to GetKeyDown so only the first frame where the button goes down is handled
Or add a flag that ignores all input while an animation is already running
Or even both (depending on your desired UX)
So e.g.
private bool alreadyAnimating;
private void ChangeLines ()
{
if(alreadyAnimating) return;
if (Input.GetKey(KeyCode.LeftArrow))
// Or as said maybe even better
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
MoveLeft();
}
else if (Input.GetKey(KeyCode.RightArrow))
// same here
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
MoveRight();
}
}
and then block and reset the flag when done with moving
alreadyAnimating = true;
transform.DoRotate (new Vector3(0, 45, 0) , 0.2f);
transform.DoMoveX (currLine.X, 0.3f)
.SetEase (Ease.Linear)
.OnComplete (()=>
{
DoTween.DoRotate (new Vector3(0, 0, 0) , 0.2f)
.OnComplete(() => { alreadyAnimating = false; })
});
I used Lerp() for these kind of conditions and it worked well all the time. As I saw your character correctly turns but it look like a snap turn. I couldn't spot an obvious error in your code. Play a little more with the rotation time and you will get your desired result. Sorry this must be a comment but dont have the reps.
The idea of the animation for switching lines is pretty simple. All you need is an animation of turning the main character to the side and back. Let's say the line change animation takes 1 second. Then you need that when moving to the left lane, the main character makes a turn to the left in 0.5 seconds and the next 0.5 seconds turns straight again. While the gameobject moves into the adjacent line, the main character will make a small smooth turn and the animation will look better. This is how it is done in the example you showed in the question. In your video, instead of this animation, the main character abruptly turns to a certain angle and returns to its original position abruptly back when it reaches the required line.
This is a weirdly phrased question...and I don't know how to explain it well. I'm making a ledge hang mechanic for my game I'm making, you press either of the directions to get onto the ledge, but you also press the directions to get off of the ledge. This means while you're trying to get on the ledge, he instantly falls off. Making a fixed wait time also doesn't feel good...I'm thinking that it would set the axis to 0 until the button is let go, but the problem is that if it's 0, the button is let go. It's a conundrum I can't solve.
Here is the code that detects movements pressed on the left or right arrow keys.
mx = Input.GetAxisRaw("Horizontal");
//Detects if the left or right arrow keys are pressed, and turns it into a number.
Here's some of the code that detects movement while you're hanging There's already a problem...there's an "if hanging" statement right before that's supposed to restrict movement until you let go or jump, but since moving is supposed to ALSO get you out of the hanging, it makes this point moot. I could really just use different buttons, like restricting it to just the jump button or down key (since that uses a different variable), but I want to see if a solution is possible.
if (IsGrabbing == true)
{
rb.velocity = new Vector2(0f, 0f);
rb.gravityScale = 0f;
// mx2 detects vertical movement. This is still a problem, if you're pressing down before you hang, it'll instantly hang instead of waiting for another press, but it's not as bad because you don't press down to hang on a ledge
if (mx != 0 || mx2 < 0)
{
//Just the code that makes you let go and unable to hang, this is reset when you land.
IsGrabbing = false;
anim.SetBool("IsHanging", false);
redXSize = 10f;
redYSize = 10f;
greenXSize = 0f;
greenYSize = 0f;
rb.gravityScale = 5f;
}
I've tried many things, a lot of "while" loops I've tried just crash the game. Does anyone know how to fix this issue? I'll provide all the code if needed.
Edit: With the help of the reply, I've found the solution! There needs to be a flag set like this, where by default it's false. However, it also only checks if it's false, and the else statement means it's true, not to mention the only way to set it to true bypasses that gate anyways. This gives the result needed! Also, while "while" loops seem to break it, adding a condition to every check to not do it while grabbing seemed to fix it. Thanks, everyone!
if (IsGrabbing == true)
{
if (mx != 0 && InitiateGrab == false)
{
InitiateGrab = false;
}
else
{
InitiateGrab = true;
}
rb.velocity = new Vector2(0f, 0f);
rb.gravityScale = 0f;
if (mx != 0 && InitiateGrab == true || mx2 < 0 && InitiateGrab == true)
{
IsGrabbing = false;
InitiateGrab = false;
anim.SetBool("IsHanging", false);
anim.SetBool("IsRising", false);
redXSize = 10f;
redYSize = 10f;
greenXSize = 0f;
greenYSize = 0f;
rb.gravityScale = 5f;
}
}
Since you don't have the code. I can't exactly help you out with the specifics, but I still understand what you're asking and can still help you out. Here is the "sample" code that I'd use in your case:
bool isHanging = false;
void Update()
{
if (Input.GetKeyDown("space") && !isHanging)
{
isHanging = true;
// Use ur code to hang onto the ledge.
} else if (Input.GetKeyDown("space") && !isHanging)
{
isHanging = false;
// Use ur code to get off the ledge.
}
}
bool CheckIfHanging()
{
if(isHanging) { return false; }
else return true;
}
Essentially, this causes a toggle effect where when you press the spacebar AND are close enough to the ledge, you set the bool to true, and you hang onto the ledge and went you want to get off it, you set the isHanging to false, and run your code to jump off the ledge. This does have the effect of making ur controls a toggle control however. If you're looking for a "press" control, where you hold onto a key to hold onto the ledge, and then let go when you press off, then maybe try this:
void Update()
{
if (Input.GetKeyDown("space"))
{
// Hang onto ledge
} else if (Input.GetKeyUp("space"))
{
// Let go of ledge.
}
}
Hope I could help!
(P.S. please put ur code in next time so that I can help you out more!)
Quick update on my answer since u posted ur code. Maybe try changing ur movement code to something like this
bool isHanging = false;
int speed; [SerializeField]
void MoveCharacter()
{
if (isHanging) return;
else mx = speed * Input.GetAxisRaw("Horizontal");
}
Essentially, this still allows you to move with your arrow keys, but will essentially stop your movement code if you're hanging. Also from ur previous comment, you said that you apparently can't use Input.GetKeyDown/Up for your left/right arrows? I'd recommend using KeyCode if that's the case, that should have the input for all btns in ur keyboard.
bool HangLedge()
{
if (!isHanging && Input.GetKeyDown(KeyCode.DownArrow)) return true;
else if (!isHanging && Input.GetKeyDown(KeyCode.DownArrow)) return false;
}
// Set ur ledge code and movement code to take this bool, and run the code when it's either false/true.
Hope I could clarify!
I have a loop that draws 4 raycasts from the bottom of my character, which are used to detect collision with objects. My issue is that if raycast #1 and raycast #4 are colliding with different objects they return the values from both objects.
I'd like to make it so that I look for a hit on raycast #4 only, and if that doesn't return a hit then I check raycast #3, etc. Once I have a hit I will check for the value of the object with which the raycast is colliding. I tried using RaycastHits[], but I believe this is intended to be used when you want to analyze multiple hits within all raycasts, not just a single raycast.
for (int i = 0; i < VerticalRayCount; i++)
{
Vector2 rayOrigin = (directionY == -1) ? RaycastOrigin.bottomLeft : RaycastOrigin.topLeft;
rayOrigin += Vector2.right * (VerticalRaySpacing * i + deltaMovement.x);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask);
Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength, Color.red);
if (directionY == -1)
{
if (directionX == 1)
{
if (i == VerticalRayCount -1)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
else if (i == 0)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
}
}
}
I see two problems in your code:
In your for-loop currentPlatform gets allways overwritten by the last hit.
But since you check if(currentPlatform != hit.collider.gameObject) it will allways switch between the last two hits if there is more than one. This happens because it hits the first one and sets it. Than the second hit is not equal to the first one so it gets overwritten. So the next time the currentPlatform is still the second hit and gets again overwritten with the first hit of the new loop and always goes on like this.
To avoid the second remove those if(currentPlatform != hit.collidergameObject).
And you should add break; after a hit so the for loop doesn't continue with the other raycasts but rather exits the for loop immediately. So you get only one hit and don't overwrite them.
...
if(hit)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
// here add break so the for loop is not continued
break;
}
...
On this way currentPlattform allways has the value of the first hit.
Im working in Unity3d (this is more a C# question, so I doubt that is an issue). Im working on a movement system like you would find in Civilization. I have a loop setup so that you can move 2 squares per turn. This works fine. I click on a square 10 blocks away and it takes 5 turns to get there. Now im trying to make the pawn lerp between blocks. I have got lerp to work, problem is, it jumps from the current tile to the 1st tile, then transitions to the 2nd tile where its supposed to be. I used a coroutine to make this work instead of the update function (as the update would cause it to just lerp to the final destination instead of from current, to first, to second). So what im running into is the loop that goes through each move the pawn has, isnt waiting for the coroutine to complete before continuing its own loop. Here is the code
public void MoveNextTile()
{
float remainingMoves = moveSpeed;
while (remainingMoves > 0)
{
if (currentPath == null)
return;
//Get the cost from current tile to next tile
remainingMoves -= map.CostToEnterTile(currentPath[0].x, currentPath[0].y, currentPath[1].x, currentPath[1].y);
//Move us to the next tile in the sequence
toVector = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
Vector3 fromVec = transform.position;
StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
//transform.position = map.TileCoordToWorldCoord(currentPath[1].x, currentPath[1].y);
//Remove the old current tile
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath.RemoveAt(0);
if (currentPath.Count == 1)
{
this.tileX = currentPath[0].x;
this.tileY = currentPath[0].y;
currentPath = null;
}
}
}
IEnumerator MoveObject(Vector3 source, Vector3 target, float overTime)
{
float startTime = Time.time;
while (Time.time < startTime + overTime)
{
transform.position = Vector3.Lerp(source, target, (Time.time - startTime) / overTime);
yield return null;
}
transform.position = target;
}
I know this is a noob question. I just never have needed to do this in C# before. Thank in advance for all the help
I suggest you research into how coroutines work.
Your coroutine doesn't execute fully and then return to complete the rest of your MoveNextTile function. It actually executes up until the first yield statement and then continues execution of MoveNextTile. Each subsequent frame will continue to run one 'step' of the coroutine until the next yield statement in an attempt to replicate asynchronous methods.
What you want to do is tell your program to explicitly wait for your coroutine to finish. To do so;
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
Of course, you can only use this statement inside of an IEnumerator. So you would have to change your void MoveNextTile function to IEnumerator MoveNextTile. You end up with something as follows;
public IEnumerator MoveNextTile() {
// Ommited
while (remainingMoves > 0) {
//Ommited
yield return StartCoroutine(MoveObject(fromVec, toVector, 1.0f));
// Now MoveNextTile will continue after your MoveObject coroutine finishes.
}
}
Here is the gameplay. There is three condition.
The player step on a Switch-Tile and it became false.
1) When the Enemy step on it (trapped) AND the player step on it too, the Enemy will be destroyed.
2) But when the Enemy step on it AND the player DIDN'T step on it too, the Enemy will be escaped.
3) If the Switch-Tile condition is true then nothing happened. The effect is activated when the Switch tile is false (player step on the Switch-Tile).
Because there are a lot of Enemy and a lot of Switch-Tile, I have to use foreach loop.
The problem is after the Enemy is ESCAPED (case 2) and step on another Switch-Tile again, nothing happened to the enemy!
I didn't know what's wrong. The effect should be the same, but the Enemy pass the Switch tile like nothing happened (They should be trapped)
Can someone tell me what's wrong?
Here is the code :
public static void switchUpdate(GameTime gameTime)
{
foreach (SwitchTile switch in switchTiles)
{
foreach (Enemy enemy in EnemyManager.Enemies)
{
if (switch.Active == false)
{
if (!enemy.Destroyed)
{
if (switch.IsCircleColliding(enemy.EnemyBase.WorldCenter,
enemy.EnemyBase.CollisionRadius))
{
enemy.EnemySpeed = 10; //reducing Enemy Speed if it enemy is step on the Tile (for about two seconds)
enemy.Trapped = true;
float elapsed = (float)gameTime.ElapsedGameTime.Milliseconds;
moveCounter += elapsed;
if (moveCounter> minMoveTime)
{
//After two seconds, if the player didn't step on Switch-Tile.
//The Enemy escaped and its speed back to normal
enemy.EnemySpeed = 60f;
enemy.Trapped = false;
}
}
}
}
else if (switch.Active == true && enemy.Trapped == true
&& switch.IsCircleColliding(enemy.EnemyBase.WorldCenter,
enemy.EnemyBase.CollisionRadius)
)
{
//When the Player step on Switch-Tile and
//there is an enemy too on this tile which was trapped = Destroy Enemy
enemy.Destroyed = true;
}
}
}
}
else if (switch.Active == true && enemy.Trapped == true
&& switch.IsCircleColliding(enemy.EnemyBase.WorldCenter,
enemy.EnemyBase.CollisionRadius)
)
{
//When the Player step on Switch-Tile and
//there is an enemy too on this tile which was trapped = Destroy Enemy
enemy.Destroyed = true;
}
This code will never be true since you set whether the enemy is trapped or not only if the switch is not active. Once you set it to true you should really break out the loop and then test again to see whether you should destroy the enemy or not.
Rethink the logic you want to do, and ensure that at each stage you can gain access to the correct information for the enemy etc.
P.S You do NOT need to use a foreach loop. You could easily use a for loop and iterate over the container or if you were really sadistic you could simple hardcode for every enemy yourself. Foreach loops are simply one way to solve this kind of issue, just because they can be used, doesnt mean you need to use them ;)