Change Gravity with a Touch C# in Unity - c#

i wonne make a game where the Gravity changes form 1enter image description here to -1enter image description here when i touch one Button and back when i touch the other button Button. In the Beginning it works but then it just stops working
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Button : MonoBehaviour
{
private Rigidbody2D rb;
private bool moveUp;
private bool MoveDown;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
moveUp = false;
MoveDown = false;
}
public void PionterDownRight(){
moveUp = true;
}
public void PionterUpRight(){
moveUp=false;
}
public void PionterDownLeft(){
MoveDown= true;
}
public void PionterUpLeft(){
MoveDown = false;
}
// Update is called once per frame
void Update()
{
if(moveUp){
rb.gravityScale = -1;
}
if (MoveDown){
rb.gravityScale = 1;
}
}
}

I recommend having only one bool variable:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Button : MonoBehaviour
{
private Rigidbody2D rb;
private bool moveDown = true;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
public void PionterDownRight()
{
moveDown = false;
}
public void PionterUpRight()
{
moveDown = true;
}
public void PionterDownLeft()
{
moveDown = true;
}
public void PionterUpLeft()
{
moveDown = false;
}
// Update is called once per frame
void Update()
{
if (moveDown == true)
{
rb.gravityScale = 1;
}
else
{
rb.gravityScale = -1;
}
}
}

Graviyscale affects how much gravity will effect the rigidbody, I'm assuming its not working because it plateaus at 0 = no effect. You might have to take a different approach, something like
rb.gravityScale = -1;
replace with
rb.AddForce(new Vector2(0, 9.81f * 2));

Related

Unity C# How to make aiming bool = true, when public void Aim() is active

Here is the code for the weapon
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponInfo : MonoBehaviour
{
[SerializeField] private Weapon weapon;
[SerializeField] private Transform muzzle;
[Header("Transform")]
public Transform playerCamera;
public Transform DefaultWeaponPos;
public Transform ADSWeaponPos;
AudioSource shootingSound;
[SerializeField] private AudioClip[] pistolClips = default;
private float timeSinceLastShot = 0f;
[HideInInspector] public bool aiming = false;
private void Start()
{
shootingSound = GetComponent<AudioSource>();
Player.shootInput += Shoot;
Player.reloadInput += StartReload;
Player.aimInput += Aim;
aiming = Player.shootInput != null;
}
public void StartReload()
{
if (!weapon.reloading)
StartCoroutine(Reload());
}
private IEnumerator Reload()
{
weapon.reloading = true;
shootingSound.PlayOneShot(pistolClips[2]);
yield return new WaitForSeconds(weapon.reloadTime);
weapon.currentAmmo = weapon.magSize;
weapon.reloading = false;
}
public void Aim()
{
aiming = true;
}
private bool CanShoot() => !weapon.reloading && timeSinceLastShot > 1f / (weapon.fireRate / 60f);
public void Shoot()
{
if(weapon.currentAmmo > 0)
{
if (CanShoot())
{
if (Physics.Raycast(playerCamera.position, playerCamera.forward, out RaycastHit hitInfo, weapon.maxDistance))
{
Debug.DrawLine(playerCamera.transform.position, hitInfo.point, Color.red, 10f);
print(hitInfo.transform.name);
}
shootingSound.PlayOneShot(pistolClips[0]);
weapon.currentAmmo--;
timeSinceLastShot = 0;
OnGunShot();
}
} else if (!weapon.reloading) shootingSound.PlayOneShot(pistolClips[1]);
}
private void Update()
{
timeSinceLastShot += Time.deltaTime;
Debug.DrawRay(playerCamera.position, playerCamera.forward);
transform.position = aiming ? ADSWeaponPos.position : DefaultWeaponPos.position;
}
private void OnGunShot()
{
}
}
Basically what I want is to make it so that when the player is Aiming it changes the weapon position to ADS, but when the player is not aiming it changes it back. I tried to make it so that when aiming = Aim != null but it didnt work, I also tried other methods but I dont know what to do since I tried looking for a solution but didnt find any
And here's the code for invoking the method in the Player Script
if (Input.GetKey(aimKey))
{
aimInput?.Invoke();
}
The aimInput is a public static Action
I figured it out I just need to create another method called sideAimInput in the player, and it makes it set to aiming = false in the weaponInfo script

Why the when playing animation state it's executing the play twice in a row instead once?

