U3D总结项

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JeanShaw/article/details/52216799

出处已经忘记了,内容还是值得拜读的。
ProtoBuf
******************
* *
* 个人总结 *
* *
******************
********************切换场景保存物体*
函数:
DontDstroyOnLoad(物体名字)这样就可以在场景切换的时候不删除此物体
*不规则点击***
虚拟杆触摸的时候也可以做一下不规则的点击,给大圆加上这个代码,就可以实现只在圆内响应点击事件
设置不规则点击事件-导入按钮图片后,设置Texture Type 为 Advanced,选中Read/Write Enable选项
代码;
// 图片透明度大于该值时,触发点击事件
public float alpha = 0.5f; //自定义值0-1
private Image image;
void Start()
{
image = GetComponent();
image.eventAlphaThreshold = alpha;
}
************虚拟杆****
写虚拟杆的时候,用触摸点的X和Y坐标比上外圆的半径就是触摸点移动的距离,最大为1的时候就是到了外圆边缘

这样就可以通过这两个比值,控制角色的移动

private bool isDown = false;
private Transform smallButton;//小圆
private float radiu = 0f;//大圆半径

protected override void Start()
{
    smallButton = transform.GetChild(0);
    radiu = GetComponent<RectTransform>().rect.width / 2f;
}
//点击事件
public override void OnPointerDown(PointerEventData eventData)
{
    isDown = true;
    smallButton.position = new Vector3(eventData.position.x, eventData.position.y, smallButton.position.z);
}
//抬起事件
public override void OnPointerUp(PointerEventData eventData)
{
    isDown = false;
    smallButton.position = transform.position;

    // TODO : 向外传递零向量表示停止操作
}
/*-------------------
 * 拖拽方法,下面必须有UI控件才能实现该方法
 * eventData.delta.sqrMagnitude返回的是向量长度的平方,
 * 所以在计算范围的时候也要按平方算
 *position.magnitude返回物体坐标的长度
 *position.normalized返回物体坐标的方向
 * --------------------------------*/
void IDragHandler.OnDrag(PointerEventData eventData)
{
    //防止手机手抖,小于这个范围判定不在移动范围内
    if (eventData.delta.sqrMagnitude <= 0.5f * 0.5f)
        return;
    //鼠标拖拽坐标
    Vector3 pointePos = new Vector3(eventData.position.x, eventData.position.y, transform.position.z);
    print(pointePos - transform.position);
    //当拖拽区域超过半径时,小圆应一直处于大圆内
    if ((pointePos - transform.position).sqrMagnitude > radiu * radiu)
    {
        //normalized * radiu该数值返回向量长度+大圆坐标才是小圆应处位置
        smallButton.position = (pointePos - transform.position).normalized * radiu + transform.position;
    }
    else//否则为鼠标拖拽点击时的位置
    {
        smallButton.position = pointePos;
    }
//可以用以下两个值控制角色的行走
float h=pointePos.x/radiu;//触摸点的X坐标除以半径就是触摸点左右移动的距离
float v=pointePos.y/radiu;//触摸点的Y坐标除以半径,就可以得到上下移动的距离
}

****************ASII编码*********************************************************
———–向文件写入非字符类型数据时
StreamWriter是把各种类型的数据都转化成字符,然后把字符按照一定的格式编码出来的数据写入文件中
BinaryWriter是直接把数据在内存中的真实状态写入到文件中。

例如:
FileStream fs = File.Open(“E:\MyFile.txt”, FileMode.OpenOrCreate, FileAccess.Write)
StreamWriter sw = new StreamWriter(fs);
BinaryWriter bw = new BinaryWriter(fs);
sw.Write(100);
bw.Write(100);
用UEdit查看MyFile.txt的16进制码.
sw的输出为31 30 30,占三个字节。代表了’1’, ‘0’, ‘0’的ASCII码。它输出的全是文本数据
bw的输出为64 00 00 00 ,占四个字节。这正是100在内存中的真实状态。int类型占四个字节。
用记事本打开,sw的输出显示为:”100”, bw的输出显示为 “d “, 因为100对应了ASCII码的d
BinaryWriter写进去的东西,StreamReader是认不出来的,只能用BinaryReader的对应方法来读取,
然后在用BinaryReader读取时,指定好匹配的编码方式,就可以将原来的数据还原,写进去的是int型,
就用BinaryReader.ReadInt32()来读取
根据例子也就是说,
BinaryWriter bw = new BinaryWriter(fs, Encoding.UTF32)输出的结果是内存中的真实状态
BinaryWriter bw = new BinaryWriter(fs, Encoding.ASCII);
这就是字符’a’用ACSII格式编码的结果
———–向文件中写入字符数据时

当用于写字符的时候,StreamWriter和BinaryWriter是差不多的。二者稍有区别

执行之后,MyFile.txt内的数据为:FF FE E7 8E 8B,其中E7 8E 8B是StreamWriter采用默认编码格式
对’王’进行编码的结果,当记事本程序试图将FF FE E7 8E 8B解释成文本时,遇到FF FE会认为这是Unicode编码,
于是把后边的所有数据都按照Unicode的格式解释,于是E7 8E 8B被解释成了乱码。把FF FE 改成00 00 之后,
记事本找不到FF FE,于是就把这一坨数据按照默认方式解释,这就正确地将E7 8E 8B解释成了‘王’字。

********************求相对的点钟方位************************************
求点钟方位的时候只求一个相对方向的角度,只能判断出2个区域,所以要求两个

求一个物体相对于另一个物体的点钟方向,
用Vector.Angle(1.2)
参数1:是被相对的物体与相对物体的位置差,
参数2:相对物体的方位
这里要用两次求,求一次相对于物体的前方位,还有有方位,这样判断两次方位是否大于小于90度
就有了4块区域,根据这四块区域可以知道是左上(9-12点),左下(6-9点),右上(12-1点),右下(3-6点)

判断好4块方位区域后,再根据相对于前方向的Angle角度(0-180)结合是区域的那一块,判断钟点方位

例如船和漩涡,求船相对于漩涡的方位
float aForward= Vector3.Angle (漩涡.position-船.position,船.transform.forward);

float aForward= Vector3.Angle (漩涡.position-船.position,船.transform.right);

再判断这两个角度是否大于或小于90度
//方向角度枚举
public enum Clock{
LeftForward,//左前方 LeftBehind,//左后方 RightForward,//右前方
RightBehind//右后方

}

if (aForward < 90f && aRight > 90f) {
clock = Clock.LeftForward;
//print (“左前方”);
} else if (aForward > 90f && aRight > 90f) {
clock=Clock.LeftBehind;
//print (“左下方”);
} else if (aForward < 90f && aRight < 90f) {
clock=Clock.RightForward;
//print (“右前方”);
} else if(aForward > 90f && aRight < 90f){
clock=Clock.RightBehind;
//print (“右下方”);

}

再根据相对于的前方向判断点钟方位
//点钟方位
public enum Clocks{
One=1,Two=2,Three=3,Fore=4,Five=5,Six=6,
Seven=7,Either=8,Nine=9,Ten=10,Eleven=11,Twelve=12

}

int aF = (int)aForward;
if (aF >= 0 && aF <= 15 && (clock == Clock.RightForward||clock==Clock.LeftForward)) {
clocks = Clocks.Twelve;//12
}else if(aF>15&&aF<=45&&clock == Clock.RightForward){
clocks = Clocks.One;
}else if(aF>45&&aF<=75&&clock == Clock.RightForward){
clocks = Clocks.Two;
}else if(aF>75&&aF<=105&&(clock == Clock.RightForward||clock==Clock.RightBehind)){
clocks = Clocks.Three;
}else if(aF>105&&aF<=135&&clock == Clock.RightBehind){
clocks = Clocks.Fore;
}else if(aF>135&&aF<=165&&clock == Clock.RightBehind){
clocks = Clocks.Five;
}else if(aF>165&&aF<=180&&(clock == Clock.RightBehind||clock == Clock.LeftBehind)){
clocks = Clocks.Six;
}else if(aF>135&&aF<=165&&clock == Clock.LeftBehind){
clocks = Clocks.Seven;
}else if(aF>105&&aF<=135&&clock == Clock.LeftBehind){
clocks = Clocks.Either;
}else if(aF>75&&aF<=105&&(clock == Clock.LeftBehind||clock==Clock.LeftForward)){
clocks = Clocks.Nine;
}else if(aF>45&&aF<=75&&clock == Clock.LeftForward){
clocks = Clocks.Ten;
}else if(aF>15&&aF<=45&&clock == Clock.LeftForward){
clocks = Clocks.Eleven;
}
然后再根据clocks的值是哪个枚举值得到是哪个点钟的方位

************做物体闪动*************************************************
当时间增加的时候让物体localScale为0或1即可,变0的时间为1的时间的二倍,即可达到相等的闪现
timer += Time.deltaTime;
if (timer >=0.6f) {
captainLogo.transform.localScale = Vector3.zero;
timer = 0;
} else if(timer>=0.3f){
captainLogo.transform.localScale =captainLogoScale;
}
************指针光标*************************************************
Cursor.SetCursor(1,2,3);设置光标
参数1-贴图,参数2-从左上角的偏移所使用的纹理作为目标点,参数3–支持的平台;
例如:设置光标点击NPC的样式
public Texture2D Cursor_NPCTalk;//贴图名称
private Vector2 hotspot = Vector2.zero;
private CursorMode mode = CursorMode.Auto;//支持平台设置光标
public void SetNPCTalk()
{
Cursor.SetCursor(Cursor_NPCTalk, hotspot, mode);
}
参数1位贴图的名字


基类,一般是指被继承的类,java中为父类,
派生类 就继承类,java中为子类

Interface接口,没有方法体,继承后,必须实现接口的所有方法
Abstract抽象类,有方法体,且没有具体实现,实现必须在派生类中使用override关键字来实现

virtual方法(虚方法),用于在基类中修饰方法
1.在基类中定义了virtual,但在派生类中没有重写该虚方法,那么派生类的实例,调用的是基类方法
2.在基类中定义了virtual,然后在派生类中使用override重写了该方法,那么派生类实例,调用的是重写的方法

sealed修饰符表示密封,不能和Abstract抽象修饰使用,必须和override重写修饰使用
用于类的时候,表示该类不能再被继承,如B继承了A方法后,B类用了sealed修饰且重写了继承的方法,
那么再C继承B的时候,就不能再有A的该方法,即,但C有B的sealed修饰重写后的方法

重写(override):派生类的方法覆盖基类的方法,要求方法名,参数相同,且访问符大于基类修饰符,返回值一致,不能用private修饰
重载(overload):同一个类中的方法名相同,参数,方法体不同,或派生类

—————向量—————–
物体坐标.normalized,就是向量的方向
一个物体的坐标减去另一个物体的坐标.normalized,可以求得一个方向
例如,一个物体,在另一个物体的左面,用他们坐标差.normalized,就可以求得这个方向

物体坐标.magnitude,就是向量的长度
*****************XML与Json*******************************************
XML:
读写XML都是先创建XmlDocument对象
—写XML数据:
先创建一个可操作XML文档的XmlDocument对象
CreateElement-创建一个节点
SetAttribute-给节点添加一个属性
AppendChild-给节点添加一个子节点
InnerText给节点添加Text标签 <>Text标签

1

JSON:
—写JSON数据:
先创建一个StringBuilder对象,用来存储数据
在创建操作JSON的对象JsonWriter
StreamWriter sw = t.CreateText();流操作,写入文本
WriteObjectStart-写一个{}与writer.WriteObjectEnd ();一对
WritePropertyName -往写的{}加属性,
例如:
StreamWriter sw = t.CreateText();//TODOt暂时不知道怎么回事,应该是创建了一个TXT
StringBuilder sb = new StringBuilder ();
JsonWriter writer = new JsonWriter (sb);
writer.WriteObjectStart ();
writer.WritePropertyName (“Name”);
writer.Write (“阿三”);
writer.WritePropertyName (“Age”);
writer.Write (3);
writer.WriteObjectEnd ();
sw.writeLine(sb.ToString());
–输出:
{
“Name”: “阿三”,
“Age”: “3”
}
writer.WriteArrayStart ();-写一个[]与writer.WriteArrayEnd();一对
例如:
writer.WriteObjectStart ();
writer.WritePropertyName (“Man”);
writer.WriteArrayStart ();
writer.WriteObjectStart ();
writer.WritePropertyName (“Name”);
writer.Write (“阿三”);
writer.WriteObjectEnd ();
writer.WriteArrayEnd();
writer.WriteObjectEnd ();
输出:
{
“Man”
[{
“Name”:”阿三”
}]
};
–读JSON数据:
JsonData jd = JsonMapper.ToObject(“要读的字符串”);
print(“Man”=jd[“Man”])
JsonData jdchild=jd[“Man”]
for(int i=0;i

starting:0

WaitAndPrint:2.00315

done:2.00315

Start方法开始先把print(“starting:” + Time.time);输出,然后遇到yield return,就交给外面程序处理,
然后程序处理WaitAndPrint(float waitTime)的方法,方法里又出现yield return new WaitForSeconds(waitTime);
所以等待这个时间后输出print(“WaitAndPrint:” + Time.time);,先输出WaitAndPrint:2.00315,因为Start()方法
里的协程是要等待WaitAndPrint(float waitTime)方法执行完,才执行,所以最后输出done:2.00315
**************不规则按钮点击**************************************************
* 该脚本实现了UGUI不规则响应区域的按钮
* 使用方法:
* 1、导入按钮图片,要求该图片不应该响应事件的区域为透明,
* 2、设置Texture Type 为 Advanced(在此之前可以先将该值选择为sprite并Apply)
* 3、勾选Read/Write Enable选项,并Apply
* 4、创建按钮,并将图片给按钮上的Image(也可以先创建Image再添加Button组件)
* 5、将该脚本挂载在Image组件所在的物体上
* 6、按照脚本中注释计算alpha值,并赋给脚本中的变量
public float alpha = 0.5f; // 当透明度大于该值时,才响应事件,该值为0f - 1f之间,为图片该区域颜色a值除以255得到
private Image buttonImage;

    void Start()
    {
        buttonImage = GetComponent<Image>();
        if(buttonImage)
        {
            buttonImage.eventAlphaThreshold = alpha;
        }
    }
}

