How to assert a spawn is "filled"? - unity3d

I have two spawn spots where a player will show up upon connection.
I need that, when one of the players connects on one of the spots, any other player will always spawn at the other spot.
Here's some visual in case it helps: https://goo.gl/Y0ohZC
Thanks in advance,
IC

You can add those two possible spawn spots as empty game objects.
Then I'd make a boolean array and set its states to true or false depending on if the spot is occupied. The spots aren't directly stored in this array so you should make another array.
In C# this would approximately look like this:
public GameObject[] Spots = new GameObject[2]; //Drag the spots in here (In the editor)
bool[] OccupiedSpawnSpots = new bool[2];
int mySpotNumber = -1;
void Start() {
PhotonNetwork.ConnectUsingSettings ("v1.0.0");
}
void OnGUI() {
GUILayout.Label (PhotonNetwork.connectionStateDetailed.ToString ());
}
void OnJoinedLobby()
{
Debug.Log ("Joined the lobby");
PhotonNetwork.JoinRandomRoom ();
}
void OnPhotonRandomJoinFailed()
{
Debug.Log ("No room found. Creating a new one...");
PhotonNetwork.CreateRoom (null);
}
void OnPhotonPlayerConnected(PhotonPlayer player)
{
//If we are the MasterClient, send the list to the connected player
if (PhotonNetwork.isMasterClient)
GetComponent<PhotonView>().RPC("RPC_SendList", player, OccupiedSpawnSpots);
}
void OnApplicationQuit()
{
//If I am outside and others want to connect, my spot shouldn't be still set as occupied:
//I mean if the application quits I'm of course going to be disconnected.
//You have to do this in every possibility of getting disconnected or leaving the room
OccupiedSpawnSpots[mySpotNumber] = false;
//Send the changed List to the others
GetComponent<PhotonView>().RPC("RPC_UpdateList", PhotonTargets.All, OccupiedSpawnSpots);
}
[RPC]
void RPC_SendList(bool[] ListOfMasterClient)
{
OccupiedSpawnSpots = ListOfMasterClient;
//Get the free one
if (OccupiedSpawnSpots[0] == false)
{
//Spawn your player at 0
SpawnMyPlayer(0);
//Set it to occupied
OccupiedSpawnSpots[0] = true;
}
else //so the other must be free
{
//Spawn your player at 1
SpawnMyPlayer(1);
//Set it to occupied
OccupiedSpawnSpots[1] = true;
}
//Send the changed List to the others
GetComponent<PhotonView>().RPC("RPC_UpdateList", PhotonTargets.All, OccupiedSpawnSpots);
}
[RPC]
void RPC_UpdateList(bool[] RecentList)
{
OccupiedSpawnSpots = RecentList;
}
void SpawnMyPlayer(int SpotNumber) {
// Check if spawnspots are set OK
if (Spots == null) {
Debug.LogError ("SpawnSpots aren't assigned!");
return;
}
mySpotNumber = SpotNumber;
// The player object for the network
GameObject myPlayerGO = (GameObject)PhotonNetwork.Instantiate ("PlayerController",
Spots[SpotNumber].transform.position,
Spots[SpotNumber].transform.rotation, 0);
// Enable a disabled script for *this player only, or all would have the same camera, movement, etc
//((MonoBehaviour)myPlayerGO.GetComponent("FPSInputController")).enabled = true;
// Set a camera just for this player
//myPlayerGO.transform.FindChild ("Main Camera").gameObject.SetActive (true);
//standbyCamera.SetActive(false);
}
Note: If you have more than two players, it gets a lot more difficult. This code isn't optimized so maybe you'll find a better one, but it should give you the right idea.
Please ask if anything isn't clear to you...

Related

how to make the end of the game in pakman when all the dots have been eaten?

