Systems
在Quantum高爾夫範例中的系統被分為兩個類別:
- 驅動遊戲遊玩的系統;及,
- 用於管理玩家回合的系統。
遊戲遊玩
遊戲遊玩系統處理特定於Quantum高爾夫範例的所有遊戲邏輯。
最大玩家數量
最大玩家數量是2,並且使用以下的程式碼片段在DSL中固定:#pragma max_players 2。該值也被定義為一個常數:#define MAX_PLAYERS 2,以針對陣列初始化來重新使用它。
設定系統
SetupSystems處理初始遊戲設定——這涵蓋:
- 透過
ISignalOnPlayerDataSet信號來接收RuntimePlayer; - 基於玩家各自的
RuntimePlayer來初始化玩家 - 重新設定玩家的
TurnData執行個體;及, - 透過
SpawnSystem來觸發球生成(請參見相關系統)。
生成系統
SpawnSystem的唯一責任是在SetupSystem請求時生成高爾夫球。為了完成這點,它首先查找GameConfig資產,並且使用一個篩選器來找到所有啟用中的SpawnPoint。SpawnSystem將根據GameConfig中提供的SpawnPointType來生成球,之後將停用SpawnPoint。
生成點
SpawnPoint是場景原型中用於定義生成點位置的元件。
它在其SpawnPointType上持有資料,以及不論在其上一個實體是否已經生成。
NearHole:當在GameConfig資產中選擇NearHole選項來生成球時,定義將使用的SpawnPoint;Regular:在常規遊戲中生成球。
C#
asset ConfigAssets;
asset GameConfig;
asset UserMap;
enum SpawnPointType { Regular, NearHole }
component SpawnPoint
{
SpawnPointType Type;
Boolean IsAvailable;
entity_ref Entity;
}
synced event GameplayEnded { }
遊玩系統
PlaySystem處理球的物理,並且基於其速度來確定回合狀態。透過ISignalOnSkipCommandReceived信號來接收在玩家的打擊上的資訊。
球
球模型使用兩個元件,Actor及BallFields;兩者在Ball.qtn檔案中定義。
演出者
Actor元件持有一個player_ref到控制/擁有其所放置的實體的玩家。Active布林值用於檢查玩家是否目前在這個遊戲中是活躍中,以及在從一個已儲存檔案中載入一個遊戲時使用它。
C#
component Actor
{
Boolean Active;
player_ref Player;
}
球欄位
BallFields元件用於持有一個玩家的球的狀態。
C#
component BallFields
{
asset_ref<BallSpec> Spec;
TurnData TurnStats;
FPVector3 LastPosition;
Boolean HasCompletedCourse;
Int32 EndOfMovementTimer;
Int32 Score;
}
TurnStats:一個TurnData的執行個體,以在該特定玩家的回合時從全域CurrentTurn來累積統計資料。這個特定欄位由TurnSystem維護。LastPosition:在被發射之前的最後的位置。在球著陸在粗糙的場地上時,它用於重新設定球的位置。HasCompletedCourse:追蹤玩家是否已經完成目前的球道。它由PlaySystem檢查及更新。EnOfMovementTimer:隨著PlaySystem而增加的計時器,直到球已經停止。Score:當一個玩家的回合已經結束的時候,由ScoreSystem更新。Score:當一個玩家的回合已經結束的時候,由ScoreSystem更新。
在球發射時
當發射球時,PlaySystem觸發ISignalOnBallShot信號。
輸入
在玩家擊球時發送的PlayCommand之外,常規輸入架構還用於與其他玩家共享目前的瞄準方向及力量條標記位置。這允許在等待中的玩家的一側上複製活躍中玩家的瞄準。
input
{
FPVector3 Direction;
FP ForceBarMarkPos;
}
這些值只用於視覺效果回饋的目的,不用於任何與遊戲遊玩相關的邏輯,即使它們持有與PlayCommand相同的資訊。
力量
應用到球上的力量,是由透過PlayCommand發送為輸入的玩家打擊來定義。PlaySystem執行ISignalOnPlayCommandReceived,這允許其接收PlayCommand。系統處理命令,以確保它是有效的;這有3個步驟:
- 根據
GameConfig資產上定義的值來夾緊打擊資料(方向及力量); - 應用一個力量到球的
PhysicsBody3D來打擊球;及最後的, - 觸發
ISignalOnBallShot信號。
碰撞
在對球應用力量之外,PlaySystem也評估球道的洞及外圍粗糙場地觸發的碰撞。
如果球停在一個 粗糙場地 靜態碰撞器上,就是外圍深綠場地上,則其位置將被重新設定為它在擊球前的位置。
當一個球在Hole圖層上觸發一個靜態碰撞器 並且 其速度低於GameConfig資產中定義的HitHoleVelocityThreshold,則調用OnHitHole()方法且球道被視為完成。
玩家回合
如果全域變數CurrentTurn.Status被設定為TurnStatus.Resolving,它意味著球仍然在移動。在這個期間內,PlaySystem將在各個 Update()檢查球是否已經停止移動。停止閾值由BallSpec資產中設定的值來確定。
如果球的速度低於閾值,它被認為停止。在這個時候,在BallFields元件中的EndOfMovementTimer欄位將開始增加。當它已經到達了EndOfMovementWaitingInTicks值,EndOfPlay()方法將被調用,並且ISignalOnTurnEnded信號將被觸發,因此終止目前的玩家的回合。
當一個遊玩結束時,球的PhysicsBody3D元件被停用,以在該玩家的TurnStatus是Inactive時防止意外的互動。
回合系統
TurnSystem處理由基於回合的功能使用的與回合相關的邏輯。
各個玩家有它們自己的TurnData執行個體以從全域CurrentTurn資料執行個體來累積它們的回合。活躍中的玩家的TurnStatus也由系統持續更新。
當球在PlaySystem中被打擊時,TurnSystem設定全域CurrentTurn.Status到TurnStatus.Resolving並且在活躍中的玩家的BallFields元件TurnStats欄位上調用SetStatus()。
如果從活躍中的玩家接收到一個有效的SkipCommand,則TurnSystem簡單地觸發ISignalOnTurnEnded信號。
當接收到一個ISignalOnTurnEnded信號,TurnSystem檢查TurnType。
- 如果它是
Countdown類型,並且有符合下一個回合的條件的球,它啟用下一個球的回合。 - 如果它是
Play類型,它在目前的玩家的球上調用AccumulateStats()及SetStatus(),以轉譯它停用。在這之後,它在全域CurrentTurn上調用Reset()。
回合資料
基於回合的SDK 是一個預設Quantum SDK的附加元件。它的目標是協助您在您的與回合相關的資料上儲存及運行邏輯。
這大多發生在您的 回合資料架構執行個體 之中,其在一個資料資產中有 可設置參數,並且可以在您的各個玩家/實體上和/或全域範圍內使用,以管理您的遊戲流程。
變數
TurnData架構持有參照到一個Player及一個實體Entity以達成便利性;比如,更輕易地存取資料參照的玩家或實體。它也持有一個參照到一個TurnConfig資產,其攜帶回合的可設置參數的資訊。
每個回合有一個目前的TurnType,其 建議 而不是命令在目前的回合時允許/禁止的互動類型。Quantum高爾夫範例包含兩個預先定義的類型:
Play:允許互動。Countdown:禁止互動,並且暫停遊戲。
每個回合也追蹤其TurnStatus;它用於定義將使用哪些邏輯路徑,以在該狀態為活躍中時操控資料。Quantum高爾夫範例使用3個預先定義的狀態:
Active:可以增加回合計時器,並且接受玩家命令。Inactive:暫停計時器,並且 不 接受玩家命令。Resolving:在目前玩家已經接收到PlayCommand,並且正在執行特定遊戲邏輯(比如,模擬球物理)之後,以信號通知一個暫時狀態。
Number追蹤玩家遊玩的回合數量,並且在調用AccumulateStats()時增加1。這樣的話,當全域CurrentTurn結束時,可以更新活躍中玩家的TurnData。相反地,當全域CurrentTurn被標記為Active時,Ticks變數在每幀都增加。這樣就可以準確地知道各個玩家在目前為止已經遊玩的所有回合中在多少刷新時是活躍的。
C#
enum TurnType { Play, Countdown }
enum TurnStatus { Inactive, Active, Resolving }
struct TurnData
{
player_ref Player;
entity_ref Entity;
asset_ref<TurnConfig> ConfigRef;
TurnType Type;
TurnStatus Status;
Int32 Number;
Int32 Ticks;
}
CurrentTurn是一個全域TurnData執行個體,其在每個新的回合開始時被重新設定,以追蹤目前的玩家的回合。當回合結束時,資料被統合/累積到現有的玩家的回合資料執行個體之中。
回合可能會因為許多不同的原因而結束;Quantum高爾夫範例透過TurnEndReasons列舉來列舉了其中一些。這些可以被更改及新增到回合已經結束的信號之中,並且觸發不同的特定遊戲的邏輯常式。
C#
enum TurnEndReason { Time, Skip, Play, Resolved }
global
{
TurnData CurrentTurn;
}
方法
如果目前的回合使用一個計時器,而且更新TurnData執行個體且狀態為Active時,Ticks將增加1。
如果Ticks值達到了TurnConfig資產中定義的該回合的最大允許數量,則以傳入的TurnEndReason.Time列舉來觸發ISignalOnTurnEnded信號,作為回合結束原因。
C#
public void Update(Frame f)
{
var config = f.FindAsset<TurnConfig>(ConfigRef.Id);
if (config == null || !config.UsesTimer || Status != TurnStatus.Active)
{
return;
}
Ticks++;
if (Ticks >= config.TurnDurationInTicks)
{
f.Signals.OnTurnEnded(this, TurnEndReason.Time);
}
}
TurnData執行個體也可以從另一個執行個體來AccumulateStats();Quantum高爾夫範例中的執行方式將這個轉化為回合的Number的增加1,並且從執行個體累積Ticks。通常在全域CurrentTurn結束時,一個玩家的TurnData執行個體累積其統計資料。
C#
public void AccumulateStats(TurnData from)
{
Ticks += from.Ticks;
Number++;
}
TurnData執行個體提供兩個不同的設定器方法:
SetType()SetStatus()
即使它們不需要Frame的提供,但這樣做允許在需要時觸發事件。
C#
public void SetType(TurnType newType, Frame f = null){
if (Type == newType){
return;
}
var previousType = Type;
Type = newType;
f?.Events.TurnTypeChanged(this, previousType);
}
public void SetStatus(TurnStatus newStatus, Frame f = null){
if (Status == newStatus){
return;
}
var previousStatus = Status;
Status = newStatus;
f?.Events.TurnStatusChanged(this, previousStatus);
if (Status == TurnStatus.Active){
f?.Events.TurnActivated(this);
}
}
玩家的TurnData執行個體可以在遊戲開始時被Reset();所有多載都將重新設定Ticks值,同時也提供了根據選擇的多載來設定其他欄位的能力。
它也可以用於在回合結束時重新設定 全域目前回合,以準備追蹤另一個玩家的回合。
如果提供了一個幀(非強制性),則會在該幀上引發回合計時器重新設定事件。
C#
public void ResetTicks(Frame f = null) {
ResetData(Type, Status, Entity, Player, ConfigRef, f);
}
public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null){
ResetData(type, status, Entity, Player, config, f);
}
public void Reset(EntityRef entity, PlayerRef owner, Frame f = null){
ResetData(Type, Status, entity, owner, ConfigRef, f);
}
public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null){
ResetData(type, status, entity, owner, config, f);
}
得分系統
ScoreSystem處理結束遊戲得分。如果遊戲作為遊玩的一部分而結束(TurnType.Play)——也就是說,玩家成功將它們的球推入洞內——,獲勝玩家的得分如下所示:允許的最大擊球次數 + 1(在GameConfig的MaxStrokes欄位中定義)減去玩家將球擊入洞所使用的擊球次數。
如果球道尚未被完成,則得分為0。
遊戲結束
GameEnd系統在每回合結束時都會檢查遊戲的結束條件是否已經滿足。Quantum高爾夫範例執行2個結束條件:
- 所有玩家已經使用了允許的最大擊球次數。
- 已經完成了球道(也就是,球已經被推入球洞)
當達到這些條件的其中之一,將重新設定全域CurrentTurn資料,並且引發GameplayEnded事件。GameplayEnded事件用於與Unity通信並顯示視覺效果回饋。
基於回合的系統
基於回合的系統是跨遊戲平台的,並且可以用於任何基於回合的遊戲遊玩。
命令系統
CommandSystem負責控制玩家發送的命令類型,並且在相對應的信號/邏輯有效的情況下觸發它們。Quantum高爾夫範例中包含的兩個命令是PlayCommand及SkipCommand。
只接受目前活躍中玩家發送的命令;在全域CurrentTurn資料欄位中參照活躍中玩家。
當接收到PlayCommand時,全域CurrentTurn狀態被設定為Resolving,以在可能需要執行特定遊戲邏輯的期間停止計時器。
C#
public override void Update(Frame f)
{
var currentTurn = f.Global->CurrentTurn;
if (currentTurn.Status != TurnStatus.Active)
{
return;
}
var currentPlayer = f.Global->CurrentTurn.Player;
switch (f.GetPlayerCommand(currentPlayer))
{
case PlayCommand playCommand:
if (currentTurn.Type != TurnType.Play)
{
return;
}
f.Signals.OnPlayCommandReceived(currentPlayer, playCommand.Data);
f.Events.PlayCommandReceived(currentPlayer, playCommand.Data);
break;
case SkipCommand skipCommand:
var config = f.FindAsset<TurnConfig>(currentTurn.ConfigRef.Id);
if (!config.IsSkippable)
{
return;
}
f.Signals.OnSkipCommandReceived(currentPlayer, skipCommand.Data);
f.Events.SkipCommandReceived(currentPlayer, skipCommand.Data);
break;
}
}
}
回合計時器系統
TurnTimerSystem負責在每幀在全域CurrentTurn上調用Update()。
C#
public unsafe class TurnTimerSystem : SystemMainThread
{
public override void Update(Frame f)
{
f.Global->CurrentTurn.Update(f);
}
}
信號
Quantum信號負責系統間通信。
可以觸發 回合結束 信號以表明回合已經因為給定的 回合結束原因 而結束,並且應該用於觸發特定遊戲的回合控制邏輯,比如將回合傳送到在Quantum高爾夫範例的下一名玩家。
當接收到相對應的有效命令時(在系統上有更多關於玩家命令有效性的資訊),引發 已接收遊玩/略過命令 信號,該信號可以用於觸發所需的特定遊戲邏輯,比如擊球或結束Quantum高爾夫範例的回合。
C#
signal OnTurnEnded (TurnData data, TurnEndReason reason);
signal OnPlayCommandReceived (PlayerRef player, PlayCommandData data);
signal OnSkipCommandReceived (PlayerRef player, SkipCommandData data);
事件
Quantum事件負責將在模擬中發生的事情傳達給轉譯引擎,因此它可以透過所需的音效視覺效果回饋來做出相對應的回應。
如需更多關於常規/同步/抽象Quantum事件的資訊,請參見此處。
- 當透過回合資料設定方法進行 回合類型/狀態更改 時(更多資訊請參見方法),引發一個事件,其攜帶該回合資料執行個體值及先前的 類型/狀態。
- 當透過調用傳送非空值幀的重新設定方法來 重新設定回合計時器 時,引發一個事件,其攜帶該回合資料執行個體值。
- 當透過在設定狀態方法中更改 狀態 為 啟用 來 啟用回合 時,或 因為給定原因而結束回合 時,引發一個事件來發送其信號,在第二種情況下傳送該原因。
- 當各自的命令系統 接收到有效的遊玩/略過命令 時,引發一個事件來發送其信號。
C#
abstract event TurnEvent { TurnData Turn; }
synced event TurnTypeChanged : TurnEvent { TurnType PreviousType; }
synced event TurnStatusChanged : TurnEvent { TurnStatus PreviousStatus; }
synced event TurnEnded : TurnEvent { TurnEndReason Reason; }
synced event TurnTimerReset : TurnEvent { }
synced event TurnActivated : TurnEvent { }
abstract event CommandEvent { player_ref Player; }
event PlayCommandReceived : CommandEvent { PlayCommandData Data; }
event SkipCommandReceived : CommandEvent { SkipCommandData Data; }
Back to top