Unity c# Code working in one script but not in other? - c#

Hey guys so I have this really strange problem, so basically what happens is the player runs into the box (OnTriggerEnter function) and a weapon will show on the character and the box will spawn to another location. Except I tried to make a separate script because the script that it works on is for spawning boxes. I didn't want it in the same script because not as neat.
So this is my code for the script where it doesn't work:
public class ChangeGun : MonoBehaviour {
public Sprite gunSprite;
private string[] weapons = { "Pistol", "Shotgun", "Ak47", "Bazooka" };
public GameObject currentGun;
public void AddGunToPlayer()
{
//int randomNumber = Random.Range(0, 4);
currentGun.GetComponent<SpriteRenderer>().sprite = gunSprite;
}
}
And this is the script where it works (keep in mind I used the EXACT same code, ignore all the code in this script except the for the 2 variables and the OnTriggerEnter Function.)
public class BoxGenerator : MonoBehaviour
{
public GameObject currentGun;
public Sprite gunSprite;
public Vector3[] boxPositions;
public GameObject box;
public GameObject startingBox;
int randomNumber;
// Use this for initialization
void Start()
{
//random number between 0-10 thats vector size
randomNumber = Random.Range(0, 10);
//instantiate the starting box
startingBox = Instantiate(box);
//set starting box to new location
startingBox.transform.position = boxPositions[randomNumber];
//set tag to box
startingBox.tag = "box";
}
void Update()
{
randomNumber = Random.Range(0, 10);
}
void OnTriggerEnter(Collider col)
{
if (col.GetComponent<Collider>().tag == "box")
{
startingBox.transform.position = boxPositions[randomNumber];
currentGun.GetComponent<SpriteRenderer>().sprite = gunSprite;
}
}
}
Keep in mind when I tried to use the old script under this line "currentGun.GetComponent().sprite = gunSprite;"
I pasted this code and got the null reference exception.
ChangeGun cg = new ChangeGun();
cg.AddGunToPlayer();

ChangeGun is null since you didn't set it. You said you set it in the inspector, but in your question you created in in the code so it will have the default values.
ChangeGun cg = new ChangeGun(); // cg.currentGun is null
cg.AddGunToPlayer(); // Trying to access cg.currentGun
I suppose you have set in in the inspector, so instead of creating new ChangeGun, you need to get the component you created in Inspector:
ChangeGun cg = GetComponent<ChangeGun>();
cg.AddGunToPlayer();

Related

saving player position while moving between scenes Unity 3D

I've started learning 3D game development and now I've reached the scenes changing.
I looked over the internet and I couldn't find a solution that I can implement in my project.
At this point, I've two scenes, the street which is the main scene, and a bar scene.
I've created a sliding door that has a collider called portal that triggers the scene switch.
I've managed to swap the scene but when I'm trying to go back to my main scene I respawn and the starting point and not at the bar exit. I've tried to save the player position in a temp variable but that didn't work well. what should I do to make my player start at the store exit ?
public class PlayerMotion : MonoBehaviour
{
//...
public static Vector3 playerPosition; // for respawning use
public static bool respawnNeeded = false;
CharacterController cController;
void Update()
{
if(respawnNeeded)
{
cController.transform.position = new Vector3(StreetToBarFFPortal.tempPosition.x,
StreetToBarFFPortal.tempPosition.y,
StreetToBarFFPortal.tempPosition.z);
respawnNeeded = false;
}
//..... some movement code
playerPosition = new Vector3(cController.transform.position.x, cController.transform.position.y, cController.transform.position.z);
}
public class StreetToBarFFPortal : MonoBehaviour
{
public static Vector3 tempPosition;
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
int index = SceneManager.GetActiveScene().buildIndex;
if(index==0)
{
PlayerMotion.respawnNeeded = true;
tempPosition = new Vector3(PlayerMotion.playerPosition.x,
PlayerMotion.playerPosition.y,
PlayerMotion.playerPosition.z-4);
}
index = 1 - index;// 1 transfers to 0 and 0 transfers to 1
SceneManager.LoadScene(index);
}
}
}
I also have a GlobalManeger script with a singeltone design pattern if that helps.
You have two options:
Store it in the DontDestroyOnLoad()
Store it in playerprefs before you load the other scene and load it back when the scene is loaded again. Since you're storing a Vector3 then store the x, y and z like this:
PlayerPrefs.SetFloat("X", playerPosition.x);
PlayerPrefs.SetFloat("Y", playerPosition.y);
PlayerPrefs.SetFloat("Z", playerPosition.z);
and load is like this:
float xpos = PlayerPrefs.GetFloat("X");
float ypos = PlayerPrefs.GetFloat("Y");
float zpos = PlayerPrefs.GetFloat("Z");
playerPosition = new Vector3(xpos, ypos, zpos);
You have tempPosition which as I can see used to store player position before teleportation, am I right?
So I can't see where this tempPosition variable came from. Is it static or from a singletone script? Anyway you need to have this variable through scenes. So it should be in the script that has DontDestroyOnLoad() method;

