I'm making a pacman clone using an online tutorial. I've just gotten to pacman's movement and when Pacman touches a wall he can no longer move. Here is the movement script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class pacman : MonoBehaviour
{
public int speed;
Vector2 dest;
// Start is called before the first frame update
void Start()
{
dest = transform.position;
speed = 5;
}
// Update is called once per frame
void FixedUpdate()
{
Vector2 p = Vector2.MoveTowards(transform.position, dest, speed);
GetComponent<Rigidbody2D>().MovePosition(p);
// Check for Input if not moving
if ((Vector2)transform.position == dest)
{
if (Input.GetKey("w") )
dest = (Vector2)transform.position + Vector2.up;
if (Input.GetKey("d"))
dest = (Vector2)transform.position + Vector2.right;
if (Input.GetKey("s"))
dest = (Vector2)transform.position - Vector2.up;
if (Input.GetKey("a"))
dest = (Vector2)transform.position - Vector2.right;
}
}
}
You have a couple of things you should watch out for.
1) You're never resetting "dest" anywhere in your code. As you're moving based on the calculation being set to "p". I imagine you're character is hitting a wall, which is as close as it can get to "dest", therefore it cannot move any closer.
I can't predict what you're trying to get your gameplay to be, but I would imagine you'd want to reset "dest" in your OnCollision() to keep the object moving instead of staring at a wall.
As a general piece of advice, I wouldn't set PacMac (the player controlled unit) to be going to a destination. You'd want to calculate an offset based on the input, and then attempt to add that to the transform.position (probably safer through the RigidBody system) and then let the simulation take over from there.
2) You're moving without any reference to gametime. You should really change your offset to be calculated with taking Time.deltaTime into account. This is important for when you're running on a fast computer or a slow computer.
With your current code, you'll move a lot faster on a strong computer and slower on a slow computer.
3) Based on your pacman experience, you might want to change these to else if statements. Even better, but is harder, only accept the last input. This will keep you from moving diagonally (which your current code is susceptible to. If you do the second method, you'll want to keep a stack of all the buttons pressed in case someone tries to hold down multiple buttons at the same time.
Related
Brand new to Unity/C# so this might be a stupid error. I have a player and a push-able box that when pushed towards another object (that usually the player cannot walk through) causes the box to stop moving and the player to get stuck and be unable to move but stuck mid-animation.
They basically just all get stuck in eachother
https://i.stack.imgur.com/rkNtu.png
I followed tutorials for a lot of these things but couldn't manage to find one for pushing the box so I did it by myself, which is what I'm thinking caused these issues.
The player has a 2D circle collider and a 2D rigidbody (with a dynamic body type and discrete collision detection).
It also has all of its code to do with walking that looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
//Movement speed variable
public float moveSpeed;
//Giving information of where the collisions layer is (edited in actual unity)
public LayerMask collisionsLayer;
//Variable to check if the character is moving
private bool isMoving;
private Vector2 input;
//Animation SetUp
private Animator animator;
private void Awake()
{
animator = GetComponent<Animator>();
}
//End of Animation SetUp
private void Update()
{
//If the player is NOT moving
if (!isMoving)
{
//'GetAxisRaw' gives 1 or -1 --> right = 1, left = -1
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
//Making it so that the player cannot move diagonally
if (input.x != 0) input.y = 0;
if (input != Vector2.zero)
{
//Animation section
animator.SetFloat("moveX",input.x);
animator.SetFloat("moveY",input.y);
//End of animation section
var targetPos = transform.position;
//Adds 1 or -1 depending on the key input (GetAxisRaw)
targetPos.x += input.x;
targetPos.y += input.y;
if (IsWalkable(targetPos))
//Calls coroutine 'IEnumerator Move'
StartCoroutine(Move(targetPos));
}
}
//for animation
animator.SetBool("isMoving",isMoving);
//Special type of function w/ 'IEnumerator' as return type. Used to do something over a period of time.
IEnumerator Move(Vector3 targetPos)
{
isMoving = true;
//Checks if the diff between the target position & the players current position is greater than a vry small value
while ((targetPos - transform.position).sqrMagnitude > Mathf.Epsilon)
{
//If there is a diff btwn targetpos & the current position --> we will move the player towards the target position by a very small amount
transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
//Stops execution of the coroutine, resumes it in the next update function
yield return null;
}
//Sets the players current position to the target position
transform.position = targetPos;
isMoving = false;
}
}
//Checking if the next available tile is actually able to be walked on / if the next tile is blocked
private bool IsWalkable(Vector3 targetPos)
{
if(Physics2D.OverlapCircle(targetPos, 0.1f, collisionsLayer) != null)
{
return false;
}
return true;
}
}
The box has a box collider 2D and a 2D rigidbody (dynamic bodytype; discrete collision detection)
And the collisions tilemap (which is set to be used by the composite) has a tilemap collider 2D, rigidbody 2D (static) and a composite collider 2D
If anyone actually knows how to make it so that the entire game doesn't break when I try to move the box past objects, that'd be really helpful because I've been working on this for ages and haven't figured anything out at all
You should try running the debugger. I think you're finding yourself in an interlocked condition, where your character can't get to the target position (because it's blocked), but it hasn't reached the target position (because it's blocked), so it keeps trying to move, which prevents you from giving it a new command.
I think the easiest way to work around this is to cache your target directions - should be in positive x, positive y, or negative x, or negative y. Then, if you detect the user trying to move in the opposite direction you cancel the move and start a new one.
There are lots of ways to work around this, though, and I do see you are trying to check the target before moving to it, but you're checking the raw input, which may overshoot an obstacle. For example, if the tree is at 0.5 meters, but you're looking at a target of 1 meter, then the target is clear. You get blocked by the tree at 0.5 meters, and because you never reach 1 meter you never exit the coroutine.
You've got to run a debugger and step through the code and see what specifically isn't responding.
I'm making a 3D Side-Scroll Platformer Game,
I have trouble with my character when it steps on the moving platform it will not come along on the platform. I want my character to stay on the moving platform so I think converting my Character Controller into Rigibody will help me,
I need help to give me ideas on how I can reuse my Character Controller Script in Rigibody. This is my code, how can I reuse this in Rigibody script?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public CharacterController controller;
private Vector3 direction;
public float speed = 8;
public float jumpForce = 30;
public float gravity = -20;
public Transform groundCheck;
public LayerMask groundLayer;
public bool ableToMakeADoubleJump = true;
public Animator animator;
public Transform model;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (PlayerManager.gameOver)
{
//play death animation
animator.SetTrigger("die");
//disable the script
this.enabled = false;
}
float hInput = Input.GetAxis("Horizontal");
direction.x = hInput * speed;
animator.SetFloat("speed", Mathf.Abs(hInput));
bool isGrounded = Physics.CheckSphere(groundCheck.position, 0.15f, groundLayer);
animator.SetBool("isGrounded", isGrounded);
if (isGrounded)
{
//Jump Codes
ableToMakeADoubleJump = true;
if (Input.GetButtonDown("Jump"))
{
Jump();
}
}
else
{
direction.y += gravity * Time.deltaTime;
if (ableToMakeADoubleJump & Input.GetButtonDown("Jump"))
{
DoubleJump();
}
}
if (hInput != 0)
{
Quaternion newRoattion = Quaternion.LookRotation(new Vector3(hInput, 0, 0));
model.rotation = newRoattion;
}
//Move the player using the character controller
controller.Move(direction * Time.deltaTime);
}
private void DoubleJump()
{
//Double Jump Codes
animator.SetTrigger("doubleJump");
direction.y = jumpForce;
ableToMakeADoubleJump = false;
}
private void Jump()
{
direction.y = jumpForce;
}
}
I would not recommend switching between the two. It would get tricky, and think about it, you are alternating between two very different things. One is movement and one is physics.
However, I would reccomend adding to your current script so that the player would move with the moving platform.
There is a lot of stuff in this answer, so read the whole thing.
Btw, when I talk about velocity, in your case it is direction.
Since it seems like you know how to code pretty well, I won’t write out the script, rather tell you some physics ideas to get you going in the right direction.
The reason people can stand on a moving platform and not fall off is because of friction.
If you are standing on a gameObject with enough friction (you could add a physics material the gameObject you stand on and change friction there. Note that physics materials only work with rigidbodies, but you might want to use it to just read the value)
First of all, you are going to want to raycast down to obtain the object you are standing on. From there you can get the physics material from hit.collider.sharedMaterial (or any other hit. to obtain data about what object you are standing on.
If they friction is too low, just make the character slip off, like it was before (I assume)
If the friction is above a threshold, get the velocity from the object you are standing on. If it was a rigidbody, hit.rigidbody.velocity. If it is controlled by script, use hit.collider.gameObject.GetComponent<scriptname>().velocityvariablename This part is continued later on
This is not necessary but useful: You can think of this as grabbing on a rope. When you are grabbing on a slippery rope, and someone pulls it (Like tug of war), You won’t move because the rope will slide through your hands. If the rope had grip tape on it and someone pulled it, you would come with it because it has more friction. You can think of the platform the same way. Now on to the more complex part: When you grip a rope that is stationary, and someone pulls it, you come with it as its velocity changes. When the rope is already being pulled, so its velocity is not stationary and it is already something. You grab onto it and a similar thing happens. It is like you are becoming a part of that rope. Similar to how if you are running, the arms and legs and head is a part of you. If you lose grip, you are no longer a part of that body, like your arms falling off when running. In other words, you become part of the body when you attach yourself to it.
Bottom line:
Get the velocity of the platform and set platformVel to it, do not add that to velocity, rather do a seperate controller.Move(platformVel).
A small customization:
Vector3.Lerp the platformVel to 0, so it doesn’t change while on the platform, but gradually goes to (0,0,0) when you get off. This way, there is a little momentum maintained from standing on the platform.
Feel free to ask anything in the comments.
I've been trying to make a script that moves the selected object smoothly up and down, which seems simple enough, but I was pretty confused when my script kept putting the object in a weird Y position.
I finally got it to work, after trying half of the day, and then scrapping the code, but one question still remains unanswered in my head. Why does the following script not work?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spin : MonoBehaviour
{
string UpOrDown;
Vector3 originpos;
Vector3 goalUp;
Vector3 goalDown;
void start()
{
originpos = transform.position;
goalUp = originpos + new Vector3(0, 0.05f, 0);
goalDown = originpos - new Vector3(0, 0.05f, 0);
UpOrDown = "Up";
}
string TweenLerp(Vector3 destination, float time, string UpOrDownResult) {
while (transform.position != destination)
{
transform.position = Vector3.Lerp(transform.position, destination, time);
}
return UpOrDownResult;
}
void Update()
{
if (UpOrDown == "Up")
UpOrDown = TweenLerp(goalUp, 0.125f, "Down");
else if (UpOrDown == "Down")
UpOrDown = TweenLerp(goalDown, 0.125f, "Up");
}
}
Sorry if the script seems kind of stupid or with crappy logic, im still kind of new to c#, unity and vectors, so trying to figure out how it works.
so your code works fine, and everything is correct, the reason it's not working is that you called the Start function start(), lowercase, which makes unity not call it. To make this code work all you need is to substitute start() with Start().
Some extra notes to be aware of Lerp and the way you are using it: The way you're doing this animation does work, but not in the way that I think you intended, here's why:
Lerp stands for linear interpolation, and the way it works is that you give a start value (transform.position in your case), an end value (destination), and a value (t) between 0 and 1 that indicates if your value should be more to the start or to the end if t = 0, the result will be the start value, if t = 1, the result will be the end value.
The reason why this works is that everytime you call this lerp function with a value grater than 0, the result value is bit closer to the destination. and if you keep doing this many and many times, eventually it'll get to the destination, but not with linear speed, it will start very fast and end very slow
In your code, you are repeatedly calling this function until the transform.position is equal to the destination using a parameter t called time, the problem is that this "time" value does NOT affect the time of the animation. Since you're doing all of the lerps to get to the destination in one single while loop, they all happen in the same frame, which means that this function is doing the same as transform.position = destination but with a lot of unnecessary Lerp calculations. Btw, if you set the time to 0 in this case you get an infinite loop and you'll need to restart unity.
If you want to have the animations actually happen thru some time and not in one single frame, you should call this Lerp in the Update, so that in each frame it moves a little bit. Your Uptade could look like this:
void Update()
{
if (transform.position == goalUp)
UpOrDown = "Down";
else if (transform.position == goalDown)
UpOrDown = "Up";
transform.position = Vector3.Lerp(transform.position, UpOrDown == "Up" ? goalUp : goalDown, time);
}
One more thing: this time parameter actually makes more sense interpreted as speed, since the lower the value of time, the slower the animation will happen.
Sorry for the size of the answer, hope I have helped!
So, my issue is as follows:
I'm developing a very simple 2D game as I'm quite new to Unity. Basically, there is a knight, and there is a troll. I'm developing my custom AI for the troll and got stumped at the very beginning. I tried to make the troll sprite correctly rotate and walk towards the player (in Update()), but then it turned out that the troll game object doesn't move as much as a millionth of a unit (although it rotates correctly)!
Now, what is special about my problem is the following: it's not about using localPosition instead of position (the troll has no parents – pun intended), it's not about referencing the transform in the script via "[SerializeField]", it's not about working with static objects or messed-up prefab coordinates or any of the other issues I've found in my desperate 10-hour search on the Internet. No, the issue is that I HAVE the troll transform in my code, I modify it successfully, but then it fails to be applied to the in-game troll object. Let that sink in – I am correctly modifying the correct transform (checked multiple times via debugging) but at the beginning of the next frame, all changes get magically reverted and the transform doesn't move the slightest.
I've made almost twenty modifications to my code in order to try and make it work (including separating stuff in different files as was suggested in a particular forum topic) but it just doesn't, and I can't think of anything else. Any help is very, very much appreciated.
public class EnemyController : MonoBehaviour
{
[SerializeField]
private Animator animator;
[SerialiseField]
private float walkingSpeed;
[SerializeField]
[Header("The aggro range of the enemy in Unity units.")]
private float aggroRange;
private float dir;
private Transform playerTransform;
void Update()
{
if (Aggro(playerTransform, aggroRange))
{
//WALKING:
WalkTowards(playerTransform, walkingSpeed);
}
}
private void WalkTowards(Transform targetTransform, float walkingSpeed)
{
animator.SetBool("IsRunning", false);
dir = 0.0f;
//Get the direction (left or right)
if (targetTransform.position.x < transform.position.x) //Left
{
dir = -1.0f;
//Flip the sprite to the left
transform.rotation = Quaternion.Euler(0.0f, 180.0f, 0.0f);
}
else //Right
{
dir = 1.0f;
//Flip the sprite to the right
transform.rotation = Quaternion.Euler(0.0f, 0.0f, 0.0f);
}
transform.position = new Vector3(
transform.position.x + (walkingSpeed * dir * Time.deltaTime),
transform.position.y,
transform.position.z
);
Debug.Log(
string.Format("Troll 1 position:\nx: {0:#.##}", transform.position.x) +
string.Format(" y: {0:#.##}", transform.position.y) +
string.Format(" z: {0:#.##}", transform.position.z)
);
}
}
(NOTE: Aggro() is a custom method that determines when the enemy aggros; not included in the code as it is irrelevant to the issue)
So basically, transform.position gets changed correctly in the script and stays that way until the end of the method, but at the beginning of the next frame – i.e. when WalkTowards() gets called again, the transform.position remains the same as it was at the beginning of the last frame – as if no calculations or changes were ever made.
#Ruzihm,
You were right!!! Turns out the Animator component of the troll was causing the issue – "Apply root motion" was off, I turned it on and the position gets correctly updated now. Thank you ever so much for the help!
Example:
Trying to create simple endless moving platform with 3 cubes of scale 70 on z(Player will not move forward, will just move left/right). The RepositionPlatform script is attached to each platform/cube which is responsible for movement and checks the z position of each platform and if it is <= -100.0f, then position is changed to (0,0,200.0f).
Problem is sometimes there is a little gap between the platforms(cubes) or there is a little overlap which I don't want.
Platforms should be placed one after each other without any gap or overlap!!!
Can anyone help find the issue looking at the script or suggest any other better way ?
The script below is attached to 3 platform game objects!!!
public class RepositionPlatform : MonoBehaviour
{
private GameObject platformGO;
[SerializeField]
private float speed;
// Start is called before the first frame update
void Start()
{
platformGO = this.gameObject;
Debug.Log("In RepositionPlatform Start method - "+ platformGO.name);
}
// Update is called once per frame
void Update()
{
Debug.Log("In RepositionPlatform Update Method- " + platformGO.name);
platformGO.transform.Translate(Vector3.back * Time.deltaTime * speed);
Transform platformTransform = platformGO.transform;
if(platformTransform.position.z <= -100.0f)
{
platformTransform.position = new Vector3(0,0,200.0f);
}
}
}
Probably because speed is a floating point value. You should read up on them if you haven't already.
Long story short, you aren't accounting for how far below -100 the value might have gone, you're just resetting it.
If you translate it instead, you will preserve any extra distance beyond -100 that the transform might have gone.
Try this instead:
If (transform.position.z < -100){
transform.Translate(new Vector3(0,0,200));
}
Edit
Should be Z value, not X