Instantiate making multiple clones - unity3d

I'm making a building system in my game and the basic mechanic works but for some reason whenever I place down the object it adds incrementally more clones with every placement. The first placement creates one clone, the second placement creates two clones, the third, three clones, and so on. I fixed it for one object by moving it out of an empty game object that had all the building prefabs in the hierarchy but it only works for that.
using UnityEngine;
using UnityEngine.AI;
public class GroundPlacementController : MonoBehaviour
{
[SerializeField]
private GameObject placeableObjectPrefab;
[SerializeField]
private KeyCode newObjectHotkey = KeyCode.A;
public NavMeshObstacle nav;
private GameObject currentPlaceableObject;
private float mouseWheelRotation;
private void Update()
{
HandleNewObjectHotkey();
nav = GetComponent<NavMeshObstacle>();
if (currentPlaceableObject != null)
{
MoveCurrentObjectToMouse();
RotateFromMouseWheel();
ReleaseIfClicked();
}
}
private void HandleNewObjectHotkey()
{
if (Input.GetKeyDown(newObjectHotkey))
{
if (currentPlaceableObject != null)
{
Destroy(currentPlaceableObject);
}
else
{
currentPlaceableObject = Instantiate(placeableObjectPrefab);
}
}
}
private void MoveCurrentObjectToMouse()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
{
currentPlaceableObject.transform.position = hitInfo.point;
currentPlaceableObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
currentPlaceableObject.GetComponent<NavMeshObstacle>().enabled = false;
}
}
private void RotateFromMouseWheel()
{
Debug.Log(Input.mouseScrollDelta);
mouseWheelRotation += Input.mouseScrollDelta.y;
currentPlaceableObject.transform.Rotate(Vector3.up, mouseWheelRotation * 90f);
}
private void ReleaseIfClicked()
{
if (Input.GetMouseButtonDown(0))
{
currentPlaceableObject.GetComponent<NavMeshObstacle>().enabled = true;
print("disabled");
currentPlaceableObject = null;
print("removed prefab");
}
}
}

Related

(Unity3d) **nullreferenceexception** thrown when *instance.lockpos()* is called; plus how to make an object maintain its starting rotation

I've run up against a little problem, and I can't seem to find the solution.
I'm putting together a game in Unity3d that includes these gauntlets that can control the environment, i.e. raise downed bridges, open doors, etc. First, I'm just trying to allow the gauntlets to access objects from afar, and I found a telekinesis tutorial online that I am trying to pseudo-repurpose.
This script is supposed to raise a bridge (hence BridgeMover) until it hits a specific position and then lock it in place. Unfortunately, I'm throwing a null reference exception when the bridge hits the "lockbox" object, which is a small cube on the edge of the bridge ramp leading up to the bridge. I know this is simple, but I can't quite conceptualize it, and I've just been staring at it for a few days. This is my big issue, and any help would be appreciated.
In other news, I'm also trying to have the bridge rise as a flat plane instead of following the rotation of the cast ray, and would appreciate any advice about that as well, but I would understand if y'all want me to suffer through on that one. I'm pretty sure it has something to do with the Lerp, which I still don't really understand.
In the hierarchy, I have a handhold object, which is a child of the gauntlet object, which is a child of the camera, which is a child of the player. So
Player
Camera
Gauntlet
handHold
The lockbox object is a child of the ramp that it's attached to. So:
Ramp
Lockbox
Also, this isn't a criticism of the tutorial; I'm just swimming slightly out of my depths here. The tutorial, for those interested, can be found here.
public class HandController : MonoBehaviour
{
public static BridgeMover instance;
public Camera mainCamera;
Rigidbody rbOfHeldObject;
GameObject heldObject;
public GameObject handHold;
bool holdsObject = false;
float interactionDistance = 100;
float slideSpeed = 2;
Quaternion currentRotation;
private void Start()
{
mainCamera.GetComponent<Camera>();
}
private void Update()
{
//HandMove();
if (Input.GetMouseButtonDown(0) && !holdsObject)
Raycast();
if (Input.GetMouseButtonUp(0) && holdsObject)
ReleaseObject();
if (holdsObject && instance.LockPos())
LockObject();
if(holdsObject)
{
if (CheckDistance() >= 0.1f)
MoveObjectToPosition();
}
}
private void Raycast()
{
Ray ray = mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
RaycastHit hit;
if (Physics.Raycast(ray, out hit, interactionDistance))
{
if(hit.collider.CompareTag("Bridge"))
{
heldObject = hit.collider.gameObject;
heldObject.transform.SetParent(handHold.transform);
holdsObject = true;
rbOfHeldObject = heldObject.GetComponent<Rigidbody>();
rbOfHeldObject.constraints = RigidbodyConstraints.FreezeAll;
}
}
}
public float CheckDistance()
{
return Vector3.Distance(heldObject.transform.position, handHold.transform.position);
}
private void MoveObjectToPosition()
{
currentRotation = heldObject.transform.rotation;
heldObject.transform.position = Vector3.Lerp(heldObject.transform.position,
handHold.transform.position, slideSpeed * Time.deltaTime);
heldObject.transform.rotation = currentRotation;
}
private void ReleaseObject()
{
rbOfHeldObject.constraints = RigidbodyConstraints.None;
heldObject.transform.parent = null;
heldObject = null;
holdsObject = false;
}
private void LockObject()
{
rbOfHeldObject.useGravity = false;
heldObject.transform.parent = null;
heldObject = null;
holdsObject = false;
}
}
And then in the BridgeMover class:
public class BridgeMover : MonoBehaviour
{
public static BridgeMover instance;
bool lockPos = false;
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "LockBox")
{
lockPos = true;
}
}
public bool LockPos()
{
return lockPos;
}
}
Any help would be appreciated. Also, if any further information is needed, let me know and I'll add it.
It looks like you're trying to implement a singleton instance for BridgeMover. Unfortunately, you don't have your instance defined. Here's the standard Unity method for creating a singleton implemented in your BridgeMover code:
public class BridgeMover : MonoBehaviour
{
public static BridgeMover instance;
bool lockPos = false;
void Awake()
{
if(instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "LockBox")
{
lockPos = true;
}
}
public bool LockPos()
{
return lockPos;
}
}
Additionally, in your HandController class you should not declare a static BridgeMover. Instead, reference the static instance properly:
public static BridgeMover instance;
if (holdsObject && BridgeMover.instance.LockPos())

