塔防游戏学习笔记

学习视频:https://www.bilibili.com/video/BV15W411976h?p=13

Input.GetAxis

塔防项目代码示例

        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        float mouse = Input.GetAxis("Mouse ScrollWheel");

“Horizontal”、“Vertical”、"Mouse ScrollWheel"为项目设置,可在Edit > Project Settings > Input进行更改
塔防游戏学习笔记


Vector3

Vector3.up;

表示世界坐标系中 Y 轴正方向上的单位向量
用于编写 Vector3(0, 1, 0) 的简便方法。

Vector3.down;

表示世界坐标系中 Y 轴负方向上的单位向量
用于编写 Vector3(0, -1, 0) 的简便方法。

Vector3.left;

表示世界坐标系中 X 轴负方向上的单位向量
用于编写 Vector3(-1, 0, 0) 的简便方法。

Vector3.right;

表示世界坐标系中 X 轴正方向上的单位向量
用于编写 Vector3(1, 0, 0) 的简便方法。

Vector3.forward;

表示世界坐标系中 Z 轴正方向上的单位向量
用于编写 Vector3(0, 0, 1) 的简便方法。

Vector3.back;

表示世界坐标系中 Z 轴负方向上的单位向量
用于编写 Vector3(0, 0, -1) 的简便方法。

Vector3.magnitude

返回该向量的长度。计算方法:(xx+yy+z*z) 的平方根。

Vector3.sqrMagnitude 返回该向量的平方长度。计算方法:(xx+yy+z*z)

如果只需要比较一些向量的大小, 则可以使用 Vector3.sqrMagnitude 比较它们的平方数(省略开平方操作)

(Vector3 value).normalized

塔防项目代码示例

transform.Translate((positions[index].position - transform.position).normalized * Time.deltaTime * speed);

返回 magnitude 为 1 时的该向量。(只读)
进行标准化时,向量方向保持不变,但其长度为 1.0。

Vector3.Normalize (Vector3 value)

使该向量的 magnitude 为 1。
进行标准化时,向量方向保持不变,但其长度为 1.0。


transform 和 Transform 的区别

transform 是附加到此 GameObject 的 Transform。
Transform 是一个类,用来描述物体的位置,大小,旋转等等信息。


Translate

Translate 根据 translation 的方向和距离移动变换。

塔防项目代码示例

transform.Translate(Vector3.forward * speed * Time.deltaTime);

public void Translate(Vector3 translation);
public void Translate(Vector3 translation, Space relativeTo);

如果 relativeTo 被省略或设置为 Space.Self,则会相对于变换的本地轴来应用该移动。(在场景视图中选择对象时显示的 X、Y 和 Z 轴。) 如果 relativeTo 为 Space.World,则相对于世界坐标系应用该移动。


MonoBehaviour相关

什么时候不继承MonoBehaviour类?

如果你的类不需要引擎提供的初始化、各种物理、渲染或着色器的回调,最好不要继承 MonoBehaviour,因为游戏是需要优化节约资源的。继承后,当引擎接触事件,触发的时候,就会反射调用个各种函数,这是很需要耗费资源的。

不继承会发生什么?

  1. 不能使用 Invoke 和 Coroutine 了。
  2. 调试不方便了,不能在 Inspector 和 Debug tab 看到参数。同时如果一个类不继承 Mono,那么这个类的 List 表也无法在 Debug 界面看到。
  3. 不自动调用 Start,Update 等方法。

谁要继承?
GameObject 还是要继承 MonoBehaviour 的,这样才能 new 实例化一个对象,MonoBehaviour 里面的几十方法比如:
Start(), Awake(), Update(), FixedUpdate(), OnGUI(),这几个方法不管你用哪一个,必须要继承MonoBehaviour

如果有些内容不需要继承,但是有些参数需要显示在Inspector,要怎么做?

[System.Serializable]

可以在检视面板中嵌入带有子属性的类。这种类不需要继承MonoBehaviour,继承MonoBehaviour会浪费计算资源,遇到不需要继承MonoBehaviour,但是又想让这个类的子属性可以在检视面板中检视,就可以用这个

塔防项目代码示例

[System.Serializable]
public class Wave
{
    public GameObject enemyPrefab;
    public int count;
    public float rate;
}

额外参考:Unity3d学习日记:使用[System.Serializable]

[HideInInspector]

使变量不显示在 Inspector 中,但进行序列化。


GetComponent()

从当前游戏对象获取组件T,只在当前游戏对象中获取,没得到的就返回null,不会去子物体中去寻找。