***************打包AssetBundle***********************************************************
SelectionMode.DeepAssets,选择文件夹,包括所有子文件夹及文件
Selection.GetFiltered()//过滤物体打包的时候用来获取
如:Selection.GetFiltered(typeof(object), SelectionMode.DeepAssets);获取所有Object类型物体
AssetDatabase.GetAssetPath(obj);//获取路径
BuildAssetBundleOptions 资源包编译选项
1.CollectDependencies-包含所有依赖关系。
2.CompleteAssets-强制包括整个资源。
3.DisableWriteTypeTree-在资源包不包含类型信息。
4.DeterministicAssetBundle-编译资源包使用一个哈希表储存对象ID在资源包中。

BuildPipeline.BuildAssetBundle(1,2,3,4,5)
参数1.要打包的单个物体,
参数2.要打包的数组物体
参数3.打包后的存放路径
参数4.资源包编译,包含所有依赖选项|资源包编译,强制包括整个资源
参数5.打包的平台

5.AssetDatabase.Refresh();//刷新编辑器
完整代码,打包单个,所以参数2为null

[MenuItem(“CreatAssetBundle/Start”)]

static void CreatBundle()
{
//获取在Project视图中选择的所有游戏对象
Object []objects= Selection.GetFiltered(typeof(object), SelectionMode.DeepAssets);

  //开始遍历游戏对象
  foreach(Object obj in objects)
  {
   string path = AssetDatabase.GetAssetPath(obj);
   //打包后的路径自定义的,
   string tagetPath=Application.dataPath + "/StreamingAssets/" + obj.name + ".assetbundle";
   if (BuildPipeline.BuildAssetBundle(obj,null,tagetPath,BuildAssetBundleOptions.CollectDependencies| BuildAssetBundleOptions.CompleteAssets,BuildTarget.StandaloneWindows))
        {
            print("打包成功");
        }
        else
        {
            print(obj.name + "资源打包失败");
        }
    }
    //刷新编辑器
    AssetDatabase.Refresh();
}

****************打包所有对象为Assetbundle*************************************
Create AssetBundles All:将所有对象打包在一个Assetbundle中。
[MenuItem(“Custom Editor/Create AssetBunldes ALL”)]
static void CreateAssetBunldesALL ()
{
Caching.CleanCache ();

string Path = Application.dataPath + "/StreamingAssets/ALL.assetbundle";

Object[] SelectedAsset = Selection.GetFiltered (typeof(Object), SelectionMode.DeepAssets);

foreach (Object obj in SelectedAsset) 
{
    Debug.Log ("Create AssetBunldes name :" + obj);
}

//这里注意第二个参数就行
if (BuildPipeline.BuildAssetBundle(null, assets, path, BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies, target))) 
{
    AssetDatabase.Refresh ();

} else {

}

}
最核心的方法:
BuildPipeline.BuildAssetBundle (obj, null, targetPath, BuildAssetBundleOptions.CollectDependencies)
参数1:它只能放一个对象,因为我们这里是分别打包,所以通过循环将每个对象分别放在了这里。
参数2:可以放入一个数组对象。
也就是要分别打包的时候参数2写null,要全部打包的时候参数1写null
默认情况下打的包只能在电脑上用,如果要在手机上用就要添加一个参数。
安卓的话需要在参数后再加一个BuildTarget.Android参数
苹果的话需要在参数后再加一个BuildTarget.iPhone
———————加载AssetBundle——-
加载的时候也要分平台,不同平台路径不一样
安卓的:”jar:file://” + Application.dataPath + “!/assets/”;
苹果的:Application.dataPath + “/Raw/”;
PC的:”file://” + Application.dataPath + “/StreamingAssets/”;
读取的时候应该用协程管理
先加载,加载后,再实例,完全代码
//不同平台下StreamingAssets的路径是不同的,这里需要注意一下。
public static readonly string PathURL =

if UNITY_ANDROID

    "jar:file://" + Application.dataPath + "!/assets/";

elif UNITY_IPHONE

    Application.dataPath + "/Raw/";

elif UNITY_STANDALONE_WIN || UNITY_EDITOR

"file://" + Application.dataPath + "/StreamingAssets/";

else

    string.Empty;

endif

void OnGui()
{
    if (GUILayout.Button("读取单个"))
    {
        StartCoroutine(LoadOne(PathURL + "Cube.assetbundle"));
        StartCoroutine(LoadOne(PathURL + "Sphere.assetbundle"));
    }
    if (GUILayout.Button("读取全部"))
    {
        StopCoroutine(LoadAll(PathURL+ "All.assetbundle"));
    }
}
//加载单个
IEnumerator LoadOne(string path)
{
    WWW bundle = WWW.LoadFromCacheOrDownload(path,1);
    yield return bundle;
    //加载到游戏中
    yield return Instantiate(bundle.assetBundle.mainAsset);
    bundle.assetBundle.Unload(false);
}
//加载全部
IEnumerator LoadAll(string path)
{
    WWW bundle = WWW.LoadFromCacheOrDownload(path, 1);
    yield return bundle;
    //加载到游戏
    //yield return Instantiate(bundle.assetBundle.mainAsset);
    Object obj0 = bundle.assetBundle.LoadAsset("Cube.assetbundle");
    Object obj1 = bundle.assetBundle.LoadAsset("Sphere.assetbundle");
    yield return Instantiate(obj0);
    yield return Instantiate(obj1);
    bundle.assetBundle.Unload(false);

}

WWW bundle = new WWW(path);
这样bundle只能保存在内存中,退出游戏后还得重新下,所以游戏中不能用这种方式
应该用:
使用的方法是WWW.LoadFromCacheOrDownload(path,5);
参数1:服务器或者本地下载地址
参数2:版本号
即把以上所有读取Assetbundle中的换为这个就行
***************打包场景为Assetbundle**************************************
因为移动平台不能更新脚本,所以这个功能就会有所限制,我的建议是烘培场景、
然后把多个场景可复用的对象移除,场景中只保留独一无二的游戏对象,然后在打包场景,

运行游戏时载入场景后,在动态的将之前移除的对象重新添加进来。

[MenuItem("Custom Editor/Create Scene")]
static void CreateSceneALL ()
{
//清空一下缓存
Caching.CleanCache();
string Path = Application.dataPath + "/MyScene.unity3d";
string  []levels = {"Assets/Level.unity"};
//打包场景
BuildPipeline.BuildStreamedSceneAssetBundle(levels, path, target, BuildOptions.UncompressedAssetBundle))
AssetDatabase.Refresh ();

}

**********读取场景资源************************
不同平台下需要选择 BuildTarget.Android 和 BuildTarget.iPhone 。
切记这段代码是把Level.unity常见文件打包到MyScene.unity3d文件中,
所以在解包的时候也应当是先解开MyScene.unity3d,然后在去加载Level.unity场景,
无需在ProjectSetting中注册新场景
private IEnumerator LoadScene()
{
WWW download = WWW.LoadFromCacheOrDownload (“file://”+Application.dataPath + “/MyScene.unity3d”, 1);
yield return download;
var bundle = download.assetBundle;
Application.LoadLevel (“Level”);
}
补充:
WWW.LoadFromCacheOrDownload 这个方法建议大家以后不要再用了
因为是异步方法,而且还占用内存。

强烈建议使用AssetBundle.CreatFromFile 它是一个同步方法。现在IOS 和 android 都支持了,强烈建议用。

打包的时候需要选择不压缩。BuildAssetBundleOptions.UncompressedAssetBundle
1.打包出来的Assetbundle我们自己用LZMA压缩,上传到服务器上。
2.IOS或者Android下载这些assetbundle
3.解压缩这些assetbundle并且保存在Application.persistentDataPath 目录下。
4.以后通过AssetBundle.CreatFromFile 读取assetbundle。
**************把场景导出XML或Json***************************************************
Object.FindObjectsOfType(typeof(GameObject))//查找Object类型属性为GameObject的物体
EditorApplication.currentScene//获取打开的场景的路径
sceneName=scenePath.Substring(scenePath.LastIndexOf(‘/’)+1,scenePath.Length-scenePath.LastIndexOf(‘/’)-1);//获取场景的名字带.unity的
sceneName=sceneName.Substring(0,sceneName.LastIndexOf(‘.’));//切割名字从0到.就可以只获取场景名

—导出Unity场景的所有游戏对象信息,把游戏场景中游戏对象的、旋转、缩放、平移与Prefab的名称导出在
XML或JSON中然后解析刚刚导出的XML或JSON通过脚本把导出的游戏场景还原,
接着将层次视图中的所有游戏对象都封装成Prefab保存在资源路径中,这里注意一下如果你的Prefab绑定的脚本
中有public Object 的话 ,需要在代码中改一下。用 Find() FindTag()这类方法在脚本中Awake()方法中来拿,
不然Prefab动态加载的时候无法赋值的
然后,编写工具在Project视图中创建Editor文件夹,接着创建脚本,跟打包AssetBundle一样的格式
[MenuItem (“GameObject/ExportXML”)]
static void ExportXML (){
string filepath = Application.dataPath + @”/StreamingAssets/my.xml”;
if(!File.Exists (filepath))
{
File.Delete(filepath);
}
XmlDocument xmlDoc = new XmlDocument();
XmlElement root = xmlDoc.CreateElement(“gameObjects”);
//遍历所有的游戏场景
foreach (UnityEditor.EditorBuildSettingsScene S in UnityEditor.EditorBuildSettings.scenes){
//当关卡启用
if (S.enabled)
//得到关卡的名称
string name = S.path;
//打开这个关卡
EditorApplication.OpenScene(name);
XmlElement scenes = xmlDoc.CreateElement(“scenes”);
….
//一直把物体导出成自己想要的XML形式


xmlDoc.Save(filepath);
}
AssetDatabase.Refresh();//刷新视图
}

————解析XML场景
void Start (){
//电脑和iphong上的路径是不一样的,这里用标签判断一下。
#if UNITY_EDITOR
string filepath = Application.dataPath +”/StreamingAssets”+”/my.xml”;

elif UNITY_IPHONE

string filepath = Application.dataPath +”/Raw”+”/my.xml”;

endif

//如果文件存在话开始解析。
if(File.Exists (filepath)){
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filepath);
XmlNodeList nodeList=xmlDoc.SelectSingleNode(“gameObjects”).ChildNodes;
…..
//一顿解析后

……
//拿到 旋转 缩放 平移 以后实例对象
GameObject ob = (GameObject)Instantiate(Resources.Load(asset),pos,Quaternion.Euler(rot));
ob.transform.localScale = sca;
}
void Update (){
if(GUI.Button(new Rect(0,0,200,200),”XML WORLD”))
{
Application.LoadLevel(“切换场景的名字”);
}
}
}
其中上述代码的pos与sca是从XML里解析出的
打包解析JSON一样的形式…..略


创建背包,或者任务的时候,首先创建一个类(包括属性,get,set方法),
然后创建一个读取信息的类, 这个类切割信息的时候,首先要new出来背包,或者任务类,然后给背包类,或者任务类
赋值,,然后把背包的一个物品/任务的一条任务赋值,赋值的时候首先把它做成一个prefab,然后赋值,赋值后再
实例化到背包UI物体中/或者任务栏物体中,就会显示出来
注:
背包的一个物品.也是一个类,包括了哪个物品,等级,数量等,所以给背包一个物品赋值的时候,先要再把这个单个物品

类实例出来,再赋值,(实例物品的时候还要考虑是装备还是可以叠加的物品,再单个实例后赋值)

给角色初始化背包的时候,生成后的物品存储在一个集合里面,这个脚本生成存储的集合方法里还需要,判断生成的
物品是否为装备是的话,单独new出来一个数量为1,加入到集合,判断是否为药品,宝箱,材料等,是的话,当集合有相同
的时候把这个数量累加就可以(不用再new)集合没有相同药品,材料等就重新new一个,然后数量为1,加入集合

注:判断集合有没有相同,只判断可叠加物品,在添加集合前,遍历下集合就可以,