Spawn in 2 different prefabs over a network Unity PUN 2

I am making an online multiplayer game. Once both players are ready, 2 DIFFERENT character prefabs are spawned into a room. The problem is that each player's device can't seem to read information about the other player's character.
They're spawned using the following code:
public class GameplayManager : MonoBehaviour
{
public PhotonView view;
public string BlueName;
public string GreenName;
public GameObject BluePlayer;
public GameObject GreenPlayer;
public Vector2 BlueSpawnPos = new Vector2(4.07f, 0.02f);
public Vector2 GreenSpawnPos = new Vector2(3.97f, -0.02f);
public void Start()
{
InstantiatePlayers();
BluePlayer = GameObject.Find("blue soldier(Clone)");
BlueName = PhotonNetwork.LocalPlayer.NickName;
GreenPlayer = GameObject.Find("green soldier(Clone)");
GreenName = PhotonNetwork.LocalPlayer.NickName;
VictoryText = GameObject.Find("VictoryText").GetComponent<Text>();
}
public void InstantiatePlayers()
{
//If player 1:
if (PhotonNetwork.LocalPlayer.ActorNumber == 1)
{
BluePlayer = PhotonNetwork.Instantiate(BlueSoldier.name, BlueSpawnPos, Quaternion.identity);
Debug.Log("bname" + BlueName);
}
//if player 2:
if (PhotonNetwork.LocalPlayer.ActorNumber == 2)
{
GreenPlayer = PhotonNetwork.Instantiate(GreenSoldier.name, GreenSpawnPos, Quaternion.identity);
Debug.Log("gname" + GreenName);
}
}
I run this using the Unity Editor and a build to test. When I check the inspector in the editor, the 'Player' and 'Name' variables for the colour character in the editor have been updated correctly, but that of the other copy of the game are empty (i.e. if the editor is blue, 'BlueName' and 'BluePlayer' will be updated but 'GreenName' will be empty and 'GreenPlayer' will be 'None').
I suspect it's to do with my using if statements to spawn in characters, but I don't know if there's another way of spawning in multiple prefabs for multiple players.

Unity - Cant access reference to another script

I am trying to learn how does Unity work and I now struggle with problem that I cannot access script from another script. I was searching on Interner for couple hours, I have tried many options but nothing helped.
I have 2 scripts.
CoinSpawn.cs - attached to Player (I would change it to other object but I dont know yet to which one, because its something that runs in background so it really dont need to be on player)
CollectingCoin.cs - attached to Coin (Coin is object, that its not on game scene on the start, it spawns randomly)
CoinSpawn is script that randomly spawn Instantiate of object Coin. I want to change value of CoinSpawn.currentCoinOnScreen from CollectingCoin. I ve tried
CoinSpawn test = GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
and it doesnt work. I also have my both scripts in the same asset folder. What am I doing wrong? Thank you
CoinSpawn.cs
public class CoinSpawn : MonoBehaviour
{
public GameObject coin;
public int maximumCoinPerScreen = 10;
public int currentCoinOnScreen = 0;
private int randomNumber;
private Vector2 spawnPosition;
private void Update()
{
randomNumber = Random.Range(1, 1000);
if(randomNumber >= 0 && randomNumber <= 1 && currentCoinOnScreen != maximumCoinPerScreen)
{
currentCoinOnScreen++;
float spawnY = Random.Range
(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).y, Camera.main.ScreenToWorldPoint(new Vector2(0, Screen.height)).y);
float spawnX = Random.Range
(Camera.main.ScreenToWorldPoint(new Vector2(0, 0)).x, Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, 0)).x);
spawnPosition = new Vector2(spawnX, spawnY);
GameObject coinObject = Instantiate(coin, spawnPosition, Quaternion.identity);
}
}
}
CollectingCoin.cs
public class CollectingCoin : MonoBehaviour
{
UnityEngine.UI.Text Coins;
public static int totalCoins = 0;
private void Start()
{
Coins = GameObject.Find("Score").GetComponent<UnityEngine.UI.Text>();
}
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
totalCoins++;
Destroy(gameObject);
Coins.text = "COINS: " + totalCoins.ToString();
// TESTING
CoinSpawn test = GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
CoinSpawn test2 = GetComponent<CoinSpawn>();
}
}
}
GameObject.Find("CoinSpawn").GetComponent<CoinSpawn>();
Searches for a GameObject with the name CoinSpawn. Since you told us this component is rather attached to the player object it makes sense that it isn't found.
GetComponent<CoinSpawn>();
searches for a CoinSpawn component on the very same object your CollectingCoin is attached to. From your description this clearly also isn't the case.
Since you say the CoinSpawn is attached to the player then you probably rather want to get the component from
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
...
// rather get the component on the player object you collided with
CoinSpawn test = c2d.GetComponent<CoinSpawn>();
}
}
Alternatively assuming there is only one single instance of CoinSpawn in your scene anyway and not necessarily on your player you could use FindObjectOfType
CoinSpawn test = FindObjectOfType<CoinSpawn>();
First of all, Do not ever use GameObject.Find(), its very expensive as it will go through all game objects in your scene to find the object. and this not a performance wise.
There are many ways to do so.
Easyest one:
Add both script to same gameobject as component.
Make a global variable CoinSpawn inside CollectingCoin script and then use [serializedFiled] tag on top of it, by this way, you can drag and drop the reference in the editor before you start play. and you can access it the way you want.
2nd way:
Is same as first one, but instead of serializedFiled, just cache it at the beginning by using GetComponent.
Just make sure you have both scripts attached to the same gameobject.
public class CollectingCoin : MonoBehaviour
{
UnityEngine.UI.Text Coins;
public static int totalCoins = 0;
CoinSpawn coinSpawn;
private void Start()
{
coinSpawn = GetComponent<CoinSpawn>();
Coins = GameObject.Find("Score").GetComponent<UnityEngine.UI.Text>();
}
void OnTriggerEnter2D(Collider2D c2d)
{
if (c2d.CompareTag("Player"))
{
totalCoins++;
Destroy(gameObject);
Coins.text = "COINS: " + totalCoins.ToString();
// DO Whaterver you want with coinSpawn here
}
}
}