塔防项目代码示例

MapCube mapCube = hit.collider.GetComponent<MapCube>()

col.GetComponent<Enemy>().TakeDamage(damage);

//upgradeCanvas在Inspector界面挂载了Animator组件,所以通过GetComponent来把这个组件赋值给同为Animator 类的upgradeCanvasAnimator 
public GameObject upgradeCanvas;
private Animator upgradeCanvasAnimator;
upgradeCanvasAnimator = upgradeCanvas.GetComponent<Animator>();

Quaternion

Quaternion.identity

单位四元数,也就是默认的无旋转状态,此时与世界坐标相同,前方指向+Z,上方指向+Y

塔防项目代码示例

for(int i = 0; i<wave.count; i++)
            {
                GameObject.Instantiate(wave.enemyPrefab, START.position,Quaternion.identity);
                CountEnemyAlive++;
                if (i != wave.count-1)
                    yield return new WaitForSeconds(wave.rate);
            }

Quaternion(四元数)用于计算Unity旋转。

额外学习: 【Unity技巧】四元数(Quaternion)和旋转


GameObject.Instantiate

官方文档


Ray相关

塔防项目代码示例

//炮台建造
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

RaycastHit hit;
bool isCollider = Physics.Raycast(ray, out hit, 1000, LayerMask.GetMask("MapCube"));
if (isCollider)
{
   MapCube mapCube = hit.collider.GetComponent<MapCube>();//得到点击的MapCube
   if (selectedTurretData != null && mapCube.turretGo == null)
   {
       //如果得到的mapCube是空的,代表可以创建炮台
       if (money > selectedTurretData.cost)
       {
           ChangeMoney(-selectedTurretData.cost);
           mapCube.BuildTurret(selectedTurretData);
       }
       else
       {
	        //钱不够的提示
	        moneyAnimator.SetTrigger("Flicker");
       }
   }
   else if(mapCube.turretGo != null)
   {
       //升级炮台 
       if(mapCube.turretGo == selectedMapCube && upgradeCanvas.activeInHierarchy)
       {
           StartCoroutine(HideUpgradeUI());
       }
       else
       {
           ShowUpgradeUI(mapCube.transform.position, mapCube.isUpgraded);
       }
       selectedMapCube = mapCube;
   }
}

Camera.ScreenPointToRay
官方文档

虽然是Camera类的函数,但是 ScreenPointToRay 返回 Ray 类的值

Physics.Raycast
官方文档

如果射线与任何碰撞体相交,返回 true,否则为 false。
Extra:out hit
Unity3D教程:ref 、out、params参数区别

RaycastHit hit
官方文档


Input.GetMouseButtonDown()

官方文档

button 值为 0 表示主按钮(通常为左键),1 表示右键,2 表示中间按钮。


关于 Text 和 text

塔防项目代码示例

 public Text moneyText;
 moneyText.text

Text:
text: 使用此设置可读取或编辑 Text 中显示的消息。使用其他 Text 属性,如 size、font 和 alignment,可更改 text 的外观。


Instantiate

塔防项目代码示例

GameObject bullet = GameObject.Instantiate(bulletPrefab, firePosition.position, firePosition.rotation);

OnTriggerEnter(Collider)

官方文档

OnTriggerStay(Collider)

官方文档

OnTriggerExit(Collider)

官方文档

顺便附上一个触发器和碰撞器的介绍吧:Unity 触发器与碰撞器介绍,不要弄混!


List

(仅介绍实例用法)
塔防项目代码示例

public **List<GameObject> enemies** = new List<GameObject>();

List<int> emptyIndex = new List<int>();

transform.LookAt()

官方文档

塔防项目代码示例

transform.LookAt(target.position);

head.LookAt(targetPosition); //head 已经是 Transform 类,所以可以直接调用 LookAt()

laserEffect.transform.LookAt(pos); //laserEffect 是 GameObject 类,所以先调用 transform类再调用LookAt()

MonoBehaviour.OnMouseEnter()

当鼠标进入 GUIElement 或 Collider 时调用。
当鼠标停留在对象上时,调用相应的 onm ouseOver 函数; 当鼠标移开时,调用 onm ouseExit。
官方文档

MonoBehaviour.OnMouseExit()

当鼠标不再处于 GUIElement 或 Collider 上方时调用。
onm ouseExit 调用跟随在相应的 onm ouseEnter 和 onm ouseOver 调用之后。
在属于 Ignore Raycast 层的对象上,不调用该函数。
官方文档


EventSystem.current.IsPointerOverGameObject()

EventSystem

