Unity中级编程

Posted by LudoArt on June 17, 2020

Unity中级编程

创建属性

从类的外部去访问这个类的成员变量,有两种方法:第一种是通过定义公共变量,第二种是通过使用属性的办法(更佳)

使用属性的办法可以完成公共变量无法实现的操作:

  1. 通过省略get或set将字段变为只写或只读
  2. 可以将访问器视为函数,这表示你可以在访问器内部执行其他操作
using UnityEngine;
using System.Collections;

public class Player
{
    //成员变量可以称为字段。
    private int experience;

    //Experience 是一个基本属性
    public int Experience
    {
        get
        {
            //其他一些代码
            return experience;
        }
        set
        {
            //其他一些代码
            experience = value;
        }
    }

    //Level 是一个将经验值自动转换为玩家等级的属性
    public int Level
    {
        get
        {
            return experience / 1000;
        }
        set
        {
            experience = value * 1000;
        }
    }

    //这是一个自动实现的属性的
    //示例
    public int Health{ get; set;}
}
using UnityEngine;
using System.Collections;

public class Game : MonoBehaviour 
{
    void Start () 
    {
        Player myPlayer = new Player();

        //属性可以像变量一样使用
        myPlayer.Experience = 5;
        int x = myPlayer.Experience;
    }
}

三元运算符

using UnityEngine;
using System.Collections;

public class TernaryOperator : MonoBehaviour 
{
    void Start () 
    {
        int health = 10;
        string message;

        //这是一个三元运算的示例,其中根据
        //变量“health”选择一条消息。
        message = health > 0 ? "Player is Alive" : "Player is Dead";
    }
}

静态

包括静态变量、静态方法、静态类。

using UnityEngine;
using System.Collections;

public class Enemy
{
    //静态变量是在类的所有实例之间共享的变量。
    public static int enemyCount = 0;

    public Enemy()
    {
        //通过递增静态变量了解已创建此类的多少个对象。
        enemyCount++;
    }
}
using UnityEngine;
using System.Collections;

public class Game
{
    void Start () 
    {
        Enemy enemy1 = new Enemy();
        Enemy enemy2 = new Enemy();
        Enemy enemy3 = new Enemy();

        //可以使用类名和点运算符来访问静态变量。
        int x = Enemy.enemyCount;
    }
}
using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour 
{
    //静态变量是在类的所有实例之间共享的变量。
    public static int playerCount = 0;

    void Start()
    {
        //通过递增静态变量了解已创建此类的多少个对象。
        playerCount++;
    }
}
using UnityEngine;
using System.Collections;

public class PlayerManager : MonoBehaviour 
{
    void Start()
    {
        //可以使用类名和点运算符来访问静态变量。
        int x = Player.playerCount;
    }
}
using UnityEngine;
using System.Collections;

public static class Utilities 
{
    //可以在没有类对象的情况下调用静态方法。
    //请注意,静态方法无法访问非静态成员变量。
    public static int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
using UnityEngine;
using System.Collections;

public class UtilitiesExample : MonoBehaviour 
{
    void Start()
    {
        //可以使用类名和点运算符来访问静态方法。
        int x = Utilities.Add (5, 6);
    }
}

方法重载

方法重载的匹配方式:

  1. 首先查找是否有完全匹配的版本;
  2. 其次查找是否有唯一一个需要最少参数转换量的版本;
  3. 最后如果没有可能匹配的版本或多个版本所需的转换量相同则报错。
using UnityEngine;
using System.Collections;

public class SomeClass
{
    //第一个 Add 方法的签名为“Add(int, int)”。该签名必须具有唯一性。
    public int Add(int num1, int num2)
    {
        return num1 + num2;
    }

    //第二个 Add 方法的签名为“Add(string, string)”。同样,该签名必须具有唯一性。
    public string Add(string str1, string str2)
    {
        return str1 + str2;
    }
}
using UnityEngine;
using System.Collections;

public class SomeOtherClass : MonoBehaviour 
{
    void Start () 
    {
        SomeClass myClass = new SomeClass();

        //具体调用的 Add 方法将取决于传入的参数。
        myClass.Add (1, 2);
        myClass.Add ("Hello ", "World");
    }
}

通用(泛型)

包括泛型方法和泛型类。

注:可以通过关键词where来限制泛型T的类型

using UnityEngine;
using System.Collections;

public class SomeClass 
{
    //这是一个通用方法。
    //注意通用类型“T”。该“T”将在运行时替换为实际类型。
    public T GenericMethod<T>(T param)
    {
        return param;
    }
}
using UnityEngine;
using System.Collections;

public class SomeOtherClass : MonoBehaviour 
{
    void Start () 
    {
        SomeClass myClass = new SomeClass();

        //为了使用此方法,必须告诉此方法用什么类型替换“T”。
        myClass.GenericMethod<int>(5);
    }
}
using UnityEngine;
using System.Collections;

//这是一个通用类。注意通用类型“T”。
//“T”将被替换为实际类型,同样,
//该类中使用的“T”类型实例也将被替换。
public class GenericClass <T>
{
    T item;