The script is attached to empty gameobject
At this line i'm using the mouse left button to fire a bullet one time.
If i'm using a break point it will shot one bullet once. but if i'm not using a break point it will shot two bullets in a row one after the other.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;
public class Shooting : MonoBehaviour
{
public CinemachineVirtualCamera cmf;
[Header("Main")]
public Rigidbody bulletPrefab;
public float launchForce = 700f;
public bool automaticFire = false;
public float bulletDestructionTime;
public bool go = false;
[Space(5)]
[Header("Slow Down")]
public float maxDrag;
public float bulletSpeed;
public bool bulletsSlowDown = false;
public bool overAllSlowdown = false;
[Range(0, 1f)]
public float slowdownAll = 1f;
public List<Transform> firePoints = new List<Transform>();
public Animator anim;
private void Start()
{
if (anim != null)
{
anim.SetBool("Shooting", true);
}
}
public void Update()
{
if (overAllSlowdown == true)
{
Time.timeScale = slowdownAll;
}
if (firePoints.Count > 0))
{
for (int i = 0; i < firePoints.Count; i++)
{
if (Input.GetMouseButton(0))
{
anim.SetTrigger("Shoot");
}
if (Input.GetMouseButton(1))
{
cmf.enabled = false;
}
if (go)
{
LaunchProjectile(firePoints[i]);
go = false;
}
}
}
}
private void LaunchProjectile(Transform firePoint)
{
Rigidbody projectileInstance = Instantiate(
bulletPrefab,
firePoint.position,
firePoint.rotation);
projectileInstance.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
cmf.enabled = true;
cmf.Follow = projectileInstance.transform;
cmf.LookAt = projectileInstance.transform;
projectileInstance.AddForce(new Vector3(0, 0, 1) * launchForce);
if (bulletsSlowDown == true)
{
if (projectileInstance != null)
{
StartCoroutine(AddDrag(maxDrag, bulletSpeed, projectileInstance));
}
}
}
IEnumerator AddDrag(float maxDrag, float bulletSpeed, Rigidbody rb)
{
if (rb != null)
{
float current_drag = 0;
while (current_drag < maxDrag)
{
current_drag += Time.deltaTime * bulletSpeed;
rb.drag = current_drag;
yield return null;
}
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
rb.drag = 0;
}
}
}
This script is attached to my player with animator and i'm using this method to reference event i added to animation in the animator controller. when the event happens the variable bool flag go is set to true.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThrowObject : MonoBehaviour
{
public Shooting shooting;
public void ThrowEvent()
{
shooting.go = true;
}
}
This is a screenshot of the animator controller.
I added a new state name Throwing with two transitions from and to the Grounded state.
The Grounded state is playing idle animation.
In the transition from the Grounded to the Throwing i added a condition name Shoot type trigger.
In the transition from the Throwing state to the Grounded there is no any conditions.
Afaik animator triggers are stackable!
So since you call this in a for loop it might happen that it adds multiple triggers at once but each transition only consumes one at a time!
What I ended up using in combination with triggers in an animator is this script
public class AnimatorTriggerResetter : StateMachineBehaviour
{
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
foreach(var p in animator.parameters)
{
if (p.type == AnimatorControllerParameterType.Trigger)
{
animator.ResetTrigger(p.name);
}
}
}
}
attach this to no specific state at all but directly to the Basic layer of your AnimatorController itself => It is called for each and every state that is entered => resets all existing triggers.
In your specific case though, why not rather pull the general calls out of the loop and rather make it
if (firePoints.Count > 0 && go)
{
if (Input.GetMouseButton(0))
{
anim.SetTrigger("Shoot");
}
if (Input.GetMouseButton(1))
{
cmf.enabled = false;
}
for (int i = 0; i < firePoints.Count; i++)
{
LaunchProjectile(firePoints[i]);
}
go = false;
}

OnTriggerExit2D getting called unnecessarily

