アマゾンバナーリンク

ディスプレイ広告

スポンサーリンク

TPSオンラインゲーム開発の教科書2

2021年5月7日

こんにちは!ジェイです。今回は前回に引き続きキャラクターにカメラを追従させる処理を追加していきます。

記事内広告

キャラクター処理

自キャラにもESCキーを押すとカーソルを外したり、フォーカスしたりする機能を追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MultiplayWaveShooter;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.SceneManagement;

namespace MultiplayWaveShooter
{
    public class CPlayerController : MonoBehaviourPun
    {
        public enum EPlayerState
        {
            Idle, // アイドル状態
            Walk, // 歩き
            Jump, // ジャンプ
            Cover, // 壁の裏に隠れるカバーアクション
        }
        public EPlayerState PlayerState = EPlayerState.Idle;
        //移動速度等のパラメータ用変数(inspectorビューで設定)
        public float WalkSpeed;
        public float RunSpeed;
        private float NowSpeed;         //キャラクターの移動速度
        public float JumpSpeed;     //キャラクターのジャンプ力
        public float RotateSpeed;   //キャラクターの方向転換速度
        public float Gravity = 20.0f;      //キャラにかかる重力の大きさ
        public float SpeedSmoothing = 10.0f;
        public float AirControlAcceleration = 3.0f;

