【SiKi学院Unity】Unity中级案例 - 捕鱼达人(上)
记录里面有一些问题是还没弄清楚的
放UGUI和根目录上,坐标、缩放的区别(比如缩放的0.01388889)
新建UGUI画布的坐标为什么不在原点,跑得那么远
拖数组有的顺序,有的乱序
赋值全部通过数据文件,比如预制体,位置Transform、图片(同一图集的话还可以方便地用文件名取图片)。
StreamReader开启的时候卡,具体机制
…
记录一般是花了不少时间排查的、尝试解决的
05 水波荡漾效果的制作
将多张图拖到数组里面,乱序
导致水波动画不对,后面拖鱼的预制体顺序对的,不知道为什么有区别
(问题)拖动图片到场景生成的动画、状态机、物体名字乱
用多张图片拖成一个物体(此处系统让你命名动画名称),物体装着动画的状态机,又把物体拖成预制体。
我们要的是动画、状态机、预制体,其中状态机与动画的连接不能断
所以,
命名 == 动画
物体名 = = 状态机名 = = 多张图片中随机一张图的名字(上面拖数组都一样乱序)
原本打算用 灵者更名 来统一下名字(各种_数字很难看),但发生上面的动画名变化错误,导致动画连接没了
修改名字时,动画名不能更改,它连接到状态机,改动画麻烦的多
当生成的动画是我们命名的,我做的怎么就名字错误了
17 销毁超出边界的鱼
collider2D与 刚体2D
碰撞检测 两个碰撞体、两个刚体
触发检测 两个碰撞体中有一个触发器、一个刚体
边界是静态刚体,鱼必需要有刚体(只能碰撞检测)
边界是动态、运动学刚体,鱼可以没有刚体
由于边界刚体是动态会歪斜,所以方便的情况是——边界带运动学刚体,带触发器。鱼带碰撞器和触发脚本
鱼带碰撞脚本,collision.collider是鱼,collision是边界
鱼带触发脚本,没有collision.collider,collision是边界(但是有collider,直接做参数)
22 解决UI穿透并设置子弹属性
资源架构理清 失败
因为张图的作用,所以想理清里面的资源关系。不过也有收获,比如下面的单价 == 伤害表,index=3,4共用一套图,图的数组长度不要减少,即使赋值相同
UI
鱼群的生成,种类(18),数量,速度,间隔时间,直走转弯,地点,血量( 经验值){10,20,40,60,80…1800,2000}(单价 = = 伤害表)
枪系统,5把枪
子弹系统,5(1连1,2,3,4,4发) * (9+1特殊)=50种子弹,这个是每升一级换一下子弹颜色
单价 == 伤害表,int[] { 5,10,20,30,第1把枪,1连1发
40,50,60,70,第2把枪,1连2发
80, 90,100,200,第3把枪,1连3发
300,400,500,600,第4把枪,1连4发
700,800,900,1000 };(4*5)第5把枪,1连4发(没有1连5发的图)
每种枪4个档次的单价==伤害表,怎么对应40/4张图(4,5枪共用一套图)。
钢铁雄心将领10级最高,所需经验值是100,200,400,800,1600, 3200,6400,12800,25600,51200
问题 乱
在做到这部分时,觉得难调用,因为从前面几个视频开始想自己写(手痒了),所以将代码进行重构。
大概是
1、模型归一档
2、不需要帧更新的尽量归一档(包括数组数据的赋值)
3、需要帧更新的归一档(帧更新就是时刻监听玩家的操作)
(但其实有一些热更新的代码,比如金币数量,经验的更新直接放在Player这个静态单例类里处理,更加舒服,不用写太多的Player._instance.)
比如滑轮,左右按钮更新了bulletINDEX(子弹单价数组,20长度)。需要同时沟通子弹价格的切换,炮台的切换,所以放在第三档GameController(也就是玩家)。
传递一个bulletINDEX给BulletController,进行子弹的切换。
传递一个bulletINDEX给GunController,进行炮台的切换。
放第二档
23 制作并生成网
问题 没有生成网
触发2D没做好
问题
NullReferenceException: Object reference not set to an instance of an object UnityEditor.Graphs.Edge
问题 枪的切换没有失效旧的
使用了gunIndex=bulletINDEX/bulletPerGun的gunIndex来切换,但是玩家直接改变的值是bulletINDEX,bulletINDEX变化时没有时时刻刻给gunIndex重新赋值
24 优化鱼的生成并让网通知鱼受伤
SendMessage
调用other的方法TakeDamage ,传递的参数是damage
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == Tags.Fish)
{
other.SendMessage("TakeDamage", damage);
}
}
标签Tags
C:\Users\用户名\Documents\Visual Studio 2019\Code Snippets\Visual C#\My Code Snippets
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Tags</Title>
<Shortcut>Tags</Shortcut>
<Description>标签类</Description>
<Author>用户名</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="csharp"><![CDATA[public class Tags
{
public const string Player = "Player";
}]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
25 完善鱼死亡
问题 数据赋值
死亡没有彩云的,不能通过for赋值
json数据
表格,人看的
json,电脑读的
类,列表,代码显示的
因为看到钢铁雄心用xml,txt,csv写的数据,方便管理。
StreamReader做好后挺卡的(没有Clsoe(),Close()后速度还可以)
表格
json
注意id做成字符串,为了格式前面加00的,看起来美观。但是int可能读会被省略掉前面的0
做的起作用的用json自动统一赋值;不起作用的,比如,prefab,deathPrefab,都是手动拖拽上去的(暂时不会,钢铁4都是指定路径,赋值属性来加图片之类的)。
新情况是,开头说了,图集的话,指定图集后,可以用文件名icon_name来取得对应的图片
{
"TestData":
[
{ "id":"001","prefab":"乌龟","deathPrefab":"乌龟死亡","hp":1,"experience":1,"gold":1,"minNum":1,"maxNum":1,"minSpeed":1,"maxSpeed":1},
{ "id":"002","prefab":"刺猬鱼","deathPrefab":"刺猬鱼死亡","hp":2,"experience":2,"gold":2,"minNum":2,"maxNum":2,"minSpeed":2,"maxSpeed":2},
{ "id":"003","prefab":"大眼睛鱼","deathPrefab":"大眼睛鱼死亡","hp":3,"experience":3,"gold":3,"minNum":3,"maxNum":3,"minSpeed":3,"maxSpeed":3},
{ "id":"004","prefab":"孔雀鱼","deathPrefab":"孔雀鱼死亡","hp":5,"experience":5,"gold":5,"minNum":5,"maxNum":5,"minSpeed":5,"maxSpeed":5},
{ "id":"005","prefab":"小丑鱼","deathPrefab":"小丑鱼死亡","hp":4,"experience":4,"gold":4,"minNum":4,"maxNum":4,"minSpeed":4,"maxSpeed":4}
]
}
添加引用JsonLit
项目,添加引用,找到位置,LitJson.dll
读取
unity读取Json文件
需要将其中的GetJsonInfo()方法的节点,挂到输入框的提交编辑上
json 元素后面是否可以带逗号
C/C++,Java,PHP,JavaScript,Json数组、对象赋值时,最后一个元素后面是否可以带逗号?
做成代码片段
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>JsonToObject</Title>
<Shortcut>JsonToObject</Shortcut>
<Description>采用StreamReader读取json,返回类的集合的对象</Description>
<Author>用户名</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="csharp"><![CDATA[using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using LitJson;
using System.IO;
/**
* json文件 => StreamReader => JsonReader => 装整个
*/
//类
public class Class1Name{ public string id { get; set; }}
public class Class2Name{ public string id { get; set; }}
//类的列表,装整个json
public class RootObject
{
public List<Class1Name> List1Name { get; set; }
public List<Class2Name> List2Name { get; set; }
}
public class JsonToObject: MonoBehaviour
{
public RootObject GetRootObject(string path)// 类似"/文件夹/文件名.json"
{
StreamReader streamreader = new StreamReader(Application.dataPath + path);//读取数据,转换成数据流
JsonReader json = new JsonReader(streamreader);//再转换成json数据
RootObject rootObject = JsonMapper.ToObject<RootObject>(json);//读取
return rootObject;//调用List,rootObject.Class1Name
}
}
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
26 设计属性记录金币与经验等
顺序
问题 升到90,返回的是0级的称号
忘保存错误的代码,没有的看,是最高等级的称号的处理
问题 到80多级,返回的是90多级的称号
public void SetLevelName() //设置等级对应的称号
{
//[Tooltip("达到等级对应的称号")] public string[] levelNameArr = new string[] { "入门", "筑基", "元婴", "出窍", "分神", "合体", "度劫", "大成", "飞升", "神界" };
//[Tooltip("解锁称号对应的等级")] public int[] levelNameLevelArr = new int[] { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 };
//返回当前等级在称号对应的等级数组对应的索引
int levelNameIndex = 0;
for (int i = 0; i < levelNameLevelArr.Length+1; i++)//对最后一级的判断,要在最后+1索引,最后索引只判断到80多级
{
if (i == levelNameLevelArr.Length) //因为90级没有一个让它比较的levelNameLevelArr[i],所以单独出来
{
levelNameIndex =i;//没有此时人物90多级,但前面的levelNameLevelArr只定义到90
break;
}
else if (level < levelNameLevelArr[i]) //0-89级的判断,
{
i--;//i是上限的索引
levelNameIndex = i;
print("当前等级级别"+levelNameIndex);
break;
}
}
switch (levelNameIndex)
{
case 0 : levelName = levelNameArr[0]; break;
case 1 : levelName = levelNameArr[1]; break;
case 2 : levelName = levelNameArr[2]; break;
case 3 : levelName = levelNameArr[3]; break;
case 4 : levelName = levelNameArr[4]; break;
case 5 : levelName = levelNameArr[5]; break;
case 6 : levelName = levelNameArr[6]; break;
case 7 : levelName = levelNameArr[7]; break;
case 8 : levelName = levelNameArr[8]; break;
case 9 : levelName = levelNameArr[9]; break;
default: levelName = levelNameArr[9]; break;//因为最大称号了
}
}
30 鱼死亡后掉落金币
在鱼死生成,在自带的gold移动和销毁(根据距离Vector3(targetPos-currentPos).magnitude)
金币收集点和金币在根目录,收集点在UI,UI被金币盖住(视频是反着来),
fish
void PlayGold()
{
GameObject go = Instantiate(goldPrefab);
go.transform.SetParent(Holder._instance.goldHolder);//放根目录
go.transform.position = transform.position;
go.transform.rotation = transform.rotation;
go.transform.localScale = Vector3.one * 75f;//看着调
//销毁的代码,在Gold中实现
}
gold
金币到那个收集金币的附近就销毁
//move
transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
//when destroy
float distance = (targetPos - transform.position).magnitude;
if (distance < 0.1f)
Destroy(gameObject);
鱼死了金币没增加(经验有增加)
脚本编辑器设置了maxGold就5000,被限制了
32 处理鲨鱼死亡时播放的全屏特效
问题 position localPosition
浪花特效不正常
下图是正常时的参数
Vector3.MoveTowards,当前,目标,速度
我以当前(1000,0,0)目标(-100,0,0),速度10,因为在编辑器看,x的变化就是-1000到1000,但一直卡在开始的最右位置
浪花运行时场景时正常显示,但游戏中只有前1/4显示,后面不显示
运行时浪花的z值不断变得很大,浪花在UI上的
挂在Canvas上,限制transform.position.z=0,不起作用,依然很大;
限制transform.localPosition.z=0,起作用
总结
Canvas上用localPosition更符合编辑器上的坐标,当Canvas坐标不是000
Awake与Start
从streamReader读取json的数据,其中有一个List——bulletList(子弹)在start取值会报错,在Awake就不会
网的伤害没赋值到
在web类内定义的新建网的方法
之前直接用damage
之后用go.GetComponent< Web >().damage =
网的伤害方法没触发
触发器
Web web =new()的写法
vs支持
unity不支持
所以正负得负,所以不支持
重构网的代码
放到Web脚本下
网的伤害起作用
向fish的TakeDamage发送伤害
(现象)重构浪花脚本成过场动画脚本(不销毁,清位置不对,不动)
InterludeController
public void PlayInterlude(int interludeIndex)//边界检测
{
if (interludeIndex > interludeArr.Length - 1) return;
if (interludeIndex < 0) return;
Instantiate(interludeArr[interludeIndex]);
}
Interlude
Vector3.MoveTowards
transform.position = Vector3.MoveTowards(transform.position,… 这一块不能改,但又涉及初始位置,总路程,速度
实例在根目录000,但我们需要它一开始在场景的右边,所以在start那边赋值初始位置
编辑器看浪花从 -12,12
Canvas看浪花从-1000,1000
实际放根目录,浪花也是要2000f的速度
-729是NGUI的画布初始值,当时不会修改回 000
public class Interlude : MonoBehaviour
{
[Tooltip("过场动画开始的位置,开始就拖到开始的位置")] public Vector3 currentPos=new Vector3(12f, 0, -729f);
[Tooltip("过场结束的位置")] public Vector3 targetPos= new Vector3(-12f, 0, -729f);
[Tooltip("速度,一般是总路程的一半")] public float speed = 12f;
[Tooltip("限制不正常的z,它会暴增使得unity不识别z值太大的它")] public float z = -729f;
[Tooltip("几秒后销毁它")] public float lifeTime = 2f;
[Tooltip("测试的")] public bool reset = false;
public float timer = 0f;
// Start is called before the first frame update
void Start()
{
InterludeController cc = InterludeController._instance;
currentPos = cc.currentPos;
targetPos = cc.targetPos;
speed = cc.speed;
transform.position = currentPos;
Destroy(gameObject, lifeTime);
}
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
//卡在开始的位置,transform.position = Vector3.MoveTowards(currentPos, targetPos, speed * Time.deltaTime);
}
背景突然赋值失败、背景20级就换到最后一张图
c = (a / 20);
c = (a / 20)/4;这个写坏了
public void ChangeBg()
{
//TODO:换背景
bgIndex = level / 20;
if (bgIndex <= 0) bgIndex = 0;
if (bgIndex > backgroundArr.Length-1) bgIndex = backgroundArr.Length - 1;
backgroundNode.GetComponent<Image>().sprite = backgroundArr[bgIndex];//20级1换只有4张图
}
35-37 设计简单音效管理器
AudioSource.PlayAtPoint相当于 不断地实例 AudionSource,播放完会自动销毁。但这对时间长的音效致命
Instantiate(new GameObject(), transform.position, Quaternion.identity);
GameObject go = new GameObject();
go.AddComponent<AudioSource>();
Instantiate(go, transform.position, Quaternion.identity);
go.AddComponent<AudioSource>().Stop();
button toggle的区别
前者点击后,按钮外形状态恢复(绿色,松开还是绿色)
后者点击后,外形状态切换到另一种状态(消失不见)
换子弹的价格文本框没有更新
之前的价格表换成json生成的json数据,没有接过来
38 保存游戏
先switch再设置状态
slider(音量)激活由isMute控制
toogle由玩家控制
所以开始要保持两者保持一致
void Start()
{
//isMute归一化
bgmToggle.isOn = !AudioController._instance.isMute;
volumnSlider.gameObject.SetActive(!AudioController._instance.isMute);
}
public void OnBgmClick()
{
AudioController._instance.SwitchBgmMute();//切换isMute和bgm
isMute = AudioController._instance.isMute;//现状态
switch (isMute)
{
case true: volumnSlider.gameObject.SetActive(false); break;//静音
case false: volumnSlider.gameObject.SetActive(true); break;
}
}
音量开关和条
有可能isMute没有事可设回来,AudioController._instance.bgmAudioSource.isPlaying更直接
public void OnBgmToggleClick()
{
AudioController._instance.SwitchBgmMute();//切换
bool isMute = !AudioController._instance.bgmAudioSource.isPlaying;
SetBgmActive(isMute);
}
public void SetBgmActive(bool isMute)
{
volumnSlider.gameObject.SetActive(!isMute);
bgmToggle.isOn=!isMute;
}
存档成功,读档失败
在读档时我要求 声音开关的toggle关联isMute(toggle的那个节点默认不激活),删掉正常
初始子弹价格,等级没有更新显示
设
点击设置声音时发生溢栈
StackOverflowException: The requested operation caused a stack overflow.
点击toggle会影响isOn,但是改变isOn,toggle不受影响。所以开关不能用取反自身。isOn是UI操作传的,不能动态设置以此影响toggle的那个图像的显示隐藏
bgmToggle.isOn = !bgmToggle.isOn;//isOn
声音开关的面板默认是不激活的
开始用用isPlayinger而不是Mute来作为设置源。用.Play(),Pause()就行了
挂处理Toggle的脚本放在会被不激活的节点SettingsPannel上,不应该这样做
子弹击中鱼的特效大小太大
修改鱼身上带着的特效预制体,修改乘的缩放系数