Can only spawn one object at once in unity

In my game I have a game object called ExclamationMark which I want to spawn above enemies heads when the player gets into range and they become "Alerted".
I've made this simple script to do that, but for some reason it will only work on one game object.
My enemy script:
void CheckForPlayer()
{
// Define player and get position
var player = GameObject.FindWithTag("Player");
var playerPos = (int)player.transform.position.x;
if (transform.Find("Graphics"))
{
// Define gameobject position
var enemyPos = transform.Find("Graphics").gameObject.transform.position.x;
// Define range to spawn tiles in
var range = 5;
var rangeInfront = enemyPos + range;
var rangeBehind = enemyPos - range;
if (playerPos >= rangeBehind && playerPos <= rangeInfront)
{
enemyIsActive = true;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark();
}
}
else
{
enemyIsActive = false;
}
}
}
My ! script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ExclamationMarkSpawn : MonoBehaviour {
public GameObject spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
public void SpawnExclamationMark()
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.transform.position, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, spawnPos.transform.position, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}
IEnumerator DestroyExclamationMark()
{
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (Transform child in transform) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
}
}
Just to be sure: I assume every player has its own instance of both of your scripts attached (some maybe nested further in their own hierarchy).
I assume that since you are using transform.Find which looks for the object by name within it's own children.
In general using Find and GetComponent over and over again is very inefficient! You should in both classes rather store them to fields and re-use them. Best would be if you can actually already reference them via the Inspector and not use Find and GetComponent at all.
In general finding something by name is always error prone. Are you sure they are all called correctly? Or are others maybe further nested?
Note: Find does not perform a recursive descend down a Transform hierarchy.
I would prefer to go by the attached components. You say it has e.g. a RigidBody. If this is the only Rigidbody component in the hierarchy below your objects (usually this should be the case) then you could instead rather simply use
// pass in true to also get disabled or inactive children
Rigidbody graphics = GetComponentInChildren<Rigidbody>(true);
the same for the ExclamationMarkSpawn
// Would be even beter if you already reference these in the Inspector
[SerializeField] private Rigidbody graphics;
[SerializeField] private ExclamationMarkSpawn exclamationMark;
[SerializeField] private Transform player;
private void Awake()
{
if(!player) player = GameObject.FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
}
private void CheckForPlayer()
{
// If really needed you can also after Awake still use a lazy initialization
// this adds a few later maybe unnecessary if checks but is still
// cheaper then using Find over and over again
if(!player) player = FindWithTag("Player");
if(!graphics) graphics = GetComponentInChildren<Rigidbody>(true);
if(!exclamationMark) exclamationMark = GetComponentInChildren<ExclamationMarkSpawn>(true);
var playerPos = (int)player.position.x;
// always if making such a check also give a hint that something might be missing
if (!graphics)
{
// by adding "this" you can now simply click on the message
// in the console and it highlights the object where this is happening in the hierarchy
Debug.LogWarning("graphics is missing here :'( ", this);
return;
}
// Define gameobject position
var enemyPos = graphics.transform.position.x;
// Define range to spawn tiles in
// this entire block can be shrinked down to
if (Mathf.Abs(playerPos - enemyPos) <= 5)
{
enemyIsActive = true;
if (exclamationMark) exclamationMark.SpawnExclamationMark();
}
else
{
enemyIsActive = false;
}
}
The same also in ExclamationMarkSpawn.cs.
I would additionally only allow 1 exclamation mark being visible at the same time. For example when a player jitters in the distance especially assuming both, the player and the enemy, I would move the entire instantiation to the routine and use a flag. Especially since this is called every frame in Update while the player stays in the range!
Also re-check and make sure your enemies are not maybe referencing the same spawnPos and thus all instantiating their exclamation marks on top of each other.
public class ExclamationMarkSpawn : MonoBehaviour
{
public Transform spawnPos;
public GameObject exclamationMark;
public GameObject exclamationMarkAudio;
[SerializeField] private CameraShake cameraShake;
// only serialized for debug
[SerializeField] private bool isShowingExclamation;
private void Awake()
{
if(!cameraShake) cameraShake = Camera.main.GetComponent<CameraShake>();
// or assuming this component exists only once in the entire scene anyway
if(!cameraShake) cameraShake = FindObjectOfType<CameraShake>();
}
public void SpawnExclamationMark()
{
StartCoroutine(ShowExclamationMark());
}
private IEnumerator ShowExclamationMark()
{
// block concurrent routine call
if(isShowingExclamation) yield brake;
// set flag blocking concurrent routines
isShowingExclamation = true;
// NOTE: Also for this one you might want to rather have a flag
// multiple enemy instances might call this so you get concurrent coroutines also here
StartCoroutine(cameraShake.Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, spawnPos.position, Quaternion.identity);
if (exclamationMarkAudio) Instantiate(exclamationMarkAudio, spawnPos.position, Quaternion.identity);
yield return new WaitForSeconds(1);
var children = new List<GameObject>();
foreach (var child in transform.ToList()) children.Add(child.gameObject);
children.ForEach(child => Destroy(child));
// give the flag free
isShowingExclamation = false;
}
}
Try this;
if (transform.Find("ExclamationMark"))
{
var exMark = transform.Find("ExclamationMark").gameObject.GetComponent<ExclamationMarkSpawn>();
exMark.SpawnExclamationMark(transform.position); //Add transform.position here
}
public void SpawnExclamationMark(Vector3 EnemyPos)
{
StartCoroutine(GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraShake>().Shake(0.2f, 0.2f, 0.2f));
Instantiate(exclamationMark, EnemyPos, Quaternion.identity);
if (exclamationMarkAudio)
Instantiate(exclamationMarkAudio, EnemyPos, Quaternion.identity);
StartCoroutine(DestroyExclamationMark());
}

