https://learn.u3d.cn/tutorial/beginner-gameplay-scripting

00.VS Code编辑器

VS Code安装插件

  • C#
  • Unity Code Snippets
  • Debugger for Unity
  • Unity Tools

VS Code没有代码提示

7_erQ https://www.bilibili.com/read/cv7414785/

这里底下给出的回复是保存项目,然后关掉编译器和Unity编辑器,接着删掉项目文件夹下的.csproj和.sln这两个文件(如果你用的IDE是VS的话那么还有一个.vs的隐藏文件夹),然后重开项目,系统就又会重新生成匹配的.csproj和.sln文件。

以后在使用新的Unity版本或者引用新的工具库时,出现编译器不提示的情况就可以照上面的方法解决,不过安全起见,在删之前最好还是备份下.csproj和.sln这两个文件。

  1. 工程下的**.csproj和.sln文件中记录着当前项目的脚本文件、程序集引用以及一些平台宏、版本信息
  2. 删除工程中的**.csproj和.sln文件并不会对项目造成影响(除非你在这些文件中加入了你需要的更改)
  3. 可以通过重启项目来重新生成丢失的**.csproj和.sln文件,或者也可以通过以下方式刷新这些文件:①编辑器中"Editor->Preferences...->External Tools->Regenerate project files"刷新.csproj和.sln文件

01.Input.GetKey

调整立方体的颜色,方法是调整该对象关联的默认材质的color值

我们引用了这个脚本关联的游戏对象,接着引用渲染器 Mesh Renderer ,然后引用关联至这个渲染器的材质,最后引用这种材质的颜色,我将它设为名为Red的快捷方式,它是color类的组成部分

     void Update()
    {
         if (Input.GetKeyDown(KeyCode.R)) {
             GetComponent<Renderer>().material.color = Color.red;
        }
         if (Input.GetKeyDown(KeyCode.G)) {
             GetComponent<Renderer>().material.color = Color.green;
        }
         if (Input.GetKeyDown(KeyCode.B)) {
             GetComponent<Renderer>().material.color = Color.blue;
        }
    }

02.变量与函数

前半部分是声明,后半部分是初始化

在脚本中,我们会用到 startUpdata 函数,当这个脚本绑定的对象进入场景时就会调用 Start 函数

我们可以用 Debug.log 获取游戏中某一参数的值

     int myInt = 5;
     void Start()
    {
         int myInt = 55;
         Debug.Log(myInt * 2);
    }

我们可以指定函数的返回值,括号内写函数的传入值,实际上是创建了一个临时变量。

创建一个函数,将传入的值乘2,然后作为返回值返回

调用函数将 myInt 传入,然后再赋给myInt

   int myInt = 5;
 ​
     void Start(){
         myInt = MultiplyByTwo(myInt);
         Debug.Log(myInt);
    }
 ​
     int MultiplyByTwo(int number) {
         int result;
         result = number * 2;
         return result;
    }

03.约定和语法

缩进和注释

 using UnityEngine;
 using System.Collections;
 ​
 public class BasicSyntax : MonoBehaviour
 {
     void Start ()
    {
         Debug.Log(transform.position.x);
         
         if(transform.position.y <= 5f)
        {
             Debug.Log ("I'm about to hit the ground!");
        }
    }
 }

04.IF语句

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 ​
 public class IfSatements : MonoBehaviour {
     // Start is called before the first frame update
     float coffeeTemperature = 85.0f;
     float hotLimitTemperature = 70.0f;
     float coldLimitTemperature = 40.0f;
 ​
     // Update is called once per frame
     void Update() {
         if (Input.GetKeyDown(KeyCode.Space)) {
             TemperatureTest();
        }
         coffeeTemperature -= Time.deltaTime * 5f;
    }
 ​
     void TemperatureTest() {
         if (coffeeTemperature > hotLimitTemperature) {
             print("Coffee is too hot.");
        } else if (coffeeTemperature < coldLimitTemperature) {
             print("Coffee is too cold");
        } else {
             print("Coffee is just right.");
        }
    }
 }
 ​

