日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Siki_Unity_2-7_Stealth秘密行动

發(fā)布時間:2024/1/18 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Siki_Unity_2-7_Stealth秘密行动 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Unity 2-7 Stealth秘密行動

Abstract:向量運算;Animation動畫;Navigation尋路系統(tǒng);Mecanim動畫系統(tǒng)

任務(wù)1&2&3:游戲介紹
&&?創(chuàng)建工程和游戲場景介紹 &&?創(chuàng)建游戲環(huán)境

逃生游戲,過關(guān)條件為拿到鑰匙并從電梯處逃脫
被敵人/?攝像頭/?觸碰紅外線 --?觸發(fā)警報
紅外線可以手動斷電

右上圖場景是美工創(chuàng)建好的
  Import package->Custom package: StealthAssets.unitypackage

資源介紹:
  Animation, Audio, Fonts, Gizmos, Materials, Models, Shaders, Textures

Animation中的.fdx文件是從3d軟件中導(dǎo)出的動畫模型
humanoid動畫可以應(yīng)用在任意人形模型上

Audio:聲音文件
Fonts:字體
Gizmos:waypoint的圖標(biāo),用于敵人尋路的AI,用圖標(biāo)表示路徑
Materials:材質(zhì)
Models:模型(環(huán)境模型、物品模型等)
  里面有一個文件夾Collision Meshes,存放collision的mesh,用于碰撞檢測
  比如prop_cctvCam_collision:?一個椎體,用來檢測player是否進(jìn)入了攝像頭視野
Shaders:更好的效果
Textures:模型的貼圖

創(chuàng)建游戲環(huán)境:
  1. 新建文件夾Scenes,保存場景Stealth
  2.?創(chuàng)建空物體env,用于存放有關(guān)環(huán)境的物體
  3. 將Models->env_stealth_static拖入場景(env下)
    Reset Transform
  4.?給env_stealth_static添加碰撞器
    添加Mesh Collider
    在Mesh屬性中指定簡化的Models->Collision Meshes:
      env_stealth_collision->env_stealth_collision_001
    在scene模式下,可以看到的綠色網(wǎng)格就是剛才的collision

創(chuàng)建battleBus:
  發(fā)現(xiàn)中間一塊有一個突起的mesh?collider,而沒有物體存在
  由于在env_stealth_static中沒有放入位于地圖中間的小車

因此手動添加prop_battleBus到地圖內(nèi) (作為env子物體),并旋轉(zhuǎn)到車頭朝向雜物,位置與Mesh Collider重合即可

任務(wù)4:添加環(huán)境燈光

給環(huán)境添加環(huán)境燈光:
  在最外層創(chuàng)建空物體light (位置歸零),用于存放所有燈光
  在light中創(chuàng)建Directional Light,顏色為暗一點的灰色,Intensity調(diào)小(0.1)
  Main Camera背景顏色調(diào)為黑色,Clear Flags調(diào)為Solid?color

給房間添加燈光:
  在light中創(chuàng)建Point Light,顏色為暗橘黃色,
  一個房間添加一個,在四周的墻上或過于陰暗的地方放置若干

任務(wù)5&6:添加警報燈 &&?添加警報聲

實現(xiàn)警報燈
添加Directional Light: AlarmLight,紅色,觸發(fā)警報的時候Intensity在0.1~0.5之間不斷變化
在AlarmLight上添加腳本AlarmController.cs

public bool isAlarmOn = false; // Flag
private float lowest/highestIntensity = 0.1/ 0.5; //?指定最高值最低值
private float targetIntensity; //?當(dāng)前需要往lowest還是highest靠近

在Awake()中:
  targetIntensity = highestIntensity; //?默認(rèn)是從lowest變到highest的
  isAlarmOn = false; //?確保剛開始警報未觸發(fā)

