What I need is that an NPC that is moving in a path by script, stops in front of the player if the player is in the way.
It's a top-down 2d game rpg with grid movement.
The NPC is moving 4 cells down, 2 cells left, 4 cells up and 2 cells right all the time. I need that if the player is in the way, it stops in front and continue when the player leaves the grid.
I don't want to use a collider on the player because then I need a rigitbody2d and with that, my movement script is not working.
Here is the TileMovementController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class TileMovementController : MonoBehaviour {
public enum Direction { Left, Right, Up, Down }; // Direction of movement
protected Vector3 newPosition; // For movement
protected virtual Vector3 move(Direction dir, int steps) {
if (dir == Direction.Left && transform.position == newPosition)
newPosition += new Vector3(steps * (-1), 0, 0);
else if (dir == Direction.Right && transform.position == newPosition)
newPosition += new Vector3(steps, 0, 0);
else if (dir == Direction.Up && transform.position == newPosition)
newPosition += new Vector3(0, steps, 0);
else if (dir == Direction.Down && transform.position == newPosition)
newPosition += new Vector3(0, steps * (-1), 0);
return newPosition;
}
}
The PlayerMovementController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovementController : TileMovementController {
public float moveSpeed = 2.0f;
void Start () {
newPosition = transform.position; // Take the initial position
}
private void FixedUpdate() {
// RayCasts for collisions, ignoring layer 2 (Ignore Raycast)
if (Input.GetKey(KeyCode.A) && Physics2D.Raycast(transform.position, Vector2.left, 1, ~(1 << 2)).collider == null) { // Left
newPosition = move(Direction.Left, 1);
}
if (Input.GetKey(KeyCode.D) && Physics2D.Raycast(transform.position, Vector2.right, 1, ~(1 << 2)).collider == null) { // Right
newPosition = move(Direction.Right, 1);
}
if (Input.GetKey(KeyCode.W) && Physics2D.Raycast(transform.position, Vector2.up, 1, ~(1 << 2)).collider == null) { // Up
newPosition = move(Direction.Up, 1);
}
if (Input.GetKey(KeyCode.S) && Physics2D.Raycast(transform.position, Vector2.down, 1, ~(1 << 2)).collider == null) { // Down
newPosition = move(Direction.Down, 1);
}
transform.position = Vector3.MoveTowards(transform.position, newPosition, Time.deltaTime * moveSpeed); // Move there
}
}
And the NpcMovementController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NpcMovementController : TileMovementController {
// Defines the direction and number of steps in that direction has to move
[System.Serializable]
public struct MoveStep {
public Direction direction;
public int steps; // Each step is a grid square. 1 step = 1 unit in the grid position
public MoveStep(Direction direction, int steps) {
this.direction = direction;
this.steps = steps;
}
}
public List<MoveStep> path = new List<MoveStep>();
public float moveSpeed = 2.0f;
private int nextStepIndex = 0;
private bool waiting = false;
void Start () {
newPosition = transform.position; // Take the initial position
}
private void FixedUpdate() {
if (path.Count > 0) {
if (Vector3.Distance(transform.position, newPosition) < 0.05f && !waiting) {
transform.position = newPosition; // Adjust the position to be exactly what it should be
waiting = true;
StartCoroutine("wait", 2f);
} else {
if (Vector3.Distance(transform.position, newPosition) > 0.05f)
transform.position = Vector3.MoveTowards(transform.position, newPosition, Time.deltaTime * moveSpeed);
}
}
}
IEnumerator wait(float seconds) {
yield return new WaitForSecondsRealtime(seconds);
newPosition = move(path[nextStepIndex].direction, path[nextStepIndex].steps);
if (nextStepIndex == path.Count - 1)
nextStepIndex = 0;
else
nextStepIndex++;
waiting = false;
}
}
In the NPCMovementController I tried to do a raycast to player, but without the collider/rigitbody it does not work obviously. If I attach the collider/rigitbody it detects the player but I cannot move.
I did once something similar and I solved by checking if the position of the player is inside the grid squares which represent the path of the npc. For example.
Let's imagine that the npc moves like this:
(0,0) -> (0,1) -> (0,2) -> (0,3) -> (1,3) -> (2,3) -> (2,2) -> (2,1) -> (2,0) -> (1,0) -> (0,0) loop
So for my understanding, if the npc is in (0,0), it will detect the player in (0,1), (0,2) and (0,3). So you can check this three positions in your grid and compare them with the current position of the player, if there is a match, the player is in the path.
Now besides that, it is possible to move your player using the rigidbody (so you can keep with your raycast approach). You can use the velocity of the rigidbody to move the npc in one direction or another.
//moveHorizontal and moveVertical can have +1 or -1 values depending on the inputs of the player:
Vector3 movement = new Vector3 (moveHorizontal, 0.0f, moveVertical);
rigidbody.velocity = movement * speed;
Related
I am new to Unity so please be kind
So I want my character to move in the direction of the last key pressed. If I press W (up) and D (right) at the same time, the player will move in the direction of what came first. If I release that key and continue to hold the key that was pressed 2nd, the character doesn't change direction until you release said key and repress. This is ruining the feel of my game and I would like some help fixing this problem:)
Here is my character controller script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerController : MonoBehaviour
{
public float moveSpeed = 5f;
public Rigidbody2D rb;
public Animator anim;
Vector2 movement;
void Start()
{
anim = GetComponent<Animator>();
}
void Update()
{
movement.x = Input.GetAxisRaw("Horizontal"); //gets axis as vector2
movement.y = Input.GetAxisRaw("Vertical");
anim.SetFloat("Horizontal", movement.x);
anim.SetFloat("Vertical", movement.y); //sets animation parameters
anim.SetFloat("Speed", movement.sqrMagnitude);
if (Input.GetAxisRaw("Horizontal") == 1 || Input.GetAxisRaw("Horizontal") == -1 || Input.GetAxisRaw("Vertical") == 1 || Input.GetAxisRaw("Vertical") == -1)
//If statement to set the correct idle animation (idle right, left, down) based off last direction.
{
anim.SetFloat("LastHorizontal", Input.GetAxisRaw("Horizontal"));
anim.SetFloat("LastVertical", Input.GetAxisRaw("Vertical"));
}
}
void FixedUpdate()
{
if (Mathf.Abs(movement.x) > Mathf.Abs(movement.y)) //if statement disables diagonal movement
{
movement.y = 0;
}
else
{
movement.x = 0;
}
rb.MovePosition(rb.position + movement.normalized * moveSpeed * Time.fixedDeltaTime); //applies movement to player
}
}
Instead of changing movement only on one axis in yout fixed update, I'd try forcing the direction vector for each key. So your code would look like this:
void FixedUpdate()
{
Vector3 movement = Vector3.zero
if (Input.GetKey(KeyCode.W))
{
movement.y = 1;
}
else if (Input.GetKey(KeyCode.S))
{
movement.y = -1;
}
else if (Input.GetKey(KeyCode.A))
{
movement.x = -1;
}
else if (Input.GetKey(KeyCode.D))
{
movement.x = 1;
}
else
{
movement = Vector3.zero
}
rb.MovePosition(rb.position + movement.normalized * moveSpeed * Time.fixedDeltaTime); //applies movement to player
}
The result of this should be your character not being able to move diagonaly
I want my player not to rotate anymore after the game starts. I freeze rotation in the script and in the constraints too , but the player still rotates when it moves forward . What can I do ? ( I have a fps , and a character controller) . I also have a canvas with buttons to control left , right ? Should I put the rigibody or player script inside the Character object ( I made a player game object that contains the character and the camera)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
public float playerSpeed = 1500;
public float directionalSpeed = 20;
private Rigidbody rb;
// Start is called before the first frame update
void Start()
{
GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeRotation;
}
// Update is called once per frame
void Update()
{
#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBPLAYER
float moveHorizontal = Input.GetAxis("Horizontal");
transform.position = Vector3.Lerp(gameObject.transform.position, new Vector3(Mathf.Clamp(gameObject.transform.position.x + moveHorizontal, -2.5f, 2.5f), gameObject.transform.position.y, gameObject.transform.position.z), directionalSpeed * Time.deltaTime);
#endif
GetComponent<Rigidbody>().velocity = Vector3.forward * playerSpeed * Time.deltaTime;
transform.Rotate(Vector3.right * GetComponent<Rigidbody>().velocity.z / 3);
//MOBILE CONTROLS
Vector2 touch = Camera.main.ScreenToWorldPoint(Input.mousePosition + new Vector3(0, 0, 10f));
if (Input.touchCount > 0)
{
transform.position = new Vector3(touch.x, transform.position.y, transform.position.z);
}
}
public void MoveLeft()
{
rb.velocity = new Vector2(-playerSpeed, rb.velocity.y);
}
public void MoveRight ()
{
rb.velocity = new Vector2(playerSpeed, rb.velocity.y);
}
public void StopMoving()
{
rb.velocity = new Vector2(0f, rb.velocity.y);
}
void DetectInput()
{
float x = Input.GetAxisRaw("Horizontal");
if (x > 0 )
{
MoveRight();
}
else if ( x < 0)
{
MoveLeft();
}
else
{
StopMoving();
}
}
}
If you added a rigidbody you can freeze the position or rotation
(no matter if rigidbody is 2d or no)
For first you have to declare the rigidbody
rb = GetComponent<Rigidbody>();
And then you can freeze your rotation RigidbodyConstraints (the selected label in the screenshot)
rigidbody.constraints = RigidbodyConstraints.FreezeRotationX;
//freeze only one rotation
rigidbody.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ | RigidbodyConstraints.FreezePositionY;
//freeze all rotations
to uncheck just type
rigidbody.constraints = RigidbodyConstraints.None;
If your game is 2d just add 2d to all Rigidbodies texts
Rotate using MoveRotation
Using the Rigidbody component constraints will only constrain the game object through the physics engine. You are currently rotating the transform manually.
From documentation:
void FixedUpdate()
{
Quaternion deltaRotation = Quaternion.Euler(m_EulerAngleVelocity * Time.deltaTime);
m_Rigidbody.MoveRotation(m_Rigidbody.rotation * deltaRotation);
}
I made a 3d game with Unity and C#. I don't know how to make a object (player) control to left and right with swipe on mobile screen.
That's my C# code:
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public Rigidbody rb;
public float forwardForce = 300f;
public float sidewaysForce = 200f;
// Update is called once per frame
void FixedUpdate()
{
rb.AddForce(0, 0, forwardForce * Time.deltaTime);
if (Input.GetAxis("Horizontal") > 0.1)
{
rb.AddForce(sidewaysForce * Time.deltaTime, 0, 0);
}
if (Input.GetAxis("Horizontal") < -0.1)
{
rb.AddForce(-sidewaysForce * Time.deltaTime, 0, 0);
}
}
}
You should have a look on the Mobile Touch Tutorials, the Mobile Touch Manual and Input.GetTouch. In short: You have to get the touch, store the initial position and than compare it to current positions.
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public Rigidbody rb;
public float forwardForce = 300f;
public float sidewaysForce = 200f;
private Vector2 initialPosition;
// Update is called once per frame
void Update()
{
// are you sure that you want to become faster and faster?
rb.AddForce(0, 0, forwardForce * Time.deltaTime);
if(Input.touchCount == 1)
{
touch = Input.GetTouch(0);
if(touch.phase == TouchPhase.Began)
{
initialPosition = touch.position;
}
else if(touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
{
// get the moved direction compared to the initial touch position
var direction = touch.position - initialPosition;
// get the signed x direction
// if(direction.x >= 0) 1 else -1
var signedDirection = Mathf.Sign(direction.x);
// are you sure you want to become faster over time?
rb.AddForce(sidewaysForce * signedDirection * Time.deltaTime, 0, 0);
}
}
}
}
Note: This always compares to the initial touch position => you don't go into the other direction until you
Move the touch over the initial position in the other direction
And since you use AddForce wait the same amount of time as you added force in the other direction
Maybe you should rather only compare to the previos touch position instead of comparing to the initial one:
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public Rigidbody rb;
public float forwardForce = 300f;
public float sidewaysForce = 200f;
private Vector2 lastPosition;
// Update is called once per frame
void Update()
{
// are you sure that you want to become faster and faster?
rb.AddForce(0, 0, forwardForce * Time.deltaTime);
if(Input.touchCount == 1)
{
touch = Input.GetTouch(0);
if(touch.phase == TouchPhase.Began)
{
lastPosition = touch.position;
}
if(touch.phase == TouchPhase.Moved)
{
// get the moved direction compared to the initial touch position
var direction = touch.position - lastPosition ;
// get the signed x direction
// if(direction.x >= 0) 1 else -1
var signedDirection = Mathf.Sign(direction.x);
// are you sure you want to become faster over time?
rb.AddForce(sidewaysForce * signedDirection * Time.deltaTime, 0, 0);
lastPosition = touch.position;
}
}
}
}
Now you still have to swipe the same amount of time ... it doesn't take the swiped distance into account. So Maybe instead you should use that:
// get the moved direction compared to the initial touch position
var direction = touch.position - lastPosition ;
// are you sure you want to become faster over time?
rb.AddForce(sidewaysForce * direction.x * Time.deltaTime, 0, 0);
so the more you swipe the more force gets added.
I with this code below I can move my character and limit it to the camera view. But since I'm using input Input.GetAxisRaw ("Horizontal"); my character moves both with a-d keys and arrow keys. But I want arrow keys for another character.
I tried AddForce (which is I don't prefer for character moves) with an if statement to use specific keys. But I wasn't able to use my codes to check boundary. Can you suggest anything? (Btw my character only moves horizontally)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerBlueController : MonoBehaviour {
public float speed;
public Rigidbody2D player1rb;
public float viewpointfirst;
public float viewpointsecond;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
float horizontal = Input.GetAxisRaw ("Horizontal");
if((horizontal > 0) || (horizontal < 0))
{
Vector3 tempVect = new Vector3 (horizontal, 0, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
Vector3 newPos = player1rb.transform.position + tempVect;
checkBoundary (newPos);
}
}
void checkBoundary(Vector3 newPos)
{
Vector3 camViewPoint = Camera.main.WorldToViewportPoint (newPos);
camViewPoint.x = Mathf.Clamp (camViewPoint.x, viewpointfirst, viewpointsecond);
Vector3 finalPos = Camera.main.ViewportToWorldPoint (camViewPoint);
player1rb.MovePosition (finalPos);
}
}
Modified my old answer to use Input.GetKey instead of Input.GetAxisRaw. All you had to do was replace the horizontal variable from Vector3 (horizontal, 0, 0); with -1 when going left, 1 when going right and 0 when not pressed at-all. The rest of the code remains the-same. I added more functions to prevent writing the-same code multiple times because you want player 1 and player 2 to use different keys which requires identical code.
WASD for player 1 and arrow keys for player 2. You can change these around to whatever keys you want in the Update function where movePlayer is called.
public float speed = 50;
public Rigidbody2D rb1;
public Rigidbody2D rb2;
public void Update()
{
//Player one (WASD)
movePlayer(rb1, KeyCode.A, KeyCode.D, KeyCode.W, KeyCode.S);
//Player two (Arrow keys)
movePlayer(rb2, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow);
}
void movePlayer(Rigidbody2D targetRg, KeyCode left, KeyCode right, KeyCode up, KeyCode down)
{
Vector2 hAndV = getInput(targetRg, left, right, up, down);
Vector3 tempVect = new Vector3(hAndV.x, hAndV.y, 0);
tempVect = tempVect.normalized * speed * Time.deltaTime;
Vector3 newPos = targetRg.transform.position + tempVect;
checkBoundary(targetRg, newPos);
}
Vector2 getInput(Rigidbody2D targetRg, KeyCode left, KeyCode right, KeyCode up, KeyCode down)
{
Vector2 input = Vector4.zero;
//Horizontal
if (Input.GetKey(left))
input.x = -1;
else if (Input.GetKey(right))
input.x = 1;
else
{
input.x = 0;
targetRg.velocity = Vector3.zero;
targetRg.angularVelocity = 0f;
}
//Vertical
if (Input.GetKey(up))
input.y = 1;
else if (Input.GetKey(down))
input.y = -1;
else
{
input.y = 0;
targetRg.velocity = Vector3.zero;
targetRg.angularVelocity = 0f;
}
return input;
}
void checkBoundary(Rigidbody2D targetRg, Vector3 newPos)
{
//Convert to camera view point
Vector3 camViewPoint = Camera.main.WorldToViewportPoint(newPos);
//Apply limit
camViewPoint.x = Mathf.Clamp(camViewPoint.x, 0.04f, 0.96f);
camViewPoint.y = Mathf.Clamp(camViewPoint.y, 0.07f, 0.93f);
//Convert to world point then apply result to the target object
Vector3 finalPos = Camera.main.ViewportToWorldPoint(camViewPoint);
targetRg.MovePosition(finalPos);
}
Im trying to make simple 3rd person character controller using unitys character controller component instead of rigidbody. I have problem when making my character sliding down the slope, the motion is jerky, just as if the character was going down stairs.
I move my character using normal to the ground by reversing its y axis, then i apply some additional gravity and put this vector to charactercontroller.move() function.
Here is some of the code where i apply slide and gravity:
void ProcessMotion(){
MoveVector = transform.TransformDirection (MoveVector);
if (MoveVector.magnitude > 1)
MoveVector = Vector3.Normalize (MoveVector);
ApplySlide ();
MoveVector *= MoveSpeed;
MoveVector = new Vector3 (MoveVector.x, VerticalVel, MoveVector.z);
ApplyGravity ();
TP_Controller.CharacterController.Move (MoveVector*Time.deltaTime);
}
public void Jump(){
if (TP_Controller.CharacterController.isGrounded) {
VerticalVel=jumpSpeed;
}
}
void SnapAlignCharacterWithCamera(){
if (MoveVector.x != 0 || MoveVector.z != 0) {
transform.rotation = Quaternion.Euler(transform.eulerAngles.x, Camera.main.transform.eulerAngles.y, transform.eulerAngles.z);
}
}
void ApplyGravity(){
if (MoveVector.y > -TermVel) {
MoveVector = new Vector3 (MoveVector.x, MoveVector.y - Gravity * Time.deltaTime, MoveVector.z);
}
if (TP_Controller.CharacterController.isGrounded && MoveVector.y < - 1) {
MoveVector = new Vector3 (MoveVector.x, -1, MoveVector.z);
}
}
void ApplySlide(){
if (!TP_Controller.CharacterController.isGrounded) {
return;
}
SlideDirection = Vector3.zero;
RaycastHit hitInfo;
if (Physics.Raycast (transform.position , Vector3.down, out hitInfo)) {
if(hitInfo.normal.y < SlideTreshold){
SlideDirection = new Vector3(hitInfo.normal.x, -hitInfo.normal.y, hitInfo.normal.z)*10;
}
}
if (SlideDirection.magnitude < MaxMagnitude) {
MoveVector += SlideDirection;
//Debug.DrawLine (transform.position,transform.position + new Vector3(hitInfo.normal.x*0.5f,-hitInfo.normal.y,hitInfo.normal.z*0.5f), Color.red,1.0f);
}else {
MoveVector = SlideDirection;
}
}
And here are screens with gizmos that show path of the object:
Sliding slowly
Sliding 10xfaster
In advance thanks for your help!
I'm not sure the scale of your objects, but it looks like your raycast is coming from inside of your capsule, and possibly colliding with the bottom. The resulting RaycastHit would report that information, which is a normal that points downward, so when you reverse it, you are heading upwards, creating the sawtooth effect.