05.循环

while循环

条件为真时执行循环,条件为假时跳出循环

     int cupInTheSink = 4;
     void Start()
    {
         while (cupInTheSink>0){
             Debug.Log("I have washed a cup!");
             cupInTheSink--;
        }
    }

do-while循环:

无论是否复合条件都先执行一次do中内容,然后再在while中进行判断

     bool shouldContine=false;
     void Start(){
         do{
             print("hello world");
        }
         while (shouldContine == true);
    }

for循环:

     int numEnemies = 3;
     void Start(){
         for(int i=0;i<numEnemies;i++){
             Debug.Log("Creating enemy number: " + i);
        }
    }

Foreach循环

         string[] strings = new string[3];
         
         strings[0] = "First string";
         strings[1] = "Second string";
         strings[2] = "Third string";
         
         foreach(string item in strings)
        {
             print (item);
        }

06.作用域和访问修饰符 public、private

alphabatagamaA06_ScopAndAccessModifiers作用域内,而penscrayonsanswerExample作用域内

公开访问修饰符public可以在游戏引擎Inspector窗口中显示并可以修改值,如果在Start和Awake函数中设置该值,它们会覆盖Inspector中设置的值(输出29),在游戏运行中在Inspector中修改的话则会是Inspector的值(游戏中修改的值不会保存)。游戏中的Inspector中修改 -> Start和Awake函数 -> Inspector中修改 -> 公开访问修饰符public

  • 私有访问修饰符private只能在类内使用,在C#中未指定访问修饰符的任意变量默认为private私有访问修饰符
  • 公开访问修饰符public可以跨脚本调用
 public class ScopeAndAccessModifiers : MonoBehaviour
 {
     public int alpha = 5;
     private int beta = 0;
     private int gamma = 5;
     private AnotherClass myOtherClass;
     
     void Start ()
    {
         alpha = 29;
         myOtherClass = new AnotherClass();
         myOtherClass.FruitMachine(alpha, myOtherClass.apples);
    }
 ​
     void Example (int pens, int crayons)
    {
         int answer;
         answer = pens * crayons * alpha;
         Debug.Log(answer);
    }
 ​
     void Update ()
    {
         Debug.Log("Alpha is set to: " + alpha);
    }
 }
 public class AnotherClass
 {
     public int apples;
     public int bananas;
     private int stapler;
     private int sellotape;
 ​
     public void FruitMachine (int a, int b)
    {
         int answer;
         answer = a + b;
         Debug.Log("Fruit total: " + answer);
    }
 ​
     private void OfficeSort (int a, int b)
    {
         int answer;
         answer = a + b;
         Debug.Log("Office Supplies total: " + answer);
    }
 }
 ​

07.Awake和Start函数

Awake和Start是在加载脚本时自动调用的两个函数,执行顺序Awake() →OnEnable() → start()

  • 首先调用Awake,即使还未启用脚本组件
  • Start在Awake之后首次更新前调用,前提是已经启用了脚本组件

Awake和Start在一个对象绑定脚本的生命周期内只能调用一次,不能通过禁用和重新启用来重复执行

     void Awake() 
    {
         Debug.Log("Awake called.");
    }
 ​
     void Start()
    {
         Debug.Log("Start called.");
    }

08.Update和FixdUpdate

  • Update每帧执行一次,电脑运行速度不同调用时间亦会不同,不是按照固定时间调用的
  • FixdUpdate调用间隔时间相同(默认0.02s)
     void FixedUpdate()
    {
         Debug.Log("FixedUpdate time: " + Time.deltaTime);
    }
     void Update()
    {
         Debug.Log("Update time: " + Time.deltaTime);
    }

09.矢量数学(线性代数)

向量可以使用勾股定理计算

向量加法可直接相加

