My question is in the code below, I try different methods to load the object from the Addressable, I tried referencing the object using drag and drop and only by code, which is the Loadasset and give a key to it.
My question is the Key aMMox/AmooBox.FBX is only recognised by the Addressable.LoadAsset method. But if the object is downloaded it should be stored somewhere, maybe is the location where I stored it in the asset folder in the first place? If that's true can't I just get the object by the path after confirming it's been downloaded? Because if I have many game objects, keeping LoadAsset and giving it a key seems a dumb way to do it
[SerializeField] private AssetReference playerArmatureAssetReference;
[SerializeField] private AssetRefereceAudioClip musicAssetReference;
[SerializeField] private AssetReferenceTexture2D unityLogoAsset;
private void Start()
{
Addressables.LoadAsset<GameObject>("aMMOox/AmmoBox.FBX").Completed += onLoadDone;
Addressables.InitializeAsync().Completed += Addressables_Completed;
}
void Addressables_Completed(AsyncOperationHandle<IResourceLocator> obj)
{
musicAssetReference.LoadAssetAsync<AudioClip>().Completed += (clip) =>
{
Debug.Log("Working");
var audioSource = gameObject.AddComponent<AudioSource>();
audioSource.clip = clip.Result;
audioSource.playOnAwake = false;
audioSource.loop = true;
audioSource.Play();
};
playerArmatureAssetReference.LoadAssetAsync<GameObject>().Completed += (gameobj) =>
{
Debug.Log("CloudGameObj");
var Zombie = gameobj.Result;
Instantiate(Zombie, new Vector3(0.031f, -0.023f, 0.128f), Quaternion.identity);
};
}
private void onLoadDone(AsyncOperationHandle<GameObject> obj)
{
var test = obj.Result;
Instantiate(test, new Vector3(0.031f, -0.023f, 0.128f), Quaternion.identity);
Debug.Log(test.name);
}
}
Related
i have two simple 3d cube objects a,b. so my task is to randomly disable box collider one of the object every time game start.
my initial thought is randomly generate Boolean and if it is false then disable the box collider.
You can add you cube's to array and use Random.Range(0, yourArray.Length) to get index of the cube which should be deactivated. In your case it will be some overhead but this solution will be work for different cube`s count in the future.
In code it will looks like:
// You can serialize this array and add cubes from Inspector in Editor
// or add script with this code to the parent gameobject
// and use GetComponentsInChildren<BoxCollider>() to get all colliders
var cubes = new BoxCollider[2] {firstCube, secondCube};
var cubeToDeactivationIndex = Random.Range(0, cubes.Length);
cubes[cubeToDeactivationIndex].enabled = false;
About your second question. If I understand correctly, implementation will be next:
using System;
using UnityEngine;
using UnityEngine.Assertions;
using Random = UnityEngine.Random;
// Interface need to provide limited API
// without open references to gameobject and transform of the object
public interface ICollidable
{
event Action<ICollidable> OnCollided;
int HierarchyIndex { get; }
void DisableCollider();
}
// This component you should add to the object which will collide with player
public class CollidableObject : MonoBehaviour, ICollidable
{
[SerializeField]
private Collider _objectCollider;
public event Action<ICollidable> OnCollided;
public int HierarchyIndex => transform.GetSiblingIndex();
private void Start()
{
Assert.IsNotNull(_objectCollider);
}
//Here you can use your own logic how to detect collision
//I written it as example
private void OnCollisionEnter(Collision collision)
{
if (collision.transform.CompareTag("Player")) {
OnCollided?.Invoke(this);
}
}
public void DisableCollider()
{
_objectCollider.enabled = false;
}
}
// This component should be on the parent gameobject for your `
// collidable objects. As alternative you can serialize array
// and add all collidable objects from the Inspector but in that
// case signature of the array should be CollidableObject[]
// because Unity can`t serialize interfaces
public class RamdomizedActivitySwitcher : MonoBehaviour
{
private ICollidable[] _collidableObjects;
private void Awake()
{
_collidableObjects = GetComponentsInChildren<ICollidable>(true);
foreach (var collidable in _collidableObjects)
{
collidable.OnCollided += DisableObjectWhenMatch;
}
}
private void DisableObjectWhenMatch(ICollidable collidedObject)
{
var randomIndex = Random.Range(0, _collidableObjects.Length);
if (randomIndex == collidedObject.HierarchyIndex) {
collidedObject.DisableCollider();
}
}
}
}
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())
Im using a raycast, putting its RaycastHit into a variable called raycastHit, using that to get the raycastHit.transform.name, could i then use the information from the name of the gameObject to move its position? Or if not, could i get the information from the raycastHit to instead move it straight away? (outside of the function of the raycast) I already looked for an answer, but ive been only finding results on how to move any gameObject.
EDIT1:
my current version of the void:
public RaycastHit raycastHit;
private void HandleHookShotStart()
{
if (TestInputDownHookShot())
{
int gm = LayerMask.NameToLayer("Ground");
int lwm = LayerMask.NameToLayer("LightWeight");
int layers = 1 << gm | 1 << lwm;
if (Physics.Raycast(Shoulder.transform.position, cam.transform.forward, out raycastHit, float.PositiveInfinity, layers))
{
//Hit Something
debugHitpointTransform.position = raycastHit.point;
hookshotPosition = raycastHit.point;
hookShotSize = 0f;
HookShotTransform.gameObject.SetActive(true);
HookShotTransform.localScale = Vector3.zero;
if (raycastHit.collider.gameObject.layer == gm)
{
Debug.Log("ground");
hitType = HitType.Ground;
}
else if (raycastHit.collider.gameObject.layer == lwm)
{
//Debug.Log("lightweight");
hitType = HitType.Lightweight;
goToMove = raycastHit.collider.gameObject;
string raycastHitObjectname = goToMove.name;
}
state = State.HookShotThrown;
}
}
}
EDIT2:
private void HandleHookShotGrab()
{
GameObject raycastHitObject = GameObject.Find(raycastHitObjectName);
}
error is on raycastHitObjectName
error:
CS0103 the name 'raycastHitObjectName' does not exist in the current context
You dont need to find your gameobject by name, you can add a ref to the gameobject in the script from which you would be moving the gameobject from.
public RaycastHit raycastHit;
public GameObject goToMove;//drag in to your gameObject component in unity editor
private void HandleRayCast()
{
if (Physics.Raycast(transform.position, cam.transform.forward, out raycastHit))
{
//Hit Something
goToMove.transform.position = raycastHit.point;
}
}
If you still need to find the gameObject by name, you can still get in the Start phase.
void Start() {
goToMove = GameObject.Find("yourGameObjectName");
}
Don use GameObject.Find in Updates. Use it to keep a reference to your gameObjects at the initialization phase of your Monobehaviours. In small games or playaround projects its not a big deal, but it is an "expensive" command, so needs to be used with care.
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");
}
}
}
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 ;)