me and my colleagues are creating a 2D sidescroller game in Unity with C#.
The player, is supposed to be able to pick objects (by touching them) and throw them.
Our plan is this:
1) make variables for the objects, their booleans and their rigid bodies.
2) verify if the object is touching the player
3) if it's true, then the object will parent to the player (setting their position to the player's hand).
4) to throw, the code will check if the player has the object (by using a boolean) and then it will unparent and throw by addforce.
The code doesn't have any errors and it works except on the throwing part (the player can grab and ungrab but can't throw).
The player can pick and unpick but not throwing and i don't understand why because the code looks right and the console doesn't show me any errors :/
Have a look to my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public bool ispicked1 = false;
public GameObject pickable1;
public Rigidbody2D pickable1rb;
public GameObject Parent;
public float force;
void Start() {
pickable1rb = GetComponent<Rigidbody2D>();
}
void Update() {
if (ispicked1 == true) {
pickable1.transform.position = Parent.transform.position;
}
else if (ispicked1 == false) {
pickable1.transform.parent = null;
}
if (Input.GetMouseButton(1) && ispicked1 == true) {
ispicked1 = false;
pickable1rb.AddForce(transform.up * force, ForceMode2D.Impulse);
}
}
private void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.name == "pickable1") {
Debug.Log("Tocou em objecto");
ispicked1 = true;
pickable1.transform.SetParent(Parent.transform);
}
}
}
Side question: i want the player to throw at the direction he's facing, what is the best way to do that? I can only choose between right, left or up :/
UPDATE:
I solved all the problems and i created a side script for the object to be thrown and they are 100% working! Here are the codes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Inventory : MonoBehaviour
{
public bool ispicked1 = false;
public bool ispicked2 = false;
public GameObject pickable1;
public GameObject pickable2;
public GameObject Parent;
public bool isThrown = false;
public ThrowableObject throwableinstance1;
public ThrowableObject throwableinstance2;
public bool isfull = false;
void Start() {
throwableinstance1 = GameObject.Find("pickable1").GetComponent<ThrowableObject>();
throwableinstance2 = GameObject.Find("pickable2").GetComponent<ThrowableObject>();
}
void Update() {
if (ispicked1 == true) {
pickable1.transform.position = Parent.transform.position;
isfull = true;
}
else if (ispicked1 == false) {
pickable1.transform.parent = null;
}
if (ispicked2 == true) {
pickable2.transform.position = Parent.transform.position;
isfull = true;
} else if (ispicked2 == false) {
pickable2.transform.parent = null;
}
if (Input.GetMouseButton(1) && ispicked1 == true) {
ispicked1 = false;
isThrown = true;
throwableinstance1.Throw();
isfull = false;
}
if (Input.GetMouseButton(1) && ispicked2 == true) {
ispicked2 = false;
isThrown = true;
throwableinstance2.Throw();
isfull = false;
}
}
private void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.name == "pickable1" && isfull == false) {
ispicked1 = true;
pickable1.transform.SetParent(Parent.transform);
}
if (collision.gameObject.name == "pickable2" && isfull == false) {
ispicked2 = true;
pickable2.transform.SetParent(Parent.transform);
}
}
}
Here's the code to throw the pickable object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThrowableObject : MonoBehaviour
{
public Inventory inventoryinstance;
public Rigidbody2D throwablerb;
public Transform Player;
public GameObject PickableObject;
public EnemyHealth1 enemyhealth1instance;
public EnemyHealth2 enemyhealth2instance;
public EnemyHealth3 enemyhealth3instance;
void Start()
{
inventoryinstance = GameObject.Find("Player").GetComponent<Inventory>();
enemyhealth1instance = GameObject.Find("enemy1").GetComponent<EnemyHealth1>();
enemyhealth2instance = GameObject.Find("enemy2").GetComponent<EnemyHealth2>();
enemyhealth3instance = GameObject.Find("enemy3_leper").GetComponent<EnemyHealth3>();
}
public void Throw()
{
if(Player.localScale.x < 1)
{
throwablerb.AddForce(transform.right * -1);
} else if(Player.localScale.x > 0)
{
throwablerb.AddForce(transform.right);
}
}
private void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.name == "enemy1") {
enemyhealth1instance.GetComponent<EnemyHealth1>().EnemyHealthbar1-= 1;
Destroy(PickableObject);
}
if (collision.gameObject.name == "enemy2") {
enemyhealth2instance.GetComponent<EnemyHealth2>().EnemyHealthbar2-=1;
Destroy(PickableObject);
}
if (collision.gameObject.name == "enemy3_leper") {
enemyhealth3instance.GetComponent<EnemyHealth3>().EnemyHealthbar3-=1;
Destroy(PickableObject);
}
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.name == "enemy1_hitbox") {
enemyhealth1instance.GetComponent<EnemyHealth1>().EnemyHealthbar1-=1;
Destroy(PickableObject);
}
}
}
While you're answering my question, I'll do some quick code reviewing:
Overall, it feels weird to be keeping track and maintaining "holding & throwing an item" in a class called "Inventory" that has references to a separate object, that is being held & a reference to the player. What is this script? The item, the player or a third party; a separate inventory? (Questions to ask yourself, you don't need to answer them - just think about it 😊)
pickable1
pickable1object
pickable1rb
These variable names makes little sense to me in current context; why not just isPickedUp, object, rb?
if (pickable1 == true) {
pickable1object.transform.position = Playerparent.transform.position;
}
if (pickable1 == false) {
pickable1object.transform.parent = null;
}
pickable1 will not be both true and false, doing 2 ifs are just unnecessary computations. What you're looking for here is an if/else or if/elseif.
Furthermore, if you child it to the player, logically, shouldn't you NOT have to set the position every frame? Else, what's the purpose of childing?
void FixedUpdate() {
if (Input.GetMouseButton(1)) {
// [...]
}
// [...]
}
Logically, you shouldn't be checking for input in the fixed update since inputs are tied to a frame, meaning Update(). In this case, it may accidentally work reliably most of the time because you're not checking if the player CLICKED a button, rather you're checking if player is HOLDING a button. (GetMouseButton vs GetMouseButtonDown). If you were to check for if the player CLICKED a button in the FixedUpdate, it would only work on the few lucky frames where FixedUpdate and Update were running at the exact same time.
Regarding your question: The code looks alright, it's probably not behave in the way you expect. From what I can tell in the code it will...
If player has picked up an item, it will have force added to it if holding down Mouse 1 for as long as player is holding down mouse.
Why nothing is happening may be due to inspector values being off. Debug the values of force and see if the code is triggering at all. Consider moving the parent = null code (or just remove the parenting alltogether).
It could also be that as soon as you push the item outside of the player, it falls down and triggers OnCollisionEnter and is grabbed by the player again.
The problem with your code is that you use transform.up which will just cause it to throw upwards rather than the direction the player is facing. So you can use transform.right for the direction the player is facing. You can use both to create more of an arc.
You'll have some problems with pivoting with the way you have it setup.
What I recommend is to have two transforms which are children of the player to pivot the sprite properly. (You can change the position of the transform in the keyframes of the player's animation)
The code would look something like this:
public class Inventory : MonoBehaviour
{
private bool carryingItem => pickedUpItem == null;
private PickableItem pickedUpItem;
[SerializeField] private Vector2 throwingForce;
[SerializeField] private Transform spawnPosition; //Should be a child of the player gameobject. This will be position of the object
[SerializeField] private Transform handTransform; //Should also be a child of the player gameobject. This needs to be animated for the keyframes that move the hand
void Update()
{
if (carryingItem && Input.GetMouseButtonDown(1))
{
pickedUpItem.transform.SetParent(null);
pickedUpItem.transform.position = spawnPosition.position;
pickedUpItem.GetComponent<Rigidbody2D>().AddForce(transform.right * throwingForce.x + transform.up * throwingForce.y);
pickedUpItem = null;
}
}
private void OnCollisionEnter2D(Collision2D other)
{
var pickable = other.transform.GetComponent<PickableItem>();
if (pickable && pickedUpItem == null) //This kind of depends on the game design because maybe you want to pick it up if you're carrying something already
{
pickedUpItem = pickable;
pickable.transform.SetParent(handTransform);
}
pickable.transform.localPosition.Set(0,0,0);
}
}
You set the picked up item to the parent where you want to be pivoted around.
Related
I'm sorry for any messy code, I'm relatively new to this. I made a working teleport in Unity but whenever I teleport from one of the teleports to the other, I wanna make it so there's a 5 second cooldown before you can use the teleporter again. So I used IEnumerator, added 5 seconds before "justTeleported" became false again, but when I teleported, I instantly got teleported back, and had to wait 5 seconds before I could try again. So my though was maybe I'm touching the trigger too quickly, before it can become false, that's why I added the two seconds. But now, whenever I get on the teleporter, it goes from true to false to true a couple times, and then I eventually get teleported back to where I came from. If anyone could help, I would be very thankful. Thank you.
{
public Transform Destination;
bool justTeleported;
public GameObject Player = GameObject.FindGameObjectWithTag("Player");
// Start is called before the first frame update
void Start()
{
justTeleported = false;
}
private void Update()
{
print(justTeleported)
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.tag == "Player" && justTeleported == false)
{
StartCoroutine("Cooldown");
}
}
IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(2f);
Player.transform.position = Destination.transform.position;
yield return new WaitForSecondsRealtime(5f);
justTeleported = false;
}
Because each of the teleports has its own bool justTeleported, setting the first true doesn't automatically set the other true, so you need some way to tell the Destination teleport script to start it's own cooldown coroutine. Here's my test script, it works for me.
using System.Collections;
using UnityEngine;
public class Teleport : MonoBehaviour
{
public Teleport Destination = null;
public float CooldownTime = 5f;
private bool justTeleported = false;
public void StartCoolDown()
{
StartCoroutine(Cooldown());
}
private void OnTriggerEnter2D(Collider2D coll)
{
if (coll.gameObject.CompareTag("Player") && !justTeleported) //CompareTag("tag") is better than .tag == "tag"
{
TeleportPlayer(coll.gameObject);
}
}
private void TeleportPlayer(GameObject player)
{
if (Destination) //if Destination is not null
{
StartCoolDown();
Destination.StartCoolDown(); //tell Destination to cooldown
player.transform.position = Destination.transform.position;
}
}
private IEnumerator Cooldown()
{
justTeleported = true;
yield return new WaitForSeconds(CooldownTime);
justTeleported = false;
}
}
Teleport1 (Destination: Teleport2)
Teleport2 (Destination: Teleport1)
I'm currently creating a pong game in which the moment the ball hits one of the pong paddles, it splits in two. I do this by destroying the paddle that receives the collision, and instating a split paddle that I've made a prefab.
My issue is every prefab is tagged differently and every time the ball hits a paddle, it should detct that tag and do something... but after the first split, once the new paddle is instantiated, the function doesn't fire...
Can I have several if/else if statements like this? What am I missing?
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PaddleSplit_Script : MonoBehaviour
{
public GameObject split_paddle1;
public GameObject split_paddle2;
public GameObject split_paddle3;
public GameObject split_opponent_paddle1;
public GameObject split_opponent_paddle2;
public GameObject split_opponent_paddle3;
//public override void Apply(GameObject target)
//{
// void
// if (target.gameObject.CompareTag("Player 1"))
// {
// //Instantiate()
// }
//}
private void OnCollisionEnter2D(Collision2D collision)
{
// Pre-State
if (collision.gameObject.CompareTag("Player 1"))
{
Debug.Log("Player Split");
Destroy(collision.gameObject);
Instantiate(split_paddle1);
//Destroy(gameObject);
}
else if (collision.gameObject.CompareTag("Player 2"))
{
Debug.Log("Opponent Split");
Destroy(collision.gameObject);
Instantiate(split_opponent_paddle1);
//Destroy(gameObject);
}
// Primary State
else if (collision.gameObject.CompareTag("Player 1_1"))
{
Debug.Log("Player split again");
Destroy(collision.gameObject);
Instantiate(split_paddle2);
}
else if (collision.gameObject.CompareTag("Player 2_1"))
{
Debug.Log("Opponent split again");
Destroy(collision.gameObject);
Instantiate(split_opponent_paddle2);
}
// Secondary State
// else if (collision.gameObject.CompareTag("Player 1_2"))
// {
// Destroy(collision.gameObject);
// Instantiate(split_paddle3);
// }
// else if (collision.gameObject.CompareTag("Player 2_2"))
// {
// Destroy(collision.gameObject);
// Instantiate(split_opponent_paddle3);
// }
}
}
As you'll notice, I broke them down in states (pre-state being the paddle un-split).
What I am trying to accomplish is once the ball hits a paddle, it should detect that collision based off the tag it hits....
Can I have several if/else if statements like this?
Of course you can! OnCollisionEnter2D (in this regard) is a c# method like any other.
Should you? That's another question.
Unfortunately the tag system is quite limited there and not really dynamically extendable. Also you might just miss to tag and referene your objects correctly.
Instead of using tags in this case I would rather go with a simple component like e.g.
public enum Side
{
Player,
Opponent
}
public class Paddle : Monobehaviour
{
public int splitLevel;
public Side side;
}
and do e.g.
public Paddle[] paddlesPrefabs;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.TryGetComponent<Paddle>(out var paddle))
{
var currentSplitLevel = paddle.splitLevel;
var side = paddle.Side;
var newSplitLevel = currentSplitLevel + 1;
if(newSplitLevel >= paddlesPrefabs.Length)
{
// GameOverAndLoserIs(side);
}
else
{
var newPrefab = paddlesPrefabs[newSplitLevel];
var newPaddle = Instantiate(newPrefab);
newPaddle.splitLevel = newSplitLevel;
newPaddle.side = side;
}
}
}
preface: i am very new to c#, and this is an object grabbing script i have taken from a prefab and edited.
script goal: detect if a game object is in pickup distance. if it is, when the grip trigger is pressed, parent the object to the virtual hand. when the trigger is released, remove the parent relationship. Also, while the object is parented to the controller, if the B button is held, scale the object up, and if the A button is held, scale the object down, and reset the scale upon thumbstick click.
I can see when my controller collides with the game object, but when i press the designated button to parent the object, nothing seems to happen.
I'd really appreciate any help I could get with this script, as there might be a significant amount i'm doing wrong.
I should mention that I don't really need any sort of physics simulations, all i need is the ability to manipulate an object floating in space for viewing purposes.
Thanks in advance!
Here is my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grabber : MonoBehaviour
{
// Update is called once per frame
void Update()
{
OVRInput.Update();
if ((OVRInput.Get(OVRInput.Axis1D.SecondaryHandTrigger)) > 0.2f && CollidingObject)
{
GrabObject();
}
if ((OVRInput.Get(OVRInput.Axis1D.SecondaryHandTrigger)) < 0.2f && ObjectInHand)
{
ReleaseObject();
}
if (OVRInput.Get(OVRInput.Button.Two) && ObjectInHand)
{
ScaleUp();
}
if (OVRInput.Get(OVRInput.Button.One) && ObjectInHand)
{
ScaleDown();
}
if (OVRInput.Get(OVRInput.Button.SecondaryThumbstick) && ObjectInHand)
{
ScaleReset();
}
}
public GameObject CollidingObject;
public GameObject ObjectInHand;
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.GetComponent<Rigidbody>())
{
CollidingObject = other.gameObject;
}
}
public void OnTriggerExit(Collider other)
{
CollidingObject = null;
}
private void GrabObject()
{
ObjectInHand = CollidingObject;
ObjectInHand.transform.SetParent(this.transform);
ObjectInHand.GetComponent<Rigidbody>().isKinematic = true;
}
private void ReleaseObject()
{
ObjectInHand.GetComponent<Rigidbody>().isKinematic = false;
ObjectInHand.transform.SetParent(null);
ObjectInHand = null;
}
Vector3 scaleChangeUp = new Vector3(0.01f, 0.01f, 0.01f);
Vector3 scaleChangeDown = new Vector3(-0.01f, -0.01f, -0.01f);
public void ScaleUp()
{
ObjectInHand.transform.localScale += scaleChangeUp;
}
public void ScaleDown()
{
ObjectInHand.transform.localScale += scaleChangeDown;
}
private void ScaleReset()
{
ObjectInHand.transform.localScale = Vector3.one;
}
}
I'm currently working on a game where i have a lightswitch, whenever i'm touching the spherecollider around the switch, the light should turn on. And when i'm not touching the spherecollider anymore, it should turn off. I've not been doing this in a long time, so i've gotten a bit rusty.
public Light ceilingLight;
public bool LS;
private void Update()
{
if (LS == true)
{
ceilingLight.enabled = true;
}
if(LS == false)
{
ceilingLight.enabled = false;
}
}
private void OnTriggerEnter(Collider other)
{
if(other.tag == "Player")
{
LS = true;
}
if(other.tag != "Player")
{
LS = false;
}
}
First: Instead of using Update you rather want it to be event driven and this avoid unnecessary Update calls when nothing changed anyway.
Then as was mentioned in the comment what you currently do is enable the light if the player touches, disable it if anything else touches it.
You rather want to disable it when the player doesn't touch anymore and ignore everything else.
Simply do
public Light ceilingLight;
// optionally additionally configure whether the light should start enabled or not
public bool InitiallyEnabled;
private void Start()
{
ceilingLight.enabled = InitiallyEnabled;
}
private void OnTriggerEnter(Collider other)
{
if(!other.CompareTag("Player")) return;
ceilingLight.enabled = true;
}
private void OnTriggerExit(Collider other)
{
if(!other.CompareTag("Player")) return;
ceilingLight.enabled = false;
}
As alternative you could also use it as a toggle like
private void OnTriggerEnter(Collider other)
{
if(!other.CompareTag("Player")) return;
ceilingLight.enabled = ! ceilingLight.enabled;
}
so with every touch the player would toggle the light state without having to keep pressing it continuously.
Consider that you may need all types of things to be pressable later on in your app, so you could start with a basic Pressable class to handle the basics. You can then add this behavior to the compositing mix of your respective prefab objects (which may be Holdable, Steerable, Pressable and so on):
using UnityEngine;
public class Pressable : MonoBehaviour
{
[SerializeField] protected bool active = false;
[SerializeField] AudioSource pressSound = null;
protected virtual void Start()
{
}
public virtual void Press()
{
if (pressSound != null) { pressSound.Play(); }
active = !active;
}
}
Note the active bool will appear in the inspector, so you can set it to true at start. (You may also want to pass Press(Person person) in case something should be done with the person pressing.)
Now for your specific light switch, you would inherit this and add the logic for just lights:
using UnityEngine;
public class LightPressable : Pressable
{
Light light = null;
protected override void Start()
{
base.Start();
light = GetComponentInChildren<Light>();
UpdateBasedOnActive();
}
public override void Press()
{
base.Press();
UpdateBasedOnActive();
}
void UpdateBasedOnActive()
{
light.enabled = active;
}
}
Good luck!
When my Player (GameObject) meets Lava, they should respawn in a specific scene.
This is the code I have assigned to the Player:
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Lava")
{
GameObject.Find("Controller").GetComponent<Controller>().Respawn();
}
}
Controller is a GameObject, that I don't want to Destroy by Changing level, so this is the code for my Controller GameObject:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Controller : MonoBehaviour
{
private static bool created = false;
public static Controller instance;
GameObject Player;
Vector3 respawnPoint;
void Awake()
{
if (instance = null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
return;
}
if (!created)
{
DontDestroyOnLoad(this.gameObject);
created = true;
Player = GameObject.Find("Player");
respawnPoint = GameObject.Find("RespawnPoint").transform.position;
}
}
public void Respawn()
{
SceneManager.LoadScene(0);
Player.transform.position = respawnPoint;
}
}
RespawnPoint is just an invisible Cube GameObject, where I want the player to respawn.
Let's say the Game Starts with Scene "0" (this is where the RespawnPoint is, too.)
Then the Player goes to Scene "1" and dies (meets Lava). Then I want the Game to change back to Scene "0" and teleport the Player to the RespawnPoint.
The Scene-Change works good, but the player always starts at the same position, where he starts the first time and he's not teleported to the RespawnPoint.
What am I doing wrong?!
First of all you lack the "==" in the first 'if' from the Awake: if (instance == null
The code is fine or it does seem so to me, but the RespawnPoint should be in the Scene your Player meets the lava not in the Scene you are loading. If not the starting position of the player will always be (0,0,0).
I would recommend coming to this in a completely different way. I would make a public Transform[] Spawnpoints. Since the transform is public you can assign different objects to it. Make an empty game object and position it where you want to spawn. Then use
Void OnTriggerEnter(collider2D, other){
if(other.gameObject.tag == lava) {
transform.position = spawnpoints[0].position;
}
}
In the inspector set the Transform to be a size of 1 and set the respawn GameObject as the one transform.
Thanks to your answers, they helped me solving this problem.
I added a "DontDestroyOnLoad" to the RespawnPoint and than i changed the Controller Code to this:
{
private static bool created = false;
public static Controller instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
return;
}
if (!created)
{
DontDestroyOnLoad(this.gameObject);
created = true;
}
}
public void Respawn()
{
SceneManager.LoadScene(0);
GameObject.Find("Player").transform.position = GameObject.Find("RespawnPoint").transform.position;
}
}
now the player gets teleported to the correct RespawnPoint. Thanks for your help!