How can I create an object at the mouse point in scene view by clicking not dragging the object? - unity3d

Generally, most objects are placed in the scene view by dragging or something. I want to right click the mouse (without dragging an object) to create an object in the scene view. I know this would require some editor coding but I’m not sure how to go about it.
UPDATE
After giving it some thought I realised that using a MenuItem would be quite appropriate for me. Here is my code below:
SLMenuItems:
public class SLMenuItems : MonoBehaviour {
public bool canClickSceneViewToCreatePath = false;
void Start()
{
}
[MenuItem("Component/Create Custom Object")]
static void CreateObject() {
Debug.Log("menu item selected");
canClickSceneViewToCreatePath = true;
}
}
SLMenuItemsEditor:
[CustomEditor(typeof(SLMenuItems))]
public class SLMenuItemsEditor : Editor {
SLMenuItems slMenuItems;
void OnEnable()
{
slMenuItems = (SLMenuItems)target;
}
void OnSceneGUI()
{
if (slMenuItems.canClickSceneViewToCreatePath) {
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
// create object here at pointsPos
slMenuItems.canClickSceneViewToCreatePath = false;
}
}
}
}
I keep getting the following error:
Assets/SLMenuItems.cs(23,9): error CS0120: An object reference is required to access non-static member `SLMenuItems.canClickSceneViewToCreatePath'
pointing to the line:
canClickSceneViewToCreatePath = true;
in SLMenuItems.

Your CreateObject method is static but your canClickSceneViewToCreatePath value is not.
It has nothing to do with the editor script but with your class SlMenuItems itself.
A static method is not instanced or with other words it is kind of shared between all instances of that component type while the non-static value might be different for each component.
So how should a static method - which is the same for all instances - "know", which of the instances values it should access?
So either make the method non-static or the variable static. Depending on what your further need is.
Since the MenuItem needs a static method make the variable static as well.
I would suggest you make that class not inherit from MonoBehaviour at all since it doesn't have any behaviour for a GameObject. It only brings some editor features so rather make it a static class that can "live" in the Assets without needing to be instanced.
Than you can use SceneView.onSceneGUIDelegate to register a callback for OnSceneGUI without implementing an editor script for that:
private static GameObject lastCreated;
private static bool isCreating;
public static class SLMenuItems
{
[MenuItem("Component/Create Custom Object")]
private static void CreateObject()
{
Debug.Log("menu item selected");
isCreating = true;
lastCreated = null;
// Add a callback for SceneView update
SceneView.onSceneGUIDelegate -= UpdateSceneView;
SceneView.onSceneGUIDelegate += UpdateSceneView;
}
private static void UpdateSceneView(SceneView sceneView)
{
if(lastCreated)
{
// Keep lastCreated focused
Selection.activeGameObject = lastCreated;
}
if(isCreating)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
//Todo create object here at pointsPos
lastCreated = Instantiate(somePrefab);
// Avoid the current event being propagated
// I'm not sure which of both works better here
Event.current.Use();
Event.current = null;
// Keep the created object in focus
Selection.activeGameObject = lastCreated;
// exit creation mode
isCreating = false;
}
}
else
{
// Skip if event is Layout or Repaint
if(e.type == EventType.Layout || e.type == EventType.Repaint)
{
Selection.activeGameObject = lastCreated;
return;
}
// Prevent Propagation
Event.current.Use();
Event.current = null;
Selection.activeGameObject = lastCreated;
lastCreated = null;
// Remove the callback
SceneView.onSceneGUIDelegate -= UpdateSceneView;
}
}
}
But I suggest you change your questions title since this is actually not the solution to the "task" you describe before.

Related

How to Create an Input Name Script in Unity [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 2 years ago.
I have a name input script in the first scene, the plan is I want to call this input in the second scene, when I enter the name in the first scene, then the name will appear in the second scene too, how do you do that?
public class NamaUser : MonoBehaviour {
public InputField nama;
public Text teks;
public void NamaTeks () {
if (nama.text == "") {
teks.text = "Harap Isi Nama";
} else {
teks.text = "Namaku " + nama.text;
}
}
}
You can save the input's value PlayerPrefs.
Set the PlayerPrefs:
//Name of Pref in first parameter
//Value in second parameter
PlayerPrefs.SetString("value", teks.value);
Get the PlayerPref in second scene:
//Name of Pref in first parameter
//Returns value of PlayerPrefs
String a = PlayerPrefs.SetString("value");
Cons:
You can pass data not only between scenes but also between instances (game sessions).
Easy to manage since Unity handles all background process.
Can be used to store data to track highscores.
Pros:
Uses file system.
Data can easily be changed from prefs file.
Or, another way -- use Singelton and DontDestroyOnLoad()
Allows easy access to fields and saves an object between scenes.
For example use this template, to create your class.
using UnityEngine;
public class Singelton<T> : MonoBehaviour where T : Singelton<T>
{
private static T instance = null;
private bool alive = true;
public static T Instance
{
get
{
if (instance != null)
{
return instance;
}
else
{
//Find T
T[] managers = GameObject.FindObjectsOfType<T>();
if (managers != null)
{
if (managers.Length == 1)
{
instance = managers[0];
DontDestroyOnLoad(instance);
return instance;
}
else
{
if (managers.Length > 1)
{
Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
"But this is Singelton! Check project.");
for (int i = 0; i < managers.Length; ++i)
{
T manager = managers[i];
Destroy(manager.gameObject);
}
}
}
}
//create
GameObject go = new GameObject(typeof(T).Name, typeof(T));
instance = go.GetComponent<T>();
DontDestroyOnLoad(instance.gameObject);
return instance;
}
}
//Can be initialized externally
set
{
instance = value as T;
}
}
/// <summary>
/// Check flag if need work from OnDestroy or OnApplicationExit
/// </summary>
public static bool IsAlive
{
get
{
if (instance == null)
return false;
return instance.alive;
}
}
protected virtual void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this as T;
}
else
{
Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
"But this is Singelton! Check project.");
Destroy(gameObject);
}
}
protected virtual void OnDestroy() { alive = false; }
protected virtual void OnApplicationQuit() { alive = false; }
}
Example of using:
class MyClass Settings : Singelton<Settings>
{
string param;
}

Unity UI Button keeps double clicking, how can I work around this?

I have two buttons set up in an interactive fiction game, each press calls a new line of text. The problem is, every time I press on the button, I get two logged debug messages informing me of the click and my game moves two sections of text.
I've tried many different things to try to work around this including trying to alter the submit input in project settings and many different code forms. Any help would be greatly appreciated.
Here's my current code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AdventureGame : MonoBehaviour
{
[SerializeField]
private Text _textComponent;
[SerializeField]
private State _startingState;
private State state;
[SerializeField]
private Button _input0Button;
[SerializeField]
private Button _input1Button;
[SerializeField]
private Text _choices1;
[SerializeField]
private Text _choices2;
private bool _buttonOnePressed;
private bool _buttonTwoPressed;
void Start()
{
state = _startingState;
_textComponent.text = state.GetStateStory();
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
_buttonOnePressed = false;
_buttonTwoPressed = false;
}
void Update()
{
ManageState();
}
private void ManageState()
{
if (state._choice == true)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(true);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
else if(_buttonTwoPressed == true)
{
StartCoroutine(WaitForItTwo());
}
}
else if (state._choice == false)
{
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
_input1Button.gameObject.SetActive(false);
if(_buttonOnePressed == true)
{
StartCoroutine(WaitForItOne());
}
}
}
private void ManageChoiceOne()
{
_buttonOnePressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[0];
}
private void ManageChoiceTwo()
{
_buttonTwoPressed = false;
State[] _newState = state.GetNextStatesArray();
state = _newState[1];
}
public void Input0Button()
{
Debug.Log("Input 0 pressed");
_buttonOnePressed = true;
}
public void Input1Button()
{
Debug.Log("Input 1 pressed");
_buttonTwoPressed = true;
}
IEnumerator WaitForItOne()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceOne();
}
IEnumerator WaitForItTwo()
{
yield return new WaitForSeconds(3.0f);
ManageChoiceTwo();
}
}
First of all you keep starting new Coroutines each frame as long as e.g. _buttonOnePressed == true .. you wait 3 seconds before you finally unset this flag!
Then for the double call make sure the callbacks are not configured also in the Inspector! It seems like you have them once in the Inspector and additionally add them in your Start method so they are called twice!
Note that you won't see the callbacks added on runtime in the Inspector!
Why are you even using Update here at all? It is quite redundant to poll the state and the bool values and constantly check and handle their states in each frame. I would rather simply start the routine in the button method itself and make the whole code event driven instead!
(optionally) To give the user better feedback I would additionally in the meantime during the 3 seconds make the buttons non-interactable .. keep them active but not clickable:
// Remove state;
// Remove _buttonOnePressed
// Remove _buttonTwoPressed
private void Start()
{
// Either remove this two lines or the callbacks set in the Inspector
_input0Button.onClick.AddListener(Input0Button);
_input1Button.onClick.AddListener(Input1Button);
ManageState(_startingState);
}
// Remove Update
// This will be called only when actually needed
// since the state is passed in as parameter you don't need the private field
private void ManageState(State state)
{
// These happen in both cases anyway
_choices1.text = state.GetChoiceOne();
_choices2.text = state.GetChoiceTwo();
_textComponent.text = state.GetStateStory();
_input0Button.gameObject.SetActive(true);
// Here directly use the state flag
// since the button is disabled when needed
// there is no need for having different "states"
// since anyway only the according button(s) is(are) available
_input1Button.gameObject.SetActive(state._choice);
}
// (optional) Flag for avoiding concurrent routines
// Should be impossible since buttons get disabled but you never know
private bool alreadyHandlingButton;
private IEnumerator ManageChoice(bool isButtonOne)
{
// (optional) Skip if another routine running
if(alreadyHandlingButton) yield break;
// (optional) Block other routines just in cade
alreadyHandlingButton = true;
// Disable interactions
_input0Button.interactable = false;
_input1Button.interactable = false;
// This is the same for both buttons
yield return new WaitForSeconds(3f);
State[] _newState = state.GetNextStatesArray();
var state = _newState[isButtonOne ? 0 : 1];
// Only call ManageState when the state is actually changing
ManageState(state);
// (optional) Allow a new routine
alreadyHandlingButton = false;
// Enable interactions
_input0Button.interactable = true;
_input1Button.interactable = true;
}
public void Input0Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 0 pressed");
StartCoroutine(ManageChoice(true));
}
public void Input1Button()
{
// (optional) Ignore if other button is already handled
if(alreadyHandlingButton) return;
Debug.Log("Input 1 pressed");
StartCoroutine(ManageChoice(false));
}

How do I update a gtk listbox from an async method?

So when writing UI in GTK it's generally preferrable to handle reading of files, etc. in an Async Method. things such as listboxes, are generally bound to a ListModel, the items in the ListBox updated in accordance with the items_changed signal.
So if I have some class, that implements ListModel, and has an add function, and some FileReader that holds a reference to said ListModel, and call add from an async function, how do i make that in essence triggering the items_changed and having GTK update accordingly?
I've tried list.items_changed.connect(message("Items changed!")); but it never triggers.
I saw this: How can one update GTK+ UI in Vala from a long operation without blocking the UI
but in this example, it's just the button label that is changed, no signal is actually triggered.
EDIT: (Codesample added at the request of #Michael Gratton
//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.
//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging
public class AsyncFileContext : Object{
private int64 offset;
private bool start_read;
private bool read_to_end;
private Factories.IVCardFactory factory;
private File file;
private FileMonitor monitor;
private Gee.Set<IVCard> vcard_buffer;
private IObservableSet<IVCard> _vCards;
public IObservableSet<IVCard> vCards {
owned get{
return this._vCards;
}
}
construct{
//We want to start fileops at the beginning of the file
this.offset = (int64)0;
this.start_read = true;
this.read_to_end = false;
this.vcard_buffer = new Gee.HashSet<IVCard>();
this.factory = new Factories.GenericVCardFactory();
}
public void add_vcard(IVCard card){
//TODO: implement
}
public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
this._vCards = vcards;
this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
this.file = File.new_for_path(path);
this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
message("1");
//TODO: add connect
this.monitor.changed.connect((file, otherfile, event) => {
if(event != FileMonitorEvent.DELETED){
bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
Idle.add(() => {
read_file_async.begin(changes_done);
return false;
});
}
});
message("2");
//We don't know that changes are done yet
//TODO: Consider carefully how you want this to work when it is NOT called from an event
Idle.add(() => {
read_file_async.begin(false);
return false;
});
}
//Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
private async void read_file_async(bool changes_done) throws IOError{
if(!this.start_read){
return;
}
this.start_read = false;
var dis = new DataInputStream(yield file.read_async());
message("3");
//If we've been reading this file, and there's then a change, we assume we need to continue where we let off
//TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
if(offset > 0){
dis.seek(offset, SeekType.SET);
}
string line;
int vcards_added = 0;
while((line = yield dis.read_line_async()) != null){
message("position: %s".printf(dis.tell().to_string()));
this.offset = dis.tell();
message("4");
message(line);
//if the line is empty, we want to jump to next line, and ignore the input here entirely
if(line.chomp().chug() == ""){
continue;
}
this.factory.add_line(line);
if(factory.vcard_ready){
message("creating...");
this.vcard_buffer.add(factory.create());
vcards_added++;
//If we've read-in and created an entire vcard, it's time to yield
message("Yielding...");
Idle.add(() => {
_vCards.add_all(vcard_buffer);
vcard_buffer.remove_all(_vCards);
return false;
});
Idle.add(read_file_async.callback);
yield;
message("Resuming");
}
}
//IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
if(changes_done){ //|| vcards_added == 0){
this.offset = 0;
}
this.start_read = true;
}
}
//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{
private Gee.List<IVCard> vcard_list;
private IObservableCollection<IVCard> vcard_collection;
public VCardListModel(IObservableCollection<IVCard> vcard_collection){
this.vcard_collection = vcard_collection;
this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());
this.vcard_collection.item_added.connect((vcard) => {
vcard_list.add(vcard);
int pos = vcard_list.index_of(vcard);
items_changed(pos, 0, 1);
});
this.vcard_collection.item_removed.connect((vcard) => {
int pos = vcard_list.index_of(vcard);
vcard_list.remove(vcard);
items_changed(pos, 1, 0);
});
this.vcard_collection.cleared.connect(() => {
items_changed(0, vcard_list.size, 0);
vcard_list.clear();
});
}
public Object? get_item(uint position){
if((vcard_list.size - 1) < position){
return null;
}
return this.vcard_list.get((int)position);
}
public Type get_item_type(){
return Type.from_name("VikingvCardIVCard");
}
public uint get_n_items(){
return (uint)this.vcard_list.size;
}
public Object? get_object(uint position){
return this.get_item((int)position);
}
}
//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{
private ListModel list_model;
public ContactList(IObservableCollection<IVCard> ivcards){
this.list_model = new VCardListModel(ivcards);
bind_model(this.list_model, create_row_func);
list_model.items_changed.connect(() => {
message("Items Changed!");
base.show_all();
});
}
private Gtk.Widget create_row_func(Object item){
return new ContactRow((IVCard)item);
}
}
Heres the way i 'solved' it.
I'm not particularly proud of this solution, but there are a couple of awful things about the Gtk ListBox, one of them being (and this might really be more of a ListModel issue) if the ListBox is bound to a ListModel, the ListBox will NOT be sortable by using the sort method, and to me at least, that is a dealbreaker. I've solved it by making a class which is basically a List wrapper, which has an 'added' signal and a 'remove' signal. Upon adding an element to the list, the added signal is then wired, so it will create a new Row object and add it to the list box. That way, data is control in a manner Similar to ListModel binding. I can not make it work without calling the ShowAll method though.
private IObservableCollection<IVCard> _ivcards;
public IObservableCollection<IVCard> ivcards {
get{
return _ivcards;
}
set{
this._ivcards = value;
foreach(var card in this._ivcards){
base.prepend(new ContactRow(card));
}
this._ivcards.item_added.connect((item) => {
base.add(new ContactRow(item));
base.show_all();
});
base.show_all();
}
}
Even though this is by no means the best code I've come up with, it works very well.

Where to Store all my scene name in one place

I am making a probject in Unity, and would like to have one play to access all my SceneNaming;
Right now in the UI, I have to set the scene name manually.
I would like to store all my scene name in an object, so that I can just use a drag drop to choose all my scenes names.
I tried to put a static class and have then like this
public static string SCENE_MENU = "Menu";
public static string SCENE_WORLD = "Demo";
or inside an enum
public enum SCENE_NAME{
Menu, Demo
}
and then use GetName on the enum to get the value
What is the best approach? 1: /storage/temp/135402-screenshot-1.png
With a customer editor script you could use a SceneAsset to store a Scene's path instead.
I will use a CustomEditor here since for starters it's easier to understand what happens there. Later you might want to switch it to a CustomPropertyDrawer wot a proper class or maybe even as Attribute.
Place this in anywhere in the Assets
public class SceneLoader : MonoBehaviour
{
public string ScenePath;
public void Load()
{
//e.g.
SceneManager.LoadSceneAsync(ScenePath);
}
}
Place this inside of a folder Editor (so it will not be included in a build where the UnityEditor namespace does not exist)
[CustomEditor(typeof(SceneLoader), true)]
public class ScenePickerEditor : Editor
{
private SerializedProperty _scenePath;
private void OnEnable()
{
_scenePath = serializezObject.FindProperty("ScenePath");
}
public override void OnInspectorGUI()
{
// Draw the usual script field
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(.FromMonoBehaviour((SceneLoader)target), typeof(SceneLoader), false);
EditorGUI.EndDisabledGroup();
// Loads current Values into the serialized "copy"
serializedObject.Update();
// Get the current scene asset for the current path
var currentScene = !string.IsNullOrWhiteSpace(_scenePath.stringValue) ? AssetDatabase.LoadAssetAtPath<SceneAsset>(_scenePath.stringValue) : null;
EditorGUI.BeginChangeCheck();
var newScene = (SceneAsset)EditorGUILayout.ObjectField("Scene", currentScene, typeof(SceneAsset), false);
if (EditorGUI.EndChangeCheck())
{
_scenePath.stringValue = newScene != Null ? AssetDatabase.GetAssetPath(newScene) : "";
}
// Write back changes to the actual component
serializedObject.ApplyModifiedProperties();
}
}
And e.g. to your button attach that SceneLoader component.
Than you can simply reference the target scene in the Inspector via drag and drop. Internally it instead stores the according ScenePath.
Now in onClick instead use that SceneLoader.Load.
Note:
As mentioned here only storing the scene path might not be "save" and breaks if you later move the according scene or rename it. So maybe it would be a good extension to also store according object reference as a kind of fallback.
You could than also use this approach and extend it to be a central manager instead like
// It could as well be a ScriptableObject object
// this makes e.g. Awake run already in edit mode
[ExecuteInEditMode]
public class ScenePathManager : MonoBehaviour
{
// I would prefere references but for ease of this post
// use a Singleton for access
public static ScenePathManager Instance;
public List<string> AvailableScenePaths = new List<string>();
private void Awake ()
{
Instance = this;
}
}
and in the editor script use a list (again there are more beautiful ways like ReorderableList bit this would get to complex here
[CustomEditor(typeof(ScenePathManager))]
public class ScenePathManagerEditor : Editor
{
private SerializedProperty _availablePaths;
private void OnEnable ()
{
_availablePaths = serializedObject.FindProperty("AvailablScenePaths");
}
public override OnInpectorGUI ()
{
// Draw the usual script field
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(.FromMonoBehaviour((SceneLoader)target), typeof(SceneLoader), false);
EditorGUI.EndDisabledGroup();
serializedObject.Update();
//Do the same thing as before but this time in a loop
for(var i=0; i<_availablePaths.arraySize; i++)
{
var _scenePath = _availablePaths.GetArrayElementAtIndex(i);
// Loads current Values into the serialized "copy"
serializedObject.Update();
// Get the current scene asset for the current path
var currentScene = !string.IsNullOrWhiteSpace(_scenePath.stringValue) ? AssetDatabase.LoadAssetAtPath<SceneAsset>(_scenePath.stringValue) : null;
EditorGUI.BeginChangeCheck();
var newScene = (SceneAsset)EditorGUILayout.ObjectField("Scene", currentScene, typeof(SceneAsset), false);
if (EditorGUI.EndChangeCheck())
{
_scenePath.stringValue = newScene != Null ? AssetDatabase.GetAssetPath(newScene) : "";
}
}
serializedObject.ApplyModifiedProperties();
}
}
Than you could reference all needed scenes in that manager and than on your SceneLoader instead have a Popup field (like for enums) in order to select the scene you want
[CustomEditor (typeof (SceneLoader))]
public class SceneLoaderEditor : Editor
{
private SerializedProperty _scenePath;
private void OnEnable ()
{
_scenePath = serializedObject.FindProperty("ScenePath");
}
public override void OnInpectorGUI ()
{
//Let me shorten it a bit this time ^^
serializedObject.Update();
var availablePaths = ScenePathManager.Instance ? ScenePathManager.Instance.AvailableScenePaths : new List<string>();
var currentIndex = availablePaths.FirstOrDefault(path => string.Equals(path, _scenePath.stringValue)));
var newIndex = EditorGUILayout.PopupField("Scene", currentIndex, availabePaths.ToArray());
_scenePath.stringValue = availablePaths[newIndex];
serializedObject.ApplyModifiedProperties();
}
}
This should than give you a selection dropdown for the scene.
Note this might, however, without the object reference as backing field break evem faster of any of those strings or indexes change...
But you could use this with your manager also without the whole SceneAsset approach but only for simple strings.
Typed on my smartphone so no warranty but I hope I make my point clear

Duplicated objects are not working 'per instance' when I intended them to do [duplicate]

This question already has answers here:
How to detect click/touch events on UI and GameObjects
(4 answers)
Closed 5 years ago.
using UnityEngine;
public class LinkEnd : MonoBehaviour
{
public GameObject linkTarget;
private PointEffector2D effector;
private CircleCollider2D contact;
private AimSystem aimer;
private float distFromLink = .2f;
public bool connected;
private void Start()
{
aimer = GetComponent<AimSystem>();
}
private void Update()
{
SyncPosition();
ReactToInput();
}
public void ConnectLinkEnd(Rigidbody2D endRB)
{
HingeJoint2D joint = GetComponent<HingeJoint2D>();
if (GetComponent<HingeJoint2D>() == null)
{
joint = gameObject.AddComponent<HingeJoint2D>();
}
joint.autoConfigureConnectedAnchor = false;
joint.connectedBody = endRB;
joint.anchor = Vector2.zero;
joint.connectedAnchor = new Vector2(0f, -distFromLink);
}
private void SyncPosition()
{
if (linkTarget != null)
{
if (Vector2.Distance(transform.position, contact.transform.position) <= 0.1f)
{
connected = true;
effector.enabled = false;
contact.usedByEffector = false;
}
}
if (connected)
{
GetComponent<Rigidbody2D>().isKinematic = true;
GetComponent<Rigidbody2D>().position = linkTarget.transform.position;
}
else
GetComponent<Rigidbody2D>().isKinematic = false;
}
private void ReactToInput()
{
if (Input.GetKeyUp(KeyCode.Mouse0) || Input.GetKey(KeyCode.Mouse1))
{
connected = false;
}
}
public void OnTriggerEnter2D(Collider2D collision)
{
if (collision.GetComponent<PointEffector2D>() != null)
{
connected = true;
linkTarget = collision.gameObject;
effector = linkTarget.GetComponent<PointEffector2D>();
contact = linkTarget.GetComponent<CircleCollider2D>();
}
}
public void OnTriggerExit2D(Collider2D collision)
{
connected = false;
contact.usedByEffector = true;
effector.enabled = true;
}
}
This is an object that pins its position to another mobile object on collision, and it's supposed to stay that way until it's 'detached' by player action.
It's working almost fine, but it's not working 'per instance.'
Whether this object is a prefab or not, ReactToInput() is affecting all instances of it unlike how I wanted.
I'm missing some per instance specification here and I'm not seeing where.
Any suggestion will help and be appreciated!
++ The method ReactToInput() is triggered by key inputs. I wanted this method to be called when Player's attack 'method' happens which are bound to those key inputs, but I did what I've done only because I couldn't find an elegant way to execute it otherwise, and am really hoping there's a better way rather than using tags or GetComponent to specific object since it's supossed to affect other objects as well.
These methods are what you are looking for.
/// <summary>
/// OnMouseDown is called when the user has pressed the mouse button while
/// over the GUIElement or Collider.
/// </summary>
void OnMouseDown()
{
Debug.Log("Hi!");
}
/// <summary>
/// OnMouseUp is called when the user has released the mouse button.
/// </summary>
void OnMouseUp()
{
Debug.Log("Bye!");
}
MonoBehaviour provides many event callbacks other than Update(), and these two are some of them. The full list of event callbacks you can use for MonoBehaviour is explained in the official Monobehaviour page.
There are two methods for OnMouseUp():
OnMouseUp is called when the user has released the mouse button.
OnMouseUpAsButton is only called when the mouse is released over the
same GUIElement or Collider as it was pressed.
The GameObject your script is attached to is requires to have GUIElement or Collider as described in the manual page to use these functions.
If you do not want to use these methods, you could alternatively write your own custom InputModule, raycast to the mouse position on the screen to find which object is clicked, and send MouseButtonDown() event to a clicked GameObject.
I had to implement a custom input module to do this plus a couple of other stuff and I assure you writing custom InputModules is a headache.
EDIT:
If many different classes need to be notified when something happens, and who listens to such cases is unknown, event is a good option.
If you are using events, each event listener class such as LinkEnd is responsible to register and remove itself to such event.
Below is an example of how you could achieve this behaviour:
class Player
{
public delegate void OnSkillAFiredListener(object obj, SkillAFiredEventArgs args);
public static event OnSkillAFiredListener SkillAPressed = delegate { };
// ...
}
class LinkEnd
{
void OnEnable()
{
Player.SkillAPressed += WhatToDoWhenSkillAFired;
}
void OnDisable()
{
Player.SkillAPressed -= WhatToDoWhenSkillAFired;
}
void OnDestroy()
{
Player.SkillAPressed -= WhatToDoWhenSkillAFired;
}
public void WhatToDoWhenSkillAFired(object obj, SkillAFiredEventArgs args)
{
// get info from args
float someInfo = args.someInfo
// do something..
Bark();
}
// ...
}
It's necessary to deregister from the event in both OnDisable() and OnDestory() to avoid memory leaks (some claim such memory leaks are very minor).
Look for Observer and Publisher/Subscriber pattern to learn more about these approaches. It may not be very related to your case, but the Mediator Pattern is something that's often compared with the Observer Pattern so you might be interested to check it as well.