一、引言
在软件开发领域,设计模式是我们构建可维护、可扩展代码的重要指导原则之一。然而,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-zd]{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-zd]{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>(); } }
六、实现效果
<iframe id="NsTzfaDL-1705840313178" frameborder="0" src="//i2.wp.com/live.csdn.net/v/embed/360832" allowfullscreen="true" data-mediaembed="csdn"></iframe>
基于MVC+DoTween实现卷轴登录注册效果
源码链接:https://pan.baidu.com/s/1iHcGB2VKzYKDgyS_l8C8UQ?pwd=89mn
提取码:89mn
七、总结
以上就是基于MVC+DoTween实现一个卷轴效果的登录注册功能的全部内容,希望本文能够帮助大家更清晰的了解MVC思想的架构模式。如果有任何疑问或建议,欢迎留言交流!