每个背包格子,还有身上装备的格子挂载一个脚本,脚本里要有一个设置显示的方法,还要有一个点击的方法,
这样当有物品的时候,点击物品的时候触发点击方法后,进而再触发物品或者装备弹窗脚本上的显示装备信息方法
每个背包格子,身上装备格子都要有一个包括的总的栏,在这个栏里挂载脚本,调用设置显示的方法,就会初始化显示
背包里的物品,装备到身上的装备,背包栏里还需要有添加装备的方法,用来穿上相同装备,或者卸下装备后放入背包
的操作
********快捷键拖放技能***********************************************************
快捷栏拖药品或者技能的时候,给快捷栏添加添加一个脚本,这个脚本属性一个快捷键释放的
属性,一个为何种类型作用,枚举值(药品,技能,无,等)还有一个显示的图标属性UIsprite,
然后写两个方法 一个设置技能的方法,一个设置物品的方法:
public class ShortCutGrid{
public enum ShortType {
skill,//设置技能
inventory,//设置药品
none//初始为不为任何设置

}

private ShortType type=ShorType.none;
private int id;
private UISprite icon;
private SkillInfo info

public void SetSkill(int id){
this.id=id;
this info=SkillInfo._instance.GetSkillById(id);//通过ID得到字典中为ID的物品
icon=info.icon;

}
public void inventory(int id){

}
然后给每个技能栏里的技能图标(非快捷键栏)添加一个BOX碰撞器,添加拖拽组件拖拽组件复制打钩(也就是拖拽后原物体还在)
再添加一个脚本脚本继承拖拽的类UIDragDropItem{
注:添加脚本拖拽且复制的时候,脚本控制的实质为复制体
private int skillID;//技能的ID

protected override void OnDragDropStart(){//重写父类开始拖拽方法,且不覆盖父类方法所以要base下
base.OnDragDropStart();
skillId=transform.parent.GetComponent().id;//id为被拖拽技能的ID,从被复制物体技能脚本下拿到ID
transform.parent=transform.root;//因为复制打钩后拖拽的是复制的物体,把被复物体放在UIroot下.这样可以拖拽到UI控件外
//然后设置下Depth防止在其他控件下显示不出来
this.GetComponent().depth=40;//可以为任意大值
}
//继承拖拽结束方法
protected override void OnDragDropRelease(GameObject surface){
base.OnDragDropRelease(surface);
if(surface!=null&&surface.tag==”快捷栏的tag”){//如果拖拽到的物体不是空且为快捷栏
surface.GetComponent().SetSkill(Skillid);

}

}

}


定义物品类型的时候 ,因为物品有装备还有药品等,所以可以属性只写一个,然后把属性作为枚举,枚举里面写装备,药品等
然后再把装备 ,药品等写一个属性,再写一个枚举,进行细致分类
例如
private InventoryType inventoryTYPE; //这是物品类型,然后枚举存储物品类型的种类

public enum InventoryType {

Equip,//装备
Drug,//药品
Box//宝箱

}
private EquipType equipType;//装备类型,然后枚举存储装备类型的种类

public enum EquipType{
Helm,//武器
Cloths,//衣服
Weapon,//武器
Shose,//鞋子
Necklace,//项链
Bracelet,//手镯
Ring,//戒指
Wing//翅膀
}


四元数与欧拉角
Quaternion.Euler可以把一个向量Vector3类型的转换为四元数,


EulerAngle=旋转-世界的旋转角度


localEulerAngle=自身的旋转角度


Quaternion.identity 相对于实例化的父类相同旋转性


Quaternion.LookRotation 注视旋转


Quaternion.Lerp(1.2.3.) 旋转角度的跟随-一般攻击不在视野范围内的时候要先旋转

//参数1.攻击者旋转,参数2目标的转向,参数3转向的速度

Quaternion.Angle(1.2)返回a和b两者之间的旋转角度差

例如,a旋转了多少度,b旋转了多少度,求两个差

Vector3.Angle(1.2) //两者坐标角度差,方位角度差,1.是坐标差,2.是哪个方向方位差

例如a的position位置, b的position位置,两个不同位置存在一个角度偏差

Vector3.Lerp(1.2.3) 位置的跟随-一般返回两个坐标的位置差

缓动跟随,参数1是本物体坐标,2是要跟随的物体坐标,3是跟随速度

Vector3.normalized//方向规范化,也就是保持向量同样的方向
Vector3.magnitude//返回向量的长度,例如有偏移的时候就是偏移量


unity3d 重要函数方法

当一个脚本调用另一个脚本的方法时,如果脚本方法同一顺序可能不会有效果,所以改下代码的先后顺序
或者改下脚本的执行顺序

Update

当MonoBehaviour启用时,其Update在每一帧被调用。

LateUpdate

当Behaviour启用时,其LateUpdate在每一帧被调用。

FixedUpdate

当MonoBehaviour启用时,其 固定时间调用一次

Awake

当一个脚本实例被载入时Awake被调用。

Start

Start仅在Update函数第一次被调用前调用。

Reset

重置为默认值。

OnMouseEnter

当鼠标进入到GUIElement(GUI元素)或Collider(碰撞体)中时调用OnMouseEnter。

OnMouseOver

当鼠标悬浮在GUIElement(GUI元素)或Collider(碰撞体)上时调用 OnMouseOver .

OnMouseExit

当鼠标移出GUIElement(GUI元素)或Collider(碰撞体)上时调用OnMouseExit。

OnMouseDown

当鼠标在GUIElement(GUI元素)或Collider(碰撞体)上点击时调用OnMouseDown。

OnMouseUp

当用户释放鼠标按钮时调用OnMouseUp。

OnMouseUpAsButton

OnMouseUpAsButton只有当鼠标在同一个GUIElement或Collider按下,在释放时调用。

OnMouseDrag

当用户鼠标拖拽GUIElement(GUI元素)或Collider(碰撞体)时调用 OnMouseDrag 。

OnTriggerEnter

当Collider(碰撞体)进入trigger(触发器)时调用OnTriggerEnter。

OnTriggerExit

当Collider(碰撞体)停止触发trigger(触发器)时调用OnTriggerExit。

OnTriggerStay

当碰撞体接触触发器时,OnTriggerStay将在每一帧被调用。

OnCollisionEnter

当此collider/rigidbody触发另一个rigidbody/collider时,OnCollisionEnter将被调用。

OnCollisionExit

当此collider/rigidbody停止触发另一个rigidbody/collider时,OnCollisionExit将被调用。

OnCollisionStay

当此collider/rigidbody触发另一个rigidbody/collider时,OnCollisionStay将会在每一帧被调用。

OnControllerColliderHit

在移动的时,当controller碰撞到collider时OnControllerColliderHit被调用。

OnJointBreak

当附在同一对象上的关节被断开时调用。

OnParticleCollision

当粒子碰到collider时被调用。

OnBecameVisible

当renderer(渲染器)在任何相机上可见时调用OnBecameVisible。

OnBecameInvisible

当renderer(渲染器)在任何相机上都不可见时调用OnBecameInvisible。

OnLevelWasLoaded

当一个新关卡被载入时此函数被调用。

OnEnable

当对象变为可用或激活状态时此函数被调用。

OnDisable

当对象变为不可用或非激活状态时此函数被调用。

OnDestroy(1.2)1.要销毁的物体,2销毁的时间,也可以只写1参数

当MonoBehaviour将被销毁时,这个函数被调用。

OnPreCull

在相机消隐场景之前被调用。

OnPreRender

在相机渲染场景之前被调用。

OnPostRender

在相机完成场景渲染之后被调用。

OnRenderObject

在相机场景渲染完成后被调用。

OnWillRenderObject

如果对象可见每个相机都会调用它。

OnGUI

渲染和处理GUI事件时调用。

OnRenderImage

当完成所有渲染图片后被调用,用来渲染图片后期效果。

OnDrawGizmosSelected

如果你想在物体被选中时绘制gizmos,执行这个函数。

OnDrawGizmos

如果你想绘制可被点选的gizmos,执行这个函数。

OnApplicationPause

当玩家暂停时发送到所有的游戏物体。

OnApplicationFocus

当玩家获得或失去焦点时发送给所有游戏物体。

OnApplicationQuit

在应用退出之前发送给所有的游戏物体。

OnPlayerConnected

当一个新玩家成功连接时在服务器上被调用。

OnServerInitialized

当Network.InitializeServer被调用并完成时,在服务器上调用这个函数。

OnConnectedToServer

当你成功连接到服务器时,在客户端调用。

OnPlayerDisconnected

当一个玩家从服务器上断开时在服务器端调用。

OnDisconnectedFromServer

当失去连接或从服务器端断开时在客户端调用。

OnFailedToConnect

当一个连接因为某些原因失败时在客户端调用。

OnFailedToConnectToMasterServer

当报告事件来自主服务器时在客户端或服务器端调用。

OnMasterServerEvent

当报告事件来自主服务器时在客户端或服务器端调用。

OnNetworkInstantiate

当一个物体使用Network.Instantiate进行网络初始化时调用。

OnSerializeNetworkView

for循环语句格式:

for(声明一个int数据类型的变量;继续运行条件;在每一次循环体运行结束时运行的指令){

循环体语句块;

}

if语句格式:

格式1:
if(条件){条件成立时(即条件式的值为“true”真时)要执行的语句块;}
格式2:
if(条件){条件成立时(即条件式的值为“true”真时)要执行的语句块;}else{条件不成立时(即条件式的值为“false”假时)要执行的语句块;}

函数:也称为方法,是一系列在游戏运行到特定时候被调用的指令。一个脚本可以包含多个函数,每个函数 中又可以调用其他存在于当前脚本或存在于其他脚本中的函数。unity3d提供了很多自带函数

如:
OnMouseDown()函数、Instantiate()、Translate()、Rotate()等

另事件函数如上,功能函数。

非序列化:C#:[System.NonSerialized] javascript:@System.NonSerialized

public private

Find() 和 FindWithTag()命令:

非常耗费计算能力的命令,要避免在Update()中和每一帧都被调用的函数中使用。在Start()和Awake()中用:变量=GameObject.Find(“物体名”);或 变量=GameObject.FindWithTag(“物体标签名”);

使用公有变量:拖放一个物体到变量上。
引用脚本所在的物体的组件上的参数:如:transform.position=Vector3(0,5.5,4);
renderer.material.color=Color.blue;
light.intensity=8;

SendMessage()命令:一个调用其他物体上指令的方法。

GetComponent()命令:引用一个组件。


鼠标操作
Input.GetAxis(“Mouse ScrollWheel”)//鼠标滑轮的滑动
Input.GetAxis(“Mouse X”)//鼠标水平方向的移动
Input.GetAxis(“Mouse Y”)//鼠标垂直平方向的移动

Input.GetMouseButton(1)//鼠标左键点击
Input.GetMouseButton(2)//鼠标右键点击
Input.GetMouseButton(0)//鼠标滑动键点击

绕某点滑动,1围绕哪个点,2,围绕哪个轴,3.旋转速度
transform.RotateAround(player.position, player.up, rotateSpeed * Input.GetAxis(“Mouse X”));


射线检测:

Vector3 mousePostion = Input.mousePosition;//获取鼠标的坐标位置
Physics.Raycast()//光线投射
RaycastHit//光线投射碰撞,当光线投射有碰撞的时候out到RaycastHit的变量
Ray射线,发一条射线
当碰撞后hitInfo.collider.gameObject可以得到碰撞的物体,hitInfo.point可以得到鼠标点击坐标,即也是物体坐标
判断鼠标左键是否点击且有没有控件有的话点击角色不移动,//NGUI的方法,UGUI不能用

Input.GetMouseButton(0)&&UICamera.hoveredObject==null

Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//从屏幕位置想鼠标点击位置发射线
RaycastHit hitInfo;//光线碰撞检测
bool isCollder = Physics.Raycast(ray, out hitInfo);//光线投射
//有碰撞且碰撞到的标签为地面
if (isCollder && hitInfo.collider.tag == Tags.ground)
{
isMoving = true;
//实例化点击效果
ShowClickEffect(hitInfo.point);
LookAtTaget(hitInfo.point);
}

前提碰撞检测的物体要加碰撞器

假如要做鼠标点击屏幕角色跟随移动
代码:


委托的使用::

委托与事件其实就是当一个方法执行后异步加载另一个方法,
例如当输入密码错误要提示的时候,创建一个集合存放错误,集合的泛型是委托,委托类里除了定义委托还可以定义一个
错误的提示变量,这样当有错误的时候加入集合,然后,错误后面还可以跟一个方法体,如错误提示完接着,弹出登陆框
错误弹窗要单独写,里面有两个属性一个就是text,在弹窗内显示错误的提示语,第二个就是委托事件的变量

代码例如:

委托类:
public delegate void PopResult();
public class PopModel {

public  PopResult popResult;
public string value;

public PopModel(string value,PopResult popResult = null) {
    this.value = value;
    this.popResult = popResult;

}

错误弹窗类:
Pop类

private Text text;
PopResult popResult;
public void Active(PopModel popModel){
text.text = popModel.value;
this.popResult = popModel.popResult;
gameObject.SetActive(true);
}
public void Close() {
gameObject.SetActive(false);
if (popResult != null) {
popResult();
}

}

管理错误弹窗类
Popmessage类
public static List errors = new List();
[SerializeField]
private Pop pop;//引用pop错误框类

void Update () {
    if (errors.Count > 0) {
        PopModel err = errors[0];
        errors.RemoveAt(0);//移除
        pop.Active(err);
    }

}

有错误的时候添加错误
Popmessage.errors.Add(new PopModel(“账号或者密码不能为空”, RegClick));

public void RegClick() {

    regPanel.SetActive(true);
}

这里委托加入了 RegClick()方法体,所以可以异步调用此方法,
总结:
当有错误的时候,new委托类,把错误提示和要加载的方法添加进去,然后把这个new出来的加入到一个集合
判断集合不为空即>0的时候证明有错误,就把错误信息即异步调用的方法给委托类接收,然后调用错误弹框
错误类的方法属性为委托类,错误弹窗的text可以等于委托类里的string属性值,错误弹窗的另外一个属性
接受委托类异步方法,这样在错误弹窗关闭按钮触发事件的时候判断,这个错误弹窗接受方法属性是否为空
不为空就调用此属性();


卡牌类游戏
当给卡牌上设置Label的时候拖拽或者要移动的时候Lable可能会错位,这时要添加一个UIAnchor脚本,NGUI自带的脚本
可以设置跟随的卡牌,添加后Contaniner选择跟随的是那个物体,Side设置方位,RUNOnlyOnce根据需要设置,相机不用

播放视频的时候判断自然播放完成先给一个true的标志位,当true的时候播放,自然播放完的时候视频的.isplaying为false
所以判断标志位和自身播放完为false的isplaying比较不同时停止视频的绘制

绘制视频且正在播放的时候要停止播放不但要停止播放还要禁止绘制
GUI.label=(newRect(1.2.3.4.),”“)GUI绘制一个label,1.2绘制到屏幕的位置,3.4.绘制的Label的长宽,后面跟的
是Label要显示的内容绘制图片的时候,视频的时候一样的设置方法参数

Time.timeScale = 0;暂停游戏,=1开始游戏

存储的时候可以使用:
PlayerPrefs.SetFloat(“loadScore”, nowScore);设置玩家存储,第一个参数是存储的变量名字,第二个存储的信息
getFloat获取存储的信息,也可以是int string类型getint getstring
一个场景从另一个场景拿值的时候就可以使用PlayerPrefs.getFloat(“loadScore”,nowScore);

当需要更换显示的图片的时候可以把每个物体的名字设置为图片的名字,这样拿到物体的name,就后直接赋值给spriteName

当有一个集合放置所有物体的时候,但是我们需要显示我们可用的最大数量,而且当可用的使用后,变为另外的不可用标识
我们就需要,一个最大可用,还有一个剩余要显示可用标识的变量,还有一个数组集合
先循环i=最大可用;<=数组集合;把这些不需要显示i的gameObject.SetActive(false);
然后在用i=0;i<最大可用;i++;然后把这些可用的gameObject.SetActive(true);
然后再循环i=剩余显示可用标识变量;i<最大可用变量;i++ 这个就是可使用但是不需要显示标识的物体,物体的.spriteName
改为可用但没有标识的图片名字即可用但是已经使用过的
然后再循环i=0;i<剩余显示可用标识变量;i++;这个就是可使用也要显示标识的物体可用还未使用

当让一个卡牌随机变换且有变换效果的时候可以参考2D的,飞机大战的轮播模式,当然,当我们的卡牌轮播大于数组的时候
需要重新轮播就进行对数组长度求余,然后,当时间到的时候,停止变换,会随机到时间到达后轮播的那一张前提要有标志位

设置是否开始轮播,到达轮播总时间停止轮播

当选择性操作时例如有一些卡牌点击其中一张,在另一个地方显示所点击的卡牌图片以及卡牌角色的名字,
在每张卡牌上挂脚本,可以先把这些卡牌角色的名字存储到一个数组,然后通过
this.gameObject.name拿到卡牌物体名称,然后通过物体名称得到卡牌物体名称的最后一个数字,例如
string heroName = this.gameObject.name;//当前物体的名称
select_HeroSprite.spriteName = heroName;//要显示的精灵图片名字与物体名称一样
char heroIndexChar = heroName[heroName.Length - 1];//拿到名称中的数字
拿到名称中的最后一个数字后,可以通过这个数字判断在集合中卡牌图片的角色名字的索引
然后int heroIndex = heroIndexChar - ‘0’;//换算为int
再通过索引得到卡牌的角色名字

select_HeroLabel.text= heroNames[heroIndex - 1];//减去1因为数组开始为0索引

每当新增加或者减少一张牌的时候,把这些牌加入集合或者移除集合,然后遍历这个数组,每张牌的位置即为该
牌的索引位置*偏移值+1号牌的位置
例如偏移值xoffset=card2.positon-card1.positon;
然后增加牌的方法
void AddCard(){
GameObject go= NGUITools.AddChild(this.gameObject, cardPrefab);
cards.Add(go);//加入到集合
for(int i = 0; i < cards.Count; i++)
{
Vector3 toPosition1 = new Vector3(xOffet, 0, 0) * i + card01.position;
iTween.MoveTo(cards[i], toPosition1, 0.5f);
}
//然后遍历集合,这样就能实现重新排列顺序不管增加还是减少牌都可以实现重新排序
}

前提,先创造两个物体隐藏起来,代码获取到两个物体的位置进而计算量物体之间的差值距离

每当新增一张卡牌,上一张卡牌要下移,以此类推的时候
要先创建一个集合,然后算出两个卡牌之间的差值
先实例一个卡牌后放到第一个卡牌的位置,然后循环遍历数组,循环体里让数组I物体,移动到i.position+差值的位置
最后再把这个第一张新加的卡牌添加到集合,这样就保证了循环遍历的时候第一张卡牌不在数组里,
这样当你再次添加的时候,第一次添加的卡牌才再数组里,第一次的卡牌位置也会变换到下一个位置,新增的就会在
第一个位置
总结:
1.不管插入还是移除卡牌实例完后必须先添加到数组,再重新遍历数组,其卡牌位置就是对应数组的索引*偏移+
第一个卡牌.位置
2.如果是插入的排在最前,先插入的后移,实例完后,遍历数组,其卡牌位置是它的索引.位置+偏移,最后加入到数组
移除的话因为是要移除最后一个卡牌,最后一个即第一个添加的数组,所以是把数组[0]移除指定位置就可以
3.当是发牌位置直接移入指定位置的时候,实例后直接加入到数组再遍历即可达到在发牌处显示一下立即到手里

当先到发牌位置后移入指定位置的时候,因为刚实例位置为0,所以实例后然后yield return 0;停一帧,再把牌移入指定位置

游戏设置倒计时的时候,假如倒计时60S,先写一个60S的变量,再写一个初始值为0的计时器
然后在Update里计时器+=Time.deldaTime;,然后用60-计时器走的数,在用一个变量wickHopeTimer接收
这个数,wickHopeTimer转换成int型后赋予Lable.text即可在屏幕上显示一个倒计时
当需要倒计时到一定时间的时候有其它操作,例如想在倒计时为15S的时候有一个滑动条一样的随着最后15
秒减少直至消失,就做一个滑动条或者其它东西绳子等 ,设置好为Fill后,让其wickHope.fillAmount的值等于
wickHopeTimer/15,因为现在wickHopeTimer是随着倒计时走的 所以现在倒计时为多少时间wickHopeTimer

就是多少时间再设置为0的时候把滑动条隐藏就可以

左右横向摆放卡牌的时候,卡牌之间的偏移是X轴在改变,放左边X轴减少即是减少偏移,向右则是增加偏移

当拖放卡牌到中间区域且摆放位置第一个拖放的在中间,后面的偶数拖放放左边,奇数放右边的时候
先设置两个隐藏的卡牌当做位置,求得两个卡牌间的偏移,卡牌1放中间,卡牌2左右都行
然后求得是放左边还是放右边排列的时候,先要有一个数组存放添加进来的卡牌,添加完后,数组的长度
就是卡牌拖放进来的循序,即list.count,用一个变量接收表示当前拖放进来的卡牌的序号,然后用这个index序号
跟2取模,==0的情况就是偶数序号,所以它摆放在区域的左边,摆放的位置是第一张卡牌加两张卡牌的偏移
因为是偶数放左边所以偏移位置是2的倍数,即放在左边卡牌的位置是第一张.position-index/2*偏移xOffset
else情况放右边,偏移位置是第一张.position+index/2*偏移xOffset

在拖拽成功的时候,把角色类下的卡牌移除,同时调用失去卡牌重新排序的方法,
在拖拽类中调用移除的方法,和区域类添加当前卡牌的方法


角色选择需要点击下一个或者前一个转换角色的时候
可以先把角色做成prefab,然后放进一个数组里,然后挂脚本的时候把这个数组写进去,还要再重新new一个新数组
数组的长度为角色数组的长度,这个新数组用来实例角色,做两个角色前后点击变换按钮,
然后再Awake方法里把新建数组长度写入例如characterGameObjects = new GameObject[length];
然后在本方法里遍历这个数组实例出[i]=每一个角色,调用更新的方法

然后在更新的方法里遍历初始的角色数组,设置一个当前索引的变量,把当前索引的角色设置为显示,遍历这个初始数组
把i!=当前索引的角色设置为隐藏,

然后再创建前后点击的方法,前点击就把当前索引变量++;为了不让其超过数组的长度,然后再后面写入%=数组长度
再调用更新角色的方法
后点击方法里让当前索引–;当为-的时候,因为要更新到后面的角色,所以是数组的最后一个,所以判断当索引–的值
为-1的时候让其等于数组长度-1;再调用更新方法,至此完成


Debug.DrawLine 绘制线
SkinnedMeshRenderer组件下的CastShadows(选择trueORfalse)改变物体是否接受光照,代码可以写为
renderer.castShadows = false;
Layer Collision Matrix图层碰撞矩阵Edit->Project Settings->Physics下可以打开
当组队时,或者不想物体间发生碰撞的时候可以对这个设置,打钩代表可碰撞,取消打钩不碰撞


A*寻路
GridGraph一个基本的寻路网格
RecastGraph多层寻路网格

下载插件后导入unity,新建个地面和障碍物,分别把Layer命名为Ground和Obstacles。
在建一个空物体,命名为A*,单击Component-Pathfinding-Pathfinder 就可以看见Inspector面板出现了AstarPath,
点击Graphs,添加一个新的Graph(Add New Graphs)这里就是添加我们的导航图。这里有很多种类型,我们往后一个个介绍,
先创建一个基于单元的导航图,单击Grid Graph,会生成一个width*depth的规则网格,将NodeSize设置1,表示节点之间
的间距为1个单位,将Center的坐标设置为(0,-0.1,0)
因为我们的地面y=0,如果GridGraph的y也为0,在投射射到平面时候,可能会产生浮点误差,所以我们稍微下降一点高度。
然后是CollisionTesting 碰撞测试选项里面,改变Mask的值,我们不希望AI在Obstacles层移动,所以设置Mask设置
Obstacles层就可以。接着是HeightTesting 高度测试,我们让射线的长度为100,Mask设置Ground层,就是只在Ground层
进行投射,最后点击最下方的Scan,添加一个角色,把一个带动画的人物拖入场景,首先添加角色碰撞器,调整好属性,
选中人物,点击Component-Pathfinding-Seeker 为角色添加seeker脚本,使角色能够感知A*寻路,
然后我们在创建一个移动脚本,让角色能够移动。为角色添加AstarAI.cs 脚本
脚本内容:
先拿到角色的CharacterController(控制角色移动),拿到角色下的Seeker脚本,因为要用这个脚本的pathCallback方法
先要在FixedUpdate里判断当前路径类是否为空,是空返回再判断当前角色走的路点编号是否大于等于所有路点编号的总和,
是的话就是到达目标就返回,
然后计算角色去往的当前路点所需的行走方向和距离,控制角色的移动,也就是controller.SimpleMove(dir);
然后角色转向目标,先看向目标Quaternion targetRotation=Quaternion.LookRotation(dir);然后缓慢转向这个targetRotation
transform.rotation=Quaternion.Slerp(transform.rotation,targetRotation,Time.deltaTime*turnSpeed);
如果当前位置与当前要去往的路点距离小于一个给定的值就可以转向下一个路点
if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance)
{
currentWaypoint++;
return;
}

然后再寻路结束后,如果找到了一条路径,保存下来,并且把第一个路点设置为当前路点

代码:
//目标位置
public Transform targetPosition;
//声明一个seeker类的对象
private Seeker seeker;
private CharacterController controller;
//一个path类的对象。表示路径
public f path;
//角色每秒的速度
public float speed = 100;
//当角色与一个航点的距离小于这个值时,角色便可转向路径上的下一个航点
public float nextWaypointDistance = 3;
//角色正朝其行进的航点
private int currentWaypoint = 0;

void Start()
{
    //获得对Seeker组件的引用
    seeker = GetComponent<Seeker>();
    controller = GetComponent<CharacterController>();
    //注册回调函数,在AstartPath完成寻路后调用该函数。
    seeker.pathCallback += OnPathComplete;  
    //调用StartPath函数,开始到目标的寻路
    seeker.StartPath(transform.position, targetPosition.position);
}

private int turnSpeed = 100;
void FixedUpdate()
{
    if (path == null)
    {
        return;
    }
    //如果当前路点编号大于这条路径上路点的总和,那么已经到达路径的终点
    if (currentWaypoint >= path.vectorPath.Count)
    {
        Debug.Log("EndOfPathReached");
        return;
    }
    //计算出去往当前路点所需的行走方向和距离,控制角色移动
    Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
    dir *= speed*Time.fixedDeltaTime;
    controller.SimpleMove(dir);
    //角色转向目标
    Quaternion targetRotation=Quaternion.LookRotation(dir);
    transform.rotation=Quaternion.Slerp(transform.rotation,targetRotation,Time.deltaTime*turnSpeed);
    //如果当前位置与当前路点的距离小于一个给定值,可以转向下一个路点
    if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance)
    {
        currentWaypoint++;
        return;
    }
}
//当寻路结束后调用这个函数
public void OnPathComplete(Path p)
{
    Debug.Log("FindThePath"+p.error);
    //如果找到了一条路径,保存下来,并且把第一个路点设置为当前路点
    if (!p.error)
    {
        path = p;
        currentWaypoint = 0;
    }
}

