Anchoring in Vuforia - c#

I'm able to fix the position of the object once it's spawned. But, the object is placed onto different position on every tap on the screen. I want to avoid this problem and want my object to be fixed in a spawned position throughout the Session or Application lifetime.
This is my Deploy Stage Once script:
public class DeployStageOnce : MonoBehaviour
{
public GameObject AnchorStage;
private PositionalDeviceTracker _deviceTracker;
private GameObject _anchorGameObject;
private GameObject _previousAnchor;
public void Start()
{
if (AnchorStage == null)
{
Debug.Log("AnchorStage must be specified");
return;
}
AnchorStage.SetActive(false);
}
public void Awake()
{
VuforiaARController.Instance.RegisterVuforiaStartedCallback(OnVuforiaStarted);
}
public void OnDestroy()
{
VuforiaARController.Instance.UnregisterVuforiaStartedCallback(OnVuforiaStarted);
}
private void OnVuforiaStarted()
{
_deviceTracker = TrackerManager.Instance.GetTracker<PositionalDeviceTracker>();
}
private void AnchorGameObjectSetHitTestPosition(HitTestResult reuslt)
{
_anchorGameObject.transform.position = reuslt.Position;
_anchorGameObject.transform.rotation = reuslt.Rotation;
}
public void OnInteractiveHitTest(HitTestResult result)
{
if (result == null || AnchorStage == null)
{
Debug.LogWarning("Hit test is invalid or AnchorStage not set");
return;
}
var anchor = _deviceTracker.CreatePlaneAnchor(Guid.NewGuid().ToString(), result);
_anchorGameObject = new GameObject();
AnchorGameObjectSetHitTestPosition(result);
if (anchor != null)
{
AnchorStage.transform.parent = _anchorGameObject.transform;
AnchorStage.transform.localPosition = Vector3.zero;
AnchorStage.transform.localRotation = Quaternion.identity;
AnchorStage.SetActive(true);
}
if (_previousAnchor != null)
{
Destroy(_previousAnchor);
}
_previousAnchor = _anchorGameObject;
}
}

Well you can create an isPlaced variable in your script to check if your object is already placed like this:
private bool isPlaced = false;
public void OnInteractiveHitTest(HitTestResult result)
{
if (result == null || AnchorStage == null)
{
Debug.LogWarning("Hit test is invalid or AnchorStage not set");
return;
}
if(!isPlaced)
{
var anchor = _deviceTracker.CreatePlaneAnchor(Guid.NewGuid().ToString(), result);
_anchorGameObject = new GameObject();
AnchorGameObjectSetHitTestPosition(result);
if (anchor != null)
{
AnchorStage.transform.parent = _anchorGameObject.transform;
AnchorStage.transform.localPosition = Vector3.zero;
AnchorStage.transform.localRotation = Quaternion.identity;
AnchorStage.SetActive(true);
}
isPlaced = true;
}
}

Related

Unity - EasyAR 3.1.0 webcam black screen since updating Unity to 2021.1.18f1

