So I'm new to Csharp, and I am working on this script for a game. It's a 2D game. Ive already assigned jump movement to the game, however, I'm stuck on fixing the movement along the x axis.
I'd really appreciate your help.
using UnityEngine;
// this code uses physics to make player jump
public class Movement2d : MonoBehaviour
{
private bool jumpKeyWasPressed;
private bool movementRight;
private CharacterController characterController;
private Rigidbody2D rigidbodyComponent;
private Vector3 moveSpeed;
// Start is called before the first frame update
void Start()
{
rigidbodyComponent = GetComponent<Rigidbody2D>();
characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
//Check if space key is pressed down
if (Input.GetKeyDown(KeyCode.Space))
{
jumpKeyWasPressed = true;
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
movementRight = true;
}
}
private void FixedUpdate()
{
if (jumpKeyWasPressed)
{
//jump action assigned to space key
rigidbodyComponent.AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
jumpKeyWasPressed = false;
}
if (movementRight)
{
//moving right assigned to right arrow
Rigidbody.MovePosition();
}
}
}
If you're using RigidBody.MovePosition, you'll want to move the player according to your speed and the time elapsed since the last frame (to account for frame stutters).
Assuming your horizontal speed is stored in the x-coordinate of your speed vector, you'll want to write:
rigidBodyComponent.MovePosition(new Vector2(moveSpeed.x * Time.deltaTime, 0));
To perform left movement, use the same code, but use -moveSpeed.x instead. In both cases, don't forget to set the movement bool to false afterwards.
Keep in mind, however, that using RigidBody.MovePosition could cause your character to go through walls at high speeds. Consider using RigidBody.AddForce() as you did for jumping if you want to avoid that.
Sources:
https://docs.unity3d.com/ScriptReference/Rigidbody2D.MovePosition.html
https://docs.unity3d.com/ScriptReference/Rigidbody2D.AddForce.html
Related
I am new to Unity so forgive me if I just did a stupid mistake. I am currently watching a tutorial from Brackeys, but I wanted to challenge myself by figuring out the movement myself. I got the moving forward correct, but I couldn't do the sideways movement. Since I've been spending a long time on the sideways movement, I just decided to watch the tutorial, but even when I used their code, it still didn't work. Can someone tell me why this isn't working? (FYI, in the code below, I did set all the public variables to some type of value).
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sides : MonoBehaviour
{
public KeyCode left;
public KeyCode right;
public float sidewaysForce;
Rigidbody rb;
Vector3 v3;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void FixedUpdate()
{
if (Input.GetKey(left))
{
Debug.Log("Left");
rb.velocity = new Vector3(-sidewaysForce, 0, 0);
}
if (Input.GetKey(right))
{
Debug.Log("Right");
rb.velocity = new Vector3(sidewaysForce, 0, 0);
}
}
}
try left and right between quotation marks:
if (Input.GetKey("left"))
{
Debug.Log("Left");
rb.velocity = new Vector3(-sidewaysForce, 0, 0);
}
if (Input.GetKey("right"))
{
Debug.Log("Right");
rb.velocity = new Vector3(sidewaysForce, 0, 0);
}
You can check how to get the different keys in:
InputManager is in: Edit -> ProjectSettings -> Input
Other option is to use KeyCodes, which I prefer instead of the string argument for the intellisense benefits:
Update()
{
if ( Input.GetKey(KeyCode.UpArrow) )
//move up
if ( Input.GetKey(KeyCode.DownArrow) )
//move down
if ( Input.GetKey(KeyCode.RightArrow) )
//move right
if ( Input.GetKey(KeyCode.LeftArrow) )
//move left
}
If you're not getting the logs you added in the console, then you may have forgotten either:
To set the values of variables left and right in the inspector.
To add the Sides component to your GameObject.
I will assume your KeyCodes and the float are configured in the Inspector.
Either way you shouldn't do it like that!
You are completely overwriting the forward/backward movement and also the up/down movement with a hard 0 => you won't have any gravity and might break the forward movement - depending on which script is executed last.
You didn't share the forwards movement code so I can only guess but I suspect that there you do the very same thing like e.g.
rb.velocity = new Vector3(0, 0, someValue);
and therefore erase any sidewards movement from this script ;)
I would
a) make sure to have all movement input related stuff in a single script. Everything else just makes it unnecessarily hard to maintain and debug
b) make sure that your inputs don't eliminate each other
So something like e.g.
public class Movement : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[Header("Config")]
[SerializeField] private KeyCode _forward = KeyCode.W;
[SerializeField] private KeyCode _backward = keyCode.S;
[SerializeField] private KeyCode _left = KeyCode.A;
[SerializeField] private KeyCode _right = KeyCode.D;
public float speed;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
private void FixedUpate()
{
// by default input is 0,0,0
var input = Vector3.zero;
// user input for forward
if(Input.GetKey(_forward)) input.z = 1;
else if(Input.GetKey(_backward)) input.z = -1;
// user input for sidewards
if(Input.GetKey(_left)) input.x = -1;
else if(Input.GetKey(_right)) input.x = 1;
// make sure both combined are not bigger then 1 (avoid faster diagonal movement)
input = Vector3.ClampMagnitude(input, 1);
// apply speed multiplier
var velocity = input * speed;
// preserve the Y velocity
velocity.y = _rigidbody.velocity.y;
// finally assign back to rigidbody
_rigidbody.velocity = velocity;
}
}
In general though way more flexible and without having to configure and check the KeyCodes manually you could rather use Input.GetAxis and do e.g.
public class Movement : MonoBehaviour
{
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[Header("Config")]
public float speed;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
private void FixedUpate()
{
var input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// make sure both combined are not bigger then 1 (avoid faster diagonal movement)
input = Vector3.ClampMagnitude(input, 1);
// apply speed multiplier
var velocity = input * speed;
// preserve the Y velocity for the gravity
velocity.y = _rigidbody.velocity.y;
// finally assign back to the rigidbody
_rigidbody.velocity = velocity;
}
}
You can configure the mappings in Edit → Project Settings → Input Manager but by default the
Horizontal already refers to both A/D as well as LeftArrow/RightArrow
Vertical already refers to both W/S as well as UpArrow/DownArrow
and additionally GetAxis slightly interpolates the input so it doesn't immediately jump from 0 to 1 and back but rather increases and decreases smoothly over some frames.
If the latter bothers you you can also use GetAxisRaw which does not do the smoothing.
I just realized that I did in fact made a stupid mistake. In my other script that makes the player move forward, it sets the velocity to (0, 0, 100) in the FixedUpdate() which meant that the x axis doesn't change. Sorry for wasting your time.
So I'm new to unity only 2 days, and I'm having this problem of my player jumping in air. I can keep clicking the spacebar key and my player will keep jumping and not stop, I'm trying make it so the player can only jump while on the ground not the air. I been trying a lot of tutorials and most of them just made my game freeze up and not work anymore. I also been trying to find out how to use ground check but I'm still not sure on how to do that.
This is my code that I have right know.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PM : MonoBehaviour
{
public float moveSpeed;
public float jumpHeight;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update() {
float moveDir = Input.GetAxis("Horizontal") * moveSpeed;
rb.velocity = new Vector2(moveDir, rb.velocity.y);
// Your jump code:
if (Input.GetKeyDown(KeyCode.Space))
{
rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
}
}
}
Yes, you do need some sort of ground check. There are a lot of good tutorials online, for example, https://www.youtube.com/watch?v=c3iEl5AwUF8&t=17s ("3 ways to do a Ground Check in Unity").
To implement the simple "raycast down check", you need to assign the ground/platforms a layermask that is not the same as the player's layer. In this example, I have added a new layer named "Ground".
You can then use this simple code to do the test.
BoxCollider2D boxColliderPlayer;
int layerMaskGround;
float heightTestPlayer;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// Get the player's collider so we can calculate the height of the character.
boxColliderPlayer = GetComponent<BoxCollider2D>();
// We do the height test from the center of the player, so we should only check
// halft the height of the player + some extra to ignore rounding off errors.
heightTestPlayer = boxColliderPlayer.bounds.extents.y + 0.05f;
// We are only interested to get colliders on the ground layer. If we would
// like to jump ontop of enemies we should add their layer too (which then of
// course can't be on the same layer as the player).
layerMaskGround = LayerMask.GetMask("Ground");
}
void Update()
{
float moveDir = Input.GetAxis("Horizontal") * moveSpeed;
rb.velocity = new Vector2(moveDir, rb.velocity.y);
// Your jump code:
if (Input.GetKeyDown(KeyCode.Space) && IsGrounded() )
{
rb.velocity = new Vector2(rb.velocity.x, jumpHeight);
}
}
/// <summary>
/// Simple check to see if our character is no the ground.
/// </summary>
/// <returns>Returns <c>true</c> if the character is grounded.</returns>
private bool IsGrounded()
{
// Note that we only check for colliders on the Ground layer (we don't want to hit ourself).
RaycastHit2D hit = Physics2D.Raycast(boxColliderPlayer.bounds.center, Vector2.down, heightTestPlayer, layerMaskGround);
bool isGrounded = hit.collider != null;
// It is soo easy to make misstakes so do a lot of Debug.DrawRay calls when working with colliders...
Debug.DrawRay(boxColliderPlayer.bounds.center, Vector2.down * heightTestPlayer, isGrounded ? Color.green : Color.red, 0.5f);
return isGrounded;
}
As you can see I expect the player character to have a BoxCollider2D - I guess you already have that since the character would otherwise fall through the ground). Be careful so you do not mix normal 3D colliders when you are doing 2D stuff.
enter image description hereI am able to set Y axis of my player with a simple transform.position call, in a single step, all within onTriggerEnter method, but the motion has a single step and is therefore jerky. Now I am trying to make the motion smooth by putting the transform.position function in an Update method within the same class. However, it seems that the position values determined/updated by onTriggerEnter method are not accessible in the Update function. If I print the x and z values to console, they contain expected values from onTriggerEnter function, but appear to be 0 when I print to console from the update function.
Any ideas of what I am doing wrong?
I would never call myself a programmer, so assume the worst :-)
Thanks in advance for any help!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Step1SetElevation : MonoBehaviour
{
private float moveSpeed = 3f;
private float currX = 0.0f;
private float currZ = 0.0f;
private Vector3 currentPos;
private GameObject player;
private Collider other;
void OnTriggerEnter(Collider other)
{
player = GameObject.FindWithTag("Player");
currentPos = GameObject.Find("PlayerController").transform.position;
currX = currentPos.x;
currZ = currentPos.z;
}
void Update()
{
player = GameObject.FindWithTag("Player");
player.transform.position = new Vector3(currX, 3.4f, currZ) * Time.deltaTime * moveSpeed;
}
}
I believe your problem may be that you are using OnTriggerEnter() to set the position you want your player to move to, this will be called on the frame your player enters your trigger but then won't be called again until the player left and re-entered...
If you instead use OnTriggerStay() - something like
void Start(){ // this lookup can be expensive so lets only do it once
player = GameObject.FindWithTag("Player");
playerController = GameObject.Find("PlayerController");
}
void OnTriggerStay(Collider other){ // this is called once per frame that a collider remains in a trigger
if(other.gameObject.tag == "Player"){ // just in case anything else ever enters the collider
currentPos = playerController.transform.position;
currX = currentPos.x;
currZ = currentPos.z;
}
}
void FixedUpdate(){ // it's generally advised to move objects in fixed update
player.transform.position = new Vector3(currX, 3.4f, currZ) * Time.fixedDeltaTime * moveSpeed;
}
I have this C# script attached to my main camera game object which also has a capsule collider attribute. However, it doesn't seem to do anything. How should I modify/add to this to make the camera "jump" and fall down to the ground again?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Jump : MonoBehaviour {
[HideInInspector] public bool jump = false;
public float jumpForce = 1000f;
public Transform groundCheck;
private bool grounded = false;
private Rigidbody rb;
// Use this for initialization
void Awake ()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update ()
{
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));
if (Input.GetButtonDown("Jump") && grounded)
{
jump = true;
}
}
void FixedUpdate()
{
if (jump)
{
rb.AddForce(new Vector2(0f, jumpForce));
jump = false;
}
}
}
Also, I would like to have the key for this be the spacebar if possible, but whatever key works or is there already is fine. I am still learning C#, so please forgive me if the solution is obvious.
This line is most likely causing the problem:
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));`
There are 2 reason that it wont produce proper results:
You haven't setup your ground tiles or the place where you character moves to the "Ground" layer. You wont have this by default but you can add it from the Project Settings->Tags and Layers menu.
Your colliders are not close enough to the ground thus not causing collision.
Besides that it should work fine.
I am creating a game in Unity where I have to have a player (a ball) and three enemies (in this case three rotating cylinders). Whenever the player hits an enemy, I need it to die (which I have already done) and then print out Game over, which I don't know how to do. I also need the player to respawn after it dies, which is another think I don't know how to do. I also need to create three "virtual holes" where when the player rolls over them, it respawns, but not dies. I figure I can simulate holes by creating flat cylinders, but I don't know how to make the ball respawn but rolling over them. Thank you in advance!! Please be clear about which part does what in your answer.
//My player script
public class PlayerController : MonoBehaviour
{
public float speed;
private Rigidbody rb;
public float threshold;
public Text gameOver;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
//rolls the player according to x and z values
void FixedUpdate()
{
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
rb.AddForce(movement * speed);
}
}
//my script that makes the enemy rotate and kills the player but after the
player dies it just disappears
public class Rotater : MonoBehaviour {
public Text gameOver;
// Update is called once per frame
void Update ()
{
transform.Rotate(new Vector3(15, 30, 45) * Time.deltaTime);
}
//kills player
void OnCollisionEnter(Collision Col)
{
if (Col.gameObject.name == "Player")
{
Destroy(Col.gameObject);
gameOver.text = "Game over!";
}
}
//my script that respawns the play if it falls off the maze
public class Respawn : MonoBehaviour
{
// respawns player if it goes below a certain point (falls of edge)
public float threshold;
void FixedUpdate()
{
if (transform.position.y < threshold)
transform.position = new Vector3(-20, 2, -24);
}
}
You need to create a UI to draw text over your game. You can learn about it here.
When you have the UI in place, you can just activate/deactivate the relevant parts with SetActive(bool).
To kill and respawn the player, I suggest you not to destroy it and re-instantiate it. Instead, you can simply deactivate it and reactivate it in the new position using SetActive(bool) again.
For the holes, you can create other objects with colliders and use OnCollisionEnter as you already did but to change player's position.