I am trying to re-create the popular SnakeVSBlock mobile game. Most of the physics are pretty straight forward but I cannot seem to re-create the snake movement. What's special about the snake movement in the game is that the previous node follows the next ones path perfectly, both in X and in Y. This is my current code which does work as a snake movement but does not follow the exact path of the next node. I'm wondering if anyone has a solution to this.
public void Move()
{
if (Input.GetMouseButton(0) && GameManager.Instance.IsGameOver() == false && Nodes.Count > 0)
{
Vector3 curPos = Input.mousePosition;
curPos.z = -1;
curPos = Camera.main.ScreenToWorldPoint(curPos);
Nodes[0].GetComponent<Rigidbody2D>().velocity = new Vector2(curPos.x - Nodes[0].position.x, 0) * 20;
if (Nodes[0].position.x > 2.56f)
{
Nodes[0].position = new Vector2(2.56f,Nodes[0].position.y);
}
else if (Nodes[0].position.x < -2.56f)
{
Nodes[0].position = new Vector2(-2.56f, Nodes[0].position.y);
}
for (int i = 1; i < Nodes.Count; i++)
{
Vector2 newPos = new Vector2(Nodes[i-1].position.x, Nodes[i].position.y);
if (GameManager.Instance.GetPoweredUp())
{
Nodes[i].position = Vector2.Lerp(Nodes[i].position, newPos, Time.deltaTime * 14);
}
else
{
Nodes[i].position = Vector2.Lerp(Nodes[i].position, newPos, Time.deltaTime * 10);
}
}
}
UPDATE: I changed the basic logic for the snake movement in my game. Instead of having the snake move upwards, the snake stays in the same position and the blocks move downwards. However I still can't obtain the smooth snake movement like in the real games. Are there any other suggestions?
If I understand what you want to do, you should approach this in the reverse order.
So, if you have the nodes 0 to n:
Start from the n-th node
Assing this node the position of the (n-1)-th node
Then for each i node, assign it the position of the i-1 node
When you moved all the nodes, move the node 0 according to the user input.
It should look like this:
public void Move()
{
int n = Nodes.Count;
if (n > 0)
{
for (int i = n - 1; i > 0; i--)
{
_curNode = Nodes[i];
_nextNode = Nodes[i - 1];
Vector3 pos = _nextNode.transform.position;
pos.z = Nodes[0].position.z;
_curNode.tranform.position = pos;
}
float horizontalInput = Input.GetAxis("Horizontal");
Nodes[0].Translate(Vector3.right * Time.deltaTime * horizontalInput * 10);
Nodes[0].Translate(Vector3.up * Time.deltaTime * 4);
if (Nodes[0].position.x > 2.90)
{
Nodes[0].position = new Vector2(2.90f, Nodes[0].position.y);
}
else if (Nodes[0].position.x < -2.90)
{
Nodes[0].position = new Vector2(-2.90f, Nodes[0].position.y);
}
}
}
Also, unless you somehow increment the two LerpTime variables, you're misusing the Lerp function, and you don't need it in this context either.
This is an awesomely clear guide on what Lerp really means and how to use it properly:
http://lunarlabs.pt/blog/post/the-art-of-lerp
The old mobile game "snake" where you collect little squares has the same sort of movement. The solution then was that the parts of the snake don't actually move, just each frame you add a bit to the head and take one off the tail.
The reason why that approach works with snake but not immediately in this snake vs blocks game is that the former is discrete, and the latter is continuous.
One way to solve this is that each body part could always be lerping towards the one above it and when it gets there, it teleports back. This would give the illusion of the snake moving forward, when actually the body parts are stuck in place, just like the old snake game.
This works on paper but I have no idea how it will perform. However, if you are really desperate you could consider giving this a try.
Related
Here is a simple script to demonstrate the issue. This script should move the cursor to the left for 1 second and then back to the right for 1 second. However you'll see it just moves it to the left then stops. You can flip the order and try to move right first, but you'll notice that still won't work. I've also tested with moving up / down, moving up works as expected, moving down does not.
public class dumbtestscript : MonoBehaviour
{
void Start()
{
Mouse.current.WarpCursorPosition(new Vector3(123, 123));
StartCoroutine(MoveCursorLeftThenRightCoroutine());
}
IEnumerator MoveCursorLeftThenRightCoroutine()
{
float startTime = Time.time;
while (Time.time < startTime + 1)
{
Mouse.current.WarpCursorPosition(Input.mousePosition +
(Time.deltaTime * new Vector3(-0.1f, 0)));
yield return null;
}
while (Time.time < startTime + 2)
{
Mouse.current.WarpCursorPosition(Input.mousePosition +
(Time.deltaTime * new Vector3(0.1f, 0)));
yield return null;
}
}
}
Am I misunderstanding something about how WarpCursorPosition is supposed to work?
Any advice appreciated. Thanks.
Edit: Upon further inspection it may have to do with the vector being passed to WarpCursorPosition. For example regardless of whether we use this to move left:
Mouse.current.WarpCursorPosition(Input.mousePosition + new Vector3(-0.1f, 0));
or this:
Mouse.current.WarpCursorPosition(Input.mousePosition + new Vector3(-1f, 0));
it moves to the left at the same speed. So it seems anything between -0.1 and -1 is being rounded to -1. Conversely for going right everything between 0.1 and just under 1 is being rounded to 0 which would explain why it wasn't moving in the original.
So everything is getting round down to the nearest integer for some reason? Both Input.mousePosition and the Vector3 we are adding are both Vector3 and I thought could handle float numbers so not sure why things are being rounded down to ints, if thats whats happening?
The issue
So everything is getting round down to the nearest integer for some reason?
Most probably Yes!
WarpCursorPosition makes your actual system cursor jump to the given pixel coordinates.
While inside Unity the pixel space is always provided as float for many things and mainly for making direct calculations with it, the actual system cursor uses pixels (like everything that is actually happening on the screen) which are always full int values!
So, yes, it will always (not round but rather) floor your given input to the next int (full pixel). Basically what happens if you simply use
var x = (int) (Input.mousePosition.x - 0.1f);
=> You will see that x will be exactly Input.mousePosition.x - 1 since if e.g. the current Input.mousePosition.x was 230 then you get (int) 229.9f which is 229.
In the other direction though you get e.g. 230 + 0.1f so (int) 230.1f again simply is 230.
=> Your steps would need to at least be one full pixel!
Solution
So instead of constantly read the current Input.mousePosition which underlies the afore mentioned pixel coordinates rather use and update a local vector that does not:
IEnumerator MoveCursorLeftThenRightCoroutine()
{
// Store the current mousePosition as a Vector2
// This uses floats and is not restricted to full pixel jumps
var currentPosition = Input.mousePosition;
for(var passedTime = 0f; passedTime < 1; passedTime += Time.deltaTime)
{
// simply continously add to the float vector
currentPosition += Vector2.left * speedInPixelsPerSecond * Time.deltaTime;
// Using that float vector as input may in some frames cause no movement where the clamp results
// still in the same pixel but it will catch up as soon as the vector was increased/reduced enough
Mouse.current.WarpCursorPosition(currentPosition);
yield return nnull;
}
for(var passedTime = 0f; passedTime < 1; passedTime += Time.deltaTime)
{
currentPosition += Vector2.right * speedInPixelsPerSecond * Time.deltaTime;
Mouse.current.WarpCursorPosition(currentPosition);
yield return null;
}
}
In general for the new Input system I would rather use Mouse.current.position instead of Input.mousePosition.
I don't have any experience using WarpCursorPosition but when I was experimenting to answer this thread, I felt it's not consistent & would like to advice not to use it.
Code
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class Q69189325 : MonoBehaviour
{
private void Start()
{
Mouse.current.WarpCursorPosition(Input.mousePosition);
StartCoroutine(MoveLogicCrt());
}
IEnumerator MoveLogicCrt()
{
yield return MoveCursorToDirectionCrt(Vector3.left, 150, 5);
yield return null;
yield return MoveCursorToDirectionCrt(Vector3.right, 350, 5);
}
private IEnumerator MoveCursorToDirectionCrt(Vector3 direction, float speed, float movementTime)
{
float startTime = Time.time;
while(Time.time < startTime + movementTime)
{
Vector2 newMousePosition = Input.mousePosition + (Time.deltaTime * direction * speed);
Mouse.current.WarpCursorPosition(newMousePosition);
yield return null;
}
}
}
Note:
For some reason when I put the same speed to both left & right it doesn't move right which was weird.
Execution Overview
https://gfycat.com/eventhriftyannelida
Happy coding!
I've been looking through the Source SDK and read through how it handles player collision with the space around it. Inspired by that, I've been writing a character controller that should have similar behavior.
I have code to control a Transform transform by computing its velocity vecVelocity and trying to move it along that vector. I represent the player by a capsule of radius capsuleRadius and sphere centers capsuleStart, capsuleEnd, but I haven't given it a proper collider as of yet
When trying to deal with collisions, I came up with the following function
void TryPlayerMove()
{
Vector3 transDirection;
float transMagnitude;
Vector3 translation;
Vector3 currentMotion = Vector3.zero;
int i, j, maxHits = 4; //Bump up to 4 times before giving up
RaycastHit capsule;
bool collision;
Vector3[] collNormal = new Vector3[maxHits];
float collDistance;
//Find starting translation vector
translation = vecVelocity * Time.deltaTime;
//Store initial displacement
Vector3 oldTranslation = translation;
for (i = 0; i < maxHits; i++)
{
//Find translation vector magnitude and direction
transDirection = translation.normalized;
transMagnitude = translation.magnitude;
//If we wouldn't move anyway, feel free to break the loop
if (transMagnitude == 0)
{
break;
}
//Shoot a capsule to desired endpoint
collision = Physics.CapsuleCast(capsuleStart + currentMotion, capsuleEnd + currentMotion, capsuleRadius, transDirection, out capsule, transMagnitude);
if (collision)
{
//If we hit something, hug it and take off what is left of translation in that direction
collNormal[i] = capsule.normal.normalized;
collDistance = capsule.distance;
//currentMotion += transDirection * collDistance; //WHY DOESN'T THIS WORK???
translation = ClipVector(translation*(1f-collDistance/transMagnitude), collNormal[i]);
Debug.Log(transDirection.magnitude);
//If we're going towards something we've hit before, stop moving so we don't go into weird corner loops
for (j = 0; j < i; j++)
{
if (Vector3.Dot(translation, collNormal[j]) < 0)
{
translation = Vector3.zero;
break;
}
}
}
else
{
//Just move
currentMotion += translation;
break;
}
}
//Translate the player character and take note of its velocity for future computation
vecVelocity = currentMotion / Time.deltaTime;
transform.Translate(currentMotion, Space.World);
}
and the clipping function just makes sure our vector is REALLY not pointing at the collider
Vector3 ClipVector(Vector3 inputVector, Vector3 normalVector)
{
float projection;
Vector3 outputVector;
//Determine how much to take out
projection = Vector3.Dot(inputVector, normalVector);
//Subtract the perpendicular component
outputVector = inputVector - normalVector * projection;
//Iterate once more just to make sure
float adjust = Vector3.Dot(outputVector, normalVector);
if (adjust < 0f)
{
outputVector -= normalVector * adjust;
}
return outputVector;
}
This works fine as it is, and the controls respond quite as I would expect, but with a cosmetic fault: each time the player "collides" with something, it does so stopping at a different distance from the object, I've logged it and it fluctuates around 5-10 percent of the capsule's radius away from the player.
In the TryPlayerMove() function, there is a command that I would expect to make it hug the collider, it's commented and decorated with a note of desperation
//currentMotion += transDirection * collDistance; //WHY DOESN'T THIS WORK???
Whenever I uncomment this, the controller invariably goes through absolutely any collider it touches and completely messes up whatever motion it was supposed to have. I have no idea why this happens, though.
How could I implement a functionality for my player to hug the colliders it touches, seeing this doesn't seem to work at all?
I found a hacky fix for it out of the blue. I have a float deltaGroundI use to check for ground. I changed the command to
currentMotion += transDirection * (collDistance-deltaGround/20f);
but the 20 could have been any reasonably sized number. This makes it much more consistent but introduces some jitter in player motion when I try to ram myself against a wall. Still taking more suggestions.
I had tried Mathf.Epsilon but it didn't fix anything, by the way.
I've been adding some extra movement techniques to a Character Controller in Unity3D. Currently, I'm working on a dash function, but currently, it's behaving very weirdly.
Dashing Backwards, Works as I want it to
Dashing Forwards, Doesn't work as I want it to...
As you can see in the first Image, the silhouttes being essentially where I've dashed, I'm moving in a straight line backwards, as represented by the Green Arrows. However in the second Image, the silhouttes go diagonally to the top left, instead of straight ahead. As represented by the yellow arrows, this time green showing where I want and expect it to go.
private void DashFuncW()
{
if (Time.realtimeSinceStartup - timeOfFirstButton < DoublePressSpan && Input.GetKeyDown(KeyCode.W) && firstButtonPressed)
{
Dash(transform.forward);
firstButtonPressed = false;
timeOfFirstButton = 0f;
}
if (Input.GetKeyDown(KeyCode.W))
{
timeOfFirstButton = Time.realtimeSinceStartup;
firstButtonPressed = true;
}
}
private void DashFuncS()
{
if (Time.realtimeSinceStartup - timeOfFirstButton < DoublePressSpan && Input.GetKeyDown(KeyCode.S) && firstButtonPressed)
{
Dash(-transform.forward);
firstButtonPressed = false;
timeOfFirstButton = 0f;
}
if (Input.GetKeyDown(KeyCode.S))
{
timeOfFirstButton = Time.realtimeSinceStartup;
firstButtonPressed = true;
}
}
These 2 Functions get the double-tap style input and call the actual Dash function while passing it the Direction it needs to go to. As you can see, both use 'transform.forward', just the S key function uses '-transform.forward'. This is what confuses me the most. If it can go one way fine, why can't it go the other way, especially when they're the exact mirror values of one another.
private void Dash(Vector3 Direction)
{
GameObject WarpClone = Instantiate(gameObject);
WarpClone.transform.position = transform.position;
Destroy(WarpClone.GetComponent<CharacterMovement>());
Destroy(WarpClone.GetComponent<CharacterController>());
Destroy(WarpClone.GetComponent<Animator>());
foreach (Transform child in WarpClone.GetComponentsInChildren<Transform>())
{
Destroy(child.GetComponent<Collider>());
SkinnedMeshRenderer[] SMRs = WarpClone.GetComponentsInChildren<SkinnedMeshRenderer>();
MeshRenderer[] MRs = WarpClone.GetComponentsInChildren<MeshRenderer>();
foreach (SkinnedMeshRenderer SMR in SMRs)
{
SMR.material = warpMaterial;
}
foreach (MeshRenderer MR in MRs)
{
MR.material = warpMaterial;
}
}
Destroy(WarpClone, 100f); //100 Seconds for testing
CC.Move(Direction * dashStrength);
}
And this function is the one that creates the silhouettes and actually moves the player. If it matters any, 'dashStrength' is equal to 3, and all 3 of these functions are in a bigger 'Movement' function which moves the character based on Axis input, which runs in the Update() call, as FixedUpdate makes for jittery movement. I cannot for the life of me find out why it won't work properly. I'd appreciate any help :/
Well, if anyone ends up with the same problem somehow, I ended up fixing it by disabling the character controller and changing transform.position like so:
CC.enabled = false;
transform.position = transform.position + (Direction * dashStrength);
CC.enabled = true;
I'm still working on "perfect" ladder movement, and what I was able to come up with is a way to calculate the exact, specific distance the character needs to move while it is on the ladder to reach the point with which it will collide perfectly with the ground above the ladder.
I know this value is exactly specific because I allow my character to land on the ladder landing when the game starts (position.y = 3.6235), and when I print the movement value in the console I get exactly 3.6235.
I'm not sure that I'm implementing this correctly in code, though, as I've noticed that this value is still barely above "0" in the console once my character is all the way up the ladder. Is Mathf.Clamp() not the correct function to limit movement, or maybe I'm using it incorrectly?
public void ClimbUpLadder(ref Vector3 deltaMovement)
{
float rayLength = raycastOrigins.centerRayLength * 2;
RaycastHit2D[] hits = Physics2D.RaycastAll(new Vector2(raycastOrigins.center.x + deltaMovement.x,
(raycastOrigins.center.y - raycastOrigins.centerRayLength + skinWidth) + deltaMovement.y), Vector2.up,
rayLength, climbMask);
Debug.DrawRay(new Vector2(raycastOrigins.center.x + deltaMovement.x,
(raycastOrigins.center.y - raycastOrigins.centerRayLength + skinWidth) + deltaMovement.y), Vector2.up * rayLength, Color.green);
for (int i = 0; i < hits.Length; i++)
{
if (hits[i])
{
if (hits[i].collider.tag == "Ladder")
{
IsClimbingLadder = true;
}
if (i >= 1 && hits[i].collider.tag == "platformOneWay")
{
//This gives us the exact distance needed to finish climbing
GameObject platform = hits[i].collider.gameObject;
Transform platformTransform = platform.GetComponent<Transform>();
float finalMoveDistance = (platformTransform.position.y - characterTransform.position.y) + platformTransform.position.y;
deltaMovement.y = Mathf.Clamp(deltaMovement.y, 0, finalMoveDistance);
print(finalMoveDistance);
}
}
}
}
Unfortunately, after I set deltaMovement.y to this value it says that finalMoveDistance is around .9 or 1.0, so I still move slightly too far up the ladder. Do you think setting the character's transform.position.y directly is the best way to smooth out the movement? My goal is to eliminate any bounce when transitioning from climbing the ladder to walking on the ground again.
I think you're making a mistake here:
float finalMoveDistance = (platformTransform.position.y - characterTransform.position.y) + platformTransform.position.y;
Why do you add the platformTransform twice?
Try this:
float finalMoveDistance = (platformTransform.position.y - characterTransform.position.y);
I'm trying to make it possible to drag an object. This object can only rotate so much. (Similair to a door).
Here is the code abit edited that rotates an object which works.
I have 2 vectors for maxrotation and minrotation.
This code will be called whenever the user is dragging the interactible object. (like update but only when dragged)
if (GestureManager.Instance.IsNavigating &&
HandsManager.Instance.FocusedGameObject == gameObject)
{
//speed and navigiation of rotation
float rotationFactor;
rotationFactor = ManipulationManager.Instance.ManipulationPosition.y * RotationSensitivity;
totransform.Rotate(new Vector3(rotationFactor, 0, 0));
}
It would be great if I could use an if statement here. And I tried quite some things but it's still not working.
As stated the code paste here works. The object should be dragable but only up to a certain points.
totransform is the transform that will be rotated
Any ideas would be great and most appreciated.
Kind regards.
I think you want to look at eulerAngles. Check the values you're getting then set an if-statement before doing the rotation. This is a sample code for you to find the values you want:
if (GestureManager.Instance.IsNavigating &&
HandsManager.Instance.FocusedGameObject == gameObject)
{
//speed and navigiation of rotation
float rotationFactor = ManipulationManager.Instance.ManipulationPosition.y * RotationSensitivity;
Debug.Log(totransform.eulerAngles);
if (totransform.eulerAngles.x < 100) {
totransform.Rotate(new Vector3(rotationFactor, 0, 0));
}
}
So here is the solution that worked for me. First I declare the movement variable (not seen down below, which is 2 in this case). Then I track the distance covered and put a limit on that.
Of course there are some improvements to this code like use movement instead of 2. But because of time constraints I didn't do it.
if (GestureManager.Instance.IsNavigating &&
HandsManager.Instance.FocusedGameObject == gameObject)
{
//here we get the movement direction and set it in movement.
if (GestureManager.Instance.NavigationPosition.y > 0)
{
movement = 2;
}
else if (GestureManager.Instance.NavigationPosition.y < 0)
{
movement = -2;
}
//the first part is false if we reach higher then maxdistance and the movement is going up
//the second part is false if we reach the lower distance and the movement is going down.
if ((!(distance > maxdistance.x) || movement < 0) && ((!(distance < mindistance.x) || movement > 0)))
{
//here we add the movement to the distance so we know if it gets closer or further
distance += movement;
//here we rotate
totransform.Rotate(new Vector3(movement, 0, 0));
}
}