Unity遊戲暫停,控制時間的設計
上週遊戲釋出測試版本, 其他方向的反饋先不提。
被玩家吐槽的巨多的就是遊戲中的技能。
具體情況是 我們遊戲在戰鬥中使用的技能的時候:
- 有遮罩,會遮蔽玩家操作。
- 玩家不會無敵,而且怪物會移動。
- 而且技能動作邏輯和UI是同步的,導致有可能玩家的發出的動作和特效什麼的完全看不到。這可是遊戲核心玩法之一啊。
本來我們的遊戲就是一個主玩關卡的遊戲, 所以有一些簡單的放風箏,繞樁等元素存在。所以這種東西確實是很有必要改的。
在策劃還沒完全想明白應該怎麼去做的時候。
策劃決定先用暫停來解決這種問題:
- 當玩家釋放技能的時候, 所有邏輯暫停,直到技能UI結束。
在我和策劃的討論中,又作死的問了他們 是不是會有類似於 時間禁錮
- 單獨對某幾個怪的所有邏輯進行減速。
- 對全部怪物進行減速
策劃歪歪想 確實可能會有。
然後我就走向作死的道路。
那我們先來分析需求:
暫停
如果是普通的暫停, 比如在遊戲中點選退出按鈕,類似於這樣的,全遊戲邏輯暫停。 實際我們也沒必要說去實現一些複雜的功能需求。直接
Time.timescale = 0
就可以了。
區域性暫停
這種需求和上面就不一樣了。
比如遊戲中,當某個物件釋放技能的時候,除了這個物件之外的其他物件都暫停。
這種使用TimeScale就實現不了,因為TimeScale是全域性影響的。
這就需要我們自己接管Update,然後我們自己去控制Update的更新。如果你專案中沒有自己接管物件的Update 那麼趕緊做吧。
到這一步假定你們遊戲已經實現了Update統一。
我們的需求要求: 我們在對某個物件進行減速的時候, 那麼與他相關的所有的對東西都會被減速, 比如他發出的子彈, 比如他施放的技能, 比如技能招出的特效。
對於這樣的情況:
我們可以把整個結構分層:
一個Monster是一個域, 他產生東西都屬於他的域, 域可以被繼承, 比如他的槍打出的子彈還是屬於他的域。 域同樣可以被截斷, 比如一個Monster產生的另外一個Monster 就不屬於同一個域。 域的根就是Monster。 比較特殊的有 Buff這種, 這種就需要跟策劃討論。我們這邊的決定是 誰身上的BUff屬於誰的域
瞭解了好上面的概念,我們只需要一個域管理器裡面根據每個域儲存對應的域的物件:
下面上程式碼:
域物件介面:
public interface DomainObject {
//獲取例項ID
int nDomainId { get; set; }
//速度比例
float nSpeedPercent { get; set; }
void onSpeedChange (float nSpeed);
}
域管理器, 這個實際上應該是一個單例類
public abstract class Singleton<T> where T:new()
{
protected static T _instance;
public static T Instance
{
get
{
if (_instance == null) {
_instance = new T ();
}
return _instance;
}
}
}
public class DomainManager:Singleton<DomainManager> {
Dictionary<int, Dictionary<int, DomainObject>> m_oDomainObjectDict;
public void AddDomainObject(DomainObject oObject) {
Dictionary<int, DomainObject> oOut;
int nDomainId = oObject.nDomainId;
if(!m_oDomainObjectDict.TryGetValue( nDomainId, out oOut )) {
oOut = new Dictionary<int, DomainObject>();
m_oDomainObjectDict.Add( nDomainId, oOut );
}
if (oOut.ContainsKey( nDomainId )) {
return;
}
oOut.Add( nDomainId, oObject );
}
public void RemoveDomainObject (DomainObject oObject) {
Dictionary<int, DomainObject> oOut;
int nDomainId = oObject.nDomainId;
if (!m_oDomainObjectDict.TryGetValue( nDomainId, out oOut )) {
return;
}
if (!oOut.ContainsKey( nDomainId )) {
return;
}
oOut.Remove( nDomainId );
}
public void ChangeDomainSpeed(int nDomainId, float nSpeedPercent) {
Dictionary<int, DomainObject> oOut;
if (!m_oDomainObjectDict.TryGetValue( nDomainId, out oOut )) {
return;
}
foreach (var item in oOut) {
var iter = oOut.GetEnumerator();
while (iter.MoveNext()) {
iter.Current.Value.nSpeedPercent = nSpeedPercent;
}
iter.Dispose();
}
}
}
工具類
public static class FightHelper {
public static void ChangeAllSpeed (Transform tr, float nSpeed) {
//Animation
Animation oAni = tr.GetComponent<Animation>();
if (oAni) {
foreach (AnimationState state in oAni) {
state.speed = nSpeed;
}
}
//ParticleSystem
ParticleSystem oPar = tr.GetComponent<ParticleSystem>();
if (oPar) {
oPar.playbackSpeed = nSpeed;
}
Animator oAnimator = tr.GetComponent<Animator>();
if (oAnimator) {
oAnimator.speed = nSpeed;
}
int childcount = tr.childCount;
for (int i = 0; i >= childcount; i--) {
ChangeAllSpeed( tr.GetChild( i ), nSpeed );
}
}
}
列子:
public class TestDomainObject : MonoBehaviour, DomainObject {
public int nDomainId {
get {
return GetInstanceID();
}
set { }
}
public float nSpeedPercent {
get {
return _nSpeedPercent;
}
set {
value = value < 0 ? 0 : value;
if(_nSpeedPercent == value) {
return;
}
_nSpeedPercent = value;
onSpeedChange(_nSpeedPercent);
}
}
private float _nSpeedPercent = 1;
public void onSpeedChange (float nSpeed) {
DomainManager.Instance.ChangeDomainSpeed( GetInstanceID(), nSpeed );
FightHelper.ChangeAllSpeed( transform, nSpeed );
//DO SometionSelf
}
例子:
public void FightUpdate (float nDeltatime) {
nDeltatime *= nSpeedPercent;
//DO You Thing
}
}
public class TestGun:MonoBehaviour, DomainObject {
long nGunId;
public int nBulletId {
get {
//DO You thing
return 0;
}
}
public int nDomainId {
get;
set;
}
public float nSpeedPercent {
get {
return _nSpeedPercent;
}
set {
value = value < 0 ? 0 : value;
if (_nSpeedPercent == value) {
return;
}
_nSpeedPercent = value;
onSpeedChange( _nSpeedPercent );
}
}
public void FightUpdate (float nDeltatime) {
nDeltatime *= nSpeedPercent;
//DO You Thing
}
private float _nSpeedPercent = 1;
public void onSpeedChange (float nSpeed) {
DomainManager.Instance.ChangeDomainSpeed( GetInstanceID(), nSpeed );
FightHelper.ChangeAllSpeed( transform, nSpeed );
//DO SometionSelf
}
public void Init(long nGunId) {
this.nGunId = nGunId;
}
//當被歸還到物件池的時候
public void OnGiveBack () {
DomainManager.Instance.RemoveDomainObject( this );
}
public void InitDomainInfo(int nDomainId, int nSpeed) {
this.nDomainId = nDomainId;
this.nSpeedPercent = nSpeed;
DomainManager.Instance.AddDomainObject( this );
}
public void GetBullet () {
//自己的實現,只是展示一下做法
Bullet oBullet = new GameObject().AddComponent<Bullet>();
oBullet.Init( nBulletId );
oBullet.InitDomainInfo( nDomainId, nSpeedPercent );
}
}
幾個特別需要注意的是:
策劃一般都會有這幾種需求
1. 保持SpeedPercent 不要小於0, 小於0會出現問題
2. 全域性怪物減速, 這個就要求你們的戰鬥管理擁有怪物的例項列表,去遍歷通知
3. 單個怪物減速, 主要是這個和全域性減速可能會有衝突。這個得看你們策劃的想法,我們的效率採取的是 怪物Speed * 全怪物Speed
4. 需要考慮的優化方向,隱藏的怪物,不執行變速邏輯,在OnEnable裡面在進行變速處理, 更深一層的檢測, OnEnable裡面做判斷,是否需要做變速處理。 這個主要是因為我們的遊戲有動態視野隱藏的處理。 所以需要考慮這個優化
5. 如果你們對於Domain的使用足夠多,那麼你們完全可以把Domain單獨寫一個結構, 介面改成InitDomain(DomainInfo oInfo); 這樣的話方便擴充套件,易於維護。
以上
NOTE: 域的概念是參考的星際2編輯器, 剛開始不是這樣設計的 寫了1天半,寫到最後直接全刪了。參考星際2的域概念之後1下午就搞定。果然還是要多見識比較好的編輯器的設計。 同時感謝大帥同學的提醒。
寫文章就是傷感啊, 貼程式碼也很傷感的啊。還是喜歡記一下思路就好。。 就隨便寫寫