2019-03-01 14:53:42 qq_27461747 阅读数 251
  • C#急速入门

    Unity开发,C#绝对是基础中的基础,课程针对纯小白而设计,各种语言细节在课程里均有涉猎,从原理到实战,从底层到算法,你想了解的C#,这里应有尽有,除了讲解,还有练习,你说棒不棒,哈哈,当然如果你是有其他语言基础的同学,课程依然会让你收货满满。来吧,我们进入正题。

    17632 人正在学习 去看看 张建飞

直接上代码:

using System;
using System.Collections.Generic;
using System.Collections;

namespace Server {
    /// <summary>
    /// 协程类
    /// </summary>
    public sealed class Coroutine {
        private Coroutine() { }

        /// <summary>
        /// 协程实例
        /// </summary>
        private static Coroutine instance;

        static Coroutine() {
            instance = new Coroutine();
        }

        /// <summary>
        /// 所有的迭代器
        /// </summary>
        private List<IEnumeratorContainer> itors = new List<IEnumeratorContainer>();

        /// <summary>
        /// 启动协程
        /// </summary>
        /// <param name="iCoroutine"></param>
        public static void StartCoroutine(IEnumerator iCoroutine) {
            instance.itors.Add(new IEnumeratorContainer(iCoroutine));
        }

        /// <summary>
        /// 更新___主线程逻辑循环调用
        /// </summary>
        public static void Update() {
            
            //循环处理
            int len = instance.itors.Count;
            for (int i=len-1;i>=0;i--) {
                if (instance.itors[i].Frame()) {
                    instance.itors.RemoveAt(i);
                }
            }
        }

        /// <summary>
        /// 迭代器容器
        /// </summary>
        private class IEnumeratorContainer {
            //按帧延时, 按时间延时

            /// <summary>
            /// 迭代器_自身
            /// </summary>
            private IEnumerator itor;

            /// <summary>
            /// 是否等待
            /// </summary>
            private bool isWait {
                get {
                    return waitMillisecond > 0 || waitFrame >= 0;
                }
            }

            /// <summary>
            /// 子迭代器
            /// </summary>
            private IEnumeratorContainer childContainer;

            /// <summary>
            /// 等待的秒数
            /// </summary>
            private int waitMillisecond = -1;

            /// <summary>
            /// 等待的帧
            /// </summary>
            private int waitFrame = -1;

            /// <summary>
            /// 开始等待的时间
            /// </summary>
            private DateTime waitStartTime;

            /// <summary>
            /// 创建迭代
            /// </summary>
            /// <param name="itor"></param>
            public IEnumeratorContainer(IEnumerator itor) {
                this.itor = itor;
            }

            /// <summary>
            /// 一帧
            /// </summary>
            /// <returns>表示否最后一帧</returns>
            public bool Frame() {
                if (isWait) {   //处理等待
                    if (waitFrame >= 0) {
                        waitFrame--;
                    }
                    if (waitMillisecond > 0) {
                        TimeSpan ts = DateTime.Now - waitStartTime;
                        if (ts.TotalMilliseconds > waitMillisecond) {
                            waitMillisecond = -1;
                        }
                    }
                }
                if (isWait) //还需要等待
                    return false;


                bool isHave = true;
                if (childContainer != null) {//子迭代不为空,出来子迭代
                    if (childContainer.Frame()) {//子迭代处理完了
                        childContainer = null;
                        return Frame(); //处理自己
                    }
                } else {
                    isHave = itor.MoveNext();
                    if (isHave) { //做处理
                        if (itor.Current is IEnumerator) {//当前是个迭代
                            IEnumerator chitor = itor.Current as IEnumerator;
                            childContainer = new IEnumeratorContainer(chitor);
                        } else if (itor.Current is WaitCoroutine) { //当前是等待处理
                            WaitCoroutine wc = itor.Current as WaitCoroutine;
                            AddAttributes(wc);
                        }
                    }
                }
                return !isHave;
            }

            /// <summary>
            /// 添加等待属性
            /// </summary>
            /// <param name="wc"></param>
            private void AddAttributes(WaitCoroutine wc) {
                switch (wc.style) {
                    case 0:      //秒钟
                        waitMillisecond = wc.value * 1000;
                        waitStartTime = DateTime.Now;
                        break;
                    case 1:     //毫秒
                        waitMillisecond = wc.value;
                        waitStartTime = DateTime.Now;
                        break;
                    case 2:
                        waitFrame = wc.value;
                        break;
                }
            }