How to make the end of the game in pakman when all the dots have been eaten?
This is the end game code now
void OnTriggerEnter2D(Collider2D co)
{
if (co.name == "PacMan")
{
Destroy(co.gameObject);
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
This is the point eating code
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
If what you're asking is "how do I let the game know the level is over and trigger the end" then just have a variable to hold how many dots are in the level, and every time you eat one and that trigger collider fires, have a counter go up. When the counter equals the total, level ends.
In your class for the dots you could use something like
public class YourDotClass : MonoBehaviour
{
// Keeps track of all currently existing instances of this class
private readonly static HashSet<YourDotClass> _instances = new HashSet<YourDotClass>();
private void Awake ()
{
// Register yourself to the instances
_instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the instances
_instances.Remove(this);
// Check if any instance still exists
if(_instances.Count == 0)
{
// => The last dot was just destroyed!
EndMenu.SetActive(true);
GameObject.Find("EndGameConvas/EndGamePanel/Score").GetComponent<Text>().text = GameObject.Find("Canvas/Score").GetComponent<Text>().text;
Time.timeScale = 0;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
}
}
}
However, you should really rethink/restructure your code and think about who shall be responsible for what.
Personally I would not like the dots be responsible for increasing scores and end the game .. I would rather have a component on the player itself, let it check the collision, increase its own points and eventually tell some manager to end the game as soon as all dots have been destroyed ;)
I think you could use something like this. Just store all of the pacdots in an array and once the array is empty you could end the game.
GameObject[] pacdots = GameObject.FindGameObjectsWithTag("pacdot");
void OnTriggerEnter2D(Collider2D collision)
{
// pacman collided with a dot
if (collision.name == "PacMan")
{
Destroy(gameObject);
GameObject.Find("Canvas/Score").GetComponent<Score>().ScoreChange(1);
if (pacdots.length == 0)
{
// All dots hit do something
}
}
}

How do I spawn only 1 apple on collision

I am new to unity as a whole and decided to create small simple snake game as a test. I managed to create movement, the problem now is I want to eat the apple first, then wait till I eat the apple, then spawn a new one.
So far I managed to make it wait till I eat the first apple but then it spawns a lot of apples all at once, how do I make it that It waits till I eat the apple then spawns one apple and then another apple so far and forth.
I made only 1 apple spawn in a set location so far because I wanted to test it out before spawning in another location.
Here is the code I got so far
void OnTriggerEnter2D(Collider2D other)
{
Destroy(gameObject);
}
public void checkApple()
{
if (GameObject.Find("Apple"))
{
Debug.Log("The apple has not been eaten");
}
else
{
Debug.Log("The apple has been eaten!");
GameObject a = Instantiate(Apple) as GameObject;
a.transform.position = new Vector2(-10.26f, 3.66f);
}
}
void Update()
{
checkApple();
}
While there are better methods to handle your problem than to search your entire scene for an object name a solution to your problem would look as followed:
Currently you instantiate a new Object after you destroyed the old one, while your prefab or object might be called "Apple" the new Instance will be called "Apple(Clone)" and as you are only searching for the object that is called "Apple" and not for an object that contains the name Apple it will never find an object.
To avoid that you would need to set the name of the newly created object. For that you only need to rename the created object
if (GameObject.Find("Apple"))
{
Debug.Log("The apple has not been eaten");
}
else
{
Debug.Log("The apple has been eaten!");
GameObject a = Instantiate(Apple) as GameObject;
a.name = "Apple";
a.transform.position = new Vector2(-10.26f, 3.66f);
}
However if your final goal is to "create a new apple on a new position" it would be better to just move the object to the new position when your snake collides with the apple, if you dont want the apple to instantly spawn you could create a cooldown timer something like this:
void OnTriggerEnter2D(Collider2D other)
{
//when needed make sure that the snake only eats apples
//if(other.name.Equals("Apple")){
//for a random position you could use
// Apple.transform.position = new Vector2(Random.Range(-10.0f, 10.0f),Random.Range(-10.0f, 10.0f));
Apple.transform.position = new Vector2(-10.26f, 3.66f);
Apple.SetActive(false);
cooldown = 2f;
inactiveApple = true;
//}
}
public void checkApple()
{
cooldown -= Time.deltaTime;
if(cooldown <= 0 && inactiveApple)
{
Apple.SetActive(true);
inactiveApple = false;
}
}
Of course in the end you would need to check first if the new position is occupied by the snake befor you spawn it there, but that's off topic.
You can use invoke to wait;
float waitingTime = 1f;
void Start()
{
Spawn();
}
void OnTriggerEnter2D(Collider2D other)
{
Destroy(gameObject);
Invoke("Spawn", waitingTime);
}
void Spawn()
{
Debug.Log("The apple has been spawned!");
GameObject a = Instantiate(Apple) as GameObject;
a.transform.position = new Vector2(-10.26f, 3.66f);
}

How to check that object not hidden by another object?

I make game with isometric view. When player comes into the house the roof of it hides and player can interact with NPCs, items, etc. But now it can interact with it even when roof is visible. How to detect that item is hidden by house roof or wall or another object?
void Update() {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
if (Input.GetMouseButtonDown(0)) {
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
}
//Interact ...
}
}
}
You can simply check the distance between the hit wall/roof and NPC, from the ray-cast origin (camera). Like so:
private Camera cameraRef;
private void Awake() {
// P.S: Cache the 'Camera.main', calls to it can be expensive.
cameraRef = Camera.main;
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Ray ray = cameraRef.ScreenPointToRay(Input.mousePosition);
RaycastHit[] hits = Physics.RaycastAll(ray, Mathf.Infinity);
foreach (RaycastHit hit in hits) {
if (hit.collider.tag != "NPC") {
continue;
} else if (RaycastHitRoofOrWallFirst(hits, hit.collider.gameObject)) {
// This NPC is hidden behind a roof/wall.
continue;
}
// Interaction...
}
}
}
/// <summary>
/// Check if a target object is being hidden behind a roof/wall.
/// </summary>
/// <param name="hits">The hits that the raycast gotten.</param>
/// <param name="targetObject">The gameobject to check against.</param>
/// <returns>Return true if the target object is hidden, false if not.</returns>
private bool RaycastHitRoofOrWallFirst(RaycastHit[] hits, GameObject targetObject) {
foreach (RaycastHit hit in hits) {
if (hit.collider.CompareTag("roof") || hit.collider.CompareTag("wall")) {
float distanceFromCameraToObstacle = Vector3.Distance(cameraRef.transform.position, hit.collider.transform.position);
float distanceFromCameraToNPC = Vector3.Distance(cameraRef.transform.position, targetObject.transform.position);
// Check if the NPC is closer to the camera (raycast origin)
// compared to the roof or wall.
if (distanceFromCameraToObstacle < distanceFromCameraToNPC) {
// The roof/wall is closer to the camera (raycast origin)
// compared to the NPC, hence the NPC is blocked by the roof/wall
return true;
}
}
}
return false;
}
Here is a small visual diagram of what it should check for:
Or just use simple raycast...
If possible depending on the context, instead of using Physics.RaycastAll, you can use Physics.Raycast.
It returns the first object that the ray-cast hits.
Adding to this answer an alternative could maybe also be using OnBecameVisible
OnBecameVisible is called when the object became visible by any Camera.
This message is sent to all scripts attached to the Renderer.
and OnBecameInvisible
OnBecameInvisible is called when the Renderer is no longer visible by any Camera.
This message is sent to all scripts attached to the Renderer.
OnBecameVisible and OnBecameInvisible are useful to avoid computations that are only necessary when the object is visible.
For activating and deactivating the according NPC's colliders so the Raycast anyway will only work on visible objects in the first place.
Like on the NPCs have a script
public class InteractableController : MonoBehaviour
{
// you can also reference them via the Inspector
public Collider[] colliders;
private void Awake()
{
// pass in true to also get inactive components
if(colliders.Length = 0) colliders = GetComponentsInChildren<Collider>(true);
}
private void OnBecameInvisible()
{
foreach(var collider in colliders)
{
collider.enabled = false;
}
}
private void OnBecameVisible()
{
foreach(var collider in colliders)
{
collider.enabled = true;
}
}
}
However
Note that object is considered visible when it needs to be rendered in the Scene. It might not be actually visible by any camera, but still need to be rendered for shadows for example. Also, when running in the editor, the Scene view cameras will also cause this function to be called.