How can I check FindGameObjectsWithTag during runtime?

I'm having troubles with my enemy NavMeshAgent AI, First it searches for GameObjects tagged with "Defenses", then I set the first destination based in how close the defense is by my enemy, when the defense is destroyed, the array value is increased by one and it changes the destination to the next defense.
My problem is, my player can create (Instance) new defenses during the game, when all my defenses are destroyed, my enemy turns crazy, so, i need a way to add those new defenses to my array, below is my enemy script.
public class NavMeshEnemy : MonoBehaviour
{
[Header("References", order = 0)]
[SerializeField] NavMeshAgent enemyAgent;
[Space(10)]
[SerializeField] GameObject[] destinations;
[Space(10)]
[SerializeField] float distanceObjects;
[SerializeField] int arrayElements = 0;
void Awake()
{
enemyAgent = GetComponent<NavMeshAgent>();
destinations = GameObject.FindGameObjectsWithTag("Defenses");
}
void Start()
{
destinations = destinations.OrderBy((d) => (d.transform.position - this.transform.position).sqrMagnitude).ToArray();
}
void Update()
{
distanceObjects = Vector3.Distance(this.transform.position, destinations[arrayElements].transform.position);
enemyAgent.destination = destinations[arrayElements].transform.position;
CheckArray();
}
void CheckArray()
{
if (destinations[arrayElements].gameObject.activeInHierarchy == false)
{
if (arrayElements < destinations.Length - 1)
{
arrayElements++;
enemyAgent.destination = destinations[arrayElements].transform.position;
}
else
{
arrayElements = 0;
}
}
}
}
Thank you for reading! :)
I would rather implement a component on your prefab with a static list like e.g.
public class NavMeshDestination : MonoBehaviour
{
public static HashSet<Transform> existingDestinations = new HashSet<Transform>();
private void Awake()
{
if(!existingDestinations.Contains(this)) existingDestinations.Add(this);
}
private void OnDestroy()
{
if(existingDestinations.Contains(this)) existingDestinations.Remove(this);
}
}
And then don't even go by tag but simply do
public class NavMeshEnemy : MonoBehaviour
{
[Header("References", order = 0)]
[SerializeField] NavMeshAgent enemyAgent;
[Space(10)]
[SerializeField] float distanceObjects;
[SerializeField] int arrayElements = 0;
void Awake()
{
if(!enemyAgent) enemyAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
destinations = NavMeshDestination.existingDestinations.OrderBy(d => (d.transform.position - this.transform.position).sqrMagnitude).ToArray();
distanceObjects = Vector3.Distance(this.transform.position, destinations[arrayElements].transform.position);
enemyAgent.destination = destinations[arrayElements].transform.position;
CheckArray();
}
void CheckArray()
{
if (destinations[arrayElements].gameObject.activeInHierarchy == false)
{
if (arrayElements < destinations.Length - 1)
{
arrayElements++;
enemyAgent.destination = destinos[arrayElements].transform.position;
}
else
{
arrayElements = 0;
}
}
}
}
Note however that this still won't fix your issues with no available destination. So you probably should stop the execution if the array is empty like
if(NavMeshDestination.existingDestinations.Count == 0)
{
// Stop the enemyAgent
return;
}

