小白超神系列Unity企业特训班

飞羽时代创始人飞羽老师亲授,Unity开发全栈课程。

默认教学计划
45人加入学习
(4人评价)
价格 ¥ 4800.00
教学计划

使用NGUI,要把脚本继承一个类就会有拖拽的效果了,UIDragDropItem

[展开全文]

SubShader内部结构:

pass{}

//进行渲染,和SubShader一样,pass可以有多个

//比如边缘高亮就可以用2个pass,第二个pass渲染较大范围放到后面就形成了一圈高亮

pass里面如果要渲染颜色:

color(1,0,0,1)//红色,使用的是参数就用小括号

如果在Properties里面声明了颜色_Color那么就可以直接用color[_Color]

//这里需要用中括号,表示里面的参数是属性

color是纯色,显示不了立体效果,所以可以用material来代替

material//里面有多个参数,所以要用大括号
{

    diffuse[_color1]//物体的漫反射

    ambient[_color2]//环境光

    specular[_color3]//高光颜色

    shininess[_range]//高光范围,0~1

    Emission[_color4]//自发光

}

lighting on//开启环境光

separatespecular on//开启高光



setTexture[_TestTexture]{}//设置图片

 

[展开全文]

shader创建时外面的名称无所谓,打开后第一行才是材质球能够选择的路径名称

shader分2大块,

第一块为Properties 属性

类似于脚本里的public公共变量

里面存放shader的各种属性

标准格式:

_Name("Display Name", type) = defaultValue[{options}]

Properties{

    //如果要声明一个int的数值

    //Int _TextInt; //这是脚本的写法

    _TestInt("IntValue",int)=0

    //属性写前面,后面带括号,第一个字符串类型的值为外面显示的名称,第二个为值的类型。后面必须给其一个参数,最后不用写分号;

}

int: 

_TestInt("IntValue",int)=0

float:

_TestFloat("FloatValue",Float)=0

range:显示出来会是一个区间滑动条

_TestRange("RangeValue",Range(1,10))=1

//类型range需要加个括号,里面写入区间,后面给的默认参数必须为区间里的值

texture:

_TestTexture("TextureValue",2D)=""{}

//默认参数写一个空的字符串再带个大括号

color:

_TestColor("ColorValue",color)=(1,1,1,1)

//默认为带透明通道的

Vector:

_TestVector("VectorValue",vector)=(x,y,z,w)

//w为0时代表这是个向量,为1时则是代表点

Cube:

_TestCube("CubeValue",cube) = ""{}

//常用作天空盒

——————————

第二部分为SubShader

类似于Main(){}//作用于赋予属性

//SubShader可以有多个,起同样的作用

//用于匹配不同性能的机器

//首先使用第一个,第一个不适合就继续往下使用,知道最后一个

//一般把最好的效果放第一个,然后递减

//类似于游戏里的最高画质到最低画质

 

第三部分:

FallBack "Diffuse"

//如果前面的SubShader都不满足就调unity里默认的Diffuse材质

————————

外部3部分:

Properties、SubShader、FallBack

[展开全文]

shader3种:

1、固定管线着色器 属性方式设置 布尔值 不推荐

2、顶点片元着色器

3、表面着色器 U3D开发的

第二种最好

[展开全文]

HLSL:微软开发,只能适配于微软的平台

GLSL:配合OpenGL使用,跨平台性

CG:NVIDIA开发,真跨平台性

ShaderLab:对ShaderLab的封装,U3D自己写的框架

[展开全文]

线程和携程都是为了解决延时或多开的问题而出的

区别在于携程的都是在同一线程里

//一个人工作时区烧水,等待时间继续工作

而线程则是不同线程里

//一个人工作时叫另一个人烧水,等待时间烧水的人一直等待

 

携程规定写法:

IEnumerator func(){yield return new xxx()}

//返回值必须是IEnumerator

//方法里必须加上yield return xxx

而运行携程则是:

StartCoroutine("func")

//括号里写func() 也行

 

携程和Invoke("func",time)类似

但invoke的缺点在于不能填更多的参数以及无法停止

而携程可以通过将

