Unity How to GetCompoent<Outline>() & GetCompoent<Shadow>() with a GameObject - c#

public class ABCD : MonoBehaviour
{
[SerializeField]
Outline outline;
[SerializeField]
Shadow shadow;
private void Start()
{
outline = GetComponent<Outline>();
shadow = GetComponent<Shadow>();
}
}
we knew Outline is Implementation Shadow like this.
So When I call
shadow = GetCompoent<Shadow>();
It Possible find out the Outline Compoent , because Ouline is also a Shadow.
My question is How can I get the right Compoent?
And I can't drop the compoent to keep reference.
Because My code exactly like this
//this not a monobehavior
class MyText
{
Shadow shadow;
Outline outline;
public MyText(Transfrom transfrom)
{
outline = transfrom.GetComponent<Outline>();
shadow = transfrom.GetComponent<Shadow>();
}
}
If I create Compoent to keep reference, it will use more cost.
Use GetCompoents Can slove that , Anyone have better solution?
foreach (Shadow s in GetComponents<Shadow>())
{
if (s is Outline)
{
outline = s as Outline;
}
else
{
shadow = s;
}
}

The GetComponent will always return the first available member of asked type.
You need to use Get Components to get more than one. Here's an example from https://unitygem.wordpress.com/getcomponent-in-c/
using UnityEngine;
using System.Collections;
public class Health : MonoBehaviour
{
private Force [] scripts = null;
[SerializeField] private int health = 5;
private void Start()
{
this.scripts = this.gameObject.GetComponents<Force>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
for (int i = 0; i < this.scripts.Length; i++)
{
if(this.scripts[i].ReportForce())
{
this.health += 5;
}
}
}
}
}

Related

Cant seem to get the player position with Vector3 with unity