Unity network transform problem - Client spawning objects at wrong location

I'm working on a Fortnite-esque game for Unity.
Player spawns in, has ability to spawn cubes to make a "base".
Everything works perfectly well in mono but I'm having a strange issue with networking. On the server, my player can spawn the cubes perfectly according to the Raycast hitpoint while on the client, even though the player is a clone of the Player prefab, the spawned objects always either end up at the world origin, rather than Raycast hitpoint -or- if I remove if (!isPlayLocal) {return;} from the player's script containing Raycast info, the cube spawns inaccurately and without its corresponding material.
I'll try and pinpoint the code so I can place it here but I imagine it could be a number of things.
Local Player Auth is checked off on spawn prefabs and all prefabs have been registered in Network Manager.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class BuildingSystemNew : NetworkBehaviour
{
[SerializeField] private Camera playerCamera;
[SerializeField] private GameObject blockTemplatePrefab;
[SerializeField] private GameObject blockPrefab;
[SerializeField] private Material templateMaterial;
[SerializeField] private LayerMask buildableSurfacesLayer;
private bool buildModeOn = false;
private bool canBuild = false;
private bool crossHairOn = false;
private BlockSystem bSys;
public Texture2D crosshairImage;
private int blockSelectCounter = 0;
private GameObject weapon;
private Vector3 buildPos;
private GameObject currentTemplateBlock;
private void Start()
{
bSys = GetComponent<BlockSystem>();
}
private void Update()
{
if (isLocalPlayer == false)
return;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
if (Input.GetKeyDown("e"))
{
buildModeOn = !buildModeOn;
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
}
if (Input.GetKeyDown("r"))
{
blockSelectCounter++;
if (blockSelectCounter >= bSys.allBlocks.Count) blockSelectCounter = 0;
}
if (buildModeOn)
{
RaycastHit buildPosHit;
if (Physics.Raycast(playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)), out buildPosHit, 10, buildableSurfacesLayer))
{
Vector3 point = buildPosHit.point;
buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));
canBuild = true;
}
else
{
Destroy(currentTemplateBlock.gameObject);
canBuild = false;
}
}
if (!buildModeOn && currentTemplateBlock != null)
{
Destroy(currentTemplateBlock.gameObject);
canBuild = false;
}
if (canBuild && currentTemplateBlock == null)
{
currentTemplateBlock = Instantiate(blockTemplatePrefab, buildPos, Quaternion.identity);
currentTemplateBlock.GetComponent<MeshRenderer>().material = templateMaterial;
}
if (canBuild && currentTemplateBlock != null)
{
currentTemplateBlock.transform.position = buildPos;
if (Input.GetMouseButtonDown(0))
{
CmdPlaceBlock();
}
else if (Input.GetMouseButtonDown(1))
{
CmdDestroyBlock();
}
}
}
[Command]
public void CmdPlaceBlock()
{
GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
[Command]
private void CmdDestroyBlock()
{
RaycastHit hit;
Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
var objectHit = hit.collider.gameObject;
if (hit.collider.gameObject.tag == "Block")
{
Destroy(objectHit);
}
}
}
void OnGUI()
{
if (crossHairOn == true)
{
float xMin = (Screen.width / 2) - (crosshairImage.width / 2);
float yMin = (Screen.height / 2) - (crosshairImage.height / 2);
GUI.DrawTexture(new Rect(xMin, yMin, crosshairImage.width, crosshairImage.height), crosshairImage);
}
}
}
The Problem
You are calling
[Command]
public void CmdPlaceBlock()
{
GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
without a parameter.
A Command is called on the client but executed on the Server
=> with the local variables of the Server!
So e.g. buildPos will always have the default value 0,0,0 on the server since due to
if(!isLocalPlayer) return;
later the line
buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));
is never executed on the server. The same also applies e.g. to blockSelectCounter and probably other values your CmdPlaceBlock depends on.
Solution
You should pass your client's buildPos value (and also all other values that are different on client and server) to the server command so the server knows at which correct position the new object should be placed:
//...
if (Input.GetMouseButtonDown(0))
{
CmdPlaceBlock(buildPos);
}
//...
[Command]
public void CmdPlaceBlock(Vector3 spawnPosition)
{
GameObject newBlock = Instantiate(blockPrefab, spawnPosition, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
I added only an example for the position knowing it will always be different on client and server. But it also might apply to your other values like e.g. blockSelectCounter.
Do this change for all values that have to be the ones of the client and not the ones of the server.
Note that the types that can be passed between networking methods are limited! You can't pass e.g. any component references.
The allowed argument types are;
Basic type (byte, int, float, string, UInt64, etc)
Built-in Unity math type (Vector3, Quaternion, etc),
Arrays of basic types
Structs containing allowable types
NetworkIdentity
NetworkInstanceId
NetworkHash128
GameObject with a NetworkIdentity component attached.
Additional Hints
For readability and amount of lines you should change things like e.g.
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
to simply
// weapon.SetActive(!buildModeOn);
crossHairOn = buildModeOn;
and check bools not like
if (isLocalPlayer == false)
but rather
if(!isLocalPlayer)
It simply reads/writes easier ;)

Reference to a particular Prefab clone

I am developing a simple 2D game. In game, I've created a prefab for charcaters. and I am changing sprite of prefab runtime. This all execute fine. Now I want to apply click event on a particular prefab clone and want to increase scale of prefab. I am attaching a c# script what I have did till now.
public class ShoppingManager : MonoBehaviour {
public static ShoppingManager instance;
[System.Serializable]
public class Shopping
{
public string CharacterName;
public Sprite CharacterSprite;
}
public GameObject CharacterPrefab;
public Transform CharacterSpacer;
public List<Shopping> ShoppingList;
private CharacterScript NewCharacterScript;
/*********************************************Awake()******************************************/
void Awake()
{
MakeSingleton ();
}
/******************************Create object of the script**********************************/
void MakeSingleton()
{
instance = this;
DontDestroyOnLoad (gameObject);
}
// Use this for initialization
void Start () {
LoadCharacters ();
}
void LoadCharacters()
{
foreach (var characters in ShoppingList) {
GameObject NewCharacter = Instantiate (CharacterPrefab) as GameObject;
NewCharacterScript = NewCharacter.GetComponent<CharacterScript> ();
NewCharacterScript.CharacterName = characters.CharacterName;
NewCharacterScript.Charcater.GetComponent<Image> ().sprite = characters.CharacterSprite;
NewCharacterScript.GetComponent<Button> ().onClick.AddListener (() => CharacterClicked (NewCharacterScript.CharacterName, NewCharacterScript.Charcater));
NewCharacter.transform.SetParent (CharacterSpacer, false);
}
}
void CharacterClicked(string CharacterName, GameObject Char)
{
StartCoroutine (IncreaseScale (Char));
}
IEnumerator IncreaseScale(GameObject TempCharacter)
{
int i = 5;
while (i > 0) {
yield return new WaitForSeconds (0.1f);
Vector3 TempVector = TempCharacter.GetComponent<RectTransform> ().localScale;
TempVector.x = TempVector.x + 0.2f;
TempVector.y = TempVector.y + 0.2f;
TempCharacter.GetComponent<RectTransform> ().localScale = TempVector;
i--;
}
}
}
This code triggers click event and also it increases scale but of last clone, not of clicked prefab clone. What I am missing, I can't understand. What should I correct in this. and Yeah! I am also attaching code of a script that I've added on prefab.
public class CharacterScript : MonoBehaviour {
public string CharacterName;
public GameObject Charcater;
}
create collider for your object attach the script below to it this way each object is responsible for handling its own functionalities like increasing its own size
public class characterFunctionalities: MonoBehaviour{
void OnMouseDown()
{
StartCoroutine (IncreaseScale (this.gameobject));
}
IEnumerator IncreaseScale(GameObject TempCharacter)
{
int i = 5;
while (i > 0) {
yield return new WaitForSeconds (0.1f);
Vector3 TempVector = TempCharacter.GetComponent<RectTransform> ().localScale;
TempVector.x = TempVector.x + 0.2f;
TempVector.y = TempVector.y + 0.2f;
TempCharacter.GetComponent<RectTransform> ().localScale = TempVector;
i--;
}
}
}

Unity3d Transform.LookAt also changes position

Title says it all.
I provided an NPC with the following script, which should make him look and bark at the player.
When the player comes into the NPC's reach, however, the NPC starts walking towards the player instead of just facing him.
Any thoughts?
using UnityEngine;
public class Barking : MonoBehaviour {
public AudioSource barkingAudio;
private GameObject player;
private bool barking;
void Start () {
player = GameObject.FindGameObjectWithTag("Player");
barking = false;
}
void Update () {
if (barking)
lookAtPlayer();
}
private void lookAtPlayer()
{
transform.LookAt(player.transform.position, Vector3.up);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == player)
{
barking = true;
barkingAudio.mute = false;
barkingAudio.Play();
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject == player) {
barking = false;
barkingAudio.mute = true;
barkingAudio.Stop();
}
}
}
Since I was using a Rigidbody and rotating the transform manually, there was some unexpected behaviour.
I found some code online which I could replace the Transform.LookAt method with:
var qTo = Quaternion.LookRotation(player.transform.position - transform.position);
qTo = Quaternion.Slerp(transform.rotation, qTo, 10 * Time.deltaTime);
GetComponent<Rigidbody>().MoveRotation(qTo);
This fixed my problem!