笔者在最近的课程中需要完成一个漫游交互式的项目,于是为了总结项目中出现的知识点,方便以后的查找和翻阅就有了这么一篇文章。
项目的需求
项目的设计思路
笔者一开始的思路是把项目分为两个模块来完成(登陆模块和主模块),在后来的开发中证明了只分为两个模块是很繁琐的,在笔者后期的认为中至少得分为三个模块(登陆模块,角色控制模块,交互模块),这也是笔者的一个疏忽。
其次也是笔者犯得最大的一个疏忽就是没有备份毛坯项目
笔者认为在以后的开发学习中必须得备份3个项目出来,一个为毛坯项目,一个为测试项目,一个为完成项目,毛坯项目用于还原,测试项目用于编写新功能,最后才把写完的功能给导入到完成项目
登陆模块
笔者使用了video Player视频挂载摄像机上作为背景,使用了半透明的云彩作为登陆的文字的背景,加上了背景音乐,
两个背景板之间通过点击非UI部分进行了淡入淡出的切换,笔者一开始打算直接使用动画来达到这样的目的,但考虑到在以后可能也会有UI元素淡入淡出的需求,每次都制作动画未免太过麻烦,于是笔者就打算做一个脚本来控制ui的淡入淡出(后来完全是笔者想多了这项目就没有在使用过)。
笔者一开始的设想是制作一个单例,对外提供三个方法(保存物体颜色的方法,淡入和淡出方法),后来在编写过程中发现过于的繁琐,而且多个物体需要改变颜色时保存的颜色过多,可能会出现颜色赋值错误,于是笔者就把脚本给改成普通脚本,挂载在要淡入淡出的物体上,对外提供淡入淡出的方法,
下面就是笔者的淡入淡出脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System;
namespace XL
{
/// <summary>
/// 控制ui界面的显现淡入淡出
/// </summary>
public class UI_Fader : MonoBehaviour
{
//颜色改变的动画曲线
public AnimationCurve defaultCurve;
Image[] images;
Text[] texts;
Outline[] outlines;
Shadow[] shadows;
List<Color> imageColors, TextColors, outlinesColors, shadowsColors;
private void Awake()
{
imageColors = ExtractData(gameObject, out images, (item) => { return item.color; });
TextColors = ExtractData(gameObject, out texts, (item) => { return item.color; });
outlinesColors = ExtractData(gameObject, out outlines, (item) => { return item.effectColor; });
shadowsColors = ExtractData(gameObject, out shadows, (item) => { return item.effectColor; });
}
/// <summary>
/// 物体淡出
/// </summary>
/// <param name="seepTime">变化速度</param>
/// <param name="Time">多长时间后关闭</param>
public void To_in(float seepTime,float time)
{
StopCoroutine("startFader");
StopCoroutine("CancelActive");
setColor(images, (image, index) => { image.color = imageColors[index]; });
setColor(texts, (text, index) => { text.color = TextColors[index]; });
setColor(outlines, (outline, index) => { outline.effectColor = outlinesColors[index]; });
setColor(shadows, (shadow, index) => { shadow.effectColor = shadowsColors[index]; });
startColorChange(images,
(index) => { return new Color(imageColors[index].r, imageColors[index].g, imageColors[index].b,0); },
seepTime,
(image, endColor, y) =>
{
image.color = Color.Lerp(image.color, endColor, y);
});
startColorChange(texts, (index) => { return new Color(TextColors[index].r, TextColors[index].g, TextColors[index].b, 0); }, seepTime,
(text, endColor, y) =>
{ text.color = Color.Lerp(text.color, endColor, y);});
startColorChange(outlines, (index) => { return new Color(outlinesColors[index].r, outlinesColors[index].g, outlinesColors[index].b, 0); }, seepTime,
(outline, endColor, y) =>
{ outline.effectColor = Color.Lerp(outline.effectColor, endColor, y);});
startColorChange(shadows, (index) => { return new Color(shadowsColors[index].r, shadowsColors[index].g, shadowsColors[index].b, 0); }, seepTime,
(shadow, endColor, y) =>
{ shadow.effectColor = Color.Lerp(shadow.effectColor, endColor, y);});
// 开始组件的颜色变换
StartCoroutine(CancelActive(time));
//定时取消
}
/// <summary>
/// 多长时间取消激活
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
private IEnumerator CancelActive(float time)
{
yield return new WaitForSeconds(time);
gameObject.SetActive(false);
}
/// <summary>
/// 此物体淡入
/// </summary>
/// <param name="game"></param>
/// <param name="time"></param>
public void To_out(float time)
{
gameObject.SetActive(true);
// 设置活跃
StopCoroutine("startFader");
StopCoroutine("CancelActive");
setColor(images, (image,index) => { image.color = new Color(image.color.r, image.color.g, image.color.b, 0); });
setColor(texts, (text, index) => { text.color = new Color(text.color.r, text.color.g, text.color.b, 0); });
setColor(outlines, (outline, index) => { outline.effectColor = new Color(outline.effectColor.r, outline.effectColor.g, outline.effectColor.b, 0); });
setColor(shadows, (shadow, index) => { shadow.effectColor = new Color(shadow.effectColor.r, shadow.effectColor.g, shadow.effectColor.b, 0); });
//设置为透明颜色
startColorChange(images,
(inde)=> { return imageColors[inde]; },time,
(image, endColor, y) =>
{
image.color = Color.Lerp(image.color, endColor, y);
});
startColorChange(texts, (inde) => { return TextColors[inde]; },time,
(text, endColor, y) =>
{
text.color = Color.Lerp(text.color, endColor, y);
});
startColorChange(outlines, (inde) => { return outlinesColors[inde]; },time,
(outline, endColor, y) =>
{
outline.effectColor = Color.Lerp(outline.effectColor, endColor, y);
});
startColorChange(shadows, (inde) => { return shadowsColors[inde]; },time,
(shadow, endColor, y) =>
{
shadow.effectColor = Color.Lerp(shadow.effectColor, endColor, y);
});
//开始组件的颜色变换
}
/// <summary>
/// 取出一组进行渐变
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ts"></param>
/// <param name="endclear"></param>
/// <param name="time"></param>
/// <param name="lerpColor"></param>
private void startColorChange<T>(T[] ts,Func<int,Color> endColor, float time, Action<T, Color, float> lerpColor)
{
for (int i = 0; i < ts.Length; i++)
{
StartCoroutine(startFader(ts[i],endColor(i), time, defaultCurve, lerpColor));
}
}
/// <summary>
///设置一组组件的颜色
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ts"></param>
/// <param name="colors"></param>
/// <param name="action"></param>
void setColor<T>(T[] ts,Action<T,int> action)
{
for (int i = 0; i < ts.Length; i++)
{
action(ts[i],i);
}
}
/// <summary>
/// 取出物体上的所有T组件上的颜色
/// </summary>
/// <typeparam name="T">某一个组件</typeparam>
/// <param name="game">要取出的物体</param>
/// <param name="images">组件数组</param>
/// <param name="Record">怎么取出组件上的颜色</param>
/// <returns></returns>
private List<Color> ExtractData<T>(GameObject game, out T[] images,Func<T,Color> Record)
{
images = game.GetComponentsInChildren<T>();
List<Color> imageColors = new List<Color>();
foreach (T item in images)
{
imageColors.Add(Record(item));
}
return imageColors;
}
/// <summary>
/// 给一个组件的(color)变化颜色
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item">要变的组件</param>
/// <param name="end">最终颜色</param>
/// <param name="time">多少秒变化完成</param>
/// <returns></returns>
IEnumerator startFader<T>(T item,Color end,float time,AnimationCurve curve,Action<T, Color,float> lerpColor)
{
float _x;
do
{
_x = +Time.deltaTime / time;
float _y = curve.Evaluate(_x);
//得到对应的动画曲线值
lerpColor(item,end,_y);
//颜色插值
yield return null;
} while (1-_x>0.01);
}
}
}
其次便是登陆操作和修改密码了,笔者首先建立了SQL数据库,
连接数据库的代码在网上都有笔者在这就不做过多的累述了,笔者创建了一个UI管理器脚本,用于绑定按钮的点击操作,
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using XL;
namespace XL.WinterProject.LoadingModule
{
/// <summary>
/// 管理UI元素的触发脚本
/// </summary>
public class LoadingModule_UI_Manager : MonoBehaviour
{
[Tooltip("用户名输入框")]
public InputField Name_Field;
[Tooltip("密码输入框")]
public InputField Password_Field;
[Tooltip("登陆按钮")]
public Button Btn_Login;
[Tooltip("退出按钮")]
public Button Btn_Esc;
[Tooltip("修改密码按钮")]
public Button Btn_ChangePassword;
[Tooltip("修改用户名输入框")]
public InputField Old_Name_Field;
[Tooltip("修改密码输入框(原)")]
public InputField Old_Password_Field;
[Tooltip("修改密码输入框(新)")]
public InputField New_Password_Field;
[Tooltip("修改密码输入框(再次)")]
public InputField New_Password_Notarize_Field;
[Tooltip("返回登陆界面的按钮")]
public Button Btn_ReturnLogin_UI;
[Tooltip("确定修改密码按钮")]
public Button Btn_Notarize;
[Tooltip("加载进度条")]
public Slider Sdr_Pmgressbar;
private List<Button> buttons;//按钮集合
private UI_show UiShow { get; set; }
//控制显示UI的类
private void Awake()
{
buttons = new List<Button>();
buttons.Add(Btn_Login);
buttons.Add(Btn_Esc);
buttons.Add(Btn_ChangePassword);
buttons.Add(Btn_ReturnLogin_UI);
buttons.Add(Btn_Notarize);
UiShow = GetComponentInChildren<UI_show>();
}
private void OnEnable()
{
foreach (var item in buttons)
{
item.onClick.AddListener(Click_Sound);
//添加音效
}
Btn_Login.onClick.AddListener(OnClick_Btn_Login);
Btn_Esc.onClick.AddListener(OnClick_Btn_Esc);
Btn_ChangePassword.onClick.AddListener(OnClick_Btn_ChangePassword);
Btn_ReturnLogin_UI.onClick.AddListener(OnClick_Btn_ReturnLogin_UI);
Btn_Notarize.onClick.AddListener(OnClick_Btn_Sure_Modify);
//为按钮单击事件添加方法
}
private void OnClick_Btn_Sure_Modify()
{
string _info;
if (Old_Name_Field.text == string.Empty || Old_Password_Field.text == string.Empty
|| New_Password_Field.text == string.Empty || New_Password_Notarize_Field.text == string.Empty)
{
_info = "所有选项必须都填写";
}
else
if (New_Password_Field.text != New_Password_Notarize_Field.text)
{//如果俩次密码输入的不正确
_info = "俩次密码输入不一样";
}
else
{
if (CheckUser.Verification(out _info, Old_Name_Field.text, Old_Password_Field.text))
{
CheckUser.Change_Password(out _info,New_Password_Field.text, Old_Name_Field.text);
//调用修改
}
}
UiShow.showInfoUI(_info);
}
private void OnDisable()
{
foreach (var item in buttons)
{
item.onClick.RemoveAllListeners();
//取消事件
}
}
private void Click_Sound()
{
Audio_Manager.instance.Play_Click_Btn_Sound();
//播放按钮声音音效
}
/// <summary>
/// 修改密码
/// </summary>
private void OnClick_Btn_ChangePassword()
{
UiShow.Move_Log(MobileShift.Amend);
//移动到修改界面
}
/// <summary>
/// 返回登陆界面
/// </summary>
private void OnClick_Btn_ReturnLogin_UI()
{
UiShow.Move_Log(MobileShift.LogUI);
//移动到登陆界面
}
/// <summary>
/// 退出
/// </summary>
private void OnClick_Btn_Esc()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
/// <summary>
/// 确认
/// </summary>
private void OnClick_Btn_Login()
{
string _info;
if (CheckUser.Verification(out _info, Name_Field.text, Password_Field.text))
{
//激活(调用加载场景)
Sdr_Pmgressbar.gameObject.SetActive(true);
UiShow.LonIn(1f, 1f);
UiShow.isclick = false;
}
//显现提示UI
UiShow.showInfoUI(_info);
}
}
}
最后便是登陆模块的最后一快功能了,异步加载进度条
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
namespace XL.WinterProject.LoadingModule
{
/// <summary>
///
/// </summary>
public class ProgressBarLoading : MonoBehaviour
{
//显示进度的文本
public Text progress;
//进度条的数值
private float progressValue;
//进度条
private Slider slider;
[Tooltip("下个场景的名字")]
public string nextSceneName;
private AsyncOperation async = null;
private void Start()
{
slider = FindObjectOfType<Slider>();
StartCoroutine("LoadScene");
}
IEnumerator LoadScene()
{
async = SceneManager.LoadSceneAsync(nextSceneName);
while (!async.isDone)
{
progressValue = async.progress;
slider.value = progressValue;
progress.text = (int)(slider.value * 100) + " %";
yield return null;
}
}
}
}
然后就是主模块的设计了
在笔者的规划中,主模块主要有这三块内容,角色控制,界面ui,以及点击交互。
角色的控制
角色的控制以及播放行动动画没有什么太大难度笔者就不做累述,主要就是按下wsad和方向控制移动,视角的转动方面是取用两个分开的视角转动脚本,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using XL.Common;
using XL.WinterProject.InterfaceModule;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 角色控制器
/// </summary>
public class PlayerCharacter : MonoBehaviour
{
CharacterController character;
//角色控制器
List<MouseLook> mouseLooks = new List<MouseLook>();
[Tooltip("移动速度"), SerializeField]
private float MoveSeep=1;
[Tooltip("旋转速度"), SerializeField]
private float RotateSeep=1;
[Tooltip("跳跃高度"), SerializeField]
private float jumpHeight = 1;
[Tooltip("跳跃时间"), SerializeField]
private float jumpTime = 1;
[Tooltip("重力"), SerializeField]
private float gravity = 1;
[Tooltip("最低行走速度"), SerializeField]
private float WalkSeep;
[Tooltip("角色动画控制器"), SerializeField]
private Animator playAnimator;
private bool isjump = false;
//是否正在跳跃
private float _time;
//计时器
private void Awake()
{
character = GetComponent<CharacterController>();
foreach (var item in GetComponentsInChildren<MouseLook>())
{
item.enabled = false;
mouseLooks.Add(item);
}
}
private void Update()
{
if (!MainModule_UI_Manager.IsInput)
{ playerCharacter();
//人物的前后左右旋转
}
rotateView();
//视角的转动
characterJumping();
//人物跳跃
character.Move(Vector3.down * Time.deltaTime * gravity);
//重力
dragInteraction();
//点击
}
/// <summary>
/// 人物的前后左右旋转
/// </summary>
private void playerCharacter()
{
//旋转
if (Input.GetAxis("Horizontal") != 0)
{
if (Mathf.Abs(Input.GetAxis("Horizontal")) < WalkSeep)
{
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Idle, true);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Walk, false);
}
else
{
character.Move(transform.right * Input.GetAxis("Horizontal") * MoveSeep * Time.deltaTime);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Idle, false);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Walk, true);
}
}
//移动(ws)
if (Input.GetAxis("Vertical") != 0)
{
if (Mathf.Abs(Input.GetAxis("Vertical")) < WalkSeep)
{
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Idle, true);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Walk, false);
}
else
{
character.Move(transform.forward * Input.GetAxis("Vertical") * MoveSeep * Time.deltaTime);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Idle, false);
playAnimator.SetBool(Animation_information.Instance.Play_Bool_Walk, true);
}
}
}
/// <summary>
/// 允许跳跃的方法
/// </summary>
private void characterJumping()
{
if (Input.GetKeyDown(KeyCode.Space) && character.isGrounded)
{//如果按下空格键,并且角色是在地面上
isjump = true;
//设置正在向上跳跃
_time = jumpTime;
//赋值向上跳跃多长时间
playAnimator.SetTrigger(Animation_information.Instance.play_Trigger_Jump);
//播放跳跃动画
}
if (isjump)//如果正在向上跳跃
{
character.Move(Vector3.up * jumpHeight * Time.deltaTime / jumpTime);
//向上移动,速度为 在 jumpTime时间里跳跃jumpHeight的高度
_time -= Time.deltaTime;
//跳跃 _time 时间后停止跳跃
if (_time <= 0)
{
isjump = false;
}
}
}
/// <summary>
/// 控制是否可以转动视角
/// </summary>
private void rotateView()
{
if (Input.GetMouseButton(1)&&!MainModule_UI_Manager.IsInput)
{
foreach (var item in mouseLooks)
{
item.enabled = true;
}
}
else
{
foreach (var item in mouseLooks)
{
item.enabled = false;
}
}
}
}}
但是在角色的控制中,有一个点击鼠标左键或者右键对可交互的物体进行一定的交互的要求,笔者首先的想法是从摄像机发射一条射线,然后检测射线是否点击到可交互物体,如果是可交互则调用交互物体的交互脚本,但是每个可交互物体都会有不同的交互脚本,不可能在控制写出获取不同类名的脚本的代码,这样的代码量是巨大的。
所以笔者就想到定义两个接口,一个为左键单击的接口,一个为右键单击的接口,
可交互的物体脚本实现这个接口,角色控制类中则调用这个接口
private void dragInteraction()
{
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
GameObject ob=divergentRay();
if (ob != null)
{
IClickLeftMouseButton clickLeft = ob.GetComponent<IClickLeftMouseButton>();
if (clickLeft != null)
{
clickLeft.ClickLeftMouseButton();
}
}
}
if (Input.GetMouseButtonDown(1) && !EventSystem.current.IsPointerOverGameObject())
{
GameObject ob = divergentRay();
if (ob != null)
{
IClickRightMouseButton clickRight = ob.GetComponent<IClickRightMouseButton>();
if (clickRight != null)
{
clickRight.ClickRightMouseButton();
}
}
}
}
private GameObject divergentRay()
{
RaycastHit hit;
Camera camera = (Camera_Manager.Instance.Camera_One.depth != 0) ? Camera_Manager.Instance.Camera_One : Camera_Manager.Instance.Camera_Three;
if (Physics.Raycast(camera.ScreenPointToRay(Input.mousePosition), out hit, 20f) && (hit.transform.tag == "Interactive"))
{
return hit.transform.gameObject;
}
else
{
return null;
}
}
界面UI
界面ui从需却出发,要有一个角色第一第二人称转换按钮,背景音乐静音按钮,退出按钮,同时按钮点击要有音效
点击音效
定义一个父类,获取所有Button和Toggle,并为他们绑定播放音效的方法
public class UI_Manager :MonoBehaviour
{
//按钮的集合
protected List<Button> buttons = new List <Button>();
//单选框的集合
protected List<Toggle> toggles = new List<Toggle>();
protected void OnEnable()
{
foreach (var item in buttons)
{
item.onClick.AddListener(Click_Sound);
//添加音效
}
foreach (var item in toggles)
{
item.onValueChanged.AddListener(Click_Sound);
}
}
protected virtual void Click_Sound(bool ison)
{
}
protected virtual void Click_Sound()
{
}
protected void OnDisable()
{
foreach (var item in buttons)
{
item.onClick.RemoveAllListeners();
//取消脚本
}
}
}
定义了一个UI管理类为按键绑定方法,并对他们做出处理
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 主界面UI管理器
/// </summary>
public class MainModule_UI_Manager : UI_Manager
{
[Tooltip("人称按键")]
public Button Btn_Person;
[Tooltip("第一人称图形")]
public Image Img_One;
[Tooltip("第三人称图形")]
public Image Img_Three;
[Tooltip("人称文字显示")]
public Text Tx_Person;
[Tooltip("静音按键")]
public Toggle Tog_Mute;
[Tooltip("退出按键")]
public Button Btn_Quit;
[Tooltip("设置按键")]
public Toggle Tog_Install;
[Tooltip("介绍ui退出按键")]
public Button Btn_Introduce_Quit;
[Tooltip("介绍ui的内容")]
public InputField Fd_Content;
[Tooltip("设置键的动画")]
private Animation Atn_Install;
[Tooltip("是否正在输入"),HideInInspector]
public static bool IsInput = false;
[Tooltip("信息显示UI")]
public GameObject Introduce_UI;
private void Awake()
{
toggles.Add(Tog_Mute);
toggles.Add(Tog_Install);
buttons.Add(Btn_Quit);
buttons.Add(Btn_Person);
buttons.Add(Btn_Introduce_Quit);
Tog_Install.onValueChanged.AddListener(btn_AnToOut);
Tog_Mute.onValueChanged.AddListener(mute_Control);
Btn_Quit.onClick.AddListener(quit);
Btn_Person.onClick.AddListener(personalTransformation);
Btn_Introduce_Quit.onClick.AddListener(deactivate);
Fd_Content.onEndEdit.AddListener(amend);
Atn_Install = Tog_Install.GetComponent<Animation>();
UserInfo.Type = UserType.Administrator;
if (UserInfo.Type == UserType.GeneralUser)
{
Fd_Content.interactable = false;
}
}
private void amend(string Control)
{
UpIntroduceInfo.Instance.ChangeInfo(Control);
IsInput = false;
}
private void Update()
{
if(Introduce_UI.activeInHierarchy)
IsInput = Fd_Content.isFocused ? true : false;
}
/// <summary>
/// 取消介绍ui界面的激活
/// </summary>
private void deactivate()
{
Introduce_UI.SetActive(false);
}
/// <summary>
/// 人称按钮
/// </summary>
private void personalTransformation()
{
if (Img_One.enabled)
{
Img_One.enabled = false;
Img_Three.enabled = true;
Tx_Person.text = "第三人称";
Camera_Manager.Instance.ToCamera_Three();
//设置事件相机
if (UpIntroduceInfo.Instance.gameObject.activeInHierarchy != false)
UpIntroduceInfo.Instance.Canvas.worldCamera = Camera_Manager.Instance.Camera_Three;
}
else
{
Img_One.enabled = true;
Img_Three.enabled = false;
Tx_Person.text = "第一人称";
Camera_Manager.Instance.ToCamera_One();
UpIntroduceInfo.Instance.Canvas.worldCamera = Camera_Manager.Instance.Camera_One;
}
}
private void btn_AnToOut(bool IsOn)
{
if (IsOn)
{
Atn_Install["InstallBtn_Amt"].speed = 1;
Atn_Install.Play();
}
else
{
Atn_Install["InstallBtn_Amt"].time = Atn_Install["InstallBtn_Amt"].length;
Atn_Install["InstallBtn_Amt"].speed = -1;
Atn_Install.Play();
}
}
/// <summary>
/// 退出游戏的方法
/// </summary>
private void quit()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
/// <summary>
/// 静音按键
/// </summary>
/// <param name="ison"></param>
private void mute_Control(bool ison)
{
if (MainModule_Audio_Manager.Instance.BG_Source.isPlaying)
{
MainModule_Audio_Manager.Instance.AudioStop(MainModule_Audio_Manager.Instance.BG_Source);
}
else
{
MainModule_Audio_Manager.Instance.AudioPlay(MainModule_Audio_Manager.Instance.BG_Source,
MainModule_Audio_Manager.Instance.BG_Room_clip,true);
}
}
/// <summary>
/// 点击音效
/// </summary>
protected override void Click_Sound()
{
MainModule_Audio_Manager.Instance.AudioPlay(MainModule_Audio_Manager.Instance.OnClick_clip);
}
protected override void Click_Sound(bool isOn)
{
MainModule_Audio_Manager.Instance.AudioPlay(MainModule_Audio_Manager.Instance.OnClick_clip);
}
}
}
最后就是交互功能的实现了,笔者就将对笔者造成麻烦的给写出来,
水龙头
这是给笔者最大的一个麻烦,笔者一开始打算从网上找一下水的粒子特效就可以,可是在使用过程中却发现基本没有合适的,最后笔者选择了使用了obi流体插件,但是这是2018的插件导入到2017的项目中,报了非常多的错,后来发现报的错是项目自带基础脚本,是笔者用不到的,只要把这些给删了就好,但是在打包时还是会报错,原因就是obi插件中有一个shader使用unity2018的函数。
/// <summary>
/// 水龙头交互脚本
/// </summary>
public class Faucet_In : MonoBehaviour, IClickLeftMouseButton
{
ObiEmitter emitter;
[Tooltip("流水的速度"),SerializeField]
private float speed=3;
[Tooltip("控制水龙头声音的索引"), SerializeField]
private int index;
private void Awake()
{
emitter = GetComponentInChildren<ObiEmitter>();
}
public void ClickLeftMouseButton()
{
if (emitter.speed == 0)
{
emitter.speed = speed;
//开启水龙头音效
MainModule_Audio_Manager.Instance.AudioPlay(
MainModule_Audio_Manager.Instance.Water_Source[index], true);
}
else
{
emitter.speed = 0;
//关闭水龙头音效
MainModule_Audio_Manager.Instance.AudioStop(
MainModule_Audio_Manager.Instance.Water_Source[index]);
}
}
}
边缘外发光
这个笔者没有找到很好的解决办法,唯一想法就是使用shader;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XL.WinterProject.InterfaceModule;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 花瓶点击脚本
/// 左键单击移动,右键单击旋转
/// </summary>
public class In_Vase : MonoBehaviour, IClickRightMouseButton,IClickLeftMouseButton
{
public static GameObject game;
//材质
public Material Mat1, Mat2;
Material[] MatUp,MatStart;
private MeshRenderer mesh;
MouseCtrl thisMouseCtrl;
MouseRight thisMouseRight;
private void Awake()
{
thisMouseCtrl = this.gameObject.AddComponent<MouseCtrl>();
thisMouseRight = this.gameObject.AddComponent<MouseRight>();
thisMouseCtrl.enabled = false;
thisMouseRight.enabled = false;
mesh = GetComponent<MeshRenderer>();
MatUp = new Material[2] { Mat1, Mat2 };
MatStart = new Material[1] { Mat1 };
}
private void Update()
{
if (game != this.gameObject)
{
thisMouseCtrl.enabled = false;
thisMouseRight.enabled = false;
mesh.materials = MatStart;
}
if (game != null)
{
MainModule_UI_Manager.IsInput = true;
}
}
public void ClickLeftMouseButton()
{
if (game == this.gameObject)
{
game = null;
MainModule_UI_Manager.IsInput = false;
return;
}
//添加网格
mesh.materials = MatUp;
game = this.gameObject;
//激活移动
thisMouseCtrl.enabled = true;
thisMouseRight.enabled = false;
}
public void ClickRightMouseButton()
{
if (game == this.gameObject)
{
game = null;
MainModule_UI_Manager.IsInput = false ;
return;
}
//添加网格
mesh.materials = MatUp;
game = this.gameObject;
thisMouseRight.enabled = true;
thisMouseCtrl.enabled = false;
}
}
}
书
会判断是否是管理员去决定是否可以修改
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XL.WinterProject.InterfaceModule;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 杂志点击脚本
/// </summary>
public class In_Book : MonoBehaviour,IClickLeftMouseButton
{
[Tooltip("介绍的UI"), SerializeField]
private GameObject introduceUI;
[Tooltip("介绍的UI生成位置"), SerializeField]
private Transform pon;
public void ClickLeftMouseButton()
{
introduceUI.SetActive(true);
introduceUI.transform.position = pon.position;
introduceUI.transform.rotation = pon.rotation;
MainModule_Audio_Manager.Instance.AudioPlay(MainModule_Audio_Manager.Instance.Book_Source,false);
UpIntroduceInfo.Instance.FindIntroduction(UpIntroduceInfo.Instance.goods[1]);
}
}
}
显示界面代码
using System.Collections;
using System.Text;
using System.Collections.Generic;
using XL.Common;
using UnityEngine.UI;
using UnityEngine;
using System.Data;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 介绍ui的数据变化
/// </summary>
public class UpIntroduceInfo : MonoSingleton<UpIntroduceInfo>
{
[Tooltip("标题"), SerializeField]
private Text headline;
[Tooltip("内容"), SerializeField]
private InputField Fd_content;
[Tooltip("画布"), SerializeField]
private Canvas canvas;
public Canvas Canvas { get { return canvas; }}
[Tooltip("物品名字数组")]
public string[] goods;
[Tooltip("当前显示的物品名字")]
private string good;
private StringBuilder headlineStr=new StringBuilder();
private new void Awake()
{
base.Awake();
}
/// <summary>
/// 查找物品介绍
/// </summary>
/// <param name="name"></param>
public void FindIntroduction(string name)
{
good = name;
if (headlineStr.Length != 0)
headlineStr.Remove(0,headlineStr.Length);
headlineStr.Append(name);
headlineStr.Append("简介");
headline.text = headlineStr.ToString();
DataSet data= SQLConnect.instance.sqlCheck_Condition("Content", "Goods", "Name ='" + name + "'");
Fd_content.text = data.Tables[0].Rows[0][0].ToString();
}
/// <summary>
/// 修改
/// </summary>
/// <param name="content"></param>
public void ChangeInfo(string content)
{
string _table = "Goods";
string _content = string.Format("Content = '{0}'", content);
string _name = string.Format("Name = '{0}'", good);
SQLConnect.instance.sqlUpdate_Condition(_content, _table, _name);
}
}
}
TV
笔者设计了一个下拉框去显示可以播放的视频,
TV脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using XL.WinterProject.InterfaceModule;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// 电视单击
/// </summary>
public class In_TV : MonoBehaviour, IClickLeftMouseButton,IClickRightMouseButton
{
[Tooltip("电视UI")]
public GameObject Go_ui;
public Image Ima_ui;
public Material end_Mat;
Material start_Mat;
public MeshRenderer this_mesh;
private void Awake()
{
start_Mat = this_mesh.material;
}
public void ClickLeftMouseButton()
{
if (Go_ui.activeInHierarchy) return;
this_mesh.material = end_Mat;
Go_ui.SetActive(true);
Ima_ui.fillAmount = 0;
StartCoroutine("Toshow");
}
IEnumerator Toshow()
{
StopCoroutine("toOut");
while (Ima_ui.fillAmount != 1)
{
Ima_ui.fillAmount = Mathf.MoveTowards(Ima_ui.fillAmount, 1,Time.deltaTime*2);
yield return null;
}
}
public void ToOut()
{
StopCoroutine("Toshow");
StartCoroutine("toOut");
}
IEnumerator toOut()
{
while (Ima_ui.fillAmount != 0)
{
Ima_ui.fillAmount = Mathf.MoveTowards(Ima_ui.fillAmount, 0, Time.deltaTime * 2);
if (Ima_ui.fillAmount == 0) Go_ui.SetActive(false);
yield return null;
}
}
public void ClickRightMouseButton()
{
this_mesh.material = start_Mat;
TV_UIManager.player.Stop();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
using UnityEngine.UI;
using System.Text.RegularExpressions;
using System;
namespace XL.WinterProject.MainModule
{
/// <summary>
/// TV_UI管理器
/// </summary>
public class TV_UIManager :UI_Manager
{
public VideoPlayer TV;
public static VideoPlayer player;
public Button[] UI_buttons;
public In_TV in_TV;
public GameObject Field;
private void Awake()
{
player = TV;
foreach (Button item in UI_buttons)
{
buttons.Add(item);
}
}
public void PlayTV(VideoClip clip)
{
TV.clip = clip;
TV.Play();
in_TV.ToOut();
}
public void PlayTV_IDE()
{
Field.SetActive(true);
}
public void PlayTV_IDE(string url)
{
if (IsUrl(url))
{
TV.Stop();
TV.source = VideoSource.Url;
TV.url = url;
TV.Play();
}
Field.SetActive(false);
in_TV.ToOut();
}
/// 判断一个字符串是否为url
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsUrl(string str)
{
try
{
string Url = @"^http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?$";
return Regex.IsMatch(str, Url);
}
catch (Exception ex)
{
return false;
}
}
protected override void Click_Sound()
{
MainModule_Audio_Manager.Instance.AudioPlay(MainModule_Audio_Manager.Instance.OnClick_clip);
}
}
}
其他也没有给笔者带来什么麻烦,对了需要注意的是门的点击交互,当门被设为静态时,门的开门动画是播放不了的,
项目的打包
这一块是笔者比较难受的一个点,打包时好多shader是报错的,笔者只能一个一个去请教群里的大佬,在百度和大佬的帮助下笔者终于打包成功了。
但是最麻烦点出来了,当从登陆转到主场景时,
光照贴图丢失了!!!!
场景变暗了!!!
唯一办法就是重新烘培,但是这是一个非常大的场景,笔者的电脑在烘培过程中,总共宕机3次,至今没有烘培成功。
项目完成文件
链接:https://pan.baidu.com/s/1icJjnJ5fW2iwlHGpEJYYbg
提取码:kwv7
需要的自取。