void OnDisable()
{
    seeker.pathCallback -= OnPathComplete;
}

还要在面板此脚本下的public Transform targetPosition;目标位置处设置一个物体拖进去

如果需要角色移动的平滑些可以通过Component-Pathfinding-Modifiers添加,里面有很多种平滑脚本,
我们选中SimpleSmooth 添加到角色上,然后慢慢调整参数,


不管android,还是iphone, 资源放在StreamingAssets下,使用Application.streamingAssetsPath读,使用Application.persistentDataPath去写。


截图功能


void shot()
{
Texture2D temp = new Texture2D( width, height);
//新建一个texture2D用来临时存储我们的图片,width和height分别是图片的宽和高
temp.ReadPixels (new Rect(position_topLeft.x, position_topLeft.y,width, height)),0,0);
//为temp临时图片存入内容,就是读取屏幕上的内容,读取区域由第一个参数Rect决定, //截图区域任意性就在这里体现啦~不知道Rect的朋友我在下面讲一下
temp.Apply ();
byte[] bytes = temp.EncodeToPNG ();
//将temp临时图片的内容转换为byte数组存入临时数组bytes中
string filePath = Application.dataPath+”/123.png”;
//这个是图片存储位置,存储位置任意性就在这里体现啦~
System.IO.FileStream s = new System.IO.FileStream (filePath, System.IO.FileMode.Create);
//新建一个文件流,存储路径设为我们刚刚设置的,用于下面的文件存储
//这个地方大家要注意下,调用了windows的dll所以在Unity里要设置以下,我会在下面说明
s.Write (bytes, 0, bytes.Length);
//将存有图片信息的byte数组写入通过文件流写入
s.Close();
Debug.Log (“screenshot!”);
}
相关说明:
1.Rect是表示一个矩形区域的一种变量,它的四个初始化参数分别代表:矩形区域左上点x坐标,矩形区域左上点y坐标,矩形区域宽度,矩形区域高度。
2.调用windows的dll需要设置如下:
Edit->project setting->player->other setting,
将选项:Api Compatibility Level设为.NET 2.0


Application.platform == RuntimePlatform.Android返回游戏运行的平台(只读)。
安卓上跟其他平台不一样,安装后这些文件实际上是在一个Jar压缩包里,所以不能直接用读取文件的函数去读,
而要用WWW方式。具体做法如下:
//1.把你要读取的文件放在Unity项目的Assets/StreamingAssets文件夹下面,没有这个文件夹的话自己建一个。
//2.读取的代码(假设名为”文件.txt”)
byte[] InBytes; //用来存储读入的数据
if (Application.platform == RuntimePlatform.Android){ //判断当前程序是否运行在安卓下
string FileName = “jar:file://” + Application.dataPath + “!/assets/” + “文件.txt”;
WWW www = new WWW(FileName); //WWW会自动开始读取文件
while(!www.isDone){}//WWW是异步读取,所以要用循环来等待www.isDone是否读取完成
www.byte以字节组的形式返回获取到的网络页面中的内容(只读)。
InBytes = www.bytes;//存到字节数组里

} else { //其他平台的读取代码 

}