            /// <summary>
            /// 添加儿子
            /// </summary>
            private void AddChild(IEnumerator _itor) {
                childContainer = new IEnumeratorContainer(_itor);
            }

        }
    }

    /// <summary>
    /// 等待基类
    /// </summary>
    public abstract class WaitCoroutine {
        /// <summary>
        /// 类型 0等待 秒种  1等待毫秒值   2等待帧数
        /// </summary>
        public int style;

        /// <summary>
        /// 数值
        /// </summary>
        public int value;
    }

    /// <summary>
    /// 等待秒
    /// </summary>
    public class WaitSecond : WaitCoroutine {
        /// <summary>
        /// 秒钟
        /// </summary>
        /// <param name="second"></param>
        public WaitSecond(int second) {
            style = 0;
            value = second;
        }
    }

    /// <summary>
    /// 等待毫秒值 
    /// </summary>
    public class WaitMillisecond : WaitCoroutine {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="millisecond"></param>
        public WaitMillisecond(int millisecond) {
            style = 1;
            value = millisecond;
        }
    }

    /// <summary>
    /// 等待帧数
    /// </summary>
    public class WaitFrame : WaitCoroutine {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="frame"></param>
        public WaitFrame(int frame) {
            style = 2;
            value = frame;
        }
    }


}

 

2019-09-06 18:35:32 Terie 阅读数 86
  • C#急速入门

    Unity开发,C#绝对是基础中的基础,课程针对纯小白而设计,各种语言细节在课程里均有涉猎,从原理到实战,从底层到算法,你想了解的C#,这里应有尽有,除了讲解,还有练习,你说棒不棒,哈哈,当然如果你是有其他语言基础的同学,课程依然会让你收货满满。来吧,我们进入正题。

    17632 人正在学习 去看看 张建飞

C#迭代器 与 Unity协程

C# 迭代器

迭代器是一个接口,它允许开发者访问一个拥有相同的数据结构的集合,并且依次返回跟对应数据结构相关的一个数据,这个数据可以是数据结构本身,也可以是其他值。C#中实现迭代器有两个重要的关键字,一个是IEnumerator,一个是yield。

Enumerator

IEnumerator 是C#迭代器的封装类。当你需要实现一个类的迭代器的时候(这个类里面首先必须得有一个具有相同数据结构的连续的数据集合,简而言之,就是一个序列,这个序列的每个元素的数据结构可以很简单,也可以很复杂,比如,一个名为People的数组),需要重写这个类里面的这个方法:

// 当你想遍历一个拥有复杂数据结构的集合时,以下方法提供了遍历规则,它决定你每次遍历返回了什么
public System.Collections.IEnumerator GetEnumerator()
{
	// some code like this ...
	for(int i=0;i<People.Length;i++)
	{
		yield return People[i].name;
	}
}

然后,你就可以使用foreach遍历这个类(假设类名为City):

foreach(string peopleName in City)
{
	Console.WriteLine(peopleName);
}

完整测试代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestIterator
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassRoom cr = new ClassRoom();
            foreach(string crTemp in cr)
            {
                Console.WriteLine(crTemp);
                Console.WriteLine("MoveNext");
            }
            Console.ReadKey();
        }
    }

    public class People
    {
        public string name;
        public int age;

        public People(int i)
        {
            name = "People" + i.ToString();
            age = i + 20;
        }
    }

    public class ClassRoom
    {
        People[] students;
        public ClassRoom()
        {
            students = new People[10];
            for(int i=0;i<students.Length;i++)
            {
                students[i] = new People(i);
            }
        }
        public IEnumerator GetEnumerator()
        {
            for(int i=0;i<students.Length;i++)
            {
                yield return students[i].name;
                yield return students[i].name;
            }
        }
    }
}

输出如下

当然,你也可以在yield return时返回每个People的引用,这样你遍历City的时候,每次都能得到一个完整的“人”,而不只是它的名字。

yield

现在来重点理解以下yield。在此之前,我们修改以下代码,如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestIterator
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassRoom cr = new ClassRoom();
            foreach(string crTemp in cr)
            {
                Console.WriteLine(crTemp);
                //添加一
                Console.WriteLine("MoveNext");
            }
            Console.ReadKey();
        }
    }

    public class People
    {
        public string name;
        public int age;

        public People(int i)
        {
            name = "People" + i.ToString();
            age = i + 20;
        }
    }

    public class ClassRoom
    {
        People[] students;
        public ClassRoom()
        {
            students = new People[10];
            for(int i=0;i<students.Length;i++)
            {
                students[i] = new People(i);
            }
        }
        public IEnumerator GetEnumerator()
        {
            for(int i=0;i<students.Length;i++)
            {
                yield return students[i].name;
                // 添加一
                yield return students[i].name;
            }
        }
    }
}

