【Unity】学习记录【一】


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详解 ):
  1. 通过特性 [CreateAssetMenu(...)],之后在Project面板中右键创建
  2. 通过运行中的代码 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)方法后,可以检测鼠标是否进入当前UI

  • C#中的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 为本地参考系下的坐标

    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日

    • 文章Unity - Animator

    • 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) +=
    • 事件的五个步骤
      听到:“某对象或者某个类含有某个事件” ,这句话想说的意思是:这个事件可以通过+=让别的对象去监听它。一旦这个事件发生,那么别的对象就会调用自己的事件处理器(方法)。
    1. 我(类)要一个事件(成员)
    2. 一群别的类关心、订阅我的事件 (订阅的时候要符合约定)
    3. 我的事件发生了! (一定是由事件拥有者的内部逻辑触发的)
    4. 关心的类们被一次性通知到
    5. 被通知到的人,拿着事件参数,做出相应
    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)


    • 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("未找到文件");
            }
        }
    }
    
    

    posted @   JimmyZou  阅读(10)  评论(0编辑  收藏  举报  
    相关博文:
    阅读排行:
    · 震惊!C++程序真的从main开始吗?99%的程序员都答错了
    · 单元测试从入门到精通
    · 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
    · 上周热点回顾(3.3-3.9)
    · winform 绘制太阳,地球,月球 运作规律
    点击右上角即可分享
    微信分享提示