AssetDatabase 资源数据库,是一个对资源进行读写和各种操作的接口。
AssetDatabase.GetAssetPath 获取资源路径,返回相对于工程资源位置的路径名
//新建一个简单的材质资源,在Assets下
var material = new Material (Shader.Find(“Specular”));
AssetDatabase.CreateAsset(material, “Assets/MyMaterial.mat”);

Shader.Find//查找名为name的着色器。
//创建一个带有transparent diffuse着色器的材质
var material = new Material (Shader.Find (“Transparent/Diffuse”));


Input.GetAxisRaw与Input.GetAxis都可以控制移动,区别是,第一个更灵敏,第二个有加速度感觉PFS建议用+Raw的
FPS控制移动因为是第一人称所以控制的是角色的,不是世界的视野
所以WSAD控制的是角色的前后h*transform.right,v * transform.forward
dir.normalized*speed //normalized这个是单位向量,其实就是方位方向
FPS游戏直接给刚体速度移动角色,控制性低
物理位移放在FixedUpdate()里面处理
FPS移动刚体的时候需要用Rigidbody.MovePosition()这个方法把刚体移动到指定的position
Rigidbody.MovePosition()方法里为角色position+移动方向和速度
如: Rigidbody.MovePosition(transform.position + moveVelocity * Time.deltaTime);
第一人称PFS射击游戏一般用这个控制刚体移动,而不是给刚体施加速度向量,会导致不真实
移动方法在FixedUpdate()里面处理

控制鼠标的左右转向的时候其实是控制角色的Y轴旋转,控制鼠标上下的时候控制角色里的相机Y轴上下

制作FPS游戏的时候如果想给开枪后一个后座力,让镜头一个抖动的效果可以实现,给定的属性是一个标志位,
一个抖动值,一个抖动后回归原位的速度值,一个原来开枪前的镜头自身旋转位置,在Update()方法里判断当开枪
的按键抬起的时候把标志位设置为true,,把原来的自身旋转值得到,然后根据标志位判断,当为true的时候
给镜头自身旋转一个抖动(设置其面板上R的欧拉角也就是EulerAngles),然后再把标志位设置为false,然后再判断
当标志位为false的时候,也就是开枪完毕的时候让镜头的抖动缓慢恢复为原来的自身旋转,所以要用
transform.localRotation = Quaternion.Slerp(transform.localRotation, oldRotation, Time.deltaTime * speed);
整个流程详细代码:
public float shakeRange; //抖动值
public float speed;//恢复为原来自身旋转的速度值
bool isShake = false;//标志位
、、float shake;
Quaternion oldRotation;
void Update(){
if (Input.GetKeyUp(KeyCode.K)){//当按键抬起的时候标志位改为true并拿到镜头自身Rotation初值
isShake = true;
// shake = shakeRange;
oldRotation = transform.localRotation;
}

    if (isShake){//开枪的时候镜头抖动
transform.localRotation = Quaternion.EulerAngles(oldRotation.x + shakeRange, oldRotation.y, oldRotation.z);
        Debug.Log("shakeRange=========" + shakeRange);
        Debug.Log("transform.localRotation========" + transform.localRotation);
        isShake = false;
    }
    if (isShake == false){//开枪键抬起的时候镜头缓慢跟随恢复为原来的自身旋转角度

transform.localRotation = Quaternion.Slerp(transform.localRotation, oldRotation, Time.deltaTime * speed);
}
}


控制移动的时候要把角色跟相机分离开来,相机作为自物体存在,控制视野左右的时候,只需控制角色的左右移动,
控制上下看的时候,只需要控制相机的上下移动


做地图小图标的时候需要一个面片Quad来放置小地图上角色或者怪物物体等显示的标志,然后把标志拖到面片上然后在
面片的Shader里的-Transparent-Diffcult,就可以只显示图标,面片透明,然后给面片一个层,这个面片要放在
角色,物体或者怪物的下面,把面片做成一个Prefab这样在每一个需要图标的物体下加这个面片
做小地图的话要分层,地面,还有物体 还有图标都要设置不同的层,这样照相机只渲染图标层和地面,就可以达到
小地图的效果———–分层是Layer,相机渲染Layer不同图层设置是在相机的Camera组件-CullingMask里设置
当然这个相机不是主相机,而是一个新建的只渲染小地图的相机这个相机不需要设置为3d相机只需要设置为正交的
相机所以在Camera组件下的-Projection设置为Orthographic,把size设置可以看到小地图的视野,这个相机要放到角色
下然后reset下角度朝下
做好这些后把这个相机作为一个材质.作为图片显示到别的东西上,在Project面板创建一个RenderTextrue,把相机上
Camera组件下的RenderTextrue指定为刚才在Project面板创建的RenderTextrue,这样这个RenderTextrue材质会实时
显示小地图,然后就可以做一个UI把刚才Project面板创建的RenderTextrue作为图片显示出来在屏幕上
首先还需要创建一个材质求Materrial把它的Shader选择为Custom-Mask,然后出多出两个选项一个MainTexture把刚才的
RenderTextrue材质放入,另一个MaskTexture放入地图包里的Mask,然后在NGUI下的ROOT里创建一个SimpleTexture
注:材质选择Shader为Custom-Mask的话需要导小地图素材包
把UITexture组件下的Material放入刚才创建的材质球Materrial


刚体移动的时候,XZ移动的话就把position上的Y轴锁定不让Y轴动,沿Y轴旋转的话,就把rotation上的XZ锁定不让XZ旋转
Untiy作为四元数内部储存旋转角度。旋转一个物体,使用Transform.Rotate,使用Transform.localEulerAngles为设置作为欧拉角的旋转角度。
Transform.localEulerAngles 自身欧拉角(旋转作为欧拉角度,相对于父级的变换旋转角度)
Transform.localRotation 自身旋转角度(物体变换的旋转角度相对于父级的物体变换的旋转角度)
Rigidbody.angularVelocity 角速度(刚体的角速度向量)。
transform.eulerAngles//物体transform面板上的R三个坐标值
Vector3.up 向上等同于Vector3(0, 1, 0)的简码,也就是向y轴。
绕一个轴旋转多少度可以写为transform.rotation = Quaternion.AngleAxis(30, Vector3.up);也可以写成
Quaternion.Euler(Vector3(0, 30, 0));


UICamera.hoveredObject==null//角色移动判断的时候加这句可以防止点击UI控件的时候角色也在移动,
需要再给各个UI控件添加BOXCllider;

UIinptu的时候把 Validation设置为Integer这样默认只能输入整数

背包展开与关闭,先在Awake()里让其隐藏(u3D里面让其显示),然后调用Tween.AddOnFinished(1.)方法添加监听
动画是否播放完参数为方法名
然后给展开,关闭时间添加标志位,bool isShow = false;如果展开为true,关闭的话关闭方法isShow = false
然后在监听方法里判断如果isShow = false的话调用隐藏的方法,
然后给外界一个方法判断标志位是true还是false来确定要打开还是关闭背包
public void TranfromState() { //状态转变
if (isShow == false) {

         BagShow();

     }else{

         BagHid();

     }

}
外界调用这个TranfromState()方法直接就可以操作显示与隐藏背包