3D比2D多个了Z轴,在Unity中使用左手坐标系,举起左手食指向上指向Y轴,大拇指向右指向X轴,中指向前指向Z轴

Vector A(x,y,z) ; Vector A(x,y,z) (Ax*Bx)+(Ay*By)+(Az*Bz)=Dot Product 点乘

如果点乘等于0则相互垂直

10.启用和禁用组件 enabled

使用GetComponent<>()将这个变量设置为对象关联Light组件

如果抬起空格键,则将myLight的enable设置为非当前值

     private Light myLight;
     void Start()
    {
         myLight = GetComponent<Light>();
    }
 ​
     void Update()
    {
         if (Input.GetKeyUp(KeyCode.Space))
        {
             myLight.enabled = !myLight.enabled;
        }
    }

11.激活游戏对象 SetActive

禁用父对象同时也会禁用场景中的子对象,但它仍在其层次结构中保持活跃状态

 gameObject.SetActive(false);

要确认某个对象在场景或在层次结构中是否为活跃状态,可以使用Active SelfActive in Hierarchy状态查询

如果禁用父对象,子对象被激活但是它所处的层次结构未被激活

要激活子对象就必须激活父对象

     public GameObject myObject;
     void Start()
    {
         Debug.Log("Active Self: " + myObject.activeSelf);
         Debug.Log("Active in Hierarchy: " + myObject.activeInHierarchy);
    }

12.Translate和Rotate

 transform.Translate(new Vector3(0, 0, 1));

乘以Time.deltaTime按照每秒移动多少米的移动速度(方向*速度*时间帧)

     public float mvoeSpeed=10f;
     void Update()
    {
         transform.Translate(Vector3.forward*mvoeSpeed*Time.deltaTime);
    }

可以使用按键来控制前后

     public float mvoeSpeed=10f;
     void Update()
    {
         if(Input.GetKey(KeyCode.UpArrow))
             transform.Translate(Vector3.forward*mvoeSpeed*Time.deltaTime);
         if(Input.GetKey(KeyCode.DownArrow))
             transform.Translate(-Vector3.forward*mvoeSpeed*Time.deltaTime);
    }

transform.Rotatetransform.Translate十分相似,Vector3.up围绕哪个轴旋转

     public float turnSpeed = 50f;
     void Update()
    {
         if (Input.GetKey(KeyCode.LeftArrow))
             transform.Rotate(Vector3.up * -turnSpeed * Time.deltaTime);
         if (Input.GetKey(KeyCode.RightArrow))
             transform.Rotate(Vector3.up * turnSpeed * Time.deltaTime);
    }

(如果物体运动涉及物理应考虑使用Physics函数而非Transform和Rotate)

13.Look At 指向

LookAt可让对象指向时世界另一个Transform,

只要将脚本绑定到摄像机上,同时绑定目标对象,就可以让摄像机对准目标对象

     public Transform target;
     void Update()
    {
         transform.LookAt(target);
    }

可以再Scene视图顶部切换世界坐标Global和局部坐标Local

14.线性差值

Lerp 函数可以在两个值之间进行线性插值。线性插值会在两个给定值之间找到某个百分比的值。

例如,我们可以在数字 3 和 5 之间按 50% 进行线性插值以得到数字 4。这是因为 4 是 3 和 5 之间距离的 50%。

 // 在此示例中,result = 4
 float result = Mathf.Lerp (3f, 5f, 0.5f);

起始值3,结束值5,插值为 0.5,表示 50%。如果为 0,则函数将返回“from”值;如果为 1,则函数将返回“to”值。

Color.Lerp(两种给定颜色的某种混合),颜色由代表红色、蓝色、绿色和 Alpha 的 4 个 float 参数表示。

Vector3.Lerp(占两个给定矢量之间的百分比)

 Vector3 from = new Vector3 (1f, 2f, 3f);
 Vector3 to = new Vector3 (5f, 6f, 7f);
 ​
 // 此处 result = (4, 5, 6)
 Vector3 result = Vector3.Lerp (from, to, 0.75f);