EasyAR 3.1.0 worked properly in older versions of Unity. The webcam loaded as expected, but since I've updated to Unity 2021.1.18f1, the webcam only shows a black screen. I tested it with the samples provided here: https://www.easyar.com/view/downloadHistory.html with the same result: the webcam texture stays black.
To make sure, that it's not a problem with my webcam, I tried this example here: https://community.theta360.guide/t/simplest-webcam-test-in-unity/516/3 and was able to load the webcam texture properly.
I also tried using another webcam with EasyAR, but the EasyAR webcam texture stays black.
I have no idea what the problem is, since it worked before without any problems. Has anything changed between Unity 2020.2 and 2021.1 in terms of loading the webcam?
Here is the CameraImageRenderer.cs script:
//================================================================================================================================
//
// Copyright (c) 2015-2019 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
// EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
// and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//================================================================================================================================
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace easyar
{
[RequireComponent(typeof(RenderCameraController))]
public class CameraImageRenderer : MonoBehaviour
{
private RenderCameraController controller;
private CommandBuffer commandBuffer;
private CameraImageMaterial arMaterial;
private Material material;
private CameraParameters cameraParameters;
private bool renderImageHFlip;
private UserRequest request;
public event Action<Material, Vector2> OnFrameRenderUpdate;
private event Action<Camera, RenderTexture> TargetTextureChange;
protected virtual void Awake()
{
controller = GetComponent<RenderCameraController>();
arMaterial = new CameraImageMaterial();
}
protected virtual void OnEnable()
{
UpdateCommandBuffer(controller ? controller.TargetCamera : null, material);
}
protected virtual void OnDisable()
{
RemoveCommandBuffer(controller ? controller.TargetCamera : null);
}
protected virtual void OnDestroy()
{
arMaterial.Dispose();
if (request != null) { request.Dispose(); }
if (cameraParameters != null) { cameraParameters.Dispose(); }
}
public void RequestTargetTexture(Action<Camera, RenderTexture> targetTextureEventHandler)
{
if (request == null)
{
request = new UserRequest();
}
TargetTextureChange += targetTextureEventHandler;
RenderTexture texture;
request.UpdateTexture(controller ? controller.TargetCamera : null, material, out texture);
if (TargetTextureChange != null && texture)
{
TargetTextureChange(controller.TargetCamera, texture);
}
}
public void DropTargetTexture(Action<Camera, RenderTexture> targetTextureEventHandler)
{
if (controller)
{
targetTextureEventHandler(controller.TargetCamera, null);
}
TargetTextureChange -= targetTextureEventHandler;
if (TargetTextureChange == null && request != null)
{
request.RemoveCommandBuffer(controller ? controller.TargetCamera : null);
request.Dispose();
request = null;
}
}
public void OnAssemble(ARSession session)
{
session.FrameChange += OnFrameChange;
session.FrameUpdate += OnFrameUpdate;
}
public void SetHFilp(bool hFlip)
{
renderImageHFlip = hFlip;
}
private void OnFrameChange(OutputFrame outputFrame, Matrix4x4 displayCompensation)
{
if (outputFrame == null)
{
material = null;
UpdateCommandBuffer(controller ? controller.TargetCamera : null, material);
if (request != null)
{
request.UpdateCommandBuffer(controller ? controller.TargetCamera : null, material);
RenderTexture texture;
if (TargetTextureChange != null && request.UpdateTexture(controller.TargetCamera, material, out texture))
{
TargetTextureChange(controller.TargetCamera, texture);
}
}
return;
}
if (!enabled && request == null && OnFrameRenderUpdate == null)
{
return;
}
using (var frame = outputFrame.inputFrame())
{
using (var image = frame.image())
{
var materialUpdated = arMaterial.UpdateByImage(image);
if (material != materialUpdated)
{
material = materialUpdated;
UpdateCommandBuffer(controller ? controller.TargetCamera : null, material);
if (request != null) { request.UpdateCommandBuffer(controller ? controller.TargetCamera : null, material); }
}
}
if (cameraParameters != null)
{
cameraParameters.Dispose();
}
cameraParameters = frame.cameraParameters();
}
}
private void OnFrameUpdate(OutputFrame outputFrame)
{
if (!controller || (!enabled && request == null && OnFrameRenderUpdate == null))
{
return;
}
if (request != null)
{
RenderTexture texture;
if (TargetTextureChange != null && request.UpdateTexture(controller.TargetCamera, material, out texture))
{
TargetTextureChange(controller.TargetCamera, texture);
}
}
if (!material)
{
return;
}
bool cameraFront = cameraParameters.cameraDeviceType() == CameraDeviceType.Front ? true : false;
var imageProjection = cameraParameters.imageProjection(controller.TargetCamera.aspect, EasyARController.Instance.Display.Rotation, false, cameraFront? !renderImageHFlip : renderImageHFlip).ToUnityMatrix();
if (renderImageHFlip)
{
var translateMatrix = Matrix4x4.identity;
translateMatrix.m00 = -1;
imageProjection = translateMatrix * imageProjection;
}
material.SetMatrix("_TextureRotation", imageProjection);
if (OnFrameRenderUpdate != null)
{
OnFrameRenderUpdate(material, new Vector2(Screen.width * controller.TargetCamera.rect.width, Screen.height * controller.TargetCamera.rect.height));
}
}
private void UpdateCommandBuffer(Camera cam, Material material)
{
RemoveCommandBuffer(cam);
if (!cam || !material)
{
return;
}
if (enabled)
{
commandBuffer = new CommandBuffer();
commandBuffer.Blit(null, BuiltinRenderTextureType.CameraTarget, material);
cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
}
private void RemoveCommandBuffer(Camera cam)
{
if (commandBuffer != null)
{
if (cam)
{
cam.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
commandBuffer.Dispose();
commandBuffer = null;
}
}
private class UserRequest : IDisposable
{
private RenderTexture texture;
private CommandBuffer commandBuffer;
~UserRequest()
{
if (commandBuffer != null) { commandBuffer.Dispose(); }
if (texture) { Destroy(texture); }
}
public void Dispose()
{
if (commandBuffer != null) { commandBuffer.Dispose(); }
if (texture) { Destroy(texture); }
GC.SuppressFinalize(this);
}
public bool UpdateTexture(Camera cam, Material material, out RenderTexture tex)
{
tex = texture;
if (!cam || !material)
{
if (texture)
{
Destroy(texture);
tex = texture = null;
return true;
}
return false;
}
int w = (int)(Screen.width * cam.rect.width);
int h = (int)(Screen.height * cam.rect.height);
if (texture && (texture.width != w || texture.height != h))
{
Destroy(texture);
}
if (texture)
{
return false;
}
else
{
texture = new RenderTexture(w, h, 0);
UpdateCommandBuffer(cam, material);
tex = texture;
return true;
}
}
public void UpdateCommandBuffer(Camera cam, Material material)
{
RemoveCommandBuffer(cam);
if (!cam || !material)
{
return;
}
if (texture)
{
commandBuffer = new CommandBuffer();
commandBuffer.Blit(null, texture, material);
cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
}
public void RemoveCommandBuffer(Camera cam)
{
if (commandBuffer != null)
{
if (cam)
{
cam.RemoveCommandBuffer(CameraEvent.BeforeForwardOpaque, commandBuffer);
}
commandBuffer.Dispose();
commandBuffer = null;
}
}
}
}
}
Here is the VideoCameraDevice.cs script:
//================================================================================================================================
//
// Copyright (c) 2015-2019 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
// EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
// and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//================================================================================================================================
using System;
using UnityEngine;
namespace easyar
{
public class VideoCameraDevice : CameraSource
{
/// <summary>
/// EasyAR Sense API. Accessible between DeviceCreated and DeviceClosed event if available.
/// </summary>
public CameraDevice Device { get; private set; }
public CameraDeviceFocusMode FocusMode = CameraDeviceFocusMode.Continousauto;
public Vector2 CameraSize = new Vector2(1280, 960);
public CameraDeviceOpenMethod CameraOpenMethod = CameraDeviceOpenMethod.DeviceType;
[HideInInspector, SerializeField]
public CameraDeviceType CameraType = CameraDeviceType.Back;
[HideInInspector, SerializeField]
public int CameraIndex = 0;
[HideInInspector, SerializeField]
private CameraDevicePreference cameraPreference = CameraDevicePreference.PreferObjectSensing;
private CameraParameters parameters = null;
private bool willOpen;
public event Action DeviceCreated;
public event Action DeviceOpened;
public event Action DeviceClosed;
public enum CameraDeviceOpenMethod
{
DeviceType,
DeviceIndex,
}
public override int BufferCapacity
{
get
{
if (Device != null)
{
return Device.bufferCapacity();
}
return bufferCapacity;
}
set
{
bufferCapacity = value;
if (Device != null)
{
Device.setBufferCapacity(value);
}
}
}
public override bool HasSpatialInformation
{
get { return false; }
}
public CameraDevicePreference CameraPreference
{
get { return cameraPreference; }
// Switch to prefered FocusMode when switch CameraPreference.
// You can set other FocusMode after this, but the tracking results may differ.
set
{
cameraPreference = value;
switch (cameraPreference)
{
case CameraDevicePreference.PreferObjectSensing:
FocusMode = CameraDeviceFocusMode.Continousauto;
break;
case CameraDevicePreference.PreferSurfaceTracking:
FocusMode = CameraDeviceFocusMode.Medium;
break;
default:
break;
}
}
}
public CameraParameters Parameters
{
get
{
if (Device != null)
{
return Device.cameraParameters();
}
return parameters;
}
set
{
parameters = value;
}
}
protected override void OnEnable()
{
base.OnEnable();
if (Device != null)
{
Device.start();
}
}
protected override void Start()
{
if (!CameraDevice.isAvailable())
{
throw new UIPopupException(typeof(CameraDevice) + " not available");
}
base.Start();
}
protected override void OnDisable()
{
base.OnDisable();
if (Device != null)
{
Device.stop();
}
}
public override void Open()
{
willOpen = true;
CameraDevice.requestPermissions(EasyARController.Scheduler, (Action<PermissionStatus, string>)((status, msg) =>
{
if (!willOpen)
{
return;
}
if (status != PermissionStatus.Granted)
{
throw new UIPopupException("Camera permission not granted");
}
Close();
Device = CameraDeviceSelector.createCameraDevice(CameraPreference);
if (DeviceCreated != null)
{
DeviceCreated();
}
bool openResult = false;
switch (CameraOpenMethod)
{
case CameraDeviceOpenMethod.DeviceType:
openResult = Device.openWithPreferredType(CameraType);
break;
case CameraDeviceOpenMethod.DeviceIndex:
openResult = Device.openWithIndex(CameraIndex);
break;
default:
break;
}
if (!openResult)
{
Debug.LogError("Camera open failed");
Device.Dispose();
Device = null;
return;
}
Device.setFocusMode(FocusMode);
Device.setSize(new Vec2I((int)CameraSize.x, (int)CameraSize.y));
if (parameters != null)
{
Device.setCameraParameters(parameters);
}
if (bufferCapacity != 0)
{
Device.setBufferCapacity(bufferCapacity);
}
if (sink != null)
Device.inputFrameSource().connect(sink);
if (DeviceOpened != null)
{
DeviceOpened();
}
if (enabled)
{
OnEnable();
}
}));
}
public override void Close()
{
willOpen = false;
if (Device != null)
{
OnDisable();
Device.close();
Device.Dispose();
if (DeviceClosed != null)
{
DeviceClosed();
}
Device = null;
}
}
public override void Connect(InputFrameSink val)
{
base.Connect(val);
if (Device != null)
{
Device.inputFrameSource().connect(val);
}
}
}
}
Here is the RenderCameraController.cs script:
//================================================================================================================================
//
// Copyright (c) 2015-2019 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
// EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
// and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//================================================================================================================================
using UnityEngine;
namespace easyar
{
public class RenderCameraController : MonoBehaviour
{
public Camera TargetCamera;
public RenderCameraParameters ExternalParameters;
private CameraImageRenderer cameraRenderer;
private Matrix4x4 currentDisplayCompensation = Matrix4x4.identity;
private CameraParameters cameraParameters;
private bool projectHFilp;
private ARSession arSession;
protected virtual void OnEnable()
{
if (arSession)
{
arSession.FrameChange += OnFrameChange;
arSession.FrameUpdate += OnFrameUpdate;
}
}
protected virtual void OnDisable()
{
if (arSession)
{
arSession.FrameChange -= OnFrameChange;
arSession.FrameUpdate -= OnFrameUpdate;
}
}
protected virtual void OnDestroy()
{
if (cameraParameters != null)
{
cameraParameters.Dispose();
}
if (ExternalParameters)
{
ExternalParameters.Dispose();
}
}
internal void OnAssemble(ARSession session)
{
arSession = session;
if (!TargetCamera)
{
TargetCamera = session.Assembly.Camera;
}
if (enabled)
{
arSession.FrameChange += OnFrameChange;
arSession.FrameUpdate += OnFrameUpdate;
}
cameraRenderer = GetComponent<CameraImageRenderer>();
if (cameraRenderer)
{
cameraRenderer.OnAssemble(session);
}
}
internal void SetProjectHFlip(bool hFlip)
{
projectHFilp = hFlip;
}
internal void SetRenderImageHFilp(bool hFlip)
{
if (cameraRenderer)
{
cameraRenderer.SetHFilp(hFlip);
}
}
private void OnFrameChange(OutputFrame outputFrame, Matrix4x4 displayCompensation)
{
if (outputFrame == null)
{
return;
}
currentDisplayCompensation = displayCompensation.inverse;
using (var frame = outputFrame.inputFrame())
{
if (cameraParameters != null)
{
cameraParameters.Dispose();
}
cameraParameters = frame.cameraParameters();
if (ExternalParameters)
{
ExternalParameters.Build(cameraParameters);
}
}
}
private void OnFrameUpdate(OutputFrame outputFrame)
{
var camParameters = ExternalParameters ? ExternalParameters.Parameters : cameraParameters;
var projection = camParameters.projection(TargetCamera.nearClipPlane, TargetCamera.farClipPlane, TargetCamera.aspect, EasyARController.Instance.Display.Rotation, false, false).ToUnityMatrix();
if (ExternalParameters)
{
projection *= ExternalParameters.Transform;
}
projection *= currentDisplayCompensation;
if (projectHFilp)
{
var translateMatrix = Matrix4x4.identity;
translateMatrix.m00 = -1;
projection = translateMatrix * projection;
}
TargetCamera.projectionMatrix = projection;
GL.invertCulling = projectHFilp;
}
}
}
I am getting desperate on how to solve the problem.
So if anyone is facing the same issue:
to solve the blackscreen simply open "CameraImageRenderer.cs", go to line 180 and replace the line
commandBuffer.Blit(null, BuiltinRenderTextureType.CameraTarget, material);
with
commandBuffer.Blit(material.HasProperty("_MainTex") ? material.GetTexture("_MainTex") : null, BuiltinRenderTextureType.CameraTarget, material);
This issue got solved in the latest update on EasyAR 4.1. I found the solution here: https://www.easyar.cn/view/questionDetails.html#163

PlayerPrefs won't save or load anything when I build the game

I'm trying to make a simple script that saves a text from an input field, like a "remember me" check box. It works fine on the editor. It saves, it logs correctly, it loads fine. But when I build the scene it doesn't work, it's like PlayerPrefs never saves anything. Anyone knows why?
public Toggle rememberMe;
public InputField fieldMail;
public InputField fieldPassword;
private string mail;
private string password;
private int rememberMeChecked = 0;
private bool justChecked = false;
public EventTrigger loginButton;
private void Awake()
{
if (!PlayerPrefs.HasKey("Checked")) { PlayerPrefs.SetInt("Checked", 0); }
if (!PlayerPrefs.HasKey("Mail")) { PlayerPrefs.SetString("Mail", string.Empty); }
if (!PlayerPrefs.HasKey("Password")) { PlayerPrefs.SetString("Password", string.Empty); }
}
private void Start()
{
rememberMeChecked = PlayerPrefs.GetInt("Checked");
mail = PlayerPrefs.GetString("Mail");
password = PlayerPrefs.GetString("Password");
if (!mail.Equals(string.Empty)) { fieldMail.text = mail; }
if (!password.Equals(string.Empty)) { fieldPassword.text = password; }
if (rememberMeChecked == 0) { rememberMe.isOn = false; }
if (rememberMeChecked == 1) { rememberMe.isOn = true; }
justChecked = rememberMe.isOn;
rememberMe.onValueChanged.AddListener(delegate {
justChecked = !justChecked;
if (justChecked) { Save(1); }
else { Save(0); }
LogInfo();
});
}
private void OnEnable()
{
fieldMail.text = mail;
fieldPassword.text = password;
if (rememberMeChecked == 0) { rememberMe.isOn = false; }
if (rememberMeChecked == 1) { rememberMe.isOn = true; }
}
public void OnButtonClicked()
{
PlayerPrefs.Save();
Debug.Log("PlayerPrefs saved.");
LogInfo();
}
public void Save(int rememberMeChecked)
{
switch (rememberMeChecked)
{
case 1:
{
PlayerPrefs.SetString("Mail", fieldMail.text);
PlayerPrefs.SetString("Password", fieldPassword.text);
PlayerPrefs.SetInt("Checked", rememberMeChecked);
}
break;
case 0:
default:
{
PlayerPrefs.SetString("Mail", string.Empty);
PlayerPrefs.SetString("Password", string.Empty);
PlayerPrefs.SetInt("Checked", rememberMeChecked);
}
break;
}
PlayerPrefs.Save();
}

Unsure why logic will not turn game object active

My logic has worked in the past, but it is unclear why this does not work to turn my gameobjects active. This is especially puzzling as I have no errors. The fields match up correctly and I have no errors as mentioned. This is used for an inventory system.
public class addToArtMode : MonoBehaviour
{
public bool On = true;
public GameObject slotPanel;
void Start()
{
this.GetComponent<Button>().onClick.AddListener(() => AddToArtMode());
slotPanel = GameObject.Find("Slot Panel");
}
public void AddToArtMode()
{
if (On)
{
Debug.Log("ID'ing ");
slotPanel = GameObject.Find("Slot Panel");
foreach (Transform child in slotPanel.transform)
{
var parentName = this.GetComponentInParent<ArtBrowseContentInformation>().NameofArt;
var childname = child.GetComponent<InventoryContentInfo>().NameofArt;
if (childname == parentName)
{
child.gameObject.SetActive(true);
On = false;
Debug.Log("what ");
}
else
{
Off();
}
}
void Off()
{
foreach (Transform child in slotPanel.transform)
{
var parentName = this.GetComponentInParent<ArtBrowseContentInformation>().NameofArt;
var childname = child.GetComponent<InventoryContentInfo>().NameofArt;
if (childname == parentName)
{
Debug.Log("the heck");
child.gameObject.SetActive(false);
On = true;
}
}
}
}
}
}
}
try gameObject.enabled = false/true;

How to convert listener from Java to C#

I need to display radio options as a grid and found this gist offering a RadioGridGroup class I can use instead of the RadioGroup provided in Android. (https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33)
I have converted everything into the code below and just wanted to ask some questions.
(1) Is it okay that I just deleted all the "final" keywords? ie int result = sNextGeneratedId.Get();
(2) Is converting parent == RadioGridGroup.this && child instanceof AppCompatRadioButton to parent is RadioGridGroup && child is AppCompatRadioButton an accurate conversion?
(3) In the CheckedStateTracker which extends/implements a Listener.. It has access to the RadioGridGroup fields like mProtectFromCheckedChange in the Java example, but in the C# example, those variables are inaccessable there. How do I solve this?
(4) For the listeners I had them implement Java.Lang.Object as well so I don't need to implement the Dispose side CheckedStateTracker implements CompoundButton.OnCheckedChangeListener became CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Java.Util.Concurrent.Atomic;
/**
* https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
*
* <p>This class is used to create a multiple-exclusion scope for a set of radio
* buttons. Checking one radio button that belongs to a radio group unchecks
* any previously checked radio button within the same group.</p>
* <p/>
* <p>Intially, all of the radio buttons are unchecked. While it is not possible
* to uncheck a particular radio button, the radio group can be cleared to
* remove the checked state.</p>
* <p/>
* <p>The selection is identified by the unique id of the radio button as defined
* in the XML layout file.</p>
* <p/>
* <p>See
* {#link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
* for layout attributes.</p>
*
* #see AppCompatRadioButton
*/
public class RadioGridGroup: Android.Support.V7.Widget.GridLayout
{
private static AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private int mCheckedId = -1;
private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
private bool mProtectFromCheckedChange = false;
private OnCheckedChangeListener mOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
public RadioGridGroup(Context context) : base(context)
{
init();
}
public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
{
init();
}
private void init()
{
mChildOnCheckedChangeListener = new CheckedStateTracker();
mPassThroughListener = new PassThroughHierarchyChangeListener();
base.SetOnHierarchyChangeListener(mPassThroughListener);
}
public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
{
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
protected override void OnFinishInflate()
{
base.OnFinishInflate();
if (mCheckedId != -1)
{
mProtectFromCheckedChange = true;
SetCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
setCheckedId(mCheckedId);
}
}
public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
{
if (child is AppCompatRadioButton) {
AppCompatRadioButton button = (AppCompatRadioButton)child;
if (button.Checked)
{
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.Id);
}
}
base.AddView(child, index, prs);
}
public void check(int id)
{
if (id != -1 && (id == mCheckedId))
{
return;
}
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
if (id != -1)
{
SetCheckedStateForView(id, true);
}
setCheckedId(id);
}
private void setCheckedId(int id)
{
mCheckedId = id;
if (mOnCheckedChangeListener != null)
{
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
private void SetCheckedStateForView(int viewId, bool chkd)
{
View checkedView = FindViewById(viewId);
if (checkedView != null && checkedView is AppCompatRadioButton) {
((AppCompatRadioButton)checkedView).Checked = (chkd);
}
}
public int GetCheckedCheckableImageButtonId()
{
return mCheckedId;
}
public void clearCheck()
{
check(-1);
}
public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
{
mOnCheckedChangeListener = listener;
}
public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
{
base.OnInitializeAccessibilityEvent(ev);
ev.ClassName = (this.GetType().Name);
}
public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(info);
info.ClassName = (this.GetType().Name);
}
public interface OnCheckedChangeListener
{
void onCheckedChanged(RadioGridGroup group, int checkedId);
}
internal class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (mProtectFromCheckedChange)
{
return;
}
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
int id = buttonView.Id;
SetCheckId(id);
}
}
internal class PassThroughHierarchyChangeListener : Java.Lang.Object
ViewGroup.IOnHierarchyChangeListener
{
internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
public void OnChildViewAdded(View parent, View child)
{
if (parent is RadioGridGroup && child is AppCompatRadioButton) {
int id = child.Id;
// generates an id if it's missing
if (id == View.NoId)
{
id = View.GenerateViewId();
child.Id = (id);
}
((AppCompatRadioButton)child).SetOnCheckedChangeListener(
mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
}
}
public void OnChildViewRemoved(View parent, View child)
{
if (parent is RadioGridGroup && child is AppCompatRadioButton) {
((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
}
}
}
public static int GenerateViewId()
{
for (; ; )
{
int result = sNextGeneratedId.Get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.CompareAndSet(result, newValue))
{
return result;
}
}
}
}
UPDATE:
Below is my code which includes passing the enclosing parent to the listeners, and using those to compare the parent as well as to access the parent's fields. I've also added readonly to just the one location where it didn't error out to do so.
using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Java.Util.Concurrent.Atomic;
/**
* https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
*
* <p>This class is used to create a multiple-exclusion scope for a set of radio
* buttons. Checking one radio button that belongs to a radio group unchecks
* any previously checked radio button within the same group.</p>
* <p/>
* <p>Intially, all of the radio buttons are unchecked. While it is not possible
* to uncheck a particular radio button, the radio group can be cleared to
* remove the checked state.</p>
* <p/>
* <p>The selection is identified by the unique id of the radio button as defined
* in the XML layout file.</p>
* <p/>
* <p>See
* {#link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
* for layout attributes.</p>
*
* #see AppCompatRadioButton
*/
public class RadioGridGroup : Android.Support.V7.Widget.GridLayout
{
private static readonly AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private int mCheckedId = -1;
private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
private bool mProtectFromCheckedChange = false;
private OnCheckedChangeListener mOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
public RadioGridGroup(Context context) : base(context)
{
Init();
}
public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
{
Init();
}
private void Init()
{
mChildOnCheckedChangeListener = new CheckedStateTracker(this);
mPassThroughListener = new PassThroughHierarchyChangeListener(this);
base.SetOnHierarchyChangeListener(mPassThroughListener);
}
public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
{
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
protected override void OnFinishInflate()
{
base.OnFinishInflate();
if (mCheckedId != -1)
{
mProtectFromCheckedChange = true;
SetCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
SetCheckedId(mCheckedId);
}
}
public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
{
if (child is AppCompatRadioButton) {
AppCompatRadioButton button = (AppCompatRadioButton)child;
if (button.Checked)
{
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
SetCheckedId(button.Id);
}
}
base.AddView(child, index, prs);
}
public void check(int id)
{
if (id != -1 && (id == mCheckedId))
{
return;
}
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
if (id != -1)
{
SetCheckedStateForView(id, true);
}
SetCheckedId(id);
}
private void SetCheckedId(int id)
{
mCheckedId = id;
if (mOnCheckedChangeListener != null)
{
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
private void SetCheckedStateForView(int viewId, bool chkd)
{
View checkedView = FindViewById(viewId);
if (checkedView != null && checkedView is AppCompatRadioButton) {
((AppCompatRadioButton)checkedView).Checked = (chkd);
}
}
public int GetCheckedCheckableImageButtonId()
{
return mCheckedId;
}
public void clearCheck()
{
check(-1);
}
public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
{
mOnCheckedChangeListener = listener;
}
public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
{
base.OnInitializeAccessibilityEvent(ev);
ev.ClassName = (this.GetType().Name);
}
public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(info);
info.ClassName = (this.GetType().Name);
}
public interface OnCheckedChangeListener
{
void onCheckedChanged(RadioGridGroup group, int checkedId);
}
private class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
readonly RadioGridGroup enclosingClass;
public CheckedStateTracker(RadioGridGroup enclosing)
{
enclosingClass = enclosing;
}
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (enclosingClass.mProtectFromCheckedChange)
{
return;
}
enclosingClass.mProtectFromCheckedChange = true;
if (enclosingClass.mCheckedId != -1)
{
enclosingClass.SetCheckedStateForView(enclosingClass.mCheckedId, false);
}
enclosingClass.mProtectFromCheckedChange = false;
int id = buttonView.Id;
enclosingClass.SetCheckedId(id);
}
}
private class PassThroughHierarchyChangeListener : Java.Lang.Object,
ViewGroup.IOnHierarchyChangeListener
{
internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
readonly RadioGridGroup enclosingClass;
public PassThroughHierarchyChangeListener(RadioGridGroup enclosing)
{
enclosingClass = enclosing;
}
public void OnChildViewAdded(View parent, View child)
{
if (parent == enclosingClass && child is AppCompatRadioButton) {
int id = child.Id;
// generates an id if it's missing
if (id == View.NoId)
{
id = View.GenerateViewId();
child.Id = (id);
}
((AppCompatRadioButton)child).SetOnCheckedChangeListener(
enclosingClass.mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
}
}
public void OnChildViewRemoved(View parent, View child)
{
if (parent == enclosingClass && child is AppCompatRadioButton) {
((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
}
}
}
public static int GenerateViewId()
{
for (; ; )
{
int result = sNextGeneratedId.Get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.CompareAndSet(result, newValue))
{
return result;
}
}
}
}
To answer your multiple questions:
It is ok in this scenario. You need to first understand what final is used for in Java. Here, you can replace final with readonly for all the three fields/variables.
The conversion isn't accurate. In the first case, you are doing an instance comparison of the parent instance whereas in the second case you are doing the type comparison of the parent instance.
As per this SO post , in C#, the nested class do not hold reference of the enclosing class. So it is basically not possible to do something like RadioGridGroup.this in C#, because even though RadioGridGroup encloses the class where RadiGridGroup.this field is being accessed, the reference to that is not available in the enclosed class. Hence, you cannot refer to any private member of the this instance of the RadioGridGroup class. The solution to this is simple. Just treat RadioGridGroup as any other class inside PassThroughHierarchyChangeListener and pass a reference to the this instance of the RadioGridGroup to PassThroughHierarchyChangeListener constructor. So your code would change to something like
internal class CheckedStateTracker : CompoundButton.IOnCheckedChangeListener {
//...
readonly RadioGridGroup enclosingClass;
//Constructor of CheckedStateTracker
//With this, access the members of RadioGridGroup class with enclosingClass scope
//So mProtectFromCheckedChange becomes enclosingClass.mProtectFromCheckedChange
CheckedStateTracker ( RadioGridGroup enclosing){
enclosingClass = enclosing;
}
}
Finally, you instantiate your mChildOnCheckedChangeListener as the following
//Line 51
mChildOnCheckedChangeListener = new CheckedStateTracker(this);

How can I add a global bool flag to control all the doors lock states?

Now it's working fine in editor and in runtime for each door.
But I want to add a global public flag that will control all the doors at once in editor and in runtime. If I change the global flag to true all the doors will be locked and same if set to false.
The DoorsLockManager script:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
And the editor script:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
// FindPropertyRelative seems not to only work for MonoBehaviour classes
// so we have to use this hack around
var serializedDoor = new SerializedObject(door.objectReferenceValue);
// If it's public no worry anyway
// If it's private still works since we made it a SerializeField
var lockState = serializedDoor.FindProperty("doorLockState");
// Fetch current values into the serialized "copy"
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// for the PropertyField there is
// no return value since it automatically uses
// the correct drawer for the according property
// and directly changes it's value
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
// or alternatively
//lockState.boolValue = EditorGUILayout.Toggle("Door " + i + " Lockstate", lockState.boolValue);
// Write back changes, mark as dirty if changed
// and add a Undo history entry
serializedDoor.ApplyModifiedProperties();
}
}
}
Well, you could simply add one to the DoorsLockManager
[ExecuteInEditMode]
public class DoorsLockManager : MonoBehaviour
{
[HideInInspector]
public List<HoriDoorManager> Doors = new List<HoriDoorManager>();
// The global state
[SerializeField] private bool _globalLockState;
// During runtime use a property instead
public bool GlobalLockState
{
get { return _globalLockState; }
set
{
_globalLocakState = value;
// apply it to all doors
foreach(var door in Doors)
{
// now you would need it public again
// or use the public property you had there
Door.doorLockState = _globalLocakState;
}
}
}
private void Awake()
{
var doors = GameObject.FindGameObjectsWithTag("Door");
Doors = new HoriDoorManager[doors.Length].ToList();
for (int i = 0; i < doors.Length; i++)
{
Doors[i] = doors[i].GetComponent<HoriDoorManager>();
}
}
}
and in the editor make it "overwrite" all the other flags if changed:
[CustomEditor(typeof(DoorsLockManager))]
public class DoorsLockManagerEditor : Editor
{
private SerializedProperty _doors;
private SerializedProperty _globalLockState;
private bool shouldOverwrite;
private void OnEnable()
{
_doors = serializedObject.FindProperty("Doors");
_globalLockState = serializedObject.FindProperty("_globalLockState");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
shouldOverwrite = false;
// Begin a change check here
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(_globalLockState);
if(EditorGUI.EndChangeCheck())
{
// overwrite only once if changed
shouldOverwrite = true;
}
for (int i = 0; i < _doors.arraySize; i++)
{
var door = _doors.GetArrayElementAtIndex(i);
// if door == null the script itself has an error since it can't even find the SerializedProperty
if (door == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get door property", target);
return;
}
if (door.objectReferenceValue == null) continue;
var serializedDoor = new SerializedObject(door.objectReferenceValue);
var lockState = serializedDoor.FindProperty("doorLockState");
serializedDoor.Update();
if (lockState == null)
{
EditorGUILayout.HelpBox("There was an error in the editor script!\nPlease check the log", MessageType.Error);
Debug.LogError("Couldn't get lockState property", target);
return;
}
// HERE OVERWRITE
if(shouldOverwrite)
{
lockState.boolValue = _globalLockState.boolValue;
}
else
{
EditorGUILayout.PropertyField(lockState, new GUIContent("Door " + i + " Lockstate"));
}
serializedDoor.ApplyModifiedProperties();
}
serializedObject.ApplyModifiedProperties();
}
}
You could create a static class with the flag in it, and then have the door's "activated" state tied to it. Example:
public static class GlobalVariables()
{
public static bool DoorsLocked = true; //Doors would be locked dependent on logic
}
Then you just set the doors' state to the global variable in code. When you change the global bool, the doors change.
GlobalVariables.DoorsLocked = false; //unlocks all doors reading the global bool
Hope this helps!

Categories

Resources