我添加了两行代码,代码上面有“添加一”的注释。现在看看打印结果:
在这里插入图片描述
可以看到,foreach循环了二十次。但是ClassRoom里的students数组只有10个人。说明foreach遍历的序列并不是students本身,而是yield序列。每次遍历的时候,都会从yield return的地方返回一个值,同时下次从上次yield return返回的代码处继续运行,直到遇到下一个yield return。在我们重写的迭代器代码里,for循环总共循环了十次,但是每次循环都有两个yield return,所以整个迭代器内部总共有20个yield return。可以想象迭代器维护了一个游标,游标维护当前所处的yield。foreach每次从当前游标指向的yield return处取一个值,然后游标移动到下一个yield处。。。如此循环,直到所有的yield return的值都被取到。
上述代码(修改前)可以用以下代码替换:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestIterator
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassRoom cr = new ClassRoom();
            IEnumerator it = cr.GetEnumerator();
            Console.WriteLine(it.Current);
            while(it.MoveNext())
            {
                Console.WriteLine(it.Current);
            }
            Console.ReadKey();
        }
    }

    public class People
    {
        public string name;
        public int age;

        public People(int i)
        {
            name = "People" + i.ToString();
            age = i + 20;
        }
    }

    public class ClassRoom
    {
        People[] students;
        public ClassRoom()
        {
            students = new People[10];
            for(int i=0;i<students.Length;i++)
            {
                students[i] = new People(i);
            }
        }
        public IEnumerator GetEnumerator()
        {
            for(int i=0;i<students.Length;i++)
            {
                yield return students[i].name;
            }
        }
    }
}

获取GetIEnumerator()返回的迭代器,存储在变量it中,其中it.Current表示当前迭代器的游标所指向的对应yield return返回的物体,it.MoveNext()表示将迭代器的游标往前走一位,这个方法返回一个布尔值,如果下一位有值,则返回true,一开始迭代器的游标指向空,所以遍历的一开始都需要先执行it.MoveNext(),这种设计跟使用带head头部的链表的思想是一样的。
输出与第一次的输出相同。所以实际上foreach是一种语法糖,它实际上是获取了这个类的迭代器,然后用迭代器取值。迭代器迭代的次数跟访问的那个序列的长度是没有关系的,跟你使用的yield的次数有关。

Unity协程

首先Untiy的协程并不是多线程,它本质上还是在主线程上执行。由于诸多原因,Unity并不
支持多线程。
但是很多事情是不能在短短一帧就执行完毕的,比如寻路,一个机器人可能每帧都要检查当前所处的状态,然后判定自己应该往哪儿走,这种看似区别于主线程之外的任务其实仍然在主线程中执行,只是Unity把任务拆成很多或者无数个小段,每段在每一帧,或者每隔几帧几秒执行一次。
先来看看如何在Untiy里写协程,假设我们需要一个方法,射击时,在每帧的时候,枪口发射一颗子弹

public void Shoot()
{
	StartCoroutine("BulletFire");
}

private IEnumerator BulletFire()
{
	// Instantiate an bullet and let it fly
	yield return null;
}

我没有看过Unity的源码(实际上Unity也不开源),但是我觉得Unity里协程的实现方式,应该是:StartCoroutine()获取一个迭代器,然后维护一个计时器,这个计时器可能是计的是物理时间,也可能是帧数,或者是跟帧率有关的游戏时间,然后每当计时器计完一次数,就执行迭代器里的代码,并返回遇到的第一个yield所返回的物体,即return it.Current,然后it.MoveNext(),执行上次yield之后的代码。这个返回的物体应该是被计时器所接收,如果是null,那么计时器就记一次帧数表示一轮,如果是WaitForSeconds类型时,表示计时器一轮计数应当是一个物理时间。如果接收的是一个方法(这个方法必须返回System.Object能够强制转换的类型),那么计时器表示等待该函数执行完毕,并在下一帧完成计时(等同于null,因为实际上任何同步方法在Unity内都是一帧执行完毕)。
yield return返回的是一个WaitForSeconds类型时,即代码如下

yield return new WaitForSeconds(2f);

它会每帧执行一个另一个计时(物理时间),当完成 2 秒的计时时便表示迭代器的计时完成。然后迭代器MoveNext()

注:按照这样的想法,WaitForSeconds计时结束的时间一定大于或等于要求的时间。这个时间差取决于计时中最后一帧的持续时间。一般来说,这个时间差是可以忽略不计的,但是如果反复采用WaitForSeconds计时,误差就会不断累积。。。