Coroutine cor=StartCoroutine(func3())

赋值

然后StopCoroutine(cor)来停止携程

//比如说打断持续施法和延迟受伤

//StopAllCoroutine()能停止所有携程

 

[展开全文]

先将格子的道具信息保存为json,然后把json和账号关联保存到sql

//数据更新是update Save save="xxx"

//添加是Save(save) values("xxx")

 

先制作2个按钮,一个保存一个加载,待会和同名的两个方法关联

给背包面板的脚本添加2个方法:Save、Load并和按钮关联

Save:

编为数组,转为json,存到数据库

public void Save()
    {
        //[0,0,0,0,1,2,3,4,5,6,2]
        //save(id,save)
        JsonData jd = new JsonData();
        jd.SetJsonType(JsonType.Array);
        //将json的格子道具保存为数组类型的数据
        foreach (GridBase grid  in grids)
        {
            ItemControl ic= grid.GetItem();
            if (ic!=null)
            {
                jd.Add(ic.ID);
            }
            else
            {
                jd.Add(0);
            }
        }
        //转成json
        string jdStr = jd.ToJson();
        int count = SqlManager.Instance.ExecuteScalar(string.Format("select count(save) from Save where id={0}", PlayerInfo.ID));
        if (count!=0)
        {
            //更新数据
            SqlManager.Instance.ExecuteNonQuery(string.Format("update Save set save='{0}' where id={1}", jdStr, PlayerInfo.ID));
        }
        else
        {
            //添加新数据
            SqlManager.Instance.ExecuteNonQuery(string.Format("insert into Save values({0},'{1}')", PlayerInfo.ID, jdStr));
        }
        Debug.Log("保存成功");
    }

Load:

数据库取出,转为json,转为数组,遍历生成

    public void Load()
    {
        SqliteDataReader reader = SqlManager.Instance.ExecuteReader(string.Format("select save from Save where id={0}", PlayerInfo.ID));
        while (reader.Read())
        {
            string json = reader["save"].ToString();
            //解析json//ToObject
            JsonData jd = JsonMapper.ToObject(json);
            for (int i = 0; i < grids.Length; i++)
            {
                if ((int)jd[i] != 0)//为空就不要加
                {
                    grids[i].AddItem((int)jd[i]);
                }
            }
        }
        Debug.Log("加载成功");
        reader.Close();
    }

 

[展开全文]

制作提示板:

public static TipControl instance;

private Text tipName;
private Text tipContent;
// Start is called before the first frame update
void Awake()
{
    instance = this;
    tipName = transform.GetChild(0).GetComponent<Text>();
    tipContent= transform.GetChild(1).GetComponent<Text>();
    gameObject.SetActive(false);
}

//显示
public void Show(int id)
{
    gameObject.SetActive(true);
    tipName.text = ItemManager.Instance.ItemDic[id].Name;
    tipContent.text = ItemManager.Instance.ItemDic[id].Des;
}

//隐藏
public void Hide()
{
    gameObject.SetActive(false);
}
// Update is called once per frame
void Update()
{
    if (gameObject.activeSelf)
    {
        transform.position = Input.mousePosition;
    }
}

//获取2个文本的脚本

//激活时就将tip隐藏

//给2个公开方法,一个显示一个隐藏。

//随时判断,如果显示,就跟随鼠标

——————

给格子的基类2个回调:鼠标进入和离开

IPointerEnterHandler,IPointerExitHandler