访问另一个类的方法的时候,把一个类设置成单例, public static 类名 属性 ;然后在Awake(){}里写入属性=this
如果设置的是private 则另外再加一个get方法来让外界访问
如果为服务端,因为不是unity3D脚本,所以没有Awake()方法,要把Awake()方法替换为构造方法,再设置get方法,一样
可以被外界访问 例如服务端的单例:
private static TaidouApplication _instance;
//构造方法,把类作为单例
public TaidouApplication() {
_instance = this;
}
public static TaidouApplication Instance{
get { return _instance; }
unity里面:
private static photonEngine _instance;
void Awake() {_instance = this;}
public static photonEngine Instance{
get { return _instance; }
或者直接 共有访问修饰符
public static photonEngine _instance;
void Awake() {_instance = this;}


当角色不在怪物攻击视野内的时候,可以让怪物自动移动类似巡逻的意思,不在的话就让敌人的XZ每隔一段时间随机生成
代码:
public float speed=4;
private float timer=0;
private float dir_x=0;
private float dir_z=0;
void Update(){
timer+=Time.deltaTime;
if(timer>4){//当计数器大于4秒的时候
dir_x=Random.Range(-1,1f);//给X,Z分别一个随机数
dir_z=Random.Range(-1,1f);
timer=0;..然后把计数器归0,重新计时
}
//然后让怪物自动在这个范围移动
transf.translate(new Vector3(dir_x,0,dir_z)*speed*Time.deltaTime);
}

制作怪物生成器的话建一个Cube物体,把is Trigger打钩,把网格勾掉,添加自定义脚本,首先创建两个数组一个存放
敌人,一个存放敌人生成后所在的位置,还有碰撞后多少秒生成,一般是0秒,还有多久后生成的变量,遍历两个数组
然后实例化,第一个为敌人,第二个为位置的空物体即空物体的position,不需要旋转,,然后在面板上把要生成的敌人
放到敌人数组,把位置的空物体放到位置数组,,记得在角色攻击敌人的时候,在敌人的脚本中在start方法把敌人加入
到角色攻击敌人数组的集合这样角色就可以攻击到敌人,使用协程控制生成,然后在OnTriggerEnter中判断是否为玩家碰撞,
是的话就调用协程的StartCoroutine()方法
/*
敌人生成触发器-角色碰撞到就触发
*/

public class EnemyTrriger : MonoBehaviour {
public GameObject[] enemyPrefabs;//要生成的敌人
public Transform[] spawnPosArray;//生成的位置
public float time = 0;//多少秒后开始生成
public float repeateRate=0;//每隔多久生成一波
private bool isSpawnPosArray = false;//判断是否触发了生成敌人,触发后角色走过时就不再触发

void OnTriggerEnter(Collider col) {
   if (col.tag== "player"&&isSpawnPosArray==false)
    {
    isSpawnPosArray==true;
        Debug.Log(col);
        StartCoroutine(SpawnEnemy());
    }
}
IEnumerator SpawnEnemy() {
//等待多久生成敌人
    yield return new WaitForSeconds(time);

      foreach(GameObject go in  enemyPrefabs){
        foreach (Transform t in spawnPosArray) {

        //实例集合中的敌人,放到位置集合,不需要旋转
            GameObject.Instantiate(go, t.position, Quaternion.identity);


        }
//等待多久再次生成敌人
        yield return new WaitForSeconds(repeateRate);


    }


}

}

特效的移动可以是:transform.position += transform.forward * moveSpeed * Time.deltaTime;
当特效或者法术是可移动攻击的时候需要加入碰撞器,然后脚本里判断是否进入或离开触发器的方法
void OnTriggerEnter(Collider col)//进入触发器
void OnTriggerExit(Collider col)//离开触发器
进入的时候需要先判断如果是敌人攻击则col.tag应该攻击玩家,反之玩家法术则攻击敌人
if (col.tag == “Player”),且集合里也没有这个玩家的话就加入集合,判断集合里有没有需要通过
playerList.IndexOf(col.gameObject)<0则表示没有就加入集合
离开触发器的时候只需要>0,然后从集合中Remove

InvokeRepeating(1.2.3);重复调用一个方法,参数1为方法,参数2为多久开始调用,参数3时多少秒调用
InvokeRepeating(“attack”,0,0.25f);如果调用攻击的时候 是0秒开始调用 0.25秒开始调用,1秒就触发4次伤害,

伤害是一秒触发一次 ,所以这个应该是伤害的1/4

CancelInvoke(1.)不再调用某个方法参数为引号方法名
攻击的时候把集合里面的遍历一遍,把受到碰撞-被攻击的触发被攻击的脚本(动画,受到的伤害值等)


Rigidbody.MovePosition();移动刚体
Rigidbody.MovePosition(transform.position+transform.forward*moveSpeed*Time.deltaTime);
CharacterController.SimpleMove()移动一个CharacterController进而移动物体
cc.SimpleMove(transform.forward * speend);//给敌人一个自由移动事件


当一个敌人需要有视野攻击的时候, 先要判断在不在攻击视野范围内,在的话进行计数器–判断是否到攻击时间,进行攻击
如果在视野内不在攻击范围内的话,就追赶(移动角色去追赶敌人),不在攻击视野范围内的话,就要进行旋转角度,即
旋转到可攻击的视野内

当敌人有视野的时候,如果在视野内可以进行攻击或追赶,如果不在视野内,需要先转向到视野内
转向的角度就是与角色的角度差,所以先得到角度差,然后注视这个角度差
//在攻击视野之外,进行转向
Quaternion tagetRotation = Quaternion.LookRotation(playerPos - transform.position);//角色朝向-角度差朝向
//转向目标方向
transform.rotation= Quaternion.Lerp(transform.rotation, tagetRotation, rotateSpeed*Time.deltaTime);
Quaternion.Lerp(1.2.3)//参数1.攻击者旋转方向,参数2目标的转向,参数3 转向的速度
//转向目标方向
transform.rotation= Quaternion.Lerp(transform.rotation, tagetRotation,rotateSpeed*Time.deltaTime);

Quaternion.Lerp()与Quaternion.LookRotation()前者为缓慢转向目标,后者转向目标(看向目标)


如果要限制一个旋转的角度可以使用Quaternion.eulerAngles得到来得到欧拉角度,然后使用Mathf.Clamp对其进行插值运算

如:transform面板上的R的x角度Quaternion.eulerAngles.x

Quaternion.AngleAxis(1.2.)1.旋转角度,2,绕哪个轴

如:transform.rotation = Quaternion.AngleAxis(30, Vector3.up);//设置变换的旋转,绕y轴旋转30度

视野的上下左右旋转:
当鼠标右键滑动的时候,先用一个bool变量判断是鼠标右键是抬起还是按下,抬起的时候标志位设为false,按下的时候
设为true,然后再判断这个bool的标志位,如果为true的时候证明鼠标按下,当按下的时候左右拖动鼠标的时候应该让
视野围绕着角色旋转所以要用transform.RotateAround(1.2.3),1围绕哪个点,2,围绕哪个轴,3.旋转速度(鼠标x水平左右)
例如:
transform.RotateAround(player.position, player.up, rotateSpeed * Input.GetAxis(“Mouse X”));
因为是鼠标右键按下左右拖动,所以是鼠标X键水平左右滑动的时候,围绕着角色的position,围绕的轴为角色的up轴
当上下滑动视野的时候,应该是围绕mouse.Y鼠标水平的上下方向,因为上下滑动所以应该围绕角色旋转,围绕的轴则是
相机的左右轴,代码:
transform.RotateAround(player.position, transform.right, -rotateSpeed * Input.GetAxis(“Mouse Y”));
水平方向上下视野旋转的时候就是角色transform面板上R的x旋转所以把这个x限制两个范围就可以当上下滑动视野的时候
不至于超过范围所以要先拿到这个X 就是transform.eulerAngles.x;然后限制一个范围例如
if (x <= 10 || x >= 70) {//如果相机旋转的角度大于这个范围则归为原来的角度
当超过这个范围的时候,就让相机的position还有rotation归为原来的position和rotation
transform.eulerAngles其实就如同rotation,但是这里不能替换为rotation,
左右上下旋转做好后,还要把偏移设置下,
当在Update里面调用的时候要先调用上下左右的拖动,后调用缩放视野

//处理视野的旋转
void RotateView() {
//Input.GetAxis(“Mouse X”);//得到鼠标在水平方向的滑动
//Input.GetAxis(“Mouse Y”);//得到鼠标在垂直方向的滑动
if (Input.GetMouseButtonDown(1)) {
isRotaing = true;
}
if (Input.GetMouseButtonUp(1)) {
isRotaing = false;
}
if (isRotaing) {
//相机绕某点滑动,1围绕哪个点,2,围绕哪个轴,3.旋转速度
transform.RotateAround(player.position, player.up, rotateSpeed * Input.GetAxis(“Mouse X”));
Vector3 originalPos = transform.position;//相机原position
Quaternion rotation = transform.rotation;//相机原rotation
transform.RotateAround(player.position, transform.right, -rotateSpeed * Input.GetAxis(“Mouse Y”));
float x = transform.eulerAngles.x;//拿到transform面板上的R的x角度
if (x <= 10 || x >= 70) {//如果相机旋转的角度大于这个范围则归为原来的角度
transform.position = originalPos;
transform.rotation = rotation;
}

        ////拿到transform面板上的R
        //float x = transform.eulerAngles.x;
        //x = Mathf.Clamp(x,15,80);//限制相机的R的X角度
        //transform.eulerAngles = new Vector3(x, transform.eulerAngles.y, transform.eulerAngles.z);
        offsetPositon = transform.position - player.position;//算出开始相机与角色位置的偏移


    }

}

视野拉近的时候因为要根据鼠标滑轮滑动的长度计算,所以,要先计算出偏移的长度Vector3.magnitude然后用当前
偏移长度-=滑轮滑动的长度,再限制两个固定数内,然后把新的偏移长度赋给初始偏移,应该是原来偏移长度为0*当前
偏移长度就达到了视野缩进:例如偏移offsetPositon=相机position-角色position,把当前偏移保存下变量为distance
所以写一个方法代码如下:
//视野缩近
void ScrollView() {
//向后 返回负值-拉远视野,向前滑动为正值-拉近视野
distance -= Input.GetAxis(“Mouse ScrollWheel”)*scrollSpeed;//新的偏移长度
distance = Mathf.Clamp(distance,6.5f,18);//将一个数限制在两个数范围内,达到限制视野拉近距离
offsetPositon = offsetPositon.normalized * distance;
}//把新的偏移长度再赋给原来的偏移

视野拉近拉远

缩近视野的时候只需要保存当前的相机与人物距离,用当前距离减等于滑轮滑动就得到当前的距离(即新的视野距离)
然后让这个新的当前距离用Mathf.Clamp(distance,6.5f,18);限制在两个数范围内就可以控制缩近大小
然后让位置偏移等于这个新的相机与角色的距离值offsetPositon = offsetPositon.normalized * distance
开始的时候要让距离值=0,在Start方法里把开始值再赋予offsetposition.magnitude(返回vector3长度)

代码:
void ScrollView() {
//向后 返回负值-拉远视野,向前滑动为正值-拉近视野
distance -= Input.GetAxis(“Mouse ScrollWheel”)*scrollSpeed;
distance = Mathf.Clamp(distance,6.5f,18);//将一个数限制在两个数范围内
offsetPositon = offsetPositon.normalized * distance;

}
这里 offsetPositon.normalized保持方向不变(vector3归1) *distance,相当于等于当前的distance值
然后再Update方法里调用这个ScrollView方法即可实现视野拉近拉远


unity3d Update()和FixedUpdate()的区别Update会在每次渲染新的一帧时,被调用。而FixedUpdate
会在每个固定的时间间隔被调用,Update 和FixedUpdate的时间间隔一样,也不一定一样的帧率,
因为Update受当前渲染的物体,更确切的说是三角形的数量影响,有时快有时慢,帧率会变化,
update被调用的时间间隔就发生变化。但是FixedUpdate则不受帧率的变化,它是以固定的时间间隔来被调用,
那么这个时间间隔怎么设置呢?Edit->Project Setting->time下面的Fixed timestep。
FixedUpdate()主要应用在跟随的时候例如相机跟随角色,比在update中好
相机缓动跟随角色的时候,需要有角色的坐标,及定义的偏差,还需要缓动的一个速度代码:
Vector.Lerp(1.2.3.)缓动跟随,参数1是本物体,2是要跟随的物体,3是跟随速度
主相机跟随角色
*/
public class FollowTarget : MonoBehaviour {

public  Vector3 offset;//位置偏移
private Transform player;//得到主角
public float smoothin=1;//相机缓动效果-速度
// Use this for initialization
void Start () {
    player = GameObject.FindGameObjectWithTag("Player").transform;//通过Tag标签得到角色组件
}

// Update is called once per frame
void Update () {
    //transform.position = player.position + offset;//相机的位置等于角色+位置偏移
    Vector3 targetPos = player.position + offset;
    //相机由自身位置缓动到角色位置
    transform.position= Vector3.Lerp(transform.position,targetPos,smoothin*Time.deltaTime);
}

}


tweenAlpha.ResetToBeginning();//把动画设置到初始状态,可以反复调用
制作角色血量低屏幕血红效果的时候用NGUI里面的Wooden Atlas,把Script的图找到Glow-Inner,添加一个
Tween Alph的动画,值从1变换到0,默认不播放,默认Scripte的颜色ALPH是0然后角色受伤的时候代码控制:
/控制角色低血量屏幕血红效果/
public class BloodScreen : MonoBehaviour {

private  static BloodScreen _instance;//防止修改
private  UISprite sprite;//颜色的Alph设置
private  TweenAlpha tweenAlpha;//动画设置
public static BloodScreen Instance {

    get {
        return _instance;

    }

}

void Awake() {
    _instance = this;

    sprite=this.GetComponent<UISprite>();

tweenAlpha=this.GetComponent<TweenAlpha>();

}

//血屏显示

public void Show(){

sprite.alpha=1;

    tweenAlpha.ResetToBeginning();

    tweenAlpha.PlayForward();



}

然后在角色受到攻击的时候调用Show方法
角色受到攻击的动画状态机在行走与站立状态机里设置


动画播放的时候看看预制体身上是Animation 还是Animator,Animation的话直接可以调用Play播放方法
Animator的话需要先获得这个Animator组件,然后进行Set状态机里设置的属性例如SetTrigger,SetBool
SendMessageOptions.DontRequireReceiver作为参数,即使没有SendMessage中传入的方法也不报错


NGUITools.AddChild(1.2);实例化一个物体作为自物体放在某个物体下面1.为父物体,2.为要实例的物体


Capsule Collider可以使物体无法穿越自己身体

Character Controller//敌人行走的时候先添加一个角色控制器,
CharacterController.SimpleMove简单自由移动

Transform.rotation 旋转角度 ,Transform.Translate 平移

Vector3.forward = (0,0,1)
transform.forward = transform.TransformDirection(Vector3.forward)

Transform.TransformDirection 变换方向,从自身(局部)坐标到世界坐标变换方向。
攻击一个物体后退的效果:
Transform.InverseTransformDirection 变换反方向,从世界坐标到自身坐标(局部)

Transform.TransformPoint 变换位置,从自身(局部)坐标到世界坐标
把一个物体的世界坐标变为另一物体的局部坐标:
Transform.InverseTransformPoint 相反变换位置,从世界坐标到自身坐标(局部)

脚本在那个组件身上,transform就代表这个组件上的物体等同GameObject

World Space(世界坐标):我们在场景中添加物体(如:Cube),他们都是以世界坐标显示在场景中的。transform.position可以获得该位置坐标(世界坐标)。
转换坐标的时候,从世界到自身,就用转换坐标减自身的position,从自身到世界,就用转换坐标加自身的position
rigidbody.velocity 刚体速度
rigidbody.angularVelocity 刚体旋转rigidbody.angularVelocity =transform.up就是向上(Y轴)旋转


角色与怪物战斗的时候,先判断怪物是否在角色的攻击方向再判断是否在攻击范围内,所以先把怪物的世界坐标变为角色的局部坐标,然后把所有场景怪物遍历一遍,把符合的怪物重新放到一个数组里面,然

后把这些怪物数组返回,周围攻击不许要判断
攻击方向,也不需要转换坐标,因为周围攻击是全方位,普通攻击只在一个方位攻击
transform.InverseTransformPoint(go.transform.position);//把世界坐标转换为局部坐标


Vector3.Distance(两个参数),返回第一个减第二个参数的距离坐标
Vector3.Angle(两个参数);返回两个物体的夹角,第一个为两个物体的position差值,第二个为,谁攻击就是谁的方向
加入角色攻击就是计算角色向哪个方向攻击的夹角
计算在不在夹角攻击范围内,只需要小于计算出夹角的二分之一即可,计算的时候让被攻击者的Y轴等于攻击者Y轴代码:
Vector3 playerPos = player.position;
playerPos.y = transform.position.y;//保证夹角不收Y轴影响
//计算哪个方位时角色与BOSS的夹角
float angle= Vector3.Angle(playerPos-transform.position,transform.forward);
if (angle < viewAngle / 2)
在UPDATE里面调用


//协程.多少时间调用一次函数,比在UPdate计算怪物与角色距离减少性能//1方法,2.多少秒开始
参数2多少秒后开始,每个多少参数3的时间调用参数1的方法
InvokeRepeating(1.2.3.)

SimpleMove(1.2.)自由移动参数1哪个物体,参数2.移动方向

怪物移动的时候要随着角色的方向看向角色,到达攻击距离攻击角色的时候也要随着角色的方向攻击代码:
再在start里面调用InvokeRepeating()方法,时时跟踪角色
//Update里面要调这个方法所以不能用GameObject,所以要取下transform属性
Transform player =TranScriptManage._instance.player.transform;
Vector3 targetPos = player.position;//敌人要朝向角色的坐标位置
targetPos.y = transform.position.y;//Y轴不变还是敌人的Y轴坐标
transform.LookAt(targetPos);//看向这个坐标
cc.SimpleMove(transform.forward * speend);//给敌人一个自由移动事件

**************Itwen的使用*******************************************************
Itween插件的moveBy方法有三个参数,一个是哪个物体位移,第二个是,位移距离(局部变量),第三个是多少时间开始:
iTween.MoveBy(this.gameObject,Vector3.forward*moveForward,0.3f);

角色连击次数为了效果,可以先把连击次数出来的时候自身再设置为0再放大
transform.localScale = Vector3.one;//自身先为0,,,
//多久放大自身,第一个参数为物体,第二个为放大倍数,第三个为时间
iTween.ScaleTo(this.gameObject,new Vector3(1.5f,1.5f,1.5f),0.1f);
然后可以添加震动效果用iTween.ShakePosition()1.物体,2震动范围.3.时间
iTween.ShakePosition(this.gameObject, new Vector3(0.2f, 0.2f, 0.2f), 0.1f);//自身震动效果
连击的时候需要设置一个计时器,这个计时器时间段内再次打击是有效的连击,count就++,让UI显示等于Count
在update()方法里面让计时器随着Time.deltaTime减少,当小于0的时候,连击次数隐藏,次数归零,UI显示也归零
iTween.FadeTo(this.gameObject, 0, 3f);1.那个物体,2变换的自身坐标,3多久后变换,
************死亡爆破****************************************
敌人死亡爆破的方法使用MeshRender插件,给怪物模型的长方体添加这个MeshRender代码,然后在怪物脚本里死亡
动画方法里添加第二种死亡方式代码:
//怪物死亡事件
void Dead()
{
Destroy(hpBarGameObject);//销毁血条
Destroy(hudTextGameObject);//销毁存放受伤的物体
//第一种死亡是播放死亡动画
int random = Random.Range(0,10);//随机数
if (random <= 7)//随机数不大于7的时候使用死亡动画死亡
{
animation.Play(“die”);
}
else {
//随机数大于7使用破碎的方法死亡
this.GetComponentInChildren().Explode();//使用这个组件脚本的破碎方法
this.GetComponentInChildren().enabled = false;//把渲染再禁止掉

    }

}

}
敌人死亡后把其身上的碰撞器销毁,这样敌人刚死亡时,角色可以穿过
this.GetComponent().enabled = false;//禁用碰撞器
**************进度条/血条***************************************

进度条的设置 需要添加一个背景 一个前置背景,在背景里面添加NGUI slider脚本
BackGround-本身,Foreground-前置条,然后把前置条的 UIScripte下的 Type改为Tiled(默认Simple)
setCurrentPercent百分比的文字显示 setCurrentProgress小数的文字显示
进度条上面的文字用Label显示后添加到背景条里面的On Value Change下 MEthord选择UIlable/setCurrentPercent

怪物的血条制作的时候用一个空物体存放血条,一个背景条,一个红色条用NGUI slider,
然后用NGUI的跟随脚本UIFollowTarget.cs进行跟随血条的怪物,导入脚本后放在NGUI文件下,
给放置血条的空物体加入UIFollowTarget.cs跟随脚本,Target属性就是要跟随的怪物,UIFollowTarget.cs,脚本上的
Disable if Invisible 勾取消,(这个指的是血条不在视野内时,血条不可见)然后制作成一个prefab,在UIroot下创建
一个不可见的Invisibel widget在Invisibel widget上创建一个脚本来管理这些血条,脚本代码:

public class HpBarManage : MonoBehaviour {
public static HpBarManage _instance;
private GameObject hpBarPrefab;//用来放血条的物体(血条)

void Awake() {
    _instance = this;


}


//创建血条,参数为要跟随的目标
public GameObject GetHpBar(GameObject  target) {
  GameObject go= NGUITools.AddChild(this.gameObject,hpBarPrefab);
  go.GetComponent<UIFollowTarget>().target = target.transform;

  return go;

}

}
然后在敌人脚本stat()方法里调用这个方法,因为血条位置不对所以要在敌人头上放一个空物体,用来存放自己的血条
所以参数为敌人自己下的存放血条的空物体代码:
Transform hpBar = transform.Find(“HpBarPoint”);//怪物放血条的位置
HpBarManage._instance.GetHpBar(hpBar.gameObject);

HUDText在NGUI里用来管理敌人受到伤害显示数字的脚本,在用UIFollowTarget.cs管理敌人受到伤害数字的跟随角色,
代码跟血条代码类似,也需要创建一个空物体存放HUDText与UIFollowTarget脚本做成一个Prefab,代码
//创建敌人受到伤害数字,参数为要跟随的目标
public GameObject GetHudText(GameObject target) {
GameObject go = NGUITools.AddChild(this.gameObject, hudTextPrefab);
go.GetComponent().target = target.transform;
return go;

}

然后在敌人受到伤害的方法里调用 huText.Add(1.2.3.)方法参数1,加或减受到减少血(角色攻击值)2.颜色3.受伤数字呆多久
***********敌人AI***************************************************************
敌人攻击的时候,需要有1.敌人与角色的距离2.敌人的攻击范围,3.敌人攻击的速率(多少秒攻击一次),4.所以还需要一个计时器,
到达这个时间后敌人攻击攻击一次后要把这个计时器归零,进行下次计时攻击,
等待的时候要把敌人的攻击动画变为等待动画(animation(“站立状态”)),
否则else{敌人运行走的状态(朝着角色方向运动)}不管是攻击还是站立还是行走都要面向角色
攻击的效果可以在攻击动画上添加事件,如果无法添加,先把敌人动画复制出,然后来把面板上敌人的动画替换,然后编辑
Animation面板选择敌人攻击动画进行设置攻击方法


Quaternion.identity同一性,就是没有任何旋转
初始化一个物体参数为即克隆1原始物体,2位置设置在position,3设置旋转在rotation,返回的是克隆后的物体
GameObject.Instantiate(damageEffectprefab, transform.position, Quaternion.identity);
如做敌人出血效果的时候用这个实例化出血特效,在怪物的位置,旋转随着敌人统一(Quaternion.identity)

敌人被攻击的声音,可以在出血特效上添加一个Audio放入受攻击音效


UIButtonScale脚本 按钮点击的时候先放大,再缩回
做技能冷却的时候需要把技能声音设置为OnPress;(这样播放出声音,技能也出声音)
技能冷却直接在该技能上添加一个遮罩,把遮罩的UISprite下的 Type改为Filled,可以时钟旋转

做技能冷却的时候,首先需要一个技能冷却时间属性,一个倒计时属性,一个标志位false,一个按键keycode,技能本身
按钮属性,还有一个接收指定触发技能按键的,按键,在
开始方法获得按钮,并监听技能点击后的方法体,方法里面要把标志位设置为true就是可以冷却计时了,把keycode键
同时设置为0,这样技能释放后再点击对应的技能数字键就不会再冷却的时候再次触发,再update方法里判断,
当标志位为true即可以冷却计时时,计时器timer += Time.deltaTime;,然后滑动的值filledImage.fillAmount =
(coldTime - timer) / coldTime;,就会有倒计时冷却效果,然后判断当时间计时器timer>=技能冷却需要时间后把
filledImage.fillAmount=0;计时器归0,标志位再设置为false不计时,把按键btn.enabled=true;this.keycode=kode
这个kode是开始方法里接受指定触发技能的按键 kode = keycode;
***********技能攻击**************************************************
MovieTexture//用来播放视频前提电脑要安装QuikeTime,,在创建一个声音组件,把视频下的声音加进去然后写一个
OnGUI方法然后在Start方法里调用movTexture.play方法,
void OnGUI()
{
GUI.DrawTexture(new Rect(0,0,Screen.width,Screen.height),movTexture);
}

多个技能先建一个new Layer,可以把Weight设置最大,覆盖角色走动的动画机,再设置一个空的Creat stat-Empty
这样当有技能触发的时候可以覆盖角色移动状态机, 当为空的Empty的时候会播放角色走动状态机

技能是连击的时候,可以先把技能拆分成单个攻击动画,然后用状态机调整动画播放,用Trigger触发器,不用bool
因为连击包含多个动作需要每次点击,触发单个动作,用bool点击一次按键会全部触发

技能增加特效的时候,可以在技能动画上,添加Events,在需要增加特效的动画处添加需要的参数,参数里
要有特效的物体名字,然后用代码控制这些参数达到攻击特效的效果,代码如下:
Events里面存的是 特效的名字以及声音,以及需要的参数
public class PlayerAttack : MonoBehaviour {
//特效字典,存放角色特效
private Dictionary

}

public TextAsset objectsInfoListtext;//读取文本
private Dictionary

首先创建一个项目,然后在引用的地方,添加引用类库:

日志类库
ExitGames.Logging.Log4Net.dll

log4net.dll

必用类库
ExitGamesLibs.dll
Photon.SocketServer.dll

PhotonHostRuntimeInterfaces.dll

数据库与U3D类库
Photon3Unity3D.dll

MySQL.Data.dll—需要在管理NUGet程序包-官方包源找

—需要在管理NUGet程序包-官方包源找,管理数据库连接必须
NHibernate.dll
FluentNHibernate.dll


类库添加完然后
ApplacationBase:第一个方法用来创建PeerBase与客户端交互,
第二个方法是初始化调用方法,-一般日志方法放在这里
第三个是关闭连接调用方法
项目里继承ApplacationBase,引用下类库及实现接口,第一个PeerBase接口需要另外创建一个类继承,需要跟客户端交互
然后实现PeerBase构造方法,需要手动创建:
public ClientPeer(IRpcProtocol protocol, IPhotonPeer Peer):base(protocol,Peer){}
然后在实现ApplacationBase方法里的第一个PeerBase里:创建一个PeerPeerBase
//第一个参数为协议名称,
return new ClientPeer(initRequest.Protocol, initRequest.PhotonPeer);

在第二个方法void Setup()里把日志方法配上
//日志
ExitGames.Logging.LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);
GlobalContext.Properties[“Photon:ApplicationLogPath”] = Path.Combine(this.ApplicationRootPath,”log”);
GlobalContext.Properties[“LogFileName”] = “TD” + this.ApplicationName;
XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(this.BinaryPath, “log4net.config”)));
配完以后记得把log4net.config文件引入到自己工程根目录,把配置文件里的Photon.LoadBalancing配置项删除