        private PhotonTransformViewClassic _PhotonTransformViewClassic;
        //移動処理に必要なコンポーネントを設定
        private Animator _Animator;                 //モーションをコントロールするためAnimatorを取得
        private CharacterController _CharacterController;    //キャラクター移動を管理するためCharacterControllerを取得
        [HideInInspector]
        public float Horizontal = 0.0f;
        [HideInInspector]
        public float Vertical = 0.0f;
        public Vector3 TargetDirection;        //移動する方向のベクトル
        public Vector3 TargetRotateDirection;
        public Vector3 MoveDirection = Vector3.zero;
        public bool IsGrounded;
        public bool IsAiming = false;
        private Vector3 AirVelocity = Vector3.zero;
        private float MoveSpeed = 0.0f;
        private float VerticalSpeed = 0.0f;
        private bool IsAimBlocked;
        private int hFloat;
        private int vFloat;
        void Start()
        {
            NowSpeed = WalkSpeed;
            hFloat = Animator.StringToHash("H");
            vFloat = Animator.StringToHash("V");
            _Animator = GetComponent<Animator>();
            _CharacterController = GetComponent<CharacterController>();
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
        bool MoveFlag = false;
        float JumpTime = 0.0f;
        float JumpInterval = 3.0f;
        float TotalTime = 0.0f;
        float Sqrt2 = 1 / Mathf.Sqrt(2);
        bool Menu = false;
        void Update()
        {
            // カーソルを外す
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                // ルートに存在する全オブジェクトを取得する
                GameObject[] all_gameobject = SceneManager.GetActiveScene().GetRootGameObjects();
                // それらをループする
                foreach (GameObject val in all_gameobject)
                {
                    if (val.tag == "MainCamera")
                    {
                        bool freezeCam = val.GetComponent<CThirdPersonOrbitCam>().freezeCam;
                        val.GetComponent<CThirdPersonOrbitCam>().freezeCam = !freezeCam;
                    }
                }

                Menu = !Menu;
                if (Menu) // ロックを解除
                {
                    Cursor.lockState = CursorLockMode.None;
                    Cursor.visible = true;
                }
                else // ロックする
                {
                    Cursor.lockState = CursorLockMode.Locked;
                    Cursor.visible = false;
                }
            }
            //★進行方向計算
            //キーボード入力を取得
            Vertical = Input.GetAxis("Vertical");         //InputManagerの↑↓の入力       
            Horizontal = Input.GetAxis("Horizontal");       //InputManagerの←→の入力 
            Vector3 forward = Vector3.zero;
            Vector3 right = Vector3.zero;
            Camera main_camera = Camera.main;
            if (main_camera)
            {
                //カメラの正面方向ベクトルからY成分を除き、正規化してキャラが走る方向を取得
                forward = Vector3.Scale(main_camera.transform.forward, new Vector3(1, 0, 1)).normalized;
                right = main_camera.transform.right; //カメラの右方向を取得
            }
            float vx, vy;
            MoveFlag = (Vertical == 0 && Horizontal == 0) ? false : true;
            switch (PlayerState)
            {
                case EPlayerState.Idle:
                    if (TargetDirection != Vector3.zero)
                    {
                        PlayerState = EPlayerState.Walk;
                    }

                    //Jumpボタンでジャンプ処理
                    if (Time.time > JumpTime + JumpInterval && !IsAiming && _CharacterController.isGrounded && Input.GetButton("Jump"))
                    {
                        JumpTime = Time.time;
                        PlayerState = EPlayerState.Jump;
                        VerticalSpeed = Mathf.Sqrt(2.0f * Gravity * JumpSpeed);
                        _Animator.SetBool("jump", true);
                        //_AudioSource.PlayOneShot(_AudioClip[3]);
                    }

                    if (Horizontal != 0 && Vertical != 0)
                    {
                        vx = Mathf.Abs(Horizontal) * Sqrt2;
                        vy = Mathf.Abs(Vertical) * Sqrt2;
                    }
                    else
                    {
                        vx = Mathf.Abs(Horizontal);
                        vy = Mathf.Abs(Vertical);
                    }
                    _Animator.SetFloat("Speed", Mathf.Min(1.0f, (vx + vy)), 0.1f, Time.deltaTime);
                    break;
                case EPlayerState.Walk:
                    if (!MoveFlag)
                    {
                        PlayerState = EPlayerState.Idle;
                    }

                    NowSpeed = (Input.GetKey(KeyCode.LeftShift) && !IsAiming) ? RunSpeed : WalkSpeed;

                    //Jumpボタンでジャンプ処理
                    if (Time.time > JumpTime + JumpInterval && !IsAiming && _CharacterController.isGrounded && Input.GetButton("Jump"))
                    {
                        JumpTime = Time.time;
                        PlayerState = EPlayerState.Jump;
                        VerticalSpeed = Mathf.Sqrt(2.0f * Gravity * JumpSpeed);
                        _Animator.SetBool("jump", true);
                        //_AudioSource.PlayOneShot(_AudioClip[3]);
                    }

                    if (Horizontal != 0 && Vertical != 0)
                    {
                        vx = Mathf.Abs(Horizontal) * Sqrt2;
                        vy = Mathf.Abs(Vertical) * Sqrt2;
                    }
                    else
                    {
                        vx = Mathf.Abs(Horizontal);
                        vy = Mathf.Abs(Vertical);
                    }
                    _Animator.SetFloat("Speed", Mathf.Min(1.0f, (vx + vy)), 0.1f, Time.deltaTime);
                    break;
                case EPlayerState.Jump:
                    if (_CharacterController.isGrounded)
                    {
                        if (_Animator != null)
                            _Animator.SetBool("jump", false);
                        if (!MoveFlag)
                        {
                            PlayerState = EPlayerState.Idle;
                        }
                        else
                        {
                            PlayerState = EPlayerState.Walk;
                        }
                    }
                    break;
            }

            //カメラの方向を考慮したキャラの進行方向を計算
            TargetDirection = Horizontal * right + Vertical * forward;
            TargetRotateDirection = TargetDirection.normalized;
            RotationControl(TargetRotateDirection); //旋回用関数 

            float cur_smooth = SpeedSmoothing * Time.deltaTime;
            float target_speed = Mathf.Min(TargetDirection.magnitude, 1.0f);
            MoveSpeed = Mathf.Lerp(MoveSpeed, target_speed * NowSpeed, cur_smooth);

            //★地上にいる場合の処理
            // ここが地面にずっといてもTrueになったりFlaseになったりするのを確認2018/5/23
            // 原因はMoveDirectionのy軸も一緒に計算して_CharacterController.Moveに渡してたことみたい
            // xz軸の移動のみMoveDirectionに入れy軸は別に計算する仕様にした
            IsGrounded = _CharacterController.isGrounded;

            if (_CharacterController.isGrounded)
            {
                //移動のベクトルを計算
                MoveDirection = TargetDirection.normalized;
                AirVelocity = Vector3.zero;
            }
            else//空中操作の処理(重力加速度等)
            {
                // 空中での動き
                if (TargetDirection != Vector3.zero)
                {
                    AirVelocity = TargetDirection.normalized * Time.deltaTime * AirControlAcceleration;
                }
                VerticalSpeed -= Gravity * Time.deltaTime;
            }
            //最終的な移動処理
            Vector3 movement = MoveDirection * MoveSpeed + new Vector3(0, VerticalSpeed, 0) + AirVelocity;
            movement *= Time.deltaTime;
            _CharacterController.Move(movement);
            var velocity = _CharacterController.velocity;
            //_PhotonTransformViewClassic.SetSynchronizedValues(velocity, 0);
        }
        Vector3 NewDir = Vector3.zero;
        //キャラクターが移動方向を変えるときの処理
        void RotationControl(Vector3 rotate_direction)
        {
            rotate_direction.y = 0;

            //それなりに移動方向が変化する場合のみ移動方向を変える
            if (rotate_direction.sqrMagnitude > 0.01)
            {
                //緩やかに移動方向を変える
                float step = RotateSpeed * Time.deltaTime;
                NewDir = Vector3.Slerp(transform.forward, rotate_direction, step);
                transform.rotation = Quaternion.LookRotation(NewDir);
            }
        }
    }
}

