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.
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!")
}
I've been trying to fix this one bug in my code for over 7 hours now, upon being teleported, the movement controls cease to function, the mouse works fine, you can look around, but you can't move around.
I wanted to set up some simple code that would teleport the player to a "checkpoint" upon achieving a negative or null y level. I was doing this for a parkour based game, if the player fell off the platform they would have to start over, but after teleporting, it becomes impossible to move as I'm sure I have already said. My code is pretty simple:
public class Main : MonoBehaviour
{
float Fall;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 Checkpoint = new Vector3 (0,3,0);
GameObject Player = GameObject.FindGameObjectWithTag("Player");
Fall = GameObject.FindGameObjectWithTag("Player").transform.position.y;
if (Fall<-4)
{
Player.transform.position = Checkpoint;
}
}
}
You would think that this would just simply change the coordinates of the player, but I think this might be screwing with the FPSController script.
I am using Unity3d, with Standard Assets imported, All of the code is in C#.
Instead of checking the Y value of your character, I would instead place a death collider under the map. Make this a trigger and if the player touches this trigger, then teleport them back. Nothing with your code should screw with the FPS controller so it might be something else. I would also highly recommend not using a FindGameObjectWithTag in the Update() method as it is extremely expensive to use this every frame, especially twice. If you would rather keep the Update() Y component of the position check, please rewrite the code to something like this:
public class Main : MonoBehaviour
{
// assign this object of your player in the inspector - it stores the reference to reuse
// instead of grabbing it every frame
[SerializeField] private Transform playerTransform = null;
// make this a variable as it is not changing - might as well make this const too
private Vector3 checkpoint = new Vector3(0, 3, 0);
// constant value of what to check for in the Y
private const int FALL_Y_MARKER = -4;
// Update is called once per frame
void Update()
{
if (playerTransform.position.y < FALL_Y_MARKER)
{
playerTransform.position = checkpoint;
}
}
}
With your current code, there should be nothing breaking your input/movement, but with that said, we can not see your input/movement code. All the above snippet does is check if the Y component of the player objects position is below a certain value, and if it is, it sets the position to a new vector. Can you post a bit more movement code or somewhere else it can go wrong that you think is the issue?
I am developing a VR game.
Is a fighting game, the player will be able the punch the enemies (on his fist will be a collider and a disabled object with the damage script).
I need a script that will activate another object (the one with damage script) - (placed on his fist) but just on certain speed or force (you know, like in real life- if the enemy will be touched with the hand he should not be damaged, just on high force or speed)
What is the best solution?
thank you!
Since your player's fist is not being controlled by the physics system, it will not be possible to read the velocity of the player's hand like you would a normal Rigidbody. That being said, you can still calculate the speed and handle all of what you wish to do in one script.
Here's an example:
[RequireComponent(typeof(DamageScript))]
public class HandSpeedMonitor : Monobehaviour
{
public float threshold;
DamageScript damageScript;
Vector3 lastPos;
public void Awake()
{
damageScript = this.GetComponent<DamageScript>();
}
public void Start()
{
lastPos = this.transform.position;
}
public void Update()
{
float velocity = (lastPos - this.transform.position).magnitude / Time.deltaTime;
if(!damageScript.enabled && velocity > threshold)
damageScript.enabled = true;
else if(damageScript.enabled)
damageScript.enabled = false;
}
}
However, since .magnitude is an expensive call, you may want to consider storing your "threshold" as a squared speed "sqrThreshold" and use .sqrMagnitude, since it removes the square root component of vector math (saving on processing).
I want my objects to fall when the player got to that scene . My game have a long map and I want them not to fall when I start the game .There is any code for player detection in the view? for the objects to fall?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FallDown : MonoBehaviour
{
public float fallSpeed = 8.0f;
//Variables for starting position and length until reset
private Vector3 _startingPos;
public float FallDistance = 5f;
void Start()
{
transform.Translate(Vector3.down * fallSpeed * Time.deltaTime, Space.World);
// Save starting position
_startingPos = transform.position;
}
void Update()
{
transform.Translate(Vector3.down * fallSpeed * Time.deltaTime, Space.World);
// If the object has fallen longer than
// Starting height + FallDistance from its start position
if (transform.position.y > _startingPos.y + FallDistance)
{
transform.position = _startingPos;
}
}
}
While the answer above is correct, Colliders might not be what you want to implement in this case. Colliders are used to, well, detect collisions, while you want the objects to fall when the player is at a specific distance from them. For this, I'd suggest adding a reference to the player GameObject first:
private GameObject playerRef;
And in the Start function find the player:
playerRef = GameObject.Find("yourPlayerGameObjectNameHere");
Get the GameObjects that you want to fall either by finding them, like above, or by passing a public reference to them through the inspector. Afterwards, you can use Vector3.distance between each GameObject and the player, like so:
if( Vector3.Distance(player.transform.position, fallingObject.transform.position) < yourDistanceHere ){
// Make the object fall
}
Have you tried implementing this behavior using Colliders?
To do it so, all you've got to do is add both a Collider Component and a RigidBody to your player and to the falling objects.
Once you've added them and configured their parameters, you can check the collision by using the method OnColissionEnter. This method will be triggered every time that a collision is detected by the GameObject that is holding the script. In your case, the falling objects should hold it.
private void OnCollisionEnter(Collision other)
{
//MAKE THE OBJECTS FALL
}
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.