访问修饰符
-
public:可以在Inspector的组件中编辑和显示,简而言之就是可以在编辑器里面看到和编辑,并且可以跨脚本使用
-
private:只能在这个类里面使用,无法显示在编辑器上,且不能跨脚本使用
Awake()和Start()
-
Awake():初始化操作
-
Start():在Awake()后面进行,是在这个脚本调用时才会执行
注意:把脚本挂在一个GameObject上面,如果没有启用这个组件(脚本),只会调用Awake()函数
Update()和FixedUpdate()
两者的不同:FixUpdate()时间间隔是固定的,Update()可能每一帧的时间间隔不同
-
Update()常见使用例子:
-
移动非物理物体
-
简单的计时器
-
接受输入
-
FixedUpdate()常见使用例子:
-
任何影响刚体(即物理对象)的动作
-
使用力来定义移动
注意:ctrl+shift+M启动引导,可以快速调用自带的函数
矢量数学
注意:Z轴是朝里面的
暂时无法在飞书文档外展示此内容
注意:但是在二维空间中,z轴正方向指向的是屏幕外面
启用和禁用组件
public class EnableComponent:MonoBehavior
{
private Light myLight;
void start()
{
myLight=GetComponent<Light>();
}
void Update()
{
if(Input.GetKeyUp(KeyCode.space))
{
myLight.enabled=!myLight.enabled; //按下空格键时如果组件正在启用就禁用,如果正在禁用就启用
}
}
}
也可以启用和禁用脚本
激活游戏对象
父对象停止激活同样也会停用子对象,但是子对象依然处于活跃状态
public class ActiveObjects:MonoBehaviour
{
void Start()
{
gameObject.setActive(false);
}
}
平移和旋转
public class TransfomFunctions:MonBehaviour
{
public float movespeed=10f;
public float turnSpeed=50f;
void Update()
{
//平移
transform.Translate(vector3.forward*movespeed*Time.deltatime);//逐帧运动
if(Input.GetKey(KeyCode.UpArrow))//按键进行移动
transform.Translate(Vector3.forward*movement*Time.daltatime);
transform.position += new Vector3(Input.GetAxis("Horizontal") * speed * Time.deltaTime, Input.GetAxis("Vertical") * speed * Time.deltaTime, 0);
//这种方法也可以实现按键进行移动
//旋转
if(Input.GetKey(KeyCode.LeftArrow))
transform.Rotate(Vector3.up,-turnSpeed*Time.deltatime);//第一个参数是确定绕着哪个轴
}
}
注意:
-
Vector3.forward
:相对于世界坐标系的前方向,固定为(0, 0, 1)
。 -
transform.forward
:相对于物体自身的前方向,随物体旋转而改变。
LookAt
作用:将旋转朝向target
public class CameraLookAt:MonoBehaviour
{
public Transform target;
void Update()
{
transform.LookAt(target);
}
}
比如说我想让摄像机始终对准一个物体,我就可以这样去做,我可以把场景中的一个GameObject直接拖到这个变量上面,unity会自动处理这种关系,Transform是GameObject上的一个组件,unity会自动找到这个GameObject上面的Transform组件,Rigid也是同理
Destroy
销毁自身:
Destroy(gameObject);
//这里的 gameObject 是指当前脚本所附加的那个对象。
销毁某个GameObject:
public class DestroyComponent:MonoBehaviour
{
public GameObject other;
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destroy(other);
}
}
}
销毁组件:
public class DestroyComponent:MonoBehaviour
{
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destory(GetComponent<MeshRenderer>());
}
}
}
延时销毁:
public class DestroyComponent:MonoBehaviour
{
void Update()
{
if(Input.GetKey(KeyCode.Space))
{
Destory(gameObject,3f); //意思是延时3秒销毁自身
}
}
}
gameobject的大小写问题:
-
小写的
gameObject
是指当前脚本所在的对象实例,必须使用小写的gameObject
。 -
大写的
GameObject
是类名,不能直接传递给Destroy()
函数。
GetButton和GetKey
1. Input.GetKey
:
-
用途:检测特定键盘键是否被按下。
-
检测方式:直接检测某个物理键(例如 "W"、"Space" 键等)的按下状态。
-
使用方法:
if (Input.GetKey(KeyCode.Space)) { // 检测到空格键被按下 }
-
Input.GetKey(KeyCode)
:你需要传递一个KeyCode
枚举值来检测某个键的按下状态。 -
例子:
-
-
常见场景:用于检测具体的键盘按键,比如方向键、字母键等。
2. Input.GetButton
:
-
用途:检测由**输入管理器(Input Manager)**设置的按钮是否被按下。
-
检测方式:通过输入管理器中定义的按钮名称检测输入,常用于处理虚拟按钮,例如游戏手柄的按钮、键盘按键等。
-
使用方法:
if (Input.GetButton("Jump")) { // 检测到跳跃按钮被按下 }
-
Input.GetButton("ButtonName")
:你需要传递一个在输入管理器中设置的按钮名称字符串。 -
例子:
-
-
常见场景:适合处理复杂的输入,比如需要支持游戏手柄和键盘同时操作的情况下。通过输入管理器,你可以绑定多个物理按键到同一个虚拟按钮。例如,你可以将 "Jump" 映射到键盘的 "Space" 和游戏手柄的 "A" 按钮。
主要区别:
-
输入方式:
-
GetKey
:直接检测物理键(通过KeyCode
)。 -
GetButton
:检测由输入管理器配置的虚拟按钮名称,可以同时映射多个物理输入。
-
-
灵活性:
-
GetKey
更直接,用于具体的键检测。 -
GetButton
更灵活,可以处理跨设备的输入映射,适合复杂输入设置。
-
总结:
-
Input.GetKey
用于检测具体的键盘按键,比如直接检测“W”键是否被按下。 -
Input.GetButton
则通过输入管理器配置,用于检测虚拟按钮,适用于跨设备输入,例如同时支持键盘和手柄的跳跃操作。
如果你的游戏只需要简单的键盘输入,使用 GetKey
更为简单直接。如果你需要支持键盘、手柄等多种输入设备,GetButton
是更好的选择,因为它更具扩展性。
这个Name就是GetButton后面要写的东西
GetButtonDown,GetButton和GetButtonUp的区别:
第一次按下,第一帧返回 |
随着帧数的增加 |
松开按钮那一帧 | |
GetButtonDown |
True |
False |
False |
GetButton |
True |
True |
False |
GetButtonUp |
False |
False |
True |
GetAxis
Negative Button和Positive Button的区别:
Positive Button
:用于表示轴的正向输入(+1),例如向右或向上。
Negative Button
:用于表示轴的负向输入(-1),例如向左或向下。
Gravity和Sensitivity
Gravity
:用于表示Axis值变为0的速度,这个值越大,速度越快
Sensitivity
:与上面的相反,这个表示到达1或-1的速度,这个值越大,速度越快
GetAxis和GetAxisRaw的区别
-
GetAxis
GetAxis
返回的是一个平滑过渡的值,就像是输入信号经过了“减震器”处理,让它变得更加柔和和平滑。它会在按下和松开按键时逐渐增加或减少输入值,因此适合需要流畅控制的情况。
-
值范围:
-1
到1
,中间值会逐渐变化。 -
平滑过渡:如果你按住按键(例如方向键),
GetAxis
的值会从0
逐渐增加到1
,松开时也会逐渐回到0
。这种平滑的变化适合像角色移动或者摄像机视角这样的场景,避免突然跳动。
举个例子: 假设你驾驶一辆汽车,当你踩油门时,GetAxis
的输入就像逐渐加速一样,你的速度会慢慢上升,而不是突然达到最大速度。同样,松开油门时,它会慢慢减速。
-
GetAxisRaw
GetAxisRaw
则更直接,它返回的值没有经过平滑处理,是“生硬的”输入结果。按下按键时,它立即返回 -1
、0
或 1
,不会有中间值。适合需要精确和立即响应的场合,比如移动的方向控制。
-
值范围:也在
-1
到1
之间,但会立即跳到对应值。 -
没有平滑过渡:按下按键时,
GetAxisRaw
的值直接从0
跳到1
,松开时直接跳回0
,没有渐变过程。这适合需要快速反应的场景,比如按键事件或者菜单导航。
举个例子: 当你在玩一个经典的街机游戏时,按下方向键时角色会立即移动,不会有任何延迟或平滑过渡。这种“立刻反应”的效果就是 GetAxisRaw
的表现。
总结对比
-
GetAxis
:值是平滑过渡的,按键响应是渐进的,适合需要控制速度或者平滑运动的情况。 -
GetAxisRaw
:值是即时的,按键响应是立刻的,适合需要快速、直接响应的情况。
Dead和Snap
1. Dead(死区)
-
作用:
Dead
是输入的死区,用来忽略轴输入的微小值,确保设备在微小偏移时不会被认为是有效输入。 -
值范围:通常在 0 到 1 之间,代表了一个输入轴在这个范围内的值将被视为 0。
-
用途:当使用模拟输入设备(如操纵杆或游戏手柄)时,由于它们可能会存在非常小的偏移或抖动,
Dead
参数可以确保这些微小的无意输入不会触发动作。这非常有用,避免了角色因为设备敏感度太高而在游戏中产生不必要的移动。
例子: 如果你设置 Dead
为 0.1
,那么任何输入轴的值在 -0.1
到 0.1
之间的数值都会被视为 0,输入将不会影响物体的运动。
2. Snap(轴复位/快速切换)
-
作用:
Snap
用于控制输入从正方向切换到负方向时,是否应立即将输入值重置为 0。它可以帮助处理按键方向的快速切换(例如从按下右方向键立即切换到左方向键)。 -
值:
Snap
是一个布尔值(true
或false
)。如果启用了Snap
(即设置为true
),当输入从一个方向(如正方向)突然切换到另一个方向(如负方向)时,输入轴的值会快速归零,而不会缓慢过渡。
例子: 假设你有一个水平轴,Positive Button
是“D”键(向右),Negative Button
是“A”键(向左):
-
如果
Snap
为true
,当你松开“D”键然后立即按下“A”键时,输入值会瞬间从+1
切换到-1
,中间不会有过渡。 -
如果
Snap
为false
,输入值会平滑过渡,先从+1
逐渐减小到 0,再从 0 变为-1
。
结合例子理解
假设你有一个游戏角色的水平移动,通过 Input.GetAxis("Horizontal")
来控制它:
-
Dead:如果你设置了一个较大的
Dead
区(比如0.2
),那么轻微的输入(比如手柄轻微晃动)将不会导致角色移动,只有超出这个死区的输入才会生效。 -
Snap:如果你想让角色在从左移动立即切换到右移动时没有过渡效果,启用
Snap
可以让这种切换更加迅速。
但是需要如下代码:
void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
}
OnMouseDown
OnMouseDown()
是 Unity 中的一种事件函数,它用于检测当用户在游戏对象上按下鼠标按钮时触发的行为。
具体来说,当用户用鼠标**点击(按下)**带有碰撞器(Collider)的游戏对象时,Unity 会自动调用这个方法,你可以在这个方法里定义相应的逻辑来响应鼠标点击事件。
使用场景:
OnMouseDown()
常用于实现一些点击互动效果,比如点击对象后改变它的颜色、销毁对象、打开菜单等。
基本用法:
public class Example : MonoBehaviour
{ // 当用户点击当前对象时会触发此方法
void OnMouseDown()
{// 执行点击后的操作
Debug.Log("Object clicked!");
}
}
注意事项:
-
对象需要有碰撞器:
OnMouseDown()
只能在带有碰撞器(Collider
或Collider2D
)的游戏对象上起作用。碰撞器可以是立方体、球体、胶囊体等。-
例如,如果你有一个 3D 对象,你需要给它添加一个
BoxCollider
、SphereCollider
等。如果是 2D 游戏对象,则需要添加Collider2D
。
-
-
对象需要在摄像机视野内: 对象必须在相机的视野范围内,才能检测到鼠标点击。如果对象不可见或摄像机没有对准对象,它将无法被点击到。
-
相机需要启用物理光线投射(Raycasting): 默认情况下,摄像机的设置允许物理光线投射(Raycasting),这意味着 Unity 可以检测到鼠标点击的对象。如果你有多个摄像机,确保主摄像机启用了光线投射。
-
适用于所有鼠标按钮:
OnMouseDown()
响应的是任何鼠标按键的按下事件(左键、右键或中键)。但默认情况下,它通常响应鼠标左键的点击。
示例:点击对象后销毁它
public class DestroyOnClick : MonoBehaviour
{
void OnMouseDown()
{// 销毁当前对象
Destroy(gameObject);
}
}
在这个示例中,当用户点击附加了这个脚本的对象时,OnMouseDown()
会被调用,接着 Destroy(gameObject)
会销毁该对象。
示例:给点击的物体施加一个力
public void MouseClick:MonoBehaviour
{
private RigitBody rb;
private void Awake()
{
rb=GetComponent<RigitBody>();
}
void OnMouseDown()
{
rb.AddForce(-transform.forward*500f);
rb.useGravity=true;
}
}
OnMouseDown()
是 Unity 中处理鼠标点击事件的便捷方法,当用户点击一个带有碰撞器的对象时,会触发这个方法。在游戏开发中,它常用于创建交互,比如点击物体来触发动画、销毁物体、打开对话框等。
GetComponent
可以调用自身或其他GameObject的组件或附加的脚本
public class UsingOtherComponent:MonoBehaviour
{
public GameObject otherGameObject;
private AnotherScript anotherScript; //以这个脚本名称为类型来命名
private YetAnotherScript yetAnotherScript;
void Awake()
{
anotherScript=GetComponent<AnotherScript>();
yetAnotherScript=otherGameObject.GetComponent<YetAnother>();
}
void Start()
{
//
}
需要注意:GetComponent本身会占用大量处理能力,所以最好在Awake()或者Start()中调用一次
DeltaTime
定义:
指的是两次更新或者固定更新函数调的间隔时长,但是每一帧的间隔时间是不同的,就像你打王者帧数是不一样的
void Update()
{
if (!isDead)
{
transform.position += new Vector3(Input.GetAxis("Horizontal") * speed * Time.deltaTime, Input.GetAxis("Vertical") * speed * Time.deltaTime, 0);
}
}
就像上面这样,
Time.deltaTime
是每帧之间经过的时间,它保证了你的代码在不同帧率下都能保持一致的速度。如果没有 Time.deltaTime
,物体移动的距离会直接依赖于帧率,帧率高时物体会移动得更快,帧率低时移动得更慢。
使用 Time.deltaTime
可以让移动的速度与时间挂钩,而不是与帧数挂钩,保证了无论每秒渲染多少帧,物体的移动速度都保持一致,从而让运动变得平滑。
具体地说:
-
如果一帧的时间较长,
Time.deltaTime
会较大,乘以速度后弥补了由于帧率低导致的移动“跳跃”。 -
如果一帧的时间较短,
Time.deltaTime
会较小,物体只会移动很小的距离,这样即使帧率很高,物体也不会移动过快。
这样我们更改的就是每秒的值,而非每帧的值
具体例子:
transform.position += new Vector3(Input.GetAxis("Horizontal") * speed * Time.deltaTime, Input.GetAxis("Vertical") * speed * Time.deltaTime, 0);
Input.GetAxis("Horizontal") * speed
:这里的 speed
是你设定的速度,它表示物体每秒应该移动的单位量。
Time.deltaTime
:它将 speed
从“每秒移动量”转换为“每帧的移动量”。假设你的物体每秒应该移动 5
个单位,而每帧的时间间隔是 1/60
秒,那么这一帧物体的移动距离就是 5 * (1/60)
,即 0.0833
个单位。
这意味着,即使帧率不稳定,物体的总移动量仍然是基于每秒的时间,而不是每帧的次数。
数据类型:
Value:
只是复制操作
Reference:
是引用操作,对其的改变会实际影响
public class DatatypeScript:MonoBehaviour
{
void Start()
{
Vector3 currentPosition=transform.position;
currentPosition=new Vector3(0,2,0);
}
} //不会更改实际的位置
public class DatatypeScript:MonoBehaviour
{
void Start()
{
Transform tran=transform;
tran.position=new vector(0,2,0);
}
} //实际的位置也会更改
首字母大小写
在 Unity 中确实有这样一个规律,很多组件的类名与属性名是相关联的。通常,一个大写的类名代表 Unity 中的一个组件类型,而小写的属性名通常是对当前对象的那个组件实例的引用。这个规律帮助开发者更方便地访问对象上的常见组件。
具体规律解释:
-
大写的类名:通常用于表示某个组件的类型,即组件的类。例如:
-
Transform
:表示 Unity 中的Transform
组件类型,负责管理对象的位置、旋转和缩放。 -
Rigidbody
:表示物理系统中的刚体组件,用于管理物体的物理行为。 -
Collider
:表示碰撞器组件,用于物体的碰撞检测。
-
-
小写的属性名:表示当前游戏对象上该类型组件的一个实例引用。这些属性是
MonoBehaviour
类的成员变量,直接指向游戏对象上的对应组件。常见的如:-
transform
:表示当前游戏对象的Transform
组件。 -
rigidbody
:表示当前游戏对象的Rigidbody
组件。 -
collider
:表示当前游戏对象的Collider
组件。
-
这些小写的属性让你可以直接在脚本中访问游戏对象的常用组件,而不需要手动通过 GetComponent<T>()
方法获取。
举例:
void Start()
{// 当前游戏对象的 Transform 组件
transform.position = new Vector3(0, 0, 0);
// 当前游戏对象的 Rigidbody 组件
Rigidbody rb = rigidbody;
// 如果当前对象上没有刚体组件,这里可以手动获取
Rigidbody rbManual = GetComponent<Rigidbody>();
}
在上面的例子中:
-
transform
直接访问当前对象的Transform
组件。 -
rigidbody
直接访问当前对象的Rigidbody
组件。
但有些情况例外:
-
不是所有组件都有默认的属性名:虽然
Transform
、Rigidbody
等常见组件有小写的快捷属性,但是一些不常用的组件(如AudioSource
、Light
等)则需要使用GetComponent<T>()
来手动获取。
4. AudioSource audioSource = GetComponent<AudioSource>();
-
属性是只读的:这些小写的属性通常是只读的,你不能直接给它们赋值,比如你不能直接用
transform = someOtherTransform;
,只能操作它们的内部属性(如位置、旋转等)。
其他常见组件的类和属性:
-
Transform
→transform
-
Rigidbody
→rigidbody
-
Collider
→collider
-
Renderer
→renderer
-
Camera
→camera
-
Light
→light
总结:
Unity 确实有这个规律:类名(大写)表示组件类型,属性名(小写)表示当前对象上的组件实例。这些内置的快捷访问属性让你可以更方便地操作常见组件,而不必通过 GetComponent<T>()
反复查找。
Instantiate函数
public class UsingInstantiate:MonoBehaviour
{
public RigidBody projectile;
public Transform barrelend;
void Update()
{
if(Input.GetButtonDown("Fire"))
{
Instantiate(projectitle,barrelend.position,barrelend.rotation);
}
}
}
把场景中的对象拖到projectile和barrelend框框里面就可以获得这个实例的RigidBody和Transform
public class UsingInstantiate:MonoBehaviour
{
public RigidBody projectile;
public Transform barrelend;
void Update()
{
if(Input.GetButtonDown("Fire"))
{
Rigidbody projectileInstance;
projectileInstance=Instantiate(projectitle,barrelend.position,barrelend.rotation) as RigidBody;
projectileInstance.AddForce(barrelend.up*350f);//给物体实现向上发射的效果
}
}
}
数组
public class Arrays:MonoBehaviour
{
int[]myIntArray={12,76,8,947,903};
//或者
int[]myIntArray1=new int[5];
void Start()
{
myIntArray[0]=12;
myIntArray[1]=76;
myIntArray[2]=8;
myIntArray[3]=947;
myIntArray[4]=903;
}
}
public GameObject[]Players;
void Start()
{
players=GameObject.FindGameObjectsWithTag("Player");
}
Invoke
Invoke("函数名称",延迟多少秒执行);
意思是延迟一段时间执行这个函数,只有返回值是void才能这么使用
public class InvokeScript:MonoBehaviour
{
public GameObject target;
void Start()
{
Invoke("SpawnObject",2);
}
void SpawnObject()
{
Instantiate(target,new Vector3(0,2,0),Quaternion.identity);
}
}
InvokeRepeating
:
InvokeRepeating("MethodName", startDelay, repeatRate);
-
startDelay
:第一次调用该方法的延迟时间。 -
repeatRate
:之后重复调用该方法的时间间隔。
示例:
public class Example : MonoBehaviour
{void Start()
{// 延迟 2 秒后开始,每隔 1 秒调用一次 PrintMessage 方法
InvokeRepeating("PrintMessage", 2f, 1f);
}
void PrintMessage()
{
Debug.Log("This message is displayed repeatedly every second.");
}
}
在这个例子中,PrintMessage
方法将在 2 秒后第一次被调用,然后每 1 秒重复调用一次,直到取消该操作或对象销毁。
取消调用:
你可以使用 CancelInvoke()
方法来取消已经计划的 Invoke()
或 InvokeRepeating()
调用。
CancelInvoke("MethodName");
枚举
enum Direction{North,East,South,West}; //默认North是0,East是1,依次往后推
enum Direction{North=10,East=11,South=13,West=27}
void Start()
{
Direction myDirection;
myDirection=Direction.North;
}
void ReserveDirection(Direction dir)
{
if(dir==Direction.North)
dir=Direction.South;
//以此类推
.....
}
属性
属性(Property) 是一种在类或对象中用于封装字段(Field)的机制。属性允许你通过类似访问字段的方式,来读取或修改对象的值,同时可以控制对这些值的访问、验证和操作。属性不仅仅是简单的字段,它们通常带有特定的逻辑,可以在读取和设置值时进行额外的操作。
属性(Property)是什么?
属性是一个带有get和set访问器的特殊成员,它提供了一种更加安全和灵活的方式来访问对象的内部数据。属性可以理解为字段的“外包装”,它允许你在获取或设置值时加入自定义的逻辑。
形象解释:
可以把属性看作是一个**“智能字段”**,它不仅仅是一个单纯的变量,更像是带有保护和规则的门卫,你可以通过这个门卫来获取和设置值,但他可以根据需要控制、检查或者修改你获取或设置的内容。
访问器(Accessor)是什么?
访问器 是属性中的组成部分,它们是用于读取或设置属性值的特定方法。访问器包括两种类型:
-
Getter(get 访问器):用于读取属性的值。
-
Setter(set 访问器):用于设置属性的值。
通过访问器,你可以在属性被访问或修改时执行额外的逻辑,比如验证输入数据、记录日志或者限制权限。
代码示例:
这个创建的公共属性通常是私有属性的首字母大写
public class Person
{
// 这是一个私有字段,只能在类内部访问
private string name;
// 定义一个公共属性来访问私有字段
public string Name
{
// get 访问器,用于读取属性值
get
{
return name;
}
// set 访问器,用于设置属性值
set
{
name = value;
}
}
}
public class Game:MonBehaviour
{
void Start()
{
Person myPerson=new Person();
myPerson.Name="hehe";
string Text01=myPerson.Name;
}
}
解释代码:
-
name
是一个私有字段,只能在类内部访问。 -
Name
是一个公共属性,通过get
和set
访问器来控制对name
字段的访问。-
get 访问器 允许你读取
name
的值。 -
set 访问器 允许你设置
name
的值.
-
为什么要使用属性和访问器?
-
封装数据:通过属性,你可以保护类的内部数据结构,不让它们直接暴露给外部代码,从而减少出错的可能性。
-
数据验证:在
set
访问器中可以添加验证逻辑,确保属性的值在被设置时符合要求。 -
灵活性:你可以在
get
和set
访问器中添加额外的逻辑,而不改变外部访问的方式。这样在代码维护时可以保持接口的一致性。 -
只读或只写:可以通过定义只有
get
或set
的访问器来创建只读或只写的属性。例如,去掉set
访问器就会创建一个只读属性。 -
可以在里面调用函数:
public class Player
{
private int level;
public int Level
{
get
{
return experience/1000;
}
set
{
experience=value*1000;
}
}
public int health{get;set;}
}
访问器的用法
-
get 访问器:通常用于计算、返回或直接访问内部字段。它允许你在获取值时执行一些额外操作,而外部代码却不需要知道这些操作的细节。
-
set 访问器:用于控制对属性值的更改。你可以在
set
中进行数据验证、触发事件或者执行额外的操作。
总结
-
属性 是一种可以通过
get
和set
访问器来封装和控制字段的方式。 -
访问器 是属性中用于读取和修改值的特殊 方法,
get
访问器用于读取值,set
访问器用于设置值。 -
属性可以让你更安全地访问对象的内部数据,同时还能保持灵活性和可维护性。
三元运算符
message=health>0?"Player is Alive":"Player is Dead";
静态
类的每个对象都共享同一个值
//比如说·我想知道我创建了多少Enemy
public class Enemy
{
public static int enemyCount=0;
public Enemy()
{
enemyCount++;
}
}
public class Game
{
void Start()
{
Enemy enemy1=new Enemy();
Enemy enemy2=new Enemy();
Enemy enemy3=new Enemy();
int x=Enemy.enemyCount;
}
}
静态方法无需创建类即可直接使用
-
静态类无法实例化
-
静态类中所有成员都必须是静态的
就如Input.GetButtonDown一样,不需要创建实例就可以访问
public static class Utilties
{
public static int Add(int num1,int num2)
{
return num1+num2;
}
}
public class UtitliesExample:MonoBehaviour
{
void Start()
{
int x=Utilties.Add(5,6);
}
静态方法属于类,非静态方法属于实例
静态方法属于类,非静态方法属于实例”这句话的意思是,静态方法是直接与类本身相关联的,而非静态方法则与类的具体对象(实例)**相关联。下面通过详细解释和例子,帮助你理解这一点。
-
静态方法属于类
-
静态方法是用
static
关键字修饰的,它与类本身关联,不依赖于任何对象实例。这意味着你可以直接通过类名来调用静态方法,而不需要创建类的对象。 -
调用方式:
类名.静态方法()
。
示例:静态方法
public class MathUtility
{
// 静态方法
public static int Add(int a, int b)
{
return a + b;
}
}
// 调用静态方法
int result = MathUtility.Add(3, 5); // 输出 8
解释:
-
MathUtility.Add(3, 5)
直接通过MathUtility
类名调用Add
方法,不需要创建MathUtility
的实例(即不需要new MathUtility()
)。 -
这表明
Add
方法属于类本身,与任何实例无关。
-
非静态方法属于实例
-
非静态方法没有
static
修饰,它与**类的具体实例(对象)**相关联。要调用非静态方法,必须先创建类的实例,方法会作用于这个实例的状态或数据。 -
调用方式:
对象.非静态方法()
。
示例:非静态方法
public class Person
{
public string Name;
// 非静态方法
public void SayHello()
{
Console.WriteLine($"Hello, my name is {Name}.");
}
}
// 创建实例并调用非静态方法
Person person = new Person();
person.Name = "Alice";
person.SayHello(); // 输出: Hello, my name is Alice.
解释:
-
person.SayHello()
调用的是Person
类的非静态方法,但这里必须先通过new Person()
创建person
实例。 -
SayHello
方法依赖于person
对象的状态(Name
字段),因此它与person
的实例相关。
总结静态方法与非静态方法的区别
1. 静态方法
-
属于类:不依赖任何实例,直接通过类名调用。
-
访问限制:静态方法只能访问其他静态成员(静态字段或静态方法),不能访问类的非静态成员,因为它们没有实例。
-
例子:
public class MyClass
{
public static void StaticMethod()
{
Console.WriteLine("This is a static method.");
}
}
MyClass.StaticMethod(); // 通过类名调用
2. 非静态方法
-
属于实例:必须通过类的具体对象调用,方法通常依赖实例的状态(字段或属性)。
-
访问权限:非静态方法可以访问类的所有成员,包括静态和非静态成员,因为每个实例都能访问类中的所有资源。
-
例子:
public class MyClass
{
public string Name;
public void NonStaticMethod()
{
Console.WriteLine($"This is a non-static method. Name is {Name}");
}
}
MyClass myObject = new MyClass();
myObject.Name = "Alice";
myObject.NonStaticMethod(); // 通过实例调用
进一步理解:类和实例的关系
-
类(Class) 是一种模板或蓝图,定义了对象的结构和行为,但类本身并不是实际存在的“物体”。
-
实例(Instance) 是类的具体化,当你使用
new
关键字创建对象时,你就得到一个类的实例,每个实例都有自己独立的数据和状态。
类与实例的关系可以类比为:
-
类 是一个建筑的设计图纸,它描述了建筑的结构(字段)和功能(方法),但图纸本身并不是建筑。
-
实例 是根据图纸建造的具体建筑。每个建筑(实例)都有自己独特的特征,比如颜色、大小等(即每个对象的属性值不同)。
静态方法属于“图纸”,非静态方法属于“建筑”
-
静态方法属于图纸(类),因为它定义的是图纸本身的功能,不依赖于任何具体的建筑。
-
非静态方法属于建筑(实例),因为它需要依赖具体建筑的特征来执行,比如建筑的具体位置或大小。
总结
-
静态方法属于类:它与类相关联,独立于任何对象,可以通过类名直接调用。静态方法无法访问实例的状态或非静态成员。
-
非静态方法属于实例:它依赖于类的具体对象(实例),通过实例调用。非静态方法可以访问实例的状态和非静态成员。
静态方法适用于不依赖于实例的工具类或全局逻辑,而非静态方法适合处理与具体对象相关的操作和状态。
泛型
public class SomeClass
{
public T GenericMethod<T>(T param)
{
return param;
}
}
public class SomeOtherClass:MonoBehaviour
{
void Start()
{
Someclass myClass=new SomeClass();
myClass.GenericMethod<int>(5);
}
}
public class GenericClass<T>
{
T item;
public void UpdateItem(T newItem)
{
item=newItem;
}
}
public class GenericClassExample:MonoBehaviour
{
void Start()
{
GenericClass<int>myClass=new GenericClass<int>();
myClass.UpdateItem(5);
}
}
协程
协程的概念
在Unity中,协程是一种可以暂停执行并在某个条件满足后再继续的代码块。它允许我们“分步”执行一系列操作,不需要一次性完成,并且能够在两个步骤之间设置延时。
类比解释
想象你在厨房准备一顿大餐。为了节省时间,你希望在等待水煮开的过程中,去切蔬菜、调配酱汁、甚至摆盘,而不是干站着等水开。这时候,你心里会安排一个“等待水开”的任务。这个任务不会影响你去完成其他工作,等到水开时,你再回来处理它——这就是协程的作用。
在游戏开发中,比如在角色逐步恢复生命值、等待一定时间生成新的敌人等,协程能帮你实现这些“分布式”的操作,避免一次性卡住游戏主线程。
协程的实际例子
假设你在制作一个塔防游戏,每隔5秒就要生成一个敌人,而不是一开局就一下子生成所有的敌人。
-
传统方法的问题:如果你用一个普通循环,可能会这样写:
void GenerateEnemies()
{
for (int i = 0; i < 10; i++)
{
SpawnEnemy();
// 等待5秒(理论上)
}
}
-
这里的问题是,代码会停在等待的地方,整个游戏都卡住,5秒后才继续下一个敌人生成。玩家体验会非常差,因为游戏“卡住了”。
-
协程的解决方法:用协程来处理,效果会更流畅,因为协程可以暂停、延时并继续,而不会打断游戏。
IEnumerator GenerateEnemiesCoroutine()
{
for (int i = 0; i < 10; i++)
{
SpawnEnemy(); // 生成敌人
yield return new WaitForSeconds(5.0f); // 等待5秒,不影响其他代码执行
}
}
-
在这种情况下,
yield return new WaitForSeconds(5.0f);
就是让协程暂停5秒,而不会暂停主线程。游戏依然正常运行,你的其他代码(比如角色的移动、界面更新)不受影响,而协程5秒后会自动恢复,生成下一个敌人。
什么时候用协程?
协程非常适合用在游戏中需要等待、分阶段执行的场景,比如:
-
每隔一段时间生成一个道具、敌人或奖励。
-
逐步更新一个值,比如角色的生命值、体力等。
-
实现简单的动画或镜头效果,比如缓慢淡入、淡出、移动摄像头。
总结
协程就是一种“聪明的助手”,能帮你把大任务拆解成小任务,在每个任务间隙中“休息一下”,而不让主任务停下。这样可以确保游戏流畅运行,避免等待、延迟等操作导致的卡顿。
具体例子:
public override IEnumerator DamageCharacter(int damage, float interval) //damage是对角色造成的伤害,interval是相邻两次伤害之间间隔的时间
{
while(true)
{
hitPoints-=damage;
if(hitPoints<=float.Epsilon)
{
KillCharacter();
break;
}
if(hitPoints>float.Epsilon)
{
yield return new WaitForSeconds(interval);
}
else
{
break;
}
}
}
Yield return 表示“返回一个等待条件,并暂停协程,直到条件满足后继续”
OnEnable()函数
和start()函数有点像,但是不同的是这个函数是在每次对象或脚本启用时调用