17.5 リファクタリング
幻の第17回でコードを引っ掻き回した結果、PlayerShootingクラスが鬼のように見づらくなりました。今回は、リロードと武器チェンジの処理を別クラスに引っぺがし、射撃処理自体も関数化することで可読性を高めるリファクタリングを行います。どちらかというと、「こうなりました」という報告。
PlayerReloadクラス
今回Interfaceを練習がてら使ってみました。ただ、武器種ごとにリロードを実装するつもりだったのが結果的にReload関係のクラスは1つで済んだので、Interfaceの用途としては正しくありません。
1 2 3 4 5 6 |
public interface IReloadable { //リロードをする void Reload(int capacityAmmo); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class PlayerReload : MonoBehaviour,IReloadable { PlayerShooting playerShooting; private void Awake() { playerShooting = this.gameObject.GetComponent<PlayerShooting>(); } public void Reload(int capacityAmmo) { playerShooting.CurrentAmmo = capacityAmmo; //残弾数UIを戻す playerShooting.AmmoFullImg.fillAmount = 1.0f; playerShooting.IsReloading = false; } } |
Interfaceの説明については、こちらの説明が簡潔で非常に理解しやすかったです。
https://qiita.com/Akira_Kido_N/items/7cd18944173cd4cd7229
イメージをつかんだらこちらを読んでより本質的な理解に進むとよいかと。
https://niconare.nicovideo.jp/watch/kn3404
PlayerChangeWeaponクラス
switchの中をもっと共通化できないか30秒くらい悩んでできなさそうだったのであきらめました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
public class PlayerChangeWeapon : MonoBehaviour { PlayerShooting playerShooting; private void Awake() { playerShooting = GetComponent<PlayerShooting>(); } public void ChangeWeapon(WeaponType caseWeapon) { //残弾数UIもマックスに playerShooting.AmmoFullImg.fillAmount = 1.0f; //いったん全部Falseにして、必要な奴だけSwitchの中でTrueにする playerShooting.SmgAmmoPrt.SetActive(false); playerShooting.SgAmmoPrt.SetActive(false); playerShooting.SrAmmoPrt.SetActive(false); //装備している武器の値 playerShooting.PlayerWeapon = caseWeapon; switch (caseWeapon) { case WeaponType.SubmachineGun: playerShooting.GunHUDIcon.sprite = playerShooting.SmgHUDIcon; //銃アイコン playerShooting.CurrentAmmo = playerShooting.SmgMaxAmmo; //弾数装填しなおし playerShooting.CurrentMaxAmmo = playerShooting.SmgMaxAmmo; playerShooting.SmgAmmoPrt.SetActive(true); //SMGの残弾数表示する枠線だけを表示 playerShooting.TimeBetweenBullets = playerShooting.SmgTimeBetweenBullets; //発射レート playerShooting.CurrentReloadTime = playerShooting.SmgReloadTime; playerShooting.CurrentReloadClip = playerShooting.SmgReloadClip; break; case WeaponType.ShotGun: playerShooting.GunHUDIcon.sprite = playerShooting.SgHUDIcon; playerShooting.CurrentAmmo = playerShooting.SgMaxAmmo; playerShooting.CurrentMaxAmmo = playerShooting.SgMaxAmmo; playerShooting.SgAmmoPrt.SetActive(true); playerShooting.TimeBetweenBullets = playerShooting.SgTimeBetweenBullets; playerShooting.CurrentReloadTime = playerShooting.SgReloadTime; playerShooting.CurrentReloadClip = playerShooting.SgReloadClip; break; case WeaponType.SniperRifle: playerShooting.GunHUDIcon.sprite = playerShooting.SrHUDIcon; playerShooting.CurrentAmmo = playerShooting.SrMaxAmmo; playerShooting.CurrentMaxAmmo = playerShooting.SrMaxAmmo; playerShooting.SrAmmoPrt.SetActive(true); playerShooting.TimeBetweenBullets = playerShooting.SrTimeBetweenBullets; playerShooting.CurrentReloadTime = playerShooting.SrReloadTime; playerShooting.CurrentReloadClip = playerShooting.SrReloadClip; break; } playerShooting.IsChangingWeapon = false; } } |
PlayerShootingクラス
全部張ると長くなるので、UpdateとShootメソッドだけ。特にShootメソッドの可読性が上がってます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
void Update() { //射撃メソッド実行直後にリセット timer += Time.deltaTime; //isSlowをFalseにするタイミングでリセット slowTimer += Time.deltaTime; //StopMovingのタイミングでリセット hitStopTimer += Time.deltaTime; //射撃メソッドの実行判定 if (Input.GetButton("Fire1") && timer >= TimeBetweenBullets && Time.timeScale != 0 && IsReloading == false && IsChangingWeapon == false) { //残弾数のチェック if (CurrentAmmo <= 0) { return; } else { //装備してる武器によってSwitchして、Shootメソッドに渡す引数を変える switch (PlayerWeapon) { case WeaponType.SubmachineGun: Shoot(false, false, smgNumBullet, smgFireClip, smgRange, smgDamagePerShot, smgMaxNbPower, SmgMaxAmmo); break; case WeaponType.ShotGun: Shoot(false, true, sgNumBullet, sgFireClip, sgRange, sgDamagePerShot, sgMaxNbPower, SgMaxAmmo); break; case WeaponType.SniperRifle: Shoot(true, false, srNumBullet, srFireClip, srRange, srDamagePerShot, srMaxNbPower, SrMaxAmmo); break; } //次の射撃可能時間までのカウンターをリセット timer = 0f; } } //SMGの発射レートが高いので一瞬で消えているが、固定値にしたほうがいいかも //SMGの発射レートを下げたときにいつまでもエフェクトが残ってしまうバグにつながりかねない if (timer >= smgTimeBetweenBullets * effectsDisplayTime) { DisableEffects(); } //プレイヤーの移動速度を戻す if (slowTimer >= slowTime) { if (isSlow) { playerMovement.RecoverMovingSpeed(); isSlow = false; } } //リロード if (Input.GetKeyDown(KeyCode.R) && IsReloading == false && IsChangingWeapon == false) { IsReloading = true; //リロードSEへ差し替え&再生 gunAudio.clip = CurrentReloadClip; gunAudio.Play(); StartCoroutine("DelayReload"); } //武器チェンジ if (Input.anyKeyDown) { ChangeWeaponCheck(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
void Shoot ( bool isPierce, bool isDiffuse, int numBullet, AudioClip fireClip, float range, int damagePerShot, int maxNbPower, int maxAmmo ) { //PlayerMovementのSlowMoving()を実行する //重ね掛けしないようにフラグで状態管理 if (isSlow == false) { playerMovement.SlowMoving(); isSlow = true; slowTimer = 0f; } //ワンクリックで同時発射するRayの本数を指定 numRay = numBullet; shootRay = new Ray[numRay]; //射撃SEへ差し替え&再生 gunAudio.clip = fireClip; gunAudio.Play(); //マズルフラッシュの表示 gunLight.enabled = true; //射撃エフェクト 直前に出てたものの中断と新たに表示 gunParticles.Stop(); gunParticles.Play(); //弾道を設定 //Rayの始点(マズル固定)と方向(一定範囲内でランダム)をnumRayの数だけ設定。 SetGunLine(isDiffuse); for (int i = 0; i < numRay; i++) { //SRの処理 if (isPierce) { RaycastHit[] hits = Physics.RaycastAll(shootRay[i]); //射線の終点を射程とイコールに gunLine[i].SetPosition(1, shootRay[i].origin + shootRay[i].direction * range); foreach (var shootHit in hits) { //クラス取得~ノックバック実行までをパッケージ HitEnemy(shootHit, damagePerShot, maxNbPower, i); //なぜか同じ敵に二回ヒットすることがあるので、ぶつかったらforeachを抜ける if (isHit) { break; } } isHit = false; } else //SMG,SGの処理 { //objectのLayerにshootableMask指定するの忘れがち if (Physics.Raycast(shootRay[i], out shootHit, range, shootableMask)) { //クラス取得~ノックバック実行までをパッケージ HitEnemy(shootHit, damagePerShot, maxNbPower, i); //衝突地点を終点に gunLine[i].SetPosition(1, shootHit.point); } else { //rayが何にもぶつからなかった時の射線終点指定 gunLine[i].SetPosition(1, shootRay[i].origin + shootRay[i].direction * range); } } } //残弾数を減らす CurrentAmmo--; //残弾数UIを減らす //1でなく1.0fで書かないとちゃんと割り算の結果が小数で返らないっぽい AmmoFullImg.fillAmount -= 1.0f / maxAmmo; } |
リファクタリングはうまくはまるとパズルを解いたような快感がありますね。漫画やゲームの整理が昔から好きだったのでその楽しさにも似てるかも。
次回はコチラ
前回はコチラ
Unity公式チュートリアルSurvival Shooter WITH PK Chapter.16「調整:攻撃中の移動速度を遅くする」
「Unity公式チュートリアルSurvival Shooter WITH PK Chapter.17.5「リファクタリング」」への3件のフィードバック