How do I access an updated variable between two scripts in Unity with C#?

Hopefully this isn't too much detail, I'm not used to asking programming questions.
I'm attempting to do the 3D Video Game Development with Unity 3D course that's on Udemy, though using C# instead of Javascript. I just finished up the tutorial that involves creating a space shooter game.
In it, a shield is created by the user when pressing a button. The shield has a "number of uses" variable that does not actually get used by the time the tutorial has finished. I'm trying to add it in, and have successfully managed to implement it so that with each use, we decrease the number of uses remaining, and no longer are able to instantiate the shield once that number is <=0.
This variable is stored on the player, and if I print it from the player, it returns the value I would expect.
However, I'm using a separate SceneManager.cs (this is where the tutorial placed the lives, and score, and timer variables ) where I print numbers into the GUI. My problem is that I cannot get my number of uses variable to stay current when I try to print it from the scene manager... it registers the initial value, but doesn't update after that.
Here is the Player Script
using UnityEngine;
using System.Collections;
public class player_script : MonoBehaviour {
// Inspector Variables
public int numberOfShields = 2; // The number of times the user can create a shield
public Transform shieldMesh; // path to the shield
public KeyCode shieldKeyInput; // the key to activate the shield
public static bool shieldOff = true; // initialize the shield to an "off" state
public int NumberOfShields
{
get{return numberOfShields;}
set{numberOfShields = value;}
}
// Update is called once per frame
void Update()
{
// create a shield when shieldKey has been pressed by player
if (Input.GetKeyDown (shieldKeyInput)) {
if(shieldOff && numberOfShields>0)
{
// creates an instance of the shield
Transform clone;
clone = Instantiate (shieldMesh, transform.position, transform.rotation) as Transform;
// transforms the instance of the shield
clone.transform.parent = gameObject.transform;
// set the shield to an on position
shieldOff = false;
// reduce the numberOfShields left
numberOfShields -=1;
}
}
print ("NumberOfShields = " + NumberOfShields);
}
public void turnShieldOff()
{
shieldOff = true;
}
}
when I run "print ("NumberOfShields = " + NumberOfShields);" I get the value I expect. (astroids trigger the turnShieldOff() when they collide with a shield.
Over in my Scene Manager however... this is the code I'm running:
using UnityEngine;
using System.Collections;
public class SceneManager_script : MonoBehaviour {
// Inspector Variables
public GameObject playerCharacter;
private player_script player_Script;
private int shields = 0;
// Use this for initialization
void Start ()
{
player_Script = playerCharacter.GetComponent<player_script>();
}
// Update is called once per frame
void Update ()
{
shields = player_Script.NumberOfShields;
print(shields);
}
// GUI
void OnGUI()
{
GUI.Label (new Rect (10, 40, 100, 20), "Shields: " + shields);
}
}
Any idea what I'm doing wrong that prevents shields in my SceneManager script from updating when NumberOfShields in my player_script updates?
I think you might have assigned a prefab into playerCharacter GameObject variable instead of an actual in game unit. In this case it will always print the default shield value of prefab. Instead of assigning that variable via inspector try to find player GameObject in Start function. You can for example give your player object a tag and then:
void Start() {
playerCharacter = GameObject.FindGameObjectWithTag("Player");
}

Categories