gg.easy.airship 0.1.2163 → 0.1.2164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/Runtime/Code/AirshipConst.cs +2 -2
- package/Runtime/Code/Analytics/ClientFolderUploader.cs +145 -0
- package/Runtime/Code/Analytics/ClientFolderUploader.cs.meta +2 -0
- package/Runtime/Code/Bootstrap/ClientBootstrap.cs +4 -0
- package/Runtime/Code/Network/StateSystem/AirshipNetworkedStateManager.cs +30 -12
- package/Runtime/Code/Player/Character/MovementSystems/Character/CharacterMovement.cs +127 -31
- package/Runtime/DevConsole/Resources/Prefabs/FAB_DevConsole.Instance.prefab +305 -1
- package/Runtime/Scenes/CoreScene.unity +266 -251
- package/package.json +1 -1
package/.editorconfig
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*.cs]
|
|
4
|
+
csharp_new_line_before_open_brace = none
|
|
5
|
+
csharp_new_line_before_else = false
|
|
6
|
+
csharp_new_line_before_catch = false
|
|
7
|
+
csharp_new_line_before_finally = false
|
|
8
|
+
csharp_new_line_before_members_in_object_initializers = false
|
|
9
|
+
csharp_new_line_before_members_in_anonymous_types = false
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// ReSharper disable InconsistentNaming
|
|
2
2
|
namespace Code {
|
|
3
3
|
public static class AirshipConst {
|
|
4
|
-
public const int playerVersion =
|
|
4
|
+
public const int playerVersion = 13;
|
|
5
5
|
|
|
6
6
|
/// <summary>
|
|
7
7
|
/// The server will kick clients that have a playerVersion lower than this value.
|
|
8
8
|
/// </summary>
|
|
9
|
-
public const int minAcceptedPlayerVersionOnServer =
|
|
9
|
+
public const int minAcceptedPlayerVersionOnServer = 13;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.IO.Compression;
|
|
4
|
+
using System.Threading.Tasks;
|
|
5
|
+
using Code.Http.Internal;
|
|
6
|
+
using Code.Http.Public;
|
|
7
|
+
using Code.Platform.Shared;
|
|
8
|
+
using Proyecto26;
|
|
9
|
+
using UnityEngine;
|
|
10
|
+
|
|
11
|
+
namespace Code.Analytics {
|
|
12
|
+
[Serializable]
|
|
13
|
+
public class PostArtifactResponse {
|
|
14
|
+
public string id;
|
|
15
|
+
public string url;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[Serializable]
|
|
19
|
+
public class RequestBody {
|
|
20
|
+
public string type;
|
|
21
|
+
public string name;
|
|
22
|
+
public string contentType;
|
|
23
|
+
public long contentLength;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public class ClientFolderUploader : MonoBehaviour {
|
|
27
|
+
|
|
28
|
+
// Start in the past otherwise they can't do this for the first 10 seconds since time starts at 0.
|
|
29
|
+
private float timeOfLastUpload = -10;
|
|
30
|
+
|
|
31
|
+
public void ButtonClick() {
|
|
32
|
+
if (Time.unscaledTime - timeOfLastUpload < 10) {
|
|
33
|
+
Debug.LogWarning("[ClientFolderUploader] Upload already in progress or too soon after last upload");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
timeOfLastUpload = Time.unscaledTime;
|
|
37
|
+
|
|
38
|
+
_ = UploadAsync();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async Task UploadAsync() {
|
|
42
|
+
try {
|
|
43
|
+
await Upload();
|
|
44
|
+
Debug.Log("[ClientFolderUploader] Upload completed successfully");
|
|
45
|
+
} catch (Exception ex) {
|
|
46
|
+
Debug.LogError($"[ClientFolderUploader] Upload failed: {ex.Message}");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async Task Upload() {
|
|
51
|
+
try {
|
|
52
|
+
var path = Path.GetDirectoryName(Application.consoleLogPath);
|
|
53
|
+
|
|
54
|
+
var zipPath = Path.Combine(Application.temporaryCachePath, "logs.zip");
|
|
55
|
+
if (File.Exists(zipPath)) {
|
|
56
|
+
File.Delete(zipPath);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var playerLogFile = Path.Combine(path, "Player.log");
|
|
60
|
+
var playerPrevLogFile = Path.Combine(path, "Player-prev.log");
|
|
61
|
+
var editorLogFile = Path.Combine(path, "Editor.log");
|
|
62
|
+
var editorPrevLogFile = Path.Combine(path, "Editor-prev.log");
|
|
63
|
+
|
|
64
|
+
Debug.Log("[ClientFolderUploader] Creating zip archive");
|
|
65
|
+
try {
|
|
66
|
+
using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) {
|
|
67
|
+
var fileExists = false;
|
|
68
|
+
if (File.Exists(playerLogFile)) {
|
|
69
|
+
fileExists = true;
|
|
70
|
+
Debug.Log("[ClientFolderUploader] Adding Player.log");
|
|
71
|
+
archive.CreateEntryFromFile(playerLogFile, "Player.log");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (File.Exists(playerPrevLogFile)) {
|
|
75
|
+
fileExists = true;
|
|
76
|
+
Debug.Log("[ClientFolderUploader] Adding Player-prev.log");
|
|
77
|
+
archive.CreateEntryFromFile(playerPrevLogFile, "Player-prev.log");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (File.Exists(editorLogFile)) {
|
|
81
|
+
fileExists = true;
|
|
82
|
+
Debug.Log("[ClientFolderUploader] Adding Editor.log");
|
|
83
|
+
archive.CreateEntryFromFile(editorLogFile, "Editor.log");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (File.Exists(editorPrevLogFile)) {
|
|
87
|
+
fileExists = true;
|
|
88
|
+
Debug.Log("[ClientFolderUploader] Adding Editor-prev.log");
|
|
89
|
+
archive.CreateEntryFromFile(editorPrevLogFile, "Editor-prev.log");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!fileExists) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (Exception ex) {
|
|
97
|
+
Debug.LogError($"[ClientFolderUploader] Zip creation failed: {ex.Message}");
|
|
98
|
+
throw;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
var contentType = "application/zip";
|
|
102
|
+
var contentLength = new FileInfo(zipPath).Length;
|
|
103
|
+
|
|
104
|
+
var body = new RequestBody {
|
|
105
|
+
type = "CLIENT_DEBUG_ARCHIVE",
|
|
106
|
+
name = "PlayerLogs.zip",
|
|
107
|
+
contentType = contentType,
|
|
108
|
+
contentLength = contentLength,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
Debug.Log("[ClientFolderUploader] Creating artifact");
|
|
112
|
+
var url = $"{AirshipPlatformUrl.contentService}/artifacts/platform/signed-url";
|
|
113
|
+
|
|
114
|
+
Http.HttpResponse res;
|
|
115
|
+
if (String.IsNullOrEmpty(InternalHttpManager.authToken)) {
|
|
116
|
+
res = await HttpManager.PostAsync(url, JsonUtility.ToJson(body));
|
|
117
|
+
} else {
|
|
118
|
+
res = await InternalHttpManager.PostAsync(url, JsonUtility.ToJson(body));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
122
|
+
Debug.LogError($"[ClientFolderUploader] Error response from server: {res.statusCode} - {res.error}");
|
|
123
|
+
throw new Exception($"Failed to get signed URL: {res.error}");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
var response = JsonUtility.FromJson<PostArtifactResponse>(res.data);
|
|
127
|
+
|
|
128
|
+
Debug.Log("[ClientFolderUploader] Uploading file");
|
|
129
|
+
await HttpManager.PutAsync(new RequestHelper() {
|
|
130
|
+
Uri = response.url,
|
|
131
|
+
BodyRaw = File.ReadAllBytes(zipPath),
|
|
132
|
+
Headers = new System.Collections.Generic.Dictionary<string, string>
|
|
133
|
+
{
|
|
134
|
+
{ "Content-Type", contentType },
|
|
135
|
+
},
|
|
136
|
+
}, "");
|
|
137
|
+
|
|
138
|
+
File.Delete(zipPath);
|
|
139
|
+
} catch (Exception ex) {
|
|
140
|
+
Debug.LogError($"[ClientFolderUploader] Error during upload process: {ex.Message}");
|
|
141
|
+
throw;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -8,7 +8,11 @@ public class ClientBootstrap : MonoBehaviour
|
|
|
8
8
|
private void Start()
|
|
9
9
|
{
|
|
10
10
|
if (RunCore.IsClient()) {
|
|
11
|
+
#if UNITY_STANDALONE_OSX
|
|
12
|
+
Application.targetFrameRate = -1;
|
|
13
|
+
#else
|
|
11
14
|
Application.targetFrameRate = (int)Math.Ceiling(Screen.currentResolution.refreshRateRatio.value);
|
|
15
|
+
#endif
|
|
12
16
|
Application.logMessageReceived += AnalyticsRecorder.RecordLogMessageToAnalytics;
|
|
13
17
|
}
|
|
14
18
|
}
|
|
@@ -76,6 +76,8 @@ namespace Code.Network.StateSystem
|
|
|
76
76
|
// Non-auth server command tracking
|
|
77
77
|
// Note: we also re-use some of the above command buffer fields
|
|
78
78
|
private SortedList<int, State> serverReceivedStateBuffer = new SortedList<int, State>();
|
|
79
|
+
// Last observed state from OnTick when running as a non-auth server.
|
|
80
|
+
private State serverLastObservedState;
|
|
79
81
|
|
|
80
82
|
// Client processing for input prediction
|
|
81
83
|
private State clientLastConfirmedState;
|
|
@@ -224,6 +226,12 @@ namespace Code.Network.StateSystem
|
|
|
224
226
|
simManager.OnSetSnapshot -= this.OnSetSnapshot;
|
|
225
227
|
simManager.OnCaptureSnapshot -= this.OnCaptureSnapshot;
|
|
226
228
|
simManager.OnLagCompensationCheck -= this.OnLagCompensationCheck;
|
|
229
|
+
|
|
230
|
+
this.OnClientReceiveSnapshot -= ClientReceiveSnapshot;
|
|
231
|
+
this.OnClientReceiveDiff -= ClientReceiveDiff;
|
|
232
|
+
this.OnServerReceiveSnapshot -= ServerReceiveSnapshot;
|
|
233
|
+
this.OnServerReceiveInput -= ServerReceiveInputCommand;
|
|
234
|
+
this.OnServerReceiveFullSnapshotRequest -= ServerReceiveFullSnapshotRequest;
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
private void SendNetworkMessages() {
|
|
@@ -810,20 +818,30 @@ namespace Code.Network.StateSystem
|
|
|
810
818
|
// not a shared timeline.
|
|
811
819
|
latestState.tick = tick;
|
|
812
820
|
latestState.time = time;
|
|
821
|
+
this.serverLastObservedState = latestState;
|
|
813
822
|
// Use this as the current state for the server
|
|
814
823
|
this.stateSystem.SetCurrentState(latestState);
|
|
815
824
|
// Since it's new, update our server interpolation functions
|
|
816
|
-
this.stateSystem.InterpolateReachedState(latestState);
|
|
817
|
-
// Add the state to our history as we would in a authoritative setup
|
|
818
|
-
this.stateHistory.Set(tick, latestState);
|
|
825
|
+
this.stateSystem.InterpolateReachedState(latestState);
|
|
819
826
|
}
|
|
820
827
|
}
|
|
821
828
|
|
|
822
|
-
public void NonAuthServerCaptureSnapshot(int tick, double time, bool replay)
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
829
|
+
public void NonAuthServerCaptureSnapshot(int tick, double time, bool replay) {
|
|
830
|
+
if (this.serverLastObservedState != null) {
|
|
831
|
+
// Non server auth will accept the client auth as the official position. No need to get
|
|
832
|
+
// the state after the physics tick as the position that was pulled from the buffer in OnTick
|
|
833
|
+
// is the official position.
|
|
834
|
+
var authState = this.serverLastObservedState.Clone() as State;
|
|
835
|
+
authState.tick = tick;
|
|
836
|
+
authState.time = time;
|
|
837
|
+
this.stateHistory.Set(tick, authState);
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
// If we haven't received any auth state from the client, just use what the server has so we have
|
|
841
|
+
// something to send to observers.
|
|
842
|
+
var currentState = this.stateSystem.GetCurrentState(this.serverLastProcessedCommandNumber, tick, time);
|
|
843
|
+
this.stateHistory.Set(tick, currentState);
|
|
844
|
+
}
|
|
827
845
|
}
|
|
828
846
|
|
|
829
847
|
public void NonAuthServerSetSnapshot(int tick)
|
|
@@ -977,7 +995,7 @@ namespace Code.Network.StateSystem
|
|
|
977
995
|
if (!this.observerHistory.GetAround(NetworkTime.time, out State prevState, out State nextState))
|
|
978
996
|
{
|
|
979
997
|
// if (clientTime < this.observerHistory.Keys[0]) return; // Our local time hasn't advanced enough to render the positions reported. No need to log debug
|
|
980
|
-
// Debug.LogWarning("Frame " + Time.frameCount + " not enough state history for rendering. " + this.observerHistory.Keys.Count +
|
|
998
|
+
// Debug.LogWarning("Frame " + Time.frameCount + " not enough state history for rendering " + this.name + ". " + this.observerHistory.Keys.Count +
|
|
981
999
|
// " entries. First " + this.observerHistory.Keys[0] + " Last " +
|
|
982
1000
|
// this.observerHistory.Keys[^1] + " Target " + NetworkTime.time + " Buffer is: " + NetworkClient.bufferTime + " Estimated Latency (1 way): " +
|
|
983
1001
|
// (NetworkTime.rtt / 2) + " TScale: " + Time.timeScale);
|
|
@@ -1177,7 +1195,7 @@ namespace Code.Network.StateSystem
|
|
|
1177
1195
|
// Debug.Log("Server received command " + command.commandNumber + " for " + this.gameObject.name);
|
|
1178
1196
|
// This should only occur if the server is authoritative.
|
|
1179
1197
|
if (!serverAuth) {
|
|
1180
|
-
Debug.LogWarning($"Received input command from {this.name}, but the networking mode is not server authoritative. Command will be ignored.");
|
|
1198
|
+
// Debug.LogWarning($"Received input command from {this.name}, but the networking mode is not server authoritative. Command will be ignored.");
|
|
1181
1199
|
return;
|
|
1182
1200
|
}
|
|
1183
1201
|
|
|
@@ -1198,8 +1216,8 @@ namespace Code.Network.StateSystem
|
|
|
1198
1216
|
// Reject commands if our buffer is full.
|
|
1199
1217
|
if (this.serverCommandBuffer.Count > this.serverCommandBufferMaxSize)
|
|
1200
1218
|
{
|
|
1201
|
-
Debug.LogWarning("Dropping command " + command.commandNumber +
|
|
1202
|
-
|
|
1219
|
+
// Debug.LogWarning("Dropping command " + command.commandNumber +
|
|
1220
|
+
// " for "+ this.name + " due to exceeding command buffer size. First command in buffer is " + this.serverCommandBuffer.Values[0]);
|
|
1203
1221
|
return;
|
|
1204
1222
|
}
|
|
1205
1223
|
|
|
@@ -28,8 +28,8 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
28
28
|
CharacterInputData> {
|
|
29
29
|
[FormerlySerializedAs("rigidbody")] public Rigidbody rb;
|
|
30
30
|
public Transform rootTransform;
|
|
31
|
-
public Transform airshipTransform; //The visual transform controlled by this script
|
|
32
|
-
public Transform graphicTransform; //A transform that games can animate
|
|
31
|
+
public Transform airshipTransform; // The visual transform controlled by this script. This always has the exact rotations used for movement
|
|
32
|
+
public Transform graphicTransform; // A transform that games can animate. This may have slightly altered rotation for visuals
|
|
33
33
|
public CharacterMovementSettings movementSettings;
|
|
34
34
|
public BoxCollider mainCollider;
|
|
35
35
|
|
|
@@ -63,6 +63,19 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
63
63
|
"Controls the speed in which characters in orbit camera rotate to face look direction. Degrees per second.")]
|
|
64
64
|
public float smoothedRotationSpeed = 360f;
|
|
65
65
|
|
|
66
|
+
[Tooltip("If true, the character body will automatically rotate in the direction of the look vector.")]
|
|
67
|
+
public bool rotateAutomatically = true;
|
|
68
|
+
|
|
69
|
+
[Tooltip("If enabled, the head will be rotated to look in the same direction as the look vector. The body will rotate only when needed. \"Rotate Automatically\" must also be checked.")]
|
|
70
|
+
public bool rotateHeadToLookVector = true;
|
|
71
|
+
|
|
72
|
+
[Tooltip("How much influence the look vector has on the look rotation.")]
|
|
73
|
+
[Range(0, 1)]
|
|
74
|
+
public float lookVectorInfluence = 0.4f;
|
|
75
|
+
|
|
76
|
+
[Tooltip("How far the head can rotate before the body rotates in degrees.")] [Range(0, 180)]
|
|
77
|
+
public int headRotationThreshold = 60;
|
|
78
|
+
|
|
66
79
|
[Tooltip(
|
|
67
80
|
"If true animations will be played on the server. This should be true if you care about character movement animations server-side (like for hit boxes).")]
|
|
68
81
|
public bool playAnimationOnServer = true;
|
|
@@ -71,6 +84,8 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
71
84
|
|
|
72
85
|
private CharacterPhysics physics;
|
|
73
86
|
private Transform _cameraTransform;
|
|
87
|
+
private CharacterRig _rig;
|
|
88
|
+
private Quaternion initialHeadRotation;
|
|
74
89
|
private bool _smoothLookVector = false;
|
|
75
90
|
|
|
76
91
|
/**
|
|
@@ -209,6 +224,19 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
209
224
|
physics = new CharacterPhysics(this);
|
|
210
225
|
}
|
|
211
226
|
|
|
227
|
+
_rig = GetComponentInChildren<CharacterRig>();
|
|
228
|
+
|
|
229
|
+
// Verify rig is setup correctly for rotateHeadToLookVector
|
|
230
|
+
if (rotateHeadToLookVector) {
|
|
231
|
+
if (_rig == null || _rig.head == null || _rig.spine == null) {
|
|
232
|
+
rotateHeadToLookVector = false;
|
|
233
|
+
Debug.LogWarning(
|
|
234
|
+
"[CharacterMovement] Disabling rotateHeadToLookVector, cannot find rig's head transform.");
|
|
235
|
+
} else {
|
|
236
|
+
initialHeadRotation = _rig.head.rotation;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
212
240
|
_cameraTransform = Camera.main.transform;
|
|
213
241
|
}
|
|
214
242
|
|
|
@@ -274,14 +302,9 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
274
302
|
if (!rb.isKinematic) {
|
|
275
303
|
rb.linearVelocity = snapshot.velocity;
|
|
276
304
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
lookTarget = new Vector3(0, 0, .01f);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
airshipTransform.rotation = Quaternion.LookRotation(lookTarget);
|
|
284
|
-
|
|
305
|
+
|
|
306
|
+
HandleCharacterRotation(snapshot.lookVector);
|
|
307
|
+
|
|
285
308
|
OnSetSnapshot?.Invoke(snapshot);
|
|
286
309
|
}
|
|
287
310
|
|
|
@@ -370,13 +393,9 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
370
393
|
var rootPosition = rb.position;
|
|
371
394
|
|
|
372
395
|
// Apply rotation when ticking on the server. This rotation is automatically applied on the owning client in LateUpdate.
|
|
396
|
+
// and for observers in Interpolate()
|
|
373
397
|
if (isServer && !isClient) {
|
|
374
|
-
|
|
375
|
-
if (lookTarget == Vector3.zero) {
|
|
376
|
-
lookTarget = new Vector3(0, 0, .01f);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
airshipTransform.rotation = Quaternion.LookRotation(lookTarget);
|
|
398
|
+
HandleCharacterRotation(command.lookVector);
|
|
380
399
|
}
|
|
381
400
|
|
|
382
401
|
//Ground checks
|
|
@@ -554,7 +573,7 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
554
573
|
var isMoving = currentVelocity.sqrMagnitude > .1f;
|
|
555
574
|
var inAir = didJump || (!detectedGround && !currentMoveSnapshot.prevStepUp);
|
|
556
575
|
var tryingToSprint = movementSettings.onlySprintForward
|
|
557
|
-
? command.sprint &&
|
|
576
|
+
? command.sprint && airshipTransform.InverseTransformVector(command.moveDir).z > 0.1f
|
|
558
577
|
: //Only sprint if you are moving forward
|
|
559
578
|
command.sprint && command.moveDir.magnitude > 0.1f; //Only sprint if you are moving
|
|
560
579
|
|
|
@@ -1422,11 +1441,6 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
1422
1441
|
newLook.z = 0.01f;
|
|
1423
1442
|
}
|
|
1424
1443
|
|
|
1425
|
-
airshipTransform.rotation = Quaternion.Lerp(
|
|
1426
|
-
Quaternion.LookRotation(oldLook),
|
|
1427
|
-
Quaternion.LookRotation(newLook),
|
|
1428
|
-
(float)delta);
|
|
1429
|
-
|
|
1430
1444
|
lookVector = Vector3.Lerp(snapshotOld.lookVector, snapshotNew.lookVector, (float)delta);
|
|
1431
1445
|
|
|
1432
1446
|
OnInterpolateState?.Invoke(snapshotOld, snapshotNew, delta);
|
|
@@ -1459,22 +1473,104 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
1459
1473
|
}
|
|
1460
1474
|
|
|
1461
1475
|
public void LateUpdate() {
|
|
1462
|
-
// We only update rotation in late update if we are running on a client
|
|
1463
|
-
// this system
|
|
1476
|
+
// We only update rotation in late update if we are running on a client
|
|
1464
1477
|
if (isServer && !isClient) {
|
|
1465
1478
|
return;
|
|
1466
1479
|
}
|
|
1467
1480
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
}
|
|
1481
|
+
HandleCharacterRotation(this.lookVector);
|
|
1482
|
+
}
|
|
1471
1483
|
|
|
1472
|
-
|
|
1484
|
+
/**
|
|
1485
|
+
* Internal character rotation handling. Used in a few places to kick off updating the character's
|
|
1486
|
+
* transform and visual rotation based on the configuration of the character.
|
|
1487
|
+
*/
|
|
1488
|
+
private void HandleCharacterRotation(Vector3 lookVector) {
|
|
1489
|
+
if (!rotateAutomatically) return;
|
|
1490
|
+
UpdateCharacterRotation(lookVector);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* This function allows you to set the rotation of the character body and head when "rotateAutomatically" is turned off.
|
|
1495
|
+
* It will use the specified look direction and respect configured visual settings. This function should be called
|
|
1496
|
+
* in LateUpdate every frame with an updated look direction.
|
|
1497
|
+
*
|
|
1498
|
+
* Calling this only once will update the head and body rotation just enough to look in the specified direction.
|
|
1499
|
+
* It will not align the rotation exactly. If you want to align the rotation exactly. First set the airshipTransform
|
|
1500
|
+
* and graphicsHolder transforms to the desired look direction before calling this function. You can also use
|
|
1501
|
+
* UpdateHeadRotation to set a look direction for the head directly.
|
|
1502
|
+
*
|
|
1503
|
+
* This function is not networked.
|
|
1504
|
+
*/
|
|
1505
|
+
public void UpdateCharacterRotation(Vector3 lookDirection) {
|
|
1506
|
+
var lookTarget = new Vector3(lookDirection.x, 0, lookDirection.z);
|
|
1473
1507
|
if (lookTarget == Vector3.zero) {
|
|
1474
1508
|
lookTarget = new Vector3(0, 0, .01f);
|
|
1475
1509
|
}
|
|
1476
1510
|
|
|
1477
|
-
|
|
1511
|
+
// Debug.Log("Handle rotation: " + rotateHeadToLookVector);
|
|
1512
|
+
if (!rotateHeadToLookVector) {
|
|
1513
|
+
airshipTransform.rotation = Quaternion.LookRotation(lookTarget).normalized;
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
UpdateBodyRotation(lookTarget);
|
|
1518
|
+
UpdateHeadRotation(lookDirection);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
private void UpdateBodyRotation(Vector3 direction) {
|
|
1522
|
+
direction.y = 0; // Don't rotate the character off the ground
|
|
1523
|
+
|
|
1524
|
+
// If we are moving, start rotating towards the correct direction immediately. Don't negate any additional rotation
|
|
1525
|
+
if (this.currentMoveSnapshot.velocity.magnitude > 0) {
|
|
1526
|
+
airshipTransform.rotation = Quaternion.LookRotation(direction).normalized;
|
|
1527
|
+
graphicTransform.rotation = Quaternion.Slerp(graphicTransform.rotation, airshipTransform.rotation, smoothedRotationSpeed * Mathf.Deg2Rad * Time.deltaTime);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Since graphicTransform is a child of the airship transform, we "undo" the
|
|
1532
|
+
// change we are going to apply so that we can rotate the graphicTransform independently
|
|
1533
|
+
Quaternion previousParentRotation = airshipTransform.rotation;
|
|
1534
|
+
airshipTransform.rotation = Quaternion.LookRotation(direction).normalized;
|
|
1535
|
+
Quaternion deltaRotation = airshipTransform.rotation * Quaternion.Inverse(previousParentRotation);
|
|
1536
|
+
graphicTransform.rotation = Quaternion.Inverse(deltaRotation) * graphicTransform.rotation;
|
|
1537
|
+
|
|
1538
|
+
// Now calculate if we need to rotate the graphicTransform (body) or if the head
|
|
1539
|
+
// rotation will be enough.
|
|
1540
|
+
Vector3 currentForward = graphicTransform.rotation * Vector3.forward;
|
|
1541
|
+
currentForward.y = 0;
|
|
1542
|
+
|
|
1543
|
+
currentForward.Normalize();
|
|
1544
|
+
direction.Normalize();
|
|
1545
|
+
|
|
1546
|
+
float angle = Vector3.SignedAngle(currentForward, direction, Vector3.up);
|
|
1547
|
+
if (Mathf.Abs(angle) > headRotationThreshold)
|
|
1548
|
+
{
|
|
1549
|
+
float rotateAmount = Mathf.Abs(angle) - headRotationThreshold;
|
|
1550
|
+
float sign = Mathf.Sign(angle);
|
|
1551
|
+
|
|
1552
|
+
// We only rotate just enough to allow us to not snap our neck, but don't rotate the body
|
|
1553
|
+
// any more than that.
|
|
1554
|
+
Quaternion partialRotation = Quaternion.AngleAxis(rotateAmount * sign, Vector3.up);
|
|
1555
|
+
graphicTransform.rotation = partialRotation * graphicTransform.rotation;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Sets the head look direction independently of the body using "Look Vector Influence". Does _NOT_ limit the
|
|
1561
|
+
* amount of rotation from the body forward direction. Use UpdateCharacterRotation for rotation that take the configured
|
|
1562
|
+
* limits into account.
|
|
1563
|
+
*
|
|
1564
|
+
* This function is not networked.
|
|
1565
|
+
*/
|
|
1566
|
+
public void UpdateHeadRotation(Vector3 direction) {
|
|
1567
|
+
if (direction.magnitude == 0) {
|
|
1568
|
+
direction = new Vector3(0, 0, 0.01f);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
var characterUp = _rig.spine ? _rig.spine.up : Vector3.up;
|
|
1572
|
+
var lerpedLookRotation = Vector3.Lerp(Quaternion.Inverse(initialHeadRotation) * _rig.head.rotation * Vector3.forward, direction, lookVectorInfluence);
|
|
1573
|
+
_rig.head.rotation = initialHeadRotation * Quaternion.LookRotation(lerpedLookRotation, characterUp);
|
|
1478
1574
|
}
|
|
1479
1575
|
|
|
1480
1576
|
public void Update() {
|
|
@@ -1548,7 +1644,7 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
1548
1644
|
moveDirInput = moveDir;
|
|
1549
1645
|
break;
|
|
1550
1646
|
case MoveDirectionMode.Character:
|
|
1551
|
-
moveDirInput =
|
|
1647
|
+
moveDirInput = airshipTransform.TransformDirection(moveDir);
|
|
1552
1648
|
break;
|
|
1553
1649
|
case MoveDirectionMode.Camera:
|
|
1554
1650
|
var forwardZeroY = _cameraTransform.forward;
|
|
@@ -1625,7 +1721,7 @@ namespace Code.Player.Character.MovementSystems.Character {
|
|
|
1625
1721
|
if (mode == NetworkedStateSystemMode.Input || (mode == NetworkedStateSystemMode.Authority && isClient)) {
|
|
1626
1722
|
if (_smoothLookVector && moveDirInput != Vector3.zero) {
|
|
1627
1723
|
lookVector = Vector3.RotateTowards(
|
|
1628
|
-
|
|
1724
|
+
airshipTransform.forward,
|
|
1629
1725
|
moveDirInput.normalized,
|
|
1630
1726
|
smoothedRotationSpeed * Mathf.Deg2Rad * Time.deltaTime,
|
|
1631
1727
|
0f
|