カーソルをロックしたり外したりする処理を追加しています。非アクティブのオブジェクトは検索できないので、SceneManager.GetActiveScene().GetRootGameObjects();ですべてのオブジェクトを検索しています。

カメラ処理

using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using MultiplayWaveShooter;

namespace MultiplayWaveShooter
{
	public class CThirdPersonOrbitCam : MonoBehaviourPun
	{
		public Transform player;
		public CharacterController _CharacterController;
		public Vector3 PivotOffset = new Vector3(0.0f, 1.0f, 0.0f);
		public Vector3 PlayerCamOffset = new Vector3(0.4f, 0.5f, -2.0f);
		public Vector3 VehicleCamOffset = new Vector3(0.4f, 0.5f, -3.0f);
		public float smooth = 10f;
		public float horizontalAimingSpeed = 6f;
		public float verticalAimingSpeed = 6f;
		public float maxVerticalAngle = 30f;
		public float minVerticalAngle = -60f;
		public string XAxis = "Analog X";
		public string YAxis = "Analog Y";

		private float angleH = 0;
		private float angleV = 0;
		private Vector3 relCameraPos;
		private float relCameraPosMag;
		private Vector3 smoothPivotOffset;
		private Vector3 smoothCamOffset;
		private Vector3 targetPivotOffset;
		private Vector3 targetCamOffset;
		private float defaultFOV;
		private float targetFOV;
		private float targetMaxVerticalAngle;
		private float deltaH = 0;
		private Vector3 firstDirection;
		private Vector3 directionToLock;
		private float recoilAngle = 0f;
		private Vector3 forwardHorizontalRef;
		private float leftRelHorizontalAngle, rightRelHorizontalAngle;

		// Get the camera horizontal angle.
		public float GetH { get { return angleH; } }
		public bool freezeCam = false;

		public int State = 0; // 0:プレイヤー用カメラモード:それ以外は乗り物用カメラモード