在某些情况下,可使用 Lerp 函数使值随时间平滑。

 void Update ()
 {
     light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f);
 }

如果光的强度从 0 开始,则在第一次更新后,其值将设置为 4。下一帧会将其设置为 6,然后设置为 7,再然后设置为 7.5,依此类推。因此,经过几帧后,光强度将趋向于 8,但随着接近目标,其变化速率将减慢。请注意,这是在若干个帧的过程中发生的。如果我们不希望与帧率有关,则可以使用以下代码,这意味着强度变化将按每秒而不是每帧发生。

 void Update ()
 {
     light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f * Time.deltaTime);
 }

请注意,在对值进行平滑时,通常情况下最好使用 SmoothDamp 函数。仅当您确定想要的效果时,才应使用 Lerp 进行平滑。

15.Destroy 销毁

运行时移除游戏对象或从游戏对象移除组件,也可通过延迟达到相同目的

 Destroy(gameObject);

按下空格时,游戏对象就会被销毁

     void Update()
    {
         if(Input.GetKey(KeyCode.Space)){
             Destroy(gameObject);
        }
    }

你可能将这个脚本用于不同用途,因此不应该销毁对象,否则脚本也会同时销毁,所以应该引用另一对象

     public GameObject other;
     void Update()
    {
         if(Input.GetKey(KeyCode.Space)){
             Destroy(other);
        }
    }

也可以用Destroy移除组件

 Destroy(GetComponent<MeshRenderer>());

可以在后面增加延迟

 Destroy(gameObject,3);

16.GetButton和GetKey

GetButton和GetKey二者区别是GetKey会使用KeyCode明确置顶按键名称,

建议使用GetButton指定你自己的控制,输入管理器Input Manager允许指定输入名称,然后给它指定一个键或按钮,菜单栏 -> Project Settings -> Input Manager(旧版输入系统)

  • GetButtonDown(1帧后恢复FALSE)
  • GetButton(直至抬起)
  • GetButtonUp(抬起后触发)
 Input.GetButtonDown("")

17.GetAxis

GetAxis返回浮点型小数,这个值介于-1到1之间

  • Gravity是归零速度,数值越大归零越快
  • Sensitivity是反应速度,数值越大速度越快
  • Dead盲区,用于调整误差

获取横轴或者纵轴的值,只需要在代码中添加

 Input.GetAxis("Horizontal");
 Input.GetAxis("Vertical");

也可使用Input.GetAxis("Raw")来返回整数值

 Input.GetAxis("Raw");

18.OnMouseDown 鼠标点击

OnMouseDown可检测碰撞体或GUI文本元素的点击

     void OnMouseDown()
    {
         Debug.Log("Clicked on the Cube!");
    }

给被点击的对象添加一个作用力AddForce,

     private Rigidbody rb;
     private void Awake() {
         rb = GetComponent<Rigidbody>();
    }
     
     void OnMouseDown()
    {
         rb.AddForce(transform.forward * 500f);
         rb.useGravity = true;
    }

19.GetComponent 获取组件

GetComponent 比较占用性能,建议在Awake和Start中调用一次

 public class A19_AnotherScript : MonoBehaviour
 {
     public int playerScore = 9001;
 }
 public class A19_YetAnotherScript : MonoBehaviour
 {
     public int numberOfPlayerDeaths = 3;
 }

GetComponent会返回调用它的游戏对象中任意指定类型组件的引用

 public class A19_UsingOtherComponents : MonoBehaviour
 {
     public GameObject otherGameObject;
 ​
     private A19_AnotherScript anotherScript;
     private A19_YetAnotherScript yetAnotherScript;
     void Awake()
    {
         anotherScript = GetComponent<A19_AnotherScript>();
         yetAnotherScript = otherGameObject.GetComponent<A19_YetAnotherScript>();
    }
 ​
     void Start()
    {
         Debug.Log("The player's score is "+anotherScript.playerScore);
         Debug.Log("The player has died " + yetAnotherScript.numberOfPlayerDeaths + "times");
    }
 }

