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.