How do I stop my objects from going through walls when I pick them up and drop them down?

When I pick an object up I can glitch it through the map making it fall out of the world. This happens when I pick it up and drop the object half way through the floor. The outcome I receive is not what I was expecting what can I do to fix this. Also yes the colliders and rigidbody's are setup correctly.
public GameObject PressEtoInteractText;
public bool pickup, inrange;
public Collider Playercol;
public Vector3 guide;
private GameObject temp;
private Rigidbody rb;
void Update()
{
if (Input.GetKeyDown(KeyCode.E) && inrange == true)
{
PressEtoInteractText.SetActive(false);
pickup = true;
}
if (Input.GetMouseButtonDown(0) && pickup == true)
{
pickup = false;
Playercol.isTrigger = true;
}
UpdatePickUpFollow();
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Interact")
{
PressEtoInteractText.SetActive(true);
temp = other.gameObject;
inrange = true;
}
if (other.gameObject.tag == "Interact" && temp.transform.position == guide)
{
return;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.name != "Interact")
{
PressEtoInteractText.SetActive(false);
inrange = false;
}
}
public void PickUp()
{
rb = temp.GetComponent<Rigidbody>();
rb.MovePosition(transform.position += guide);
Playercol.isTrigger = false;
}
public void UpdatePickUpFollow()
{
if (pickup == true)
{
PickUp();
}
}
If you deactivate the objects collider with tempCol.enabled = false; it will not register any collisions and you can just push or pull it through walls all day. Make sure to remove that from your script.
Also, using MoveTowards can cause issues with collision detection. In my experience it is best to use AddForce or MovePosition to move the Rigidbody component instead of modifying the Transform directly. Try Rigidbody.MovePosition(Vector3 position). Maybe this works better for you.
Here is a link to the documentation page. It's basically your exact use case:
Rigidbody.MoveTowards Unity Docs
(Hint: notice how they use FixedUpdate instead of regular Update. You should also always do this when working with Rigidbodies because it is synced with physics updates.)
//Edit:
This is a little implementation of your code in a cleaner and hopefully correct way. I have not tested this and there is propably something I missed. Use this as a reference to build your own working solution.
//set in inspector
public Transform guidePosition;
public GameObject pressEToInteractText;
private bool isObjectInRange;
private GameObject inRangeObject;
private bool isObjectPickedUp;
private Rigidbody pickedUpRigidBody;
/* check if button is pressed.
* If no object is in hand -> pick up;
* if object is in hand -> drop;
*/
private void FixedUpdate()
{
if (Input.GetKeyDown(KeyCode.E) && isObjectInRange && !isObjectPickedUp)
{
PickupObject();
}
if(Input.GetKeyDown(KeyCode.E) && isObjectPickedUp)
{
DropObject();
}
if (isObjectPickedUp)
{
PickedUpObjectFollow();
}
}
//save references
private void PickupObject()
{
pickedUpRigidBody = inRangeObject.GetComponent<Rigidbody>();
isObjectPickedUp = true;
}
//remove saved references
private void DropObject()
{
isObjectPickedUp = false;
pickedUpRigidBody = null;
}
//move position to guidePosition
private void PickedUpObjectFollow()
{
pickedUpRigidBody.MovePosition(guidePosition.position);
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Interact") && !isObjectPickedUp)
{
pressEToInteractText.SetActive(true);
isObjectInRange = true;
inRangeObject = other.gameObject;
}
}
private void OnTriggerExit(Collider other)
{
if(other.CompareTag("Interact") && other.gameObject == inRangeObject)
{
pressEToInteractText.SetActive(false);
isObjectInRange = false;
inRangeObject = null;
}
}
You could do multiple things. Unfortunately the way VR tracks the hands and there is no way to stop ur hands in real life your hand will defy the physics in the application.
I would either make the item drop from the users hand when it gets close enough to a certain area that you dont want it going through.
Or you can make it that if it does pass through a space you can report the item right above the floor it went through.
Ive looked long and hard for an answer to this and it doesnt seem like it can at this point be blocked from moving through if its currently grabbed.