GetComponent常用来访问其他脚本,它也可用来访问API未公开的其他组件,

 public class A19_UsingOtherComponents : MonoBehaviour
 {
     private BoxCollider boxCol;
     void Awake()
    {
         boxCol = GetComponent<BoxCollider>();
    }
 ​
     void Start()
    {
         boxCol.size = new Vector3(3, 3, 3);
    }
 }

20.DeltaTime

指两次更新或者固定更新函数的间隔时长,作用是让其他增量计算的值变得平滑

*Time.deltaTime即使帧率变化,速度也会保持恒定

     public float speed = 8f;
     public float countdown = 3.0f;
     void Update()
    {
         countdown -= Time.deltaTime;
         if (countdown <= 0.0f)
             GetComponent<Light>().enabled = true;
         if (Input.GetKey(KeyCode.RightArrow))
             transform.position += new Vector3(speed * Time.deltaTime, 0.0f, 0.0f);
    }

21.数据类型

两种主要的数据类型:值类型和引用类型

值类型存储某种值;所有引用类型变量都包含存储位置和存储地址

  • 如果值类型改变则只会影响特定变量
  • 如果引用类型改变,所以包含特定存储地址的变量都会受到影响

Value值类型:

  • int
  • float
  • double
  • bool
  • char
  • Structs
    • Vector3
    • Quaternion

Reference引用类型:

  • Classes
    • Transform
    • GameObject

值类型包含其自己的数据副本,更改它们只会影响特定变量

 Vector3 currentPosition = transform.position;
 currentPosition = new Vector3(0, 2, 0);

引用类型更改其中一个,另一个也会改变

 Transform tran = transform;
 tran.position = new Vector3(0, 2, 0);

22.类

  • 构造函数的名称始终都类的名称
  • 构造函数一定不会有返回类型,连void都没有
  • 一个类可能又多个不同的构造函数,但对象初始化时只会调用其中一个构造函数
 public class A22_Inventory : MonoBehaviour
 {
     // 在Inventory类中有Stuff子类,其中有3个int变量
     public class Stuff {
         public int projectileA;
         public int projectileB;
         public int projectileC;
         public float fuel;
 ​
         public Stuff(int prA,int prB,int prC){
             projectileA = prA;
             projectileB = prB;
             projectileC = prC;
        }
 ​
         public Stuff(int prA,float fu)
        {
             projectileA = prA;
             fuel = fu;
        }
 ​
         // 构造函数允许设置默认值
         public Stuff(){
             projectileA = 1;
             projectileB = 1;
             projectileC = 1;
        }
    }
 ​
     // 创建这个类的一个实例,类名后面的括号表面使用的是构造函数,类或struct可能有多个构造函数,
     public Stuff myStuff = new Stuff(50,5,5);
     // 根据参数不同匹配不同的构造函数
     public Stuff myOtherStuff = new Stuff(50, 1.5f);
     void Start()
    {
         Debug.Log("myStuff.projectileA");
    }
 }

23.Instantiate 实例

用于克隆游戏对象,常用于克隆prefab(预配置对象),但这样实例的对象只会在原地出现

     public Rigidbody projectile;
     void Update()
    {
         if(Input.GetButtonDown("Firel")){
             Instantiate(projectile);
        }
    }

创建一个空对象为实例对象赋值,这样就可以用空对象的位置和旋转

     public Rigidbody projectile;
     public Transform barrelEnd;
     void Update()
    {
         if (Input.GetButtonDown("Fire1"))
        {
             Instantiate(projectile, barrelEnd.position, barrelEnd.rotation);
        }
    }