I hope you all are doing well. I have been following a Unity tutorial for a rhythm game and I have found this bug that I could not get past. Essentially, my OnTriggerExit2D is getting called too early. I'll include a picture in the conclusion of this post. I have tried logging the game object and it seems that all of my button objects suffer the same fate. I have included a link of the tutorial that I have been following in the conclusion. Any help towards figuring this out would be helpful.
Tutorial Link: https://www.youtube.com/watch?v=PMfhS-kEvc0&ab_channel=gamesplusjames
What my game looks like, the missed shows up when I've hit it.
Debug Output
GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
{
public AudioSource theMusic;
public bool startPlaying;
public BeatScroller theBS;
public static GameManager instance;
public int currentScore;
public int scorePerNote = 100;
public int scorePerGoodNote = 125;
public int scorePerPerfectNote = 150;
public int currentMultiplier;
public int multiplierTracker;
public int [] multiplierTresholds;
public Text scoreText;
public Text multiText;
// Start is called before the first frame update
void Start()
{
instance = this;
scoreText.text = "Score: 0";
multiText.text = "Multiplier: x1";
currentMultiplier = 1;
}
// Update is called once per frame
void Update()
{
if(!startPlaying){
if(Input.anyKeyDown){
startPlaying = true;
theBS.hasStarted = true;
theMusic.Play();
}
}
}
public void NoteHit(){
Debug.Log("Note Hit On Time");
if(currentMultiplier-1 < multiplierTresholds.Length){
multiplierTracker++;
if(multiplierTresholds[currentMultiplier-1] <= multiplierTracker){
multiplierTracker = 0;
currentMultiplier++;
}
}
multiText.text = "Multiplier: x"+currentMultiplier;
//currentScore += scorePerNote * currentMultiplier;
scoreText.text = "Score: "+currentScore;
}
public void NormalHit(){
currentScore += scorePerNote * currentMultiplier;
NoteHit();
}
public void GoodHit(){
currentScore += scorePerGoodNote * currentMultiplier;
NoteHit();
}
public void PerfectHit(){
currentScore += scorePerPerfectNote * currentMultiplier;
NoteHit();
}
public void NoteMissed(){
Debug.Log("MISSED!");
multiText.text = "Multiplier: x1";
}
}
BeatScroller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeatScroller : MonoBehaviour
{
public float beatTempo;
public bool hasStarted;
// Start is called before the first frame update
void Start()
{
beatTempo = beatTempo / 60f;
}
// Update is called once per frame
void Update()
{
if(!hasStarted){
}else{
transform.position -= new Vector3(0f, beatTempo*Time.deltaTime, 0f);
}
}
}
ButtonController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ButtonController : MonoBehaviour
{
// Start is called before the first frame update
private SpriteRenderer theSR;
public Sprite defaultImage;
public Sprite pressedImage;
public KeyCode keyToPress;
void Start()
{
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(keyToPress))
{
theSR.sprite = pressedImage;
}
if(Input.GetKeyUp(keyToPress))
{
theSR.sprite = defaultImage;
}
}
}
noteObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class noteObject : MonoBehaviour
{
public bool canBePressed;
public KeyCode KeyToPress;
public GameObject hitEffect, goodEffect, perfectEffect, missedEffect;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Input.GetKeyDown(KeyToPress))
{
if(canBePressed)
{
gameObject.SetActive(false);
if(Mathf.Abs(transform.position.y) > 0.25){
GameManager.instance.NormalHit();
Debug.Log("Normal Hit!");
Instantiate(hitEffect,transform.position, hitEffect.transform.rotation);
}else if(Mathf.Abs(transform.position.y) > 0.05f){
GameManager.instance.GoodHit();
Debug.Log("Good Hit!!");
Instantiate(goodEffect,transform.position, goodEffect.transform.rotation);
}else{
GameManager.instance.PerfectHit();
Debug.Log("PERFECT HIT!!!");
Instantiate(perfectEffect,transform.position, perfectEffect.transform.rotation);
}
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if(other.tag == "Activator")
{
canBePressed = true;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator")
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}
}
EffectObject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EffectObject : MonoBehaviour
{
public float lifeTime = 1f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Destroy(gameObject, lifeTime);
}
}
Hmm ... You say that OnTriggerExit2D is called to early? I assume it's called when the elements are still inside one another? If that's the case I guess your bounding boxes don't have the right size, or the right shape. I see arrows, do they have a rectangular bounding box or a polygon one that follows their shape? Are all your bounding boxes the right size?
I figured out what was wrong thanks to AdrAs's comment.
Turns out I had to check the y position of my arrow and collider were well enough below the height of my button box collider. In addition to that, I reshaped my colliders. I found that this code did the trick for me. Thank You All For the Nudges in the right direction.
noteObject -> new OnTriggerExit2D
private void OnTriggerExit2D(Collider2D other)
{
if(other.tag == "Activator" && transform.position.y < -0.32)
{
Debug.Log("Exited collider on game object: "+ other.gameObject.name);
canBePressed = false;
GameManager.instance.NoteMissed();
Instantiate(missedEffect,transform.position, missedEffect.transform.rotation);
}
}