(Unity2D) Increasing and Decreasing Stat Bar Issues

I have several stat bars, attached to objects, that shorten in length over time. If the PC collides with the object, the stat bar should steadily increase in length instead.
Instead, the bar lengthens to its fullest extent, and doesn't begin to shorten again until the PC interacts with another stat bar object; at which point the bar goes back to the length it had been previously.
This is the code for the progression of the bars:
void Update() {
if (contact) {
Increase();
Debug.Log("increasing");
} else if (contact == false) {
Decrease();
Debug.Log("decreasing");
}
}
void Decrease() {
if (filled > 0) {
filled -= 0.006f;
timerBar.fillAmount = filled / maxTime;
}
}
void Increase() {
if (filled < maxTime) {
timerBar.fillAmount = (filled += 0.006f);
}
}
In a separate script, I am keeping the conditions for the definitions of "contact".
void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
barProg.contact = true;
Debug.Log("is touching");
}
}
void OnCollisionExit2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
barProg.contact = false;
Debug.Log("is not touching");
}
}
Here are the images regarding the script attachments and colliders:
"yellowBar (progBar)" etc. are referring to the scripts attached to the canvas images of the statbars. So the little yellow depleting line (a statbar) is "yellowBar", and it references the script attached to the individual stat bar (progBar).
Shown below is an example of what I'm talking about. I refill the blue bar, at which point it stays the same until I go to refill the yellow bar, at which point the blue bar goes back to the amount it had been when I refilled it.
your going to want something like this, this is pseudo so bear with me....
bool healing=false;
public void OnTriggerEnter2D(Collisider2D col)
{
if(col.tag=="Player")
{
healing = true;
}
}
public void OnTriggerExit2D(Collisider2D col)
{
if(col.tag=="Player")
{
healing = false;
}
}
public void Update()
{
if(healing)
{
if(filled<maxTime)
{
timerBar.fillAmount += 0.006f;
}
}
}
bnwhose health your increasing, so each one needs the script.
you PC must be tagged "Player" to catch the collision.
your objects will need colliders, i assume they already have them.
and either the object or pc, will need a rigidbody. it seem you have most of this already. the reason this works is beacuae of the boolean. it sets it true when you enter the trigger and false when you leave it. so its only tru for that item while your touching that item, then inupdate while its true, we increase the healthbar.
putting this script on each of your objects directly will keep them from conflicting with each other, or updating the rong one.
good luck!
I have several suggestions to hopefully get you on the right track. This script should be attached the objects that fill up the status bars. you need to write a separate script to actually adjust the status bars based on the fillpercentage, but I'll leave that to you. This script is ONLY responsible for tracking how full, not displaying anything. This is good practice to separate your logic from your UI, since you can change one without affecting the other.
Also, you should avoid using "magic numbers" in your code. Make variables instead. see https://en.wikipedia.org/wiki/Magic_number_(programming)
Furthermore, you can switch between increasing and decreasing using a bool, rather than calls to separate methods. This keeps the logic in the same place and makes changing it in the future easier.
public class ColoredObject : Monobehaviour {
//bool to switch increasing and decreasing
bool increasing = false;
//can set this value in inspector to adjust speed it fills
//can even have different fill speeds for each color
public float fillSpeed;
//stores how full the bar is
float percentageFull = 100;
//if player contacts, start increasing
void OnCollisionEnter2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
increasing = true;
}
}
//if player exits, start decreasing
void OnCollisionExit2D(Collision2D collision) {
if (collision.gameObject.tag == "Player") {
increasing = false;
}
}
void Update () {
UpdateFill()
}
public void UpdateFill() {
if (increasing) {
//fill based on time passed since last frame
percentageFull += fillSpeed * Time.deltatime;
}
else {
//empty based on time passed since last frame
percentageFull -= fillSpeed * Time.deltatime;
}
// keep it between 0 and 100%
percentageFull = Mathf.Clamp(percentageFull, 0f, 100f);
}
}
Let me know how it goes, and I can try to help you fine-tune it if this wasn't exactly what you wanted.
EDIT: if you want different increasing and decreasing speed just add another variable, and replace fillSpeed in the decreasing section.