    public void UpdateItem(T newItem)
    {
        item = newItem;
    }
}
using UnityEngine;
using System.Collections;

public class GenericClassExample : MonoBehaviour 
{
    void Start () 
    {        
        //为了创建通用类的对象,必须
        //指定希望该类具有的类型。
        GenericClass<int> myClass = new GenericClass<int>();

        myClass.UpdateItem(5);
    }
}

继承

注:创建子类实例时自动调用父类的构造函数。可以用base的方法显式地指定调用父类的哪个构造函数。

using UnityEngine;
using System.Collections;

//这是基类,
//也称为父类。
public class Fruit 
{
    public string color;

    //这是 Fruit 类的第一个构造函数,不会被任何派生类继承。
    public Fruit()
    {
        color = "orange";
        Debug.Log("1st Fruit Constructor Called");
    }

    //这是 Fruit 类的第二个构造函数,不会被任何派生类继承。
    public Fruit(string newColor)
    {
        color = newColor;
        Debug.Log("2nd Fruit Constructor Called");
    }

    public void Chop()
    {
        Debug.Log("The " + color + " fruit has been chopped.");        
    }

    public void SayHello()
    {
        Debug.Log("Hello, I am a fruit.");
    }
}
using UnityEngine;
using System.Collections;

//这是派生类,也称为子类。
public class Apple : Fruit 
{
    //这是 Apple 类的第一个构造函数。
    //它立即调用父构造函数,甚至在它运行之前调用。
    public Apple()
    {
        //注意 Apple 如何访问公共变量 color,
        //该变量是父 Fruit 类的一部分。
        color = "red";
        Debug.Log("1st Apple Constructor Called");
    }

    //这是 Apple 类的第二个构造函数。
    //它使用“base”关键字指定要调用哪个父构造函数。
    public Apple(string newColor) : base(newColor)
    {
        //请注意,该构造函数不会设置 color,
        //因为基构造函数会设置作为参数
        //传递的 color。
        Debug.Log("2nd Apple Constructor Called");
    }
}
using UnityEngine;
using System.Collections;

public class FruitSalad : MonoBehaviour 
{
    void Start () 
    {
        //让我们用默认构造函数来说明继承。
        Debug.Log("Creating the fruit");
        Fruit myFruit = new Fruit();
        Debug.Log("Creating the apple");
        Apple myApple = new Apple();

        //调用 Fruit 类的方法。
        myFruit.SayHello();
        myFruit.Chop();

        //调用 Apple 类的方法。
        //注意 Apple 类如何访问Fruit 类的所有公共方法。
        myApple.SayHello();
        myApple.Chop();

        //现在,让我们用读取字符串的构造函数来说明继承。
        Debug.Log("Creating the fruit");
        myFruit = new Fruit("yellow");
        Debug.Log("Creating the apple");
        myApple = new Apple("green");

        //调用 Fruit 类的方法。
        myFruit.SayHello();
        myFruit.Chop();

        //调用 Apple 类的方法。
        //注意 Apple 类如何访问Fruit 类的所有公共方法。
        myApple.SayHello();
        myApple.Chop();
    }
}

多态

using UnityEngine;
using System.Collections;

public class Fruit 
{
    public Fruit()
    {
        Debug.Log("1st Fruit Constructor Called");
    }

    public void Chop()
    {
        Debug.Log("The fruit has been chopped.");        
    }

    public void SayHello()
    {
        Debug.Log("Hello, I am a fruit.");
    }
}
using UnityEngine;
using System.Collections;

public class Apple : Fruit 
{
    public Apple()
    {
        Debug.Log("1st Apple Constructor Called");
    }