null reference exception when accessing the variable from a prefab

I get the null reference exception error when trying to change the boolean to right (or left in that regard). My prefab should spawn at FirepointL.
My script does recognise the prefeb as it does not return a Null for finding the prefab (tested this).
I made sure my boolean was set to Public and i had dropped all the GameObjects to their designated places in the Inspector.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public GameObject bullet;
private Rigidbody2D myRigidbody;
private float speed = 15;
private bool facingRight;
private bool ground = false;
private float jump = 23;
// Start is called before the first frame update
void Start()
{
facingRight = true;
myRigidbody = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
bullet = GameObject.FindGameObjectWithTag("Button");
Movement(horizontal);
Flip(horizontal);
if (Input.GetKey("w"))
{
if (ground)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, jump);
}
}
// this is the part that returns the error
if (facingRight == true)
{
bullet.GetComponent<weapon>().right = true;
}
if (facingRight == false)
{
bullet.GetComponent<weapon>().right = false;
}
}
void OnTriggerEnter2D()
{
ground = true;
}
void OnTriggerExit2D()
{
ground = false;
}
private void Movement(float horizontal)
{
myRigidbody.velocity = new Vector2(horizontal * speed,myRigidbody.velocity.y);
}
private void Flip(float horizontal)
{
if (horizontal > 0 && !facingRight || horizontal < 0 && facingRight)
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class weapon : MonoBehaviour
{
// Start is called before the first frame update
public bool right;
public Transform firepointR;
public Transform firepointL;
public GameObject bulletPrefab;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("space"))
{
Debug.Log("It's the space key");
Shoot();
}
}
void Shoot()
{
if (right == true)
{
Instantiate(bulletPrefab, firepointR.position, firepointR.rotation);
}
if(right == false)
{
Instantiate(bulletPrefab, firepointL.position, firepointL.rotation);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bullet : MonoBehaviour
{
public float speed = 20;
public Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
rb.velocity = transform.right * speed;
}
// Update is called once per frame
void Update()
{
}
}
Not sure if it's the exact issue, but there's a problem in PlayerMovement class. When you retrieve bullet you assume that an object with Button tag is present.
In my opinion, you should check for it with
// this is the part that returns the error
if (bullet && facingRight == true)
{
bullet.GetComponent<weapon>().right = true;
}
if (bullet && facingRight == false)
{
bullet.GetComponent<weapon>().right = false;
}
I think there is a problem with naming conventions. You are trying to find out a bullet whose name is "Button" but when you are instantiating the gameobject, it names it to something like Button(clone).
One solution that comes in my mind:
1st step:
Make a public static variable inside PlayerMovement script.
public static PlayerMovement Instance;
private void Awake()
{
Instance = this;
}
Set its value inside the awake function so that you can call it from anywhere.
2nd step:
I modified the shoot function.
void Shoot()
{
GameObject _firePosition = right == true ? firepointR : firepointL;
PlayerMovement.Instance.bullet = Instantiate(bulletPrefab, _firePosition.position, _firePosition.rotation); // we are setting the reference on runtime whenever we spawn a new bullet.
}
3rd Step:
Remove this line from PlayerMovement script as it is not needed now.
bullet = GameObject.FindGameObjectWithTag("Button");
PS: Code will not work if you don't follow step 3. Let me know if it helps. :)

Assigned object value changed when assigning it to another object variable

Basically what i'm trying to do is resetting the player stats using a stored default value. The thing is when the player revived and its stats restored, the default stats ended up changing when i did not change its value.
I've tried to use copy constructor and to set each variable individually and it works. Some how just setting it directly ended up with having that bug.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
* things to do:
* 1. respawn point
* 2. fix this strange bug
*/
namespace Outbreak
{
public class LivingEntity : MonoBehaviour, IDamageable
{
public Stats defaultStats;
public event System.Action OnDeath;
protected Stats stats;
public Stats Stats
{
get
{
return stats;
}
}
protected virtual void Awake()
{
stats = new Stats(defaultStats);
}
// Start is called before the first frame update
protected virtual void Start()
{
}
// Update is called once per frame
protected virtual void Update()
{
}
public void TakeDamage(float damage)
{
Debug.Log(defaultStats.IsDead);
//if not dead
if (stats.IsDead == false)
{
//and it still has health left
if (stats.Health > 0)
{
//take damage
stats.Health -= damage;
}
//after taking damage check if health is depleted.
if (stats.Health <= 0)
{
//pronouce it dead
Die();
}
}
}
protected virtual void Die()
{
//set its status to dead
stats.IsDead = true;
//broadcast to all listener that this player is dead
if (OnDeath != null)
{
OnDeath();
}
//make player invisible
gameObject.GetComponent<MeshRenderer>().enabled = false;
//prevent any collision
gameObject.GetComponent<CapsuleCollider>().enabled = false;
//prevent player detecting collision
gameObject.GetComponent<Rigidbody>().detectCollisions = false;
//set to kinematic
gameObject.GetComponent<Rigidbody>().isKinematic = true;
}
protected IEnumerator DelayedRevival()
{
yield return new WaitForSeconds(3.0f);
Revive();
yield return null;
}
protected virtual void Revive()
{
//2. reset to default stats
//stats = new Stats(defaultStats);
//stats.IsDead = false;
//stats.Health = 3;
//stats.MovementSpeed = 10;
stats = defaultStats;
//1. set position to last respawn point location
transform.position = Vector3.zero + (Vector3.up * 1.5f);
//make player visible
gameObject.GetComponent<MeshRenderer>().enabled = true;
//allow for collision
gameObject.GetComponent<CapsuleCollider>().enabled = true;
//allow player to detect collision
gameObject.GetComponent<Rigidbody>().detectCollisions = true;
//set to dynamic
gameObject.GetComponent<Rigidbody>().isKinematic = false;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Outbreak
{
[RequireComponent(typeof(CharacterController), typeof(CharacterInput))]
public class Character : LivingEntity
{
protected override void Awake()
{
base.Awake();
}
// Start is called before the first frame update
protected override void Start()
{
base.Start();
}
// Update is called once per frame
protected override void Update()
{
base.Update();
}
protected override void Die()
{
base.Die();
gameObject.GetComponent<CharacterInput>().enabled = false;
gameObject.GetComponent<CharacterController>().enabled = false;
StartCoroutine(DelayedRevival());
}
protected override void Revive()
{
base.Revive();
gameObject.GetComponent<CharacterInput>().enabled = true;
gameObject.GetComponent<CharacterController>().enabled = true;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Outbreak
{
[System.Serializable]
public class Stats
{
[SerializeField]
private float health;
[SerializeField]
private float movementSpeed;
[SerializeField]
private bool isDead;
public Stats()
{
health = 3.0f;
movementSpeed = 5.0f;
isDead = false;
}
public Stats(Stats stats)
{
health = stats.health;
movementSpeed = stats.movementSpeed;
isDead = stats.isDead;
}
public float Health
{
set
{
health = value;
}
get
{
return health;
}
}
public float MovementSpeed
{
set
{
movementSpeed = value;
}
get
{
return movementSpeed;
}
}
public bool IsDead
{
set
{
isDead = value;
}
get
{
return isDead;
}
}
}
}
I expect that the default value should not be change but the actual result ended up with the default value changing.
Your problem line is
stats = defaultStats;
Since the type Stats is a reference-type not a value-type this assignment makes stats have the same reference as defaultStats => they point now to one and the same object.
Any future changes you make to one of them is done to one and the same referenced Stats object.
You should either make your assignment like you did in Awake:
stats = new Stats(defaultStats);
which copies the values instead.
Or alternatively you could make your Stats not a class but a struct instead which converts it to a value-type
[Serializable]
public struct Stats
{
...
}
Hint: As said in the comments you should make all GetComponent calls only once in e.g. Awake and later reuse the stores references like
private MeshRenderer meshRenderer;
private CapsuleCollider capsuleCollider;
private Rigidbody rigidBody;
private void Awake()
{
meshRenderer = GetComponent<MeshRenderer>();
capsuleCollider = GetComponent<CapsuleCollider>();
rigidBody = GetComponent<RigidBody>();
}
than later you reuse those stored references e.g.
meshRenderer.enabled = false;
capsuleCollider.enabled = false;
rigidBody.detectCollisions = false;
rigidBody.isKinematic = true;
And in Character you do the same with CharacterInput and CharacterController.

Categories