在Update()中實現(xiàn)警報燈閃爍的效果:
  if(isAlarmOn) { //?警報開啟狀態(tài)
  light.intensity=Mathf.Lerp(ligh.intensity, targetIntensity, Time.deltaTime*speed);

現(xiàn)在的警報實現(xiàn)了從lowestIntensity到highestIntensity的變亮過程
  判斷接近 (Lerp是一個無限趨近的過程) targetIntensity后,改變target的值

  if(Mathf.Abs(light.intensity - targetIntansity) < 0.05f) { //?達(dá)到目標(biāo)值
    if(targetIntensity == highestIntensity) {
    targetIntensity = lowestIntensity;
  } else {?同理; }

當(dāng)警報消除的時候,需要將Intensity設(shè)為0(直接設(shè)置會顯得不自然,采用Lerp方法)
  light.intensity = Mathf

但是(個人認(rèn)為)這么寫需要一直執(zhí)行intensity的賦值操作

為了讓外界訪問AlarmController.cs方便,采用單例模式
  public static AlarmController _instance;
  在Awake()中:_instance = this;
  在外界直接訪問_instance即可

public class AlarmController : MonoBehaviour {public static AlarmController _instance;public bool isAlarmOn = false;private Light alarmLight;private float lowestIntensity = 0.1f;private float highestIntensity = 0.75f;private float targetIntensity;private float currentIntensity;private float intensityChangeSpeed = 5f;void Awake () {_instance = this;targetIntensity = highestIntensity;currentIntensity = lowestIntensity;isAlarmOn = false;alarmLight = GetComponent<Light>();}void Update() {if (isAlarmOn) {if (Mathf.Abs(currentIntensity - targetIntensity) < 0.05) {if (targetIntensity.Equals(highestIntensity)) {targetIntensity = lowestIntensity;} else {targetIntensity = highestIntensity;}}currentIntensity = Mathf.Lerp(currentIntensity, targetIntensity, Time.deltaTime * intensityChangeSpeed);} else {// 警報處于關(guān)閉狀態(tài)currentIntensity = Mathf.Lerp(currentIntensity, 0, Time.deltaTime * intensityChangeSpeed);}alarmLight.intensity = currentIntensity;} }

實現(xiàn)警報聲效果:

在env_stealth_static模型里會找到若干個喇叭: prop_magaphone
對這六個喇叭添加AudioSource組件,用于播放聲音 alarm_triggered
取消勾選play?on?awake
將Min Distance設(shè)置大一點 (5)
之后需要播放警報聲時,需要找到這六個喇叭,因此把他們設(shè)置為tag=Siren

任務(wù)7:游戲控制器GameController --?控制整個游戲的運行

游戲控制器:
  燈光、聲音、警報主角位置(比如主角位置暴露了,需要記錄看到主角的最新位置,讓機(jī)
  器人朝著最新位置移動)

創(chuàng)建空物體GameController,添加GameController.cs腳本

需要控制警報燈:
  public bool isAlarmOn = false;
  在Update()中將isAlarmOn傳遞給任務(wù)6中的單例模式AlarmController
    //?因為要隨時控制isAlarmOn的值
    AlarmController._instance.isAlarmOn = this.isAlarmOn;

需要控制警報聲:
  // 得到六個警報喇叭
  private GameObject[] sirens = GameObject.FindGameObjectsWithTag("Siren");
兩個方法:分別控制警報聲的響起和停止
  private void PlaySiren() {
    foreach (GameObject siren in sirens) {
      if(!siren.audio.isPlaying) { //?如果沒有播放
        siren.audio.Play(); //?新版unity改用GetComponent AudioSource
  }}}
  private void StopSiren() { // 相似,但是不需判斷當(dāng)前是否正在播放 }
在Update()中,控制警報聲的播放和停止
  if(isAlarmOn) {
    PlaySiren();
  } else {
    StopSiren();
  }

public class GameController : MonoBehaviour {public bool isAlarmOn = false;private GameObject[] sirens;private void Awake() {isAlarmOn = false;sirens = GameObject.FindGameObjectsWithTag("Siren");}private void Update() {AlarmController._instance.isAlarmOn = this.isAlarmOn;if (isAlarmOn) {PlaySiren();} else {StopSiren();}}private void PlaySiren() {foreach (GameObject siren in sirens) {if (!siren.GetComponent<AudioSource>().isPlaying) {siren.GetComponent<AudioSource>().Play();}}}private void StopSiren() {foreach (GameObject siren in sirens) {siren.GetComponent<AudioSource>().Stop();}} }

任務(wù)8&9:實時攝像機(jī)CCTV Camera && 攝像機(jī)的自動旋轉(zhuǎn)
任務(wù)10:攝像機(jī)的警報觸發(fā)功能

在Prefab中可以找到 prop_cctvCam

在環(huán)境中需要有三個cctv Camera,分別在

創(chuàng)建空物體Camera,用于放置所有cctvCamera

第一個camera放在bus的兩桶油下方
  攝像機(jī)的旋轉(zhuǎn)(Joint)通過x和y的旋轉(zhuǎn)實現(xiàn),設(shè)置為低頭60°
給攝像機(jī)添加燈光
  在cctv_cam_body上添加Light組件,Light設(shè)為Spot(探照燈)
  Light的cookie設(shè)置為texture: fx_cameraView_alp,顏色改為紅色
將cctv_collision添加進(jìn)cctv_cam_body,調(diào)整位置
  取消cctv_collision的renender渲染
  給collision添加mesh collider碰撞器,用于碰撞檢測
將上面的cctv_Camera做成prefab

將其他兩個camera分別放置

攝像機(jī)的旋轉(zhuǎn)

通過Animation的方式,控制joint部分的y軸旋轉(zhuǎn)

1. 新建Animator -- Window->Animation->Create CameraSweepAnimation.anim

2. Add Property: Transform->Rotation

3. 在對應(yīng)時間點上添加KeyFrame,并設(shè)置Rotation.y的值(90~0~-90)
  Sample為每秒的幀數(shù)

4. 因為左下角的攝像機(jī)不需要進(jìn)行旋轉(zhuǎn),因此不能apply to prefabs
  在另一個需要旋轉(zhuǎn)的攝像機(jī)上添加Animator組件,并賦值,即可

攝像機(jī)的警報觸發(fā):

思路:在左下角攝像機(jī)中完成警報觸發(fā)功能,并apply to prefabs

1. 在prop_cctvCam的collision子物體上添加腳本CctvCamCollision.cs

2. 將碰撞器設(shè)置為Trigger,因為不需要有物理碰撞效果

3. OnTriggerEnter(Collider other) {}
  if(other.tag.Equals("Player")) { // 觸發(fā)警報 }

4. 觸發(fā)警報需要設(shè)置GameController.cs中的isAlarmOn
  GameController設(shè)置為單例模式
  public static GameController _instance;
  _instance = this;

設(shè)置isAlarmOn:
  GameController._instance.isAlarmOn = true;

5. 需要記錄當(dāng)前警報觸發(fā)位置
  在GameController.cs中
  public Vector3 lastPlayerPos;
  GameController._instance.lastPlayerPos = other.transform.position;

6. 使用OnTriggerStay()更好,因為當(dāng)Player在其中移動的時候,會觸發(fā)位置更新

任務(wù)11:標(biāo)簽的管理(代碼管理)

使用代碼進(jìn)行標(biāo)簽的管理(使用字符串的過程中很可能出現(xiàn)字符串打錯等情況)

創(chuàng)建腳本Tags.cs
  // 注意:Tags不是作為一個組件存在的,只是存放了一些變量

public const string player = "Player";
  // const -- 常量(tags不需要修改)

使用的時候:
  if(other.tag == Tags.player) {}

還有其他tags:
  "Siren"、"Enemy"等

任務(wù)12&13:添加激光警報裝置 && 警報的觸發(fā)和閃爍

fx_laserfence_laser:?
  調(diào)整大小、角度和位置(門柱上正好有孔,與laser一一對應(yīng))

1. 給laser添加Collider組件,用于碰撞檢測 -- BoxCollider,選擇Trigger

2. 給laser添加light組件,發(fā)光:PointLight、范圍變小、紅色、強(qiáng)度增大

3. 給laser添加Audio Source組件: Audio->laser_ham,發(fā)聲:
Spetial Blend、min/max distance、PlayOnAwake、Loop

4. 做成Prefab

5. 創(chuàng)建空物體lasers,放入另外五個激光警報(一共6個)即可

激光警報的觸發(fā):

添加腳本LaserController.cs

OnTriggerStay() {
  // 和攝像頭觸發(fā)警報一樣的操作
  // 因此在GameController中寫成函數(shù)public void SwitchAlarmOn(Transform t)
  if(tag....) GameController._instance.SwitchAlarmOn(other.transform);

Apply to prefabs.

激光警報的閃爍:

兩個激光(最長的那兩條)需要間隔閃爍,方便Player的通過

在LaserController中:

public bool isFlicker; public float onTime = 3f; public float offTime = 1.5f; private float timer = 0;private void Update() {if(isFlicker) {if(this.gameObject.GetComponent<Renderer>().enabled) {// 當(dāng)前亮著timer += Time.deltaTime;if(timer >= onTime) {this.gameObject.GetComponent<Renderer>().enabled = false;timer = 0;}} else {// 當(dāng)前暗著timer += Time.deltaTime;if(timer >= offTime) {this.gameObject.GetComponent<Renderer>().enabled = true;timer = 0; }}}}

(Collider也需要禁用和啟用) --??
  gameObject.GetComponent<BoxCollider>().enabled;

在Inspector中將兩個需要閃爍的激光的isFlicker屬性設(shè)置為true
兩個激光閃爍不同步,一個為(1.6, 2.8), 一個為(2.1, 2.5)(隨意)

任務(wù)14&15:主角和主角的動畫

添加主角:

Models->char_ethan

給Player添加碰撞器Capsule Collider

給Player添加剛體Rigidbody
  因為主角不需要通過rigidbody進(jìn)行移動(使用動畫控制),所以勾選IsKinematic
    (在任務(wù)16中會發(fā)現(xiàn)這是一個bug)
  主角在(x, z)平面上移動,且只圍繞y軸旋轉(zhuǎn),所以Freeze Position: y; Rotation: x, z

主角移動的動畫:

在Humanoid中的動畫是人形動畫,可以使用

新建一個Animator Controller,名為PlayerController

打開Animator編輯器(選中PlayerController,Window->Animator)

1. 主角的狀態(tài):
  walk/ run (walk和run是同一個動作,只不過速度不同 -- 相同動畫)
  sneaker
  idle
  death

2. 添加parameter
  float: speed -- 移動速度
  bool: sneaker -- 是否處于sneaker狀態(tài)(按下shift鍵)

3. 默認(rèn)狀態(tài):idle
  將humanoid_idel->Idle動畫拖入狀態(tài)機(jī)(黃色為默認(rèn)動畫)

4. 右鍵Create State -> From Blend Tree
  (上面提到walk和run是同一個動作,用BlendTree實現(xiàn))
  命名為Locomotion (移動、運動)
  雙擊進(jìn)入編輯模式
  Add Motion Field,添加Walk和Run動畫
  在Parameter中選擇speed,用speed來控制walk和run的融合
  uncheck Automate Thresholds (自動生成參數(shù))
    取消勾選后可以手動確定:參數(shù)是根據(jù)哪個值來確定的,這里選擇speed
    
    這里表示1.555在Walk,speed增大漸漸進(jìn)入run的狀態(tài),5.667為run狀態(tài)

5. Idle->Locomotion
  make transition: Conditions: speed > 0.1
  Locomotion->Idle: speed < 0.1

6. Sneak: humanoid_sneak: sneak動畫拖入狀態(tài)機(jī)
  Idle->Sneak:
    make transition: Conditions: speed > 0.1 && sneak == true;
  Sneak->Idle: speed < 0.1

7. Locomotion->Sneak:
  speed > 0.1 && sneak == true;
  Sneak->Locomotion:
  speed > 0.1 && sneak == false;

由于player的移動是通過動畫實現(xiàn)的
  因此如果覺得移動過慢/快,可以修改對應(yīng)狀態(tài)的speed

8. Dying: humanoid_dying->Dying
  添加parameter: bool dead
  因為任何狀態(tài)下都會死亡 --
    AnyState->Dying: make transition: dead = true;

任務(wù)16&17:控制主角的運動和移動

運行游戲,手動調(diào)整主角的速度,會發(fā)現(xiàn)主角出現(xiàn)了移動,主角的transform也發(fā)生變化
  由于Player->Animator勾選了屬性Apply Root Motion,表示動畫會影響transform的值

思路:按下上下左右鍵時,設(shè)置speed即可
  根據(jù)按鍵控制朝向

在char_ethan添加腳本PlayerController.cs

在Update()中實現(xiàn):

// 得到按鍵的信息
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");

// 需要得到Animator來改變speed的值
private Animator anim = GetComponent...

(我的思路)
  anim.SetFloat("Speed", (Mathf.Max(h,v) * 5.56f));
  -- 結(jié)果為不適合
    1.?數(shù)值變化太快,walk->run過于迅速
    2.?而且延遲較大,Input.GetAxis()的值需要時間來歸零和增大

(正確思路 -- 利用差值)
  if(Mathf.Max(Mathf.Abs(h), Mathf.Abs(v)) > 0.1f) {
    // 開始移動
    newSpeed = Mathf.Lerp(anim.GetFloat("speed"), maxSpeed, delta...)
  } else { // 停止移動
    // 相同思路,利用差值,但是將speedRate增大,因為需要很快停止移動
    // 試驗過后發(fā)現(xiàn)還是不好控制,于是直接將speed設(shè)為0
    // 但是從跑步到Idle的動畫很不平滑 -- 自行選擇
    newSpeed = 0;
  }
  anim.SetFloat("speed", newSpeed);

出現(xiàn)bug -- Player會進(jìn)行穿墻,就好像Collider沒有任何作用一樣
  原因:任務(wù)14中給Player添加Rigidbody時勾選了IsKinemetic選項,取消勾選即可

控制Player的移動方向:
  在需要移動的if條件中:
    // 取得目標(biāo)方向
    Vector3 targetDir = new Vector3(h, 0, v);
    // 取得當(dāng)前方向
    Vector3 currDir = transform.forward;
    // 取得兩個方向的夾角
    float angle = Vector3.Angle(targetDir, currDir);
    // 使用勻速旋轉(zhuǎn)的方法
    transform.Rotate(Vector3.up * angle * Time.deltaTime * rotateSpeed);
    // rotateSpeed可設(shè)為5

到此為止player的移動功能實現(xiàn)了,但是
  出現(xiàn)Bug --

1. 角色在停止控制后有的時候會繼續(xù)不聽旋轉(zhuǎn)
  (個人推測是慣性?)
  將角色的Rotation: y Freeze即可

2. 拐彎的時候會出現(xiàn)有的時候往大彎繞
  比如:角色朝左,向下控制,角色會通過上右下而向下移動
     angle的值也是從90變大到180再變小
     細(xì)心觀察會發(fā)現(xiàn),角色永遠(yuǎn)是順時針旋轉(zhuǎn)
  原因:Vector3.Angle()返回值沒有正負(fù),小于180°
     Rotate(...)方向為Vector3.up,因此永遠(yuǎn)是順時針旋轉(zhuǎn)
       而旋轉(zhuǎn)一些以后,angle值變大,需要繼續(xù)旋轉(zhuǎn),直到angle值為0
  解決方法 -- 想不出如何判斷使用Vector3.up還是Vector3.down
    舍棄該方法
    使用Quaternion和Slerp來解決
    Quaternion targetQuaternion = Quaternion.LookRotation(targerDir);
    // 四元數(shù)返回的是一個帶有方向的角度 (該函數(shù)默認(rèn)以Vector3.up為上方)
    transform.rotation = Quaternion.Slerp(transform.rotation,
      targetQuaternion, Time.deltaTime * rotationSpeedRate);

private Animator animator; private float increaseSpeedRate = 2.5f; private float decreaseSpeedRate = 60f; private float rotateSpeedRate = 2; private float newSpeed; private const float maxSpeed = 5.66f;private void Awake() {animator = GetComponent<Animator>(); }void Update() {float h = Input.GetAxis("Horizontal");float v = Input.GetAxis("Vertical");if (Mathf.Max(Mathf.Abs(h), Mathf.Abs(v)) > 0.1f) {// 開始移動newSpeed = Mathf.Lerp(animator.GetFloat("speed"), maxSpeed, Time.deltaTime * increaseSpeedRate);// 開始旋轉(zhuǎn)// 取得目標(biāo)方向Vector3 targetDir = new Vector3(h, 0, v);Quaternion targetDirQuaternion = Quaternion.LookRotation(targetDir);transform.rotation = Quaternion.Slerp(transform.rotation, targetDirQuaternion, Time.deltaTime * rotateSpeedRate);} else {// 停止移動newSpeed = Mathf.Lerp(animator.GetFloat("speed"), 0, Time.deltaTime * decreaseSpeedRate);// newSpeed = 0; }animator.SetFloat("speed", newSpeed); }

實現(xiàn)sneak緩慢行走:

if(Input.GetKeyDown(KeyCode.LeftShift)) {
  anim.SetBool("sneak", true);
}
if(Input.GetKeyUp(KeyCode.LeftShift)) {
  anim.SetBool("sneak", false);
}

會發(fā)現(xiàn),從Idle時按住shift后再按方向鍵,剛開始一小小段時間會進(jìn)行walk而不是sneak
解決方法 --
  將動畫狀態(tài)機(jī)中Idle->Locomotion的condition加上sneaker=false;即可

此時sneak的速度有點慢
  控制sneak的動畫播放速度即可
  選中動畫狀態(tài)機(jī)中的sneak,將inspector中的speed加大即可

任務(wù)18:主角行走聲音和游戲背景音樂

添加背景音樂:

在GameController中添加AudioSource: music_normal,PlayOnAwake,Loop,2D

添加行走聲音:

在主角上添加AudioSource: player_footsteps

在PlayerController.cs中添加兩個方法

void PlayFootstepsAudio() {
  if(!audio.isPlaying) {
    audio.Play();
}}

void StopFootstepsAudio() {
  audio.Stop();
}

在Locomotion的狀態(tài)下需要播放聲音,其他狀態(tài)不要

if(anim.GetCurrentAnimatorStateInfo(0).IsName("Locomotion")) {
  // play audio
} else {
  // stop audio
}

切換背景音樂:

當(dāng)警報被觸發(fā)的時候,背景音樂會從music_normal切換至music_panic
思路:如果直接暫停播放會顯得比較突兀,因此使用對兩個Volume進(jìn)行差值運算的方法

在GameController上添加另一個AudioSource: music_panic, Loop

在GameController.cs中:
  定義兩個AudioSource來存放兩個聲音
  在Update()中修改兩個AudioSource的Volume
    在原有的isAlarmOn判斷下
    musicNormal.volume=Mathf.Lerp(musicNormal.volume, 0, Time.deltaTime);
    musicPanic.volume=Mathf.Lerp(musicPanic.volume, 1, Time.deltaTime);
    !isAlarmOn時也類似
  也可以添加一個musicFadeSpeed來控制漸變速度
  Panic的背景聲音有點大,因此改為0.5f

任務(wù)19:添加自動門

自動門機(jī)制:當(dāng)主角靠近門的時候會門會自動打開: door_generic_slide
  門的模型都自帶兩個動畫 open和close

創(chuàng)建空物體,用于存放所有門

使用Animator狀態(tài)機(jī)進(jìn)行門的控制
  新建Animator,名為normalDoorAnimationController
  將兩個動畫拖入狀態(tài)機(jī) -- 默認(rèn)為close狀態(tài),設(shè)為default
  添加Parameters: bool closing; // 當(dāng)closing為true時,進(jìn)行關(guān)閉,為false時進(jìn)行開啟
    closing初始值為true
  添加狀態(tài)close和open之間的transition

給door_generic_slide->door_generic_slide_panel 添加BoxCollider來檢測碰撞
給door_generic_slide添加sphere collider,用于trigger開門動畫

在door上添加DoorController.cs腳本 -- 代碼控制門的開關(guān)
  因為是控制門的開關(guān),因此如果是Enemy或Player在觸發(fā)區(qū)內(nèi),保持開門狀態(tài)
  OnTriggerStay(Collider other) {
    if(other.tag == Tags.Player || ... == Tags.Enemy) {
      anim.SetBool("closing", false);
  }}
  // 那如何關(guān)門呢?OnTriggerExit()? 不行,如果區(qū)域內(nèi)有兩個人的話,怎么解決?
  // 使用count來計數(shù) -- 就不能用OnTriggerStay了
  OnTriggerEnter(...) {
    if(other.tag ... || ... ) {
      count++;
  }}
  OnTriggerExit(...) { // 相同情況 count--; }

  在Update中用count來判斷是否開關(guān)門
????????????doorAnimator.SetBool("closing", (count<=0));

添加開關(guān)門的聲音:打開和關(guān)閉時播放聲音
  在門上添加AudioSource: door_open
  在Update中更改doorAnimator.closing時
    // 播放聲音
    if(anim.IsInTransition(0) {
      // 如果在0 layer中正在進(jìn)行某個Transition
      audioDoorOpen.Play();

將門做成Prefab,并創(chuàng)建其他兩扇門

任務(wù)20:添加電梯門 -- 設(shè)置內(nèi)側(cè)外側(cè)打開和關(guān)閉動畫

電梯的自動門是兩扇,往兩邊打開
  電梯門分為外側(cè)和內(nèi)側(cè)兩扇門(內(nèi)側(cè)門即電梯,跟隨上下移動的)

添加外側(cè)門:door_exit_outer

相同的,分別給door_exit_outer_left和right添加上BoxCollider
  給door_exit_outer添加上SphereCollider作為Trigger
  AudioSource為door_open

外側(cè)門的控制代碼使用上一任務(wù)的DoorController.cs即可
  Animator使用上一任務(wù)的NormalDoorController即可
    -- 復(fù)制一個AnimatorController,并將狀態(tài)中的動畫(Motion)換成對應(yīng)的動畫
     ? door_exit_outer_close和door_exit_outer_open

添加內(nèi)側(cè)門:prop_lift_exit

注意電梯門的方向 prop_lift_exit->door_exit_inner

在Lift上添加腳本LiftController.cs
  // 讓inner door的z軸坐標(biāo)跟隨outer door的x軸坐標(biāo)變化即可
  // 首先得到是個transform: inner_door和outer_door的
  innerDoorLeft.position?=?new?Vector3(outerDoorLeft.position.x,?
    innerDoorLeft.position.y,?innerDoorLeft.position.z);
????????innerDoorRight.position?=?new?Vector3(outerDoorRight.position.x,?
    innerDoorRight.position.y,?innerDoorRight.position.z);

內(nèi)側(cè)門的開關(guān)實現(xiàn)了,但是速度會比較快,因此采用另一種方法 -- Lerp
  newInnerDoorLeftX?=?Mathf.Lerp(innerDoorLeft.position.x,?
    outerDoorLeft.position.x,?Time.deltaTime);

再給內(nèi)側(cè)門左右加上BoxCollider

任務(wù)21:對電梯門添加鑰匙控制

因為兩種門共用一個腳本DoorController.cs
  使用一個bool keyRequired = false; 來判斷該門是否需要鑰匙?打開
  在PlayerController.cs中使用bool hasKey=false; 來記錄當(dāng)前是否擁有鑰匙

  修改DoorController.cs中OnTriggerEnter部分:
    如果該門需要key,且collider為Player,且身上有key,才進(jìn)行count++
    -- enemy在需要key的門前不會使其打開

    if(keyRequired) {
      if(other.tag == Tags.player) {
        if(other.gameObject.getcomponent<...script...>().GetHasKey()) {
          count++;
    }}} else { // normal door }

  對于OnTriggerExit() 也一樣需要相同的判斷,否則會平白無故count--;

  -- 小小不算bug的bug -- 在Trigger內(nèi)部修改hasKey,就再也不能開門了
    邏輯,修改hasKey后出去, count--, count=-1, 再進(jìn)去, count++, count=0;

將電梯外門的keyRequired勾選上即可

當(dāng)沒有鑰匙的人接近了門的時候,需要播放AudioSource: door_accessDenied

  判斷是否Player有鑰匙后
  else {
    GetComponents<AudioSource>()[1].Play();  

任務(wù)22:主角拾起鑰匙的功能

Models->prop_key_card

放置在這個位置
  

給Key添加Light,藍(lán)色,intensity調(diào)大

創(chuàng)建Animator: KeyCardController
  將key_card中自帶的默認(rèn)動畫spin拖入狀態(tài)機(jī)即可 -- key的旋轉(zhuǎn)動畫

給Key添加Sphere Collider,用來作為Trigger,檢測主角是否到達(dá)拾起鑰匙的區(qū)域

給Key添加腳本KeycardController.cs
  OnTriggerEnter(...) {
    // Player進(jìn)入觸發(fā)區(qū)時,拾起鑰匙
    PlayerController player = other.GetComponent<PlayerController>();
    player.hasKey = true;
    // 播放得到鑰匙的聲音
    -- 給Key添加一個AudioSource: keycard_pickUp
      通過audioSource.Play() 能行嗎?不行,因為this已經(jīng)被Destroy了
    解決方法:AudioClip --?
      public AudioClip audio;
      // 在transform.position處播放audioClip
      AudioSource.PlayClipAtPoint(audio, transform.position);
    // 銷毀鑰匙
    Destroy(this.gameObject);
  }

任務(wù)23:激光的開關(guān)控制

游戲機(jī)制:一個電閘開關(guān)控制一個激光,Player在電閘旁邊按下z來關(guān)閉對應(yīng)激光

創(chuàng)建空物體switchUnits,用來存放所有(4個)開關(guān)

關(guān)閉開關(guān)后,激光會消失,開關(guān)上面鎖的顯示會改變(改變材質(zhì)即可)

給SwitchUnit添加BoxCollider防止Player走進(jìn)去
  添加BoxCollider作為Trigger -- Sphere不合適,在墻外也能觸發(fā)
  添加AudioSource: switch_deactivation -- 解鎖聲音

添加腳本SwitchUnitController.cs控制開關(guān)邏輯

OnTriggerStay() { -- 需要在觸發(fā)區(qū)域內(nèi)
  if(other.tag == Tags.player) { -- 如果是Player進(jìn)入
    if(Input.GetKeyDown(KeyCode.z)) { -- 如果按下了z鍵
      // 關(guān)閉對應(yīng)激光 -- public GameObject laser;
      laser.SetActive(false);
      // 播放聲音
      audio.Play();
      // 修改解鎖標(biāo)志 -- 替換material -- public Material unlockedMaterial;
      transform.Find("prop_switchUnit_screen").
        GetComponent<MeshRenderer>().material = unlockedMaterial;
}}}

-- 小Bug:在觸發(fā)區(qū)域內(nèi)多次按下z鍵,會進(jìn)行多次的音效播放
  解決方法(個人):
    定義一個bool isSwitchValid = true
    在OnTriggerStay最外層用if(isSwitchValid) 判斷即可

將其做成Prefab,添加其他四個SwitchUnit

private void OnTriggerStay(Collider other) {// 需要在觸發(fā)區(qū)域內(nèi)if (isSwitchValid) {if (other.tag == Tags.player) {// 如果是Player進(jìn)入if (Input.GetKeyDown(KeyCode.Z)) {// 按下z鍵// 關(guān)閉對應(yīng)激光 -- public GameObject laser;laser.SetActive(false);// 播放聲音 unlockedAudio.Play();// 修改解鎖標(biāo)志(替換material) - public Material unlockedMaterial;transform.Find("prop_switchUnit_screen").GetComponent<MeshRenderer>().material = unlockedMaterial;isSwitchValid = false; }}}}

任務(wù)24&25&26:攝像機(jī)的跟隨移動 && 攝像機(jī)的視野問題 && 攝像機(jī)的緩動

按F聚焦Player,在Scene視圖下調(diào)整好合適的視角
  選中MainCamera,GameObject->Align With View -- 將Camera設(shè)置為當(dāng)前視角

攝像機(jī)的跟隨移動
  會利用很多Lerp差值運算

在攝像機(jī)上添加腳本 FollowPlayer

// 得到相機(jī)和Player之間位置的offset
private Transform player = GameObject.FindWithTag(Tags.player).transform;
private Vector3 offset = tranform.position - player.position;
offset = new Vector3(0, offset.y, offset.z); // 保持x方向值相等

// 在Update中更新Camera位置
transform.position = player.position + offset;

出現(xiàn)問題:若主角在下側(cè)墻的上方,則視野會被墻擋住
--改進(jìn)相機(jī)的視野實現(xiàn)

攝像機(jī)的視野智能化調(diào)整:

解決思路:從Camera發(fā)射射線到Player,如果射線沒有接觸到Player,則旋轉(zhuǎn)
  如何旋轉(zhuǎn)?得到Player正上方的距離為offset的點,在該點和Camera之間等距劃分
  三點,依次判斷這三點是否符合要求,若符合,調(diào)整Camera位置和方向

代碼實現(xiàn):

// 起始點
Vector3 beginPos = player.position + offset;
// 終點 -- Player正上方點
Vector3 endPos = player.position + offset.magnitude * Vector3.up;

// 得到兩點之間的四等分點 (3個點) -- 用向量運算方法或差值方法均可
Vector3 pos1 = Vector3.Lerp(beginPos, endPos, 0.25f);
其余的為pos2 = ... 0.5f;? pos3 = ... 0.75f;
存放在數(shù)組中:
Vector3[] posArray = new Vector3[5];

// 判斷在哪個點可以看到Player -- 用射線的方法
foreach(Vector3 pos in posArray) {
  // 遍歷所有可能位置
  RaycastHit hitInfo;
  if(Physics.Raycast(pos, player.position - pos, out hitInfo) {
    // 如果有碰撞的話
    if(hitInfo.collider.tag == Tags.player) {
      // 如果在視野內(nèi)能看到Player -- 移動攝像機(jī)
      transform.position = pos;
      // 改變攝像機(jī)的朝向
      transform.LookAt(player.position);
      break;
}}}

現(xiàn)在,攝像機(jī)的視角問題解決了,但是攝像機(jī)需要進(jìn)行緩動,不然會顯得卡頓 -- Lerp?

攝像機(jī)的緩動:

將上面的transfrom.position = pos; 改為
transform.position = Vector3.Lerp(transform.position, pos, Time.deltaTime);

將上面的transform.LookAt(player); 改為
Quaternion currQuaternion = transform.rotation;
transform.LookAt(player);
transform.rotation = Quaternion.Slerp(currQuaternion, transform.rotation,
  Time.deltaTime);

private Transform player; private Vector3 offset; private float cameraMovingSpeed = 2; private float cameraRotateSpeed = 10;private void Start() {player = GameObject.FindWithTag(Tags.player).transform;// 保持在x軸方向值相同offset = transform.position - player.position; }private void Update() {// 得到起始點和終點Vector3 beginPos = player.position + offset;Vector3 endPos = player.position + offset.magnitude * Vector3.up;// 得到四等分點Vector3[] posArray = new Vector3[5];float lerp = 0.25f;float currLerp = 0;for (int i = 0; i < posArray.Length; i++) {posArray[i] = Vector3.Lerp(beginPos, endPos, currLerp);currLerp += lerp;}// 判斷哪個點是適合的foreach (Vector3 pos in posArray) {RaycastHit hitInfo;if(Physics.Raycast(pos, player.position - pos, out hitInfo))Debug.DrawRay(pos, player.position - pos);print(hitInfo.collider);if(hitInfo.collider.tag == Tags.player) {// 在視野內(nèi)能看到Playertransform.position = Vector3.Lerp(transform.position, pos, Time.deltaTime * cameraMovingSpeed);// 改變攝像機(jī)的朝向 -- 使用Quaternion的差值進(jìn)行轉(zhuǎn)向Quaternion currQuaternion = transform.rotation;transform.LookAt(player);transform.rotation = Quaternion.Slerp(currQuaternion, transform.rotation, Time.deltaTime * cameraRotateSpeed);break; }}}}

后期運行發(fā)現(xiàn)bug -- 當(dāng)Player走到cctv_Cam下的collider的時候,也會導(dǎo)致camera視角變化
  將hitInfo.collider.tag == Tags.player修改為
  hitInfo.colllider.tag == Tags.player || hitInfo.collider.tag == Tags.cctvCollider

任務(wù)27:Navigation自動尋路導(dǎo)航網(wǎng)格

機(jī)器人的自動導(dǎo)航系統(tǒng) --?

把env_stealth_static和battle_bus設(shè)置為static (Navigation bake的目標(biāo))
Window->Navigation->Bake->Bake

檢查一下自動生成的區(qū)域是否正確 -- 發(fā)現(xiàn)網(wǎng)布下方的通路沒有打開


減小Agent Radius (增大區(qū)域的面積,離突起物更近的地方也可被視作可用區(qū)域)
減小Agent Height
Slope=0;因為在該場景下,是平面的,無需設(shè)置坡度
StepHeight=0;高度差小于這個值的,視為通路

任務(wù)28:添加機(jī)器人

Enemy: char_robotGuard -- AI實現(xiàn)、動畫實現(xiàn)、射擊實現(xiàn)等等

1. 防止碰撞墻體等
  添加碰撞器 Capsule Collider
  添加Rigidbody

2. 實現(xiàn)導(dǎo)航功能 -- 判斷機(jī)器人當(dāng)前應(yīng)該去的方向
  添加Nav Mesh Agent組件
    Radius 自身寬度
    Speed 移動速度
    Acceleration 加速度
    AngularSpeed 旋轉(zhuǎn)速度
    等等
  注:NavMeshAgent是可以控制運動的(直接設(shè)置position)
    但是由于動畫也會控制運動,故不使用

3. 實現(xiàn)發(fā)現(xiàn)Player功能 -- 即機(jī)器人視野和聽覺
  添加Sphere Collider作為Trigger
  當(dāng)Player在Trigger之內(nèi),會進(jìn)行機(jī)器人視野的檢測

將機(jī)器人做成Prefab

任務(wù)29:設(shè)置機(jī)器人的狀態(tài)機(jī)(動畫效果)

創(chuàng)建Animator,名為EnemyController

BaseLayer -- Idle, Locomotion,
將動畫humanoid_idle拖入狀態(tài)機(jī),set as default
創(chuàng)建BlendTree,名為Locomotion,表示機(jī)器人的運動
  機(jī)器人運動動畫:
    Walk/Run, Walk/RunLeft/RightShort/Medium/Wide
    TurnOnSpotLeft/RightA/B/C/D
    將這些動畫都添加如BlendTree的motion中
  參數(shù)為speed和rotation,因此為2d的BlendTree
    將BlendType改為2D Freeform Cartesian
  Compute Positions = Speed and AngularSpeed,數(shù)值會自動生成?


  可以看出來,每個點代表一個動畫狀態(tài),
  PosX表示轉(zhuǎn)向速度,PosY表示行走速度
  比如豎直中間兩個點上面的為Run,下面的為Walk

需要兩個Float Parameter變量分別對應(yīng)AngularSpeed: PosX和Speed: PosY

發(fā)現(xiàn)Idle其實應(yīng)該在Locomotion中,而不是單獨存在
  刪除Idle狀態(tài),將Idle動畫添加到Locomotion中

射擊動畫:
  weapon_lower/weapon_raise/weapon_shoot
  增加一個Layer,稱為Shoot
  添加一個bool Parameter: playerInSight,用來判斷是否處于射擊狀態(tài)
  添加一個Empty狀態(tài)作為默認(rèn)狀態(tài) default
  transition: default -> weapon_raise :?playerInSight?= true
  transition: weapon_raise -> weapon_shooting : 沒寫condition時表示接著播放
    這里也可以寫上playerInSight=true作為條件,表示raise后如果還是true才射擊
    如果為false了就不進(jìn)行射擊了
  transition: weapon_raise -> weapon_lower : playerInSight = false;
  transition: weapon_shoot -> weapon_lower :?playerInSight = false;
    如果playerInSight依然為true,則會進(jìn)行循環(huán)播放
  transition: weapon_lower -> default : no condition

因為射擊動畫只控制了手的動作而沒有控制身體
  因此需要創(chuàng)建一個身體遮罩
  在Project中create->Avatar Mask,命名為EnemyShootMask
  在Inspector中選擇Humanoid,將全身禁止只留下雙手和頭部

點擊狀態(tài)機(jī)的Shoot Layer旁邊的設(shè)置按鈕,將EnemyShootMask賦值
  
  因為這一Layer權(quán)重比上一層高,所以Weight = 1,并選用Override

任務(wù)30:機(jī)器人的聽覺和視覺

聽覺:Sphere Collider
視覺:前方錐形范圍 110°

添加腳本EnemySight.cs

private bool isPlayerInSight = false;
private float viewFieldAngle = 110;

視覺:

當(dāng)主角在SphereCollider范圍內(nèi) -- 在OnTriggerStay() 中
  if(other.tag = player) {
    // 得到兩個方向:enemy前方和enemy朝向player的方向
    Vector3 forward = transform.forward;
    Vector3 playerDir = other.transform.position - transform.position;
    float tempAngle = Vector3.Angle(forward, playerDir);
    // 判斷是否在錐形視野范圍之內(nèi)
    if(tempAngle <= viewFieldAngle * 0.5) {
      playerInSight = true;
    } else {?playerInSight = false; }
  }

當(dāng)主角不在SphereCollider范圍內(nèi)(不在聽覺范圍內(nèi)) -- OnTriggerExit()?
  if(other.tag == player) {
    playerInSight = false; // 出了這個范圍肯定是看不到了
  }

聽覺:機(jī)制 -- 兩點之間可通路的最短距離(Navigation),即墻等障礙物是完全隔音的
  聽到聲音后,會設(shè)置警報位置,讓Enemy去追蹤

存儲當(dāng)前警報位置 -- 被發(fā)現(xiàn)的Player位置
public Vector3 alertPosition = Vector3.zero; // 需要被外界使用,默認(rèn)為zero
在上面Player被看到時,playerInSight=true;后加上 alertPos=other.transform.position;
  同理,playerInSight=false;后加上?alertPos=Vector3.zero; // 歸零

在TriggerStay中(因為Trigger就是表示可以聽到聲音的范圍)
// 判斷player是否處于Locomotion狀態(tài)
if(playerAnim.GetCurrentAnimatorStateInfo(0).IsName("Locomotion")) {
  // 使用NavMeshAgent判斷最短路徑距離
  // private NavMeshAgent navAgent = GetComponent<NavMeshAgent>();
  // 因為navAgent.CalculatePath(targetPos, navMeshPah), 需要一個空Path傳入
    -- calculate a path to a specified point and stored the resulting path
     ? return a bool if there is a path
  NavMeshPath navPath = new NavMeshPath();
  // navPath.corners,為一個存放所有路徑拐點的Vector3數(shù)組
  if(navAgent.CalculatePath(other.transform.position, navPath)) {
    Vector3[] waypoints = new Vector3[navPath.cornors.Length+2];

// 給所有路徑點賦值
waypoints[0] = transform.position;
waypoints[waypoints.Length-1] = other.transform.position;
for(int i = 1; i < waypoints.Length - 1; i++) {
  waypoints[i] = navPath.corners[i-1];
}

// 計算總長度
float totalDistance = 0;
for(int i = 1; i < waypoints.Length; i++) {
  // 從第二個點開始,計算與前一個點之間的距離
  totalDistance += (waypoint[i-1] - waypoint[i]).magnitude;
}

// 總長度與最大長度進(jìn)行比較,判斷是否在最大距離之內(nèi)
private SphereCollider sphereCollider = GetComponent<...>();
if(totalDistance <= sphereCollider.radius) { // 可以聽到
  alertPosition = other.transform.position;

private void OnTriggerStay(Collider other) {if(other.tag == Tags.player) {// Player進(jìn)入聽覺視覺觸發(fā)區(qū)域// 得到兩個方向:enemy前方和enemy朝向player的方向Vector3 forward = transform.forward;Vector3 playerDir = other.transform.position - transform.position;float tempAngle = Vector3.Angle(forward, playerDir);// 判斷是否在錐形視野范圍之內(nèi)if(tempAngle <= viewFieldAngle * 0.5f) {playerInSight = true;// 更新警報位置alertPosition = other.transform.position;} else {playerInSight = false;alertPosition = Vector3.zero;}// 聽覺實現(xiàn) -- 通路距離在給定范圍之內(nèi),則進(jìn)行追蹤if (playerAnim.GetCurrentAnimatorStateInfo(0).IsName("Locomotion")) {// 若當(dāng)前Player狀態(tài)為Locomotion -- 有聲音發(fā)出NavMeshPath navPath = new NavMeshPath();if (navAgent.CalculatePath(other.transform.position, navPath)) {Vector3[] waypoints = new Vector3[navPath.corners.Length + 2];waypoints[0] = transform.position;waypoints[waypoints.Length - 1] = other.transform.position;for (int i = 1; i < waypoints.Length - 1; i++) {waypoints[i] = navPath.corners[i - 1];}// 計算路徑總長度float totalDistance = 0;for (int i = 1; i < waypoints.Length; i++) {totalDistance += (waypoints[i-1]-waypoints[i]).magnitude;}// 判斷是否在最大距離內(nèi) -- 最大距離即為SphereCollider的半徑if (totalDistance <= sphereCollider.radius) {alertPosition = other.transform.position; }}}}}private void OnTriggerExit(Collider other) {if(other.tag == Tags.player) {playerInSight = false;alertPosition = Vector3.zero; }}

運行,聽覺和視覺都正常運行
  Bug1: 但是,因為SphereCollider的關(guān)系,Camera.FollowPlayer()中需要加上另一個條件
    hitInfo.collider.tag == Enemy

  Bug2: 機(jī)器人移動到門附近的時候,因為SphereCollider范圍很大,會觸發(fā)門的開關(guān)
    而只有當(dāng)CapsuleCollider進(jìn)入的時候才會觸發(fā)門的開關(guān)
    改為:if(tag... && other.GetType().ToString()=="UnityEngine.CapsuleColiider")
      或: if?(other.tag?==?Tags.enemy?&&?other?is?CapsuleCollider)?{
      或:?if?(other.tag?==?Tags.enemy?&&?other.isTrigger == false)?{
    即可

任務(wù)31:機(jī)器人發(fā)起警報的功能

警報機(jī)制:當(dāng)Enemy看到Player時,通知其他機(jī)器人進(jìn)行追蹤
  看到或聽到都會更新警報發(fā)出位置

記得之前在GameController中有一個警報方法SwitchAlarmOn將isAlarmOn設(shè)置為true

那么,在EnemySight.cs中,
  當(dāng)isPlayerInSight為true時,加上一句:
  GameController._instance.SwitchAlarmOn(other.transform); // 將player位置傳遞

實現(xiàn)了Enmey看到Player時,開啟警報
還需實現(xiàn)追蹤功能
  機(jī)制:alertPos不等于Vector3.zero時,就進(jìn)行追蹤
     而且,GameController中的lastPlayerPos修改時 (其他警報器),也要進(jìn)行追蹤

當(dāng)lastPlayerPos修改時,更新
  // (Awake()中)用來存儲之前的lastPlayerPos;
  private Vector3 preLastPlayerPos = GameController._instance.lastPlayerPos;
  // 進(jìn)行檢測 -- Update()中
  if(preLastPlayerPos != GameController._instance.lastPlayerPos) {
    alertPosition =?GameController._instance.lastPlayerPos;
    preLastPlayerPos = alertPosition;
  }

報錯: preLastPlayerPosition?=?GameController._instance.lastPlayerPos;
  
原因:GameController.cs中_instance = this的賦值在Awake()中
     而preLastPlayerPos這句話的賦值也是在Awake()中且用到了_instance
     因此可能會出現(xiàn)空指針的錯誤

解決方法:將preLastPlayerPos的賦值放在Start()中

現(xiàn)在可以通過cctvCam和laser觸發(fā)警報并修改alertPosition的值,
Bug -- 在cctvCam中移動不會更新lastPlayerPos:
  cctvCamCollision.cs中的觸發(fā)條件改為OnTriggerStay() 而不是OnTriggerEnter()

Bug -- 在SphereCollider之內(nèi)且playerInSight=false時,alertPosition會被強(qiáng)制設(shè)為zero
  因為在兩個else情況下,把alertPosition設(shè)置為了Vector3.zero
  解決方法:去掉OnTriggerStay中的設(shè)置即可,OnTriggerExit需要設(shè)置為zero

任務(wù)32&33:使用Navigation控制機(jī)器人的巡邏 && 行走的動畫

創(chuàng)建空物體waypoints,用來存儲巡邏的路徑點
  在里面創(chuàng)建若干個空物體waypoint -- 用來表示路徑的下一個臨時目標(biāo)位置
  分別在Scene中設(shè)置position
    但是空物體在環(huán)境中是沒有圖標(biāo)表示的,因此不好觀察
    在Inspector中物體名的左邊可以選擇icon->other->waypoint

注意:使用NavMeshAgent進(jìn)行控制,是直接設(shè)置transform的

創(chuàng)建腳本EnemyMoveAI.cs

// 定義數(shù)組,存儲waypoints的位置
public Transform[] waypoints = new Transform[4];
private int index = 0; // 當(dāng)前目標(biāo)索引

巡邏的方法:
private void Patrolling() {
  -- 機(jī)制:巡邏到達(dá)一個waypoint后,需要休息一段時間,再去下一個waypoint
  -- private float patrolTimer = 0;
  -- private float patrolWaitingTime = 3;
  // 需要使用NavMeshAgent進(jìn)行路徑導(dǎo)航
  -- private NavMeshAgent navAgent = GetComponent<...>();
  -- Awake()中,設(shè)navAgent.destination = waypoints[index].position; //index=0
  // 檢測有沒到達(dá)目標(biāo)位置
  if(navAgent.remainingDistance < 0.5f) {
    // 休息
    navAgent.isStoped = true;
    patrolTimer += Time.deltaTime;
    if(patrolTimer >= patrolWaitingTime) {
      // 休息完畢,下一個點
      // 一共四個點,循環(huán)播放,index不能超過三
      index++;
      index %= 4;
      navAgent.destination = waypoints[index].position;
      navAgent.isStoped = false; // 開始進(jìn)行下一個點的尋路
      patrolTimer = 0;
  }}

現(xiàn)在,機(jī)器人可以進(jìn)行四個點的巡邏的,但是,沒有行走動畫的播放,僅僅是位置的移動
  NavMeshAgent提供的尋路為直接修改transform的,我們利用它的指向功能
  結(jié)合動畫,來實現(xiàn)動畫版的巡邏

將NavMeshAgent的位置控制取消
  navAgent.updatePosition = false;
  navAgent.updateRotation = false;?

新建腳本EnemyAnimationController.cs -- 通過控制狀態(tài)機(jī)來控制行動

private NavMeshAgent navAgent = GetComponent<...>();

在Update()里
navMesh.desiredVelocity // 期望速度 -- 當(dāng)前移動的速度和方向
思路:navMesh的期望速度設(shè)置給用來控制動畫狀態(tài)機(jī)的Speed和AngularSpeed

當(dāng)休息時(即navAgent.isStopped = true)
if(navAgent.desiredVelocity = Vector3.zero) {
  enemyAnim.SetFloat("speed", 0);
  enemyAnim.SetFloat("angularSpeed", 0);
  // 注意 重載 SetFloat(name, value, dampTime, deltaTime)
    dampTime: 阻尼時間,在該時間內(nèi),將name逐漸設(shè)置為value,漸變過程
  // 直接設(shè)置,動畫有時候會顯得突兀,這里先不用,看看效果不好再用
} else {? -- 行走狀態(tài)

  // 計算角度AngularSpeed
  float angle = Vector3.Angle(transform.forward, navAgent.desiredVelocity);
  // anim中的AngularSpeed為弧度值
  float angleRad = Mathf.Deg2Rad * angle;? // Dot = 2PI/360
  // 算出弧度的方向(正負(fù)) -- 根據(jù)動畫來說,右轉(zhuǎn)為正
  -- Vector3.Cross(a,b)
    方向:應(yīng)用左手定則,result向量(中指)分別垂直于a(拇指)和b(食指)
    大小:為a和b大小的乘積
  思路:通過Cross的方向即可得angleRad的正負(fù):向上為正,向下為負(fù)
  Vector crossResult = Vector3.Cross(...forward, ...desiredVelocity);
  if(crossResult.y < 0) {?angleRad?= -angleRad; }
  anim.SetFloat("angularSpeed", angularSpeed);

  思路:當(dāng)角度大于九十度時,沒必要往前走了,便設(shè)定為原地旋轉(zhuǎn),
     當(dāng)角度小于九十度時,邊走邊旋轉(zhuǎn)
  if(angle >= 90) {
    anim.SetFloat("speed", 0);
  } else {
    // 計算當(dāng)前速度
    
    // ab為當(dāng)前朝向,ac為desiredVelocity
    // 當(dāng)前速度設(shè)為ac在ab方向上的分速度ad即可
    慢慢加快速度,動畫會平滑很多
    -- Vector3.Project(vector, onNormal)?:
     ? 返回vector在onNormal方向上的分向量projection
    Vector3 projection = Vector3.Project(...desiredVelocity, ...forward);
    anim.SetFloat("speed", projection.magnitude);
  }

Bug -- NavMeshAgent的位置與機(jī)器人的位置分離了
  Siki上沒有出現(xiàn)這個情況
  同樣的問題:http://tieba.baidu.com/p/5313022190?traceid=
  解決方法:在Update() 最后將NavMeshAgent的位置設(shè)置為transform的位置即可
    navAgent.nextPosition = transform.position;

public class EnemyAnimationController : MonoBehaviour {public float speedDampTime = 0.3f;public float angularSpeedDampTime = 0.3f;private Animator enemyAnim;private NavMeshAgent navAgent;void Awake () {enemyAnim = GetComponent<Animator>();navAgent = GetComponent<NavMeshAgent>();}void Update () {// 如果navAgent.isStopped = true -- 休息狀態(tài)if(navAgent.desiredVelocity == Vector3.zero) {enemyAnim.SetFloat("speed", 0, speedDampTime, Time.deltaTime);enemyAnim.SetFloat("angularSpeed", 0, angularSpeedDampTime, Time.deltaTime);} else {// 行走狀態(tài)// 旋轉(zhuǎn)角度大小float angle = Vector3.Angle(transform.forward, navAgent.desiredVelocity);float angleRad = angle * Mathf.Deg2Rad;// 旋轉(zhuǎn)角度方向Vector3 crossResult = Vector3.Cross(transform.forward, navAgent.desiredVelocity);if(crossResult.y < 0) { // 左轉(zhuǎn)angleRad = -angleRad;}enemyAnim.SetFloat("angularSpeed", angleRad, angularSpeedDampTime, Time.deltaTime);// 兩種情況,角度大于90時,原地旋轉(zhuǎn);小于90時,邊走便旋轉(zhuǎn)if(angle >= 90) {// 原地旋轉(zhuǎn)enemyAnim.SetFloat("speed", 0, speedDampTime, Time.deltaTime);} else {// 計算speed -- desiredVelocity在當(dāng)前方向forward上的分速度Vector3 projection = Vector3.Project(navAgent.desiredVelocity, transform.forward);enemyAnim.SetFloat("speed", projection.magnitude, speedDampTime, Time.deltaTime);}}navAgent.nextPosition = transform.position;} }

任務(wù)34:機(jī)器人的追捕 AI

機(jī)器人的三種狀態(tài):巡邏、追捕、射擊
  巡邏:普通
  追捕:alertPosition != Vector3.zero
  射擊:EnemySight.isPlayerInSight == true;

EnemyMoveAI.cs中

追捕方法:

private void Chasing() {
  nagAgent.speed = 5; // 追捕時速度快 (記住在Patrolling()中navAgent.speed=3;)
  navAgent.destination = enemySight.alertPosition; (EnemySight sight = ...)
  // 追捕時,目標(biāo)距離大一些,就可進(jìn)行射擊()
  if(navAgent.remainingDistance < 2f) {
    到達(dá)目標(biāo)位置后,若看見Player就自動射擊(在Animator和Shooting()中控制)
    如果沒看見,alertPosition也沒更新,就在原地停留一段時間,解除警報
    -- public chaseWaitingTime = 5;
    -- private chaseTimer = 0;
    chaseTimer += Time.deltaTime;
    if(chaseTimer > chaseWaitingTime) {
      // 解除警報,回到巡邏位置
      sight.alertPosition = Vector3.zero;
      GameController._instance.lastPlayerPosition = Vector3.zero;
      GameController._instance.isAlarmOn = false;
      index = 0;
      navAgent.destination = waypoints[index];
      chaseTimer = 0; //計時器歸零
    }

任務(wù)35:解決bug:視野檢測和自動門的開啟和關(guān)閉

Bug -- 視野檢測:若Player與Enemey之間有障礙物(墻),Enemy也是可見Player的
  解決方法:若在視野viewFieldAngle內(nèi),則在Enemy眼睛處發(fā)射一條射線,方向為playerDir

Raycast的起點為transform.position + Vector3.up * 1.8f; 終點為alertPos
RaycastHit hitInfo;
Physics.Raycast(transform.position+Vector3.up*1.8f, playerDir, out hitInfo);
if(hitInfo.collider.tag == Tags.player) {
  在視野內(nèi)
}

if (tempAngle <= viewFieldAngle * 0.5f) {// 判斷是否有障礙物(如墻)擋住視線 RaycastHit hitInfo;Physics.Raycast(transform.position + Vector3.up * 1.8f, playerDir, out hitInfo);if(hitInfo.collider.tag == Tags.player) {playerInSight = true;// 更新警報位置alertPosition = other.transform.position;GameController._instance.SwitchAlarmOn(other.transform);} else {playerInSight = false;} } else {playerInSight = false; }

Bug -- 門的控制在Player和Enemy同時進(jìn)出的時候有問題
  解決方法:將player和enemy分別判斷并各自count++/--即可

任務(wù)36&37:機(jī)器人的射擊動畫 && 傷害計算

?

射擊動畫:

void?Update?()?{
????if(enemySight.playerInSight)?{
????????//?只要能看見Player,就射擊
????????Shoot();
????}?else?if?(enemySight.alertPosition?!=?Vector3.zero)?{
????????//?有動靜?--?進(jìn)行追捕
????????Chasing();
????}?else?{
????????Patrolling();
????}
}

在Shoot()中不處理動畫
  navAgent.isStopped = true; // 停止導(dǎo)航

-- 調(diào)用EnemyAnimationController.cs來控制射擊動畫的播放
在Update()控制行走的代碼后,通過playerInSight的賦值來控制動畫
  private EnemySight enemySight = GetComponent...
  enemyAnim.SetBool("playerInSight", enemySight.playerInSight);

傷害計算:

在player上添加腳本PlayerHealth.cs

public float hp = 100; // 血量

private void TakeDamege(float damage) {
  hp -= damage;
}

public bool isPlayerAlive() {
  return (hp>0);

在EnemyMoveAI.cs中
  在Update()判斷Shoot()或Chasing()時,加上判斷如果isPlayerAlive才進(jìn)行該操作

檢測enemy狀態(tài)機(jī)中humanoid_weapon_shoot動畫的播放:播放一次進(jìn)行一次傷害計算
  動畫->Inspector->Curves->Shot:看曲線的變化
  在狀態(tài)機(jī)中設(shè)置一個參數(shù),用來記錄上面Shot曲線的當(dāng)前值
    添加Parameter float Shot, 這個值會自動被曲線Shot的當(dāng)前值賦值

在Enemy上添加腳本EnemyShooting() -- 用來處理射擊有關(guān)的操作

在Update()中
if(anim.GetFloat("Shot") > 0.5f) { // 進(jìn)行了一次射擊
  Shooting();
} else {
  hasShot = false; // 沒有開槍,則標(biāo)志位重置
}

傷害機(jī)制:距離越近傷害越大,傷害有保底值
  主角在SphereCollider范圍之內(nèi)會進(jìn)行射擊,則在最外側(cè)傷害最低
public float minDamage = 30;

private void Shooting() {? // 計算傷害
  -- private bool hasShot = false;
  if(!hasShot) { // 進(jìn)行傷害
    -- 從PlayerHealth腳本獲得
    // 計算傷害
    float distance = Vector3.Distance(transform.position, playerHealth.transform.pos);
    float damage = minDamage+ (100-minDamage)*(distance/sphereCollider.radius);
    playerHealth.TakeDamage(damage);
    hasShot = true;
  }

Player死亡動畫:

回顧:在Player的動畫狀態(tài)機(jī)中的Parameter bool dead;
  dead = true時 播放dead動畫

在PlayerHealth.cs中持有animator
  在TakeDamage() 中判斷
    if(!IsPlayerAlive()) {
      anim.SetBool("dead", true);
    }

Bug -- Player死亡以后,enemy還是會在盯著,沒有回去巡邏
  解決方法:在EnemySight中觸發(fā)playerInSight的判斷條件里加上
    && playerHealth.isPlayerAlive()

  而且Shooting()時把navAgent設(shè)為Stopped了
? ? ?   Enemy從Shooting轉(zhuǎn)為Patrolling時候需要重新設(shè)置開始
    navAgent.isStopped = false;
    // navAgent.destination =? waypoints[index].position; // 不要也行

Bug -- Enemy在PlayerInSight=true的狀態(tài)下正在射擊,但Player跑出了射擊范圍
  此時Enemy就會呆在原地不動
  思路:與上面一個bug相似,Shooting后navAgent被stop了,下一幀轉(zhuǎn)為Chasing()時還是true
    在Chasing()剛開始的時候,navAgent.isStopped = false;
    

Bug -- Player死亡以后,死亡動畫會不斷播放
  思路:猜想是因為狀態(tài)機(jī)中Any State--dead=true-->Dead
    AnyState也包括了Dead本身,于是有Dead->Dead的動畫
      https://blog.csdn.net/ln_polaris/article/details/50724425
    將transition屬性取消勾選Can Transition To Self即可

任務(wù)38&39:添加其他機(jī)器人 和 游戲失敗狀態(tài) && 游戲勝利狀態(tài)

將robot apply給prefab

創(chuàng)建空物體Robots,用來存儲其他兩個敵人(一共三個)

創(chuàng)建對應(yīng)的waypoints

游戲失敗狀態(tài):
  機(jī)制:死亡之后,等待4秒,重新加載Scene

IEnumerator ReloadScene() {
  yield return new WaitForSeconds(4f);
  SceneManager.LoadScene(0);
}

在TakeDamage()中
  在播放死亡動畫之后
  StartCoroutine(ReloadScene());

播放失敗音效
  添加AudioSource組件: endgame
  defeatAudio.Play();

游戲勝利狀態(tài):
  機(jī)制:勝利之后,啟動電梯,并重新加載Scene

prop_lift_exit

添加BoxCollider作為Trigger

編輯之前創(chuàng)建的腳本LiftController.cs

private float liftUpWaitingTime = 3;
private float liftUpTimer = 0;
private bool isPlayerInside = false;

分別在OnTriggerEnter/Exit()中控制isPlayerInside,記得判斷collider.tag為player

在Update()中:
  if(isPlayerInside) {? // player進(jìn)入電梯
    liftUpTimer += Time.deltaTime;
    if(liftUpTimer > liftUpWaitingTime) { // 等待時間到,升起電梯
      -- 電梯只會升起一次
      // 播放電梯升起音效
      liftUpAudio.Play();
    }

發(fā)現(xiàn),若干秒后電梯向上移動了,但是:

1. Player沒有隨著電梯上升而上升
  原因:電梯地板沒有設(shè)置collider
    給電梯添加Mesh Collider, 選擇mesh: prop_lift_exit)collision_001
  原因:player的position.y在Inspector中Freeze,取消勾選

2. 電梯上升了一小段便停下了
  原因未知,但是修改完bug1后這個bug自動消失了

電梯上升后,游戲勝利
  機(jī)制:電梯上升后一段時間,宣布游戲勝利,重新載入Scene

private float gameWinTimer = 0;
private float gameWinWaitingTime = 4;

在電梯上升的代碼塊中:
gameWinTimer += Time.deltaTime;
if (gameWinTimer > gameWinTime) {
  SceneManager.LoadScene(0);
}

public class LiftController : MonoBehaviour {...private float liftUpWaitingTime = 3;private float liftUpTimer = 0;private float gameWinWaitingTime = 5;private float gameWinTimer = 0;private bool isPlayerInside = false;void Update () {...// 升起電梯if(isPlayerInside) {// Player在電梯內(nèi)liftUpTimer += Time.deltaTime;if(liftUpTimer > liftUpWaitingTime) {// 等待時間結(jié)束transform.Translate(Vector3.up * Time.deltaTime);gameWinTimer += Time.deltaTime;if(gameWinTimer > gameWinWaitingTime) {SceneManager.LoadScene(0);}}}}private void OnTriggerEnter (Collider other) {if(other.tag == Tags.player){isPlayerInside = true;}}private void OnTriggerExit (Collider other) {if(other.tag == Tags.player) {isPlayerInside = false; }}}

任務(wù)40:游戲提示設(shè)置 和 總結(jié)

創(chuàng)建UI->Text,字體為Sansation_Light,位置設(shè)置為左下角,顏色灰白色

創(chuàng)建腳本MessageShow.cs

void Start () {msg = GetComponent<Text>();msg.text = "WASD to Move\n" +"Z to Switch Off\n" +"LeftShift to Sneak"; }

?

?

?

轉(zhuǎn)載于:https://www.cnblogs.com/FudgeBear/p/8717949.html

總結(jié)

以上是生活随笔為你收集整理的Siki_Unity_2-7_Stealth秘密行动的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

色av色av色av | 久操视频在线免费看 | av日韩精品 | 久久久精品小视频 | 大胆欧美gogo免费视频一二区 | 国产精品久久二区 | 狠狠干干 | 中文区中文字幕免费看 | .国产精品成人自产拍在线观看6 | 亚洲精品欧洲精品 | 亚洲黄色在线观看 | 片网址| 狠狠狠狠狠狠狠干 | 亚洲在线色| 天天草天天草 | 综合久久久久久久久 | 成人午夜黄色 | 国产一级在线播放 | 亚洲天天综合网 | 欧美一区二区视频97 | 亚洲欧洲一级 | 久影院| 99精品偷拍视频一区二区三区 | 特级大胆西西4444www | 99国产精品久久久久久久久久 | 久草在线资源观看 | 久草| 久99久视频 | 久久精品直播 | 在线有码中文字幕 | 中文字幕亚洲欧美 | 久久九九影院 | 毛片视频网址 | 狠狠操天天射 | 欧美性久久久久久 | 亚洲黄污 | 亚洲黄色在线播放 | 777视频在线观看 | 欧美a√在线 | 一级片免费在线 | 深爱激情五月婷婷 | 日韩av图片 | 亚洲视频456 | 中文字幕乱偷在线 | 日韩国产精品久久 | 日韩欧美精品在线视频 | 日韩毛片一区 | 久久精品99国产精品酒店日本 | 日本不卡久久 | 麻豆免费视频网站 | 国产日韩精品一区二区 | 国产日产欧美在线观看 | 久久夜靖品 | 精品在线观看一区二区三区 | 日本中文字幕在线电影 | 日韩在线观看视频中文字幕 | 久草视频在线观 | 丁香激情综合久久伊人久久 | 免费视频三区 | 日韩成片| 国产成人资源 | 一二三精品视频 | 国际精品久久久久 | 久久久亚洲麻豆日韩精品一区三区 | 久久精品成人热国产成 | 欧美性成人 | 国模视频一区二区三区 | 97网| 国产无吗一区二区三区在线欢 | 尤物九九久久国产精品的分类 | 成年人在线| 国产精品剧情 | 久久久www成人免费毛片麻豆 | www成人精品 | 超碰在线成人 | 超碰免费久久 | 狠狠色丁香婷婷综合久小说久 | 99热国产在线 | 99国产成+人+综合+亚洲 欧美 | 国产日韩高清在线 | 日韩一区二区三区观看 | 97视频久久久 | 免费看日韩片 | 色网站免费在线看 | 黄色在线免费观看网站 | 日日爽天天爽 | 天天天天色射综合 | 久久伊人八月婷婷综合激情 | 免费在线观看中文字幕 | 精品美女久久久久 | 日韩色高清 | 国产视频日韩 | 五月天婷亚洲天综合网鲁鲁鲁 | 97电影网手机版 | 国产一级二级在线播放 | 99精品一区 | 91综合久久一区二区 | 超碰97久久| 中文字幕在线国产 | 激情视频一区二区三区 | 天天激情天天干 | 欧美精品一区二区三区四区在线 | 亚洲免费在线看 | 色狠狠婷婷 | 日韩毛片在线一区二区毛片 | 女人18毛片90分钟 | 久久免费电影网 | 久久精品视频网址 | 成人av电影免费在线播放 | 香蕉视频18 | 日日操夜 | 中文字幕专区高清在线观看 | 综合色在线观看 | 久久96国产精品久久99软件 | 一区二区三区久久 | 日韩高清一区在线 | 国产第一页在线播放 | 亚洲视频在线免费观看 | 夜夜躁日日躁狠狠躁 | 国产精品久久久久一区二区三区共 | 天天玩夜夜操 | 天天操天天射天天舔 | 在线观看午夜 | 亚洲免费小视频 | 免费黄色特级片 | 91网免费观看 | 久久视频这里有久久精品视频11 | 在线精品视频在线观看高清 | 青草视频在线免费 | 国内精品久久久久久久 | 婷婷丁香花 | www日韩精品 | 少妇搡bbbb搡bbb搡69 | 天天爽天天碰狠狠添 | 国产录像在线观看 | 经典三级一区 | 亚洲欧美日韩精品久久久 | 国产精品国产三级国产aⅴ无密码 | 天堂网一区 | 91丨九色丨91啦蝌蚪老版 | 91亚洲精品在线观看 | 中文视频一区二区 | 国产精品一区电影 | 香蕉视频亚洲 | 免费a级黄色毛片 | 制服丝袜在线 | 亚洲一级片| 久久久国产精品人人片99精片欧美一 | 99资源网 | 色多视频在线观看 | 国产伦理久久精品久久久久_ | 五月天色中色 | 中文字幕av电影下载 | 欧美精品亚州精品 | 欧美男同视频网站 | 成人av网站在线观看 | 色99色| 久久视频国产精品免费视频在线 | 国产色一区| 成人中文字幕+乱码+中文字幕 | 久久不射电影网 | 99精品国产99久久久久久97 | 久久综合偷偷噜噜噜色 | 麻豆国产在线播放 | 久久综合9988久久爱 | 激情综合啪啪 | 天天看天天操 | 美女免费视频网站 | 欧美久久久久久久久中文字幕 | 欧美性猛片, | 国产高清不卡在线 | 久久久久色| 中文字幕一区二区三区精华液 | 五月婷婷狠狠 | 日韩午夜视频在线观看 | 久久无码精品一区二区三区 | 久久av影视 | 亚洲成av人片在线观看www | 久久亚洲区 | 亚洲国产福利视频 | 五月婷婷综 | 天天色天天综合 | 亚洲精品国内 | 久草在线视频新 | 西西4444www大胆视频 | 在线播放精品一区二区三区 | 成人av教育| 天堂中文在线视频 | 欧美极度另类 | 亚洲一级电影在线观看 | 色婷婷国产精品一区在线观看 | 福利网在线 | 黄色成年网站 | 欧美一级网站 | 国内精品久久久久久久影视麻豆 | 免费高清在线视频一区· | 欧美成年黄网站色视频 | 91日韩在线专区 | 日韩精品免费一区二区在线观看 | 国产精品小视频网站 | 亚洲精品观看 | 91香蕉视频720p | 亚洲 欧美 成人 | 热久久影视 | 91丨九色丨国产丨porny精品 | 国产精品露脸在线 | 久久a v视频 | 国产69熟 | 国产原创中文在线 | 激情伊人五月天 | 国产一区二区中文字幕 | 天堂v中文 | 亚洲一级免费电影 | 丁香 久久 综合 | 91成人精品视频 | 激情久久网 | 国产一级免费视频 | 欧美中文字幕第一页 | 久草网在线观看 | 免费午夜视频在线观看 | 午夜一级免费电影 | 国产自制av | 国产精品久久久久久久久久三级 | 色婷婷啪啪免费在线电影观看 | 久草资源在线观看 | 一区二区伦理电影 | wwxxxx日本 | 国产传媒中文字幕 | 日韩在线观看视频在线 | 香蕉一区| 久久99热久久99精品 | 久草在线免费色站 | 天天操导航 | 国产福利资源 | 欧美成人日韩 | 亚洲永久字幕 | 特级黄色片免费看 | 欧美日本国产在线观看 | 亚洲人成精品久久久久 | 国产999精品久久久久久绿帽 | 久久精品欧美日韩精品 | 999免费视频 | 久久99国产精品视频 | 精品国产自在精品国产精野外直播 | 永久免费的啪啪网站免费观看浪潮 | 婷婷色网视频在线播放 | 娇妻呻吟一区二区三区 | 亚洲香蕉视频 | 黄色av电影在线观看 | av五月婷婷 | 天天色天天射天天综合网 | 亚洲午夜小视频 | 成人久久18免费网站麻豆 | 六月激情| 国产视频亚洲视频 | 日韩精品 在线视频 | 欧美性久久久久久 | 91九色九色| 99精品视频观看 | 色在线网站 | 99视频精品免费观看, | 伊人婷婷色 | 欧美激情精品久久久久久免费 | 精品黄色片 | 在线观看亚洲免费视频 | 日韩久久久 | 国产精品美女久久久久aⅴ 干干夜夜 | 久久久激情视频 | 国产高清无av久久 | 久久久不卡影院 | 久久久久久久久久影院 | 久久爱资源网 | 91精品国产91久久久久 | 欧美日韩视频一区二区 | 九九热只有这里有精品 | 超碰成人网 | 中文字幕av最新 | 成人在线免费观看视视频 | 久久久久久久久久网站 | 狠狠色综合欧美激情 | 久久久久色 | 天堂网av在线 | 999毛片| 亚洲蜜桃在线 | 婷婷色综合色 | 精品欧美一区二区在线观看 | 黄av在线| 久久精品成人热国产成 | 日韩高清 一区 | 97在线免费观看 | 国产正在播放 | 日韩在线第一区 | 丁香激情视频 | 美女久久久久 | 在线亚洲欧美日韩 | 久久99国产精品二区护士 | 999国内精品永久免费视频 | 操老逼免费视频 | 国产99久久久欧美黑人 | 国产精品一区二区三区四 | 美女免费视频一区二区 | 成人在线你懂得 | 精品影院一区二区久久久 | 亚洲狠狠丁香婷婷综合久久久 | 亚洲国产人午在线一二区 | 免费福利小视频 | 91九色综合 | 一区二区三区在线观看免费视频 | 四虎最新入口 | 精品黄色视 | 久久综合狠狠综合久久激情 | 在线观看理论 | 久久在视频 | 国产精品美女久久久久久久久 | 久久不射电影院 | 亚洲黄色激情小说 | 处女av在线 | av日韩不卡| 国产剧情亚洲 | 人人爽爽人人 | 久久人人添人人爽添人人88v | 国产r级在线观看 | 亚洲高清av在线 | 亚洲精品字幕在线观看 | 中文字幕av有码 | 色综合天天干 | 国产手机视频在线观看 | 久草在线资源观看 | 国产一级片不卡 | 中文字幕在线影院 | 一区二区三区在线观看免费视频 | 精品在线小视频 | 久久九九久久精品 | 国产毛片久久 | 国产精品青青 | 友田真希x88av | 黄色软件网站在线观看 | 国产传媒一区在线 | 欧美精品日韩 | 91亚洲视频在线观看 | 天天综合日日夜夜 | 97在线看| 亚洲视屏在线播放 | 日韩a级黄色 | 天堂麻豆 | 亚洲精品videossex少妇 | 六月天综合网 | 黄色国产在线观看 | 婷婷色在线 | 国产精品久久99精品毛片三a | 国产又粗又猛又色 | 国产不卡免费av | av大全免费在线观看 | 欧美日韩国产免费视频 | .国产精品成人自产拍在线观看6 | 在线观看免费黄视频 | 欧美另类xxx | 99综合电影在线视频 | 亚洲视频久久 | 天天干.com | 在线观看视频91 | 中文字幕av在线不卡 | 国产亚洲人成网站在线观看 | 不卡的av中文字幕 | 日本精品二区 | 九九综合九九 | 国产精品99久久久久久武松影视 | 国产97在线观看 | 国产精品专区h在线观看 | 久久伊人热 | 国产日韩精品欧美 | 视频成人 | 久久精品毛片基地 | 国产日韩在线一区 | 日韩欧美在线第一页 | 精品福利片| 91中文字幕在线观看 | 婷婷视频在线观看 | 91精品国产一区二区在线观看 | 亚洲日本va午夜在线影院 | 国产视频精选 | 99精品视频99 | 欧美日韩aa | 亚洲精品国偷拍自产在线观看蜜桃 | av在线之家电影网站 | 麻豆视频国产在线观看 | 丝袜av网站| 国产一区二区在线观看视频 | 黄色特一级片 | 成 人 黄 色 视频免费播放 | 久久另类小说 | 天天综合网在线 | 欧美日韩国产伦理 | 亚洲理论片在线观看 | 黄a网 | 久草视频在线播放 | 欧美一级视频免费看 | avove黑丝 | 天天色天天 | 中文字幕在线观看免费高清完整版 | 欧美亚洲国产精品久久高清浪潮 | 亚洲成av人影片在线观看 | 在线影院 国内精品 | 久久久国产精品一区二区中文 | 在线中文字幕视频 | 婷婷色综合 | 69欧美视频 | 欧美黑人巨大xxxxx | 在线观看中文字幕一区 | 国产精品高清在线观看 | 亚洲综合五月 | 黄色亚洲 | 日韩电影在线观看中文字幕 | 免费看的毛片 | 国产日产精品久久久久快鸭 | 国产亚洲精品久久19p | 久影院 | 超碰最新网址 | 国产精品乱码在线 | 狠狠色狠狠色综合日日92 | 亚洲 在线 | 日批视频| 免费观看日韩av | 91视视频在线直接观看在线看网页在线看 | 国产不卡在线观看 | 一区二区三区 中文字幕 | 久久久国际精品 | 亚洲国产97在线精品一区 | 一本一道久久a久久综合蜜桃 | 激情综合五月天 | 国产精品一区二区免费看 | 99视频+国产日韩欧美 | 97中文字幕| 特级毛片爽www免费版 | 天天色官网 | 香蕉视频一级 | 久久麻豆精品 | 欧美精品在线观看 | 久久综合色一综合色88 | 欧美成人69av | 国产一区二区三精品久久久无广告 | 2023年中文无字幕文字 | 天天天天综合 | 国产人免费人成免费视频 | 亚洲国产日韩欧美在线 | 日韩三级视频在线观看 | 免费亚洲视频在线观看 | 天天射天天干天天爽 | 国内揄拍国产精品 | 乱男乱女www7788 | 国内精品久久久精品电影院 | 日本二区三区在线 | 最近能播放的中文字幕 | 九九在线高清精品视频 | 国产成人免费高清 | 91传媒免费在线观看 | 国产 中文 日韩 欧美 | 美女免费网视频 | 国产精品视频区 | 国产亚洲一级高清 | 精品国产伦一区二区三区观看说明 | av中文字幕在线免费观看 | 成人免费看电影 | 国产精品99久久久久久有的能看 | aaa黄色毛片| 久久艹人人| 国产亚洲精品久久久久久移动网络 | 午夜性盈盈 | 免费黄色一区 | 国产精品久久久久久久久久不蜜月 | 亚洲国产片 | 欧美黄色成人 | 又大又硬又黄又爽视频在线观看 | 一级黄色毛片 | 夜夜嗨av色一区二区不卡 | 国产涩涩在线观看 | 911免费视频 | 麻豆免费观看视频 | 久久国产精品免费看 | 久草视频免费在线播放 | 免费网站黄 | 少妇bbb搡bbbb搡bbbb | 91男人影院 | 一区二区久久久久 | 色综合色综合久久综合频道88 | 国产r级在线观看 | 国产精品99久久久久久有的能看 | 亚洲手机av | 欧美精选一区二区三区 | 在线成人国产 | 亚洲天堂自拍视频 | 色噜噜色噜噜 | 亚州成人av在线 | 久久综合九色综合97_ 久久久 | 国产精品一区二区白浆 | 日日干天天操 | 久久第四色| 国产一级性生活 | 国产区精品区 | 91看片在线看片 | 91视频黄色 | 中文字幕精品视频 | 国产91小视频 | 日韩一区在线免费观看 | 午夜精品一区二区三区在线观看 | 国产精品久久久视频 | 国产精品乱看 | 亚洲国产一区在线观看 | 天天干 夜夜操 | 狠狠操.com | 一区二区中文字幕在线播放 | 成人av一区二区三区 | 狠狠狠狠狠狠天天爱 | 超碰97网站 | 超碰国产在线观看 | 日韩免费成人 | 亚洲三级黄色 | 日韩久久久 | 国产精品嫩草55av | 激情久久伊人 | 99资源网| 天天操天天色综合 | 在线视频精品播放 | 国产不卡在线看 | 黄色免费电影网站 | 国产视频不卡一区 | 福利视频入口 | 久久久午夜视频 | 97在线观视频免费观看 | 国产免费区 | 亚洲黄色在线免费观看 | av黄色影院 | 久久精品国产亚洲精品 | 成人毛片在线观看视频 | 亚洲精品国产精品久久99 | 天天爽夜夜爽精品视频婷婷 | 久草视频在线播放 | 国产一区观看 | 免费视频91 | 91手机电视| 亚洲精品色 | 免费日韩av电影 | 午夜婷婷在线观看 | 色婷婷av一区二 | 亚洲精品美女视频 | 91视频网址入口 | 黄色aaa级片 | 日韩欧美电影 | av日韩精品 | 国产一级片免费观看 | 日日天天| 美女免费电影 | 国产午夜精品视频 | 日韩在线不卡av | 亚洲高清色综合 | 国产精品久久久久久久久久新婚 | 91麻豆看国产在线紧急地址 | 激情小说网站亚洲综合网 | 日韩在线观看三区 | 青青河边草免费直播 | 久久国产精品视频观看 | 精品久久久久久综合 | 亚洲丝袜一区二区 | 天天曰天天射 | 亚洲精品午夜久久久久久久久久久 | 91免费网站在线观看 | 成人三级视频 | 在线观看一级 | 五月花激情 | 日本最新一区二区三区 | 免费在线观看av片 | 丝袜护士aⅴ在线白丝护士 天天综合精品 | 亚洲mv大片欧洲mv大片免费 | 亚洲第五色综合网 | 亚洲第一色| 国产精品日韩久久久久 | 亚州av一区 | aaa毛片视频 | 久久影院精品 | 国产精品一区二区吃奶在线观看 | 日韩在线观看电影 | 日韩在线视频网站 | 色就干| 婷婷丁香激情五月 | 日韩欧美成 | 99精品国产一区二区三区麻豆 | 免费看的黄色录像 | 国内久久精品视频 | 成人小视频在线观看免费 | 中文字幕免费国产精品 | 亚洲五月六月 | 国产又粗又猛又黄视频 | 国产精品免费视频观看 | 国产无套视频 | 日本在线精品视频 | 欧美日韩精品在线一区二区 | 国产在线自 | 在线视频 你懂得 | 热99在线视频 | 亚洲综合色视频 | 99久久精品国产观看 | 国产小视频在线观看 | 天天操夜夜操国产精品 | 激情欧美丁香 | 亚洲一区在线看 | 欧美成人久久 | 99久久精品无码一区二区毛片 | 亚洲精品福利在线 | 久久国内视频 | 国产中文字幕大全 | 激情av综合 | 91九色蝌蚪在线 | 91九色在线| 免费国产一区二区视频 | 五月综合在线观看 | 探花国产在线 | 日韩视频一区二区 | 女人18精品一区二区三区 | 97精品国产97久久久久久粉红 | 色综合亚洲精品激情狠狠 | 97国产小视频 | 国产在线无 | 最新av电影网站 | 国产原厂视频在线观看 | 91麻豆精品国产自产在线游戏 | 国产精品 国产精品 | 伊人色综合久久天天网 | 久久久久亚洲天堂 | 欧洲精品久久久久毛片完整版 | 黄a在线看 | 日本电影久久 | 91看片麻豆 | 久久国产精品影片 | 日韩三级免费 | 深爱激情综合网 | 久久久久久久久久久久av | 97av视频| 天天曰天天曰 | 国产99精品在线观看 | 99久久精品久久久久久清纯 | 免费黄色在线网址 | 91最新视频在线观看 | 激情综合网五月婷婷 | 日韩久久精品一区 | 又色又爽又激情的59视频 | 超碰在线免费97 | 日韩精品在线视频免费观看 | 九九欧美 | 91看片淫黄大片在线播放 | 亚洲免费av电影 | 日本激情中文字幕 | 婷婷激情小说网 | a黄色大片 | 开心综合网 | 狠狠ri| 人人狠 | av一二三区 | 国产网红在线观看 | 午夜影视av| 高清不卡一区二区三区 | 国产美女视频一区 | 天天夜夜亚洲 | 精一区二区 | www.av在线播放 | 成人一级电影在线观看 | 91探花在线视频 | 一区二区三区高清 | 麻豆传媒电影在线观看 | 激情图片久久 | 国产精品久久久久永久免费看 | 亚洲婷久久 | 99久久精品久久亚洲精品 | 久久av伊人 | 久久久视频在线 | 久久久国产精品一区二区中文 | av中文字幕在线免费观看 | 一级片黄色片网站 | 天天爽夜夜爽人人爽曰av | 毛片永久新网址首页 | 国产成人av一区二区三区在线观看 | 欧美成人h版 | 99久久er热在这里只有精品66 | 最近中文字幕完整视频高清1 | 天天干天天在线 | 欧美久久影院 | 国产剧情在线一区 | 精品福利在线视频 | 九九九热精品免费视频观看 | 97成人资源| 黄色午夜网站 | 国产一区在线播放 | 涩涩网站在线观看 | 亚洲精品高清视频 | 成人一区在线观看 | www.黄色网.com | 亚洲视频 中文字幕 | 91网在线 | 久久精品影视 | 日韩欧美v| 亚洲激情视频在线 | 国产精品女同一区二区三区久久夜 | 欧美性大战久久久久 | 久草在线免费看视频 | 中文字幕亚洲高清 | 不卡的av在线 | 日批在线观看 | 国产精品久久久久久久久久新婚 | 亚洲国产精品传媒在线观看 | 婷婷综合视频 | 91视频免费网站 | 国产在线第三页 | 久久久久久久99精品免费观看 | 91av久久| 在线观看激情av | 99国产在线视频 | 天天躁天天操 | 国产麻豆果冻传媒在线观看 | 国产精品久久久久永久免费 | 99亚洲国产| 黄色av网站在线免费观看 | 国产手机在线观看视频 | 五月天色站 | www黄色| 在线观看中文字幕视频 | www最近高清中文国语在线观看 | 在线观看爱爱视频 | 日日夜夜精品视频 | 色综合久久中文字幕综合网 | 黄色软件网站在线观看 | 亚洲欧美国产精品久久久久 | 国产欧美日韩精品一区二区免费 | 久久久久中文 | 日韩在线观看一区二区三区 | 国产精品一区二区三区视频免费 | 亚洲理论电影 | 在线看片日韩 | 98超碰人人 | 久久在线 | 午夜av免费观看 | 91麻豆传媒 | 日韩av在线网站 | 香蕉影院在线观看 | 一区二区三区在线观看免费视频 | 九九久久久 | 三级av中文字幕 | 在线v片免费观看视频 | 久久久免费精品国产一区二区 | 精品国产福利在线 | 91视频 - 88av | 国产综合久久 | 天天综合色 | 91亚洲精品久久久中文字幕 | 综合色综合色 | 日韩欧美视频在线播放 | 色婷婷www| 久草电影免费在线观看 | 欧美日韩精品综合 | 久久综合成人 | www.黄色| 91免费的视频在线播放 | 久久这里只有精品视频99 | 国产日韩精品一区二区三区在线 | 2020天天干夜夜爽 | 98超碰在线 | 四虎国产精品永久在线国在线 | 国产一区二区不卡在线 | 三日本三级少妇三级99 | 四虎www| 国产精品21区 | 日韩免费av片 | 91人人澡人人爽人人精品 | 婷色| 91成人免费观看视频 | 国产成人精品一区一区一区 | 亚洲最大色 | 91看片成人| 国产亚洲欧美日韩高清 | 久久中文字幕在线视频 | 麻豆传媒视频在线免费观看 | 久草视频观看 | 国产精品入口66mio女同 | 色综合久久中文字幕综合网 | 九九久久久久久久久激情 | 国产精品激情在线观看 | 日韩两性视频 | 亚洲精品合集 | 在线视频 国产 日韩 | 成人中心免费视频 | 国产精品视频在线观看 | 九九热视频在线免费观看 | 国产色 在线| 中文字幕在线免费97 | 国产又粗又硬又长又爽的视频 | www.久久色| 成人av免费在线 | 在线观看国产日韩欧美 | 精品福利视频在线观看 | 国产精品一区久久久久 | 爱射综合 | 精品一二三区 | 五月天激情综合 | 国产视频精品在线 | 五月婷婷视频在线观看 | .国产精品成人自产拍在线观看6 | 99久热在线精品视频成人一区 | 日韩av中文在线观看 | 日韩高清成人在线 | 日韩中文字幕电影 | 超碰电影在线观看 | av手机在线播放 | 正在播放国产91 | 日日日日| 天天做天天爱夜夜爽 | 天天色天天综合 | 综合激情伊人 | 日本中文字幕一二区观 | 正在播放国产一区二区 | 欧美电影在线观看 | 337p日本欧洲亚洲大胆裸体艺术 | 综合av在线 | 久久综合99| 黄色亚洲精品 | 一区二区三区视频 | 国产成人免费av电影 | 欧美日韩中文在线 | 免费在线观看av网站 | 国产精品99久久久久人中文网介绍 | 婷婷资源站 | 不卡电影一区二区三区 | 欧美另类巨大 | 亚洲第一av在线播放 | 亚洲国产日韩一区 | 亚洲va在线va天堂va偷拍 | 日日夜夜婷婷 | 成年人免费在线观看网站 | 日韩精品 在线视频 | 国产精品一区在线观看 | 黄色特一级| 久久精品伊人 | 国产亚洲成av片在线观看 | 国产尤物在线观看 | 免费在线观看成人小视频 | 久久国产精品99精国产 | 亚洲狠狠婷婷 | 五月天狠狠操 | 成人 亚洲 欧美 | zzijzzij亚洲成熟少妇 | 日韩精品免费一区二区在线观看 | 99精彩视频在线观看免费 | 欧美不卡视频在线 | 色资源中文字幕 | 久久精品日产第一区二区三区乱码 | 中文字幕在线不卡国产视频 | 丁香综合五月 | 欧美一级爽| 成人久久电影 | 激情欧美在线观看 | 视频精品一区二区三区 | 日本特黄特色aaa大片免费 | 日操操| 91成人网在线观看 | 干干夜夜| 五月激情在线 | 久久久久日本精品一区二区三区 | 99热在线看| 日韩精品免费一区 | 激情导航| 二区在线播放 | 玖玖爱在线观看 | 久久国产精品精品国产色婷婷 | 国内精品久久久久久久久久清纯 | 国产精品久久一卡二卡 | 久99精品| 精品国产成人在线 | av播放在线| 在线观看福利网站 | 亚洲一区二区视频 | 国产99在线播放 | 96看片| 国产精品高潮久久av | 91精品无人成人www | 91在线日韩 | 亚洲天堂在线观看完整版 | 免费欧美| 久久精品国产精品亚洲精品 | 国产一级免费在线观看 | 国产99在线免费 | 97视频免费播放 | 亚洲日本欧美在线 | 国产91精品久久久久 | 日韩欧美黄色网址 | 999久久国精品免费观看网站 | 干天天| 白丝av在线 | 国产精品久久伊人 | 久99久在线| 日日草天天干 | 日韩午夜视频在线观看 | 日本不卡久久 | 日韩久久精品一区二区 | 成人毛片一区 | 又长又大又黑又粗欧美 | 视频三区 | 国产精品爽爽久久久久久蜜臀 | 久久久99精品免费观看app | 91成人精品观看 | 77国产精品 | 午夜影视剧场 | 日本精油按摩3 | 午夜成人免费电影 | 国产精品久久久久久一区二区 | 亚洲精品在线一区二区 | 国产精品自在线拍国产 | 黄色av网站在线观看免费 | 91福利试看| 99精品观看| 一区二区三区在线观看免费 | 天天色官网 | 欧美日韩一区二区三区在线免费观看 | 国产v在线播放 | 91超级碰碰 | 色国产在线 | 久久久久久久国产精品视频 | 亚洲区色 | 色婷婷狠狠18 | 久久在现 | 97色综合| 免费在线日韩 | 99精品视频在线 | 成人av在线网址 | 久久久久久久久久久久电影 | 日韩黄色免费在线观看 | 日日干天天干 | 久久人人爽人人爽人人片av免费 | avhd高清在线谜片 | 天天操狠狠操网站 | 国内99视频 | 99视频免费在线观看 | 亚洲免费精彩视频 | 久久99影院 | 深夜免费福利网站 | 九精品| 久久精品精品电影网 | 天天做天天爱天天综合网 | 最新av免费 | 国产高清黄| 色99久久 | 欧美激情综合色 | 综合天天网 | 久久国产精品偷 | 色橹橹欧美在线观看视频高清 | 成人在线播放免费观看 | 在线国产不卡 | 日女人免费视频 | 精品国产乱码久久久久 | 午夜久久久影院 | 久久精品小视频 | 精品欧美小视频在线观看 | 欧美在线一二区 | 欧美一级片在线免费观看 | 日韩精品免费一区二区三区 | 日韩电影在线看 | 一区精品在线 | 国产欧美精品一区二区三区四区 | 国产三级久久久 | 99精品视频免费看 | 婷婷丁香花五月天 | 国产五月色婷婷六月丁香视频 | 久久黄网站 | 精品在线视频一区二区三区 | 国产精品久久久久久久久搜平片 | 91色综合| 成人禁用看黄a在线 | 91香蕉国产 | 欧美日在线 | 狠狠操夜夜操 | 日韩,中文字幕 | 久久嗨| 天天射天天射天天 | 色网站免费在线看 | 国产精品久久久久久一区二区三区 | 免费高清在线观看成人 | 天天综合成人网 | 91日韩在线专区 | 91精品视频导航 | 欧美成人精品欧美一级乱黄 | 在线天堂日本 | 超碰av在线免费观看 | 丁香六月婷| 欧美日韩国产一区二区三区在线观看 | 91视频在线观看免费 | 日日干干| 天天色.com | 中文字幕丝袜一区二区 | 中文字幕在线影院 | 国产又粗又猛又爽 | 国产区欧美 | 久久久久99精品国产片 |