然后在自己服务端类下面在Application类里写入:
private static readonly ILogger log = ExitGames.Logging.LogManager.GetCurrentClassLogger();
然后就可以在第二个方法void Setup()里用log.Debug();进行自定义日志输出
做完上述日志输出后,在服务器的目录里PhotonServer\deploy下创建工程文件夹然后里面创建一个Bin文件夹,然后
右键项目属性-输出路径改为刚才的文件夹,log4net.config再右键改为始终复制到目录,然后项目再生成一下
然后在服务器的photonServer.config里复制Default配置项把标签Default改为项目名如:TaidouServer
然后把下的保留一个还有一个Name=”CounterPublisher”的修改为自己的项
Name=”CounterPublisher”这个项是记录系统状态的如:内存占用率,连接数等

保留的一个:
Name:自定义最好和项目名字一个
BaseDirectory:项目路径
TaidouServer:继承Application的 dll文件
Type:完整路径 namespace: ;+class:
然后把主标签 =里的Name
然后把删除掉就可以启动服务器查看了在服务器目录deploy\log下如果多了攻城文件LOG配置就成功
然后把项目里的log4net.config配置文件里的标签下的修改


然后在解决方案里创建一个新的项目,用来解决请求,把class1类改为枚举类型public enum OperationCode:byte
存为byte在里面可以写一些请求
然后在项目工程里右键引用解决方案里的新项目(刚创建的请求类项目),然后在项目下创建一个Handlers包,包下面
创建一个HandlerBase类用来处理请求,一个HandlerBase对应一个OperationCode,然后在项目集成的ApplicationBase
里面管理这些handlers,在这个项目里创建一个字典管理这些OperationCode,与HandlerBase
public Dictionary

5.用Close()来关闭响应的套接字实例

面向五连接的套接字编程,服务端少了监听,和Accept()接受连接,和建立连接后返回新的套接字


Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolType)
创建Socket实例,参数1指定网络类型,参数2,指定套接字类型(数据连接方式)参数3.指定协议
当参数2为Stream(流操作)3参数3为TCP的时候为面向连接套接字

参数2为Dgram,参数3为UDP的时候为无连接套接字

Socket常用方法
1.Bind(),在服务端一个套接字被创建后,需要将它绑定到系统的特定地址,用Bind()方法完成,参数为IPEndPoint实例
()此实例包含IP地址和端口信息
2.Listen(),服务器端套接字完成了与地址的绑定后,就使用此方法监听客户端发送的连接请求参数为con_num是一个
整型值,表示服务器接受的最大连接数,超过数目后会被拒绝,此值会影响服务器的运行,因为每个接受的连接都要使用TCP
缓冲区,如果连接数目过大,收发数据的缓存将减少
3.Accept(),在服务器进入监听状态时.如有客户端发来的连接请求,服务器将使用此方法来接受连接请求Accept()返回
一个新的套接字,该套接字包含所建立的连接的信息并负责处理本连接的所有通信,而服务器刚开始创建的套接字仍然负责监听,并在需要时调用Accept()接受新的连接请求
4.Send(),当服务器接受了来自客户端的连接请求后,双方就可以利用Send()方法来发送数据,Send(byte[]data)

5.Receive(),当服务器接受了来自客户端的连接请求后,双方可以利用此方法接受数据,

6..Connect(),同服务器一样,客户端的套接字建立后,也必须与一个地址绑定,在客户端使用此方法实现绑定,remoteEP
参数为索要连接的服务端的IPEndPoint实例,调用Connect()方法后,将一直阻塞到连接建立,如果连接失败则返回异常
7.Shutdown(),当客户端和服务端的通信结束时,必须关闭响应的套接字实例,可以使用此方法来禁止该套接字上的发送
和接受,Shutdown()方法有一个枚举类型的参数,如SocketShutdown.Send表示禁用发送套接字,SocketShutdown.Receive表示禁用接受套接字,SocketShutdown.BOth表示禁用发送和接收的套接字
8.Close(),禁止套接字上的发送和接收之后,使用此方法关闭套接字连接并释放所有相关资源,这样套接字会在系统内部

缓冲区处理完毕后关闭套接字并释放资源

同步 Send方法对应异步 BeginSend 和 EndSend 方法。
同步 Receive 方法对应异步 BeginReceive 和 EndReceive 方法。


使用同步的时候—利用NetworkStream—
首先创建一个TcpListener对象监听网络上面的请求,有连接请求时,调用这个对象的AcceptSocket方法,返回一个请求
的Socket或TcpClient实例,
myListener = new TcpListener(localAddress, port);//创建TcpListener对象开始监听

myListener.Start();//开启监听网络请求

注:这里监听后我们可以创建一个线程监听客户端连接
ThreadStart ts = new ThreadStart(ListenClientConnect);
Thread myThread = new Thread(ts);
myThread.Start();

然后调用ListenClientConnect(无参方法,因为myThread.Start()无参)接受客户端连接

public void ListenClientConnect(){
等待连接:当有客户端连接的时候,
TcpClient newClient = null;
newClient = myListener.AcceptTcpClient();//返回一个请求的Socket/TcpClient的实例

}

这里我们还可以当有连接的时候调用一个有参的线程,得到连接的对象
ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData);
Thread threadReceive = new Thread(pts);
User user = new User(newClient);
—————————注解头————————-
这里新建对象后,就可以通过user.client.GetStream()得到一个NetworkStream,然后调用StreamReader
和StreamWriter对NetworkStream对象读写,参考User类
—————————注解尾———————–
threadReceive.Start(user);

userList.Add(user);//把这个对象放入到连接管理里面

private void ReceiveData(object obj){
User user = (User)obj;//从上面传进的参数拿到对象
}
拿到对象后就可以调用对象里面的user.sr.ReadLine();

———————–//上述user创建的类
public class User
{
public TcpClient client;
public StreamReader sr;
public StreamWriter sw;
public string userName;
public User(TcpClient client)
{
this.client = client;
this.userName = “”;
NetworkStream netStream = client.GetStream();
//从当前数据流中读取一行字符,返回值是字符串
sr = new StreamReader(netStream, System.Text.Encoding.UTF8);.
//从当前数据流中写入一行字符
sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
}
}
注:NetworkStream只能传送和接收字符类型的数据
“NetworkStream”对象为网络访问提供了基础数据流。我们通过名称空间”System.IO”中封装的二个类”StreamReader”和”StreamWriter”来实现对”NetworkStream”对象的访问。其中”StreamReader”类中的

