I have a question. I have to make the character move in two positions, just like in the picture below.
All I want to know is how to do this thing. For example, character moves automatically to the right, then when it clicks up, it already moves to the top bar and still moves to the right, and already when it clicks down it to go back to the bar down.
public float speed;
public GameObject[] buttons;
private void Start()
{
foreach (GameObject button in buttons)
button.GetOrAddComponent<MouseEventSystem>().MouseEvent += ChangeBoyPosition;
}
private void ChangeBoyPosition(GameObject target, MouseEventType type)
{
if (type == MouseEventType.CLICK)
{
int buttonIndex = System.Array.IndexOf(buttons, target);
if (buttonIndex == 0)
{
//do down position
}
else
{
//do up position
}
}
}
void Update()
{
//used for automatic movement
//set a speed that responds to the speed of the boy
//automatically move it to the right, then we will change the direction when it reaches the X axis limit
transform.Translate(Vector3.right * speed * Time.deltaTime);
}
Basically I have two buttons, up and down, with these I manipulate the character. If anyone knows how I could make this physics simpler? Thank you!!!
You can try using Physics2D.gravity to modify the y value.
Physics2D.gravity = new Vector2(0, -1); // To go down
Physics2D.gravity = new Vector2(0, 1); // To go up
You'd probably be putting this in an update function since it needs to check inputs, so I would recommend creating two vectors gravityUp and gravityDown and setting Physics.gravity equal to one or the other, depending on which direction you want to go.
Like this:
Vector2 gravityUp = Vector2.up;
Vector2 gravityDown = Vector2.down;
The same would apply in 3D, except you'll have to use Vector3 and Physics.gravity.
Your code would possibly look like this:
Vector2 gravityUp = Vector2.up;
Vector2 gravityDown = Vector2.down;
private void ChangeBoyPosition(GameObject target, MouseEventType type)
{
if (type == MouseEventType.CLICK)
{
int buttonIndex = System.Array.IndexOf(buttons, target);
if (buttonIndex == 0)
{
Physics2D.gravity = gravityDown;
}
else
{
Physics2D.gravity = gravityUp;
}
}
}
Related
I have a scene that contains a square sprite in the middle at 0,0,0 position as player and two more square sprites on top and bottom of it with some space between all.
I want the player start to move when I press the up arrow ker or the down arrow key. When the player hits one of those squares on top or the bottom the player must return back to it's first position.
{
Rigidbody2D playerRb;
float axisY = 0f;
bool canMove = true;
public float playerSpeed = 0f;
public float returnSpeed = 0f;
public float speed = 1f;
void Start()
{
playerRb = GetComponent<Rigidbody2D>();
}
//decides to which axis player gonna move to
private void Update()
{
if (Input.anyKey)
{
axisY = Input.GetAxisRaw("Vertical");
}
}
private void FixedUpdate()
{
PlayerMovement(axisY, playerSpeed);
}
//give velocity to player depending on axis
void PlayerMovement(float axis, float speed)
{
switch (axisY)
{
case 1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
case -1:
if (canMove)
{
canMove = false;
playerRb.velocity = new Vector2(0, axis * speed);
}
break;
default:
//do nothing
break;
}
}
//return back after hit
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
//the code below is my way to get back before i decide to lerp
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero());
}
IEnumerator BackToZero()
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
I managed to achieve this with my code but the problem is when the lerp gets closer to 0 it is taking more time to reach there. I have to wait for one second to reach to the position and my canMove bool value gets back to true.
How can I skip this and when my object position is close to 0 point snap it to there?
The problem come from the fact you use playerRb.position as the start position of your Lerp in your coroutine.
To fix this, I recommend you to buffer the playerRb.position in a member variable at the OnCollisionEnter() method right before starting your coroutine and use this buffered position in your Lerp as start position.
This way you should have a nice linear movement at a sweet linear speed without the "damping"
Hope that helped ;)
Here is the solution. When player object's position is too close to the starting position you will detect it then snap it to the position immediately by
playerRb.MovePosition().
Then enable player to move right after by setting canMove bool to true.
private void OnCollisionEnter2D(Collision2D collision)
{
axisY = 0;
playerRb.velocity = Vector2.zero;
Vector2 playerCurrentPosition = playerRb.position;
//playerRb.MovePosition(Vector2.MoveTowards(playerRb.position,Vector2.zero,returnSpeed));
StopAllCoroutines();
StartCoroutine(BackToZero(playerCurrentPosition));
}
IEnumerator BackToZero(Vector2 playerStartPos)
{
float t = 0;
while (t < 1)
{
t += Time.deltaTime;
if (Mathf.Round(Mathf.Abs(playerRb.position.y*2))==0)
{
playerRb.MovePosition(Vector2.zero);
canMove = true;
StopAllCoroutines();
}
else
playerRb.MovePosition(Vector2.Lerp(playerRb.position, Vector2.zero, returnSpeed));
yield return null;
}
canMove = true;
}
}
Try Vector3.MoveTowards(); in a while loop until you have reached your target destination
I've made an Isometric character controller so it can move and head on an isometric based perspective. The problem is that when I try to move using 2 keys (W + D, A + S...) and release those keys, the player tends to face the very last key that was released, so it is difficult to head a diagonal direction. This makes the character flip its rotation in the very last moment the keys are released.
I've been thinking of using a kind of sleep or coroutine checking if two keys were pressed and released in a short period of time just not rotate it.
There exist any not too rustic solution for this?
Here is my code (I just copied it from here: https://www.studica.com/blog/isometric-camera-unity)
private void SetIsometricDirections() {
vertical = Camera.main.transform.forward;
vertical.y = 0;
vertical = Vector3.Normalize(vertical);
horizontal = Quaternion.Euler(new Vector3(0, 90, 0)) * vertical;
}
private void Move() {
Vector3 horizontalMovement = horizontal * moveSpeed * Time.deltaTime * Input.GetAxis("HorizontalKey");
Vector3 verticalMovement = vertical * moveSpeed * Time.deltaTime * Input.GetAxis("VerticalKey");
Vector3 heading = Vector3.Normalize(horizontalMovement + verticalMovement);
Heading(heading);
transform.position += (horizontalMovement + verticalMovement);
}
private void Heading(Vector3 heading) {
transform.forward = heading;
}
The heading trick it's in the "Heading" method, obviously.
I finally found a solution.
I'll post it just in case any one else needs it in the future.
I calculate the current rotation based on the movement.
private void SetRotation() {
currentRotation = Quaternion.LookRotation(movement);
}
Then, I created a second Quaternion to store the last rotation if is not the same than the current. I also use a counter to store the time a rotation has been faced before stopping the movement or changing the direction.
private void CheckSameRotation() {
if (lastRotation != currentRotation || movement == Vector3.zero) {
sameRotationTime = 0;
lastRotation = currentRotation;
}
else {
sameRotationTime += Time.deltaTime;
}
}
Then, I used a bool to check if the time is high enough to make the rotation happen.
private void TimeAtSameRotation() {
canRotate = (sameRotationTime < 0.015f) ? false : true;
}
And then, I finally rotate the object if the movement is not zero and the condition "canRotate" is true.
private void Rotate() {
if (movement != Vector3.zero && canRotate) {
transform.rotation = currentRotation;
}
}
I am confused as to why my game cursor follow script does not seem to perform differently based on the character's position. I want the player to press "E" if the player hovers the mouse over the character's hand and clicks and drags the arm will move up and down following the mouse position.
The script was working until I moved my character along the X axis and found that the location the mouse had to click and drag stayed at the arm's original position even as the
character, his arm, and camera moved away. Would anyone have any idea why? I've pasted in the script for finding the Mouse's position and the script for having my game object copy the mouse's position.
I uploaded some videos of the script working on YouTube as well
https://www.youtube.com/watch?v=grhnqckOfc0
https://www.youtube.com/watch?v=ZlLLgnU8yMc
Thanks for your time!
public static class GameCursor
{
public static LayerMask mask;
public static Vector2 WorldPosition
{
get
{
float distance;
int mask = 1<<9; //setting layer mask to layer 9
mask = ~mask; //setting the layer mask to ignore layer(~)
//adding in the layermask to raycast will ignore 9th layer
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction * 10000f, Color.black);
if (_gamePlane.Raycast(ray, out distance))
return ray.GetPoint(distance);
return Vector2.zero;
}
}
private static Plane _gamePlane = new Plane(Vector3.forward,0);
}
public GameObject ArmR;
public static Vector2 OffsetPoint;
public Vector2 OffsetVec2;
void Start ()
{
}
private void Update()
{
if (Input.GetKey (KeyCode.E)) {
Follow ();
}
if (Input.GetKeyUp (KeyCode.E)) {
}
//print (transform.eulerAngles.z);
//Offset.transform.position = OffsetVec2;
OffsetPoint = GameCursor.WorldPosition + OffsetVec2;
Vector3 clampedRotation = transform.eulerAngles;
// Clamp the z value
if (clampedRotation.z > 90.0f) clampedRotation.z = 180.0f - clampedRotation.z;{
clampedRotation.z = Mathf.Clamp (clampedRotation.z, 0, 90);
clampedRotation.z = Mathf.Clamp (clampedRotation.z, -90, 180);
}
// assign the clamped rotation
ArmR.transform.rotation = Quaternion.Euler(clampedRotation);
//if(ArmR.transform.eulerAngles.z >= 350 || transform.eulerAngles.z <= 270){
//Debug.Log ("no");
//}
if(ArmR.transform.eulerAngles.z == 0){
//Debug.Log ("AT0");
}
if(ArmR.transform.eulerAngles.z == 90){
//Debug.Log ("AT90");
}
}
void Follow(){
ArmR.transform.up = -GameCursor.WorldPosition + OffsetVec2;
}
private void OnDrawGizmos()
{
Gizmos.DrawIcon(GameCursor.WorldPosition, "wallll", true);
}
}
I am working on a simple patrol script, and everything works but the characters are randomly rolling through the floor when they turn around. Here is a short clip of them...
Here is my script...
public class Patrol : MonoBehaviour
{
public float speed = 5;
public float directionChangeInterval = 1;
public float maxHeadingChange = 30;
public bool useRootMotion;
CharacterController controller;
float heading;
Vector3 targetRotation;
Vector3 forward
{
get { return transform.TransformDirection(Vector3.forward); }
}
void Awake()
{
controller = GetComponent<CharacterController>();
// Set random initial rotation
heading = Random.Range(0, 360);
transform.eulerAngles = new Vector3(0, heading, 0);
StartCoroutine(NewHeadingRoutine());
}
void Update()
{
transform.eulerAngles = Vector3.Slerp(transform.eulerAngles, targetRotation, Time.deltaTime * directionChangeInterval);
if (useRootMotion)
{
return;
}
else
{
controller.SimpleMove(forward * speed);
}
}
void OnControllerColliderHit(ControllerColliderHit hit)
{
if (hit.gameObject.tag == "Player")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
if (hit.gameObject.tag == "Boundary")
{
// Bounce off the obstacle and change direction
var newDirection = Vector3.Reflect(forward, hit.normal);
transform.rotation = Quaternion.FromToRotation(Vector3.forward, newDirection);
heading = transform.eulerAngles.y;
NewHeading();
}
}
/// Finds a new direction to move towards.
void NewHeading()
{
var floor = transform.eulerAngles.y - maxHeadingChange;
var ceil = transform.eulerAngles.y + maxHeadingChange;
heading = Random.Range(floor, ceil);
targetRotation = new Vector3(0, heading, 0);
}
/// Repeatedly calculates a new direction to move towards.
IEnumerator NewHeadingRoutine()
{
while (true)
{
NewHeading();
yield return new WaitForSeconds(directionChangeInterval);
}
}
}
I have tried adding a rigidbody to the characters and constraining rotation, but that doesnt work. Oddly enough, the character control isnt rotating at all. In the scene view I can see the character collider staying as it should, but the character flips through the mesh on its own.
It looks like it's because they are walking into a corner and being bounced between the two walls constantly which causes them to behave strangely. I would add a method of checking for a series of very quick collisions to detect that they are in a corner or stuck and then adapt accordingly, perhaps with a method to rotate 180 degrees and keep walking or the like.
You can do it like this:
float fTime = 0.1f
float fTimer = 0;
int iCollisionCounter;
if(collision){
if(fTimer > 0) iCollisionCounter++;
if(iCollisionCounter >= 5) //Character is stuck
fTimer = fTime;
}
Void Update(){
fTimer -= time.deltaTime;
}
That means that if there are multiple collisions within 0.1 seconds of each other you can handle it.
Hope this helps!
I have been trying to make this work for a while and I am failing.
I have a Rigidbody2D in a top down 2D level and I am trying to simply move it along the coordinates (the levels are gridlike) so one step / button press equals exactly one square walked. It should only be possible to walk in one of the four directions, and no matter when the user stops the walking motion, it should end on a square. A good game equivalent of what I want to achieve is the lufia / breath of fire / any similar RPG series. I've tried using coroutines to get the update function to wait one second after every step, but that does not seem to work. The closest I've come to is the code down below. Thanks guys!
public class PlayerMovement2D : MonoBehaviour
{
Rigidbody2D rbody;
Animator anim;
float speed = 1.25f;
Vector2 pos;
void Start()
{
rbody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
pos = rbody.position;
}
void FixedUpdate()
{
Vector2 movement_vector = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
if (movement_vector.x != 0 || movement_vector.y != 0)
{
if (movement_vector.x > 0)
{
pos += Vector2.right;
pos.y = 0;
}
else if (movement_vector.x < 0)
{
pos += Vector2.left;
pos.y = 0;
}
else if (movement_vector.y > 0)
{
pos += Vector2.up;
pos.x = 0;
}
else if (movement_vector.y < 0)
{
pos += Vector2.down;
pos.x = 0;
}
anim.SetBool("IsWalking", true);
anim.SetFloat("Input_x", movement_vector.x);
anim.SetFloat("Input_y", movement_vector.y);
}
else
{
anim.SetBool("IsWalking", false);
}
rbody.position = Vector2.MoveTowards(rbody.position, pos, speed * Time.deltaTime);
//transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
pos = rbody.position;
}
}
I think what you try to program is similar to the RogueLike game that is part of the tutorial collection in Unity website. Check first the intro video to confirm that is what you are planning to achieve
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/project-introduction?playlist=17150
If so, this is how they handle it:
using UnityEngine;
using System.Collections;
//The abstract keyword enables you to create classes and class members that are incomplete and must be implemented in a derived class.
public abstract class MovingObject : MonoBehaviour
{
public float moveTime = 0.1f; //Time it will take object to move, in seconds.
public LayerMask blockingLayer; //Layer on which collision will be checked.
private BoxCollider2D boxCollider; //The BoxCollider2D component attached to this object.
private Rigidbody2D rb2D; //The Rigidbody2D component attached to this object.
private float inverseMoveTime; //Used to make movement more efficient.
//Protected, virtual functions can be overridden by inheriting classes.
protected virtual void Start ()
{
//Get a component reference to this object's BoxCollider2D
boxCollider = GetComponent <BoxCollider2D> ();
//Get a component reference to this object's Rigidbody2D
rb2D = GetComponent <Rigidbody2D> ();
//By storing the reciprocal of the move time we can use it by multiplying instead of dividing, this is more efficient.
inverseMoveTime = 1f / moveTime;
}
//Move returns true if it is able to move and false if not.
//Move takes parameters for x direction, y direction and a RaycastHit2D to check collision.
protected bool Move (int xDir, int yDir, out RaycastHit2D hit)
{
//Store start position to move from, based on objects current transform position.
Vector2 start = transform.position;
// Calculate end position based on the direction parameters passed in when calling Move.
Vector2 end = start + new Vector2 (xDir, yDir);
//Disable the boxCollider so that linecast doesn't hit this object's own collider.
boxCollider.enabled = false;
//Cast a line from start point to end point checking collision on blockingLayer.
hit = Physics2D.Linecast (start, end, blockingLayer);
//Re-enable boxCollider after linecast
boxCollider.enabled = true;
//Check if anything was hit
if(hit.transform == null)
{
//If nothing was hit, start SmoothMovement co-routine passing in the Vector2 end as destination
StartCoroutine (SmoothMovement (end));
//Return true to say that Move was successful
return true;
}
//If something was hit, return false, Move was unsuccesful.
return false;
}
//Co-routine for moving units from one space to next, takes a parameter end to specify where to move to.
protected IEnumerator SmoothMovement (Vector3 end)
{
//Calculate the remaining distance to move based on the square magnitude of the difference between current position and end parameter.
//Square magnitude is used instead of magnitude because it's computationally cheaper.
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//While that distance is greater than a very small amount (Epsilon, almost zero):
while(sqrRemainingDistance > float.Epsilon)
{
//Find a new position proportionally closer to the end, based on the moveTime
Vector3 newPostion = Vector3.MoveTowards(rb2D.position, end, inverseMoveTime * Time.deltaTime);
//Call MovePosition on attached Rigidbody2D and move it to the calculated position.
rb2D.MovePosition (newPostion);
//Recalculate the remaining distance after moving.
sqrRemainingDistance = (transform.position - end).sqrMagnitude;
//Return and loop until sqrRemainingDistance is close enough to zero to end the function
yield return null;
}
}
//The virtual keyword means AttemptMove can be overridden by inheriting classes using the override keyword.
//AttemptMove takes a generic parameter T to specify the type of component we expect our unit to interact with if blocked (Player for Enemies, Wall for Player).
protected virtual void AttemptMove <T> (int xDir, int yDir)
where T : Component
{
//Hit will store whatever our linecast hits when Move is called.
RaycastHit2D hit;
//Set canMove to true if Move was successful, false if failed.
bool canMove = Move (xDir, yDir, out hit);
//Check if nothing was hit by linecast
if(hit.transform == null)
//If nothing was hit, return and don't execute further code.
return;
//Get a component reference to the component of type T attached to the object that was hit
T hitComponent = hit.transform.GetComponent <T> ();
//If canMove is false and hitComponent is not equal to null, meaning MovingObject is blocked and has hit something it can interact with.
if(!canMove && hitComponent != null)
//Call the OnCantMove function and pass it hitComponent as a parameter.
OnCantMove (hitComponent);
}
//The abstract modifier indicates that the thing being modified has a missing or incomplete implementation.
//OnCantMove will be overriden by functions in the inheriting classes.
protected abstract void OnCantMove <T> (T component)
where T : Component;
}
Link to this part of the tutorial:
https://unity3d.com/es/learn/tutorials/projects/2d-roguelike-tutorial/moving-object-script?playlist=17150
You can use Vector2.Lerp method in combination of Unity's Coroutine system.
public class Movement
: MonoBehaviour
{
IEnumerator m_MoveCoroutine;
float m_SpeedFactor;
void Update()
{
// if character is already moving, just return
if ( m_MoveCoroutine != null )
return;
// placeholder for the direction
Vector2 direction; // between { -1, -1 } and { 1, 1 }
// calculate your direction based on the input
// and set that direction to the direction variable
// eg. direction = new Vector2(Input.GetAxisRaw("Horizontal") > 0 ? 1 : -1,...)
// then check if direction is not { 0, 0 }
if( direction != Vector2.zero )
{
// start moving your character
m_MoveCoroutine = Move(direction);
StartCoroutine(m_MoveCoroutine);
}
}
IEnumerator Move(Vector2 direction)
{
// Lerp(Vector2 a, Vector2 b, float t);
Vector2 orgPos = transform.Position; // original position
Vector2 newPos = orgPos + direction; // new position after move is done
float t = 0; // placeholder to check if we're on the right spot
while( t < 1.0f ) // loop while player is not in the right spot
{
// calculate and set new position based on the deltaTime's value
transform.position = Vector2.Lerp(orgPos, newPos, (t += Time.deltaTime * m_SpeedFactor));
// wait for new frame
yield return new WaitForEndFrame();
}
// stop coroutine
StopCoroutine(m_MoveCoroutine);
// get rid of the reference to enable further movements
m_MoveCoroutine = null;
}
}
This method assumes that you can move in the specified direction. But you still should check if your new position is "walkable" before starting the Move Coroutine.
I think you want to do the movement in a coroutine that, while it is active, disabled further movement until it's done.
bool isIdle = true;
void Update() {
if(isIdle) {
// Your movement code that gives pos
StartCoroutine(Move(pos));
}
}
IEnumerator Move(Vector2 destination) {
isIdle = false;
do {
transform.position = Vector2.MoveTowards(transform.position, destination, speed * Time.deltaTime);
yield return new WaitForEndOfFrame();
} while (transform.position != destination);
isIdle = true;
}
Let me know if you don't understand, need further clarification or if this approach doesn't work!