创建了UI之后就会出现。在这个塔防游戏中,EventSystem监视游戏里的UI界面(红框内):
塔防游戏学习笔记

IsPointerOverGameObject()

具有给定 ID 的指针是否位于 EventSystem 对象上?(如果使用没有参数的 IsPointerOverGameObject(),它将指向“鼠标左键”(pointerId = -1);
官方文档

塔防项目代码示例

//当鼠标进入collider的时候,如果方块上没有炮塔,且鼠标没有在UI上(即IsPointerOverGameObject() == false),执行方块变色的动作
private void onm ouseEnter()
{
	if(turretGo == null && EventSystem.current.IsPointerOverGameObject() == false)
    {
        renderer.material.color = Color.red;
    }
}

Slider

UnityEngine.UI里的类

官方文档

塔防项目代码示例

public Slider hpSlider;
hpSlider.value = (float)hp / totalHp;

Button

UnityEngine.UI里的类

interactable

官方文档

SetActive()

GameObject下的函数
激活或停用对象:true 可激活 GameObject,而 false 可停用 GameObject。

官方文档

塔防项目代码示例

//只要炮塔可以升级(isDisableUpgrade = false),那么就把upgradeCanvas激活并把对应的方块位置赋值,并把buttonUpgrade.interactable设置为true,那么游戏里面,升级按钮就可以点击
public Button buttonUpgrade;
public GameObject upgradeCanvas;
void ShowUpgradeUI(Vector3 pos, bool isDisableUpgrade = false)
{
    StopCoroutine("HideUpgradeUI");
    upgradeCanvas.SetActive(false);
    upgradeCanvas.SetActive(true);
    upgradeCanvas.transform.position = pos;
    buttonUpgrade.interactable = !isDisableUpgrade;
}

GameObject.activeInHierarchy

GameObject下的变量,能返回GameObject是否激活的布尔值
官方文档

塔防项目代码示例

//如果点击方块和当前选择的炮台是一样的,且升级选项都激活了,那么就停止协程,否则就显示升级UI
if(mapCube.turretGo == selectedMapCube && upgradeCanvas.activeInHierarchy)
{
    StartCoroutine(HideUpgradeUI());
}
else
{
    ShowUpgradeUI(mapCube.transform.position, mapCube.isUpgraded);
}

协程

协程就像一个函数,能够暂停执行并将控制权返还给 Unity,然后在下一帧继续执行。

协程本质上是一个用返回类型 IEnumerator 声明的函数(取代了常用的void声明),并在主体中的某个位置包含 yield return 语句。yield return 行是暂停执行并随后在下一帧恢复的点

默认情况下,协程将在执行 yield 后的帧上恢复,但也可以使用 WaitForSeconds 来引入时间延迟:

官方文档

要将协程设置为运行状态,必须使用 StartCoroutine 函数:

官方文档

有需要的时候,可以使用 StopCoroutine 函数。

官方文档

塔防项目代码示例

IEnumerator HideUpgradeUI()
{
    upgradeCanvasAnimator.SetTrigger("Hide");
    yield return new WaitForSeconds(0.11f);
    upgradeCanvas.SetActive(false);
}


void Start()
{
    StartCoroutine("SpawnEnemy");
}

public void Stop()
{
    StopCoroutine("SpawnEnemy");
}

IEnumerator SpawnEnemy()
{
    foreach(Wave wave in waves)
    {
        for(int i = 0; i<wave.count; i++)
        {
            GameObject.Instantiate(wave.enemyPrefab, START.position, START.rotation);
            CountEnemyAlive++;
            if (i != wave.count-1)
                yield return new WaitForSeconds(wave.rate);
        }
        while (CountEnemyAlive>0)
        {
            yield return 0;
        }
        yield return new WaitForSeconds(waveRate);
    }
    while (CountEnemyAlive > 0)
    {
        yield return 0;
    }
    GameManager.Instance.Win();
}

游戏LoadScene以及结束游戏

塔防项目代码示例

using UnityEngine.SceneManagement;

public class GameMenu : MonoBehaviour
{
    public void OnStartGame()
    {
        SceneManager.LoadScene(1);
    }
    public void OnExitGame()
    {
    //宏,如果在unity编辑器里面运行游戏,点击退出的时候,就设置UnityEditor.EditorApplication.isPlaying为false,这样就能退回到编辑模式。
    //除此情况外,就调用Application.Quit();来结束游戏
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif


    }
}
上一篇:Unity 让物体在给定的时间内平滑地移动到指定位置


下一篇:Unity_DOTween