How would i get the player position so i can drop the item about 5 units infrount of the player so they do not imeadently pick up the item after they drop it and how do i get the Vector3 position of the player as well tried in many ways and i still cant get it to work i am trying to do it here:
public void DropItem()
{
if(slotsItem)
{
slotsItem.transform.parent = null;
slotsItem.gameObject.SetActive(true);
slotsItem.transform.position = Vector3.lastpos;
}
then here is the full code as well
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
public class Slot : MonoBehaviour
{
public Item slotsItem;
public Transform player;
Sprite defaultSprite;
Text amountText;
public void CustomStart()
{
defaultSprite = GetComponent<Image>().sprite;
amountText = transform.GetChild(0).GetComponent<Text>();
this.player = GameObject.FindWithTag("Player").transform;
Vector3 lastpos = player.position;
}
public void DropItem()
{
if(slotsItem)
{
slotsItem.transform.parent = null;
slotsItem.gameObject.SetActive(true);
slotsItem.transform.position = Vector3.lastpos;
}
}
public void CheckForItem()
{
if(transform.childCount > 1)
{
slotsItem = transform.GetChild(1).GetComponent<Item>();
GetComponent<Image>().sprite = slotsItem.itemSprite;
if(slotsItem.amountInStack > 1)
amountText.text = slotsItem.amountInStack.ToString();
}
else
{
slotsItem = null;
GetComponent<Image>().sprite = defaultSprite;
amountText.text = "";
}
}
}
What is Vector3.lastpos supposed to be?
I think you rather want to store a field in the class
private Vector3 lastpos;
assign this field in
public void CustomStart()
{
...
lastpos = player.position;
}
and then later use it like
public void DropItem()
{
...
slotsItem.transform.position = lastpos;
...
}
though the question is why not simply use the current position
slotsItem.transform.position = player.position;

List array unity

I have a ObjectController script that looking for game objects and adding them to an array and another script that draws outline around those game objects. There're few tasks that I'm trying to achieve:
From ObjectController script List array I want to check what object is currently selected (clicked) so i won't be able to select (click) on other objects.
onMouseButtonDown(1) i want to clear selections (outline) from those objects.
Can you please guide me in the right direction?
I'm new to coding so please, go easy on me :D
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ObjectController : MonoBehaviour
{
public List<SpriteOutline> objects;
private void Start()
{
List<SpriteOutline> objectList = FindObjectsOfType<SpriteOutline>().ToList<SpriteOutline>();
for (int i = 0; i < objectList.Count; i++)
{
objects.Add(objectList[i]);
}
}
public List<SpriteOutline> GetList()
{
return objects;
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class SpriteOutline : MonoBehaviour {
public Color OutlineColor = Color.white;
private Color _currentColor = Color.clear;
[Range(0, 16)]
public int outlineSize = 1;
private bool clicked = false;
private SpriteRenderer spriteRenderer;
ObjectController objController;
IEnumerator Start()
{
objController = GameObject.Find("ObjectController").GetComponent<ObjectController>();
yield return new WaitForEndOfFrame();
for (int i = 0; i < objController.GetList().Count; i++)
{
Debug.Log(i);
}
}
void OnEnable() {
spriteRenderer = GetComponent<SpriteRenderer>();
UpdateOutline(true);
}
void OnDisable() {
UpdateOutline(false);
}
void Update() {
UpdateOutline(true);
onMouseDown();
}
private void onMouseDown()
{
Vector3 mousePos;
mousePos = Input.mousePosition;
mousePos = Camera.main.ScreenToWorldPoint(mousePos);
CapsuleCollider2D coll = GetComponent<CapsuleCollider2D>();
if (Input.GetMouseButtonDown(0))
{
if (coll.OverlapPoint(mousePos))
{
_currentColor = Color.clear;
if (!clicked)
{
_currentColor = OutlineColor;
clicked = true;
}
else
{
clicked = false;
}
}
}
if (Input.GetMouseButtonDown(1))
{
_currentColor = Color.clear;
}
}
void UpdateOutline(bool outline) {
MaterialPropertyBlock mpb = new MaterialPropertyBlock();
spriteRenderer.GetPropertyBlock(mpb);
mpb.SetFloat("_Outline", outline ? 1f : 0);
mpb.SetColor("_OutlineColor", _currentColor);
mpb.SetFloat("_OutlineSize", outlineSize);
spriteRenderer.SetPropertyBlock(mpb);
}
}
hatebin
hatebin
A bit strange for me that you go through a controller class just to again find all instances of the actual class ;)
First you could simplify your code a lot:
public class ObjectController : MonoBehaviour
{
// The list is already public so no need to have a getter method ...
public List<SpriteOutline> objects;
private void Start()
{
// FindObjectsOfType returns an SpriteOutline[]
// so the easiest way of converting it to a list is
objects = FindObjectsOfType<SpriteOutline>().ToList();
}
}
Then you would later simply do
FindObjectOfType<ObjectController>().objects
However this isn't even necessary!
Instead of this manager/Singleton pattern here I would rather use a static and let your object instances register themselves and let your class handle it completely itself:
public class SpriteOutline : MonoBehaviour
{
[SerializeField] private Color OutlineColor = Color.white;
[SerializeField] [Range(0, 16)] private int outlineSize = 1;
[SerializeField] private SpriteRenderer _spriteRenderer;
// Here you actually register and unregisters instances
private static readonly List<SpriteOutline> _instances = new List<SpriteOutline>();
// If even needed public read-only access
public ReadOnlyCollection<SpriteOutline> Instances => _instances.AsReadOnly();
// The current selection
private static readonly HashSet<SpriteOutline> _currentSelection = new HashSet<SpriteOutline>();
// Again if needed a public read-only access
public static HashSet<SpriteOutline> CurrentSelection => new HashSet<SpriteOutline>(_currentSelection);
// backing field for storing the actual value of selected
private bool _isSelected;
// Property which additionally updates the outline when changed
public bool IsSelected
{
get => _isSelected;
set
{
if(value == _isSelected) return;
_isSelected = value;
if(value)
{
_currentSelection.Add(this);
}
else
{
_currentSelection.Remove(this);
}
UpdateOutline(enabled, value);
}
}
private void Awake()
{
if(!_spriteRenderer) _spriteRenderer = GetComponent<SpriteRenderer>();
// Register yourself
_instances.Add(this);
}
private void OnEnable()
{
UpdateOutline (true, _isSelected);
}
private void OnDisable()
{
UpdateOutline (false, _isSelected);
}
private void OnDestroy ()
{
// Unregister yourself
_instances.Remove(this);
if(_currentSelection.Contains(this)) _currentSelection.Remove(this);
}
// This simplifies your query a lot since this is anyway only called
// If the mouse is hovering the collider of this object
private void OnMouseDown()
{
// Toggle selection of this
if (Input.GetMouseButtonDown(0))
{
IsSelected = !IsSelected;
}
}
private void Update()
{
// Deselect this
if (Input.GetMouseButtonDown(1))
{
IsSelected = false;
}
}
void UpdateOutline(bool outline, bool selected)
{
var mpb = new MaterialPropertyBlock();
_spriteRenderer.GetPropertyBlock(mpb);
mpb.SetFloat("_Outline", outline ? 1f : 0);
mpb.SetColor("_OutlineColor", selected ? OutlineColor : Color.clear);
mpb.SetFloat("_OutlineSize", outlineSize);
spriteRenderer.SetPropertyBlock(mpb);
}
}
If you rather want to select only exactly one object at a time then I would exchange the HashSet<SpriteOutline> _currentSelection by something like
// The current selection
private static SpriteOutline _currentSelection;
// Again if needed a public read-only access
public static SpriteOutline CurrentSelection => _currentSelection;
accordingly change
public bool IsSelected
{
get => _isSelected;
set
{
if(value == _isSelected) return;
// assuming that if you set this via property it should
// overwrite the current selection
// otherwise you would here do
//if(_currentSelection && _currentSelection != this) return;
if(_currentSelection && _currentSelection != this) _currentSelection.IsSelected = false;
_isSelected = value;
_currentSelection = value ? this : null;
UpdateOutline(enabled, value);
}
}
and finally you wanted to block the input on objects that are not the selection:
private void OnMouseDown()
{
// Toggle selection of this
if (Input.GetMouseButtonDown(0))
{
if(_currentSelection && _currentSelection != this) return;
IsSelected = !IsSelected;
}
}
Note: Typed on smartphone but I hope the idea gets clear

Destroying exact object

I am creating a small 2d game in which you need to survive. Each tree has its own strength = 5. When the player collides and presses the left mouse button then strength is -1 and player wood stat is +1. when the tree strength is equal or less than 0 then the tree is destroyed. Here is my code : (Question is after the code)
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Stats: MonoBehaviour
{
//Player Stats
public float hp = 100;
public float wood = 0;
//Tree stats
public float treeLogStrenth = 5;
//Text
public Text woodText;
void Start ()
{
woodText.text = "0";
}
void Update ()
{
woodText.text = wood.ToString();
if (treeLogStrenth <= 0)
{
Destroy(GetComponent<PlayerCollisions>().;
}
}
}
here is another code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCollisions: MonoBehaviour
{
public void OnCollisionStay2D (Collision2D collisionInfo)
{
if (collisionInfo.gameObject.tag == "Tree" && Input.GetMouseButtonDown(0))
{
string treeName = collisionInfo.gameObject.name;
GetComponent<Stats>().wood += 1;
GetComponent<Stats>().treeLogStrenth -= 1;
}
}
}
MY QUESTION : How to make instead of creating all the time another game object for each tree and destroying it, only like one simple code that will do it. please help (UNITY NEWEST VERSION)
So to clear things up: Stats should be attached to the player, right?
You should not do things every frame in Update but rather event driven like
public class Stats : MonoBehaviour
{
// You should never allow your stats to be set via public fields
[SerializeField] private float hp = 100;
[SerializeField] private float wood = 0;
// if you need to read them from somewhere else you can use read-only properties
public float HP => hp;
public float Wood => wood;
[SerializeField] private Text woodText;
private void Start ()
{
woodText.text = "0";
}
public void AddWood(int amount)
{
wood += amount;
woodText.text = wood.ToString();
}
}
Then each tree should rather have its own component instance attached like e.g.
public class Tree : MonoBehaviour
{
[SerializeField] private float treeLogStrenth = 5;
public void HandleClick(Stats playerStats)
{
// if this tree has wood left add it to the player stats
if(treeLogStrength > 0)
{
playerStats.AddWood(1);
treeLogStrenth -= 1;
}
// destroy this tree when no wood left
if (treeLogStrenth <= 0)
{
Destroy(gameObject);
}
}
}
and then finally also attached to the Player
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store the currently collided tree in order to reuse it
private Tree currentlyCollidedTree;
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// Get the Tree component of the tree object you are currently colliding with
// but only once and store the reference in order to reuse it
if(!currentlyCollidedTree) currentlyCollidedTree= collisionInfo.gameObject.GetComponent<Tree>();
// tell the tree to handle a click and pass in your stats reference
currentlyCollidedTree.HandleClick(stats);
}
}
// reset the currentlyCollidedTree field when not colliding anymore
private void OnCollisionExit2D()
{
currentlyCollidedTree = null;
}
}
Well yes there would be an alternative where not every tree needs its own component instance but I would recommend to not use it actually!
You could make your player remember which tree he already clicked in something like
public class PlayerCollisions: MonoBehaviour
{
// better already reference this via the Inspector
[SerializeField] private Stats stats;
// will store all trees we ever clicked on in relation to the according available wood
private Dictionary<GameObject, int> encounteredTrees = new Dictionary<GameObject, int>();
// as fallback initialize it on runtime
private void Awake()
{
if(!stats) stats = GetComponent<Stats>();
}
private void OnCollisionStay2D(Collision2D collisionInfo)
{
if (collisionInfo.gameObject.CompareTag("Tree") && Input.GetMouseButtonDown(0))
{
// did we work on this tree before?
if(encounteredTrees.Contains(collisionInfo.gameObject))
{
// if so gain one wood and remove one from this tree
stats.AddWood(1);
encounteredTrees[collisionInfo.gameObject] -= 1;
// destroy the tree if no wood available and remove it from the dictionary
if(encounteredTrees[collisionInfo.gameObject] <= 0)
{
encounteredTrees.RemoveKey(collisionInfo.gameObject);
Destroy(collisionInfo.gameObject);
}
}
else
{
// the first time we work this tree gain one wood and add
// the tree as new entry to the dictionary with 4 wood left
stats.AddWood(1);
encounteredTrees.Add(collisionInfo.gameObject, 4);
}
}
}
}
this however limits you extremely and it is not possible anymore that you have e.g. different Tree prefabs with different amount of available wood ...

