2020年8月20日
unity2D 骨骼动画 2D Animation+IK 极速入门教程【完结】
- 2D 骨骼动画
2D Animation
2D IK
2020年8月21日
【中文字幕】UI背包系统(ScriptableObject物品资源编辑,物品UI界面的开关和...
- Canvas
scale with screen size - Grid Layout Group
- GameManager 与 UIManager分离
- 单例模式
public class GameManager : MonoBehaviour
{
public static GameManager instance;
private void Awake() {
if(instance==null){
instance = this;
}else{
if(instance!=this) //防止场景中有多个Manager
{
Destroy(gameObject);
}
}
DontDestroyOnLoad(gameObject);
}
}
- 特性 [RequireComponent(typeof(Rigidbody2D))] 写在类名前
- 通过挂载Destroyer类来摧毁物体
public class Destroyer : MonoBehaviour
{
[SerializeField]float destroyTime;
void Start()
{
Destroy(gameObject,destroyTime);
}
}
- 继承自 ScriptableObject 的类可以序列化为Unity项目目录中的.asset文件。有两种方式可以创建、修改 (具体可见文章 Unity ScriptableObject详解 ):
- 通过特性 [CreateAssetMenu(...)],之后在Project面板中右键创建
- 通过运行中的代码 AssetDatabase.CreateAsset(..) 创建.asset对象。
通过AssetDatabase.SaveAssets()来保存所有修改。
using UnityEngine;
[CreateAssetMenu(menuName = "物品", fileName = "新物品")]
public class Item : ScriptableObject
{
public string itemName;
public string itemDes;
public Sprite itemSprite;
public int itemPrice;
}
using UnityEditor; //AssetDatabase所在的名称空间
using UnityEngine;
public class SaveTest
{
public void save(){
Save save = createSaveObject();
save.num = 1;
AssetDatabase.CreateAsset(save,"Assets/newSaveData.asset");
save.num = 2;
AssetDatabase.SaveAssets(); //更新
}
}
- List的成员 Add、Contains
2020年8月22日
【中文字幕】UI背包系统(移除物品,Tooltips提示小窗口和代码修复)【下集】
- Destroy(gameObject)之后的代码依然会执行
- 既可以观察物体,也可以方便布置位置的布局
-
为某UI挂载 Canvas Group 组件,并取消勾选 Blocks Raycasts 可以屏蔽鼠标点击时该UI的遮挡
位于UnityEngine.EventSystems下的IPointerEnterHandler等接口
当实现OnPointerEnter(PointerEventData eventData)方法后,可以检测鼠标是否进入当前UIC#中的as语法:安全的类型转换(转型失败返回null)
Object o = new Object();
Player p = o as Player;
- 屏幕坐标 (ScreenPoint) 转换为本地坐标 (LocalPoint)
RectTransformUtility.ScreenPointToLocalPointInRectangle(rect, screenPoint, cam, localPoint)
-RectTransform rect: 将 rect 作为参考坐标系
-Vector2 screenPoint:基于屏幕坐标系的位置
-Camera cam:相机的引用(取决于 Canvas的Render Mode,Screen Space Overlay则为null)
-out Vector2 localPoint:以 rect 为本地参考系下的坐标
位于System.Text下的StringBuilder
StringBuilder sb = new StringBuilder();
sb.AppendFormat("物品:{0} \n\n", itemName);
return sb.toString();
- 结束睡觉,明天看下整个的依赖关系
2020年8月23日
- 整理了一下类图
某些UI的Preserve Aspect可以在缩放时锁定缩放比。
Shadow组件可以为UI添加阴影。
GameManager 负责游戏核心逻辑(例如更新分数), 角色关键数据(如金钱、关卡)和游戏状态(如是否暂停)等。
GameMenu 负责UI按键事件触发的自定义函数(如音乐的开关、UI的显示、游戏暂停、 保存游戏、加载游戏等)。Toggle的 isOn 属性改变后,会触发其OnValueChanged事件。
PlayerPrefs 是Unity中用于本地持久化存储的静态类,是一个特殊的缓存系统(Caching System)。数据存储在注册表的 HKCU\Software[company name][product name]。某些小数据可以用这种方式存储。
通过PlayerPrefs.SetInt( key , value ) 等函数来存储数据。
通过PlayerPrefs.GetInt( key )来获取值。
例如:
在 GameMenu.cs 的 Save( ) 中调用PlayerPrefs.SetInt( "Level" , GameManager.instance.level )
来保存当前关卡。
在 GameMenu.cs 的 Load( ) 中调用GameManager.instance.level = PlayerPrefs.GetInt( "Level" )
来加载当前关卡。通过PlayerPrefs.hasKey( "name" ) 来判断是否包含某数据。
通过序列化保存
C#如果某类需要被序列化,必须要在类前加特性[System.Serializable]
[System.Serializable]
public class Save //定义Save类用于存储所有需要保存的数据
{
public int num;
public float x;
public float y;
}
- C#中位于System.IO的File类,提供了创建、复制、剪切、打开等静态方法。
例如:File.Delete() 、File.Copy()等。 - FileStream类 以字节流 的形式对文件进行读写操作。
C#中的BinaryFormatter类 用于序列化和反序列化
(位于System.Runtime.Serialization.Formatters.Binary名称空间中)BinaryFormatter.Serialize(Stream, Object) 将对象序列化到给定流
BinaryFormatter.Deserialize(Stream) 将制定流反序列化为对象
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine.UI;
public class SaveBySerialization : MonoBehaviour
{
//将需要保存的数据保存到Save对象
public Save createSaveObject(){
Save save = new Save();
save.num = int.Parse(GetComponent<Text>().text);
save.x = transform.position.x;
save.y = transform.position.y;
return save;
}
//序列化保存到本地文件
public void save(){
Save save = createSaveObject();
FileStream fileStream = File.Create(Application.dataPath + "/data.txt");
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fileStream,save);
fileStream.Close();
}
//反序列化加载文件到save对象
public void load(){
if(File.Exists(Application.dataPath+"/data.txt")){
FileStream fileStream = File.Open(Application.dataPath+"/data.txt",FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
Save save = bf.Deserialize(fileStream) as Save;
fileStream.Close();
//将Save对象中的数据加载到游戏逻辑
GetComponent<Text>().text = save.num.ToString();
transform.position = new Vector2(save.x,save.y);
}else{
Debug.Log("not found");
}
}
}
2020年8月26日
【中文字幕】序列化和反序列化-二进制格式化保存与读取Unity数据 | 视频 13:55 处开始
还没看完..
实现了打字机效果
通过封装一个TypeWriterEffect类, 在Update中主要判断:
1.大条件 isActive
2.计时器 timer > charPerSecond,
之后逐个将成员curString追加到curText.text中。
通过typeSentence(String string)来传递curString, 同时isActive = true爱给网里下了一个手部动画的unity包, 它通过AnyState -> xx 和 Trigger 实现各种手部样子的自动动画过渡切换。
2020年8月27日
Animator中的Fixed Duration只是把过渡从百分比变到了固定的秒数。取消勾选后并不是把Transition Duration 给取消了,过渡时间还是在的!
OnTriggerStay并不是一直在检测!而是随机时间间隔进行检测的。所以里面不要放关于Input.GetKeyDown这类方法。还是放在Update里吧。
2020年9月1日
计算机素养指的是体系化的素养,像一棵树一样,而不是一片孤立的树苗。 不成体系的知识注定难以前行。
事件是一种具有通知能力的成员
事件的五个组成部分:
事件模型 | 例子 |
---|---|
事件拥有者(Event Source) | Timer |
事件(Event) | Elapsed |
事件的响应者(Event Subscriber) | Printer |
事件处理器(Event Handler) | PrintWord( object sender, ElapsedEventArgs e) |
事件订阅(+= operator) | += |
-
事件的五个步骤
听到:“某对象或者某个类含有某个事件” ,这句话想说的意思是:这个事件可以通过+=让别的对象去监听它。一旦这个事件发生,那么别的对象就会调用自己的事件处理器(方法)。
- 我(类)要有一个事件(成员)
- 一群别的类关心、订阅我的事件 (订阅的时候要符合约定)
- 我的事件发生了! (一定是由事件拥有者的内部逻辑触发的)
- 关心的类们被一次性通知到
- 被通知到的人,拿着事件参数,做出相应
using System.Timers;
using UnityEngine;
//事件拥有者 : Timer
//事件 : Elapsed
//事件响应者 : Printer
//事件处理器 : PrintWord
//事件订阅 : timer.Elapsed += Printer.PrintWord
public class EventExample : MonoBehaviour
{
Timer timer = new Timer();
public static int count=0;
public static string displayString="Hello, My Name is Jimmy";
void Start()
{
timer.Elapsed += Printer.PrintWord;
timer.Interval = 1000;
timer.Start();
}
class Printer
{
internal static void PrintWord(object sender, ElapsedEventArgs e)
{
if(count < displayString.Length)
Debug.Log(displayString[count++]);
}
}
}
- 对于一些属于类的底层的变量,不需要使它显示在Inspect,但是却要让他允许被别的类访问(例如用于保存角色位置)时可以这样:
[HideInInspector]
public float pox,poy;
刚开始看Unity接触的一些视频教程,目的是为了将细小独立的功能分离开来,让我们了解。所以在自己学习的时候也是,如果是为了去了解独立的功能,不要去考虑太多与主要目的无关的设计。 (得先会用Unity,再一边去思考如何让项目更好)
Instantiate( object, position, rotation )实例化
Quaternion.identity 代表初始旋转 Quaternion(0,0,0,0)
JSON (JavaScript Object Notation)
由于其轻量的特点,常用于在客户端和服务端的数据传输JSON序列化中需要用到:
- Save
jsonString = JsonUtility.ToJson( 拥有[System.Serializable]特性的对象 )
streamWriter = new StreamWriter( 全路径 ) // 路径一般用Application.dataPath + "/文件名.格式"
streamWriter.Write(jsonString)- Load
jsonString = streamReader.ReadToEnd()
T save = JsonUtility.FromJson<T>(jsonString)
代码如下:
public class SaveByJSON : MonoBehaviour
{
Save createSaveObject(){
//...
return save;
}
public void save(){
Save save = createSaveObject();
string jsonString = JsonUtility.ToJson(save);
StreamWriter sw = new StreamWriter(Application.dataPath + "/jsonData.json");
sw.Write(jsonString);
Debug.Log("文件jsonData.json 已保存至"+Application.dataPath);
sw.Close();
}
public void load(){
if(File.Exists(Application.dataPath+"/jsonData.json")){
StreamReader sr = new StreamReader(Application.dataPath + "/jsonData.json");
string jsonString = sr.ReadToEnd();
Save save = JsonUtility.FromJson<Save>(jsonString);
sr.Close();
GetComponent<Text>().text = save.num.ToString();
transform.position = new Vector2(save.x,save.y);
Debug.Log("加载成功");
}else{
Debug.Log("未找到文件");
}
}
}
2020年9月2日
XML(eXtensible Markup Language) 可扩展标记语言
写一个函数片段的时候,也要先分步骤注释,之后再回过头来填充。目的是为了划分算法过程。容易忘记的善后操作也可以提前写,例如xx.Close()
-
XML的保存、读取方式中,需要用到:
创建Xml时,是通过节点树的形式来创建的。每个标签具有自己的子标签,便于分层。 在创建了一个节点后,需要将其附加到其父节点上。
完整代码:
using UnityEngine;
using UnityEngine.UI;
using System.Xml;
using System.IO;
using System.Collections.Generic;
public class SaveByXML : MonoBehaviour
{
Save createSaveObject(){
Save save = new Save();
save.num = int.Parse(GetComponent<Text>().text);
save.x = transform.position.x;
save.y = transform.position.y;
save.enemyName = new List<string>{"小小怪","中中怪","大大怪"};
save.enemyHP = new List<int>{10,25,40};
return save;
}
public void save(){
XmlDocument xmlDocument = new XmlDocument();
Save save = createSaveObject();
#region Create XML Elements
XmlElement root = xmlDocument.CreateElement("Save");
root.SetAttribute("FileName","File_01");
//--num
XmlElement numElement = xmlDocument.CreateElement("num");
numElement.InnerText = save.num.ToString();
root.AppendChild(numElement);
//--x
XmlElement xElement = xmlDocument.CreateElement("xpos");
xElement.InnerText = save.x.ToString();
root.AppendChild(xElement);
//--y
XmlElement yElement = xmlDocument.CreateElement("ypos");
yElement.InnerText = save.y.ToString();
root.AppendChild(yElement);
//--list
XmlElement enemy,enemyName,enemyHP;
for(int i=0; i<save.enemyName.Count; i++){
//创建
enemy = xmlDocument.CreateElement("Enemy");
enemyName = xmlDocument.CreateElement("enemy_name");
enemyHP = xmlDocument.CreateElement("enemy_hp");
//赋值
enemyName.InnerText = save.enemyName[i];
enemyHP.InnerText = save.enemyHP[i].ToString();
//附加
enemy.AppendChild(enemyName);
enemy.AppendChild(enemyHP);
root.AppendChild(enemy);
}
//--SET ROOT
xmlDocument.AppendChild(root);
#endregion
//SAVE
xmlDocument.Save(Application.dataPath + "/xmlData.xml");
if(File.Exists(Application.dataPath + "/xmlData.xml")){
Debug.Log("xmlData.xml已保存至"+Application.dataPath);
}
}
public void load(){ //LOAD 永远是从外到内:外部的文件->内部的程序对象->执行逻辑 (外部层->数据对象层->逻辑层)
if(File.Exists(Application.dataPath + "/xmlData.xml")){
Save save = new Save();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(Application.dataPath + "/xmlData.xml");
#region Load XML Data
XmlNodeList numNode = xmlDocument.GetElementsByTagName("num"); //获取XML文件中指定标签名的元素
int num = int.Parse(numNode[0].InnerText); //提取出XML元素标签对中的值
save.num = num; //将值赋给程序中的Save对象
XmlNodeList xposNode = xmlDocument.GetElementsByTagName("xpos");
float xpos = float.Parse(xposNode[0].InnerText);
save.x = xpos;
XmlNodeList yposNode = xmlDocument.GetElementsByTagName("ypos");
float ypos = float.Parse(xposNode[0].InnerText);
save.y = ypos;
XmlNodeList enemyList = xmlDocument.GetElementsByTagName("Enemy");
if (enemyList.Count != 0)
{
XmlNodeList enemyNameList = xmlDocument.GetElementsByTagName("enemy_name");
XmlNodeList enemyHPList = xmlDocument.GetElementsByTagName("enemy_hp");
for (int i = 0; i < enemyList.Count; i++)
{
string enemyName = enemyNameList[i].InnerText;
int enemyHP = int.Parse(enemyHPList[i].InnerText);
//save.enemyName[i] = enemyName; // X
//save.enemyHP[i] = enemyHP; // X
save.enemyName.Add(enemyName); // √ (这里要用Add,因为创建之初,两个列表都是空的)
save.enemyHP.Add(enemyHP); // √
}
}
#endregion
#region LOAD Data TO Game
//将save中的数据 更新到 游戏中 ,在这里我就简单地用Log输出了
Debug.LogFormat("NUM:{0} XPOS:{1} YPOS:{2}",save.num,save.x,save.y);
for (int i = 0; i < save.enemyName.Count; i++)
{
Debug.LogFormat("Enemy[{0}] NAME:{1} HP:{2}",i,save.enemyName[i],save.enemyHP[i]);
}
#endregion
}
else
{
Debug.Log("未找到文件");
}
}
}