    //Apple 有自己的 Chop() 和 SayHello() 版本。
    //运行脚本时,请注意何时调用Fruit 版本的这些方法以及何时调用Apple 版本的这些方法。
    //此示例使用“new”关键字禁止来自 Unity 的警告,同时不覆盖Apple 类中的方法。
    public new void Chop()
    {
        Debug.Log("The apple has been chopped.");        
    }

    public new void SayHello()
    {
        Debug.Log("Hello, I am an apple.");
    }
}
using UnityEngine;
using System.Collections;

public class FruitSalad : MonoBehaviour
{
    void Start () 
    {
        //请注意,这里的变量“myFruit”的类型是
        //Fruit,但是被分配了对 Apple 的引用。这是
        //由于多态而起作用的。由于 Apple 是 Fruit,
        //因此这样是可行的。虽然 Apple 引用存储
        //在 Fruit 变量中,但只能像 Fruit 一样使用
        Fruit myFruit = new Apple();

        myFruit.SayHello();
        myFruit.Chop();

        //这称为向下转换。Fruit 类型的变量“myFruit”
        //实际上包含对 Apple 的引用。因此,
        //可以安全地将它转换回 Apple 变量。这使得
        //它可以像 Apple 一样使用,而在以前只能像 Fruit
        //一样使用。
        Apple myApple = (Apple)myFruit;

        myApple.SayHello();
        myApple.Chop();    
    }
}

成员隐藏

注:使用new关键字进行成员隐藏,这与重载(覆盖)不同。

using UnityEngine;
using System.Collections;

public class Humanoid
{
    //Yell 方法的基版本
    public void Yell()
    {
        Debug.Log ("Humanoid version of the Yell() method");
    }
}
using UnityEngine;
using System.Collections;

public class Enemy : Humanoid
{
    //这会隐藏 Humanoid 版本。
    new public void Yell()
    {
        Debug.Log ("Enemy version of the Yell() method");
    }
}
using UnityEngine;
using System.Collections;

public class Orc : Enemy
{
    //这会隐藏 Enemy 版本。
    new public void Yell()
    {
        Debug.Log ("Orc version of the Yell() method");
    }
}
using UnityEngine;
using System.Collections;

public class WarBand : MonoBehaviour 
{
    void Start () 
    {
        Humanoid human = new Humanoid();
        Humanoid enemy = new Enemy();
        Humanoid orc = new Orc();

        //注意每个 Humanoid 变量如何包含对继承层级视图中
        //不同类的引用,但每个变量都调用 Humanoid Yell() 方法。
        human.Yell();
        enemy.Yell();
        orc.Yell();
    }
}

覆盖

using UnityEngine;
using System.Collections;

public class Fruit 
{
    public Fruit ()
    {
        Debug.Log("1st Fruit Constructor Called");
    }

    //这些方法是虚方法,因此可以在子类中将它们覆盖
    public virtual void Chop ()
    {
        Debug.Log("The fruit has been chopped.");        
    }

    public virtual void SayHello ()
    {
        Debug.Log("Hello, I am a fruit.");
    }
}
using UnityEngine;
using System.Collections;

public class Apple : Fruit 
{
    public Apple ()
    {
        Debug.Log("1st Apple Constructor Called");
    }

    //这些方法是覆盖方法,因此可以覆盖父类中的任何虚方法。
    public override void Chop ()
    {
        base.Chop();
        Debug.Log("The apple has been chopped.");        
    }

    public override void SayHello ()
    {
        base.SayHello();
        Debug.Log("Hello, I am an apple.");
    }
}
using UnityEngine;
using System.Collections;

public class FruitSalad : MonoBehaviour 
{    
    void Start () 
    {
        Apple myApple = new Apple();

        //请注意,Apple 版本的方法将覆盖 Fruit 版本。
        //另外请注意,由于 Apple 版本使用“base”关键字来调用 Fruit 版本,因此两者都被调用。
        myApple.SayHello();
        myApple.Chop();    

        //“覆盖”在多态情况下也很有用。
        //由于 Fruit 类的方法是“虚”的,而 Apple 类的方法是“覆盖”的,
        //因此当我们将 Apple 向上转换为 Fruit 时,
        //将使用 Apple 版本的方法。
        Fruit myFruit = new Apple();
        myFruit.SayHello();
        myFruit.Chop();
    }
}

接口

定义接口的好处:

