Unity3Dで3D脱出ゲームの作り方のラストパートです。
途中でくじけそうになってしまいましたが、何とか完成しました。
敵の設置
AssetsのModelsのchar_robotGuardをシーンビューにドロップしてPosition X:-18 Y:0 Z:6.5にします。
char_robotGuardオブジェクトを展開してchar_robotGuard_bodyオブジェクトを選択してUse Light Probesにチェックをいれます。
char_robotGuard_skeletonオブジェクトを展開してchar_robotGuard_Hipsオブジェクトを展開してchar_robotGuard_Spineオブジェクトを展開してchar_robotGuard_Neckオブジェクトを展開してchar_robotGuard_Headオブジェクトを展開してchar_robotGuard_helmetを選択してUse Light Probesにチェックをいれます。
char_robotGuardオブジェクトを選択してTagをEnemyに変更します。
char_robotGuardオブジェクトにSphere Colliderを追加してIs Triggerにチェックをいれ、Center Y:1、Radiusを10にします。
char_robotGuard_bodyオブジェクトを選択してCapsule Colliderを追加してCenter X:0 Y:1 Z:0、Raduis 0.3、Height 2にします。
char_robotGuardオブジェクトを選択してUse GravityのチェックをはずしてIs Kinematicのチェックを入れます。Nav Mesh Agentも追加してStopping Distanceを0.8にします。
Navigationビューを選択してBakeタブをクリック。Radius 0.3、Height 1にしてBakeボタンをクリックします。
敵のアニメーターコントローラ
Unity: Stealth – Enemy Animator Controller
AssetsのAnimatorsを選択してEnemyAnimatorというAnimator Controllerを作成してAnimatorビューを選択します。
ParametersにFloatのAngular Speed、Speed、Shot、AimWeight、そしてBoolのPlayerInSightを追加します。
Blend Treeを作成してLocomationという名前にする。LocomationをダブルクリックしてLocomationを入力してBlend Typeを2D Freeform CartesianにしてParametersをAngularSpeedとSpeedにする。
+ボタンを押してMotion Fieldを複数追加してそれぞれにMotionを設定していく。21個もあるので動画を見ながら追加する。追加し終わったら、Compute PositionsをSpeed and Angular Speedにする。Modelsからchar_robotGuardをドラッグしてInspectorのPreviewにドロップする。
新しいレイヤーを作成して名前をShootingにする。
AssetsのAnimatorsを選択してEnemyShootingMaskという名前でAvatar Maskを作成してInspectorのHumanoidの図を上半身だけ緑色にします。
ShootingレイヤーのMaskをEnemyShootingMaskにする。
ここでIK PASSにチェックを入れるところですが無料版のUnityの場合、その項目がありません。スキップします。IK PASSは自動的に何かを計算するやつらしいです。
Shootingレイヤーに空のState(New State)を作成します。
AssetsのAnimationsのHumanoidのhumanoid_weapon_raiseを展開してWeaponRaiseをAnimatorビューにドロップします。
AnimatorビューのWeaponRaiseのSpeedを3、そしてFoot IKにチェックを入れます。
New StateからWeaponRaiseへTransitionを作成してConditionsをPlayerInSight trueにします。
Humanoidのhumanoid_weapon_shootを展開してWeaponShootをAnimatorビューにドロップしてFoot IKにチェックを入れます。
WeaponRaiseからWeaponShootへTransitionを作成します。
Humanoidのhumanoid_weapon_lowerを展開してWeaponLowerをAnimatorビューにドロップしてFoot IKにチェックを入れます。
WeaponShootからWeaponLowerへTransitionを作成してConditionsをPlayerInSight falseにします。
WeaponLowerからNewe StateへTransitionを作成します。
新しいレイヤーを作成して名前をGunにします。
AssetsのAnimatorsを選択してEnemyGunMaskという名前でAvatar Maskを作成してInspectorのHumanoidの図の左側の手だけ緑色にします。(左側の手が右手になる)
GunレイヤーのMaskをEnemyGunMaskにする。
Modelsのchar_robotGuardを展開してGrippedをGunレイヤへドロップします。念のためFoot IKもチェックしておく。
char_robotGuardオブジェクトを選択してAssetsのAnimatorsのEnemyAnimatorをchar_robotGuardオブジェクトのAnimator Controllerに設定する。
敵のプレイヤーの視認
char_robotGuardオブジェクトを選択してEnemySightという名前でC#スクリプトを作成します。
using UnityEngine; using System.Collections; public class EnemySight : MonoBehaviour { public float fieldOfViewAngle = 110f; public bool playerInSight; public Vector3 personalLastSighting; private NavMeshAgent nav; private SphereCollider col; private Animator anim; private LastPlayerSighting lastPlayerSighting; private GameObject player; private Animator playerAnim; private PlayerHealth playerHealth; private HashIDs hash; private Vector3 previousSighting; void Awake(){ nav = GetComponent<NavMeshAgent>(); col = GetComponent<SphereCollider>(); anim = GetComponent<Animator>(); lastPlayerSighting = GameObject.FindGameObjectWithTag(Tags.gameController).GetComponent<LastPlayerSighting>(); player = GameObject.FindGameObjectWithTag(Tags.player); playerAnim = player.GetComponent<Animator>(); playerHealth = player.GetComponent<PlayerHealth>(); hash = GameObject.FindGameObjectWithTag(Tags.gameController).GetComponent<HashIDs>(); personalLastSighting = lastPlayerSighting.resetPosition; previousSighting = lastPlayerSighting.resetPosition; } void Update(){ if (lastPlayerSighting.position != previousSighting) personalLastSighting = lastPlayerSighting.position; previousSighting = lastPlayerSighting.position; if (playerHealth.health > 0f) anim.SetBool (hash.playerInSightBool, playerInSight); else anim.SetBool (hash.playerInSightBool, false); } void OnTriggerStay(Collider other){ if(other.gameObject == player){ playerInSight = false; Vector3 direction = other.transform.position - transform.position; float angle = Vector3.Angle(direction, transform.forward); if(angle < fieldOfViewAngle * 0.5f){ RaycastHit hit; if(Physics.Raycast (transform.position + transform.up, direction.normalized, out hit, col.radius)){ if(hit.collider.gameObject == player){ playerInSight = true; lastPlayerSighting.position = player.transform.position; } } } int playerLayerZeroStateHash = playerAnim.GetCurrentAnimatorStateInfo(0).nameHash; int playerLayerOneStateHash = playerAnim.GetCurrentAnimatorStateInfo(1).nameHash; if(playerLayerZeroStateHash == hash.locomotionState || playerLayerOneStateHash == hash.shoutState){ if(CalculatePathLength(player.transform.position) <= col.radius){ personalLastSighting = player.transform.position; } } } } void OnTriggerExit(Collider other){ if (other.gameObject == player) playerInSight = false; } float CalculatePathLength(Vector3 targetPosition){ NavMeshPath path = new NavMeshPath (); if (nav.enabled) nav.CalculatePath (targetPosition, path); Vector3[] allWayPoints = new Vector3[path.corners.Length+2]; allWayPoints [0] = transform.position; allWayPoints [allWayPoints.Length - 1] = targetPosition; for(int i=0; i<path.corners.Length; i++){ allWayPoints[i+1] = path.corners[i]; } float pathLength = 0f; for(int i=0; i<allWayPoints.Length-1; i++){ pathLength += Vector3.Distance(allWayPoints[i], allWayPoints[i+1]); } return pathLength; } }
敵のアニメータの補助設定
Unity: Stealth – Animator Setup
AssetsのScriptsでAnimatorSetupという名前でC#スクリプトを作成して編集します。
using UnityEngine; using System.Collections; public class AnimatorSetup { public float speedDampTime = 0.1f; public float angularSpeedDampTime = 0.7f; public float angleResponseTime = 0.6f; private Animator anim; private HashIDs hash; public AnimatorSetup(Animator animator, HashIDs hashIDs){ anim = animator; hash = hashIDs; } public void Setup(float speed, float angle){ float angularSpeed = angle / angleResponseTime; anim.SetFloat (hash.speedFloat, speed, speedDampTime, Time.deltaTime); anim.SetFloat (hash.angularSpeedFloat, angularSpeed, angularSpeedDampTime, Time.deltaTime); } }
このスクリプトが敵の動きが急激に変わるときに変な動きにならないように動きをスムーズにする。
敵のアニメーション
Unity: Stealth – Enemy Animation
char_robotGuardオブジェクトを選択してEnemyAnimationという名前でC#スクリプトを追加して編集します。
using UnityEngine; using System.Collections; public class EnemyAnimation : MonoBehaviour { public float deadZone = 5f; private Transform player; private EnemySight enemySight; private NavMeshAgent nav; private Animator anim; private HashIDs hash; private AnimatorSetup animSetup; void Awake(){ player = GameObject.FindGameObjectWithTag (Tags.player).transform; enemySight = GetComponent<EnemySight>(); nav = GetComponent<NavMeshAgent>(); anim = GetComponent<Animator>(); hash = GameObject.FindGameObjectWithTag(Tags.gameController).GetComponent<HashIDs>(); nav.updateRotation = false; animSetup = new AnimatorSetup (anim, hash); anim.SetLayerWeight (1,1f); anim.SetLayerWeight (2,1f); deadZone *= Mathf.Deg2Rad; } void Update(){ NavAnimSetup (); } void OnAnimatorMove(){ nav.velocity = anim.deltaPosition / Time.deltaTime; transform.rotation = anim.rootRotation; } void NavAnimSetup(){ float speed; float angle; if (enemySight.playerInSight) { speed = 0f; angle = FindAngle (transform.forward, player.position - transform.position, transform.up); } else { speed = Vector3.Project(nav.desiredVelocity, transform.forward).magnitude; angle = FindAngle(transform.forward, nav.desiredVelocity, transform.up); if(Mathf.Abs(angle) < deadZone){ transform.LookAt (transform.position + nav.desiredVelocity); angle = 0f; } } animSetup.Setup (speed, angle); } float FindAngle(Vector3 fromVector, Vector3 toVector, Vector3 upVector){ if (toVector == Vector3.zero) { return 0f; } float angle = Vector3.Angle (fromVector, toVector); Vector3 normal = Vector3.Cross (fromVector, toVector); angle *= Mathf.Sign (Vector3.Dot (normal, upVector)); angle *= Mathf.Deg2Rad; return angle; } }
EnemyAnimatorのShootingレイヤーとGunレイヤーのWeightを1にしておく。
敵の銃
Unity: Stealth – Enemy Shooting
char_robotGuardオブジェクトを展開していきchar_robotGuard_RightHandオブジェクトを展開することころまでいきます。Modelsのprop_sciFiGun_lowをchar_robotGuard_RightHandオブジェクトにドロップしてPosition X:0.1 Y:0.021 Z:-0.02、Rotation X:332 Y:101.4 Z:263.8にします。空のゲームオブジェクトを作成してfx_layerShotという名前にしてprop_sciFiGun_lowオブジェクトにドロップします。
fx_layerShotオブジェクトにLightを追加してIntensityを0にします。続いてLine Rendererを追加してMaterialsのElement 0にalpha_laserShot_fxを設定してParametersのStart Widthを0.05、End Widthを0.025にします。
char_robotGuardオブジェクトを選択してEnemyShootingという名前でC#スクリプトを追加して編集します。
using UnityEngine; using System.Collections; public class EnemyShooting : MonoBehaviour { public float maximumDamge = 120f; public float minimumDamage = 45f; public AudioClip shotClip; public float flashIntensity = 3f; public float fadeSpeed = 10f; private Animator anim; private HashIDs hash; private LineRenderer laserShotLine; private Light laserShotLight; private SphereCollider col; private Transform player; private PlayerHealth playerHealth; private bool shooting; private float scaledDamage; void Awake(){ anim = GetComponent<Animator>(); laserShotLine = GetComponentInChildren<LineRenderer>(); laserShotLight = laserShotLine.gameObject.light; col = GetComponent<SphereCollider>(); player = GameObject.FindGameObjectWithTag (Tags.player).transform; playerHealth = player.gameObject.GetComponent<PlayerHealth>(); hash = GameObject.FindGameObjectWithTag(Tags.gameController).GetComponent<HashIDs>(); laserShotLine.enabled = false; laserShotLight.intensity = 0f; scaledDamage = maximumDamge - minimumDamage; } void Update(){ float shot = anim.GetFloat (hash.shotFloat); if(shot > 0.5f && !shooting){ Shoot (); } if(shot < 0.5f){ shooting = false; laserShotLine.enabled = false; } laserShotLight.intensity = Mathf.Lerp (laserShotLight.intensity, 0f, fadeSpeed * Time.deltaTime); } void OnAnimatorIK(int layerIndex){ float aimWeight = anim.GetFloat (hash.aimWeightFloat); anim.SetIKPosition (AvatarIKGoal.RightHand, player.position + Vector3.up * 1.5f); anim.SetIKPositionWeight (AvatarIKGoal.RightHand, aimWeight); } void Shoot(){ shooting = true; float fractionalDistance = (col.radius - Vector3.Distance (transform.position, player.position)) / col.radius; float damage = scaledDamage * fractionalDistance + minimumDamage; playerHealth.TakeDamage (damage); ShotEffects (); } void ShotEffects(){ laserShotLine.SetPosition (0, laserShotLine.transform.position); laserShotLine.SetPosition (1, player.position + Vector3.up * 1.5f); laserShotLine.enabled = true; laserShotLight.intensity = flashIntensity; AudioSource.PlayClipAtPoint (shotClip, laserShotLight.transform.position); } }
char_robotGuardオブジェクトのEnemy Shooting(Script)のShot Clipにweapon_sciFiGun_fireを設定します。
敵のAI
char_robotGuardオブジェクトを選択してEnemyAIという名前でC#スクリプトを追加して編集します。
using UnityEngine; using System.Collections; public class EnemyAI : MonoBehaviour { public float patrolSpeed = 2f; public float chaseSpeed = 5f; public float chaseWaitTime = 5f; public float patrolWaitTime = 1f; public Transform[] patrolWayPoints; private EnemySight enemySight; private NavMeshAgent nav; private Transform player; private PlayerHealth playerHealth; private LastPlayerSighting lastPlayerSighting; private float chaseTimer; private float patrolTimer; private int wayPointIndex; void Awake(){ enemySight = GetComponent<EnemySight>(); nav = GetComponent<NavMeshAgent>(); player = GameObject.FindGameObjectWithTag (Tags.player).transform; playerHealth = player.GetComponent<PlayerHealth>(); lastPlayerSighting = GameObject.FindGameObjectWithTag(Tags.gameController).GetComponent<LastPlayerSighting>(); } void Update(){ if (enemySight.playerInSight && playerHealth.health > 0f) Shooting (); else if (enemySight.personalLastSighting != lastPlayerSighting.resetPosition && playerHealth.health > 0f) Chasing (); else Patrolling (); } void Shooting(){ nav.Stop (); } void Chasing(){ Vector3 sightingDeltaPos = enemySight.personalLastSighting - transform.position; if(sightingDeltaPos.sqrMagnitude > 4f){ nav.destination = enemySight.personalLastSighting; } nav.speed = chaseSpeed; if (nav.remainingDistance < nav.stoppingDistance) { chaseTimer += Time.deltaTime; if (chaseTimer > chaseWaitTime) { lastPlayerSighting.position = lastPlayerSighting.resetPosition; enemySight.personalLastSighting = lastPlayerSighting.resetPosition; chaseTimer = 0f; } } else chaseTimer = 0f; } void Patrolling(){ nav.speed = patrolSpeed; if (nav.destination == lastPlayerSighting.resetPosition || nav.remainingDistance < nav.stoppingDistance) { patrolTimer += Time.deltaTime; if (patrolTimer >= patrolWaitTime) { if (wayPointIndex == patrolWayPoints.Length - 1) { wayPointIndex = 0; } else { wayPointIndex++; } patrolTimer = 0f; } } else { patrolTimer = 0f; } nav.destination = patrolWayPoints [wayPointIndex].position; } }
char_robotGuardオブジェクトをPrefabsにドロップします。
char_robotGuardオブジェクトを2つ複製して3つにします。
それぞれのchar_robotGuardオブジェクトを下記のように変更します。
char_robotGuard_001 Position X:-18 Y:0 Z:6.5
char_robotGuard_002 Position X:-19 Y:0 Z:37
char_robotGuard_003 Position X:-29 Y:0 Z:43.5
空のGameObjectを作成してwayPointsという名前に変更します。
wayPointsを複製してwayPoint_001に変更してwayPointsの子要素にします。wayPoint_011まで同じように作成します。
wayPoint_001 Position X:-26.5 Y:0 Z:7
wayPoint_002 Position X:-25.4 Y:0 Z:14.5
wayPoint_003 Position X:-18.8 Y:0 Z:14
wayPoint_004 Position X:-18.5 Y:0 Z:7
wayPoint_005 Position X:-10 Y:0 Z:36.4
wayPoint_006 Position X:-10 Y:0 Z:44.8
wayPoint_007 Position X:-17.9 Y:0 Z:44.8
wayPoint_008 Position X:-17.8 Y:0 Z:36.4
wayPoint_009 Position X:-27 Y:0 Z:35.4
wayPoint_010 Position X:-17.9 Y:0 Z:43
wayPoint_011 Position X:-28.2 Y:0 Z:44
char_robotGuard_001を選択してEnemy AI(Script)のPatrol Way PointsにwayPoint_001からwayPoint_004をドロップします。char_robotGuard_002にはwayPoint_005にはwayPoint_008をドロップします。char_robotGuard_003にはwayPoint_009、wayPoint_008、wayPoint_010、wayPoint_011を順番にドロップします。
これでPlayボタンを押してゲームをしてみます。
敵に倒されるとエラーが出ます。
char_ethanを選択してPlayer Health(Script)のDeath ClipにAudioのendgameを設定します。
Unityのバージョンが違うため倒されたときの処理をスキップしたので倒されるとアラームの音が変になって再スタートしません。でも、とにかくこれで完成です!
Unity3Dで3D脱出ゲーム
容量が65MBくらいあります。
若干不具合がありますが、兎にも角にもチュートリアルのゲーム作りは完成です。
これでUnityでのゲーム作りは自由自在!という感じには超程遠いですが、
材料をかき集めながら、オリジナルゲームを作っていきます!
コメントを残す