[Unity3D] C# Basic : Gameplay Scripting
教程:https://unity3d.com/cn/learn/tutorials/s/scripting
补充:http://www.runoob.com/csharp/csharp-inheritance.html
C#复习结合Unity3D复习笔记,用时一天。
syntax 语法
perpendicular 垂直
parenthesis 圆括号
pivot 旋转轴
Ternary 三元的
generic 通用类
Polymorphism 多态
Beginner Gameplay Scripting
1 Scripts as Behaviour Components 2 Variables and Functions 3 Conventions and Syntax 4 C# vs JS syntax 5 IF Statements 6 Loops 7 Scope and Access Modifiers 8 Awake and Start 9 Update and FixedUpdate 10 Vector Maths 11 Enabling and Disabling Components 12 Activating GameObjects 13 Translate and Rotate 14 Look At 15 Linear Interpolation 16 Destroy 17 GetButton and GetKey 18 GetAxis 19 OnMouseDown 20 GetComponent 21 Delta Time 22 Data Types 23 Classes 24 Instantiate 25 Arrays 26 Invoke 27 Enumerations 28 Switch Statements
01.
public class NewBehaviourScript : MonoBehaviour { void Update() { if (Input.GetKeyDown(KeyCode.R)) { GetComponent<Renderer>().material.color = Color.red; } ... } }
05.
ranslation = Time.deltaTime
07.
C#中五种访问修饰符作用范围 public、private、protected、internal、protected internal
08.
Awake()可用于object的初始化。
public class AwakeAndStart : MonoBehaviour { void Awake () { Debug.Log("Awake called."); } void Start () { Debug.Log("Start called."); } }
09.
Unity3D--Update和FixedUpdate的区别与共性
10.
向量计算再议。
11, 12.
myLight.enabled = True; GameObject.SetActive(false);
13.
using UnityEngine; using System.Collections; public class TransformFunctions : MonoBehaviour { public float moveSpeed = 10f; public float turnSpeed = 50f; void Update () { if(Input.GetKey(KeyCode.UpArrow)) transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime); // Or 采用fixedupate if(Input.GetKey(KeyCode.DownArrow)) transform.Translate(-Vector3.forward * moveSpeed * Time.deltaTime); if(Input.GetKey(KeyCode.LeftArrow)) transform.Rotate(Vector3.up, -turnSpeed * Time.deltaTime); if(Input.GetKey(KeyCode.RightArrow)) transform.Rotate(Vector3.up, turnSpeed * Time.deltaTime); } }
14.
// 摄像头貌似没动
Transform target;
transform.LookAt(target);
15.
插值的典型应用,光线的逐渐变化。
/* * 根据比例找到中间的一个点 */ float result = Mathf.Lerp (3f, 5f, 0.5f); Vector3 from = new Vector3 (1f, 2f, 3f); Vector3 to = new Vector3 (5f, 6f, 7f); // Here result = (4, 5, 6) Vector3 result = Vector3.Lerp (from, to, 0.75f); /* * 变化比例,从而移动这个中间的点 */ light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f); light.intensity = Mathf.Lerp(light.intensity, 8f, 0.5f * Time.deltaTime);
16.
Destroy(gameObject); // destroy object Destroy(GetComponent<MeshRenderer>()); // destroy component
17.
bool down = Input.GetButtonDown("Jump"); bool held = Input.GetButton("Jump"); bool up = Input.GetButtonUp("Jump");
准备 |
按下 |
按完 |
按完 |
结束 |
|
GetButtonDown |
false |
true |
false |
false |
false |
GetButton |
false |
true |
true |
false |
false |
GetButtonUp |
false |
false |
false |
true |
false |
18.
看样子是input的全局性的设置,待日后理解。
public class AxisExample : MonoBehaviour { public float range; public GUIText textOutput; void Update () { float h = Input.GetAxis("Horizontal"); float xPos = h * range; transform.position = new Vector3(xPos, 2f, 0); textOutput.text = "Value Returned: "+h.ToString("F2"); } }
19.
鼠标事件,以及在gravity模式下的“加力打击”效果。
public class MouseClick : MonoBehaviour { void OnMouseDown () // 当object收到鼠标点击事件时 { rigidbody.AddForce(-transform.forward * 500f); //受力方向 rigidbody.useGravity = true; } }
OnMouseDown (脚本参考)
OnMouseDrag (脚本参考)
OnMouseEnter (脚本参考)
OnMouseExit (脚本参考)
OnMouseOver (脚本参考)
OnMouseUp (脚本参考)
OnMouseUpAsButton (脚本参考)
24.
public class UsingInstantiate : MonoBehaviour { public Rigidbody rocketPrefab; public Transform barrelEnd; void Update () { if(Input.GetButtonDown("Fire1")) { Rigidbody rocketInstance; rocketInstance = Instantiate(rocketPrefab, barrelEnd.position, barrelEnd.rotation) as Rigidbody; rocketInstance.AddForce(barrelEnd.forward * 5000); } } }
子弹在一定时间后可以销毁掉。
public class RocketDestruction : MonoBehaviour { void Start() { Destroy (gameObject, 1.5f); } }
25.
public class Arrays : MonoBehaviour { public GameObject[] players;
//pubiic int[] myIntArray = new int[5]; void Start () { players = GameObject.FindGameObjectsWithTag("Player");
for(int i = 0; i < players.Length; i++) { Debug.Log("Player Number "+i+" is named "+players[i].name); } } }
27.
public class EnumScript : MonoBehaviour { enum Direction {North, East, South, West}; dir = Direction.South; ... }
28.
switch (intelligence) { case 5: print ("Why hello there good sir! Let me teach you about Trigonometry!"); break; ... default: print ("Incorrect intelligence level."); break; }
Intermediate Gameplay Scripting
Properties
Ternary Operator
Statics
Method Overloading
Generics
Inheritance
Polymorphism
Member Hiding
Overriding
Interfaces
Extension Methods
Namespaces
Lists and Dictionaries
Coroutines
Quaternions
Delegates
Attributes
Events
1.
属性:可以理解为针对某一个变量的小函数。
public class Player { private int experience; public int Experience { get { //Some other code return experience; } set { //Some other code experience = value; } } public int Level { get { return experience / 1000; } set { experience = value * 1000; } } }
使用环境:
using UnityEngine; using System.Collections; public class Game : MonoBehaviour { void Start () { Player myPlayer = new Player(); //Properties can be used just like variables myPlayer.Experience = 5; int x = myPlayer.Experience; } }
2.
??运算符,后面跟着的是一个“保险值”。
class NullCoalesce { static int? GetNullableInt() { return null; } static string GetStringValue() { return null; } static void Main() { int? x = null; // Set y to the value of x if x is NOT null; otherwise, // if x == null, set y to -1. int y = x ?? -1; // Assign i to return value of the method if the method's result // is NOT null; otherwise, if the result is null, set i to the // default value of int. int i = GetNullableInt() ?? default(int); string s = GetStringValue(); // Display the value of s if s is NOT null; otherwise, // display the string "Unspecified". Console.WriteLine(s ?? "Unspecified"); } }
5.
设计一个通用方法:
using UnityEngine; using System.Collections; public class SomeClass { public T GenericMethod<T>(T param) { return param; } }
SomeClass myClass = new SomeClass(); myClass.GenericMethod<int>(5);
设计一个通用类:
public class GenericClass <T> { T item; public void UpdateItem(T newItem) { item = newItem; } }
GenericClass<int> myClass = new GenericClass<int>(); myClass.UpdateItem(5);
6, 7
public class Apple : Fruit { public Apple():base() // 调用基类的构造方法 { color = "red"; Debug.Log("1st Apple Constructor Called"); }
}
base.GetInfo(); //调用基类的方法,显示Andy.
本部分介绍以下访问关键字:
-- 代码执行 --
MyDerivedClass myder = new MyDerivedClass(); MyDerivedClass myder2 = new MyDerivedClass(5);MyDerivedClass myder3 = new MyDerivedClass(5, 6);
-- 构造函数执行顺序 --
100 // 1,然后因为2中的this(54), 所以将要调用3【注意,这个子类的构造函数是个static】
Invoke parent class with one param, i=54 // 因为base(i),调用父类
MyDerivedClass(int i) // 3
54 // 3
102 // 3
101 // this(54)结束,返回2
Invoke parent class with one param, i=5 // 调用3,调用base(5)
MyDerivedClass(int i) // 3
5 // 3
102 // 3
Invoke parent class without param // 没有写base,所以调用默认的父类构造函数
Two param. i=5, j=6 // 4
子类:
namespace ConsoleApplication1 { public class MyDerivedClass : MyBaseClass { public int age; public static int age2; static MyDerivedClass() // 1 { age2 = 100; Console.WriteLine(age2); } public MyDerivedClass(): this(54) // 2 { age = 101; Console.WriteLine(age); } public MyDerivedClass(int i): base(i) // 3 { age = 102; Console.WriteLine("MyDerivedClass(int i)"); Console.WriteLine(i); Console.WriteLine(age); } public MyDerivedClass(int i, int j) // 4 { Console.WriteLine("Two param. i={0:D}, j={1:D}", i, j); } } }
Polymorphism,多态
Fruit myFruit = new Apple(); Apple myApple = (Apple)myFruit;
8, 9
static void Main(string[] args) { B b = new B(); b.ClassA(); A a = b; // -->a a.ClassA(); Console.WriteLine("\n"); B2 b2 = new B2(); b2.ClassA2(); A2 a2 = b2; // -->b a2.ClassA2(); Console.ReadKey(); }
-->a:
class B : A { new public void ClassA() { Console.WriteLine("B.ClassA()"); } }
-->b:
A中方法用virtual
B中对应方法override
10.
通过接口实现多继承。
public interface IKillable { void Kill(); } public interface IDamageable<T> { void Damage(T damageTaken); }
public class Avatar : MonoBehaviour, IKillable, IDamageable<float> { //The required method of the IKillable interface public void Kill() { //Do something fun } //The required method of the IDamageable interface public void Damage(float damageTaken) { //Do something fun } }
11.
//It is common to create a class to contain all of your //extension methods. This class must be static. public static class ExtensionMethods { //Even though they are used like normal methods, extension //methods must be declared static. Notice that the first //parameter has the 'this' keyword followed by a Transform //variable. This variable denotes which class the extension //method becomes a part of. public static void ResetTransformation(this Transform trans) { trans.position = Vector3.zero; trans.localRotation = Quaternion.identity; trans.localScale = new Vector3(1, 1, 1); } }
public class SomeClass : MonoBehaviour { void Start () { //Notice how you pass no parameter into this //extension method even though you had one in the //method declaration. The transform object that //this method is called from automatically gets //passed in as the first parameter. transform.ResetTransformation(); } }
参考:C#中的扩展方法学习总结
过去的思路是:我们首先需要获得某个类的源代码,然后在这个类代码中增加成员方法,这样就可以达到为一个类提供扩展方法的目的。
可是不幸地是,这种方法在没有源代码的情况下就无法奏效了,而且我们人为地去改变源代码有可能会破坏整个代码的稳定性。
那么有没有一种方法能在不改变源代码的前提下为某个类提供扩展方法呢?这就是扩展方法。
第一个参数必须是使用this关键字指明要实现扩展方法的类。
例如:
public static void SetPositionZ(this Transform tran, float z) { tran.position = new Vector3(tran.position.x, tran.position.y, z); } transform.SetPositionZ(1.0f); // <-- 如此用就方便些
// 属性就是函数名,真正的参数就是第二个参数
12.
using io = System.IO; // 给命名空间 System.IO 定义了一个别名,叫io io.File.Create();
除了防止不同开发人员类名命名重复外,而且:
超出{}范围外,using后的对象就被释放,我们就不需要再写connction.close()了。
有点Python中的with...as的意思。
//超出{}范围外,using后的对象就被释放 using (SqlConnection connection = new SqlConnection(connectionString)) { connection.open(); }
13.
列表:
using System.Collections.Generic; public class SomeClass : MonoBehaviour { void Start () { List<BadGuy> badguys = new List<BadGuy>(); badguys.Add( new BadGuy("Harvey", 50)); badguys.Sort(); foreach(BadGuy guy in badguys) { print (guy.name + " " + guy.power); } badguys.Clear(); } }
字典:
using System.Collections.Generic; public class SomeOtherClass : MonoBehaviour { void Start () { Dictionary<string, BadGuy> badguys = new Dictionary<string, BadGuy>(); BadGuy bg1 = new BadGuy("Harvey", 50); badguys.Add("gangster", bg1); BadGuy magneto = badguys["mutant"]; //提取//This is a safer, but slow, method of accessing //values in a dictionary. if(badguys.TryGetValue("birds", out temp)) { //success! } else { //failure! } } }
补充:
尽管 ref 和 out 在运行时的处理方式不同,但在编译时的处理方式相同。
因此,如果一个方法采用 ref 参数,而另一个方法采用 out 参数,则无法重载这两个方法。
例如,从编译的角度来看,以下代码中的两个方法是完全相同的,因此将不会编译以下代码:
class CS0663_Example { public void SampleMethod(ref int i) { } // <---- 必须在传递之前进行初始化 public void SampleMethod(out int i) { } }
ref和out最主要的区别是:
- ref将参数的参数值和引用都传入方法中,所以ref的参数的初始化必须在方法外部,进行,也就是ref的参数必须有初始化值,否则程序会报错;【有进有出】
- out不会将参数的参数值传入方法中,只会将参数的引用传入方法中,所以参数的初始化工作必须在其对用方法中进行,否则程序会报错;【只出没进】
14.
协程:Coroutines
.Net为了简化IEnumerator的实现,引入了yield语句。
- 简单来说,yield return用于在每次迭代时返回一个元素,这个元素可以通过IEnumerator.Current属性访问;
- 同时,yield语句会保留迭代器的当前状态,当下次迭代时,保证状态连续性。
理解一:C#中的迭代器:C#--IEnumerable 与 IEnumerator 的区别
static void Main(string[] args) {
// 1. define array ArrayList arrStus = new ArrayList { new Student("1313001", "liuliu"," little rabbit"), new Student("1313002", "zhangsan", "little tortoise") };
// 2. List<T> 继承了IEnumerable<T>, IEnumerble<T>继承了IEnumerable. List<Student> stuL = ArrListToArr<Student>(arrStus);
foreach(Student stu in stuL) { Console.WriteLine($"{ stu.Name + " " + stu.Id + " " + stu.Remarks }"); }; } static List<T> ArrListToArr<T>(ArrayList arrL) { List<T> list = new List<T>();
// 3. ArrList 定义时已继承了IEnumerable IEnumerator enumerator = arrL.GetEnumerator(); while (enumerator.MoveNext()) { T item = (T)(enumerator.Current); list.Add(item); } return list; }
理解二:进一步的,unity中协程的使用。
执行顺序是12435
- 执行到4协程注册了事件,控制权交给外部线程;
- 外部线程执行到3;
- 事件发生,程序分段执行机制goto到协程处记录了堆栈信息执行5语句。
voidStart() { print("Starting " +Time.time);---------------------------------------1 StartCoroutine(WaitAndPrint(2));-------------------------------------2 print("Done " +Time.time);-------------------------------------------3 } IEnumerator WaitAndPrint(float waitTime) { yield return new WaitForSeconds(waitTime);---------------------------4 print("WaitAndPrint " + Time.time);----------------------------------5 }
执行顺序是12453
- 程序执行到4,执行yield return表达式注册事件交出控制权给外部,
- 因为外部还要交出控制权也需要执行yield return后面的表达式语句因此会重入WaitAndPrint函数接着协程当前状态下一步执行所以执行到5,
- yield return 后面表达式语句执行完毕控制权完全交出,之后才执行3,
- 根本原因是yield return 不能直接嵌套,后面需要跟一个表达式(事件)。
IEnumerator Start() { print("Starting " +Time.time);----------------------------------------1 yield return StartCoroutine(WaitAndPrint(2.0F));----------------------2 print("Done " +Time.time);--------------------------------------------3 } IEnumerator WaitAndPrint(float waitTime) { yield return new WaitForSeconds(waitTime);----------------------------4 print("WaitAndPrint " + Time.time);-----------------------------------5 }
15.
参考:【Unity技巧】四元数(Quaternion)和旋转
16.
理解为:函数指针,可以把方法当作参数传递。
与C或C++中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。
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); } }
如果是函数指针数组的角色:
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; } }
17.
public class SpinScript : MonoBehaviour { [Range(-100, 100)]
public int speed = 0; void Update () { transform.Rotate(new Vector3(0, speed * Time.deltaTime, 0)); } }
[ExecuteInEditMode] public class ColorScript : MonoBehaviour { void Start() { renderer.sharedMaterial.color = Color.red; } }
Unity中默认情况下,脚本只有在运行的时候才被执行,加上此属性后,不运行程序,也能执行脚本。
18.
Event为喜爱他的观众(具有相同函数类型的函数)提供了订阅他的途径(即把自身加入到event的函数容器中),
这样无论他有什么动向,都可以直接通知所有他知道的粉丝(调用event会立即引用所有函数容器中的函数)。
event只负责告诉每个函数什么时候被调用,这些函数到底干了什么,event并不关心。
public class Idol : MonoBehaviour {
public delegate void IdolBehaviour(string behaviour); public static event IdolBehaviour IdolDoSomethingHandler; private void Start() { //Idol 决定搞事了, 如果他还有粉丝的话, 就必须全部都通知到 if (IdolDoSomethingHandler != null) { IdolDoSomethingHandler("Idol give up writing."); } } }
提前订阅了Idol:
public class SubscriberA : MonoBehaviour {
/// OnEnable在该脚本被启用时调用,你可以把它看做路转粉的开端 private void OnEnable() { Idol.IdolDoSomethingHandler += LikeIdol; } /// OnEnable在该脚本被禁用时调用,你可以把它看做粉转路的开端 private void OnDisable() { Idol.IdolDoSomethingHandler -= LikeIdol; }
/// 粉丝A是一个脑残粉 public void LikeIdol(string idolAction) { print(idolAction + " I will support you forever!"); } }
提前订阅了Idol:
public class SubscriberB : MonoBehaviour { /// OnEnable在该脚本被启用时调用,你可以把它看做路转粉的开端 private void OnEnable() { Idol.IdolDoSomethingHandler += HateIdol; } /// OnEnable在该脚本被禁用时调用,你可以把它看做粉转路的开端 private void OnDisable() { Idol.IdolDoSomethingHandler -= HateIdol; } /// 粉丝B是一个无脑黑 public void HateIdol(string idolAction) { print(idolAction + " I will hate you forever!"); } }
Unity Editor 和 Unity Engine的区别
前者是针对编辑器本身提供一些便于开发者编辑Unity物体的编程;后者则是具体到Unity要实现什么软件内容的编程。
- UnityEditor是Unity编辑器编程,比如你要开发一个工具,一键给场景中所有的物体添加碰撞体,那么就可以用UnityEditor来开发这个工具,这个工具最简单的一个实现形态就是从Unity编辑器上方菜单栏拓展一个按钮,当按下这个按钮的时候,场景中所有的物体都会自动添加上碰撞体,相当于你手动给所有的物体添加碰撞体,注意这个时候场景是非运行状态(即编辑状态)。
- UnityEngine是Unity编程,就是指你的具体软件内容开发了,比如你写了一个脚本功能是向前移动,那么你把这个脚本挂在一个Cube上面,当你的场景运行时Cube就会先前移动,注意这个时候场景是运行状态而非编辑状态了。