When I used portal in My Game why Destroyed "Camera Fallow"?

I made a portal to the 2D game. Normally the camera needs to follow the character. But after the portal scripts I wrote, "CameraFallowScript" does not work. The character is passing through the portal. but after passing "CameraFallowScript" disappears. I'm a little new and my English is bad.
thanks for helping.
Camera Fallow Script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFallow : MonoBehaviour
{
public GameObject target;
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = new Vector3(target.transform.position.x, target.transform.position.y, transform.position.z);
}
}
Portal Script here :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Portal : MonoBehaviour
{
private Rigidbody2D enteredRigidbody;
private float enterVelocity, exitVelocity;
private void OnTriggerEnter2D(Collider2D collision)
{
enteredRigidbody = collision.gameObject.GetComponent<Rigidbody2D>();
enterVelocity = enteredRigidbody.velocity.x;
if (gameObject.name == "BluePortal")
{
PortalControl.portalControlInstance.DisableCollider("orange");
PortalControl.portalControlInstance.CreateClone("atOrange");
}
else if (gameObject.name == "OrangePortal")
{
{
PortalControl.portalControlInstance.DisableCollider("blue");
PortalControl.portalControlInstance.CreateClone("atBlue");
}
}
}
private void OnTriggerExit2D(Collider2D collision)
{
exitVelocity = enteredRigidbody.velocity.x;
if (enterVelocity != exitVelocity)
{
Destroy(GameObject.Find("Clone"));
}
Destroy(collision.gameObject);
PortalControl.portalControlInstance.EnableColliders();
GameObject.Find("Clone").name = "Character";
CameraFallow.DontDestroyOnLoad(transform.gameObject);
}
}
PortalControl Script Here :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PortalControl : MonoBehaviour
{
public static PortalControl portalControlInstance;
[SerializeField]
private GameObject bluePortal, orangePortal;
[SerializeField]
private Transform bluePortalSpawnPoint, orangePortalSpawnPoint;
private Collider2D bluePortalCollider, orangePortalCollider;
[SerializeField]
private GameObject clone;
void Start()
{
portalControlInstance = this;
bluePortalCollider = bluePortal.GetComponent<Collider2D>();
orangePortalCollider = orangePortal.GetComponent<Collider2D>();
}
// Update is called once per frame
public void CreateClone(string whereToCreate)
{
if (whereToCreate == "atBlue")
{
var instantiatedClone = Instantiate(clone, bluePortalSpawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
if (whereToCreate == "atOrange")
{
var instantiatedClone = Instantiate(clone, orangePortalSpawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
}
public void DisableCollider(string ColliderToDisable)
{
if (ColliderToDisable == "orange")
{
orangePortalCollider.enabled = false;
}
else if (ColliderToDisable == "blue")
{
bluePortalCollider.enabled = false;
}
}
public void EnableColliders()
{
orangePortalCollider.enabled = true;
bluePortalCollider.enabled = true;
}
}
In general I wouldn't create clone I guess ... why instantiate a new player? Why not simply move it to the new position?
What happens here is that after Destroy(collision.gameobject) the FollowCamera loses the reference to its target so you would need to reassign it after the cloning e.g. in
private void OnTriggerExit2D(Collider2D collision)
{
exitVelocity = enteredRigidbody.velocity.x;
if (enterVelocity != exitVelocity)
{
Destroy(GameObject.Find("Clone"));
}
Destroy(collision.gameObject);
PortalControl.portalControlInstance.EnableColliders();
var clone = GameObject.Find("Clone");
clone.name = "Character";
DontDestroyOnLoad(clone);
// It would ofcourse be way more efficient
// if you would store this reference somewhere
FindObjectOfType<CameraFollow>().target = clone;
}
Note that in general usage of Find is expensive and you should avoid it wherever possible! Rather pass on the clone reference between all required scripts.
Regarding coding style: passing around string parameters is not really good code.
I would suggest e.g. an enum like
public enum PortalSide
{
orange,
blue
}
and then use
private Dictionary<PortalSide, Transform> portalSpawns;
private Dictionary<PortalSide, Collider> portalColliders;
private void Awake()
{
portalSpawns = new Dictionary<PortalSide, Transform> { {PortalSide.blue, bluePortalSpawnPoint} , {PortalSide.orange, orangePortalSpawnPoint}};
portalColliders = new Dictionary<PortalSide, Collider> { {PortalSide.blue, bluePortalCollider}, {PortalSide.orange, orangePortalCollider} };
}
public void CreateClone(PortalSide whereToCreate)
{
var spawnPoint = PortalSides[whereToCreate];
var instantiatedClone = Instantiate(clone, spawnPoint.position, Quaternion.identity);
instantiatedClone.gameObject.name = "clone";
}
public void DisableCollider(PortalSide ColliderToDisable)
{
var colliderToDisable = portalColliders[ColliderToDisable];
colliderToDisable.enabled = false;
}

Instantiate inside nested for loops does not do what it is intended to

I have been working on a minecraft clone game in Unity 2017.3 and C# and i started all right but when i worked on the super flat world generation system there began a problem. Inside the World mono behaviour, in the Generate void, there are three nested for loops which should generate a new dirt block on every possible position between 0 and 5
But it only makes a line of dirt block stretching along the Z axis.
Here is the code for PlaceableItem(attached to dirtPrefab) and World(attached to World)
Placeable Item class
using UnityEngine;
using System.Collections;
public class PlaceableItem : MonoBehaviour
{
public string nameInInventory = "Unnamed Block";
public int maxStack = 64;
public bool destructible = true;
public bool explodesOnX = false;
public bool abidesGravity = false;
public bool isContainer = false;
public bool canBeSleptOn = false;
public bool dropsSelf = false;
private Rigidbody rb;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
if (abidesGravity) {
rb.useGravity = true;
} else {
rb.useGravity = false;
}
}
private void OnDestroy()
{
if (dropsSelf) {
//Drop this gameObject
}
}
public void Explode() {
if (!explodesOnX)
return;
}
public void OnMouseDown()
{
if (isContainer && !canBeSleptOn) {
//Open the container inventory
} else if (canBeSleptOn && !isContainer) {
//Make the character sleep on the item
}
}
private void OnMouseOver()
{
if (Input.GetMouseButtonDown(1) && destructible) {
Destroy(gameObject);
}
}
}
World class
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class World : MonoBehaviour {
public GameObject dirtPrefab;
public bool generateAutomatically = true;
// Use this for initialization
void Start () {
if (generateAutomatically) {
Generate();
}
}
// Update is called once per frame
void Update () {
}
void Generate() {
for (int x = 0; x <= 5; x++) {
for (int y = 0; y <= 5; y++) {
for (int z = 0; z <= 5; z++) {
Instantiate(dirtPrefab, new Vector3(x, y, z), Quaternion.identity);
}
}
}
}
void RemoveAllBlocks() {
foreach (PlaceableItem placeableItem in GetComponentsInChildren<PlaceableItem>()) {
Destroy(placeableItem.gameObject);
}
}
}
Thanks in advance, fellow developers!
Hope this is not a stupid question!
[SOLVED] I realised i had a recttransform somehow attached to my dirt prefab. Removing it and adding a transform instead helped.

Categories