产生2个方法:

    public void OnPointerEnter(PointerEventData eventData)
    {
    //获取道具,并不为空时显示tip
        ItemControl ic = GetItem();
        if (ic!=null)
        {
            TipControl.instance.Show(ic.ID);
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
    //鼠标离开直接隐藏tip
        TipControl.instance.Hide();
    }

————

//优化tip显示位置,让其保证显示不超出边界

教程是手动判断左右,给个bool值来改变tip锚点的位置,然后在两个格子的子类分别给不同的bool值来区分左右

//我是判断鼠标位置位于哪个象限,来改变锚点位置

——————

保存数据:

通过登录不同的账号来获取不同的背包数据

新建一个界面,制作4个UI,帐密输入和注册登录。

//通过插件对数据库操作不方便,就创建一个脚本专门用来封装对数据库的操作

新建一个SqlManager,不要父类,单独制作单例

里面包含2个类,一个数据库连接类,一个数据库命令类

SqliteConnection  con;

SqliteCommand  cmd;

————

数据库需要4个操作:

0、对数据库的打开操作

1、对数据进行增删改等不需要返回值的

2、返回值为单数的函数查询

3、返回值为数组的

——

//打开数据库
    public void Open(string file)
    {
        //路径前必须加上Data Source=,这是规定
        con = new SqliteConnection("Data Source=" + file);
        con.Open();
    }

    //无返回值的数据库命令
    public void ExecuteNonQuery(string sqlStr)
    {
        cmd = new SqliteCommand(sqlStr, con);
        cmd.ExecuteNonQuery();
        cmd.Dispose();
        
    }

    //返回值是单数的函数查询
    public int ExecuteScalar(string sqlStr)
    {
        cmd = new SqliteCommand(sqlStr, con);
        object obj= cmd.ExecuteScalar();
        cmd.Dispose();
        //返回的是obj类型的,需要转换成int类型
        return Convert.ToInt32(obj);
    }

    //返回值是集合的数据查询
    public SqliteDataReader ExecuteReader(string sqlStr)
    {
        cmd = new SqliteCommand(sqlStr, con);
        SqliteDataReader reader=cmd.ExecuteReader();
        cmd.Dispose();
        return reader;
    }

    ~SqlManager()//删除脚本时关闭数据库
    {
        con.Close();
    }

对数据库操作的3个方法都已经封装,可以开始使用了

给登录界面添加一个脚本TitleControl

声明账号和密码的2个输入栏

oid Start()
    {
        nameField = transform.GetChild(1).GetComponent<InputField>();
        pwdField= transform.GetChild(2).GetComponent<InputField>();
        //打开数据库
        SqlManager.Instance.Open(Application.dataPath + "/Account.db");
        //创建账号表
        SqlManager.Instance.ExecuteNonQuery("create table if not exists Account(id integer primary key autoincrement,name,pwd)");
        //创建用户保存的信息
        SqlManager.Instance.ExecuteNonQuery("create table if not exists Save(id integer,save)");
    }

一个注册方法

//字符串拼接可以用string.Format来解决
    public void Register()
    {
        //先遍历查找账号是否存在
        int count = SqlManager.Instance.ExecuteScalar(string.Format("select count(*) from Account where name='{0}'", nameField.text));
        if (count>0)
        {
            Debug.Log("账号已存在");
            return;
        }
        //找不到账号则添加
        SqlManager.Instance.ExecuteNonQuery(string.Format("insert into Account(name,pwd) values('{0}','{1}')", nameField.text, pwdField.text));
        Debug.Log("注册成功");
    }

一个登陆方法

public void Login()
    {
        SqliteDataReader reader = SqlManager.Instance.ExecuteReader(string.Format("select * from Account where name='{0}'", nameField.text));
        //read()方法是一行行遍历列表,遍历完就返回false
        while (reader.Read())
        {
            PlayerInfo.ID = reader.GetInt32(0);
            PlayerInfo.Name = reader.GetString(1);//通过序号查找
            PlayerInfo.Pwd = reader["pwd"].ToString();//通过字符串查找
            if (PlayerInfo.Pwd == pwdField.text)
            {
                Debug.Log("登陆成功");
                SceneManager.LoadScene(1);
                reader.Close();
                return;
            }
            reader.Close();
            Debug.Log("密码错误");
            return;
        }
        reader.Close();
        Debug.Log("账号不存在");
    }

//创建一个PlayerInfo脚本用来存账户的3个静态信息

public class PlayerInfo
{
    public static int ID;
    public static string Name;
    public static string Pwd;
}

到这里注册登录就做完了,接下来是读取保存背包信息

——————

[展开全文]

物品管理器ItemManager不需要挂载,所以不用父类,实例化一个静态变量对象就行

同时封装,并提供一个实例化的方法

private static ItemManager instance;
public static ItemManager Instance
{
    get
    {
        if (instance == null)
        {
            instance = new ItemManager();
        }
        return instance;
    }
}

需要从XML里获取数据,所以要先创建一个类Item://放到ItemManager外

public class Item
{
    public int ID;
    public string Name;
    public string Des;
    public string Image;
}

然后需要声明一个字典对象来接受数据

//字典是键值对类型,可以更好的让数据匹配

public Dictionary<int, Item> ItemDic = new Dictionary<int, Item>();

读取数据只需要调取一次

所以应该在Instance获取字段时调取。

所以给Instance()提供一个加载数据的方法

private ItemManager()
{
    //加载xml文件
    XmlDocument doc = new XmlDocument();
    doc.Load(Application.dataPath + "/item.xml");

    XmlElement rootEle = doc.FirstChild as XmlElement;//???
    foreach (XmlElement itemEle in rootEle.ChildNodes)//???
    {
        //创建一个物品对象
        Item item = new Item();
        item.ID = int.Parse(itemEle.GetElementsByTagName("id")[0].InnerText);
        item.Name = itemEle.GetElementsByTagName("name")[0].InnerText;
        item.Des = itemEle.GetElementsByTagName("des")[0].InnerText;
        item.Image = itemEle.GetElementsByTagName("image")[0].InnerText;
        //将声明的对象保存到字典当中
        ItemDic.Add(item.ID, item);
        //键是自己的ID,值是自身
    }
}

//XmlNode不能转为XmlElement的话
//可以用XPath方法:
XmlNodeList list = doc.SelectNodes("/root/item");
foreach (XmlElement itemEle in list)

——————

创建UI:

给父节点一个grid Layout Group组件

可以让格子整齐排列

——————

//做法为数据由格子储存

//道具只显示图片,不进行计算

————

商店背包分为3部分

面板、格子、道具

————

面板:商店面板、背包面板

//两个面板都继承一个基类

//获取空格子(判断空由格子实现)

//添加道具(拿到空格子,然后用格子里的方法)

————

格子:商店格子、背包格子

//两个格子也都继承一个基类

//3个方法共用:

//判断是否为空、添加道具(id)、获取道具

————

道具只保存ID,并通过ID显示图片

//通过ItemManager里的字典用id来获取数据

 

————

给商店格子添加一个鼠标点击方法:

//命名空间为UnityEngine.EventSystems

//接口为POinterClickHandler

public void OnPointerClick(PointerEventData eventData)

{
ItemControl ic=GetItem();
if(ic!=null)
    {
InventoryControl.instance.AddItem(ic.ID);
    }
}

——————

给背包格子添加3个拖拽方法:

//开始拖拽、拖拽中、结束拖拽

//同样命名空间为UnityEngine.EventSystems

//接口为

IBeginDragHandler

IDragHandler

IEndDragHandler

//鼠标放到接口上会提示填补方法

开始拖拽:
//获取道具
ItemControl ic=GetItem();
eventData.selectedObject = ic.gameObject;
//改变道具的父物体位于格子前
eventData.selectedObject.transform.SetParent(transform.parent.parent);

 //eventData.selectedObject选中的物体

拖拽中:
if (eventData.selectedObject!=null)
{
    //道具不为空则让其位置跟随鼠标
    eventData.selectedObject.transform.position = Input.mousePosition;
}

//eventData.pointerEnter鼠标拖拽结束时位于的UI

//eventData.pointerDrag 鼠标拖拽开始时位于的UI

//拖拽为空就返回
if (eventData.selectedObject==null)
{
    return;
}
//拖拽结束的位置位于UI外或者不是在背包格子上
if (eventData.pointerEnter == null || eventData.pointerDrag.tag != "Grid")
{
    //就将道具重置位置和层级
    eventData.selectedObject.transform.SetParent(eventData.pointerDrag.transform);
    eventData.selectedObject.transform.localPosition = Vector2.zero;
    return;
}
//获取拖拽开始的格子和结束的格子
InventoryGrid endGrid = eventData.pointerEnter.GetComponent<InventoryGrid>();
InventoryGrid startGrid = eventData.pointerDrag.GetComponent<InventoryGrid>();
if (endGrid.IsEmpty())
{
    //结束的格子如果是空的,就改变道具的位置和层级到新格子
    eventData.selectedObject.transform.SetParent(endGrid.transform);
    eventData.selectedObject.transform.localPosition = Vector2.zero;
}
else
{
    //否则进行交换
    //获取新格子里的道具,将其丢到旧格子里,然后将拖拽的道具丢进格子
    ItemControl endIc = endGrid.GetItem();
    endIc.transform.SetParent(startGrid.transform);
    endIc.transform.localPosition = Vector2.zero;
    eventData.selectedObject.transform.SetParent(endGrid.transform);
    eventData.selectedObject.transform.localPosition = Vector2.zero;
}

 

[展开全文]

cmd指令:

————

创建并进入sqlite的表格文档:

sqlite3 data.db

————

创建表格://Person为表名称

create Person(pid,name,score);

//后面不加类型修饰就为弱类型,什么都能加

//如果文档里有同名的表,则无法创建//报错

但可以绕过:

create table if not exists Person(pid,name,score)

//如果不存在则创建

//添加if语句则能不报错

————

显示内容:

.table

————

删除表://Person为表名称

drop table Person;

————————————

表格4种操作:增删查改

————

増加:

insert into Person values(1,"xxx",96);

完整版在Person后应该加上(pid,name,score)

一般可以省略,除非后面添加的值有缺省,那么就要加上对应的字头

insert into Person(pid) values(6);

————

删除:

delete from Person where pid=6;

//需要加上条件语句where//相当于if

————

修改://更新

update Person set name="xxx" where pid=1;

//修改内容写在条件语句前 set xxxx=xxx

————

查找:

select代表查询

简单查找:

select * from Person;

//会将表全部显示出来

*是省略,表示全部查询,可以只查询某项,则输入对应的字头

select pid,name from Person;

查找可在后面加入条件:

select * from Person limit 3;
//表示只显示前3位的内容
//limit 表示限制
select * from Person where score>94 and score < 99;
//where 代表if   //and 代表&&

按 字头排序:

select * from Person order by score;
//从小到大
//order by 规定写法
select * from Person order by score desc;
//加上desc则是从大到小
//desc的意思是降序
select * from Person order by score desc limit 3;
//如果想限制数量则要加在最后

查询数量:

select count(*) from Person;
//查询有几个附和条件的

查询总数:

select sum(*) from Person;

查询平均值:

select avg(*) from Person;

——————

主键:primary key//唯一,不可重复,不可空

自动自增:autoincrement

唯一键:unique//不唯一,不可重复,可为空

create table if not exists Kungfu(kid integer primary key autoincrement,name text unique,kid);
//创建一个表格,kid为整形主键自增,name为文本格式唯一键
insert into Kungfu(name,pid) values("xxx",5);
//主键设置为自增,则不用输入,但要写上其他输入项
//两个表关联并显示
select Person.name,Kungfu.name,Person.score 
from Person,Kungfu 
where Person.pid=Kungfu.pid;
//select:前面为要显示的内容
//from:中间为显示的表格
//where:后面为关联的外键
//不提行,用空格隔开

 

[展开全文]

用域名来解析XML名称空间

XmlDocument doc = new XmlDocument();
doc.Load(Application.dataPath + "/XML/Text.xml");
XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace("move", "http://www.tudou.com");
XmlNodeList list = doc.SelectNodes("/root/heros/hero/move:name", nsManager);
foreach (XmlElement l in list)
{
    Debug.Log(l.InnerText);
}

索引前需要先创建一个名称空间管理器类,病添加一个名称空间映射

nsManager.AddNamespace("move", "http://www.tudou.com")

 这样XPath才能用名称空间进行解析

//需要用几个名称空间解析就添加几个映射

 

如果有默认的名称空间(不带名称)

smlns="http://www.xxxxxxxx.com";

那么XPath路径除了标明的名称空间的路径外都必须加上默认的名称空间

例如:

nsManager.AddNamespace("default", "http://www.qq.com");
doc.SelectNodes("/default:root/default:heros/default:hero/move:name", nsManager);

因为一旦标明默认的名称空间,那么没有写明名称空间的路径全部都是默认的名称空间,路径不写明就无法解析

[展开全文]

调用xml元素里的内容用InnerText,尖括号里的用Name

xml第二种解析方法:

XmlDocument doc = new XmlDocument();
doc.Load(Application.dataPath+"/XML/Text.xml");
XmlNodeList list = doc.SelectNodes("/root/heros/hero/name");
foreach (XmlElement ele in list)
{
    Debug.Log(ele.Name + ele.InnerText);
}

单斜杠/是绝对路径,双斜杠//是相对路径

能结合起来用

相对路径是遍历所有节点找到所有对应的节点,结果一样,但效率会降低

如果同名节点有多个,可以节点名后面接[],里面填对应的数字,这里是从1开始排,不是从0开始

//比如找第二个name节点就是xxx/name[2]/xxx

//倒数第二个就是[last()-1]

//想拿前2个就是[positon()<3]

//限定id的拿法就是[@id],排除不符合的

//id有值就是[@id=2],只拿id为2的

————————————————

xml的创建:

//创建文档
XmlDocument doc = new XmlDocument();
//创建文档声明
XmlDeclaration dec=doc.CreateXmlDeclaration("1.0","utf-8","");
doc.AppendChild(dec);
//root节点
XmlElement rootEle = doc.CreateElement("root");
doc.AppendChild(rootEle);
//Heros节点
XmlElement herosEle = doc.CreateElement("heros");
rootEle.AppendChild(herosEle);
//循环数据
string[] names = new string[] { "蝙蝠侠", "钢铁侠", "蜘蛛侠" };
string[] ages = new string[] { "35", "34", "20" };
for (int i = 0; i < 3; i++)
{
    //添加hero节点
    XmlElement heroEle = doc.CreateElement("hero");
    herosEle.AppendChild(heroEle);
    //添加name节点
    XmlElement nameEle = doc.CreateElement("name");
    nameEle.InnerText = names[i];
    heroEle.AppendChild(nameEle);
    //添加age节点
    XmlElement ageEle = doc.CreateElement("age");
    ageEle.InnerText = ages[i];
    heroEle.AppendChild(ageEle);
    //添加属性
    heroEle.SetAttribute("id", i +1+ "");
    //i是int类型,后面加个“”就能转换成string类型

    //添加属性的第二种方法:当做节点来添加
    /*
    XmlAttribute att = doc.CreateAttribute("id");
    att.Value = i + "";
    heroEle.Attributes.Append(att);
    */
}
//保存XML
doc.Save(Application.dataPath + "/XML/Text2.xml");

如果相同的名称有不同的含义,那么可以前面加上命名空间:来区分例如:

<hero:name>superman</name>

<move:name>superman:ironbody</name>

 

[展开全文]

xml和html类似

节点:<name>value</name>

节点名称:name    节点值:value

//结尾用斜杠/标明

可以嵌套:<name><p>value</p><name>

属性:<name id="1">value</name>

//属性必须是键值对,用空格隔开

//没有值时可以简写:<name id="1"/>

//同一层里节点名称可以相同

——————————————

xml文档里开头需要包含xml、版本、编码3部分:

<?xml version="1.0" encoding="utf-8"?>

——————————————

解析Xml:

命名空间为Using System.Xml;

第一种方法:

//xml文档类
XmlDocument doc = new XmlDocument();
//读取xml文件
doc.Load(Application.dataPath + "/XML/Text.xml");
//解析
//读取的是节点类,声明的是元素类,元素类是节点的子类
XmlElement rootEle = doc.FirstChild as XmlElement;
//heros节点
XmlElement herosEle = rootEle.LastChild as XmlElement;
//遍历hero节点
foreach (XmlElement heroEle in herosEle.ChildNodes)
{
    string id = heroEle.GetAttribute("id");
    string name = heroEle.ChildNodes[0].InnerText;
    string age = heroEle.ChildNodes[1].InnerText;
    Debug.Log(id + name + age);
}

//XmlNote转换成XmlElement报错了

 

第二种:

 

[展开全文]

解析Json前需要将解析的数据对象都写出来

然后在前面加上序列化

这样才能读取解析

//Persons ps = JsonUtility.FromJson<Persons>(ToJson);

//Json里是什么类就实例化什么类

 

第三方插件LitJson

先将插件丢到Plugins文件夹里

//类可以直接使用,不用加序列化标签

LitJson有两种创建和解析json的方法

 

第一种

和系统自带的一样

区别在于用

JsonMapper来取代JsonUtility方法

ToObject取代FromJson方法

 

//转换成Json里时中文会自动转换成UniCode编码//但可以正常使用

 

第二种

可以不写类,直接用JsonData声明对象

jd1.SetJsonType(Jsontype.object)

JsonData jd1 = new JsonData();
jd1.SetJsonType(JsonType.Object);
jd1["name"] = "蝙蝠侠";
jd1["age"] = 35;
JsonData jd2 = new JsonData();
jd2.SetJsonType(JsonType.Object);
jd2["name"] = "小丑";
jd2["age"] = 34;
JsonData jds = new JsonData();
jds.SetJsonType(JsonType.Array);
jds.Add(jd1);
jds.Add(jd2);
JsonData heroJD = new JsonData();
heroJD.SetJsonType(JsonType.Object);
heroJD["hero"] = jds;
        
Debug.Log(heroJD.ToJson());

可以设置所有的类型:

数组:Array[]、

字典:Object{}

//SetJsonType也可以不设置直接用,系统会自动识别

//优势在于不用写类

解析:

string jsonStr = "{'hero':[{'name':'蝙蝠侠','age':35},{'name':'小丑','age':32}]}";
JsonData heroJD = JsonMapper.ToObject(jsonStr);
JsonData heros = heroJD["hero"];
foreach (JsonData hero in heros)
{
    Debug.Log(hero["name"].ToString() + " " + (int)hero["age"]);
}
Debug.Log(heros[0]["name"]);

解析出来的类为JsonData,需要手动转换下

//优势在于所有解析的类都为JsonData,不需要手动写

[展开全文]

csv:用逗号,去分割数据//缺点:数据本身带逗号就会混淆

改进:用竖杠 | 去分割

 

json:数组+字典 嵌套

数组示例:值

[1,2,3,4]   ["aaa","bbb","ccc","ddd"]

字典示例:键值对

{“aaa”:1,“bbb”:2}

{“ccc”:3,“ddd”:4}

json示例:

{"persons":[{“aaa”:1,“bbb”:2}, {“ccc”:3,“ddd”:4}]}

格式化写法:

{

"persons":

  [

    {

    “aaa”:1,

    “bbb”:2

    },

    {

    “ccc”:3,

    “ddd”:4

    }

  ]

}

//转为Json的类里数据不能封装

Json使用嵌套时,需要在用到的类前面都加上序列化[Serializable],才能进行编译,不然输出就为空

 

 

//序列化要在前面加上Using System;

[展开全文]

Dotween插件可以创建移动动画

using DG.tweening

DOMoveX()可以给单独一个方向做动画

Tween tween=transform.DOMoveX(Pos,Time);

DoTween自带回调

tween.onComplete=

new TweenCallback(function);

//完成动画后自动回调方法。可以用于当动画的播放时间为随机值无法确定时的时候,比Invoke更好。

 

发射的射线只与带有Collider组件的物体进行计算,无关父子物体的层级。

[展开全文]

每波怪间隔的等候时间可以用:

timer=-EnemyTimer;

来表示。让计时器时间为负的等候时间,这样再递增就能实现间隔。

[展开全文]

OnEnable()方法调用在Awake后,在Start之前

可能会出现第一次声音没有

这种情况可能是声音脚本晚于枪的脚本实例化

可以在Edit里的Script Execution Order里将两个脚本丢入编写其实例化顺序,让声音脚本先于枪实例

//Script Execution Order:脚本执行顺序

[展开全文]

授课教师

飞羽时代创始人

课程特色

视频(249)
下载资料(16)