The problem i guess is in the waypoints script somewhere.
If i make the player to walk slow let's say speed 1 or 2 he walk to the first waypoint then turn to the next waypoint when he get to the second waypoint instead going back to the original position he make some round walking and then walk back to the first waypoint. What i want it to do is not to walk to the first waypoint but to walk to it's original start position.
Another thing if i change the player speed to 7 i see it walking fast when he get to the second waypoint he turn left and keep walking left none stop. Like depending on the speed of the player he act different.
This is a video clip showing the player on speed 2:
The second player who walk faster is on speed 2 see what happen when he get to the second waypoint:
video clip speed 2
And this is the second player on speed 7
video clip speed 7
And this is the waypoints script in c#
using UnityEngine;
using System.Collections;
public class Waypoints : MonoBehaviour {
public Transform[] waypoint;
public float patrolSpeed;
public bool loop = true;
public int dampingLook = 4;
public float pauseDuration;
private float curTime;
private int currentWaypoint = 0;
public CharacterController character;
// Use this for initialization
void Start () {
}
void LateUpdate(){
if(currentWaypoint < waypoint.Length){
patrol();
}else{
if(loop){
currentWaypoint=0;
}
}
}
void patrol(){
Vector3 nextWayPoint = waypoint[currentWaypoint].position;
// Keep waypoint at character's height
nextWayPoint.y = transform.position.y;
// Get the direction we need to move to
// reach the next waypoint
Vector3 moveDirection = nextWayPoint - transform.position;
if(moveDirection.magnitude < 1.5){
Debug.Log("enemy is close to nextwaypoint");
// This section of code is called only whenever the enemy
// is very close to the new waypoint
// so it is called once after 4-5 seconds.
if (curTime == 0)
// Pause over the Waypoint
curTime = Time.time;
if ((Time.time - curTime) >= pauseDuration){
Debug.Log("increasing waypoint");
currentWaypoint++;
curTime = 0;
}
}
else
{
Debug.Log("reaching in rotation " + moveDirection.magnitude);
// This code gets called every time update is called
// while the enemy if moving from point 1 to point 2.
// so it gets called 100's of times in a few seconds
// Now we need to do two things
// 1) Start rotating in the desired direction
// 2) Start moving in the desired direction
// 1) Let' calculate rotation need to look at waypoint
// by simply comparing the desired waypoint & current transform
var rotation = Quaternion.LookRotation(nextWayPoint - transform.position);
// A slerp function allow us to slowly start rotating
// towards our next waypoint
transform.rotation = Quaternion.Slerp(transform.rotation, rotation,
Time.deltaTime * dampingLook);
// 2) Now also let's start moving towards our waypoint
character.Move(moveDirection.normalized * patrolSpeed * Time.deltaTime);
}
}
}
Maybe this strange behaviour of the player is ok since the physics ? So if it's too fast it's making this behaviour ? But it's strange. I think no matter the speed it suppose to walk between the waypoints no ?
If i make the player to walk slow let's say speed 1 or 2 he walk to the first waypoint then turn to the next waypoint when he get to the second waypoint instead going back to the original position he make some round walking and then walk back to the first waypoint. What i want it to do is not to walk to the first waypoint but to walk to it's original start position.
This is because when the enemy reaches your last checkpoint you set the currentWaypoint to 0. So the next frame the enemy is going to look for that waypoint. and since your starting position isn't a waypoint, he will never walk to that spot again. What you could do is make another waypoint that is at the same place as your enemies startingposition. that way it will walk back to his original position.
Another thing if i change the player speed to 7 i see it walking fast when he get to the second waypoint he turn left and keep walking left none stop. Like depending on the speed of the player he act different.
This is because the movement (position) and rotation is not in sync with your patrolSpeed. you're only increasing the walk speed and not the rotationspeed. so every time your enemy has walked some distance, it needs to start rotating all over again. Try rotating your enemy with:
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * patrolSpeed);
if thats still to slow try increasing the speedmodifier of the rotation.
As for the first problem, you never store the "original" position--you just have waypoints. So when the entity gets to the last waypoint, it sets course for the first waypoint, waypoint[0] since that's what LateUpdate() sets currentWaypoint to.
As for the entity changing behavior based on speed, two things to check-- is it actually reaching inside the if(moveDirection.magnitude < 1.5) block? If not, it's moving too fast in one frame to detect. But it looks like you're always moving in moveDirection which actually is not affected by the rotation you calculate, unless your Move() function works differently than I expect, and so it's likely that the entity keeps heading in one direction because of that.
Related
I am using the Unity Engine with C#.
I have a 1x1 cube which moves forward on a grid of 49, 1x1 cubes (screenshot below) - when I press the start button on the controller.
The movement code for the cube is below.
void MovePlayerCube()
{
transform.Translate(direction * moveSpeed * Time.deltaTime);
}
When this cube passes over a cube with an arrow on it, the cube will change direction to where the arrow is pointing (staying on the same Y axis).
I need to detect the exact point at which the cube is directly over the cube with the arrow on it, and run the 'change direction' code at that point.
I'm currently using Vector3.Distance to check if the X and Z coordinates of the 2 cubes are close enough together (if they are less than 0.03f in distance), I can't check if they are equal due to floating point imprecision.
However this is really ineffective as half the time this code doesn't register for probably the same reason, and if I increase the 0.03f to a point where it never misses it becomes really noticeable that the cube isn't aligned with the grid anymore.
There has to be a proper solution to this and hopefully I've clarified the situation enough?
Any advice is appreciated.
You are moving your cube via
transform.Translate(direction * moveSpeed * Time.deltaTime);
which will never be exact an might overshoot your positions.
=> I would rather implement a coroutine for moving the cube exactly one field at a time, ensuring that after each iteration it fully aligns with the grid and run your checks once in that moment.
It doesn't even have to match exactly then, you only need to check if you are somewhere hitting a cube below you.
So something like e.g.
private Vector3Int direction = Vector3Int.left;
private IEnumerator MoveRoutine()
{
// depends on your needs if this runs just forever or certain steps
// or has some exit condition
while(true)
{
// calculate the next position
// optional round it to int => 1x1 grid ensured on arrival
// again depends a bit on your needs
var nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
// move until reaching the target position
// Vector3 == Vector3 uses a precision of 1e-5
while(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
yield return null;
}
// set target position in one frame just to be sure
transform.position = nextPosition;
// run your check here ONCE and adjust direction
}
}
start this routine only ONCE via
StartCoroutine(MoveRoutine());
or if you have certain exit conditions at least only run one routine at a time.
A Corouine is basically just a temporary Update routine with a little bit different writing => of course you could implement the same in Update as well if you prefer that
private Vector3Int direction = Vector3Int.left;
private Vector3 nextPosition;
private void Start()
{
nextPosition = transform.position;
}
private void Update()
{
if(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
}
else
{
transform.position = nextPosition;
// run your check here ONCE and adjust direction
// then set next position
nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
}
}
Then regarding the check you can have a simple raycast since you only run it in a specific moment:
if(Physics.Raycast(transform.position, Vector3.down, out var hit))
{
direction = Vector3Int.RountToInt(hit.transform.forward);
}
assuming of course your targets have colliders attached, your moved cube position (pivot) is above those colliders (assumed it from your image) and your targets forward actually points int the desired new diretcion
I would do it this way. First I would split the ability of certain objects to be "moving with certain speed" and "moving in a certain direction", this can be done with C# interfaces. Why? Because then your "arrow" cube could affect not only your current moving cube, but anything that implements the interfaces (maybe in the future you'll have some enemy cube, and it will also be affected by the arrow modifier).
IMovingSpeed.cs
public interface IMovementSpeed
{
float MovementSpeed{ get; set; }
}
IMovementDirection3D.cs
public interface IMovementDirection3D
{
Vector3 MovementDirection { get; set; }
}
Then you implement the logic of your cube that moves automatically in a certain direction. Put this component on your player cube.
public class MovingStraight: MonoBehaviour, IMovementSpeed, IMovementDirection3D
{
private float _movementSpeed;
Vector3 MovementSpeed
{
get { return _movementSpeed; }
set { _movementSpeed = value; }
}
private Vector3 _movementDirection;
Vector3 MovementDirection
{
get { return _movementDirection; }
set { _movementDirection= value; }
}
void Update()
{
// use MovementSpeed and MovementDirection to advance the object position
}
}
Now to implement how the arrow cube modifies other objects, I would attach a collision (trigger) component for both moving cube and the arrow cube.
In the component of the arrow cube, you can implement an action when something enters this trigger zone, in our case we check if this object "has direction that we can change", and if so, we change the direction, and make sure that the object will be aligned, by forcing the arrow cube's position and the other object's position to be the same on the grid.
public class DirectionModifier: MonoBehaviour
{
private Vector3 _newDirection;
private void OnTriggerEnter(Collider collider)
{
IMovementDirection3D objectWithDirection = collider as IMovementDirection3D ;
if (objectWithDirection !=null)
{
objectWithDirection.MovementDirection = _newDirection;
// to make sure the object will continue moving exactly
// from where the arrow cube is
collider.transform.position.x = transform.position.x;
collider.transform.position.y = transform.position.y;
}
}
}
If you made your trigger zones too large, however, then the moving cube will "jump" abruptly when it enters the arrow cube's trigger zone. You can fix it by either starting a coroutine as other answers suggested, or you could make the trigger zones pretty small, so that the jump is not noticeable (just make sure not to make them too small, or they may not intersect each other)
You could then similarly create many other modifying blocks, that would change speed or something
I think that it is enough for you to check if the X and Z coordinates are equal, since the movement occurs only along them
Example
if(_player.transfom.position.x == _gameSquare.transfom.position.x && _player.transfom.position.z == _gameSquare.transfom.position.z)
{
Debag.Log("Rotate!")
}
So if I wanted multiple different NPCs to each go to their own set of waypoints, what would be a good way of achieving this?
I have a setup where I have 3 sets of tags, Waypoint1-1, Waypoint2-1, and Waypoint3-1, as well as a script that auto populates all three sets of waypoints.
But how would I specify what waypoint for each of possible three NPC to choose, and then get it to target the next waypoint automatically? I.E. NPC 1 sets Waypoint1-1 as a target, NPC 2 sets Waypoint2-1 as a target, and then I have them move there. Once arrived, they set Waypoint1-2 and Waypoint2-2 as a target respectively. I don't need them to pathfind per se, and I figured transform.lookat should work; I'm more concerned with them moving between waypoints as specified.
From the way you have asked the question, I am assuming that NPC 1 is going to walk to waypoint 1, then move automatically to waypoint 2 and finally to waypoint 3. The NPC will then repeat this, effectively walking around on a triangular path.
You could do this buy creating a script, let's call it NPCMovementScript and attach it to each NPC.
Inside the script, you could have a list of waypoints and then invoke MoveTowards on the NPC object to move towards each waypoint, I have included an example in the following script:
public class NPCMovementScript : MonoBehaviour
{
public float speed = 1.0f;
public List<Transform> listOfWaypoints;
private int currentIndexOfWaypoint;
void Start()
{
currentIndexOfWaypoint = 0;
}
void Update()
{
if (Vector3.Distance(transform.position, listOfWaypoints[currentIndexOfWaypoint].position) < 0.001f)
{
currentIndexOfWaypoint++;
if(currentIndexOfWaypoint == listOfWaypoints.Count - 1)
{
currentIndexOfWaypoint = 0;
}
}
var step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, listOfWaypoints[currentIndexOfWaypoint].position, step);
}
}
You would have to populate the waypoint List<Transform> by dragging and dropping the waypoint objects in to the list, in the right order, inside the editor.
You may also want to incorporate your suggestion of using the LookAt method too.
Hope this helps.
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public float speed;
public float jump;
private Rigidbody2D rb;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
rb.position += new Vector2(Input.GetAxis("Horizontal"), 0) * Time.deltaTime * speed;
if(Mathf.Abs(rb.velocity.y) < 0.001f && Input.GetKeyDown(KeyCode.W))
{
rb.AddForce(new Vector2(0, jump), ForceMode2D.Impulse);
}
}
So I have this code for my player movement. I am wondering how can I reduce my character from sliding that much. I don't want to stop instantly after I release the key.
You could add counter-movement to make the movement to feel more responsive, or you could change the friction by adding a physics material. Counter-movement makes the player stop by adding a force opposite to the wanted direction of the movement. It will stop the player from sliding too much. Another approach is to add a physics material and up the friction a bit. This will make the player stop faster. I hope you find this helpful!
Inside of the Input Manager, Edit->Project Settings->Input Manager, there is a property called gravity.
Gravity: Speed in units per second that the axis falls toward neutral when no input is present.
Decreasing this value will cause the input to fall quicker, resulting in less/no sliding.
You can debug your input value to confirm this. You should notice a ramp up from 0 to 1/-1 when you first hold the horizontal input. Once you let go of the input, you should see the value fall back down to 0.
var inputHorz = Input.GetAxis("Horizontal");
Debug.Log(inputHorz);
Lower the value until it feel correct. This can be changed while you are playing the game, but you will need to paste that value back in after pressing stop.
public int power;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Whyareyoulikethis");
while (Input.GetKey(KeyCode.Space))
{
power = power + 10;
}
// Places the ball at the player's current position.
transform.Translate(-player.transform.forward);
rb = GetComponent<Rigidbody>();
rb.AddForce(-player.transform.forward * power);
}
What this is meant to do is while the space key is held down, power will increase by 10. Unfortunately, this does absolutely nothing. When the ball is spawned, it simply just drops down with no force added whatsoever. I have also tried GetKeyUp and GetKeyDown as opposed to Getkey, but they made no difference to the final result. I have also tried this in an if statement under void Update(), but the same happened. As stupid as it was, I also tried it in its while statement under void Update() and crashed the engine as expected.
That while loop blocks your game until it is done. So as soon as you enter it you will never come out since the Input is not updated inside of your while loop.
Also it makes no sense in Start which is only called once when your GameObject is initialized and the space key won't be pressed there.
Move the check for Input.GetKey it to Update which is called every frame.
Than Cid's comment is correct and this will increase the power quite fast and frame dependent. You probably want to increase rather with a frame-independent 60/second so rather use Time.deltaTime
in this case power should be a float instead
Than it depends where the rest should be executed but I guess e.g. at button up
public float power;
private void Start()
{
player = GameObject.Find("Whyareyoulikethis");
rb = GetComponent<Rigidbody>();
}
private void Update()
{
if(Input.GetKey(KeyCode.Space))
{
power += 10 * Time.deltaTime;
}
if(Input.GetKeyUp(KeyCode.Space))
{
// Places the ball at the player's current position
transform.position = player.transform.position;
// you don't want to translate it but set it to the players position here
// rather than using addforce you seem to simply want to set a certain velocity
// though I don't understand why you shoot backwards...
rb.velocity = -player.transform.forward * power;
}
}
As the title says, I'm trying to create a mechanic that decides what landing the third person controller should play. The player has a "HardLanding" and a "NormalLanding" animation. At first I tried achieving this with a timer, but this doesn't deliver the expected behaviour (When the player jumps forward it also plays the HardLanding animation, which is not what should happen). I'm using a character controller component, so there is no rigidbody or collider (Only the CharacterController and the script).
I have already setup a void that raycasts downwards, and this is currently being done when the player is not grounded. The problem is that the groundCheck is being updated every frame because it's in the update function. This means this ray is sent out every frame and gives different values. I think this ray should either only shoot once, so there is a single value that determines the height of the player, or that the ray that currently shoots out as long as the player is not grounded should be checked on its highest value and than I can do something like. if (rayheight number is higher than x) { play HardLanding} else {play NormalLanding}. The landing animation should start playing when the player is grounded again obviously.
Here is what I have gotton so far, which doens't work yet:
void CheckFallingDistance()
{
RaycastHit hitFall;
Vector3 bottom = controller.transform.position - new Vector3(0, controller.height / 2, 0);
if (Physics.Raycast(bottom, dirDown, out hitFall))
{
rayDistance = hitFall.distance;
}
Debug.DrawRay(bottom, dirDown * hitFall.distance, Color.cyan, 10f);
}
Below you can see my current groundcheck which still contains the timer to decide which landing animation should play. The timer part should be replaced with the raycast check for falling height.
if (GroundCheck())
{
velocityY = 0;
if (airTime > airTimeHandler)
{
//Debug.Log("Hard landing");
anim.SetBool("hardLanding", true);
anim.SetBool("onAir", false);
anim.SetBool("onAirIdle", false);
}
else
{
//Debug.Log("Normal landing");
anim.SetBool("hardLanding", false);
anim.SetBool("onAir", false);
anim.SetBool("onAirIdle", false);
}
airTime = 0f;
}
else
{
CheckFallingDistance();
if (animationSpeedPercent < 0.51f)
{
anim.SetBool("onAirIdle", true);
}
else
{
anim.SetBool("onAir", true);
}
anim.SetBool("hardLanding", false);
airTime += Time.deltaTime;
}
You know your player's absolute position (y component), what remains is finding out the gound height at this point. If your floor is at y=0 than this is trivial, but since you mention raycasting I am assuming its not.
I would assume you only need to measure height at the highest point, you can launch a Coroutine when you leave the ground (its best to avoid Update()), and check for vertical position in each frame within that coroutine. At the beggining phase of the jump the position will increase, but at some point current position will become lower than the position in the last frame. This is the frame when you shoot your raycast, and make a decision as to which animation you play. You can end your coroutine at this point (this is unless your ground is terribly uneven).
You could also measure player velocity before landing and decide based on that
Eventually I decided to ditch the raycast part and went for a check of the Y velocity of the charactercontroller component.