Spaces:
Runtime error
Runtime error
Upload 84 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- data/HFSM.TrueDecision.cs +15 -0
- data/IBotDebug.cs +15 -0
- data/IdleAction.cs +15 -0
- data/IncreaseBlackboardInt.cs +27 -0
- data/Input.User.cs +29 -0
- data/InputHandler.cs +74 -0
- data/KCCSettings.txt +194 -0
- data/Lists.txt +12 -0
- data/Map.cs +1 -0
- data/MovesIndicatorManager.cs +60 -0
- data/PickupSpawnerSystem.cs +35 -0
- data/PieceView.cs +28 -0
- data/PiecesMapBaker.cs +59 -0
- data/PlayCommand.cs +26 -0
- data/PlayerSystem.cs +25 -0
- data/Pool.cs +67 -0
- data/QuantumConsoleRunner.cs +29 -0
- data/QuantumJsonSerializer.cs +112 -0
- data/ReplayJsonSerializerSettings.cs +13 -0
- data/ReplayRunnerSample.cs +63 -0
- data/ResponseCurve.cs +33 -0
- data/RuntimeConfig.User.cs +38 -0
- data/RuntimePlayer.User.cs +24 -0
- data/SelfDestroy.cs +14 -0
- data/SetBlackboardInt.cs +23 -0
- data/SimulationConfig.User.cs +13 -0
- data/SkipCommand.cs +21 -0
- data/SpawnFX.cs +14 -0
- data/StopwatchBlock.cs +24 -0
- data/SystemSetup.cs +56 -0
- data/Traktor.User.cs +51 -0
- data/TraktorConfig.cs +15 -0
- data/TraktorInputSystem.cs +33 -0
- data/TraktorView.cs +50 -0
- data/TurnConfig.cs +12 -0
- data/TurnData.cs +115 -0
- data/TurnData.qtn +40 -0
- data/TurnData.txt +115 -0
- data/TurnTimerSystem.cs +12 -0
- data/UT.qtn +30 -0
- data/UTAgent.User.cs +13 -0
- data/UTManager.cs +48 -0
- data/UTMomentumData.User.cs +7 -0
- data/UTRoot.cs +7 -0
- data/UseCardCommand.cs +16 -0
- data/UtilityReasoner.User.cs +352 -0
- data/WaitLeaf.cs +60 -0
- data/input.txt +120 -0
- data/kinematic character.txt +397 -0
- data/list1.txt +10 -0
data/HFSM.TrueDecision.cs
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
[Serializable]
|
7 |
+
[AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)]
|
8 |
+
public partial class TrueDecision : HFSMDecision
|
9 |
+
{
|
10 |
+
public override unsafe bool Decide(Frame frame, EntityRef entity)
|
11 |
+
{
|
12 |
+
return true;
|
13 |
+
}
|
14 |
+
}
|
15 |
+
}
|
data/IBotDebug.cs
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
namespace Quantum
|
2 |
+
{
|
3 |
+
// Interface to make the communication between the three solutions involved: quantum_unity, quantum_code and quantum.ai.editor
|
4 |
+
// Basically, these are the information that the quantum.ai.editor needs to know that are meant to be filed from Unity
|
5 |
+
public interface IBotDebug
|
6 |
+
{
|
7 |
+
EntityRef EntityRef { get; }
|
8 |
+
Frame Frame { get; }
|
9 |
+
|
10 |
+
// The asset names on Unity
|
11 |
+
string GetHFSMRootName();
|
12 |
+
string GetBTRootName();
|
13 |
+
string GetUTRootName();
|
14 |
+
}
|
15 |
+
}
|
data/IdleAction.cs
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.Runtime.InteropServices;
|
3 |
+
using Photon.Deterministic;
|
4 |
+
|
5 |
+
namespace Quantum
|
6 |
+
{
|
7 |
+
[Serializable]
|
8 |
+
[AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)]
|
9 |
+
public partial class IdleAction : AIAction
|
10 |
+
{
|
11 |
+
public override unsafe void Update(Frame frame, EntityRef entity)
|
12 |
+
{
|
13 |
+
}
|
14 |
+
}
|
15 |
+
}
|
data/IncreaseBlackboardInt.cs
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
|
3 |
+
namespace Quantum
|
4 |
+
{
|
5 |
+
[Serializable]
|
6 |
+
[AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)]
|
7 |
+
public unsafe partial class IncreaseBlackboardInt : AIAction
|
8 |
+
{
|
9 |
+
public AIBlackboardValueKey Key;
|
10 |
+
public AIParamInt IncrementAmount;
|
11 |
+
|
12 |
+
public override unsafe void Update(Frame frame, EntityRef entity)
|
13 |
+
{
|
14 |
+
var blackboard = frame.Unsafe.GetPointer<AIBlackboardComponent>(entity);
|
15 |
+
|
16 |
+
var agent = frame.Unsafe.GetPointer<HFSMAgent>(entity);
|
17 |
+
var aiConfig = agent->GetConfig(frame);
|
18 |
+
|
19 |
+
var incrementValue = IncrementAmount.Resolve(frame, entity, blackboard, aiConfig);
|
20 |
+
|
21 |
+
var currentAmount = blackboard->GetInteger(frame, Key.Key);
|
22 |
+
currentAmount += incrementValue;
|
23 |
+
|
24 |
+
blackboard->Set(frame, Key.Key, currentAmount);
|
25 |
+
}
|
26 |
+
}
|
27 |
+
}
|
data/Input.User.cs
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
unsafe partial struct Input
|
7 |
+
{
|
8 |
+
// input bandwidth trick by Erick Passos
|
9 |
+
// using a single Byte to encode a 2D direction with 2 degrees of accuracy
|
10 |
+
public FPVector2 Direction
|
11 |
+
{
|
12 |
+
get {
|
13 |
+
if (EncodedDirection == default) return default;
|
14 |
+
Int32 angle = ((Int32)EncodedDirection - 1) * 2;
|
15 |
+
return FPVector2.Rotate(FPVector2.Up, angle * FP.Deg2Rad);
|
16 |
+
}
|
17 |
+
set {
|
18 |
+
if (value == default)
|
19 |
+
{
|
20 |
+
EncodedDirection = default;
|
21 |
+
return;
|
22 |
+
}
|
23 |
+
var angle = FPVector2.RadiansSigned(FPVector2.Up, value) * FP.Rad2Deg;
|
24 |
+
angle = (((angle + 360) % 360) / 2) + 1;
|
25 |
+
EncodedDirection = (Byte)(angle.AsInt);
|
26 |
+
}
|
27 |
+
}
|
28 |
+
}
|
29 |
+
}
|
data/InputHandler.cs
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System.Collections;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using UnityEngine;
|
4 |
+
using Quantum;
|
5 |
+
using Photon.Deterministic;
|
6 |
+
|
7 |
+
public unsafe class InputHandler : MonoBehaviour
|
8 |
+
{
|
9 |
+
|
10 |
+
public int InitialIndex;
|
11 |
+
public int TargetIndex;
|
12 |
+
public bool HasInitial;
|
13 |
+
public bool DebugMoves = false;
|
14 |
+
public MovesIndicatorManager MovesIndicator;
|
15 |
+
public GameObject SelectedPieceIndicator;
|
16 |
+
|
17 |
+
public UnityEngine.LayerMask BoardsRaycastMask;
|
18 |
+
|
19 |
+
private void Update()
|
20 |
+
{
|
21 |
+
HandleClick();
|
22 |
+
|
23 |
+
if (HasInitial)
|
24 |
+
{
|
25 |
+
ChessViewUpdater.Instance.SetObjectByIndex(SelectedPieceIndicator, InitialIndex);
|
26 |
+
}
|
27 |
+
else {
|
28 |
+
SelectedPieceIndicator.SetActive(false);
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
public void HandleClick()
|
33 |
+
{
|
34 |
+
if (UnityEngine.Input.GetMouseButtonDown(0))
|
35 |
+
{
|
36 |
+
// Perform the Unity raycast
|
37 |
+
Ray ray = Camera.main.ScreenPointToRay(UnityEngine.Input.mousePosition);
|
38 |
+
RaycastHit hit;
|
39 |
+
Physics.Raycast(ray, out hit, 100, BoardsRaycastMask);
|
40 |
+
|
41 |
+
// If the raycast hit the Board...
|
42 |
+
if (hit.collider != null)
|
43 |
+
{
|
44 |
+
var position = new FPVector2(FP.FromFloat_UNSAFE(hit.point.x), FP.FromFloat_UNSAFE(hit.point.z));
|
45 |
+
if (HasInitial == false || DebugMoves)
|
46 |
+
{
|
47 |
+
InitialIndex = BoardHelper.GetIndexByPosition(position);
|
48 |
+
HasInitial = true;
|
49 |
+
MovesIndicator.UpdatePossibleMovements(InitialIndex);
|
50 |
+
}
|
51 |
+
else
|
52 |
+
{
|
53 |
+
TargetIndex = BoardHelper.GetIndexByPosition(position);
|
54 |
+
Frame f = QuantumRunner.Default.Game.Frames.Verified;
|
55 |
+
if (MoveValidatorHelper.IsValidMove(ref f.Global->Board, InitialIndex, TargetIndex, true) == false)
|
56 |
+
{
|
57 |
+
InitialIndex = TargetIndex;
|
58 |
+
MovesIndicator.UpdatePossibleMovements(InitialIndex);
|
59 |
+
}
|
60 |
+
else
|
61 |
+
{
|
62 |
+
HasInitial = false;
|
63 |
+
//sendcommand
|
64 |
+
var c = new MoveCommand();
|
65 |
+
c.Data.InitialIndex = InitialIndex;
|
66 |
+
c.Data.TargetIndex = TargetIndex;
|
67 |
+
QuantumRunner.Default.Game.SendCommand(c);
|
68 |
+
MovesIndicator.ResetPrefabs();
|
69 |
+
}
|
70 |
+
}
|
71 |
+
}
|
72 |
+
}
|
73 |
+
}
|
74 |
+
}
|
data/KCCSettings.txt
ADDED
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
using Quantum.Core;
|
4 |
+
|
5 |
+
namespace Quantum
|
6 |
+
{
|
7 |
+
public enum KCCMovementType
|
8 |
+
{
|
9 |
+
None,
|
10 |
+
Free,
|
11 |
+
Tangent
|
12 |
+
}
|
13 |
+
|
14 |
+
public struct KCCMovementData
|
15 |
+
{
|
16 |
+
public KCCMovementType Type;
|
17 |
+
public FPVector2 Correction;
|
18 |
+
public FPVector2 Direction;
|
19 |
+
public FP MaxPenetration;
|
20 |
+
}
|
21 |
+
|
22 |
+
unsafe partial class KCCSettings : AssetObject
|
23 |
+
{
|
24 |
+
// This is the KCC actual radius (non penetrable)
|
25 |
+
public FP Radius = FP._0_50;
|
26 |
+
public Int32 MaxContacts = 2;
|
27 |
+
public FP AllowedPenetration = FP._0_10;
|
28 |
+
public FP CorrectionSpeed = FP._10;
|
29 |
+
public FP BaseSpeed = FP._2;
|
30 |
+
public FP Acceleration = FP._10;
|
31 |
+
public Boolean Debug = false;
|
32 |
+
public FP Brake = 1;
|
33 |
+
|
34 |
+
public void Init(ref KCC kcc)
|
35 |
+
{
|
36 |
+
kcc.Settings = this;
|
37 |
+
kcc.MaxSpeed = BaseSpeed;
|
38 |
+
kcc.Acceleration = Acceleration;
|
39 |
+
}
|
40 |
+
|
41 |
+
public void SteerAndMove(FrameBase f, EntityRef entity, in KCCMovementData movementData)
|
42 |
+
{
|
43 |
+
KCC* kcc = null;
|
44 |
+
if (f.Unsafe.TryGetPointer(entity, out kcc) == false)
|
45 |
+
{
|
46 |
+
return;
|
47 |
+
}
|
48 |
+
|
49 |
+
Transform2D* transform = null;
|
50 |
+
if (f.Unsafe.TryGetPointer(entity, out transform) == false)
|
51 |
+
{
|
52 |
+
return;
|
53 |
+
}
|
54 |
+
|
55 |
+
if (movementData.Type != KCCMovementType.None)
|
56 |
+
{
|
57 |
+
kcc->Velocity += kcc->Acceleration * f.DeltaTime * movementData.Direction;
|
58 |
+
if (kcc->Velocity.SqrMagnitude > kcc->MaxSpeed * kcc->MaxSpeed)
|
59 |
+
{
|
60 |
+
kcc->Velocity = kcc->Velocity.Normalized * kcc->MaxSpeed;
|
61 |
+
}
|
62 |
+
//transform->Rotation = FPVector2.RadiansSigned(FPVector2.Up, movementData.Direction);// FPMath.Atan2(kcc->Velocity.Y, kcc->Velocity.X);
|
63 |
+
}
|
64 |
+
else
|
65 |
+
{
|
66 |
+
// brake instead?
|
67 |
+
kcc->Velocity = FPVector2.MoveTowards(kcc->Velocity, FPVector2.Zero, f.DeltaTime * Brake);
|
68 |
+
}
|
69 |
+
|
70 |
+
if (movementData.MaxPenetration > AllowedPenetration)
|
71 |
+
{
|
72 |
+
if (movementData.MaxPenetration > AllowedPenetration * 2)
|
73 |
+
{
|
74 |
+
transform->Position += movementData.Correction;
|
75 |
+
}
|
76 |
+
else
|
77 |
+
{
|
78 |
+
transform->Position += movementData.Correction * f.DeltaTime * CorrectionSpeed;
|
79 |
+
}
|
80 |
+
|
81 |
+
}
|
82 |
+
|
83 |
+
|
84 |
+
transform->Position += kcc->Velocity * f.DeltaTime;
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
#if DEBUG
|
89 |
+
if (Debug)
|
90 |
+
{
|
91 |
+
Draw.Circle(transform->Position, Radius, ColorRGBA.Blue);
|
92 |
+
Draw.Ray(transform->Position, transform->Forward * Radius, ColorRGBA.Red);
|
93 |
+
}
|
94 |
+
#endif
|
95 |
+
}
|
96 |
+
|
97 |
+
public KCCMovementData ComputeRawMovement(FrameBase f, EntityRef entity, FPVector2 direction)
|
98 |
+
{
|
99 |
+
KCC* kcc = null;
|
100 |
+
if (f.Unsafe.TryGetPointer(entity, out kcc) == false)
|
101 |
+
{
|
102 |
+
return default;
|
103 |
+
}
|
104 |
+
|
105 |
+
Transform2D* transform = null;
|
106 |
+
if (f.Exists(entity) == false || f.Unsafe.TryGetPointer(entity, out transform) == false)
|
107 |
+
{
|
108 |
+
return default;
|
109 |
+
}
|
110 |
+
|
111 |
+
KCCMovementData movementPack = default;
|
112 |
+
|
113 |
+
|
114 |
+
movementPack.Type = direction != default ? KCCMovementType.Free : KCCMovementType.None;
|
115 |
+
movementPack.Direction = direction;
|
116 |
+
Shape2D shape = Shape2D.CreateCircle(Radius);
|
117 |
+
|
118 |
+
var layer = f.Layers.GetLayerMask("Static");
|
119 |
+
var hits = f.Physics2D.OverlapShape(transform->Position, FP._0, shape, layer, options: QueryOptions.HitStatics | QueryOptions.ComputeDetailedInfo);
|
120 |
+
int count = Math.Min(MaxContacts, hits.Count);
|
121 |
+
|
122 |
+
if (hits.Count > 0)
|
123 |
+
{
|
124 |
+
Boolean initialized = false;
|
125 |
+
hits.Sort(transform->Position);
|
126 |
+
for (int i = 0; i < hits.Count && count > 0; i++)
|
127 |
+
{
|
128 |
+
// ignore triggers
|
129 |
+
if (hits[i].IsTrigger)
|
130 |
+
{
|
131 |
+
// callback here...
|
132 |
+
continue;
|
133 |
+
}
|
134 |
+
|
135 |
+
// ignoring "self" contact
|
136 |
+
if (hits[i].Entity == entity)
|
137 |
+
{
|
138 |
+
continue;
|
139 |
+
}
|
140 |
+
|
141 |
+
var contactPoint = hits[i].Point;
|
142 |
+
var contactToCenter = transform->Position - contactPoint;
|
143 |
+
var localDiff = contactToCenter.Magnitude - Radius;
|
144 |
+
|
145 |
+
|
146 |
+
#if DEBUG
|
147 |
+
if (Debug)
|
148 |
+
{
|
149 |
+
Draw.Circle(contactPoint, FP._0_10, ColorRGBA.Red);
|
150 |
+
}
|
151 |
+
#endif
|
152 |
+
|
153 |
+
var localNormal = contactToCenter.Normalized;
|
154 |
+
|
155 |
+
count--;
|
156 |
+
|
157 |
+
// define movement type
|
158 |
+
if (!initialized)
|
159 |
+
{
|
160 |
+
initialized = true;
|
161 |
+
|
162 |
+
if (direction != default)
|
163 |
+
{
|
164 |
+
var angle = FPVector2.RadiansSkipNormalize(direction.Normalized, localNormal);
|
165 |
+
if (angle >= FP.Rad_90)
|
166 |
+
{
|
167 |
+
var d = FPVector2.Dot(direction, localNormal);
|
168 |
+
var tangentVelocity = direction - localNormal * d;
|
169 |
+
if (tangentVelocity.SqrMagnitude > FP.EN4)
|
170 |
+
{
|
171 |
+
movementPack.Direction = tangentVelocity.Normalized;
|
172 |
+
movementPack.Type = KCCMovementType.Tangent;
|
173 |
+
}
|
174 |
+
else
|
175 |
+
{
|
176 |
+
movementPack.Direction = default;
|
177 |
+
movementPack.Type = KCCMovementType.None;
|
178 |
+
}
|
179 |
+
|
180 |
+
}
|
181 |
+
}
|
182 |
+
movementPack.MaxPenetration = FPMath.Abs(localDiff);
|
183 |
+
}
|
184 |
+
|
185 |
+
// any real contact contributes to correction and average normal
|
186 |
+
var localCorrection = localNormal * -localDiff;
|
187 |
+
movementPack.Correction += localCorrection;
|
188 |
+
}
|
189 |
+
}
|
190 |
+
|
191 |
+
return movementPack;
|
192 |
+
}
|
193 |
+
}
|
194 |
+
}
|
data/Lists.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
In quantum we can to list as metioned below.
|
3 |
+
we can to list in Quantum by the following
|
4 |
+
|
5 |
+
component Targets {
|
6 |
+
list<EntityRef> Enemies;
|
7 |
+
}
|
8 |
+
The basic API methods for dealing with these Lists are:
|
9 |
+
|
10 |
+
Frame.AllocateList<T>()
|
11 |
+
Frame.FreeList(QListPtr<T> ptr)
|
12 |
+
Frame.ResolveList(QListPtr<T> ptr)
|
data/Map.cs
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
|
data/MovesIndicatorManager.cs
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System.Collections;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using UnityEngine;
|
4 |
+
using Quantum;
|
5 |
+
|
6 |
+
public unsafe class MovesIndicatorManager : QuantumCallbacks
|
7 |
+
{
|
8 |
+
|
9 |
+
public GameObject HighlightPrefab;
|
10 |
+
|
11 |
+
private List<GameObject> _prefabs = new List<GameObject>();
|
12 |
+
|
13 |
+
public void UpdatePossibleMovements(int index)
|
14 |
+
{
|
15 |
+
var f = QuantumRunner.Default.Game.Frames.Verified;
|
16 |
+
ResetPrefabs();
|
17 |
+
for (int i = 0; i < f.Global->Board.Cells.Length; i++)
|
18 |
+
{
|
19 |
+
if (MoveValidatorHelper.IsValidMove(ref f.Global->Board, index, i, true))
|
20 |
+
{
|
21 |
+
var FPPosition = BoardHelper.GetCordinatesByIndex(i);
|
22 |
+
Vector3 position = new Vector3((float)FPPosition.X + .5f, .5f, (float)FPPosition.Y + .5f);
|
23 |
+
var prefab = GetPrefab();
|
24 |
+
if (prefab == null)
|
25 |
+
{
|
26 |
+
var go = Instantiate(HighlightPrefab, position, Quaternion.identity, transform);
|
27 |
+
go.SetActive(true);
|
28 |
+
_prefabs.Add(go);
|
29 |
+
}
|
30 |
+
else
|
31 |
+
{
|
32 |
+
prefab.SetActive(true);
|
33 |
+
prefab.transform.position = position;
|
34 |
+
}
|
35 |
+
}
|
36 |
+
}
|
37 |
+
}
|
38 |
+
private GameObject GetPrefab()
|
39 |
+
{
|
40 |
+
for (int i = 0; i < _prefabs.Count; i++)
|
41 |
+
{
|
42 |
+
if (_prefabs[i] != null && _prefabs[i].activeSelf == false)
|
43 |
+
{
|
44 |
+
return _prefabs[i];
|
45 |
+
}
|
46 |
+
}
|
47 |
+
return null;
|
48 |
+
}
|
49 |
+
|
50 |
+
public void ResetPrefabs()
|
51 |
+
{
|
52 |
+
for (int i = 0; i < _prefabs.Count; i++)
|
53 |
+
{
|
54 |
+
if (_prefabs[i] != null)
|
55 |
+
{
|
56 |
+
_prefabs[i].SetActive(false);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
}
|
data/PickupSpawnerSystem.cs
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
public unsafe class PickupSpawnerSystem : SystemMainThread
|
7 |
+
{
|
8 |
+
public override void Update(Frame frame)
|
9 |
+
{
|
10 |
+
if (frame.Unsafe.TryGetPointerSingleton<PickupSpawner>(out var spawner))
|
11 |
+
{
|
12 |
+
int frameInterval = spawner->SpawnInterval * frame.UpdateRate;
|
13 |
+
//if ((frameInterval % f.Number) == 0 && spawner->Count < spawner->MaxCount)
|
14 |
+
if (spawner->Count < spawner->MaxCount)
|
15 |
+
{
|
16 |
+
EntityRef pickup = frame.Create(spawner->PickupPrototype);
|
17 |
+
if (frame.Unsafe.TryGetPointer<Transform3D>(pickup, out var transform))
|
18 |
+
{
|
19 |
+
FP angle = frame.RNG->Next(0, 360);
|
20 |
+
FPQuaternion rotation = FPQuaternion.Euler(0, angle, 0);
|
21 |
+
transform->Position = (rotation * FPVector3.Right) * frame.RNG->Next(FP._0, spawner->RandomRadius);
|
22 |
+
}
|
23 |
+
spawner->Count++;
|
24 |
+
}
|
25 |
+
|
26 |
+
}
|
27 |
+
|
28 |
+
ComponentIterator<ChainItem> chainItems = frame.GetComponentIterator<ChainItem>();
|
29 |
+
foreach (var item in chainItems)
|
30 |
+
{
|
31 |
+
if (item.Component.Destroy) frame.Destroy(item.Entity);
|
32 |
+
}
|
33 |
+
}
|
34 |
+
}
|
35 |
+
}
|
data/PieceView.cs
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System.Collections;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using UnityEngine;
|
4 |
+
using Quantum;
|
5 |
+
|
6 |
+
public class PieceView : MonoBehaviour {
|
7 |
+
public PieceType Type;
|
8 |
+
public PieceColor Color;
|
9 |
+
public int IndexOnBoard = -1;
|
10 |
+
public bool Initialized = false;
|
11 |
+
|
12 |
+
[SerializeField]
|
13 |
+
private Vector3 _targetPosition;
|
14 |
+
|
15 |
+
public void SetTargetPosition(Vector3 target)
|
16 |
+
{
|
17 |
+
Initialized = true;
|
18 |
+
_targetPosition = target;
|
19 |
+
}
|
20 |
+
|
21 |
+
void Update()
|
22 |
+
{
|
23 |
+
if (Vector3.Distance(transform.position, _targetPosition) > 0.01f && Initialized) {
|
24 |
+
transform.position = Vector3.Lerp(transform.position, _targetPosition, Time.deltaTime * 10);
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
}
|
data/PiecesMapBaker.cs
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using UnityEngine;
|
2 |
+
using Quantum;
|
3 |
+
using Photon.Deterministic;
|
4 |
+
#if UNITY_EDITOR
|
5 |
+
using UnityEditor;
|
6 |
+
#endif
|
7 |
+
|
8 |
+
//When a class implements MapDataBakerCallback, it can handle the activation of the "OnBake" event.
|
9 |
+
//This event is called when you hit the "Bake Data" at the Unity scene
|
10 |
+
//So this is useful whenever the developer needs to be into the bake process
|
11 |
+
public class PiecesMapBaker : MapDataBakerCallback
|
12 |
+
{
|
13 |
+
public override void OnBake(MapData data)
|
14 |
+
{
|
15 |
+
PieceView[] pieces = GameObject.FindObjectsOfType<PieceView>();
|
16 |
+
|
17 |
+
var boardSpec = UnityDB.FindAsset<BoardSpecAsset>(data.Asset.Settings.UserAsset.Id);
|
18 |
+
boardSpec.Settings.Pieces = new BoardSpec.PieceMap[pieces.Length];
|
19 |
+
|
20 |
+
FillBoardInformation(boardSpec.Settings, pieces);
|
21 |
+
#if UNITY_EDITOR
|
22 |
+
EditorUtility.SetDirty(boardSpec.Settings.GetUnityAsset());
|
23 |
+
#endif
|
24 |
+
|
25 |
+
}
|
26 |
+
|
27 |
+
public override void OnBeforeBake(MapData data)
|
28 |
+
{
|
29 |
+
}
|
30 |
+
|
31 |
+
private void FillBoardInformation(BoardSpec targetSpec, PieceView[] pieces)
|
32 |
+
{
|
33 |
+
for (int i = 0; i < targetSpec.Pieces.Length; i++)
|
34 |
+
{
|
35 |
+
targetSpec.Pieces[i].InitialIndex = -1;
|
36 |
+
|
37 |
+
targetSpec.Pieces[i].Type = PieceType.None;
|
38 |
+
targetSpec.Pieces[i].Color = PieceColor.None;
|
39 |
+
}
|
40 |
+
|
41 |
+
for (int i = 0; i < pieces.Length; i++)
|
42 |
+
{
|
43 |
+
FP positionX = FP.FromFloat_UNSAFE(pieces[i].transform.position.x);
|
44 |
+
FP positionY = FP.FromFloat_UNSAFE(pieces[i].transform.position.z);
|
45 |
+
var index = BoardHelper.GetIndexByPosition(new FPVector2(positionX, positionY));
|
46 |
+
if (index >= 0)
|
47 |
+
{
|
48 |
+
targetSpec.Pieces[i].InitialIndex = index;
|
49 |
+
pieces[i].IndexOnBoard = index;
|
50 |
+
targetSpec.Pieces[i].Type = pieces[i].Type;
|
51 |
+
targetSpec.Pieces[i].Color = pieces[i].Color;
|
52 |
+
}
|
53 |
+
else
|
54 |
+
{
|
55 |
+
pieces[i].IndexOnBoard = -1;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
}
|
59 |
+
}
|
data/PlayCommand.cs
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
[Serializable]
|
7 |
+
public struct PlayCommandData
|
8 |
+
{
|
9 |
+
public FP Force;
|
10 |
+
public FPVector3 Direction;
|
11 |
+
public FPVector2 Spin;
|
12 |
+
}
|
13 |
+
|
14 |
+
public class PlayCommand : DeterministicCommand
|
15 |
+
{
|
16 |
+
public PlayCommandData Data;
|
17 |
+
|
18 |
+
public override void Serialize(BitStream stream)
|
19 |
+
{
|
20 |
+
// serialize command data here
|
21 |
+
stream.Serialize(ref Data.Force);
|
22 |
+
stream.Serialize(ref Data.Direction);
|
23 |
+
stream.Serialize(ref Data.Spin);
|
24 |
+
}
|
25 |
+
}
|
26 |
+
}
|
data/PlayerSystem.cs
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
using Quantum.Task;
|
4 |
+
|
5 |
+
namespace Quantum
|
6 |
+
{
|
7 |
+
public unsafe class PlayerSystem : SystemSignalsOnly, ISignalOnPlayerDataSet
|
8 |
+
{
|
9 |
+
public void OnPlayerDataSet(Frame frame, PlayerRef player)
|
10 |
+
{
|
11 |
+
RuntimePlayer data = frame.GetPlayerData(player);
|
12 |
+
EntityRef traktorEntity = frame.Create(data.TraktorPrototype);
|
13 |
+
Traktor* traktor = frame.Unsafe.GetPointer<Traktor>(traktorEntity);
|
14 |
+
if (frame.Unsafe.TryGetPointer<Transform3D>(traktorEntity, out var transform) && frame.Unsafe.TryGetPointer<Transform3D>(traktor->Sphere, out var sphereTransform))
|
15 |
+
{
|
16 |
+
transform->Position = new FPVector3(player * 2, 0, 0);
|
17 |
+
sphereTransform->Position = transform->Position;
|
18 |
+
}
|
19 |
+
if (frame.Unsafe.TryGetPointer<Controller>(traktorEntity, out var controller))
|
20 |
+
{
|
21 |
+
controller->Player = player;
|
22 |
+
}
|
23 |
+
}
|
24 |
+
}
|
25 |
+
}
|
data/Pool.cs
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System.Collections.Generic;
|
2 |
+
using System.Runtime.CompilerServices;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
public static class Pool<T> where T : new()
|
7 |
+
{
|
8 |
+
private const int POOL_CAPACITY = 4;
|
9 |
+
|
10 |
+
private static readonly List<T> _pool = new List<T>(POOL_CAPACITY);
|
11 |
+
|
12 |
+
public static int Count => _pool.Count;
|
13 |
+
|
14 |
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
15 |
+
public static T Get()
|
16 |
+
{
|
17 |
+
bool found = false;
|
18 |
+
T item = default;
|
19 |
+
|
20 |
+
lock (_pool)
|
21 |
+
{
|
22 |
+
int index = _pool.Count - 1;
|
23 |
+
if (index >= 0)
|
24 |
+
{
|
25 |
+
found = true;
|
26 |
+
item = _pool[index];
|
27 |
+
|
28 |
+
_pool.RemoveAt(index);
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
if (found == false)
|
33 |
+
{
|
34 |
+
item = new T();
|
35 |
+
}
|
36 |
+
|
37 |
+
return item;
|
38 |
+
}
|
39 |
+
|
40 |
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
41 |
+
public static void Return(T item)
|
42 |
+
{
|
43 |
+
if (item == null)
|
44 |
+
return;
|
45 |
+
|
46 |
+
lock (_pool)
|
47 |
+
{
|
48 |
+
_pool.Add(item);
|
49 |
+
}
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
public static class Pool
|
54 |
+
{
|
55 |
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
56 |
+
public static T Get<T>() where T : new()
|
57 |
+
{
|
58 |
+
return Pool<T>.Get();
|
59 |
+
}
|
60 |
+
|
61 |
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
62 |
+
public static void Return<T>(T item) where T : new()
|
63 |
+
{
|
64 |
+
Pool<T>.Return(item);
|
65 |
+
}
|
66 |
+
}
|
67 |
+
}
|
data/QuantumConsoleRunner.cs
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.IO;
|
3 |
+
|
4 |
+
namespace Quantum {
|
5 |
+
class QuantumConsoleRunner {
|
6 |
+
static void Main(string[] args) {
|
7 |
+
|
8 |
+
Log.InitForConsole();
|
9 |
+
|
10 |
+
var pathToLUT = Path.GetFullPath(args[0]);
|
11 |
+
var pathToDatabaseFile = Path.GetFullPath(args[1]);
|
12 |
+
var pathToReplayFile = Path.GetFullPath(args[2]);
|
13 |
+
var pathToChecksumFile = args.Length > 3 ? Path.GetFullPath(args[3]) : null;
|
14 |
+
var maxIterations = args.Length > 4 ? long.Parse(args[4]) : 1;
|
15 |
+
|
16 |
+
// Demonstration of a sample runner. Please duplicate the ReplayRunnerSample class to modify, because it may get overwritten in the future.
|
17 |
+
long iteration = 0;
|
18 |
+
while (iteration < maxIterations && ReplayRunnerSample.Run(pathToLUT, pathToDatabaseFile, pathToReplayFile, pathToChecksumFile)) {
|
19 |
+
if (++iteration < maxIterations) {
|
20 |
+
Console.ForegroundColor = ConsoleColor.Blue;
|
21 |
+
Console.WriteLine($"Iteration {iteration + 1}");
|
22 |
+
Console.ForegroundColor = ConsoleColor.Gray;
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
//Console.ReadKey();
|
27 |
+
}
|
28 |
+
}
|
29 |
+
}
|
data/QuantumJsonSerializer.cs
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using System.IO;
|
4 |
+
using System.Linq;
|
5 |
+
using System.Reflection;
|
6 |
+
|
7 |
+
using Newtonsoft.Json;
|
8 |
+
using Newtonsoft.Json.Serialization;
|
9 |
+
|
10 |
+
namespace Quantum {
|
11 |
+
|
12 |
+
public class QuantumJsonSerializer : Quantum.JsonAssetSerializerBase {
|
13 |
+
private readonly JsonSerializer _serializer = CreateSerializer();
|
14 |
+
|
15 |
+
public static JsonSerializer CreateSerializer() {
|
16 |
+
return JsonSerializer.Create(CreateSettings());
|
17 |
+
}
|
18 |
+
|
19 |
+
protected override object FromJson(string json, Type type) {
|
20 |
+
using (var reader = new StringReader(json)) {
|
21 |
+
var result = _serializer.Deserialize(reader, type);
|
22 |
+
return result;
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
protected override string ToJson(object obj) {
|
27 |
+
using (var writer = new StringWriter()) {
|
28 |
+
_serializer.Serialize(writer, obj);
|
29 |
+
return writer.ToString();
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
private static JsonSerializerSettings CreateSettings() {
|
34 |
+
return new JsonSerializerSettings {
|
35 |
+
ContractResolver = new WritablePropertiesOnlyResolver(),
|
36 |
+
Formatting = Formatting.Indented,
|
37 |
+
TypeNameHandling = TypeNameHandling.Auto,
|
38 |
+
NullValueHandling = NullValueHandling.Ignore,
|
39 |
+
Converters = { new ByteArrayConverter() },
|
40 |
+
};
|
41 |
+
}
|
42 |
+
private class ByteArrayConverter : JsonConverter {
|
43 |
+
|
44 |
+
public override bool CanConvert(Type objectType) {
|
45 |
+
return objectType == typeof(byte[]);
|
46 |
+
}
|
47 |
+
|
48 |
+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
49 |
+
if (reader.TokenType == JsonToken.StartArray) {
|
50 |
+
var byteList = new List<byte>();
|
51 |
+
|
52 |
+
while (reader.Read()) {
|
53 |
+
switch (reader.TokenType) {
|
54 |
+
case JsonToken.Integer:
|
55 |
+
byteList.Add(Convert.ToByte(reader.Value));
|
56 |
+
break;
|
57 |
+
|
58 |
+
case JsonToken.EndArray:
|
59 |
+
return byteList.ToArray();
|
60 |
+
|
61 |
+
case JsonToken.Comment:
|
62 |
+
// skip
|
63 |
+
break;
|
64 |
+
|
65 |
+
default:
|
66 |
+
throw new Exception(string.Format("Unexpected token when reading bytes: {0}", reader.TokenType));
|
67 |
+
}
|
68 |
+
}
|
69 |
+
|
70 |
+
throw new Exception("Unexpected end when reading bytes.");
|
71 |
+
} else {
|
72 |
+
throw new Exception(string.Format("Unexpected token parsing binary. Expected StartArray, got {0}.", reader.TokenType));
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
77 |
+
if (value == null) {
|
78 |
+
writer.WriteNull();
|
79 |
+
return;
|
80 |
+
}
|
81 |
+
|
82 |
+
byte[] data = (byte[])value;
|
83 |
+
|
84 |
+
// compose an array
|
85 |
+
writer.WriteStartArray();
|
86 |
+
|
87 |
+
for (var i = 0; i < data.Length; i++) {
|
88 |
+
writer.WriteValue(data[i]);
|
89 |
+
}
|
90 |
+
|
91 |
+
writer.WriteEndArray();
|
92 |
+
}
|
93 |
+
}
|
94 |
+
|
95 |
+
private class WritablePropertiesOnlyResolver : DefaultContractResolver {
|
96 |
+
|
97 |
+
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) {
|
98 |
+
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
|
99 |
+
return props.Where(p => p.Writable).ToList();
|
100 |
+
}
|
101 |
+
|
102 |
+
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) {
|
103 |
+
if (member is FieldInfo) {
|
104 |
+
// just fields
|
105 |
+
return base.CreateProperty(member, memberSerialization);
|
106 |
+
} else {
|
107 |
+
return null;
|
108 |
+
}
|
109 |
+
}
|
110 |
+
}
|
111 |
+
}
|
112 |
+
}
|
data/ReplayJsonSerializerSettings.cs
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Newtonsoft.Json;
|
2 |
+
|
3 |
+
namespace Quantum {
|
4 |
+
public static class ReplayJsonSerializerSettings {
|
5 |
+
public static JsonSerializerSettings GetSettings() {
|
6 |
+
return new JsonSerializerSettings {
|
7 |
+
Formatting = Formatting.Indented,
|
8 |
+
TypeNameHandling = TypeNameHandling.Auto,
|
9 |
+
NullValueHandling = NullValueHandling.Ignore,
|
10 |
+
};
|
11 |
+
}
|
12 |
+
}
|
13 |
+
}
|
data/ReplayRunnerSample.cs
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
using System.IO;
|
4 |
+
using System.Threading;
|
5 |
+
|
6 |
+
namespace Quantum {
|
7 |
+
public class ReplayRunnerSample {
|
8 |
+
|
9 |
+
public static bool Run(string pathToLUT,string pathToDatabaseFile, string pathToReplayFile, string pathToChecksumFile) {
|
10 |
+
|
11 |
+
FPLut.Init(pathToLUT);
|
12 |
+
|
13 |
+
Console.WriteLine($"Loading replay from file: '{Path.GetFileName(pathToReplayFile)}' from folder '{Path.GetDirectoryName(pathToReplayFile)}'");
|
14 |
+
|
15 |
+
if (!File.Exists(pathToDatabaseFile)) {
|
16 |
+
Console.ForegroundColor = ConsoleColor.Red;
|
17 |
+
Console.WriteLine($"File not found: '{pathToReplayFile}'");
|
18 |
+
Console.ForegroundColor = ConsoleColor.Gray;
|
19 |
+
return false;
|
20 |
+
}
|
21 |
+
|
22 |
+
var serializer = new QuantumJsonSerializer();
|
23 |
+
var callbackDispatcher = new CallbackDispatcher();
|
24 |
+
var replayFile = serializer.DeserializeReplay(File.ReadAllBytes(pathToReplayFile));
|
25 |
+
var inputProvider = new InputProvider(replayFile.DeterministicConfig);
|
26 |
+
inputProvider.ImportFromList(replayFile.InputHistory);
|
27 |
+
|
28 |
+
var resourceManager = new ResourceManagerStatic(serializer.DeserializeAssets(File.ReadAllBytes(pathToDatabaseFile)), SessionContainer.CreateNativeAllocator());
|
29 |
+
|
30 |
+
var container = new SessionContainer(replayFile);
|
31 |
+
container.StartReplay(new QuantumGame.StartParameters {
|
32 |
+
AssetSerializer = serializer,
|
33 |
+
CallbackDispatcher = callbackDispatcher,
|
34 |
+
EventDispatcher = null,
|
35 |
+
ResourceManager = resourceManager,
|
36 |
+
}, inputProvider);
|
37 |
+
|
38 |
+
var numberOfFrames = replayFile.Length;
|
39 |
+
var checksumVerification = String.IsNullOrEmpty(pathToChecksumFile) ? null : new ChecksumVerification(pathToChecksumFile, callbackDispatcher);
|
40 |
+
|
41 |
+
while (container.Session.FramePredicted == null || container.Session.FramePredicted.Number < numberOfFrames) {
|
42 |
+
Thread.Sleep(1);
|
43 |
+
container.Service(dt: 1.0f);
|
44 |
+
|
45 |
+
if (Console.KeyAvailable) {
|
46 |
+
if (Console.ReadKey().Key == ConsoleKey.Escape) {
|
47 |
+
Console.WriteLine("Stopping replay");
|
48 |
+
return false;
|
49 |
+
}
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
Console.WriteLine($"Ending replay at frame {container.Session.FramePredicted.Number}");
|
54 |
+
|
55 |
+
checksumVerification?.Dispose();
|
56 |
+
container.Destroy();
|
57 |
+
|
58 |
+
resourceManager.Dispose();
|
59 |
+
|
60 |
+
return true;
|
61 |
+
}
|
62 |
+
}
|
63 |
+
}
|
data/ResponseCurve.cs
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
[BotSDKHidden]
|
7 |
+
[System.Serializable]
|
8 |
+
public unsafe partial class ResponseCurve : AIFunctionFP
|
9 |
+
{
|
10 |
+
public AIParamFP Input;
|
11 |
+
|
12 |
+
[BotSDKHidden]
|
13 |
+
public FPAnimationCurve Curve;
|
14 |
+
|
15 |
+
public override void Loaded(IResourceManager resourceManager, Native.Allocator allocator)
|
16 |
+
{
|
17 |
+
base.Loaded(resourceManager, allocator);
|
18 |
+
}
|
19 |
+
|
20 |
+
public override FP Execute(Frame frame, EntityRef entity = default)
|
21 |
+
{
|
22 |
+
if (Input.FunctionRef == default) return 0;
|
23 |
+
|
24 |
+
FP input = Input.ResolveFunction(frame, entity);
|
25 |
+
FP result = Curve.Evaluate(input);
|
26 |
+
|
27 |
+
if (result > 1) result = 1;
|
28 |
+
else if (result < 0) result = 0;
|
29 |
+
|
30 |
+
return result;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
}
|
data/RuntimeConfig.User.cs
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
partial class RuntimeConfig
|
7 |
+
{
|
8 |
+
public bool ShowIntroduction;
|
9 |
+
|
10 |
+
public AssetRefHFSMRoot GameManagerHFSM;
|
11 |
+
|
12 |
+
public enum GameMode { CoinGrab, BossBattle };
|
13 |
+
public GameMode ConfigType = GameMode.CoinGrab;
|
14 |
+
|
15 |
+
public AssetRefEntityPrototype[] RoomFillBots;
|
16 |
+
public FP RoomFillInterval = 2;
|
17 |
+
public bool FillWithBots = true;
|
18 |
+
|
19 |
+
partial void SerializeUserData(BitStream stream)
|
20 |
+
{
|
21 |
+
stream.Serialize(ref ShowIntroduction);
|
22 |
+
stream.Serialize(ref GameManagerHFSM);
|
23 |
+
|
24 |
+
stream.SerializeArrayLength(ref RoomFillBots);
|
25 |
+
for (var i = 0; i < RoomFillBots.Length; i++)
|
26 |
+
{
|
27 |
+
stream.Serialize(ref RoomFillBots[i]);
|
28 |
+
}
|
29 |
+
stream.Serialize(ref RoomFillInterval);
|
30 |
+
|
31 |
+
Int32 current = (Int32)ConfigType;
|
32 |
+
stream.Serialize(ref current);
|
33 |
+
ConfigType = (GameMode)current;
|
34 |
+
|
35 |
+
stream.Serialize(ref FillWithBots);
|
36 |
+
}
|
37 |
+
}
|
38 |
+
}
|
data/RuntimePlayer.User.cs
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
using System.Collections.Generic;
|
4 |
+
using System.Linq;
|
5 |
+
using System.Text;
|
6 |
+
|
7 |
+
namespace Quantum
|
8 |
+
{
|
9 |
+
partial class RuntimePlayer
|
10 |
+
{
|
11 |
+
public AssetRefEntityPrototype SelectedCharacter;
|
12 |
+
public string PlayerName;
|
13 |
+
public bool ForceAI;
|
14 |
+
public int TeamIndex;
|
15 |
+
|
16 |
+
partial void SerializeUserData(BitStream stream)
|
17 |
+
{
|
18 |
+
stream.Serialize(ref SelectedCharacter);
|
19 |
+
stream.Serialize(ref PlayerName);
|
20 |
+
stream.Serialize(ref ForceAI);
|
21 |
+
stream.Serialize(ref TeamIndex);
|
22 |
+
}
|
23 |
+
}
|
24 |
+
}
|
data/SelfDestroy.cs
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System.Collections;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using UnityEngine;
|
4 |
+
|
5 |
+
public class SelfDestroy : MonoBehaviour
|
6 |
+
{
|
7 |
+
public float TTL = 2;
|
8 |
+
|
9 |
+
void Update()
|
10 |
+
{
|
11 |
+
if (TTL < 0) Destroy(gameObject);
|
12 |
+
TTL -= Time.deltaTime;
|
13 |
+
}
|
14 |
+
}
|
data/SetBlackboardInt.cs
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
|
3 |
+
namespace Quantum
|
4 |
+
{
|
5 |
+
[Serializable]
|
6 |
+
[AssetObjectConfig(GenerateLinkingScripts = true, GenerateAssetCreateMenu = false, GenerateAssetResetMethod = false)]
|
7 |
+
public unsafe partial class SetBlackboardInt : AIAction
|
8 |
+
{
|
9 |
+
public AIBlackboardValueKey Key;
|
10 |
+
public AIParamInt Value;
|
11 |
+
|
12 |
+
public override unsafe void Update(Frame frame, EntityRef entity)
|
13 |
+
{
|
14 |
+
var blackboard = frame.Unsafe.GetPointer<AIBlackboardComponent>(entity);
|
15 |
+
|
16 |
+
var agent = frame.Unsafe.GetPointer<HFSMAgent>(entity);
|
17 |
+
var aiConfig = agent->GetConfig(frame);
|
18 |
+
|
19 |
+
var value = Value.Resolve(frame, entity, blackboard, aiConfig);
|
20 |
+
blackboard->Set(frame, Key.Key, value);
|
21 |
+
}
|
22 |
+
}
|
23 |
+
}
|
data/SimulationConfig.User.cs
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
using System.Collections.Generic;
|
4 |
+
using System.Linq;
|
5 |
+
using System.Text;
|
6 |
+
|
7 |
+
namespace Quantum
|
8 |
+
{
|
9 |
+
partial class SimulationConfig : AssetObject
|
10 |
+
{
|
11 |
+
|
12 |
+
}
|
13 |
+
}
|
data/SkipCommand.cs
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
[Serializable]
|
7 |
+
public struct SkipCommandData
|
8 |
+
{
|
9 |
+
// game-specific command data here
|
10 |
+
}
|
11 |
+
|
12 |
+
public class SkipCommand : DeterministicCommand
|
13 |
+
{
|
14 |
+
public SkipCommandData Data;
|
15 |
+
|
16 |
+
public override void Serialize(BitStream stream)
|
17 |
+
{
|
18 |
+
// serialize command data here
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
data/SpawnFX.cs
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.Collections;
|
3 |
+
using System.Collections.Generic;
|
4 |
+
using UnityEngine;
|
5 |
+
|
6 |
+
public class SpawnFX : MonoBehaviour
|
7 |
+
{
|
8 |
+
public Transform VFXPrefab;
|
9 |
+
|
10 |
+
private void OnDestroy()
|
11 |
+
{
|
12 |
+
Instantiate(VFXPrefab, transform.position, Quaternion.identity);
|
13 |
+
}
|
14 |
+
}
|
data/StopwatchBlock.cs
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.Diagnostics;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
public class StopwatchBlock : IDisposable
|
7 |
+
{
|
8 |
+
private Stopwatch _stopwatch;
|
9 |
+
private string _blockName;
|
10 |
+
|
11 |
+
public StopwatchBlock(string blockName)
|
12 |
+
{
|
13 |
+
_blockName = blockName;
|
14 |
+
_stopwatch = new Stopwatch();
|
15 |
+
_stopwatch.Start();
|
16 |
+
}
|
17 |
+
|
18 |
+
void IDisposable.Dispose()
|
19 |
+
{
|
20 |
+
_stopwatch.Stop();
|
21 |
+
Log.Info($"{_blockName}: {_stopwatch.Elapsed.TotalMilliseconds} ms");
|
22 |
+
}
|
23 |
+
}
|
24 |
+
}
|
data/SystemSetup.cs
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
using System.Collections.Generic;
|
4 |
+
using System.Linq;
|
5 |
+
using System.Text;
|
6 |
+
|
7 |
+
namespace Quantum
|
8 |
+
{
|
9 |
+
public static class SystemSetup
|
10 |
+
{
|
11 |
+
public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig)
|
12 |
+
{
|
13 |
+
return new SystemBase[] {
|
14 |
+
// pre-defined core systems
|
15 |
+
new Core.CullingSystem2D(),
|
16 |
+
new Core.CullingSystem3D(),
|
17 |
+
|
18 |
+
new Core.PhysicsSystem2D(),
|
19 |
+
new Core.PhysicsSystem3D(),
|
20 |
+
|
21 |
+
#if DEBUG
|
22 |
+
Core.DebugCommand.CreateSystem(),
|
23 |
+
#endif
|
24 |
+
|
25 |
+
new Core.NavigationSystem(),
|
26 |
+
new Core.EntityPrototypeSystem(),
|
27 |
+
new Core.PlayerConnectedSystem(),
|
28 |
+
|
29 |
+
new BotSDKDebuggerSystem(),
|
30 |
+
|
31 |
+
new GameplaySystemsGroup("Gameplay Systems",
|
32 |
+
new MatchSystem(),
|
33 |
+
new PlayerJoiningSystem(),
|
34 |
+
new CommandsSystem(),
|
35 |
+
|
36 |
+
new GameManagerSystem(),
|
37 |
+
new MemorySystem(),
|
38 |
+
new AISystem(),
|
39 |
+
new TeamDataSystem(),
|
40 |
+
|
41 |
+
new AttributesSystem(),
|
42 |
+
new HealthSystem(),
|
43 |
+
new ImmuneSystem(),
|
44 |
+
new VisibilitySystem(),
|
45 |
+
new InputSystem(),
|
46 |
+
new AttackSystem(),
|
47 |
+
new RespawnSystem(),
|
48 |
+
new SkillSystem()
|
49 |
+
),
|
50 |
+
|
51 |
+
// We don't add it to the group as it is a SignalsOnly system; no need to be enabled/disabled with the rest
|
52 |
+
new InventorySystem(),
|
53 |
+
};
|
54 |
+
}
|
55 |
+
}
|
56 |
+
}
|
data/Traktor.User.cs
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using System.Collections.Generic;
|
3 |
+
using Photon.Deterministic;
|
4 |
+
|
5 |
+
namespace Quantum
|
6 |
+
{
|
7 |
+
|
8 |
+
unsafe partial struct Traktor
|
9 |
+
{
|
10 |
+
public void Update(FrameThreadSafe frame, ref TraktorInputSystem.Filter filter, Input input)
|
11 |
+
{
|
12 |
+
TraktorConfig config = frame.FindAsset<TraktorConfig>(Config.Id);
|
13 |
+
|
14 |
+
ChainSystem.Filter sphereData = default;
|
15 |
+
var getter = frame.ComponentGetter<ChainSystem.Filter>();
|
16 |
+
;
|
17 |
+
|
18 |
+
if (getter.TryGet(frame, Sphere, &sphereData))
|
19 |
+
{
|
20 |
+
FPVector2 direction = input.Direction;
|
21 |
+
|
22 |
+
FPVector3 forward = filter.Transform->Forward;
|
23 |
+
FPVector3 right = filter.Transform->Right;
|
24 |
+
FP forwardSpeed = FPVector3.Dot(forward, sphereData.Body->Velocity);
|
25 |
+
|
26 |
+
// now rotate
|
27 |
+
FP lateralForce = config.LateralForce.Evaluate(forwardSpeed / config.MaxSpeed);
|
28 |
+
|
29 |
+
if (direction.X != 0)
|
30 |
+
{
|
31 |
+
FP rotationFactor = lateralForce * config.RotationMultiplier * direction.X;
|
32 |
+
filter.Transform->Rotate(FP._0, rotationFactor * frame.DeltaTime, FP._0);
|
33 |
+
}
|
34 |
+
|
35 |
+
FPVector3 force = default;
|
36 |
+
if (direction.Y > 0)
|
37 |
+
{
|
38 |
+
force += direction.Y * forward * config.Acceleration;
|
39 |
+
}
|
40 |
+
FP slipSpeed = FPVector3.Dot(right, sphereData.Body->Velocity);
|
41 |
+
force += right * slipSpeed * config.LateralForceMultiplier;
|
42 |
+
// swizzle from 2D to 3D
|
43 |
+
sphereData.Body->AddForce(force);
|
44 |
+
|
45 |
+
// snap to sphere position
|
46 |
+
filter.Transform->Position = sphereData.Transform->Position;
|
47 |
+
filter.Transform->Rotation = filter.Transform->Rotation.Normalized;
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
}
|
data/TraktorConfig.cs
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
partial class TraktorConfig : AssetObject
|
7 |
+
{
|
8 |
+
public FP Acceleration;
|
9 |
+
public FP Drag;
|
10 |
+
public FP MaxSpeed;
|
11 |
+
public FPAnimationCurve LateralForce;
|
12 |
+
public FP LateralForceMultiplier = 1;
|
13 |
+
public FP RotationMultiplier = 1;
|
14 |
+
}
|
15 |
+
}
|
data/TraktorInputSystem.cs
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
using Quantum.Task;
|
4 |
+
|
5 |
+
namespace Quantum
|
6 |
+
{
|
7 |
+
public unsafe class TraktorInputSystem : SystemThreadedFilter<TraktorInputSystem.Filter>, ISignalOnComponentAdded<Traktor>
|
8 |
+
{
|
9 |
+
public struct Filter
|
10 |
+
{
|
11 |
+
public EntityRef Entity;
|
12 |
+
public Transform3D* Transform;
|
13 |
+
public Traktor* Traktor;
|
14 |
+
}
|
15 |
+
|
16 |
+
public void OnAdded(Frame frame, EntityRef entity, Traktor* traktor)
|
17 |
+
{
|
18 |
+
EntityRef sphere = frame.Create(traktor->SpherePrototype);
|
19 |
+
traktor->Sphere = sphere;
|
20 |
+
|
21 |
+
}
|
22 |
+
|
23 |
+
public override void Update(FrameThreadSafe frame, ref Filter filter)
|
24 |
+
{
|
25 |
+
Input input = default;
|
26 |
+
if (frame.TryGetPointer<Controller>(filter.Entity, out var controller))
|
27 |
+
{
|
28 |
+
input = *((Frame)frame).GetPlayerInput(controller->Player);
|
29 |
+
}
|
30 |
+
filter.Traktor->Update(frame, ref filter, input);
|
31 |
+
}
|
32 |
+
}
|
33 |
+
}
|
data/TraktorView.cs
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Quantum;
|
2 |
+
using UnityEngine;
|
3 |
+
using Photon.Deterministic;
|
4 |
+
|
5 |
+
public class TraktorView : MonoBehaviour
|
6 |
+
{
|
7 |
+
Vector3 _lastPosition;
|
8 |
+
public float SpeedThreshold = 0.1f;
|
9 |
+
public float RaycastDistance = 0.51f;
|
10 |
+
public float RotationFactor = 8f;
|
11 |
+
public Transform TraktorBody;
|
12 |
+
Quaternion _desiredRotation;
|
13 |
+
private bool _wasGrounded;
|
14 |
+
private EntityView _view;
|
15 |
+
private EntityRef _sphere;
|
16 |
+
void Start()
|
17 |
+
{
|
18 |
+
_view = GetComponent<EntityView>();
|
19 |
+
var frame = QuantumRunner.Default.Game.Frames.Verified;
|
20 |
+
var controller = frame.Get<Controller>(_view.EntityRef);
|
21 |
+
if (QuantumRunner.Default.Session.IsLocalPlayer(controller.Player))
|
22 |
+
{
|
23 |
+
Camera.main.GetComponent<CameraFollow>().Target = transform;
|
24 |
+
}
|
25 |
+
|
26 |
+
if (frame.TryGet<Traktor>(_view.EntityRef, out var traktor))
|
27 |
+
{
|
28 |
+
_sphere = traktor.Sphere;
|
29 |
+
}
|
30 |
+
_lastPosition = transform.position;
|
31 |
+
_desiredRotation = transform.rotation;
|
32 |
+
}
|
33 |
+
|
34 |
+
private void LateUpdate()
|
35 |
+
{
|
36 |
+
var frame = QuantumRunner.Default.Game.Frames.Predicted;
|
37 |
+
if (frame.TryGet<Drivable>(_sphere, out var drivable) && drivable.Grounded && frame.TryGet<PhysicsBody3D>(_sphere, out var body))
|
38 |
+
{
|
39 |
+
var direction = body.Velocity.ToUnityVector3();
|
40 |
+
_desiredRotation = Quaternion.LookRotation(direction.normalized, drivable.SurfaceNormal.ToUnityVector3());
|
41 |
+
var euler = _desiredRotation.eulerAngles;
|
42 |
+
euler.y = 0;
|
43 |
+
_desiredRotation = Quaternion.Euler(euler);
|
44 |
+
}
|
45 |
+
TraktorBody.localRotation = Quaternion.Slerp(TraktorBody.localRotation, _desiredRotation, RotationFactor * Time.deltaTime);
|
46 |
+
_lastPosition = transform.position;
|
47 |
+
}
|
48 |
+
|
49 |
+
|
50 |
+
}
|
data/TurnConfig.cs
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
public partial class TurnConfig
|
7 |
+
{
|
8 |
+
public Boolean UsesTimer;
|
9 |
+
public Int32 TurnDurationInTicks;
|
10 |
+
public Boolean IsSkippable;
|
11 |
+
}
|
12 |
+
}
|
data/TurnData.cs
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
partial struct TurnData
|
7 |
+
{
|
8 |
+
public void Update(Frame f)
|
9 |
+
{
|
10 |
+
var config = f.FindAsset<TurnConfig>(ConfigRef.Id);
|
11 |
+
if (config == null || !config.UsesTimer || Status != TurnStatus.Active)
|
12 |
+
{
|
13 |
+
return;
|
14 |
+
}
|
15 |
+
Ticks++;
|
16 |
+
if (Ticks >= config.TurnDurationInTicks)
|
17 |
+
{
|
18 |
+
f.Signals.OnTurnEnded(this, TurnEndReason.Time);
|
19 |
+
}
|
20 |
+
}
|
21 |
+
|
22 |
+
public void AccumulateStats(TurnData from)
|
23 |
+
{
|
24 |
+
Ticks += from.Ticks;
|
25 |
+
Number++;
|
26 |
+
}
|
27 |
+
|
28 |
+
public void SetType(TurnType newType, Frame f = null)
|
29 |
+
{
|
30 |
+
if (Type == newType)
|
31 |
+
{
|
32 |
+
return;
|
33 |
+
}
|
34 |
+
var previousType = Type;
|
35 |
+
Type = newType;
|
36 |
+
f?.Events.TurnTypeChanged(this, previousType);
|
37 |
+
}
|
38 |
+
|
39 |
+
public void SetStatus(TurnStatus newStatus, Frame f = null)
|
40 |
+
{
|
41 |
+
if (Status == newStatus)
|
42 |
+
{
|
43 |
+
return;
|
44 |
+
}
|
45 |
+
var previousStatus = Status;
|
46 |
+
Status = newStatus;
|
47 |
+
f?.Events.TurnStatusChanged(this, previousStatus);
|
48 |
+
if (Status == TurnStatus.Active)
|
49 |
+
{
|
50 |
+
f?.Events.TurnActivated(this);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
// frame is only necessary if caller wants to raise events
|
55 |
+
public void ResetTicks(Frame f = null)
|
56 |
+
{
|
57 |
+
ResetData(Type, Status, Entity, Player, ConfigRef, f);
|
58 |
+
}
|
59 |
+
|
60 |
+
public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null)
|
61 |
+
{
|
62 |
+
ResetData(type, status, Entity, Player, config, f);
|
63 |
+
}
|
64 |
+
|
65 |
+
public void Reset(EntityRef entity, PlayerRef owner, Frame f = null)
|
66 |
+
{
|
67 |
+
ResetData(Type, Status, entity, owner, ConfigRef, f);
|
68 |
+
}
|
69 |
+
|
70 |
+
public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null)
|
71 |
+
{
|
72 |
+
ResetData(type, status, entity, owner, config, f);
|
73 |
+
}
|
74 |
+
|
75 |
+
private void ResetData(TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, AssetRefTurnConfig config, Frame f = null)
|
76 |
+
{
|
77 |
+
if (entity != EntityRef.None)
|
78 |
+
{
|
79 |
+
Entity = entity;
|
80 |
+
}
|
81 |
+
if (owner != PlayerRef.None)
|
82 |
+
{
|
83 |
+
Player = owner;
|
84 |
+
}
|
85 |
+
|
86 |
+
if (config != null)
|
87 |
+
{
|
88 |
+
ConfigRef = config;
|
89 |
+
}
|
90 |
+
|
91 |
+
var previousType = Type;
|
92 |
+
Type = type;
|
93 |
+
var previousStatus = Status;
|
94 |
+
Status = status;
|
95 |
+
|
96 |
+
Ticks = 0;
|
97 |
+
|
98 |
+
if (Type != previousType)
|
99 |
+
{
|
100 |
+
f?.Events.TurnTypeChanged(this, previousType);
|
101 |
+
}
|
102 |
+
|
103 |
+
if (Status != previousStatus)
|
104 |
+
{
|
105 |
+
f?.Events.TurnStatusChanged(this, previousStatus);
|
106 |
+
if (Status == TurnStatus.Active)
|
107 |
+
{
|
108 |
+
f?.Events.TurnActivated(this);
|
109 |
+
}
|
110 |
+
}
|
111 |
+
|
112 |
+
f?.Events.TurnTimerReset(this);
|
113 |
+
}
|
114 |
+
}
|
115 |
+
}
|
data/TurnData.qtn
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import PlayCommandData;
|
2 |
+
import SkipCommandData;
|
3 |
+
|
4 |
+
asset TurnConfig;
|
5 |
+
|
6 |
+
enum TurnType { Play, Countdown }
|
7 |
+
enum TurnStatus { Inactive, Active, Resolving }
|
8 |
+
enum TurnEndReason { Time, Skip, Play, Resolved }
|
9 |
+
|
10 |
+
struct TurnData
|
11 |
+
{
|
12 |
+
player_ref Player;
|
13 |
+
entity_ref Entity;
|
14 |
+
asset_ref<TurnConfig> ConfigRef;
|
15 |
+
TurnType Type;
|
16 |
+
TurnStatus Status;
|
17 |
+
Int32 Number;
|
18 |
+
Int32 Ticks;
|
19 |
+
}
|
20 |
+
|
21 |
+
global {
|
22 |
+
TurnData CurrentTurn;
|
23 |
+
}
|
24 |
+
|
25 |
+
// ------------------ Signals ------------------
|
26 |
+
signal OnTurnEnded (TurnData data, TurnEndReason reason);
|
27 |
+
signal OnPlayCommandReceived (PlayerRef player, PlayCommandData data);
|
28 |
+
signal OnSkipCommandReceived (PlayerRef player, SkipCommandData data);
|
29 |
+
|
30 |
+
// ------------------ Events ------------------
|
31 |
+
abstract event TurnEvent { TurnData Turn; }
|
32 |
+
synced event TurnTypeChanged : TurnEvent { TurnType PreviousType; }
|
33 |
+
synced event TurnStatusChanged : TurnEvent { TurnStatus PreviousStatus; }
|
34 |
+
synced event TurnEnded : TurnEvent { TurnEndReason Reason; }
|
35 |
+
synced event TurnTimerReset : TurnEvent { }
|
36 |
+
synced event TurnActivated : TurnEvent { }
|
37 |
+
|
38 |
+
abstract event CommandEvent { player_ref Player; }
|
39 |
+
event PlayCommandReceived : CommandEvent { PlayCommandData Data; }
|
40 |
+
event SkipCommandReceived : CommandEvent { SkipCommandData Data; }
|
data/TurnData.txt
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
using Photon.Deterministic;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
partial struct TurnData
|
7 |
+
{
|
8 |
+
public void Update(Frame f)
|
9 |
+
{
|
10 |
+
var config = f.FindAsset<TurnConfig>(ConfigRef.Id);
|
11 |
+
if (config == null || !config.UsesTimer || Status != TurnStatus.Active)
|
12 |
+
{
|
13 |
+
return;
|
14 |
+
}
|
15 |
+
Ticks++;
|
16 |
+
if (Ticks >= config.TurnDurationInTicks)
|
17 |
+
{
|
18 |
+
f.Signals.OnTurnEnded(this, TurnEndReason.Time);
|
19 |
+
}
|
20 |
+
}
|
21 |
+
|
22 |
+
public void AccumulateStats(TurnData from)
|
23 |
+
{
|
24 |
+
Ticks += from.Ticks;
|
25 |
+
Number++;
|
26 |
+
}
|
27 |
+
|
28 |
+
public void SetType(TurnType newType, Frame f = null)
|
29 |
+
{
|
30 |
+
if (Type == newType)
|
31 |
+
{
|
32 |
+
return;
|
33 |
+
}
|
34 |
+
var previousType = Type;
|
35 |
+
Type = newType;
|
36 |
+
f?.Events.TurnTypeChanged(this, previousType);
|
37 |
+
}
|
38 |
+
|
39 |
+
public void SetStatus(TurnStatus newStatus, Frame f = null)
|
40 |
+
{
|
41 |
+
if (Status == newStatus)
|
42 |
+
{
|
43 |
+
return;
|
44 |
+
}
|
45 |
+
var previousStatus = Status;
|
46 |
+
Status = newStatus;
|
47 |
+
f?.Events.TurnStatusChanged(this, previousStatus);
|
48 |
+
if (Status == TurnStatus.Active)
|
49 |
+
{
|
50 |
+
f?.Events.TurnActivated(this);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
// frame is only necessary if caller wants to raise events
|
55 |
+
public void ResetTicks(Frame f = null)
|
56 |
+
{
|
57 |
+
ResetData(Type, Status, Entity, Player, ConfigRef, f);
|
58 |
+
}
|
59 |
+
|
60 |
+
public void Reset(TurnConfig config, TurnType type, TurnStatus status, Frame f = null)
|
61 |
+
{
|
62 |
+
ResetData(type, status, Entity, Player, config, f);
|
63 |
+
}
|
64 |
+
|
65 |
+
public void Reset(EntityRef entity, PlayerRef owner, Frame f = null)
|
66 |
+
{
|
67 |
+
ResetData(Type, Status, entity, owner, ConfigRef, f);
|
68 |
+
}
|
69 |
+
|
70 |
+
public void Reset(TurnConfig config, TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, Frame f = null)
|
71 |
+
{
|
72 |
+
ResetData(type, status, entity, owner, config, f);
|
73 |
+
}
|
74 |
+
|
75 |
+
private void ResetData(TurnType type, TurnStatus status, EntityRef entity, PlayerRef owner, AssetRefTurnConfig config, Frame f = null)
|
76 |
+
{
|
77 |
+
if (entity != EntityRef.None)
|
78 |
+
{
|
79 |
+
Entity = entity;
|
80 |
+
}
|
81 |
+
if (owner != PlayerRef.None)
|
82 |
+
{
|
83 |
+
Player = owner;
|
84 |
+
}
|
85 |
+
|
86 |
+
if (config != null)
|
87 |
+
{
|
88 |
+
ConfigRef = config;
|
89 |
+
}
|
90 |
+
|
91 |
+
var previousType = Type;
|
92 |
+
Type = type;
|
93 |
+
var previousStatus = Status;
|
94 |
+
Status = status;
|
95 |
+
|
96 |
+
Ticks = 0;
|
97 |
+
|
98 |
+
if (Type != previousType)
|
99 |
+
{
|
100 |
+
f?.Events.TurnTypeChanged(this, previousType);
|
101 |
+
}
|
102 |
+
|
103 |
+
if (Status != previousStatus)
|
104 |
+
{
|
105 |
+
f?.Events.TurnStatusChanged(this, previousStatus);
|
106 |
+
if (Status == TurnStatus.Active)
|
107 |
+
{
|
108 |
+
f?.Events.TurnActivated(this);
|
109 |
+
}
|
110 |
+
}
|
111 |
+
|
112 |
+
f?.Events.TurnTimerReset(this);
|
113 |
+
}
|
114 |
+
}
|
115 |
+
}
|
data/TurnTimerSystem.cs
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
|
3 |
+
namespace Quantum
|
4 |
+
{
|
5 |
+
public unsafe class TurnTimerSystem : SystemMainThread
|
6 |
+
{
|
7 |
+
public override void Update(Frame f)
|
8 |
+
{
|
9 |
+
f.Global->CurrentTurn.Update(f);
|
10 |
+
}
|
11 |
+
}
|
12 |
+
}
|
data/UT.qtn
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
asset UTRoot;
|
2 |
+
asset Consideration;
|
3 |
+
|
4 |
+
component UTAgent
|
5 |
+
{
|
6 |
+
UtilityReasoner UtilityReasoner;
|
7 |
+
AssetRefAIConfig Config;
|
8 |
+
}
|
9 |
+
|
10 |
+
struct UtilityReasoner
|
11 |
+
{
|
12 |
+
AssetRefUTRoot UTRoot;
|
13 |
+
[HideInInspector] list<AssetRefConsideration> Considerations;
|
14 |
+
[HideInInspector] list<UTMomentumPack> MomentumList;
|
15 |
+
[HideInInspector] FP TimeToTick;
|
16 |
+
[HideInInspector] dictionary<AssetRefConsideration, FP> CooldownsDict;
|
17 |
+
[HideInInspector] list<AssetRefConsideration> PreviousExecution;
|
18 |
+
}
|
19 |
+
|
20 |
+
struct UTMomentumPack
|
21 |
+
{
|
22 |
+
AssetRefConsideration ConsiderationRef;
|
23 |
+
UTMomentumData MomentumData;
|
24 |
+
}
|
25 |
+
|
26 |
+
struct UTMomentumData
|
27 |
+
{
|
28 |
+
Int32 Value;
|
29 |
+
Byte DecayAmount;
|
30 |
+
}
|
data/UTAgent.User.cs
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
namespace Quantum
|
2 |
+
{
|
3 |
+
public partial struct UTAgent
|
4 |
+
{
|
5 |
+
// Used to setup info on the Unity debugger
|
6 |
+
public string GetRootAssetName(Frame frame) => frame.FindAsset<UTRoot>(UtilityReasoner.UTRoot.Id).Path;
|
7 |
+
|
8 |
+
public AIConfig GetConfig(Frame frame)
|
9 |
+
{
|
10 |
+
return frame.FindAsset<AIConfig>(Config.Id);
|
11 |
+
}
|
12 |
+
}
|
13 |
+
}
|
data/UTManager.cs
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using System;
|
2 |
+
|
3 |
+
namespace Quantum
|
4 |
+
{
|
5 |
+
public unsafe static class UTManager
|
6 |
+
{
|
7 |
+
public static Action<EntityRef, string> SetupDebugger;
|
8 |
+
|
9 |
+
public static Action<EntityRef, long> ConsiderationChosen;
|
10 |
+
public static Action<EntityRef> OnUpdate;
|
11 |
+
|
12 |
+
/// <summary>
|
13 |
+
/// Initializes the Utility Reasoner, allocating all frame data needed.
|
14 |
+
/// If no UTRoot asset is passed by parameter, it will try to initialize with one already set on the Component, if any.
|
15 |
+
/// </summary>
|
16 |
+
public static void Init(Frame frame, UtilityReasoner* reasoner, AssetRefUTRoot utRootRef = default, EntityRef entity = default)
|
17 |
+
{
|
18 |
+
reasoner->Initialize(frame, utRootRef, entity);
|
19 |
+
}
|
20 |
+
|
21 |
+
public static void Free(Frame frame, UtilityReasoner* reasoner)
|
22 |
+
{
|
23 |
+
reasoner->Free(frame);
|
24 |
+
}
|
25 |
+
|
26 |
+
/// <summary>
|
27 |
+
/// Ticks the UtilityReasoner. The Considerations will be evaluated and the most useful will be executed.
|
28 |
+
/// It can be agnostic to entities, meaning that it is possible to have a UtilityReasoner as part of Global
|
29 |
+
/// </summary>
|
30 |
+
/// <param name="frame"></param>
|
31 |
+
/// <param name="reasoner"></param>
|
32 |
+
/// <param name="entity"></param>
|
33 |
+
public static void Update(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default)
|
34 |
+
{
|
35 |
+
if (entity != default)
|
36 |
+
{
|
37 |
+
OnUpdate?.Invoke(entity);
|
38 |
+
}
|
39 |
+
|
40 |
+
if (reasoner == default && entity != default)
|
41 |
+
{
|
42 |
+
reasoner = &frame.Unsafe.GetPointer<UTAgent>(entity)->UtilityReasoner;
|
43 |
+
}
|
44 |
+
|
45 |
+
reasoner->Update(frame, reasoner, entity);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
}
|
data/UTMomentumData.User.cs
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
namespace Quantum
|
2 |
+
{
|
3 |
+
[System.Serializable]
|
4 |
+
public partial struct UTMomentumData
|
5 |
+
{
|
6 |
+
}
|
7 |
+
}
|
data/UTRoot.cs
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
namespace Quantum
|
2 |
+
{
|
3 |
+
public partial class UTRoot
|
4 |
+
{
|
5 |
+
public AssetRefConsideration[] ConsiderationsRefs;
|
6 |
+
}
|
7 |
+
}
|
data/UseCardCommand.cs
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
namespace Quantum
|
2 |
+
{
|
3 |
+
using Photon.Deterministic;
|
4 |
+
|
5 |
+
public class UseCardCommand : DeterministicCommand
|
6 |
+
{
|
7 |
+
public byte CardIndex;
|
8 |
+
public FPVector2 Position;
|
9 |
+
|
10 |
+
public override void Serialize(BitStream stream)
|
11 |
+
{
|
12 |
+
stream.Serialize(ref CardIndex);
|
13 |
+
stream.Serialize(ref Position);
|
14 |
+
}
|
15 |
+
}
|
16 |
+
}
|
data/UtilityReasoner.User.cs
ADDED
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using Quantum.Collections;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
public unsafe partial struct UtilityReasoner
|
7 |
+
{
|
8 |
+
public void Initialize(Frame frame, AssetRefUTRoot utRootRef = default, EntityRef entity = default)
|
9 |
+
{
|
10 |
+
// If we don't receive the UTRoot as parameter, we try to find it on the component itself
|
11 |
+
// Useful for pre-seting the UTRoot on a Prototype
|
12 |
+
UTRoot utRootInstance;
|
13 |
+
if (utRootRef == default)
|
14 |
+
{
|
15 |
+
utRootInstance = frame.FindAsset<UTRoot>(UTRoot.Id);
|
16 |
+
}
|
17 |
+
else
|
18 |
+
{
|
19 |
+
UTRoot = utRootRef;
|
20 |
+
utRootInstance = frame.FindAsset<UTRoot>(utRootRef.Id);
|
21 |
+
}
|
22 |
+
|
23 |
+
// Initialize the Reasoner's considerations.
|
24 |
+
// Can be useful further when creating dynamically added Considerations to the Agent (runtime)
|
25 |
+
QList<AssetRefConsideration> considerationsList = frame.AllocateList<AssetRefConsideration>();
|
26 |
+
for (int i = 0; i < utRootInstance.ConsiderationsRefs.Length; i++)
|
27 |
+
{
|
28 |
+
considerationsList.Add(utRootInstance.ConsiderationsRefs[i]);
|
29 |
+
}
|
30 |
+
Considerations = considerationsList;
|
31 |
+
|
32 |
+
CooldownsDict = frame.AllocateDictionary<AssetRefConsideration, FP>();
|
33 |
+
|
34 |
+
QList<AssetRefConsideration> previousExecution = frame.AllocateList<AssetRefConsideration>();
|
35 |
+
for (int i = 0; i < 6; i++)
|
36 |
+
{
|
37 |
+
previousExecution.Add(default);
|
38 |
+
}
|
39 |
+
PreviousExecution = previousExecution;
|
40 |
+
|
41 |
+
MomentumList = frame.AllocateList<UTMomentumPack>();
|
42 |
+
|
43 |
+
if (entity != default)
|
44 |
+
{
|
45 |
+
UTManager.SetupDebugger?.Invoke(entity, utRootInstance.Path);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
public void Free(Frame frame)
|
50 |
+
{
|
51 |
+
UTRoot = default;
|
52 |
+
frame.FreeList<AssetRefConsideration>(Considerations);
|
53 |
+
frame.FreeDictionary<AssetRefConsideration, FP>(CooldownsDict);
|
54 |
+
frame.FreeList<AssetRefConsideration>(PreviousExecution);
|
55 |
+
frame.FreeList<UTMomentumPack>(MomentumList);
|
56 |
+
}
|
57 |
+
|
58 |
+
public void Update(Frame frame, UtilityReasoner* reasoner, EntityRef entity = default)
|
59 |
+
{
|
60 |
+
Consideration[] considerations = SolveConsiderationsList(frame, Considerations, reasoner, entity);
|
61 |
+
Consideration chosenConsideration = SelectBestConsideration(frame, considerations, 1, reasoner, entity);
|
62 |
+
|
63 |
+
if (chosenConsideration != default)
|
64 |
+
{
|
65 |
+
chosenConsideration.OnUpdate(frame, reasoner, entity);
|
66 |
+
UTManager.ConsiderationChosen?.Invoke(entity, chosenConsideration.Identifier.Guid.Value);
|
67 |
+
}
|
68 |
+
|
69 |
+
TickMomentum(frame, entity);
|
70 |
+
}
|
71 |
+
|
72 |
+
public Consideration[] SolveConsiderationsList(Frame frame, QListPtr<AssetRefConsideration> considerationsRefs, UtilityReasoner* reasoner, EntityRef entity = default)
|
73 |
+
{
|
74 |
+
QList<AssetRefConsideration> solvedRefs = frame.ResolveList(considerationsRefs);
|
75 |
+
Consideration[] considerationsArray = new Consideration[solvedRefs.Count];
|
76 |
+
for (int i = 0; i < solvedRefs.Count; i++)
|
77 |
+
{
|
78 |
+
considerationsArray[i] = frame.FindAsset<Consideration>(solvedRefs[i].Id);
|
79 |
+
}
|
80 |
+
|
81 |
+
return considerationsArray;
|
82 |
+
}
|
83 |
+
|
84 |
+
public Consideration SelectBestConsideration(Frame frame, Consideration[] considerations, byte depth, UtilityReasoner* reasoner, EntityRef entity = default)
|
85 |
+
{
|
86 |
+
if (considerations == default)
|
87 |
+
return null;
|
88 |
+
|
89 |
+
QList<UTMomentumPack> momentumList = frame.ResolveList(MomentumList);
|
90 |
+
|
91 |
+
// We get the Rank of every Consideration Set
|
92 |
+
// This "filters" the Considerations with higher absolute utility
|
93 |
+
AssetRefConsideration[] highRankConsiderations = new AssetRefConsideration[considerations.Length];
|
94 |
+
int highestRank = -1;
|
95 |
+
int counter = 0;
|
96 |
+
QDictionary<AssetRefConsideration, FP> cooldowns = frame.ResolveDictionary(CooldownsDict);
|
97 |
+
|
98 |
+
for (int i = 0; i < considerations.Length; i++)
|
99 |
+
{
|
100 |
+
Consideration consideration = considerations[i];
|
101 |
+
|
102 |
+
// Force low Rank for Considerations in Cooldown
|
103 |
+
if (cooldowns.Count > 0 && cooldowns.ContainsKey(considerations[i]) == true)
|
104 |
+
{
|
105 |
+
cooldowns[considerations[i]] -= frame.DeltaTime;
|
106 |
+
if (cooldowns[considerations[i]] <= 0)
|
107 |
+
{
|
108 |
+
cooldowns.Remove(considerations[i]);
|
109 |
+
}
|
110 |
+
{
|
111 |
+
continue;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
// If the Consideration has Momentum, then it's Rank should is defined by it
|
116 |
+
// Otherwise, we calculate the Rank dynamically
|
117 |
+
int rank;
|
118 |
+
if (ContainsMomentum(momentumList, consideration, out var momentum) == true)
|
119 |
+
{
|
120 |
+
rank = momentum.Value;
|
121 |
+
}
|
122 |
+
else
|
123 |
+
{
|
124 |
+
rank = consideration.GetRank(frame, entity);
|
125 |
+
}
|
126 |
+
|
127 |
+
if (rank > highestRank)
|
128 |
+
{
|
129 |
+
counter = 0;
|
130 |
+
|
131 |
+
highestRank = rank;
|
132 |
+
highRankConsiderations[counter] = considerations[i];
|
133 |
+
}
|
134 |
+
else if (highestRank == rank)
|
135 |
+
{
|
136 |
+
counter++;
|
137 |
+
highRankConsiderations[counter] = considerations[i];
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
// We clean the indices on the high rank sets that were not selected
|
142 |
+
for (int i = counter + 1; i < highRankConsiderations.Length; i++)
|
143 |
+
{
|
144 |
+
if (highRankConsiderations[i] == default)
|
145 |
+
break;
|
146 |
+
|
147 |
+
highRankConsiderations[i] = default;
|
148 |
+
}
|
149 |
+
|
150 |
+
// Based on the higher rank, we check which Considerations sets have greater utility
|
151 |
+
// Then we choose that set this frame
|
152 |
+
Consideration chosenConsideration = default;
|
153 |
+
FP highestScore = FP.UseableMin;
|
154 |
+
for (int i = 0; i <= counter; i++)
|
155 |
+
{
|
156 |
+
if (highRankConsiderations[i] == default)
|
157 |
+
continue;
|
158 |
+
|
159 |
+
Consideration consideration = frame.FindAsset<Consideration>(highRankConsiderations[i].Id);
|
160 |
+
|
161 |
+
FP score = consideration.Score(frame, entity);
|
162 |
+
if (highestScore < score)
|
163 |
+
{
|
164 |
+
highestScore = score;
|
165 |
+
chosenConsideration = consideration;
|
166 |
+
}
|
167 |
+
}
|
168 |
+
|
169 |
+
if (chosenConsideration != default)
|
170 |
+
{
|
171 |
+
// If the chosen Consideration and it is not already under Momentum,
|
172 |
+
// we add add it there, replacing the previous Momentum (if any)
|
173 |
+
if (chosenConsideration.MomentumData.Value > 0 && ContainsMomentum(momentumList, chosenConsideration, out var momentum) == false)
|
174 |
+
{
|
175 |
+
InsertMomentum(frame, momentumList, chosenConsideration);
|
176 |
+
}
|
177 |
+
|
178 |
+
// If the chosen Consideration has cooldown and it is not yet on the cooldowns dictionary,
|
179 |
+
// we add it there
|
180 |
+
if (chosenConsideration.Cooldown > 0 && cooldowns.ContainsKey(chosenConsideration) == false)
|
181 |
+
{
|
182 |
+
cooldowns.Add(chosenConsideration, chosenConsideration.Cooldown);
|
183 |
+
}
|
184 |
+
|
185 |
+
// Add the chosen set to the choices history
|
186 |
+
OnConsiderationChosen(frame, reasoner, chosenConsideration, entity);
|
187 |
+
}
|
188 |
+
else
|
189 |
+
{
|
190 |
+
OnNoConsiderationChosen(frame, reasoner, depth, entity);
|
191 |
+
}
|
192 |
+
|
193 |
+
// We return the chosen set so it can be executed
|
194 |
+
return chosenConsideration;
|
195 |
+
}
|
196 |
+
|
197 |
+
#region Momentum
|
198 |
+
private bool ContainsMomentum(QList<UTMomentumPack> momentumList, Consideration consideration, out UTMomentumData momentum)
|
199 |
+
{
|
200 |
+
for (int i = 0; i < momentumList.Count; i++)
|
201 |
+
{
|
202 |
+
if (momentumList[i].ConsiderationRef == consideration)
|
203 |
+
{
|
204 |
+
momentum = momentumList[i].MomentumData;
|
205 |
+
return true;
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
momentum = default;
|
210 |
+
return false;
|
211 |
+
}
|
212 |
+
|
213 |
+
private void InsertMomentum(Frame frame, QList<UTMomentumPack> momentumList, AssetRefConsideration considerationRef)
|
214 |
+
{
|
215 |
+
Consideration newConsideration = frame.FindAsset<Consideration>(considerationRef.Id);
|
216 |
+
|
217 |
+
// First, we check if this should be a replacement, which happens if:
|
218 |
+
// . The momentum list already have that same Depth added
|
219 |
+
// . Or when it have a higher Depth added
|
220 |
+
bool wasReplacedment = false;
|
221 |
+
for (int i = 0; i < momentumList.Count; i++)
|
222 |
+
{
|
223 |
+
Consideration currentConsideration = frame.FindAsset<Consideration>(momentumList[i].ConsiderationRef.Id);
|
224 |
+
if (currentConsideration.Depth == newConsideration.Depth || currentConsideration.Depth > newConsideration.Depth)
|
225 |
+
{
|
226 |
+
momentumList.GetPointer(i)->ConsiderationRef = considerationRef;
|
227 |
+
momentumList.GetPointer(i)->MomentumData = frame.FindAsset<Consideration>(considerationRef.Id).MomentumData;
|
228 |
+
|
229 |
+
// We clear the rightmost momentum entries
|
230 |
+
if (i < momentumList.Count - 1)
|
231 |
+
{
|
232 |
+
for (int k = i + 1; k < momentumList.Count; k++)
|
233 |
+
{
|
234 |
+
momentumList.RemoveAt(k);
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
wasReplacedment = true;
|
239 |
+
break;
|
240 |
+
}
|
241 |
+
}
|
242 |
+
|
243 |
+
// If there was no replacement, we simply add it to the end of the list as this
|
244 |
+
// consideration probably has higher Depth than the others currently on the list
|
245 |
+
// which can also mean that the list was empty
|
246 |
+
if (wasReplacedment == false)
|
247 |
+
{
|
248 |
+
UTMomentumPack newMomentum = new UTMomentumPack()
|
249 |
+
{
|
250 |
+
ConsiderationRef = considerationRef,
|
251 |
+
MomentumData = frame.FindAsset<Consideration>(considerationRef.Id).MomentumData,
|
252 |
+
};
|
253 |
+
momentumList.Add(newMomentum);
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
private void TickMomentum(Frame frame, EntityRef entity = default)
|
258 |
+
{
|
259 |
+
QList<UTMomentumPack> momentumList = frame.ResolveList(MomentumList);
|
260 |
+
|
261 |
+
// We decrease the timer and check if it is time already to decay all of the current Momentums
|
262 |
+
TimeToTick -= frame.DeltaTime;
|
263 |
+
bool decay = false;
|
264 |
+
if (TimeToTick <= 0)
|
265 |
+
{
|
266 |
+
decay = true;
|
267 |
+
TimeToTick = 1;
|
268 |
+
}
|
269 |
+
|
270 |
+
for (int i = 0; i < momentumList.Count; i++)
|
271 |
+
{
|
272 |
+
UTMomentumPack* momentum = momentumList.GetPointer(i);
|
273 |
+
|
274 |
+
// If we currently have a commitment, we check if it is done already
|
275 |
+
// If it is done, that Consideration's Rank shall be re-calculated
|
276 |
+
// If it is not done, then the Consideration's Rank will be kept due to the commitment
|
277 |
+
// unless some other Consideration has greater Rank and replaces the current commitment
|
278 |
+
Consideration momentumConsideration = frame.FindAsset<Consideration>(momentum->ConsiderationRef.Id);
|
279 |
+
|
280 |
+
if (momentum->MomentumData.Value > 0 && momentumConsideration.MomentumData.DecayAmount > 0)
|
281 |
+
{
|
282 |
+
if (decay)
|
283 |
+
{
|
284 |
+
momentum->MomentumData.Value -= momentumConsideration.MomentumData.DecayAmount;
|
285 |
+
}
|
286 |
+
}
|
287 |
+
|
288 |
+
|
289 |
+
bool isDone = false;
|
290 |
+
if (momentumConsideration.Commitment != default)
|
291 |
+
{
|
292 |
+
isDone = momentumConsideration.Commitment.Execute(frame, entity);
|
293 |
+
}
|
294 |
+
if (isDone == true || momentum->MomentumData.Value <= 0)
|
295 |
+
{
|
296 |
+
momentum->MomentumData.Value = 0;
|
297 |
+
momentumList.RemoveAt(i);
|
298 |
+
}
|
299 |
+
}
|
300 |
+
}
|
301 |
+
#endregion
|
302 |
+
|
303 |
+
#region ConsiderationsChoiceReactions
|
304 |
+
private static void OnConsiderationChosen(Frame frame, UtilityReasoner* reasoner, AssetRefConsideration chosenConsiderationRef, EntityRef entity = default)
|
305 |
+
{
|
306 |
+
Consideration chosenConsideration = frame.FindAsset<Consideration>(chosenConsiderationRef.Id);
|
307 |
+
|
308 |
+
QList<AssetRefConsideration> previousExecution = frame.ResolveList(reasoner->PreviousExecution);
|
309 |
+
|
310 |
+
if (previousExecution[chosenConsideration.Depth - 1] != chosenConsideration)
|
311 |
+
{
|
312 |
+
// Exit the one that we're replacing
|
313 |
+
var replacedSet = frame.FindAsset<Consideration>(previousExecution[chosenConsideration.Depth - 1].Id);
|
314 |
+
if (replacedSet != default)
|
315 |
+
{
|
316 |
+
replacedSet.OnExit(frame, reasoner, entity);
|
317 |
+
}
|
318 |
+
|
319 |
+
// Exit the consecutive ones
|
320 |
+
for (int i = chosenConsideration.Depth; i < previousExecution.Count; i++)
|
321 |
+
{
|
322 |
+
var cs = frame.FindAsset<Consideration>(previousExecution[i].Id);
|
323 |
+
if (cs == default)
|
324 |
+
break;
|
325 |
+
|
326 |
+
cs.OnExit(frame, reasoner, entity);
|
327 |
+
previousExecution[i] = default;
|
328 |
+
}
|
329 |
+
|
330 |
+
// Insert and Enter on the new chosen consideration
|
331 |
+
previousExecution[chosenConsideration.Depth - 1] = chosenConsideration;
|
332 |
+
chosenConsideration.OnEnter(frame, reasoner, entity);
|
333 |
+
}
|
334 |
+
}
|
335 |
+
|
336 |
+
private static void OnNoConsiderationChosen(Frame frame, UtilityReasoner* reasoner, byte depth, EntityRef entity = default)
|
337 |
+
{
|
338 |
+
QList<AssetRefConsideration> previousExecution = frame.ResolveList(reasoner->PreviousExecution);
|
339 |
+
|
340 |
+
for (int i = depth - 1; i < previousExecution.Count; i++)
|
341 |
+
{
|
342 |
+
var cs = frame.FindAsset<Consideration>(previousExecution[i].Id);
|
343 |
+
if (cs == default)
|
344 |
+
break;
|
345 |
+
|
346 |
+
cs.OnExit(frame, reasoner, entity);
|
347 |
+
previousExecution[i] = default;
|
348 |
+
}
|
349 |
+
}
|
350 |
+
#endregion
|
351 |
+
}
|
352 |
+
}
|
data/WaitLeaf.cs
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
using Photon.Deterministic;
|
2 |
+
using System;
|
3 |
+
|
4 |
+
namespace Quantum
|
5 |
+
{
|
6 |
+
[Serializable]
|
7 |
+
public unsafe partial class WaitLeaf : BTLeaf
|
8 |
+
{
|
9 |
+
// How many time shall be waited
|
10 |
+
// This is measured in seconds
|
11 |
+
public FP Duration;
|
12 |
+
|
13 |
+
// Indexer for us to store the End Time value on the BTAgent itself
|
14 |
+
public BTDataIndex EndTimeIndex;
|
15 |
+
|
16 |
+
public override void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent)
|
17 |
+
{
|
18 |
+
base.Init(frame, blackboard, agent);
|
19 |
+
|
20 |
+
// We allocate space for the End Time on the Agent so we can change it in runtime
|
21 |
+
agent->AddFPData(frame, 0);
|
22 |
+
}
|
23 |
+
|
24 |
+
public override void OnEnter(BTParams btParams)
|
25 |
+
{
|
26 |
+
base.OnEnter(btParams);
|
27 |
+
|
28 |
+
FP currentTime;
|
29 |
+
FP endTime;
|
30 |
+
|
31 |
+
// Get the current time
|
32 |
+
currentTime = btParams.Frame.BotSDKGameTime;
|
33 |
+
// Add the Duration value so we know when the Leaf will stop running
|
34 |
+
endTime = currentTime + Duration;
|
35 |
+
|
36 |
+
// Store the final value on the Agent data
|
37 |
+
btParams.Agent->SetFPData(btParams.Frame, endTime, EndTimeIndex.Index);
|
38 |
+
}
|
39 |
+
|
40 |
+
protected override BTStatus OnUpdate(BTParams btParams)
|
41 |
+
{
|
42 |
+
FP currentTime;
|
43 |
+
FP endTime;
|
44 |
+
|
45 |
+
currentTime = btParams.Frame.BotSDKGameTime;
|
46 |
+
endTime = btParams.Agent->GetFPData(btParams.Frame, EndTimeIndex.Index);
|
47 |
+
|
48 |
+
// If waiting time isn't over yet, then we need more frames executing this Leaf
|
49 |
+
// So we say that we're still Running
|
50 |
+
if (currentTime < endTime)
|
51 |
+
{
|
52 |
+
return BTStatus.Running;
|
53 |
+
}
|
54 |
+
|
55 |
+
// If the waiting time is over, then we succeeded on waiting that amount of time
|
56 |
+
// Then we return Success
|
57 |
+
return BTStatus.Success;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
}
|
data/input.txt
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Introduction
|
2 |
+
Input is a crucial component of Quantum's core architecture. In a deterministic networking library, the output of the system is fixed and predetermined given a certain input. This means that as long as the input is the same across all clients in the network, the output will also be the same.
|
3 |
+
|
4 |
+
Back To Top
|
5 |
+
|
6 |
+
|
7 |
+
Defining In DSL
|
8 |
+
Input can be defined in any dsl file. For example, an input struct where you have a movement direction and a singluar jump button would look something like this:
|
9 |
+
|
10 |
+
input
|
11 |
+
{
|
12 |
+
button Jump;
|
13 |
+
FPVector3 Direction;
|
14 |
+
}
|
15 |
+
The server is responsible for batching and sending down input confirmations for full tick-sets (all player's input). For this reason, this struct should be kept to a minimal size as much as possible.
|
16 |
+
|
17 |
+
Back To Top
|
18 |
+
|
19 |
+
|
20 |
+
Commands
|
21 |
+
deterministic commands are another input path for Quantum, and can have arbitrary data and size, which make them ideal for special types of inputs, like "buy this item", "teleport somewhere", etc.
|
22 |
+
|
23 |
+
Back To Top
|
24 |
+
|
25 |
+
|
26 |
+
Polling In Unity
|
27 |
+
To send input to the Quantum simulation, you must poll for it inside of unity. To do this, we subscribe to the PollInput callback inside of a MonoBehaviour in our gameplay scene.
|
28 |
+
|
29 |
+
private void OnEnable()
|
30 |
+
{
|
31 |
+
QuantumCallback.Subscribe(this, (CallbackPollInput callback) => PollInput(callback));
|
32 |
+
}
|
33 |
+
Then, in the callback, we read from out input source and populate our struct.
|
34 |
+
|
35 |
+
public void PollInput(CallbackPollInput callback)
|
36 |
+
{
|
37 |
+
Quantum.Input i = new Quantum.Input();
|
38 |
+
|
39 |
+
var direction = new Vector3();
|
40 |
+
direction.x = UnityEngine.Input.GetAxisRaw("Horizontal");
|
41 |
+
direction.y = UnityEngine.Input.GetAxisRaw("Vertical");
|
42 |
+
|
43 |
+
i.Jump = UnityEngine.Input.GetKeyDown(KeyCode.Space);
|
44 |
+
|
45 |
+
// convert to fixed point.
|
46 |
+
i.Direction = direction.ToFPVector3();
|
47 |
+
|
48 |
+
callback.SetInput(i, DeterministicInputFlags.Repeatable);
|
49 |
+
}
|
50 |
+
NOTE: The float to fixed point conversion here is deterministic because it is done before it is shared with the simulation.
|
51 |
+
|
52 |
+
Back To Top
|
53 |
+
|
54 |
+
|
55 |
+
Optimization
|
56 |
+
It is genrally best practice to make sure that your Input definition consumes as little bandwidth as possible in order to maximize the amount of players your game can handle. Below are a few ways to optimize it.
|
57 |
+
|
58 |
+
Back To Top
|
59 |
+
|
60 |
+
|
61 |
+
Buttons
|
62 |
+
Instead of using booleans or similar data types to represent key presses, the Button type is used inside the Input DSL definition. This is because it only uses one bit per instance, so it is favorable to use where possible. Although they only use one bit over the network, locally they will contain a bit more game state. This is because the single bit is only representative of whether or not the button was pressed during the current frame, the rest of the information is computed locally. Buttons are defined as follows:
|
63 |
+
|
64 |
+
input
|
65 |
+
{
|
66 |
+
button Jump;
|
67 |
+
}
|
68 |
+
Back To Top
|
69 |
+
|
70 |
+
|
71 |
+
Encoded Direction
|
72 |
+
In a typical setting, movement is often represented using a direction vector, often defined in a DSL file as such:
|
73 |
+
|
74 |
+
input
|
75 |
+
{
|
76 |
+
FPVector2 Direction;
|
77 |
+
}
|
78 |
+
However, FPVector2 is comprised of two 'FP', which takes up 16 bytes of data, which can be a lot of data sent, especially with many clients in the same room. One such way of optimizing it, is by extending the Input struct and encoding the directional vector into a Byte instead of sending the full vector every single time. One such implemetation is as follows:
|
79 |
+
|
80 |
+
First, we define our input like normal, but instead of including an FPVector2 for direction, we replace it with a Byte where we will store the encoded version.
|
81 |
+
|
82 |
+
input
|
83 |
+
{
|
84 |
+
Byte EncodedDirection;
|
85 |
+
}
|
86 |
+
Next, we extend the input struct the same way a component is extended (see: adding functionality):
|
87 |
+
|
88 |
+
namespace Quantum
|
89 |
+
{
|
90 |
+
partial struct Input
|
91 |
+
{
|
92 |
+
public FPVector2 Direction
|
93 |
+
{
|
94 |
+
get
|
95 |
+
{
|
96 |
+
if (EncodedDirection == default)
|
97 |
+
return default;
|
98 |
+
|
99 |
+
Int32 angle = ((Int32)EncodedDirection - 1) * 2;
|
100 |
+
|
101 |
+
return FPVector2.Rotate(FPVector2.Up, angle * FP.Deg2Rad);
|
102 |
+
}
|
103 |
+
set
|
104 |
+
{
|
105 |
+
if (value == default)
|
106 |
+
{
|
107 |
+
EncodedDirection = default;
|
108 |
+
return;
|
109 |
+
}
|
110 |
+
|
111 |
+
var angle = FPVector2.RadiansSigned(FPVector2.Up, value) * FP.Rad2Deg;
|
112 |
+
|
113 |
+
angle = (((angle + 360) % 360) / 2) + 1;
|
114 |
+
|
115 |
+
EncodedDirection = (Byte) (angle.AsInt);
|
116 |
+
}
|
117 |
+
}
|
118 |
+
}
|
119 |
+
}
|
120 |
+
This implementation allows for the same usage as before, but it only takes up a singular byte instead of 16 bytes. It does this by utilzing a Direction property, which encodes and decodes the value from EncodedDirection automatically. No newline at end of file
|
data/kinematic character.txt
ADDED
@@ -0,0 +1,397 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Introduction
|
2 |
+
A Kinematic Character Controller, KCC for short, is used to move a character within the world according to its own set of rules. Using a KCC rather than physics/force based movement allows for tighter control and snappy movement. Although those concepts are core to every game, they vary tremendously in their definition as they are related to the overall gameplay. Therefore the KCCs included in the Quantum SDK are to be considered a starting point; however, game developers will likely have to create their own in order to get the best possible results for their specific context.
|
3 |
+
|
4 |
+
Quantum comes with two pre-build KCCs, one for 2D (side-scrolling) and one for 3D movement. The API allows characters to move through terrains, climb steps, slide down slopes, and use moving platforms.
|
5 |
+
|
6 |
+
The KCCs take physics data of both static and dynamic objects into consideration when calculating the movement vectors. Objects will block and define the character's movement. Collision callbacks with the environment objects will be trigged as well.
|
7 |
+
|
8 |
+
Back To Top
|
9 |
+
|
10 |
+
|
11 |
+
Requirements
|
12 |
+
To use or add a KCC to an entity, the entity has to already have a Transform component. A PhysicsBody can be used but is not necessary; it is generally advised against using a PhysicsBody with a KCC as the physics system may affect it and result in unintended movement.
|
13 |
+
|
14 |
+
If you are not familiar with Quantum's Physics yet, please review the Physics documentation first.
|
15 |
+
|
16 |
+
Back To Top
|
17 |
+
|
18 |
+
|
19 |
+
Raycasts & ShapeOverlap
|
20 |
+
The KCC only uses ShapeOverlaps - circle for 2D and sphere for 3D - to calculate its movement. Thus an entity with only a KKC component will be ignored by raycasts. Should the entity be subject to raycasting, it has to also carry a PhysicsCollider.
|
21 |
+
|
22 |
+
Back To Top
|
23 |
+
|
24 |
+
|
25 |
+
Note
|
26 |
+
This page covers both the 2D and 3D KCCs.
|
27 |
+
|
28 |
+
Back To Top
|
29 |
+
|
30 |
+
|
31 |
+
The Character Controller Component
|
32 |
+
You can add the CharacterController component to your entity by either:
|
33 |
+
|
34 |
+
adding the "Character Controller" component to the Entity Prototype in Unity; or,
|
35 |
+
adding the "Character Controller" component via code.
|
36 |
+
KCC 2D and 2D Components in Unity
|
37 |
+
The Character Controller 2D and 3D components attached to an Entity Prototype in the Unity Editor.
|
38 |
+
To add the Character Controller via code, follow the examples below.
|
39 |
+
|
40 |
+
// 2D KCC
|
41 |
+
var kccConfig = FindAsset<CharacterController2DConfig>(KCC_CONFIG_PATH);
|
42 |
+
var kcc = new CharacterController2D();
|
43 |
+
kcc.Init(f, kccConfig)
|
44 |
+
f.Add(entity, kcc);
|
45 |
+
|
46 |
+
// 3D KCC
|
47 |
+
var kccConfig = FindAsset<CharacterController3DConfig>(KCC_CONFIG_PATH);
|
48 |
+
var kcc = new CharacterController3D();
|
49 |
+
kcc.Init(f, kccConfig)
|
50 |
+
f.Add(entity, kcc);
|
51 |
+
Back To Top
|
52 |
+
|
53 |
+
|
54 |
+
Note
|
55 |
+
The component has to be initiliazed after being created. The available initializing options are:
|
56 |
+
|
57 |
+
(code) the Init() method without parameter, it will load the DefaultCharacterController from Assets/Resources/DB/Configs.
|
58 |
+
(code) the Init() method with parameter, it will load the passed in CharacterControllerConfig.
|
59 |
+
(editor) add the CharacterControllerConfig to the Config slot in the Character Controller component.
|
60 |
+
Back To Top
|
61 |
+
|
62 |
+
|
63 |
+
The Character Controller Config
|
64 |
+
Create your own KCC config asset via the context menu under Create > Quantum > Assets > Physics > CharacterController2D/3D.
|
65 |
+
|
66 |
+
|
67 |
+
Default Config Assets
|
68 |
+
The default 2D and 3D KCC Config assets are located inside the Assets/Resources/DB/Configs folder. Here is how the 3D KCC config looks like:
|
69 |
+
|
70 |
+
KCC 3D Default Config
|
71 |
+
The DefaultCharacterController3D Config Asset.
|
72 |
+
Back To Top
|
73 |
+
|
74 |
+
|
75 |
+
A Brief Explanation Into The Config Fields
|
76 |
+
Offset is used to define the KCC local position based into the entity position. It is commonly used to position the center of the KCC at the feet of the character. Remember: the KCC is used to move the character, so it does not necessarily have to encapsulate the character's whole body.
|
77 |
+
Radius defines the boundaries of the character and should encompass the character horizontal size. This is used to know whether a character can move in a certain direction, a wall is blocking the movement, a step is to be climbed, or a slope to be slid on.
|
78 |
+
Max Penetration smoothens the movement when a character penetrates other physics objects. If the character passes the Max Penetration, a hard fix will be applied and snap it into the correct position. Reducing this value to zero will apply all corrections fully and instantly; this may result in jagged movement.
|
79 |
+
Extent defines a radius in which collisions are detected preemptively.
|
80 |
+
Max Contacts is used to select the amount of contact points computed by the KCC. 1 will usually work fine and is the most performant option. If you experience jerky movement, try setting this to 2; the additional overhead is negligible.
|
81 |
+
Layer Mask defines which collider layers should be taken into consideration by the physics query performed by the KCC.
|
82 |
+
Air Control toggle to True and the KCC is able to perform movement adjustments when it not touching the ground.
|
83 |
+
Acceleration defines how fast the character accelerates.
|
84 |
+
Base Jump Impulse defines the strength of the impulse when calling the KCC Jump() method. If no value is passed to the method, this value will be used.
|
85 |
+
Max Speed caps the character's maximal horizontal speed.
|
86 |
+
Gravity applies a gravity force to the KCC.
|
87 |
+
Max Slope defines the maximal angle, in degrees, the character can walk up and down.
|
88 |
+
Max Slope Speed limits the speed at which the character slides down a slope when the movement type is Slope Fall instead of Horizontal Fall.
|
89 |
+
Back To Top
|
90 |
+
|
91 |
+
|
92 |
+
Character Controller API
|
93 |
+
The API shown below focuses on the 3D KCC. The 2D and 3D APIs are very similar though.
|
94 |
+
|
95 |
+
|
96 |
+
Properties And Fields
|
97 |
+
Each CharacterController component has these fields.
|
98 |
+
|
99 |
+
public FP MaxSpeed { get; set;}
|
100 |
+
public FPVector3 Velocity { get; set;}
|
101 |
+
public bool Grounded { get; set;}
|
102 |
+
public FP CurrentSpeed { get;}
|
103 |
+
public AssetGUID ConfigId { get;}
|
104 |
+
Back To Top
|
105 |
+
|
106 |
+
|
107 |
+
Tip
|
108 |
+
The MaxSpeed is a cached value after initialization. It can therefore be modified at runtime, e.g. when performing dashes.
|
109 |
+
|
110 |
+
Back To Top
|
111 |
+
|
112 |
+
|
113 |
+
API
|
114 |
+
Each KCC components has the following methods:
|
115 |
+
|
116 |
+
// Initialization
|
117 |
+
public void Init(FrameBase frame, CharacterController3DConfig config = null);
|
118 |
+
|
119 |
+
// Jump
|
120 |
+
public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null);
|
121 |
+
|
122 |
+
// Move
|
123 |
+
public void Move(FrameBase frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null);
|
124 |
+
|
125 |
+
// Raw Information
|
126 |
+
public static CharacterController3DMovement ComputeRawMovement(Frame frame, EntityRef entity, Transform3D* transform, CharacterController3D* kcc, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, bool? useManifoldNormal = null);
|
127 |
+
The Jump and Move methods are convenient for prototyping, while ComputeRawMovement provides the key information for creating your own custom movement. In the example KCC's provided by Quantum, the information from ComputeRawMovement is used by the internal steering method ComputeRawSteer to compute the steering used in Move.
|
128 |
+
|
129 |
+
IMPORTANT: The implementations of Jump(), Move() and ComputeRawSteer() are presented below for fostering understanding and help create custom implementations specific to the game's requirements.
|
130 |
+
|
131 |
+
Back To Top
|
132 |
+
|
133 |
+
|
134 |
+
CharacterController3DMovement
|
135 |
+
ComputeRawMovement() computes the environmental data necessary for the steering by performing a ShapeOverlap and processing the data. The method returns a CharacterController3DMovement struct which can then be applied to the character movement. The movement data provided can also be used to create a custom steering implementation.
|
136 |
+
|
137 |
+
The CharacterController3DMovement struct holds the following information:
|
138 |
+
|
139 |
+
public enum CharacterMovementType
|
140 |
+
{
|
141 |
+
None, // grounded with no desired direction passed
|
142 |
+
FreeFall, // no contacts within the Radius
|
143 |
+
SlopeFall, // there is at least 1 ground contact within the Radius, specifically a contact with a normal angle vs -gravity <= maxSlopeAngle). It is possible to be "grounded" without this type of contact (see Grounded property in the CharacterController3DMovement)
|
144 |
+
Horizontal, // there is NO ground contact, but there is at least one lateral contact (normal angle vs -gravity > maxSlopeAngle)
|
145 |
+
}
|
146 |
+
|
147 |
+
public struct CharacterController3DMovement
|
148 |
+
{
|
149 |
+
public CharacterMovementType Type;
|
150 |
+
|
151 |
+
// the surface normal of the closest unique contact
|
152 |
+
public FPVector3 NearestNormal;
|
153 |
+
|
154 |
+
// the average normal from all contacts
|
155 |
+
public FPVector3 AvgNormal;
|
156 |
+
|
157 |
+
// the normal of the closest contact that qualifies as ground
|
158 |
+
public FPVector3 GroundNormal;
|
159 |
+
|
160 |
+
// the surface tangent (from GroundNormal and the derived direction) for Horizontal move, or the normalized desired direction when in CharacterMovementType.FreeFall
|
161 |
+
public FPVector3 Tangent;
|
162 |
+
|
163 |
+
// surface tangent computed from closest the contact normal vs -gravity (does not consider current velocity of CC itself).
|
164 |
+
public FPVector3 SlopeTangent;
|
165 |
+
|
166 |
+
// accumulated projected correction from all contacts within the Radius. It compensates with dot-products to NOT overshoot.
|
167 |
+
public FPVector3 Correction;
|
168 |
+
|
169 |
+
// max penetration of the closest contact within the Radius
|
170 |
+
public FP Penetration;
|
171 |
+
|
172 |
+
// uses the EXTENDED radius to assign this Boolean AND the GroundedNormalas to avoid oscilations of the grounded state when moving over slightly irregular terrain
|
173 |
+
public Boolean Grounded;
|
174 |
+
|
175 |
+
// number of contacts within Radius
|
176 |
+
public int Contacts;
|
177 |
+
}
|
178 |
+
ComputeRawMovement() is used by the Move() method.
|
179 |
+
|
180 |
+
Back To Top
|
181 |
+
|
182 |
+
|
183 |
+
Jump()
|
184 |
+
This is only a reference implementation.
|
185 |
+
The Jump simply adds an impulse to the KCC's current Velocity and toggles the Jumped boolean which will be processed by the internal ComputeRawSteer Method.
|
186 |
+
|
187 |
+
public void Jump(FrameBase frame, bool ignoreGrounded = false, FP? impulse = null) {
|
188 |
+
|
189 |
+
if (Grounded || ignoreGrounded) {
|
190 |
+
|
191 |
+
if (impulse.HasValue)
|
192 |
+
Velocity.Y.RawValue = impulse.Value.RawValue;
|
193 |
+
else {
|
194 |
+
var config = frame.FindAsset(Config);
|
195 |
+
Velocity.Y.RawValue = config.BaseJumpImpulse.RawValue;
|
196 |
+
}
|
197 |
+
|
198 |
+
Jumped = true;
|
199 |
+
}
|
200 |
+
}
|
201 |
+
Back To Top
|
202 |
+
|
203 |
+
|
204 |
+
Move()
|
205 |
+
This is only a reference implementation.
|
206 |
+
Move() takes the following things by taking into consideration when calculating the character's new position:
|
207 |
+
|
208 |
+
the current position
|
209 |
+
the direction
|
210 |
+
the gravity
|
211 |
+
jumps
|
212 |
+
slopes
|
213 |
+
and more
|
214 |
+
All these aspects can be defined in the config asset passed to the Init() method. This is convenient for prototyping FPS/TPS/Action games which have terrains, mesh colliders and primitives.
|
215 |
+
|
216 |
+
NOTE: Since it calculates everything and returns a final FPVector3 result, it does not give you much control over the movement itself. For tighter control over the movement, you should use ComputeRawMovement() and create your own custom steering + movement.
|
217 |
+
|
218 |
+
public void Move(Frame frame, EntityRef entity, FPVector3 direction, IKCCCallbacks3D callback = null, int? layerMask = null, Boolean? useManifoldNormal = null, FP? deltaTime = null) {
|
219 |
+
Assert.Check(frame.Has<Transform3D>(entity));
|
220 |
+
|
221 |
+
var transform = frame.GetPointer<Transform3D>(entity);
|
222 |
+
var dt = deltaTime ?? frame.DeltaTime;
|
223 |
+
|
224 |
+
CharacterController3DMovement movementPack;
|
225 |
+
fixed (CharacterController3D* thisKcc = &this) {
|
226 |
+
movementPack = ComputeRawMovement(frame, entity, transform, thisKcc, direction, callback, layerMask, useManifoldNormal);
|
227 |
+
}
|
228 |
+
|
229 |
+
ComputeRawSteer(frame, ref movementPack, dt);
|
230 |
+
|
231 |
+
var movement = Velocity * dt;
|
232 |
+
if (movementPack.Penetration > FP.EN3) {
|
233 |
+
var config = frame.FindAsset<CharacterController3DConfig>(Config.Id);
|
234 |
+
if (movementPack.Penetration > config.MaxPenetration) {
|
235 |
+
movement += movementPack.Correction;
|
236 |
+
} else {
|
237 |
+
movement += movementPack.Correction * config.PenetrationCorrection;
|
238 |
+
}
|
239 |
+
}
|
240 |
+
|
241 |
+
transform->Position += movement;
|
242 |
+
}
|
243 |
+
Back To Top
|
244 |
+
|
245 |
+
|
246 |
+
ComputeRawSteer()
|
247 |
+
Steering involves computing the movement based on the position, radius and velocity of the character, and corrects the movement if necessary.
|
248 |
+
|
249 |
+
This is only a reference implementation.
|
250 |
+
ComputeRawSteer is an internal method that does the bulk of the movement calculations based on the type of movement the character is currently performing. In the example KCCs, Move requests the movementPack values from ComputeRawMovement and passes them to ComputeRawSteer.
|
251 |
+
|
252 |
+
private void ComputeRawSteer(FrameThreadSafe f, ref CharacterController3DMovement movementPack, FP dt) {
|
253 |
+
|
254 |
+
Grounded = movementPack.Grounded;
|
255 |
+
var config = f.FindAsset(Config);
|
256 |
+
var minYSpeed = -FP._100;
|
257 |
+
var maxYSpeed = FP._100;
|
258 |
+
|
259 |
+
switch (movementPack.Type) {
|
260 |
+
|
261 |
+
// FreeFall
|
262 |
+
case CharacterMovementType.FreeFall:
|
263 |
+
|
264 |
+
Velocity.Y -= config._gravityStrength * dt;
|
265 |
+
|
266 |
+
if (!config.AirControl || movementPack.Tangent == default(FPVector3)) {
|
267 |
+
Velocity.X = FPMath.Lerp(Velocity.X, FP._0, dt * config.Braking);
|
268 |
+
Velocity.Z = FPMath.Lerp(Velocity.Z, FP._0, dt * config.Braking);
|
269 |
+
} else {
|
270 |
+
Velocity += movementPack.Tangent * config.Acceleration * dt;
|
271 |
+
}
|
272 |
+
|
273 |
+
break;
|
274 |
+
|
275 |
+
// Grounded movement
|
276 |
+
case CharacterMovementType.Horizontal:
|
277 |
+
|
278 |
+
// apply tangent velocity
|
279 |
+
Velocity += movementPack.Tangent * config.Acceleration * dt;
|
280 |
+
var tangentSpeed = FPVector3.Dot(Velocity, movementPack.Tangent);
|
281 |
+
|
282 |
+
// lerp current velocity to tangent
|
283 |
+
var tangentVel = tangentSpeed * movementPack.Tangent;
|
284 |
+
var lerp = config.Braking * dt;
|
285 |
+
Velocity.X = FPMath.Lerp(Velocity.X, tangentVel.X, lerp);
|
286 |
+
Velocity.Z = FPMath.Lerp(Velocity.Z, tangentVel.Z, lerp);
|
287 |
+
|
288 |
+
// we only lerp the vertical velocity if the character is not jumping in this exact frame,
|
289 |
+
// otherwise it will jump with a lower impulse
|
290 |
+
if (Jumped == false) {
|
291 |
+
Velocity.Y = FPMath.Lerp(Velocity.Y, tangentVel.Y, lerp);
|
292 |
+
}
|
293 |
+
|
294 |
+
// clamp tangent velocity with max speed
|
295 |
+
var tangentSpeedAbs = FPMath.Abs(tangentSpeed);
|
296 |
+
if (tangentSpeedAbs > MaxSpeed) {
|
297 |
+
Velocity -= FPMath.Sign(tangentSpeed) * movementPack.Tangent * (tangentSpeedAbs - MaxSpeed);
|
298 |
+
}
|
299 |
+
|
300 |
+
break;
|
301 |
+
|
302 |
+
// Sliding due to excessively steep slope
|
303 |
+
case CharacterMovementType.SlopeFall:
|
304 |
+
|
305 |
+
Velocity += movementPack.SlopeTangent * config.Acceleration * dt;
|
306 |
+
minYSpeed = -config.MaxSlopeSpeed;
|
307 |
+
|
308 |
+
break;
|
309 |
+
|
310 |
+
// No movement, only deceleration
|
311 |
+
case CharacterMovementType.None:
|
312 |
+
|
313 |
+
var lerpFactor = dt * config.Braking;
|
314 |
+
|
315 |
+
if (Velocity.X.RawValue != 0) {
|
316 |
+
Velocity.X = FPMath.Lerp(Velocity.X, default, lerpFactor);
|
317 |
+
if (FPMath.Abs(Velocity.X) < FP.EN1) {
|
318 |
+
Velocity.X.RawValue = 0;
|
319 |
+
}
|
320 |
+
}
|
321 |
+
|
322 |
+
if (Velocity.Z.RawValue != 0) {
|
323 |
+
Velocity.Z = FPMath.Lerp(Velocity.Z, default, lerpFactor);
|
324 |
+
if (FPMath.Abs(Velocity.Z) < FP.EN1) {
|
325 |
+
Velocity.Z.RawValue = 0;
|
326 |
+
}
|
327 |
+
}
|
328 |
+
|
329 |
+
// we only lerp the vertical velocity back to 0 if the character is not jumping in this exact frame,
|
330 |
+
// otherwise it will jump with a lower impulse
|
331 |
+
if (Velocity.Y.RawValue != 0 && Jumped == false) {
|
332 |
+
Velocity.Y = FPMath.Lerp(Velocity.Y, default, lerpFactor);
|
333 |
+
if (FPMath.Abs(Velocity.Y) < FP.EN1) {
|
334 |
+
Velocity.Y.RawValue = 0;
|
335 |
+
}
|
336 |
+
}
|
337 |
+
|
338 |
+
minYSpeed = 0;
|
339 |
+
|
340 |
+
break;
|
341 |
+
}
|
342 |
+
|
343 |
+
// horizontal is clamped elsewhere
|
344 |
+
if (movementPack.Type != CharacterMovementType.Horizontal) {
|
345 |
+
var h = Velocity.XZ;
|
346 |
+
|
347 |
+
if (h.SqrMagnitude > MaxSpeed * MaxSpeed) {
|
348 |
+
h = h.Normalized * MaxSpeed;
|
349 |
+
}
|
350 |
+
|
351 |
+
Velocity.X = h.X;
|
352 |
+
Velocity.Y = FPMath.Clamp(Velocity.Y, minYSpeed, maxYSpeed);
|
353 |
+
Velocity.Z = h.Y;
|
354 |
+
}
|
355 |
+
|
356 |
+
// reset jump state
|
357 |
+
Jumped = false;
|
358 |
+
}
|
359 |
+
Back To Top
|
360 |
+
|
361 |
+
|
362 |
+
Collision Callbacks
|
363 |
+
Whenever the KCC detects intersections with colliders a callback is triggered.
|
364 |
+
|
365 |
+
public interface IKCCCallbacks2D
|
366 |
+
{
|
367 |
+
bool OnCharacterCollision2D(FrameBase f, EntityRef character, Physics2D.Hit hit);
|
368 |
+
void OnCharacterTrigger2D(FrameBase f, EntityRef character, Physics2D.Hit hit);
|
369 |
+
}
|
370 |
+
|
371 |
+
public interface IKCCCallbacks3D
|
372 |
+
{
|
373 |
+
bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit);
|
374 |
+
void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit);
|
375 |
+
}
|
376 |
+
To receive the callbacks and use its information implement the corresponding IKCCCallbacks interface in a system.
|
377 |
+
|
378 |
+
Important Note that the collision callbacks return a Boolean value. This is allows you decide whether a collision should be ignored. Returning false makes the character pass through physics object it collided with.
|
379 |
+
|
380 |
+
Besides implementing the callbacks the movement methods should also pass the IKCCCallbacks object; below is a code snippet using the collision callbacks.
|
381 |
+
|
382 |
+
public unsafe class SampleSystem : SystemMainThread, IKCCCallbacks3D
|
383 |
+
{
|
384 |
+
public bool OnCharacterCollision3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) {
|
385 |
+
// read the collision information to decide if this should or not be ignored
|
386 |
+
return true;
|
387 |
+
}
|
388 |
+
|
389 |
+
public void OnCharacterTrigger3D(FrameBase f, EntityRef character, Physics3D.Hit3D hit) {
|
390 |
+
}
|
391 |
+
|
392 |
+
public override void Update(Frame f) {
|
393 |
+
// [...]
|
394 |
+
// adding the IKCCCallbacks3D as the last parameter (this system, in this case)
|
395 |
+
var movement = CharacterController3D.Move((Entity*)character, input->Direction, this);
|
396 |
+
// [...]
|
397 |
+
}
|
data/list1.txt
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
In quantum we can to list as metioned below.
|
2 |
+
|
3 |
+
component Targets {
|
4 |
+
list<EntityRef> Enemies;
|
5 |
+
}
|
6 |
+
The basic API methods for dealing with these Lists are:
|
7 |
+
|
8 |
+
Frame.AllocateList<T>()
|
9 |
+
Frame.FreeList(QListPtr<T> ptr)
|
10 |
+
Frame.ResolveList(QListPtr<T> ptr)
|