  1. 一个类可以继承多个接口;
  2. 接口用于跨多个互不相关的类定义通用功能
using UnityEngine;
using System.Collections;

//这是只有一个必需方法的基本接口。
public interface IKillable
{
    void Kill();
}

//这是一个通用接口,其中 T 是将由实现类提供的数据类型的占位符。
public interface IDamageable<T>
{
    void Damage(T damageTaken);
}
using UnityEngine;
using System.Collections;

public class Avatar : MonoBehaviour, IKillable, IDamageable<float>
{
    //IKillable 接口的必需方法
    public void Kill()
    {
        //执行一些有趣操作
    }

    //IDamageable 接口的必需方法
    public void Damage(float damageTaken)
    {
        //执行一些有趣操作
    }
}

列表和字典

  • List是一个动态改变大小的数组
  • Dictionary是通过一个或多个key访问的值的集合
using UnityEngine;
using System.Collections;
using System; //这允许 IComparable 接口

//这是您将存储在不同集合中的类。
//为了使用集合的 Sort() 方法,此类需要
//实现 IComparable 接口。
public class BadGuy : IComparable<BadGuy>
{
    public string name;
    public int power;

    public BadGuy(string newName, int newPower)
    {
        name = newName;
        power = newPower;
    }

    //IComparable 接口需要此方法。
    public int CompareTo(BadGuy other)
    {
        if(other == null)
        {
            return 1;
        }

        //返回力量差异。
        return power - other.power;
    }
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SomeClass : MonoBehaviour
{
    void Start () 
    {
        //这是创建列表的方式。注意如何在
        //尖括号 (< >) 中指定类型。
        List<BadGuy> badguys = new List<BadGuy>();

        //这里将 3 个 BadGuy 添加到列表
        badguys.Add( new BadGuy("Harvey", 50));
        badguys.Add( new BadGuy("Magneto", 100));
        badguys.Add( new BadGuy("Pip", 5));

        badguys.Sort();

        foreach(BadGuy guy in badguys)
        {
            print (guy.name + " " + guy.power);
        }

        //这会清除列表,使其为空。
        badguys.Clear();
    }
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SomeOtherClass : MonoBehaviour 
{
    void Start ()
    {
        //这是创建字典的方式。注意这是如何采用
        //两个通用术语的。在此情况中,您将使用字符串和
        //BadGuy 作为两个值。
        Dictionary<string, BadGuy> badguys = new Dictionary<string, BadGuy>();

        BadGuy bg1 = new BadGuy("Harvey", 50);
        BadGuy bg2 = new BadGuy("Magneto", 100);

        //可以使用 Add() 方法将变量放入字典中。
        badguys.Add("gangster", bg1);
        badguys.Add("mutant", bg2);

        BadGuy magneto = badguys["mutant"];

        BadGuy temp = null;

        //这是一种访问字典中值的更安全
        //但缓慢的方法。
        if(badguys.TryGetValue("birds", out temp))
        {
            //成功!
        }
        else
        {
            //失败!
        }
    }
}

协程

注:与属性结合使用时更具优势

using UnityEngine;
using System.Collections;

public class CoroutinesExample : MonoBehaviour
{
    public float smoothing = 1f;
    public Transform target;


    void Start ()
    {
        StartCoroutine(MyCoroutine(target));
    }


    IEnumerator MyCoroutine (Transform target)
    {
        while(Vector3.Distance(transform.position, target.position) > 0.05f)
        {
            transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);

            yield return null;
        }

        print("Reached the target.");

        yield return new WaitForSeconds(3f);

        print("MyCoroutine is now finished.");
    }
}

点击移动功能

using UnityEngine;
using System.Collections;

public class PropertiesAndCoroutines : MonoBehaviour
{
    public float smoothing = 7f;
    public Vector3 Target
    {
        get { return target; }
        set
        {
            target = value;

            StopCoroutine("Movement");
            StartCoroutine("Movement", target);
        }
    }


    private Vector3 target;


    IEnumerator Movement (Vector3 target)
    {
        while(Vector3.Distance(transform.position, target) > 0.05f)
        {
            transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);

            yield return null;
        }
    }
}
using UnityEngine;
using System.Collections;

public class ClickSetPosition : MonoBehaviour
{
    public PropertiesAndCoroutines coroutineScript;


    void OnMouseDown ()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        Physics.Raycast(ray, out hit);

        if(hit.collider.gameObject == gameObject)
        {
            Vector3 newTarget = hit.point + new Vector3(0, 0.5f, 0);
            coroutineScript.Target = newTarget;
        }
    }
}