		private void Start()
		{
			ReSet();
		}
		public void ReSet()
		{
			if (player == null) return;
			Vector3 cam_offset = State == 0 ? PlayerCamOffset : VehicleCamOffset;
			_Camera = Camera.main;// transform.GetComponent<Camera>();

			transform.position = player.position + Quaternion.identity * PivotOffset + Quaternion.identity * cam_offset;
			transform.rotation = Quaternion.identity;

			relCameraPos = transform.position - player.position;
			relCameraPosMag = relCameraPos.magnitude - 0.5f;

			smoothPivotOffset = PivotOffset;
			smoothCamOffset = cam_offset;
			defaultFOV = transform.GetComponent<Camera>().fieldOfView;
			angleH = player.eulerAngles.y;

			ResetTargetOffsets();
			ResetFOV();
			ResetMaxVerticalAngle();
		}
		[HideInInspector]
		public Camera _Camera;
		void LateUpdate()
		{
			if (player == null) return;

			if (!freezeCam)
			{
				// Mouse:
				angleH += Mathf.Clamp(Input.GetAxis("Mouse X"), -1, 1) * horizontalAimingSpeed;
				angleV += Mathf.Clamp(Input.GetAxis("Mouse Y"), -1, 1) * verticalAimingSpeed;
				// Joystick:
				//angleH += Mathf.Clamp(Input.GetAxis(XAxis), -1, 1) * 60 * horizontalAimingSpeed * Time.deltaTime;
				//angleV += Mathf.Clamp(Input.GetAxis(YAxis), -1, 1) * 60 * verticalAimingSpeed * Time.deltaTime;
			}

			angleV = Mathf.Clamp(angleV, minVerticalAngle, targetMaxVerticalAngle);

			angleV = Mathf.LerpAngle(angleV, angleV + recoilAngle, 10f * Time.deltaTime);

			if (firstDirection != Vector3.zero)
			{
				angleH -= deltaH;
				UpdateLockAngle();
				angleH += deltaH;
			}

			if (forwardHorizontalRef != default(Vector3))
			{
				ClampHorizontal();
			}

			Quaternion camYRotation = Quaternion.Euler(0, angleH, 0);
			Quaternion aimRotation = Quaternion.Euler(-angleV, angleH, 0);
			transform.rotation = aimRotation;

			_Camera = transform.GetComponent<Camera>();
			if (_Camera != null)
			{
				_Camera.fieldOfView = Mathf.Lerp(_Camera.fieldOfView, targetFOV, Time.deltaTime);
			}

			Vector3 baseTempPosition = player.position + camYRotation * targetPivotOffset;
			Vector3 noCollisionOffset = targetCamOffset;
			for (float zOffset = targetCamOffset.z; zOffset <= 0; zOffset += 0.5f)
			{
				noCollisionOffset.z = zOffset;
				if (DoubleViewingPosCheck(baseTempPosition + aimRotation * noCollisionOffset, Mathf.Abs(zOffset)) || zOffset == 0)
				{
					break;
				}
			}

			smoothPivotOffset = Vector3.Lerp(smoothPivotOffset, targetPivotOffset, smooth * Time.deltaTime);
			smoothCamOffset = Vector3.Lerp(smoothCamOffset, noCollisionOffset, smooth * Time.deltaTime);

			Debug.Log(player.position);
			transform.position = player.position
				+ camYRotation * smoothPivotOffset
				+ aimRotation * smoothCamOffset;

			if (recoilAngle > 0)
				recoilAngle -= 5 * Time.deltaTime;
			else if (recoilAngle < 0)
				recoilAngle += 5 * Time.deltaTime;
		}

		public void ToggleClampHorizontal(float LeftAngle = 0, float RightAngle = 0, Vector3 fwd = default(Vector3))
		{
			forwardHorizontalRef = fwd;
			leftRelHorizontalAngle = LeftAngle;
			rightRelHorizontalAngle = RightAngle;
		}

		private void ClampHorizontal()
		{
			Vector3 cam2dFwd = this.transform.forward;
			cam2dFwd.y = 0;
			float angleBetween = Vector3.Angle(cam2dFwd, forwardHorizontalRef);
			float sign = Mathf.Sign(Vector3.Cross(cam2dFwd, forwardHorizontalRef).y);
			angleBetween = angleBetween * sign;

			float acc = Mathf.Clamp(Input.GetAxis("Mouse X"), -1, 1) * horizontalAimingSpeed;
			acc += Mathf.Clamp(Input.GetAxis("Analog X"), -1, 1) * 60 * horizontalAimingSpeed * Time.deltaTime;

			if (sign < 0 && angleBetween < leftRelHorizontalAngle)
			{
				if (acc > 0)
					angleH -= acc;
			}
			else if (angleBetween > rightRelHorizontalAngle)
			{
				if (acc < 0)
					angleH -= acc;
			}
		}