2018-05-24 09:14:56 qq_39225721 阅读数 188
  • C#急速入门

    Unity开发,C#绝对是基础中的基础,课程针对纯小白而设计,各种语言细节在课程里均有涉猎,从原理到实战,从底层到算法,你想了解的C#,这里应有尽有,除了讲解,还有练习,你说棒不棒,哈哈,当然如果你是有其他语言基础的同学,课程依然会让你收货满满。来吧,我们进入正题。

    17632 人正在学习 去看看 张建飞

效果图:


上代码:

public class TextFadeOut : MonoBehaviour {


    public float speed = 0.5f;
    Text text;
    Color color;


    void Start () {


        text = GetComponent<Text>();
        color = text.color;
}

void Update () {


        if (gameObject.activeSelf)
        {
            color.a -= Time.deltaTime * speed;
            text.color = color;
        }
        
}

}

注意:两个Text——"Ready"  "GO!" 都要挂上TextFadeOut脚本。并且两个Text,先禁用隐藏掉。


public class PrepareLevel : MonoBehaviour {


    public GameObject GetReady;
    public GameObject Go;


void Start () {
        StartCoroutine(PrepareRoutine());
}


    IEnumerator PrepareRoutine() {
        yield return new WaitForSeconds(1f);


        GetReady.SetActive(true);


        yield return new WaitForSeconds(2f);


        GetReady.SetActive(false);
        Go.SetActive(true);


        yield return new WaitForSeconds(1f);
        Go.SetActive(false);
    }
}

注意:创建一个空物体,名字改为GameController,然后将PrepareLevel 脚本挂在它上面。

2018-07-26 17:07:05 Merrina 阅读数 623
  • C#急速入门

    Unity开发,C#绝对是基础中的基础,课程针对纯小白而设计,各种语言细节在课程里均有涉猎,从原理到实战,从底层到算法,你想了解的C#,这里应有尽有,除了讲解,还有练习,你说棒不棒,哈哈,当然如果你是有其他语言基础的同学,课程依然会让你收货满满。来吧,我们进入正题。

    17632 人正在学习 去看看 张建飞
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class IeTest : MonoBehaviour {
	public Button StartC;
	public Button Stopc;
	public Text Numc;
	int i = 0;
	// Use this for initialization
	void Start () {
		//开启协程
		StartC.onClick.AddListener(delegate() {
//			StartCoroutine(Test());
//			StartCoroutine("Test");
//						StartCoroutine(Test2(i));
						StartCoroutine("Test2",i);
		});
		//结束协程
		Stopc.onClick.AddListener(delegate() {
//			StopCoroutine("Test");
			StopCoroutine("Test2");
//			StopAllCoroutines();
		});

	}
	//协程
	IEnumerator Test(){
		
		while (true) {
			yield return new WaitForSeconds (1);
			i++;
			Numc.text = i + "";
		}

	}
	IEnumerator Test2(int i){

		while (true) {
			yield return new WaitForSeconds (1);
			i++;
			Numc.text = i + "";
		}

	}
}

 


经过测试StopAllCoroutines()可以停止所有书写模式开启的协程;StopCoroutine("Test")只能够停止StartCoroutine("Test")书写模式的协程;StartCoroutine(Test())模式开启的协程只能由StopAllCoroutines()停止,不能由StartCoroutine("Test")停止。

注意:继承父类重写虚方法,是另起一个协程,只对重写中开启的协程有关,所以StopAllCoroutines()不是只针对当前脚本中写的协程有用,而是对由当前脚本开启的协程有用,例如在子类中重写了父类中的虚方法,并调用了,就是在子类中又开启了一个父类中的私有类型的协程,在子类中的StopAllCoroutines();命令,只对子类中又开启的协程有用。

2018-06-27 15:48:27 wk6sae88 阅读数 604
  • C#急速入门

    Unity开发,C#绝对是基础中的基础,课程针对纯小白而设计,各种语言细节在课程里均有涉猎,从原理到实战,从底层到算法,你想了解的C#,这里应有尽有,除了讲解,还有练习,你说棒不棒,哈哈,当然如果你是有其他语言基础的同学,课程依然会让你收货满满。来吧,我们进入正题。

    17632 人正在学习 去看看 张建飞
一开始开启协程时需要用函数名字符串作为参数,结束时也用字符串。否则不能结束。

C#实现Unity协程

阅读数 9982

C# 杀掉后台进程

阅读数 1571

C#中协程的原理

阅读数 2886

没有更多推荐了,返回首页