一般而言instantiate会返回一个名为object的类型,但为了投射并给添加作用力,需要将这个类型强制转换为Rigidbody(as Rigidbody),然后将值存储在Rigidbody变量中(projectileInstance)

 Rigidbody projectileInstance;
 projectileInstance=Instantiate(projectile, barrelEnd.position, barrelEnd.rotation) as Rigidbody;
 projectileInstance.AddForce(barrelEnd.up * 350f);

实例的对象会一直存在在场景中,可以写一个脚本定时销毁

     void Start()
    {
         Destroy (gameObject, 1.5f);
    }

24.Arrays 数组

     int[] muIntArray=new int[5];
     void Start()
    {
         muIntArray[0] = 12;
         muIntArray[1] = 43;
         muIntArray[2] = 56;
         muIntArray[3] = 47;
         muIntArray[4] = 32;
    }

没有明确声明长度,长度由大括号中的元素数量决定

 int[] myIntArray = { 12, 43, 56, 47, 32 };

如果设置为public则可以在Inspector中看到这个数组并为其赋值

 public GameObject[] players;

我们希望players数组储存场景中的所有玩家,FindGameObjectsWithTag返回场景中带有指定标记的

  players = GameObject.FindGameObjectsWithTag("Player");

使用循环可以很轻松遍历数组中的值

         for(int i=0;i<players.Length;i++){
             Debug.Log("PlayerPrefs Number " + i + " is named " + players[i].name);
        }

25.Invoke 延时

将函数调用延迟,函数名称、延时时长

只有不包含参数,且返回类型为void的方法才能被Invoke调用

     public GameObject target;
     void Start()
    {
         Invoke("SpawObject", 2);
    }
     void SpawObject(){
         Instantiate(target, new Vector3(0, 2, 0), Quaternion.identity);
    }

如果想反复调用可以使用InvokeRepeating,第三个参数为调用时间间隔

 public class A25_InvokeRepeating : MonoBehaviour
 {
     public GameObject target;
     void Start()
    {
         InvokeRepeating("SpawObject", 2,1);
    }
     void SpawObject()
    {
         // 随机x、z的坐标
         float x = Random.Range(-2.0f, 2.0f);
         float z = Random.Range(-2.0f, 2.0f);
         Instantiate(target, new Vector3(x, 2, z), Quaternion.identity);
    }
 }

停止调用

 CancelInvoke("SpawObject");

26.枚举

枚举可以在类内外创建,也可以创建只包含次枚举的C#脚本,不讲它声明为类,而是将它声明为枚举,然后可以在其他脚本的类中使用这个枚举

 enum Direction { North,East,South,West}

North下标为0,East为1,同时可以赋值

 enum Direction { North=10,East=11,South=12,West=13}

还可以更改枚举中常量的类型,可以改为任意整数类型

 enum Direction : short{ North,East,South,West}
 ​
 public class A26_CardinalDirection : MonoBehaviour
 {
     enum Direction { North,East,South,West}
     void Start()
    {
         Direction myDirection;
         myDirection = Direction.North;
    }
     Direction ReverseDirection(Direction dir)
    {
         if (dir == Direction.North)
             dir = Direction.South;
         else if (dir == Direction.South)
             dir = Direction.North;
         else if (dir == Direction.East)
             dir = Direction.West;
         else if (dir == Direction.West)
             dir = Direction.East;
 ​
         return dir;
    }
 }

27.Switch

将单一变量与一系列常量进行对比

 public class A27_ConversationScript : MonoBehaviour
 {
     public int intelligence = 5;
 ​
     void Greet()
    {
         switch (intelligence)
        {
             case 5:
                 print("Why hello there good sir! Let me teach you about Trigonometry!");
                 break;
             case 4:
                 print("Hello and good day!");
                 break;
             case 3:
                 print("Whadya want?");
                 break;
             case 2:
                 print("Grog SMASH!");
                 break;
             case 1:
                 print("Ulg, glib, Pblblblblb");
                 break;
             default:
                 print("Incorrect intelligence level.");
                 break;
        }
    }
 }

THE END