		public void BounceVertical(float degrees)
		{
			recoilAngle = degrees;
		}

		private void UpdateLockAngle()
		{
			directionToLock.y = 0f;
			float centerLockAngle = Vector3.Angle(firstDirection, directionToLock);
			Vector3 cross = Vector3.Cross(firstDirection, directionToLock);
			if (cross.y < 0) centerLockAngle = -centerLockAngle;
			deltaH = centerLockAngle;
		}

		public void LockOnDirection(Vector3 direction)
		{
			if (firstDirection == Vector3.zero)
			{
				firstDirection = direction;
				firstDirection.y = 0f;
			}
			directionToLock = Vector3.Lerp(directionToLock, direction, 0.15f * smooth * Time.deltaTime);
		}

		public void UnlockOnDirection()
		{
			deltaH = 0;
			firstDirection = directionToLock = Vector3.zero;
		}

		public void SetTargetOffsets(Vector3 newPivotOffset, Vector3 newCamOffset)
		{
			targetPivotOffset = newPivotOffset;
			targetCamOffset = newCamOffset;
		}

		public void ResetTargetOffsets()
		{
			targetPivotOffset = PivotOffset;
			targetCamOffset = State == 0 ? PlayerCamOffset : VehicleCamOffset;
		}

		public void ResetYCamOffset()
		{
			targetCamOffset.y = PlayerCamOffset.y;
		}

		public void SetYCamOffset(float y)
		{
			targetCamOffset.y = y;
		}

		public void SetXCamOffset(float x)
		{
			targetCamOffset.x = x;
		}

		public void SetFOV(float customFOV)
		{
			this.targetFOV = customFOV;
		}
		public float GetFOV()
		{
			return targetFOV;
		}

		public void ResetFOV()
		{
			this.targetFOV = defaultFOV;
		}

		public void SetMaxVerticalAngle(float angle)
		{
			this.targetMaxVerticalAngle = angle;
		}

		public void ResetMaxVerticalAngle()
		{
			this.targetMaxVerticalAngle = maxVerticalAngle;
		}
		bool DoubleViewingPosCheck(Vector3 checkPos, float offset)
		{
			float playerFocusHeight = _CharacterController.height * 0.75f;
			return ViewingPosCheck(checkPos, playerFocusHeight) && ReverseViewingPosCheck(checkPos, playerFocusHeight, offset);
		}
		bool ViewingPosCheck(Vector3 checkPos, float deltaPlayerHeight)
		{
			Vector3 target = player.position + (Vector3.up * deltaPlayerHeight);
			if (Physics.SphereCast(checkPos, 0.2f, target - checkPos, out RaycastHit hit, relCameraPosMag))
			{
				Collider col = hit.transform.GetComponent<Collider>();
				if (col != null && hit.transform != player && !col.isTrigger)
				{
					return false;
				}
			}
			return true;
		}

		bool ReverseViewingPosCheck(Vector3 checkPos, float deltaPlayerHeight, float maxDistance)
		{
			Vector3 origin = player.position + (Vector3.up * deltaPlayerHeight);
			if (Physics.SphereCast(origin, 0.2f, checkPos - origin, out RaycastHit hit, maxDistance))
			{
				Collider col = hit.transform.GetComponent<Collider>();
				if (col != null && hit.transform != player && hit.transform != transform && !col.isTrigger)
				{
					return false;
				}
			}
			return true;
		}

		public float GetCurrentPivotMagnitude(Vector3 finalPivotOffset)
		{
			return Mathf.Abs((finalPivotOffset - smoothPivotOffset).magnitude);
		}
	}
}

上記のスクリプトをカメラにアタッチしてから、自キャラをPlayerとCharacter Controllerにセットします。

実行結果

#Unity
カメラの処理を追加しました

https://gyazo.com/a03d877c1d7113ae41cbb579c3ab6e97

Originally tweeted by ジェイ@Unityオンラインゲームスペシャリスト (@JY20160816) on 2021年5月4日.

+3