一、引言
在软件开发领域,设计模式是我们构建可维护、可扩展代码的重要指导原则之一。然而,MVC(Model-View-Controller)并非Gang of Four(GoF)总结的经典 23 种设计模式之一,所以确切说MVC不是一个设计模式,而是多种设计模式的组合。在MVC中,我们可以观察到类似于观察者模式、策略模式等设计模式的思想,MVC是一种思想或者软件架构模式。
二、MVC架构模式
MVC模式是一种将应用程序分为三个独立组件的软件架构模式。这三个组件分别是:
-
模型(Model): 负责应用程序的数据和业务逻辑。模型处理数据的获取、存储和处理,同时包含业务规则和逻辑。
-
视图(View): 负责展示模型的数据,以用户友好的方式呈现。视图是用户与应用程序交互的界面,负责接收用户输入并将其传递给控制器。
-
控制器(Controller): 充当模型和视图之间的协调者,处理用户的输入并更新模型和视图。控制器接收用户输入,调用模型进行相应的处理,然后更新视图以反映最新的数据状态。
MVC的灵感来自多种设计原则,如分离关注点、模块化和松耦合等,使得每个组件可以相对独立地进行开发、测试和维护。
三、MVC的业务流程
MVC的业务流程通常包括以下几个步骤:
-
用户输入: 用户与视图进行交互,通过用户输入触发事件。
-
控制器响应: 控制器接收用户输入事件,并根据业务逻辑调用相应的模型进行处理。
-
模型处理: 模型处理控制器传递过来的请求,执行相应的业务逻辑,可能涉及数据的增删改查等操作。
-
更新视图: 模型处理完成后,通知控制器数据的变化,控制器负责更新视图以展示最新的数据状态。
-
用户反馈: 视图更新后,用户可以看到相应的变化,并再次与视图进行交互。
这个业务流程形成了一个闭环,用户的每一次操作都会触发MVC中的一系列动作,保持应用程序的稳定和响应。
四、 DoTween简介
DoTween(DOTween)是一个用于Unity游戏引擎的强大的动画插值库。它提供了简单而强大的方式来创建平滑、高效的动画效果,包括对象的移动、旋转、缩放以及颜色等属性的变化。
1. 特性与优势
1.1 简单易用
DoTween通过链式调用的方式,使得动画的创建和管理变得非常直观和简便。这种方式允许您在一行代码中定义多个动画效果。
1.2 高性能
DoTween被设计为高性能的动画引擎,采用了一些优化技术,使得动画在运行时能够保持平滑且效率高。
1.3 支持多种属性
除了基本的位移、旋转和缩放,DoTween还支持颜色渐变、透明度变化等多种属性的动画效果。
1.4 缓动函数
DoTween内置了大量的缓动函数(easing functions),用于定义动画的变化速度曲线,从而创建更加生动和自然的动画效果。
五、基于MVC+DoTween来实现登录注册功能
下面我将用DoTween这个UI插件基于MVC的思想来实现一个卷轴效果的登入注册效果。以便更好的理解MVC以及DoTween的具体使用。以下是三个层的脚本类
1.制作UI
1.1. 创建一个名为EnterPanel的Panel物体,在该物体下面创建一个名为juanzhou的空物体,在该空物体上添加一个Horizontal Layout Group组件并修改参数
1.2. 在juanzhou物体下创建3个Image组件物体,分别作为卷轴的两个轴以及轴面。
1.3. 分别调整各种为合适大小 ,轴面需要添加一个Mask遮罩
1.4. 登录、注册、修改密码UI在卷轴中部分内
后面的那些组件的制作我就不再赘述,大家自行创建就好,主要就是上面的卷轴UI制作。
2.代码实现
2.1. 模型层
using System.Collections;
using System.Collections.Generic;
using Mr.Le.Utility.Manager;
using UnityEngine;
namespace MVC.Model
{
public static class LoginModel
{
private static Dictionary<string, UserData> userData;
public static string dataFilePath;
static LoginModel()
{
userData = new Dictionary<string, UserData>();
dataFilePath = "userData";
if (JsonManager.Instance.LoadData<Dictionary<string, UserData>>(dataFilePath) != null)
{
userData = JsonManager.Instance.LoadData<Dictionary<string, UserData>>(dataFilePath);
}
}
/// <summary>
/// 检查用户名是否存在
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
public static bool CheckUsernameExists(string username, AccountType accountType)
{
if (userData != null)
{
if (userData.ContainsKey(username) && userData[username].AccountType == accountType)
{
return true;
}
}
return false;
}
/// <summary>
/// 检查明用户密码是否正确
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public static bool CheckPassword(string username, string password, AccountType accountType)
{
if (userData.ContainsKey(username) && userData[username].AccountType == accountType)
{
return userData[username].Password == password;
}
return false;
}
/// <summary>
/// 注册账号
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="securityQuestion"></param>
/// <param name="securityAnswer"></param>
public static void RegisterUser(string username, string password, string securityQuestion, string securityAnswer, AccountType accountType)
{
UserData newUser = new UserData();
newUser.Password = password;
newUser.SecurityQuestion = securityQuestion;
newUser.SecurityAnswer = securityAnswer;
newUser.AccountType = accountType;
userData[username] = newUser;
JsonManager.Instance.SaveData(userData, dataFilePath);
}
/// <summary>
/// 检查密保答案是否正确
/// </summary>
/// <param name="username"></param>
/// <param name="securityAnswer"></param>
/// <returns></returns>
public static bool CheckSecurityAnswer(string username, string securityQuestion, string securityAnswer, AccountType accountType)
{
if (userData.ContainsKey(username) && userData[username].AccountType == accountType)
{
return userData[username].SecurityQuestion == securityQuestion &&
userData[username].SecurityAnswer == securityAnswer;
}
return false;
}
/// <summary>
/// 重置密码
/// </summary>
/// <param name="username"></param>
/// <param name="newPassword"></param>
public static void ResetPassword(string username, string newPassword, AccountType accountType)
{
if (userData.ContainsKey(username) && userData[username].AccountType == accountType)
{
userData[username].Password = newPassword;
JsonManager.Instance.SaveData(userData, dataFilePath);
}
}
}
//用户类型
public enum AccountType
{
Common, //普通用户
Admin //管理员
}
[System.Serializable]
public class UserData
{
public string Password;
public string SecurityQuestion;
public string SecurityAnswer;
public AccountType AccountType;
}
}
2.2. 控制层
这里也可以采用观察者设计模式的事件管理器将方法监听,然后在视图层去调用
using System.Text.RegularExpressions;
using Mr.Le.Utility.Manager;
using MVC.Model;
using MVC.View;
using UnityEngine;
namespace MVC.Controller
{
public class LoginController : MonoBehaviour
{
private LoginView _loginView;
private RegisterView _registerView;
private ResetPasswordView _resetPasswordView;
[SerializeField] private GameObject LoginPanel, RegisterPanel, ResetPasswordPanel;
#region Common
private void Awake()
{
_loginView = GetComponentInChildren<LoginView>();
}
#endregion
#region 登录控制公共函数
/// <summary>
/// 登录控制
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="accountType"></param>
public void Login(string username, string password, AccountType accountType)
{
bool isUsernameCorrect = LoginModel.CheckUsernameExists(username, accountType);
if (string.IsNullOrEmpty(username))
{
_loginView.HintMessage("请输入用户名!");
}
else if (string.IsNullOrEmpty(password))
{
_loginView.HintMessage("请输入密码!");
}
else
{
if (isUsernameCorrect)
{
if (LoginModel.CheckPassword(username, password, accountType))
{
if (accountType == AccountType.Common)
{
_loginView.HintMessage(accountType + "用户登录成功!");
}
else if (accountType == AccountType.Admin)
{
_loginView.HintMessage(accountType + "管理员登录成功!");
}
}
else
{
_loginView.HintMessage("密码错误!");
}
}
else
{
_loginView.HintMessage("用户名不存在,请注册!");
}
}
}
/// <summary>
/// 注册控制
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="securityQuestion"></param>
/// <param name="securityAnswer"></param>
/// <param name="accountType"></param>
public void Register(string username, string newPassword, string surePassword, string securityQuestion,
string securityAnswer, AccountType accountType)
{
_registerView = GetComponentInChildren<RegisterView>();
if (string.IsNullOrEmpty(username))
{
_registerView.HintMessage("用户名不能为空!");
}
else if (string.IsNullOrEmpty(newPassword))
{
_registerView.HintMessage("请设置密码!");
}
else if (string.IsNullOrEmpty(surePassword))
{
_registerView.HintMessage("确认密码不能为空!");
}
else if (string.IsNullOrEmpty(securityQuestion))
{
_registerView.HintMessage("请选择密保问题!");
}
else if (string.IsNullOrEmpty(securityAnswer))
{
_registerView.HintMessage("密保答案不能为空!");
}
else
{
if (username.Length > 10)
{
_registerView.HintMessage("用户名位数不能大于10个字符!");
}
else if (!newPassword.Equals(surePassword))
{
_registerView.HintMessage("两次密码不一致!");
}
else
{
Regex pattern = new Regex(@"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$");
Match match = pattern.Match(newPassword);
if (match.Success)
{
if (LoginModel.CheckUsernameExists(username, accountType))
{
_registerView.HintMessage("用户名已存在,请尝试其他用户名!");
}
else
{
LoginModel.RegisterUser(username, newPassword, securityQuestion, securityAnswer,
accountType);
_registerView.HintMessage("账号创建成功,请登录!");
}
}
else
{
_registerView.HintMessage("密码至少8个字符,包含1个大写字母,1个小写字母和1个数字,不能包含特殊字符(非数字字母)");
}
}
}
}
/// <summary>
/// 忘记密码控制
/// </summary>
/// <param name="username"></param>
/// <param name="securityQuestion"></param>
/// <param name="securityAnswer"></param>
/// <param name="accountType"></param>
/// <param name="newPassword"></param>
public void ForgetPassword(string username, string securityQuestion, string securityAnswer,
AccountType accountType, string newPassword, string surePassword)
{
_resetPasswordView = GetComponentInChildren<ResetPasswordView>();
if (string.IsNullOrEmpty(username))
{
_resetPasswordView.HintMessage("请输入用户名!");
}
else if (string.IsNullOrEmpty(securityQuestion))
{
_resetPasswordView.HintMessage("请选择密保问题!");
}
else if (string.IsNullOrEmpty(securityAnswer))
{
_resetPasswordView.HintMessage("请输入密码答案!");
}
else if (!newPassword.Equals(surePassword))
{
_resetPasswordView.HintMessage("两次密码不一致!");
}
else
{
if (LoginModel.CheckUsernameExists(username, accountType))
{
if (LoginModel.CheckSecurityAnswer(username, securityQuestion, securityAnswer,
accountType))
{
Regex pattern = new Regex(@"^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$");
Match match = pattern.Match(newPassword);
if (match.Success)
{
//执行重置密码操作
LoginModel.ResetPassword(username, newPassword, accountType);
_resetPasswordView.HintMessage("密码修改成功!");
}
else
{
_resetPasswordView.HintMessage("密码至少8个字符,包含1个大写字母,1个小写字母和1个数字,不能包含特殊字符(非数字字母)");
}
}
else
{
_resetPasswordView.HintMessage("该密保问题或答案有误!");
}
}
else
{
_resetPasswordView.HintMessage("该用户名不存在!");
}
}
}
#endregion
#region 外部访问接口
public GameObject GetLoginPanel() => LoginPanel;
public GameObject GetRegisterPanel() => RegisterPanel;
public GameObject GetResetPasswordPanel() => ResetPasswordPanel;
#endregion
}
}
2.3. 视图层
2.3.1. 登录
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using MVC.Model;
using MVC.Controller;
using DG.Tweening;
using Mr.Le.Utility.Manager;
namespace MVC.View
{
/// <summary>
/// 登录面板视图层
/// </summary>
public class LoginView : MonoBehaviour
{
[SerializeField] private TMP_InputField usernameInput, passwordInput;
[SerializeField] private Button enterBtn, registerBtn, forgetPasswordBtn;
[SerializeField] private Toggle userSelect, adminSelect, isShowPassword;
[SerializeField] private TMP_Text messageText;
[SerializeField] private GameObject infomationCavans_Obj;
private LoginController _loginController;
private AccountType _accountType;
#region Common
private void Awake()
{
_loginController = GetComponentInParent<LoginController>();
passwordInput.inputType = TMP_InputField.InputType.Password;
userSelect.isOn = true;
}
private void Start()
{
enterBtn.onClick.AddListener(OnLoginBtn);
isShowPassword.onValueChanged.AddListener(OnShowPasswordChanged);
registerBtn.onClick.AddListener(OnRegisterBtn);
forgetPasswordBtn.onClick.AddListener(OnForgetPasswordBtn);
}
#endregion
#region 按钮监听
private void OnShowPasswordChanged(bool showPassword)
{
if (showPassword)
{
passwordInput.inputType = TMP_InputField.InputType.Password;
passwordInput.Select();
passwordInput.ActivateInputField();
}
else
{
passwordInput.inputType = TMP_InputField.InputType.Standard;
passwordInput.Select();
passwordInput.ActivateInputField();
}
}
private void OnLoginBtn()
{
string username = usernameInput.text;
string password = passwordInput.text;
if (userSelect.isOn)
{
_accountType = AccountType.Common;
_loginController.Login(username, password, _accountType);
}
else if (adminSelect.isOn)
{
_accountType = AccountType.Admin;
_loginController.Login(username, password, _accountType);
}
}
private void OnRegisterBtn()
{
//DoTween控制卷轴
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(0, 514), 1.5f).OnComplete(() =>
{
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(825, 514), 3f);
//隐藏登录面板
this.gameObject.SetActive(false);
//打开注册面板
_loginController.GetRegisterPanel().SetActive(true);
});
}
private void OnForgetPasswordBtn()
{
//DoTween控制卷轴
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(0, 514), 1.5f).OnComplete(() =>
{
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(825, 514), 3f);
//隐藏登录面板
this.gameObject.SetActive(false);
//打开重置密码面板
_loginController.GetResetPasswordPanel().SetActive(true);
});
}
#endregion
public void HintMessage(string message)
{
messageText.text = message;
TimerManager.Instance.GetOneTimer(2f, () =>
{
messageText.text = "";
});
}
}
}
2.3.2. 注册
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using MVC.Controller;
using MVC.Model;
using DG.Tweening;
using Mr.Le.Utility.Manager;
namespace MVC.View
{
/// <summary>
/// 登录面板视图层
/// </summary>
public class RegisterView : MonoBehaviour
{
[SerializeField] private TMP_InputField usernameInput, NewPasswordInput, SurePasswordInput, SecurityAnswerInput;
[SerializeField] private Button backBtn, registerBtn;
[SerializeField] private Toggle userSelect, adminSelect, isShowPassword;
[SerializeField] private TMP_Dropdown securityQuestionDropdown;
[SerializeField] private TMP_Text messageText;
[SerializeField] private GameObject infomationCavans_Obj;
private LoginController _loginController;
private AccountType _accountType;
private string selectedOptionText;
#region Common
private void Awake()
{
_loginController = GetComponentInParent<LoginController>();
NewPasswordInput.inputType = TMP_InputField.InputType.Password;
SurePasswordInput.inputType = TMP_InputField.InputType.Password;
userSelect.isOn = true;
}
private void Start()
{
backBtn.onClick.AddListener(() =>
{
//DoTween控制卷轴
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(0, 514), 1.5f).OnComplete(() =>
{
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(825, 514), 3f);
this.gameObject.SetActive(false);
_loginController.GetLoginPanel().SetActive(true);
});
});
isShowPassword.onValueChanged.AddListener(OnShowPasswordChanged);
registerBtn.onClick.AddListener(OnRegisterBtn);
securityQuestionDropdown.onValueChanged.AddListener(OnsecurityQuestionDropdown);
}
#endregion
#region 按钮监听
private void OnsecurityQuestionDropdown(int value)
{
selectedOptionText = securityQuestionDropdown.options[value].text;
}
private void OnShowPasswordChanged(bool showPassword)
{
if (showPassword)
{
NewPasswordInput.inputType = TMP_InputField.InputType.Password;
SurePasswordInput.inputType = TMP_InputField.InputType.Password;
NewPasswordInput.Select();
NewPasswordInput.ActivateInputField();
SurePasswordInput.Select();
SurePasswordInput.ActivateInputField();
}
else
{
NewPasswordInput.inputType = TMP_InputField.InputType.Standard;
SurePasswordInput.inputType = TMP_InputField.InputType.Standard;
NewPasswordInput.Select();
NewPasswordInput.ActivateInputField();
SurePasswordInput.Select();
SurePasswordInput.ActivateInputField();
}
}
private void OnRegisterBtn()
{
string username = usernameInput.text;
string newPassword = NewPasswordInput.text;
string surePassword = SurePasswordInput.text;
string securityQuestion = selectedOptionText;
string securityAnswer = SecurityAnswerInput.text;
if (userSelect.isOn)
{
_accountType = AccountType.Common;
_loginController.Register(username, newPassword, surePassword, securityQuestion, securityAnswer, _accountType);
}
else if (adminSelect.isOn)
{
_accountType = AccountType.Admin;
_loginController.Register(username, newPassword, surePassword, securityQuestion, securityAnswer, _accountType);
}
}
#endregion
public void HintMessage(string message)
{
messageText.text = message;
TimerManager.Instance.GetOneTimer(1f, () =>
{
messageText.text = "";
});
}
}
}
2.3.3. 修改密码
using System;
using System.Collections;
using System.Collections.Generic;
using MVC.Controller;
using MVC.Model;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using DG.Tweening;
using Mr.Le.Utility.Manager;
namespace MVC.View
{
public class ResetPasswordView : MonoBehaviour
{
[SerializeField] private TMP_InputField usernameInput, NewPasswordInput, SurePasswordInput, SecurityAnswerInput;
[SerializeField] private Button backBtn, resetBtn;
[SerializeField] private Toggle userSelect, adminSelect, isShowPassword;
[SerializeField] private TMP_Dropdown securityQuestionDropdown;
[SerializeField] private TMP_Text messageText;
private LoginController _loginController;
[SerializeField] private GameObject infomationCavans_Obj;
private AccountType _accountType;
private string selectedOptionText;
#region Common
private void Awake()
{
_loginController = GetComponentInParent<LoginController>();
NewPasswordInput.inputType = TMP_InputField.InputType.Password;
SurePasswordInput.inputType = TMP_InputField.InputType.Password;
userSelect.isOn = true;
}
private void Start()
{
backBtn.onClick.AddListener(() =>
{
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(0, 514), 1.5f).OnComplete(() =>
{
infomationCavans_Obj.GetComponent<RectTransform>().DOSizeDelta(new Vector2(825, 514), 3f);
this.gameObject.SetActive(false);
_loginController.GetLoginPanel().SetActive(true);
});
});
isShowPassword.onValueChanged.AddListener(OnShowPasswordChanged);
resetBtn.onClick.AddListener(OnResetBtn);
securityQuestionDropdown.onValueChanged.AddListener(OnsecurityQuestionDropdown);
}
#endregion
#region 按钮监听
private void OnShowPasswordChanged(bool showPassword)
{
if (showPassword)
{
NewPasswordInput.inputType = TMP_InputField.InputType.Password;
SurePasswordInput.inputType = TMP_InputField.InputType.Password;
NewPasswordInput.Select();
NewPasswordInput.ActivateInputField();
SurePasswordInput.Select();
SurePasswordInput.ActivateInputField();
}
else
{
NewPasswordInput.inputType = TMP_InputField.InputType.Standard;
SurePasswordInput.inputType = TMP_InputField.InputType.Standard;
NewPasswordInput.Select();
NewPasswordInput.ActivateInputField();
SurePasswordInput.Select();
SurePasswordInput.ActivateInputField();
}
}
private void OnsecurityQuestionDropdown(int value)
{
selectedOptionText = securityQuestionDropdown.options[value].text;
}
private void OnResetBtn()
{
string username = usernameInput.text;
string newPassword = NewPasswordInput.text;
string surePassword = SurePasswordInput.text;
string securityQuestion = selectedOptionText;
string securityAnswer = SecurityAnswerInput.text;
if (userSelect.isOn)
{
_accountType = AccountType.Common;
_loginController.ForgetPassword(username, securityQuestion, securityAnswer, _accountType, newPassword, surePassword);
}
else if (adminSelect.isOn)
{
_accountType = AccountType.Admin;
_loginController.ForgetPassword(username, securityQuestion, securityAnswer, _accountType, newPassword, surePassword);
}
usernameInput.text = "";
NewPasswordInput.text = "";
SurePasswordInput.text = "";
SecurityAnswerInput.text = "";
}
#endregion
public void HintMessage(string message)
{
messageText.text = message;
TimerManager.Instance.GetOneTimer(1f, () =>
{
messageText.text = "";
});
}
}
}
3.挂载对应脚本
4.调用
EnterPanel面板
using Mr.Le.Utility.Manager;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
namespace Mr.Le.Utility.UI
{
public class LoginPanel : PanelBase
{
private Button button;
protected override void Init()
{
button = GetController<Button>("switchButton");
button.onClick.AddListener(SwitchButtonClick);
}
private void SwitchButtonClick()
{
UIManager.Instance.ShowPanel<GamePanel>(PanelShowLayer.Middle, Ani.Fade);
UIManager.Instance.HidePanel<LoginPanel>(Ani.Fade, () =>
{
Debug.LogFormat("隐藏{0}完毕!", gameObject.name);
});
}
}
}
Main脚本调用
using Mr.Le.Utility.Manager;
using UnityEngine;
public class Main : MonoBehaviour
{
private void Awake()
{
UIManager.Instance.ShowPanel<EnterPanel>();
}
}
六、实现效果
基于MVC+DoTween实现卷轴登录注册效果
源码链接:https://pan.baidu.com/s/1iHcGB2VKzYKDgyS_l8C8UQ?pwd=89mn
提取码:89mn
七、总结
以上就是基于MVC+DoTween实现一个卷轴效果的登录注册功能的全部内容,希望本文能够帮助大家更清晰的了解MVC思想的架构模式。如果有任何疑问或建议,欢迎留言交流!