四元数

四元数是处理旋转的最佳方式,可以避免万向节锁问题。

using UnityEngine;
using System.Collections;

public class MotionScript : MonoBehaviour 
{    
    public float speed = 3f;


    void Update () 
    {
        transform.Translate(-Input.GetAxis("Horizontal") * speed * Time.deltaTime, 0, 0);
    }
}
using UnityEngine;
using System.Collections;

public class LookAtScript : MonoBehaviour 
{
    public Transform target;


    void Update () 
    {
        Vector3 relativePos = target.position - transform.position;
        transform.rotation = Quaternion.LookRotation(relativePos);
    }
}
using UnityEngine;
using System.Collections;

public class GravityScript : MonoBehaviour 
{
    public Transform target;


    void Update () 
    {
        Vector3 relativePos = (target.position + new Vector3(0, 1.5f, 0)) - transform.position;
        Quaternion rotation = Quaternion.LookRotation(relativePos);

        Quaternion current = transform.localRotation;

        transform.localRotation = Quaternion.Slerp(current, rotation, Time.deltaTime);
        transform.Translate(0, 0, 3 * Time.deltaTime);
    }
}

委托

委托有利用动态控制调用哪个函数。

委托还支持多播,即允许单个委托变量同时代表多个方法。

using UnityEngine;
using System.Collections;


public class DelegateScript : MonoBehaviour 
{    
    delegate void MyDelegate(int num);
    MyDelegate myDelegate;


    void Start () 
    {
        myDelegate = PrintNum;
        myDelegate(50);

        myDelegate = DoubleNum;
        myDelegate(50);
    }

    void PrintNum(int num)
    {
        print ("Print Num: " + num);
    }

    void DoubleNum(int num)
    {
        print ("Double Num: " + num * 2);
    }
}
using UnityEngine;
using System.Collections;

public class MulticastScript : MonoBehaviour 
{
    delegate void MultiDelegate();
    MultiDelegate myMultiDelegate;


    void Start () 
    {
        myMultiDelegate += PowerUp;
        myMultiDelegate += TurnRed;

        if(myMultiDelegate != null)
        {
            myMultiDelegate();
        }
    }

    void PowerUp()
    {
        print ("Orb is powering up!");
    }

    void TurnRed()
    {
        renderer.material.color = Color.red;
    }
}

属性

Range属性

在编辑器中,加上Range属性的变量将多一个滑动条可更改其值。

using UnityEngine;
using System.Collections;

public class SpinScript : MonoBehaviour 
{
    [Range(-100, 100)] public int speed = 0;

    void Update () 
    {
        transform.Rotate(new Vector3(0, speed * Time.deltaTime, 0));
    }
}

ExecuteInEditMode属性

ExecuteInEditMode属性使得脚本在非运行模式下即可做出改变(注意:该改变不可逆,是永久性的)

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class ColorScript : MonoBehaviour 
{
    void Start()
    {
        renderer.sharedMaterial.color = Color.red;
    }
}

事件

使用事件构造广播系统。

如果想创建一个包含多个类的动态方法系统,使用事件变量,而非委托变量。

using UnityEngine;
using System.Collections;

public class EventManager : MonoBehaviour 
{
    public delegate void ClickAction();
    public static event ClickAction OnClicked;


    void OnGUI()
    {
        if(GUI.Button(new Rect(Screen.width / 2 - 50, 5, 100, 30), "Click"))
        {
            if(OnClicked != null)
                OnClicked();
        }
    }
}
using UnityEngine;
using System.Collections;

public class TeleportScript : MonoBehaviour 
{
    void OnEnable()
    {
        EventManager.OnClicked += Teleport;
    }


    void OnDisable()
    {
        EventManager.OnClicked -= Teleport;
    }


    void Teleport()
    {
        Vector3 pos = transform.position;
        pos.y = Random.Range(1.0f, 3.0f);
        transform.position = pos;
    }
}
using UnityEngine;
using System.Collections;

public class TurnColorScript : MonoBehaviour 
{
    void OnEnable()
    {
        EventManager.OnClicked += TurnColor;
    }


    void OnDisable()
    {
        EventManager.OnClicked -= TurnColor;
    }


    void TurnColor()
    {
        Color col = new Color(Random.value, Random.value, Random.value);
        renderer.material.color = col;
    }
}