Related Posts Plugin for WordPress, Blogger...

2-14 Using Interfaces In C#

本章要來介紹Interface在C#的妙用,除去大家可能習以為常的設計模式之類的,今天要談的是運用Interface在Unity中,製作一個可以進行攻擊的物件,並使Player跟Enemy損血。

首先,在Project視窗中找到一個RollerBall物件,這東西放在Standard Asset中,若沒有就請大家自己匯入吧。

接著替RollerBall更名為Projectile,並且新增Rigidbody、Sphere Collider,Rigidbody的Is Kinematic打勾,Sphere Collider的Is Trigger打勾。簡言之依照下圖進行設定。

上述設定方式,是為確保Trigger可以跟任何物件進行反應,大家可以從Untiy官網有關Collider教學中看到表格中有相關介紹。
https://docs.unity3d.com/Manual/CollidersOverview.html

Trigger messages are sent upon collision
Static ColliderRigidbody ColliderKinematic Rigidbody ColliderStatic Trigger ColliderRigidbody Trigger ColliderKinematic Rigidbody Trigger Collider
Static Collider    YY
Rigidbody Collider   YYY
Kinematic Rigidbody Collider   YYY
Static Trigger Collider YY YY
Rigidbody Trigger ColliderYYYYYY
Kinematic Rigidbody Trigger ColliderYYYYYY
接下來,新增一個名為IDamageable的Interface吧。
IDamageable的寫法如下:

public interface IDamageable{
 void TakeDamage(float damage);
}



然後,讓Player.cs跟Enemy.cs實作IDamageable的TakeDamage方法,亦即當有其他物件碰觸到Player跟Enemy時,都會呼叫TakeDamage方法。

Player.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour, IDamageable {

 [SerializeField] float maxHealthPoints = 100f;

 float currentHealthPoint = 100f;

 public float healthAsPercentage{
  get{ return currentHealthPoint / maxHealthPoints; }
 }

 public void TakeDamage(float damage){
  // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值
  currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints);
 }
}

Enemy.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityStandardAssets.Characters.ThirdPerson;

public class Enemy : MonoBehaviour, IDamageable {

 [SerializeField] float maxHealthPoints = 100f;
 [SerializeField] float attackRadius = 5.0f;

 float currentHealthPoint = 100f;
 AICharacterControl aiCharacterControl = null;
 GameObject player;

 public float healthAsPercentage{
  get{ return currentHealthPoint / maxHealthPoints; }
 }

 void Start(){
  aiCharacterControl = GetComponent ();
  player = GameObject.FindGameObjectWithTag ("Player");
 }

 void Update(){
  //計算Player跟Enemy的距離
  float distanceToPlayer = Vector3.Distance (player.transform.position, transform.position);
  //若彼此之間的距離小於AttackRadius,就讓Enemy開始追蹤Player
  if (distanceToPlayer <= attackRadius) {
   aiCharacterControl.SetTarget (player.transform);
  } else {
   aiCharacterControl.SetTarget (transform);
  }
 }

 public void TakeDamage(float damage){
  // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值
  currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints);
 }
}


最後,我們要替那顆球Projectile新增Projectile.cs。此時,就能夠看出Interface的好處了,不管球球碰到的是Player還是Enemy,我們呼叫的Function都是來自IDamageable的TakeDamage。若是不透過Interface的寫法,我們就必須要判斷碰到的是Player還是Enemy然後再呼叫個別的TakeDamage。顯而易見,後者的做法就無法達到統一呼叫的作用了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Projectile : MonoBehaviour {

 [SerializeField] float damageCaused = 10f;

 void OnTriggerEnter(Collider collider){
  // 取得Component為IDamageable
  Component damageableComponent = collider.gameObject.GetComponent (typeof(IDamageable));
  // 若IDamageable存在
  if (damageableComponent) {
   // 呼叫damageableComponent的TakeDamage方法
   (damageableComponent as IDamageable).TakeDamage (damageCaused);
  }
 }
}


讓我們來執行遊戲吧,當Player經過那顆球球時,就會因為碰觸到而損血。

假如引誘敵人去碰觸那顆球球,也會跟著損血了。

留言