ReadLine ( 方法就是从”NetworkStream”对象中读取一行字符;”StreamWriter”类中的WriteLine ( 方法就是对”NetworkStream”对象中写入一行字符串。从而实现在网络上面传输字符串


———————关闭线程和流——————
networkStream.Close()
streamReader.Close()
streamWriter.Close()
_thread1.Abort()
tcpListener.Stop()
sock.Shutdown(SocketShutdown.Both);
socketForClient.Close()


利用Socket(套接字)几乎可以处理任何在网络中需要传输的数据类型
属性 说明
AddressFamily 获取Socket的地址族。
Available 获取已经从网络接收且可供读取的数据量。
Blocking 获取或设置一个值,该值指示Socket是否处于阻塞模式。
Connected 获取一个值,该值指示Socket是否已连接到远程资源。
Handle 获取Socket的操作系统句柄。
LocalEndPoint 获取本地终结点。
ProtocolType 获取Socket的协议类型。
RemoteEndPoint 获取远程终结点。

SocketType 获取Socket的类型。

Accept 创建新的Socket以处理传入的连接请求。
BeginAccept 开始一个异步请求,以创建新的Socket来接受传入的连接请求。
BeginConnect 开始对网络设备连接的异步请求。
BeginReceive 开始从连接的Socket中异步接收数据。
BeginReceiveFrom 开始从指定网络设备中异步接收数据。
BeginSend 将数据异步发送到连接的
BeginSendTo 向特定远程主机异步发送数据。
Bind 使Socket与一个本地终结点相关联。
Close 强制Socket连接关闭。
Connect 建立到远程设备的连接。
EndAccept 结束异步请求以创建新的Socket来接受传入的连接请求
EndConnect 结束挂起的异步连接请求。
EndReceive 结束挂起的异步读取。
EndReceiveFrom 结束挂起的、从特定终结点进行异步读取。
EndSend 结束挂起的异步发送
EndSendTo 结束挂起的、向指定位置进行的异步发送。
GetSocketOption 返回Socket选项的值。
IOControl 为Socket设置低级别操作模式
Listen 将Socket置于侦听状态。
Poll
Receive 接收来自连接Socket的数据。
ReceiveFrom 接收数据文报并存储源终结点。
Select 确定一个或多个套接字的状态。
Send 将数据发送到连接的
SendTo 将数据发送到特定终结点。
SetSocketOption 设置Socket选项。

Shutdown 禁用某Socket上的发送和接收。

其中“BeginAccept”和“EndAccept”、“BeginConnect”和“EndConnect”

“BeginReceive”和“EndReceive”、“BeginReceiveFrom”和“EndReceiveFrom”

“BeginSend”和“EndSend”、“BeginSendTo”和“EndSendTo”是六组异步方法

其功能分别相当于“Accept”、“Connect”、“Receive”、“ReceiveFrom”


——————–异步操作:—————-
AsyncCallback委托用于引用异步操作完成时调用的回调方法。在异步操作方式下,由于程序可以在启动异步操作后继续执行其他代码,因此必须有一种机制,以保证该异步操作完成时能及时通知调用者

。这种机制可以通过AsyncCallback委托实现。
异步操作每一个方法都有一个Begin….方法和一个End..方法
如:BeginAcceptTcpClient和EndAcceptTcpClient

程序调用Begin…方法时,系统会自动在线程池中创建对应的线程进行异步操作,
从而保证调用方和被调用方同时执行,当线程池中的Begin…方法执行完毕时,
会自动通过AsyncCallback委托调用在Begin…方法的参数中指定的回调方法。
回调方法是在程序中事先定义的,在回调方法中,通过End…方法获取Begin…方法的
返回值和所有输入/输出参数,从而达到异步操作方式下完成参数传递的目的。

BeginAcceptTcpClient和EndAcceptTcpClient方法包含在System.Net.Sockets命名空间下的TcpListener类中
服务器端可以使用TcpListener类提供的BeginAcceptTcpClient方法开始接收新的客户端连接请求。
在这个方法中,系统自动利用线程池创建需要的线程,并在操作完成时利用异步回调机制调用提供给它的方法,
同时返回相应的状态参数
——————服务器中的连接—————-
AsyncCallback callback = new AsyncCallback(AcceptTcpClientCallback);
tcpListener.BeginAcceptTcpClient(callback, tcpListener);
void AcceptTcpClientCallback( IAsyncResult ar)
{
……
TcpListener myListener = (TcpListener)ar.AsyncState;
TcpClient client = myListener.EndAcceptTcpClient(ar);
……
}
这里调用了ar.AsyncState;是一个最简单的 object引用,
把调用异步方法的你的代码所传送的对象参数给“读取”出来

程序执行EndAcceptTcpClient方法后,会自动完成客户端连接请求,
并返回包含底层套接字的TcpClient对象,接下来我们就可以利用这个对象与客户端进行通信了。

程序执行BeginAcceptTcpClient方法后,在该方法返回状态信息之前,
不会像同步TCP方式那样阻塞等待客户端连接,而是继续往下执行。
如果我们希望在其返回状态信息之前阻塞当前线程的执行,可以调用ManualResetEvent对象的WaitOne方法。

—————-客户端中的连接:——————-
AsyncCallback requestCallback = new AsyncCallback(RequestCallback);
tcpClient.BeginConnect(远程主机IP或域名,远程主机端口号, requestCallback, tcpClient);
……
void RequestCallback(IAsyncResult ar)
{
……
tcpClient = (TcpClient)ar.AsyncState;
client.EndConnect(ar);
……
}
在自定义的RequestCallback中,
通过获取的状态信息得到新的TcpClient类型的对象,并调用EndConnect结束连接请求

————–连接成功后接收与发送:——————
使用BeginWrite方法异步发送数据,程序必须创建实现AsyncCallback委托的回调方法,
并将其名称传递给BeginWrite方法。在BeginWrite方法中,传递的state参数必须至少包含NetworkStream对象。
如回调需要更多信息,则可以创建一个小型类或结构,用于保存NetworkStream和其他所需的信息,
并通过state参数将结构或类的实例传递给BeginWrite方法。

在回调方法中,必须调用EndWrite方法。程序调用BeginWrite后,
系统自动使用单独的线程来执行指定的回调方法,并在EndWrite上一直处于阻塞状态,
直到NetworkStream对象发送请求的字节数或引发异常

—————-发送方法———-
NetworkStream stream = tcpClient.GetStream();
byte[] bytesData = System.Text.Encoding.UTF8.GetBytes(str + “\r\n”);
stream.BeginWrite(bytesData, 0, bytesData.Length, new AsyncCallback(SendCallback), stream); stream.Flush();
private void SendCallback(IAsyncResult ar)
{
stream.EndWrite(ar);
}
—————-接收方法———-
NetworkStream stream = tcpClient.GetStream();
ReadObject readObject = new ReadObject(stream, client.ReceiveBufferSize);
stream.BeginRead(readObject.bytes, 0, readObject.bytes.Length, ReadCallback, readObject);
void ReadCallback(IAsyncResult ar) {
ReadObject readObject = (ReadObject)ar.AsyncState;
int count = readObject.netStream.EndRead(ar);
//处理读取的保存在ReadObject类中的字节数组
}
因为接收需要知道接收的数据,所以,建立一个接收数据的空间client.ReceiveBufferSize,根据实际数据
设置网络缓冲区空间大小,然后就开始读,接收读取成功后需要知道读取成功的长度

读的时候发送的委托参数为读的数据,数据里包含NetworkStream ,写的时候不需要返回长度,直接发送委托参数
NetworkStream ,


“Send”和“SendTo”方法。

System.Text.Encoding.Default.GetString()//转换为字符串类型
System.Text.Encoding.Default.GetByte()//转换为Byte字节数组
—————c#拷贝数组——–
int[] pins = { 9, 3, 7, 2 };
方法1:
int[] copy=new int[pins.length];
for (int i = 0; i < copy.length; i++)
{
copy[i] = pins[i];
}
方法2:
int[] copy = new int[pins.Length];
pins.CopyTo(copy, 0);
方法3:
Array.Copy(pins,copy,copy.Length)//1.原数组2.新数组3.拷贝长度
方法4:
Int[] copy=(int[])pins.Clone();


C#中多线程的并发,
1.启动线程 调用Thread类的Start()方法
2.休眠线程,调用Sleep()方法,此方法两个参数如Thread.Sleep(1000),整数型只休眠毫秒数
TimeSpan WaitTime=new TimeSpan(0,0,0,0,1000);
Thread.Sleep(WaitTime)//线程休眠按天小时分钟秒毫秒计算
3.挂起线程Supend(),线程挂起是暂停线程,如果不启动,则永远保持挂起状态,只有当前运行的才可以被挂起,判断是否运行通常查询Thread的ThreadState属性值
if(t1.ThreadState==ThreadState.Running)//判断线程是否还在运行,
t1.Suspend()//挂起
4.继续线程,已经挂起的线程可以使用Thread类的Resume方法继续运行,没有挂起的线程不起作用,所以使用此方法前也
要判断是否被挂起,
if(t1.ThreadState==ThreadState.Suspend)//判断是否挂起
t1.Resume();//继续线程
5.终止线程,先判断线程是否处于活动,活动的话才可以使用Thread类的Abort方法进行终止
if(t1.IsAlive)//判断线程是否处于活动状态
t1.Abort();
6.实现线程同步的方式就是加锁Lock(),还有一个Monitor方法实现同步
Monitor方法是个封装类,类中有
Enter(),方法,获取锁定对象的钥匙,
TryEnter().与上述方法一样,只是,没获取到对象钥匙时,不会阻塞,而是返回false表示失败
Exit()结束对一个对象的检视.即交出锁定对象的钥匙,
Pulse()该方法通知位于等待列中的下一个线程进入就绪队列,调用该方法,就绪队列中的就可以获取钥匙
PulseAll(),通知所有线程进入就绪队列
Wait()释放对象的锁,并阻止当前线程直到重新获取该锁

线程创建:
1.ThreadStart这个委托定义为void ThreadStart(),也就是说,所执行的方法不能有参数。
2.ParameterThreadStart的定义为void ParameterizedThreadStart(object state),使用这个这个委托定义的线程的启动函数可以接受一个输入参数
例:
static void Main(string[] args)
{
Thread t1 = new Thread(new ThreadStart(TestMethod));
Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
t1.IsBackground = true;
t2.IsBackground = true;
t1.Start();//调用无参方法,start启用没参数
t2.Start(“hello”);//调用有参方法,start启用有参数
Console.ReadKey();
}

    public static void TestMethod()
    {
        Console.WriteLine("不带参数的线程函数");
    }

    public static void TestMethod(object data)
    {
        string datastr = data as string;
        Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);

}

线程传多参数:
想要传入多个参数的话 可以把有一个参数的方法重载后,弄成一个数组传入,如:
private method(int begin,int end){}//原方法
//重载的方法
void method(object o)

{

//此处对传进来的参数进行处理

int[] p = (int[])o;

//调用原来的method方法

method(p[0],p[1]);

}
int para[]=new int[2]{100,10000};
Thread t = new Thread(new ParameterizedThreadStart(method))//这里的方法是重载的方法
t.start(para);//然后把定义的数组就赋予了重载的方法,这样,重载方法调用原方法,就相当于线程调用了有两个

参数的方法

线程代用有参数的时候,只需在线程start方法里,传入所需参数a,就达到了给调用有参数的方法里的参数赋予a参数


多线程中通过EventWaitHandle类用于异步操作的同步,即控制一个或多个线程继续执行或者等待其他线程完成
三个方法:
1.Reset方法:
将信号的状态设置为非终止状态,即不让操作系统发出信号,从而导致等待收到信号才能继续执行的线程阻塞。
2.Set方法:将事件状态设置为终止状态,这样等待的线程将会收到信号,从而继续执行而不再等待
3.WaitOne方法:阻塞当前线程,等待操作系统为其发出信号,直到收到信号才解除阻塞
操作系统发出信号的方式有两种:
1) 发一个信号,使某个等待信号的线程解除阻塞,继续执行。

2) 发一个信号,使所有等待信号的线程全部解除阻塞,继续执行。

类中实例化这个EventWaitHandle后就可以在线程中使用它的上述三个方法
EventWaitHandle myEventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
当实例中第2个参数为EventResetMode.ManualReset的时候,让所有进程全部结束阻塞
当参数为EventResetMode.AutoResetEven的时候,按线程队列顺序进行


在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更

新界面显示
正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。两个方法参数1.一个委托事

件.2,想给组件添加的内容
所以要定义一个委托,委托可以触发一个方法,所以

当VS的窗口项目创建时,有别的线程访问创建的窗体内组件的时候,直接访问是不安全的,必须用组件.Invoke();方法
这样就保证了组件线程的安全性,所以首先要判断组件.InvokeRequired是否为true,为true证明有,组件意外的线程
要访问组件具体代码:
例1:
delegate void ListBoxCallback(string str);//首先定义一个委托
public void SetListBox(string str)
{
if (listbox.InvokeRequired == true)//true的时候组件外的线程访问
{
ListBoxCallback d = new ListBoxCallback(SetListBox);
listbox.Invoke(d, str);//封送到自身线程,后就为false了
}
else//组件本身线程访问
{
listbox.Items.Add(str);
listbox.SelectedIndex = listbox.Items.Count - 1;
listbox.ClearSelected();
}

}

例2:
public delegate void MyInvoke(string str);
private void btnStartThread_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWord));
thread.Start();
}
public void DoWord()
{
MyInvoke mi = new MyInvoke(SetTxt);
BeginInvoke(mi,new object[]{“abc”});
}

    public void SetTxt(string str)
    {
        txtReceive.Text += str + System.Environment.NewLine;
    }

例2中点击开始按钮的时候想更新文本内容的显示,但是不能直接调用,所以把更新显示文本内容独立写一个方法
然后,通过线程调用一个方法,用组件的BeginInvoke把这个方法封送到适当的线程(组件本身线程)
总结:也就是说只要想外部想访问组件的线程不安全,必须调用组件的Invoke/BeginInvoke就能把调用封装回组件
的自身线程中


string name = Dns.GetHostName();
listBox1.Items.Add(“您的主机名:”+name);
IPHostEntry ip1 = Dns.GetHostEntry(name);
listBox1.Items.Add(“您的所有IP地址:”);
foreach(IPAddress ip2 in ip1.AddressList)
{
listBox1.Items.Add(“【”+ip2+ “】”);
}
RemoteEndPoint获取远程的EndPoint对象,可强转为IPEndPoint(包括端口与IP)
通过IPHostEntry host = Dns.GetHostByAddress( IPEndPoint)//得到IP与主机名
string sHostName = host.HostName ;//再通HostName得到主机名


猜你喜欢

转载自blog.csdn.net/JeanShaw/article/details/52216799
今日推荐