- Home /
Inversion of Control questions
Hi folks, I'm very new to architectureing and designing applications (in particular games). I've had a big headache developing games before (as I now understand is because of a bad design). So now I roughly decided to dig into SOLID principle and it's 'D' letter which stands for dependency inversion. Unfortunately, I have already developed a small part of my new game without following DI principle and have to refactor code in order to move further. I've read a couple of articles but still have a questions.
I would realy appreciate any help here to understand this principle and apply it on real life.
I have a 'WelcomeScene' and three buttons on it: 'Tryout', 'Signup' and 'Login'.
Tryout simply goes to 'main scene'. Signup shows 'RegisterUser' window. Login shows 'Login' window. User can reset password in 'ResetPassword' window.
WelcomeMenu.cs
using UnityEngine;
using UnityEngine.UI;
namespace MyWorldTraveler.UI.Menus
{
public sealed class WelcomeMenu : MonoBehaviour
{
public GameObject TestPopup;
public GameObject LoginButton;
public GameObject SignupButton;
public GameObject TryoutButton;
public GameObject CreateUserWindow;
public GameObject LoginWindow;
private Text loginButtonText;
void Awake()
{
loginButtonText = LoginButton.GetComponentInChildren<Text>();
}
// Use this for initialization
void Start()
{
checkFacebookInitialize();
}
void OnDestroy()
{
//prevent memory leak here.
FB.OnInitComplete -= menuButtonsInitialize;
}
public void OnTryoutButtonPressed()
{
}
public void OnSignupButtonPressed()
{
//TODO: Check internet availability here
CreateUserWindow.SetActive(true);
}
public void OnLoginButtonPressed()
{
LoginWindow.SetActive(true);
}
private void OnSuccessfulLogin()
{
TestPopup.SetActive(true);
var content = TestPopup.transform.Find("Content").GetComponent<Text>();
content.text = "Successfuly logged in!";
loginButtonText.text = "Logout";
}
public void OnOkButtonPressed()
{
TestPopup.SetActive(false);
}
private void checkFacebookInitialize()
{
if (FB.IsInitialized)
{
menuButtonsInitialize();
}
else
{
FB.OnInitComplete = menuButtonsInitialize;
}
}
private void menuButtonsInitialize()
{
loginButtonText.text = "Logout";
TryoutButton.GetComponent<Button>().enabled = false;
SignupButton.GetComponent<Button>().enabled = false;
}
}
}
Login.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using MyWorldTraveler.Social;
using MyWorldTraveler.Tools;
using com.shephertz.app42.paas.sdk.csharp.user;
using com.shephertz.app42.paas.sdk.csharp;
namespace MyWorldTraveler.UI.Social
{
/// <summary>
/// Login window's script at welcome scene.
/// </summary>
public sealed class Login : MonoBehaviour
{
public InputField LoginField;
public InputField PasswordField;
public GameObject ResetPasswordPopup;
public Button LoginButton;
private string _login = string.Empty;
private string _password = string.Empty;
void OnEnable()
{
ResetPasswordPopup.SetActive(false);
LoginButton.gameObject.SetActive(false);
UsersManager.Instance.OnPasswordResetted += OnPasswordResettedHandler;
}
void OnDisable()
{
LoginField.text.text = string.Empty;
PasswordField.text.text = string.Empty;
UsersManager.Instance.OnPasswordResetted -= OnPasswordResettedHandler;
}
void OnPasswordResettedHandler()
{
//TODO: Somehow tell a user that password has been successfuly resetted.
}
public void OnLoginFieldValueChanged(InputField input)
{
_login = input.value;
if (_login.Length.IsPositive() && _password.Length.IsPositive())
{
LoginButton.gameObject.SetActive(true);
}
else
{
}
}
public void OnPasswordFieldValueChanged(InputField input)
{
_password = input.value;
if (_password.Length.IsPositive())
{
LoginButton.gameObject.SetActive(true);
}
else
{
}
}
public void OnFacebookLoginButtonPressed()
{
BasicFacebookSocial.Instance.Authenticate(authenticationSuccessResponse =>
{
if (authenticationSuccessResponse) GoTo.MainScene();
});
}
public void OnLoginButtonPressed()
{
if (_login.IsEmail())
{
UsersManager.Instance.app42loginUserByEmail(_login, _password, successAction(), failAction());
}
else
{
UsersManager.Instance.app42loginUser(_login, _password, successAction(), failAction());
}
}
public void OnForgotPasswordButtonPressed()
{
ResetPasswordPopup.SetActive(true);
}
public void OnCancelButtonPressed()
{
gameObject.SetActive(false);
}
private Action<User> successAction()
{
return (user) =>
{
GoTo.MainScene();
};
}
private Action<App42Exception> failAction()
{
return (exception) =>
{
var code = exception.GetAppErrorCode();
workoutLoginFail(code);
};
}
private void workoutLoginFail(int errorCode)
{
}
}
}
RegisterUser.cs
using UnityEngine;
using UnityEngine.UI;
using MyWorldTraveler.Social;
using MyWorldTraveler.Settings;
using MyWorldTraveler.Tools;
using System.Collections;
namespace MyWorldTraveler.UI.Social
{
/// <summary>
/// Register a user window's script at welcome scene.
/// </summary>
public sealed class RegisterUser : MonoBehaviour
{
public Button SubmitButton;
public Toggle AvailabilityToggle;
public InputField Username;
public InputField Email;
public Text Status;
private string _username;
private string _email;
private string _password;
private string _repassword;
private bool _isUsernameValid;
private bool _isEmailValid;
private bool _isPasswordValid;
void Start()
{
SubmitButton.gameObject.SetActive(false);
AvailabilityToggle.isOn = false;
}
public void OnSubmitButtonPressed()
{
UsersManager.Instance.app42CreateUser(_username, _password, _email, successCallback: user =>
{
//somehow use app42's user object here
StartCoroutine(workoutSignUpSuccess());
}, errorCallback: fail =>
{
//tell a user that registration failed
var code = fail.GetAppErrorCode();
workoutErrorCode(code);
});
}
public void OnCancelButtonPressed()
{
gameObject.SetActive(false);
}
public void OnUsernameValueChanged(InputField input)
{
if (input.value.Length >= GameConstants.MinimumUsernameLength)
{
_username = input.value;
_isUsernameValid = true;
AvailabilityToggle.isOn = true;
SubmitButton.gameObject.SetActive(isSubmitAvailable());
}
else
{
//Status.text = "Username must be at least 4 characters long";
AvailabilityToggle.isOn = false;
_isUsernameValid = false;
SubmitButton.gameObject.SetActive(false);
}
}
public void OnEmailValueChanged(InputField input)
{
if (input.value.IsEmail())
{
_email = input.value;
_isEmailValid = true;
SubmitButton.gameObject.SetActive(isSubmitAvailable());
}
else
{
_isEmailValid = false;
SubmitButton.gameObject.SetActive(false);
}
}
public void OnPasswordValueChanged(InputField input)
{
_password = input.value;
if (input.value.Length >= GameConstants.MinimumPasswordLength && input.text.Equals(_repassword))
{
_isPasswordValid = true;
SubmitButton.gameObject.SetActive(isSubmitAvailable());
}
else
{
_isPasswordValid = false;
SubmitButton.gameObject.SetActive(false);
}
}
public void OnRepasswordValueChanged(InputField input)
{
_repassword = input.value;
if (input.value.Equals(_password))
{
_isPasswordValid = true;
SubmitButton.gameObject.SetActive(isSubmitAvailable());
}
else
{
_isPasswordValid = false;
SubmitButton.gameObject.SetActive(false);
}
}
private bool isSubmitAvailable()
{
return _isUsernameValid && _isEmailValid && _isPasswordValid;
}
private void workoutErrorCode(int code)
{
if (code.Equals(GameConstants.App42UserNameExistsErrorCode))
{
Username.text.text = string.Empty;
_isUsernameValid = false;
SubmitButton.gameObject.SetActive(false);
AvailabilityToggle.isOn = false;
Status.text = "This username has been already exists!";
}
else if (code.Equals(GameConstants.App42UserEmailExistsErrorCode))
{
Email.text.text = string.Empty;
_isEmailValid = false;
SubmitButton.gameObject.SetActive(false);
Status.text = "User with this e-mail has been already registered!";
}
}
IEnumerator workoutSignUpSuccess()
{
Status.color = Color.green;
Status.text = "OK!";
//TODO: Show visual effects
yield return new WaitForSeconds(1f);
GoTo.MainScene();
}
}
}
ResetPasswordPopup.cs
using System.Linq;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using MyWorldTraveler.Tools;
using MyWorldTraveler.Social;
namespace MyWorldTraveler.UI.Social
{
public sealed class ResetPasswordPopup : MonoBehaviour
{
public Button ResetButton;
public Text UserInputText;
private IEnumerable _parentSelectables;
private string _userInput;
void Awake()
{
_parentSelectables = transform.parent.GetComponentsInChildren<Selectable>()
.Where(sl => sl.transform.parent.name != transform.name)
.AsEnumerable<Selectable>();
}
void OnEnable()
{
disableParentMonoBehaviours();
}
void OnDisable()
{
enableParentMonoBehaviours();
}
public void OnUserInputInfoValueChange(InputField input)
{
var isActive = input.value.Length.IsPositive() ? true : false;
_userInput = input.value;
ResetButton.gameObject.SetActive(isActive);
}
public void OnResetPasswordButtonPressed()
{
UsersManager.Instance.app42resetPassword(_userInput,
successCallback: response =>
{
}, errorCallback: fail =>
{
var code = fail.GetAppErrorCode();
workoutResetPassowrdFail(code);
});
}
public void OnCancelButtonPressed()
{
gameObject.SetActive(false);
}
private void workoutResetPassowrdFail(int code)
{
UserInputText.text = "";
}
private void disableParentMonoBehaviours()
{
foreach (Selectable _mb in _parentSelectables)
{
_mb.enabled = false;
}
}
private void enableParentMonoBehaviours()
{
foreach (Selectable _mb in _parentSelectables)
{
_mb.enabled = true;
}
}
}
}
Should I treat fields of types GameObject, Button, Text, Toggle, InputField as dependencies? Should I wrap them in an abstraction, lets say, IControl?
In WelcomeMenu.cs I have calls to FB class of Facebook SDK but I have BasicFacebookSocial singleton class which performs init, authorize and logout and I have UsersManager singleton class which uses App42 services which provide login, logout and many other stuff. How can I merge these functionality into abstraction? Would it better to user interfaces or abstract classes?
How to deal with dependencies from the same layer ?
I really appreciate any of your help folks.
Answer by danvalho · Dec 30, 2014 at 08:02 PM
Considering that you are currently studying patterns and your target is to know the best practices in order to achieve your goals, I reccomend you to read Developer's Guide to Dependency Injection Using Unity. Unity (not Unity3d :P) is part of Microsoft's .Net framework and it has containers already implemented to permit Dependency Injection. And if you don't want to use Microsoft's Unity, reading this guide will be useful to you understand how dependency injection could be implemented using C# with some good practices, and so you can implement your own DI containers and providers. I hope I helped you.
I heard about it. But can I use it within Unity engine projects?
I think it is a good resource for you to understand DI. But if you want to use a DI framework alongside with unity3d, you can check an existent one like Strange
Your answer
![](https://koobas.hobune.stream/wayback/20220613175158im_/https://answers.unity.com/themes/thub/images/avi.jpg)