【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 @ 2020-08-20 01:16  JimmyZou  阅读(0)  评论(0编辑  收藏  举报  来源