http://www.xuanyusong.com/archives/1405
之前MOMO在 Unity3D研究院之角色控制器组件研究(二十二)文章中向大家介绍了角色控制器组件。默认系统提供了JavaScript脚本的支持,可是我们还是喜欢用C#来编写脚本,本篇文章MOMO将把角色控制器的所有脚本全部改成C#语言。方便自己也方便大家学习,哇咔咔。首先,我们将角色控制器包引入工程中。如下图所示,默认提供的脚本除了MouseLook以外其它的都是JS脚本,本篇文章MOMO将把它们全部修改成C#。刚好也是答应Unity圣典的站长录制游戏开发视频,视频中我说下一节我将教大家怎么把角色控制器组件的脚本全部改成C#。
首先把CharacterMotor.js修改成C# 它主要设置角色控制的系数,如运动、跳跃、移动、滑动等。第一人称与第三人称主角模型的移动与旋转的角度都最后都是在这里计算的,请大家好好看看这个类, 尤其是UpdateFunction()方法。
CharacterMotor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
|
using
UnityEngine
;
using
System
.
Collections
;
/**
* @Author : www.xuanyusong.com
*/
[
RequireComponent
(
typeof
(
CharacterController
)
)
]
[
AddComponentMenu
(
"Character/Character Motor"
)
]
public
class
CharacterMotor
:
MonoBehaviour
{
// Does this script currently respond to input?
public
bool
canControl
=
true
;
public
bool
useFixedUpdate
=
true
;
// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
// Very handy for organization!
// The current global direction we want the character to move in.
[
System
.
NonSerialized
]
public
Vector3
inputMoveDirection
=
Vector3
.
zero
;
// Is the jump button held down? We use this interface instead of checking
// for the jump button directly so this script can also be used by AIs.
[
System
.
NonSerialized
]
public
bool
inputJump
=
false
;
[
System
.
Serializable
]
public
class
CharacterMotorMovement
{
// The maximum horizontal speed when moving
public
float
maxForwardSpeed
=
10.0f
;
public
float
maxSidewaysSpeed
=
10.0f
;
public
float
maxBackwardsSpeed
=
10.0f
;
// Curve for multiplying speed based on slope (negative = downwards)
public
AnimationCurve
slopeSpeedMultiplier
=
new
AnimationCurve
(
new
Keyframe
(
-
90
,
1
)
,
new
Keyframe
(
0
,
1
)
,
new
Keyframe
(
90
,
0
)
)
;
// How fast does the character change speeds? Higher is faster.
public
float
maxGroundAcceleration
=
30.0f
;
public
float
maxAirAcceleration
=
20.0f
;
// The gravity for the character
public
float
gravity
=
10.0f
;
public
float
maxFallSpeed
=
20.0f
;
// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
// Very handy for organization!
// The last collision flags returned from controller.Move
[
System
.
NonSerialized
]
public
CollisionFlags
collisionFlags
;
// We will keep track of the character's current velocity,
[
System
.
NonSerialized
]
public
Vector3
velocity
;
// This keeps track of our current velocity while we're not grounded
[
System
.
NonSerialized
]
public
Vector3
frameVelocity
=
Vector3
.
zero
;
[
System
.
NonSerialized
]
public
Vector3
hitPoint
=
Vector3
.
zero
;
[
System
.
NonSerialized
]
public
Vector3
lastHitPoint
=
new
Vector3
(
Mathf
.
Infinity
,
0
,
0
)
;
}
public
CharacterMotorMovement
movement
=
new
CharacterMotorMovement
(
)
;
public
enum
MovementTransferOnJump
{
None
,
// The jump is not affected by velocity of floor at all.
InitTransfer
,
// Jump gets its initial velocity from the floor, then gradualy comes to a stop.
PermaTransfer
,
// Jump gets its initial velocity from the floor, and keeps that velocity until landing.
PermaLocked
// Jump is relative to the movement of the last touched floor and will move together with that floor.
}
// We will contain all the jumping related variables in one helper class for clarity.
[
System
.
Serializable
]
public
class
CharacterMotorJumping
{
// Can the character jump?
public
bool
enabled
=
true
;
// How high do we jump when pressing jump and letting go immediately
public
float
baseHeight
=
1.0f
;
// We add extraHeight units (meters) on top when holding the button down longer while jumping
public
float
extraHeight
=
4.1f
;
// How much does the character jump out perpendicular to the surface on walkable surfaces?
// 0 means a fully vertical jump and 1 means fully perpendicular.
public
float
perpAmount
=
0.0f
;
// How much does the character jump out perpendicular to the surface on too steep surfaces?
// 0 means a fully vertical jump and 1 means fully perpendicular.
public
float
steepPerpAmount
=
0.5f
;
// For the next variables, @System.NonSerialized tells Unity to not serialize the variable or show it in the inspector view.
// Very handy for organization!
// Are we jumping? (Initiated with jump button and not grounded yet)
// To see if we are just in the air (initiated by jumping OR falling) see the grounded variable.
[
System
.
NonSerialized
]
public
bool
jumping
=
false
;
[
System
.
NonSerialized
]
public
bool
holdingJumpButton
=
false
;
// the time we jumped at (Used to determine for how long to apply extra jump power after jumping.)
[
System
.
NonSerialized
]
public
float
lastStartTime
=
0.0f
;
[
System
.
NonSerialized
]
public
float
lastButtonDownTime
=
-
100f
;
[
System
.
NonSerialized
]
public
Vector3
jumpDir
=
Vector3
.
up
;
}
public
CharacterMotorJumping
jumping
=
new
CharacterMotorJumping
(
)
;
[
System
.
Serializable
]
public
class
CharacterMotorMovingPlatform
{
public
bool
enabled
=
true
;
public
MovementTransferOnJump
movementTransfer
=
MovementTransferOnJump
.
PermaTransfer
;
[
System
.
NonSerialized
]
public
Transform
hitPlatform
;
[
System
.
NonSerialized
]
public
Transform
activePlatform
;
[
System
.
NonSerialized
]
public
Vector3
activeLocalPoint
;
[
System
.
NonSerialized
]
public
Vector3
activeGlobalPoint
;
[
System
.
NonSerialized
]
public
Quaternion
activeLocalRotation
;
[
System
.
NonSerialized
]
public
Quaternion
activeGlobalRotation
;
[
System
.
NonSerialized
]
public
Matrix4x4
lastMatrix
;
[
System
.
NonSerialized
]
public
Vector3
platformVelocity
;
[
System
.
NonSerialized
]
public
bool
newPlatform
;
}
public
CharacterMotorMovingPlatform
movingPlatform
=
new
CharacterMotorMovingPlatform
(
)
;
[
System
.
Serializable
]
public
class
CharacterMotorSliding
{
// Does the character slide on too steep surfaces?
public
bool
enabled
=
true
;
// How fast does the character slide on steep surfaces?
public
float
slidingSpeed
=
15f
;
// How much can the player control the sliding direction?
// If the value is 0.5 the player can slide sideways with half the speed of the downwards sliding speed.
public
float
sidewaysControl
=
1.0f
;
// How much can the player influence the sliding speed?
// If the value is 0.5 the player can speed the sliding up to 150% or slow it down to 50%.
public
float
speedControl
=
0.4f
;
}
public
CharacterMotorSliding
sliding
=
new
CharacterMotorSliding
(
)
;
[
System
.
NonSerialized
]
public
bool
grounded
=
true
;
[
System
.
NonSerialized
]
public
Vector3
groundNormal
=
Vector3
.
zero
;
private
Vector3
lastGroundNormal
=
Vector3
.
zero
;
private
Transform
tr
;
private
CharacterController
controller
;
void
Awake
(
)
{
controller
=
GetComponent
<
CharacterController
>
(
)
;
tr
=
transform
;
}
private
void
UpdateFunction
(
)
{
// We copy the actual velocity into a temporary variable that we can manipulate.
Vector3
velocity
=
movement
.
velocity
;
// Update velocity based on input
velocity
=
ApplyInputVelocityChange
(
velocity
)
;
// Apply gravity and jumping force
velocity
=
ApplyGravityAndJumping
(
velocity
)
;
// Moving platform support
Vector3
moveDistance
=
Vector3
.
zero
;
if
(
MoveWithPlatform
(
)
)
{
Vector3
newGlobalPoint
=
movingPlatform
.
activePlatform
.
TransformPoint
(
movingPlatform
.
activeLocalPoint
)
;
moveDistance
=
(
newGlobalPoint
-
movingPlatform
.
activeGlobalPoint
)
;
if
(
moveDistance
!=
Vector3
.
zero
)
controller
.
Move
(
moveDistance
)
;
// Support moving platform rotation as well:
Quaternion
newGlobalRotation
=
movingPlatform
.
activePlatform
.
rotation
*
movingPlatform
.
activeLocalRotation
;
Quaternion
rotationDiff
=
newGlobalRotation
*
Quaternion
.
Inverse
(
movingPlatform
.
activeGlobalRotation
)
;
var
yRotation
=
rotationDiff
.
eulerAngles
.
y
;
if
(
yRotation
!=
0
)
{
// Prevent rotation of the local up vector
tr
.
Rotate
(
0
,
yRotation
,
0
)
;
}
}
// Save lastPosition for velocity calculation.
Vector3
lastPosition
=
tr
.
position
;
// We always want the movement to be framerate independent. Multiplying by Time.deltaTime does this.
Vector3
currentMovementOffset
=
velocity
*
Time
.
deltaTime
;
// Find out how much we need to push towards the ground to avoid loosing grouning
// when walking down a step or over a sharp change in slope.
float
pushDownOffset
=
Mathf
.
Max
(
controller
.
stepOffset
,
new
Vector3
(
currentMovementOffset
.
x
,
0
,
currentMovementOffset
.
z
)
.
magnitude
)
;
if
(
grounded
)
currentMovementOffset
-=
pushDownOffset
*
Vector3
.
up
;
// Reset variables that will be set by collision function
movingPlatform
.
hitPlatform
=
null
;
groundNormal
=
Vector3
.
zero
;
// Move our character!
movement
.
collisionFlags
=
controller
.
Move
(
currentMovementOffset
)
;
movement
.
lastHitPoint
=
movement
.
hitPoint
;
lastGroundNormal
=
groundNormal
;
if
(
movingPlatform
.
enabled
&&
movingPlatform
.
activePlatform
!=
movingPlatform
.
hitPlatform
)
{
if
(
movingPlatform
.
hitPlatform
!=
null
)
{
movingPlatform
.
activePlatform
=
movingPlatform
.
hitPlatform
;
movingPlatform
.
lastMatrix
=
movingPlatform
.
hitPlatform
.
localToWorldMatrix
;
movingPlatform
.
newPlatform
=
true
;
}
}
// Calculate the velocity based on the current and previous position.
// This means our velocity will only be the amount the character actually moved as a result of collisions.
Vector3
oldHVelocity
=
new
Vector3
(
velocity
.
x
,
0
,
velocity
.
z
)
;
movement
.
velocity
=
(
tr
.
position
-
lastPosition
)
/
Time
.
deltaTime
;
Vector3
newHVelocity
=
new
Vector3
(
movement
.
velocity
.
x
,
0
,
movement
.
velocity
.
z
)
;
// The CharacterController can be moved in unwanted directions when colliding with things.
// We want to prevent this from influencing the recorded velocity.
if
(
oldHVelocity
==
Vector3
.
zero
)
{
movement
.
velocity
=
new
Vector3
(
0
,
movement
.
velocity
.
y
,
0
)
;
}
else
{
float
projectedNewVelocity
=
Vector3
.
Dot
(
newHVelocity
,
oldHVelocity
)
/
oldHVelocity
.
sqrMagnitude
;
movement
.
velocity
=
oldHVelocity
*
Mathf
.
Clamp01
(
projectedNewVelocity
)
+
movement
.
velocity
.
y
*
Vector3
.
up
;
}
if
(
movement
.
velocity
.
y
<
velocity
.
y
-
0.001
)
{
if
(
movement
.
velocity
.
y
<
0
)
{
// Something is forcing the CharacterController down faster than it should.
// Ignore this
movement
.
velocity
.
y
=
velocity
.
y
;
}
else
{
// The upwards movement of the CharacterController has been blocked.
// This is treated like a ceiling collision - stop further jumping here.
jumping
.
holdingJumpButton
=
false
;
}
}
// We were grounded but just loosed grounding
if
(
grounded
&&
!
IsGroundedTest
(
)
)
{
grounded
=
false
;
// Apply inertia from platform
if
(
movingPlatform
.
enabled
&&
(
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
InitTransfer
||
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
PermaTransfer
)
)
{
movement
.
frameVelocity
=
movingPlatform
.
platformVelocity
;
movement
.
velocity
+=
movingPlatform
.
platformVelocity
;
}
SendMessage
(
"OnFall"
,
SendMessageOptions
.
DontRequireReceiver
)
;
// We pushed the character down to ensure it would stay on the ground if there was any.
// But there wasn't so now we cancel the downwards offset to make the fall smoother.
tr
.
position
+=
pushDownOffset
*
Vector3
.
up
;
}
// We were not grounded but just landed on something
else
if
(
!
grounded
&&
IsGroundedTest
(
)
)
{
grounded
=
true
;
jumping
.
jumping
=
false
;
SubtractNewPlatformVelocity
(
)
;
SendMessage
(
"OnLand"
,
SendMessageOptions
.
DontRequireReceiver
)
;
}
// Moving platforms support
if
(
MoveWithPlatform
(
)
)
{
// Use the center of the lower half sphere of the capsule as reference point.
// This works best when the character is standing on moving tilting platforms.
movingPlatform
.
activeGlobalPoint
=
tr
.
position
+
Vector3
.
up
*
(
controller
.
center
.
y
-
controller
.
height
*
0.5f
+
controller
.
radius
)
;
movingPlatform
.
activeLocalPoint
=
movingPlatform
.
activePlatform
.
InverseTransformPoint
(
movingPlatform
.
activeGlobalPoint
)
;
// Support moving platform rotation as well:
movingPlatform
.
activeGlobalRotation
=
tr
.
rotation
;
movingPlatform
.
activeLocalRotation
=
Quaternion
.
Inverse
(
movingPlatform
.
activePlatform
.
rotation
)
*
movingPlatform
.
activeGlobalRotation
;
}
}
void
FixedUpdate
(
)
{
if
(
movingPlatform
.
enabled
)
{
if
(
movingPlatform
.
activePlatform
!=
null
)
{
if
(
!
movingPlatform
.
newPlatform
)
{
Vector3
lastVelocity
=
movingPlatform
.
platformVelocity
;
movingPlatform
.
platformVelocity
=
(
movingPlatform
.
activePlatform
.
localToWorldMatrix
.
MultiplyPoint3x4
(
movingPlatform
.
activeLocalPoint
)
-
movingPlatform
.
lastMatrix
.
MultiplyPoint3x4
(
movingPlatform
.
activeLocalPoint
)
)
/
Time
.
deltaTime
;
}
movingPlatform
.
lastMatrix
=
movingPlatform
.
activePlatform
.
localToWorldMatrix
;
movingPlatform
.
newPlatform
=
false
;
}
else
{
movingPlatform
.
platformVelocity
=
Vector3
.
zero
;
}
}
if
(
useFixedUpdate
)
UpdateFunction
(
)
;
}
void
Update
(
)
{
if
(
!
useFixedUpdate
)
UpdateFunction
(
)
;
}
private
Vector3
ApplyInputVelocityChange
(
Vector3
velocity
)
{
if
(
!
canControl
)
inputMoveDirection
=
Vector3
.
zero
;
// Find desired velocity
Vector3
desiredVelocity
;
if
(
grounded
&&
TooSteep
(
)
)
{
// The direction we're sliding in
desiredVelocity
=
new
Vector3
(
groundNormal
.
x
,
0
,
groundNormal
.
z
)
.
normalized
;
// Find the input movement direction projected onto the sliding direction
var
projectedMoveDir
=
Vector3
.
Project
(
inputMoveDirection
,
desiredVelocity
)
;
// Add the sliding direction, the spped control, and the sideways control vectors
desiredVelocity
=
desiredVelocity
+
projectedMoveDir
*
sliding
.
speedControl
+
(
inputMoveDirection
-
projectedMoveDir
)
*
sliding
.
sidewaysControl
;
// Multiply with the sliding speed
desiredVelocity
*=
sliding
.
slidingSpeed
;
}
else
desiredVelocity
=
GetDesiredHorizontalVelocity
(
)
;
if
(
movingPlatform
.
enabled
&&
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
PermaTransfer
)
{
desiredVelocity
+=
movement
.
frameVelocity
;
desiredVelocity
.
y
=
0
;
}
if
(
grounded
)
desiredVelocity
=
AdjustGroundVelocityToNormal
(
desiredVelocity
,
groundNormal
)
;
else
velocity
.
y
=
0
;
// Enforce max velocity change
float
maxVelocityChange
=
GetMaxAcceleration
(
grounded
)
*
Time
.
deltaTime
;
Vector3
velocityChangeVector
=
(
desiredVelocity
-
velocity
)
;
if
(
velocityChangeVector
.
sqrMagnitude
>
maxVelocityChange
*
maxVelocityChange
)
{
velocityChangeVector
=
velocityChangeVector
.
normalized
*
maxVelocityChange
;
}
// If we're in the air and don't have control, don't apply any velocity change at all.
// If we're on the ground and don't have control we do apply it - it will correspond to friction.
if
(
grounded
||
canControl
)
velocity
+=
velocityChangeVector
;
if
(
grounded
)
{
// When going uphill, the CharacterController will automatically move up by the needed amount.
// Not moving it upwards manually prevent risk of lifting off from the ground.
// When going downhill, DO move down manually, as gravity is not enough on steep hills.
velocity
.
y
=
Mathf
.
Min
(
velocity
.
y
,
0
)
;
}
return
velocity
;
}
private
Vector3
ApplyGravityAndJumping
(
Vector3
velocity
)
{
if
(
!
inputJump
||
!
canControl
)
{
jumping
.
holdingJumpButton
=
false
;
jumping
.
lastButtonDownTime
=
-
100
;
}
if
(
inputJump
&&
jumping
.
lastButtonDownTime
<
0
&&
canControl
)
jumping
.
lastButtonDownTime
=
Time
.
time
;
if
(
grounded
)
velocity
.
y
=
Mathf
.
Min
(
0
,
velocity
.
y
)
-
movement
.
gravity
*
Time
.
deltaTime
;
else
{
velocity
.
y
=
movement
.
velocity
.
y
-
movement
.
gravity
*
Time
.
deltaTime
;
// When jumping up we don't apply gravity for some time when the user is holding the jump button.
// This gives more control over jump height by pressing the button longer.
if
(
jumping
.
jumping
&&
jumping
.
holdingJumpButton
)
{
// Calculate the duration that the extra jump force should have effect.
// If we're still less than that duration after the jumping time, apply the force.
if
(
Time
.
time
<
jumping
.
lastStartTime
+
jumping
.
extraHeight
/
CalculateJumpVerticalSpeed
(
jumping
.
baseHeight
)
)
{
// Negate the gravity we just applied, except we push in jumpDir rather than jump upwards.
velocity
+=
jumping
.
jumpDir
*
movement
.
gravity
*
Time
.
deltaTime
;
}
}
// Make sure we don't fall any faster than maxFallSpeed. This gives our character a terminal velocity.
velocity
.
y
=
Mathf
.
Max
(
velocity
.
y
,
-
movement
.
maxFallSpeed
)
;
}
if
(
grounded
)
{
// Jump only if the jump button was pressed down in the last 0.2 seconds.
// We use this check instead of checking if it's pressed down right now
// because players will often try to jump in the exact moment when hitting the ground after a jump
// and if they hit the button a fraction of a second too soon and no new jump happens as a consequence,
// it's confusing and it feels like the game is buggy.
if
(
jumping
.
enabled
&&
canControl
&&
(
Time
.
time
-
jumping
.
lastButtonDownTime
<
0.2
)
)
{
grounded
=
false
;
jumping
.
jumping
=
true
;
jumping
.
lastStartTime
=
Time
.
time
;
jumping
.
lastButtonDownTime
=
-
100
;
jumping
.
holdingJumpButton
=
true
;
// Calculate the jumping direction
if
(
TooSteep
(
)
)
jumping
.
jumpDir
=
Vector3
.
Slerp
(
Vector3
.
up
,
groundNormal
,
jumping
.
steepPerpAmount
)
;
else
jumping
.
jumpDir
=
Vector3
.
Slerp
(
Vector3
.
up
,
groundNormal
,
jumping
.
perpAmount
)
;
// Apply the jumping force to the velocity. Cancel any vertical velocity first.
velocity
.
y
=
0
;
velocity
+=
jumping
.
jumpDir
*
CalculateJumpVerticalSpeed
(
jumping
.
baseHeight
)
;
// Apply inertia from platform
if
(
movingPlatform
.
enabled
&&
(
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
InitTransfer
||
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
PermaTransfer
)
)
{
movement
.
frameVelocity
=
movingPlatform
.
platformVelocity
;
velocity
+=
movingPlatform
.
platformVelocity
;
}
SendMessage
(
"OnJump"
,
SendMessageOptions
.
DontRequireReceiver
)
;
}
else
{
jumping
.
holdingJumpButton
=
false
;
}
}
return
velocity
;
}
void
OnControllerColliderHit
(
ControllerColliderHit
hit
)
{
if
(
hit
.
normal
.
y
>
0
&&
hit
.
normal
.
y
>
groundNormal
.
y
&&
hit
.
moveDirection
.
y
<
0
)
{
if
(
(
hit
.
point
-
movement
.
lastHitPoint
)
.
sqrMagnitude
>
0.001
||
lastGroundNormal
==
Vector3
.
zero
)
groundNormal
=
hit
.
normal
;
else
groundNormal
=
lastGroundNormal
;
movingPlatform
.
hitPlatform
=
hit
.
collider
.
transform
;
movement
.
hitPoint
=
hit
.
point
;
movement
.
frameVelocity
=
Vector3
.
zero
;
}
}
private
IEnumerator
SubtractNewPlatformVelocity
(
)
{
// When landing, subtract the velocity of the new ground from the character's velocity
// since movement in ground is relative to the movement of the ground.
if
(
movingPlatform
.
enabled
&&
(
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
InitTransfer
||
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
PermaTransfer
)
)
{
// If we landed on a new platform, we have to wait for two FixedUpdates
// before we know the velocity of the platform under the character
if
(
movingPlatform
.
newPlatform
)
{
Transform
platform
=
movingPlatform
.
activePlatform
;
yield
return
new
WaitForFixedUpdate
(
)
;
yield
return
new
WaitForFixedUpdate
(
)
;
if
(
grounded
&&
platform
==
movingPlatform
.
activePlatform
)
yield
return
1
;
}
movement
.
velocity
-=
movingPlatform
.
platformVelocity
;
}
}
private
bool
MoveWithPlatform
(
)
{
return
(
movingPlatform
.
enabled
&&
(
grounded
||
movingPlatform
.
movementTransfer
==
MovementTransferOnJump
.
PermaLocked
)
&&
movingPlatform
.
activePlatform
!=
null
)
;
}
private
Vector3
GetDesiredHorizontalVelocity
(
)
{
// Find desired velocity
Vector3
desiredLocalDirection
=
tr
.
InverseTransformDirection
(
inputMoveDirection
)
;
float
maxSpeed
=
MaxSpeedInDirection
(
desiredLocalDirection
)
;
if
(
grounded
)
{
// Modify max speed on slopes based on slope speed multiplier curve
var
movementSlopeAngle
=
Mathf
.
Asin
(
movement
.
velocity
.
normalized
.
y
)
*
Mathf
.
Rad2Deg
;
maxSpeed
*=
movement
.
slopeSpeedMultiplier
.
Evaluate
(
movementSlopeAngle
)
;
}
return
tr
.
TransformDirection
(
desiredLocalDirection
*
maxSpeed
)
;
}
private
Vector3
AdjustGroundVelocityToNormal
(
Vector3
hVelocity
,
Vector3
groundNormal
)
{
Vector3
sideways
=
Vector3
.
Cross
(
Vector3
.
up
,
hVelocity
)
;
return
Vector3
.
Cross
(
sideways
,
groundNormal
)
.
normalized
*
hVelocity
.
magnitude
;
}
private
bool
IsGroundedTest
(
)
{
return
(
groundNormal
.
y
>
0.01
)
;
}
float
GetMaxAcceleration
(
bool
grounded
)
{
// Maximum acceleration on ground and in air
if
(
grounded
)
return
movement
.
maxGroundAcceleration
;
else
return
movement
.
maxAirAcceleration
;
}
float
CalculateJumpVerticalSpeed
(
float
targetJumpHeight
)
{
// From the jump height and gravity we deduce the upwards speed
// for the character to reach at the apex.
return
Mathf
.
Sqrt
(
2
*
targetJumpHeight
*
movement
.
gravity
)
;
}
bool
IsJumping
(
)
{
return
jumping
.
jumping
;
}
bool
IsSliding
(
)
{
return
(
grounded
&&
sliding
.
enabled
&&
TooSteep
(
)
)
;
}
bool
IsTouchingCeiling
(
)
{
return
(
movement
.
collisionFlags
&
CollisionFlags
.
CollidedAbove
)
!=
0
;
}
bool
IsGrounded
(
)
{
return
grounded
;
}
bool
TooSteep
(
)
{
return
(
groundNormal
.
y
<=
Mathf
.
Cos
(
controller
.
slopeLimit
*
Mathf
.
Deg2Rad
)
)
;
}
Vector3
GetDirection
(
)
{
return
inputMoveDirection
;
}
void
SetControllable
(
bool
controllable
)
{
canControl
=
controllable
;
}
// Project a direction onto elliptical quater segments based on forward, sideways, and backwards speed.
// The function returns the length of the resulting vector.
float
MaxSpeedInDirection
(
Vector3
desiredMovementDirection
)
{
if
(
desiredMovementDirection
==
Vector3
.
zero
)
return
0
;
else
{
float
zAxisEllipseMultiplier
=
(
desiredMovementDirection
.
z
>
0
?
movement
.
maxForwardSpeed
:
movement
.
maxBackwardsSpeed
)
/
movement
.
maxSidewaysSpeed
;
Vector3
temp
=
new
Vector3
(
desiredMovementDirection
.
x
,
0
,
desiredMovementDirection
.
z
/
zAxisEllipseMultiplier
)
.
normalized
;
float
length
=
new
Vector3
(
temp
.
x
,
0
,
temp
.
z
*
zAxisEllipseMultiplier
)
.
magnitude
*
movement
.
maxSidewaysSpeed
;
return
length
;
}
}
void
SetVelocity
(
Vector3
velocity
)
{
grounded
=
false
;
movement
.
velocity
=
velocity
;
movement
.
frameVelocity
=
Vector3
.
zero
;
SendMessage
(
"OnExternalVelocity"
)
;
}
// Require a character controller to be attached to the same game object
//@script RequireComponent (CharacterController)
//@script AddComponentMenu ("Character/Character Motor")
}
|
接着把FPSInputControoler.js修改成C#语言。它用于第一人称控制角色移动,这里会监听主角按下的方向键最后传给CharacterMotor去计算模型的位置与旋转的角度。
FPSInputController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
using
UnityEngine
;
using
System
.
Collections
;
/**
* @Author : www.xuanyusong.com
*/
[
RequireComponent
(
typeof
(
CharacterMotor
)
)
]
[
AddComponentMenu
(
"Character/FPS Input Controller"
)
]
public
class
FPSInputController
:
MonoBehaviour
{
private
CharacterMotor
motor
;
// Use this for initialization
void
Awake
(
)
{
motor
=
GetComponent
<
CharacterMotor
>
(
)
;
}
// Update is called once per frame
void
Update
(
)
{
// Get the input vector from kayboard or analog stick
Vector3
directionVector
=
new
Vector3
(
Input
.
GetAxis
(
"Horizontal"
)
,
0
,
Input
.
GetAxis
(
"Vertical"
)
)
;
if
(
directionVector
!=
Vector3
.
zero
)
{
// Get the length of the directon vector and then normalize it
// Dividing by the length is cheaper than normalizing when we already have the length anyway
var
directionLength
=
directionVector
.
magnitude
;
directionVector
=
directionVector
/
directionLength
;
// Make sure the length is no bigger than 1
directionLength
=
Mathf
.
Min
(
1
,
directionLength
)
;
// Make the input vector more sensitive towards the extremes and less sensitive in the middle
// This makes it easier to control slow speeds when using analog sticks
directionLength
=
directionLength
*
directionLength
;
// Multiply the normalized direction vector by the modified length
directionVector
=
directionVector
*
directionLength
;
}
// Apply the direction to the CharacterMotor
motor
.
inputMoveDirection
=
transform
.
rotation
*
directionVector
;
motor
.
inputJump
=
Input
.
GetButton
(
"Jump"
)
;
}
}
|
MouseLook.cs因为已经是C#语言所以就不翻译了,然后是PlatFormInputController.cs 把它转成C#语言。它和FPSInputController一样会控制主角,但是它会更加精细的计算模型旋转的插值系数。
PlatformInputController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
using
UnityEngine
;
using
System
.
Collections
;
/**
* @Author : www.xuanyusong.com
*/
[
RequireComponent
(
typeof
(
CharacterController
)
)
]
[
AddComponentMenu
(
"Character/Platform Input Controller"
)
]
public
class
PlatformInputController
:
MonoBehaviour
{
public
bool
autoRotate
=
true
;
public
float
maxRotationSpeed
=
360
;
private
CharacterMotor
motor
;
// Use this for initialization
void
Awake
(
)
{
motor
=
GetComponent
<
CharacterMotor
>
(
)
;
}
// Update is called once per frame
void
Update
(
)
{
// Get the input vector from kayboard or analog stick
Vector3
directionVector
=
new
Vector3
(
Input
.
GetAxis
(
"Horizontal"
)
,
Input
.
GetAxis
(
"Vertical"
)
,
0
)
;
if
(
directionVector
!=
Vector3
.
zero
)
{
// Get the length of the directon vector and then normalize it
// Dividing by the length is cheaper than normalizing when we already have the length anyway
var
directionLength
=
directionVector
.
magnitude
;
directionVector
=
directionVector
/
directionLength
;
// Make sure the length is no bigger than 1
directionLength
=
Mathf
.
Min
(
1
,
directionLength
)
;
// Make the input vector more sensitive towards the extremes and less sensitive in the middle
// This makes it easier to control slow speeds when using analog sticks
directionLength
=
directionLength
*
directionLength
;
// Multiply the normalized direction vector by the modified length
directionVector
=
directionVector
*
directionLength
;
}
// Rotate the input vector into camera space so up is camera's up and right is camera's right
directionVector
=
Camera
.
main
.
transform
.
rotation
*
directionVector
;
// Rotate input vector to be perpendicular to character's up vector
var
camToCharacterSpace
=
Quaternion
.
FromToRotation
(
-
Camera
.
main
.
transform
.
forward
,
transform
.
up
)
;
directionVector
=
(
camToCharacterSpace
*
directionVector
)
;
// Apply the direction to the CharacterMotor
motor
.
inputMoveDirection
=
directionVector
;
motor
.
inputJump
=
Input
.
GetButton
(
"Jump"
)
;
// Set rotation to the move direction
if
(
autoRotate
&&
directionVector
.
sqrMagnitude
>
0.01
)
{
Vector3
newForward
=
ConstantSlerp
(
transform
.
forward
,
directionVector
,
maxRotationSpeed
*
Time
.
deltaTime
)
;
newForward
=
ProjectOntoPlane
(
newForward
,
transform
.
up
)
;
transform
.
rotation
=
Quaternion
.
LookRotation
(
newForward
,
transform
.
up
)
;
}
}
Vector3
ProjectOntoPlane
(
Vector3
v
,
Vector3
normal
)
{
return
v
-
Vector3
.
Project
(
v
,
normal
)
;
}
Vector3
ConstantSlerp
(
Vector3
from
,
Vector3
to
,
float
angle
)
{
float
value
=
Mathf
.
Min
(
1
,
angle
/
Vector3
.
Angle
(
from
,
to
)
)
;
return
Vector3
.
Slerp
(
from
,
to
,
value
)
;
}
}
|
接着是ThirdPersonCamera 我们把它改成C#语言。它主要控制第三人称视角时摄像机的控制。
ThirdPersonCamera.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
using
UnityEngine
;
using
System
.
Collections
;
/**
* @Author : www.xuanyusong.com
*/
public
class
ThirdPersonCamera
:
MonoBehaviour
{
public
Transform
cameraTransform
;
private
Transform
_target
;
public
float
distance
=
7.0f
;
public
float
height
=
3.0f
;
public
float
angularSmoothLag
=
0.3f
;
public
float
angularMaxSpeed
=
15.0f
;
public
float
heightSmoothLag
=
0.3f
;
public
float
snapSmoothLag
=
0.2f
;
public
float
snapMaxSpeed
=
720.0f
;
public
float
clampHeadPositionScreenSpace
=
0.75f
;
public
float
lockCameraTimeout
=
0.2f
;
private
Vector3
headOffset
=
Vector3
.
zero
;
private
Vector3
centerOffset
=
Vector3
.
zero
;
private
float
heightVelocity
=
0.0f
;
private
float
angleVelocity
=
0.0f
;
private
bool
snap
=
false
;
private
ThirdPersonController
controller
;
private
float
targetHeight
=
100000.0f
;
void
Awake
(
)
{
if
(
!
cameraTransform
&&
Camera
.
main
)
cameraTransform
=
Camera
.
main
.
transform
;
if
(
!
cameraTransform
)
{
Debug
.
Log
(
"Please assign a camera to the ThirdPersonCamera script."
)
;
enabled
=
false
;
}
_target
=
transform
;
if
(
_target
)
{
controller
=
_target
.
GetComponent
<
ThirdPersonController
>
(
)
;
}
if
(
controller
)
{
CharacterController
characterController
=
(
CharacterController
)
_target
.
collider
;
centerOffset
=
characterController
.
bounds
.
center
-
_target
.
position
;
headOffset
=
centerOffset
;
headOffset
.
y
=
characterController
.
bounds
.
max
.
y
-
_target
.
position
.
y
;
}
else
Debug
.
Log
(
"Please assign a target to the camera that has a ThirdPersonController script attached."
)
;
Cut
(
_target
,
centerOffset
)
;
}
void
DebugDrawStuff
(
)
{
Debug
.
DrawLine
(
_target
.
position
,
_target
.
position
+
headOffset
)
;
}
float
AngleDistance
(
float
a
,
float
b
)
{
a
=
Mathf
.
Repeat
(
a
,
360
)
;
b
=
Mathf
.
Repeat
(
b
,
360
)
;
return
Mathf
.
Abs
(
b
-
a
)
;
}
void
Apply
(
Transform
dummyTarget
,
Vector3
dummyCenter
)
{
// Early out if we don't have a target
if
(
!
controller
)
return
;
Vector3
targetCenter
=
_target
.
position
+
centerOffset
;
Vector3
targetHead
=
_target
.
position
+
headOffset
;
// DebugDrawStuff();
// Calculate the current & target rotation angles
float
originalTargetAngle
=
_target
.
eulerAngles
.
y
;
float
currentAngle
=
cameraTransform
.
eulerAngles
.
y
;
// Adjust real target angle when camera is locked
float
targetAngle
=
originalTargetAngle
;
// When pressing Fire2 (alt) the camera will snap to the target direction real quick.
// It will stop snapping when it reaches the target
if
(
Input
.
GetButton
(
"Fire2"
)
)
snap
=
true
;
if
(
snap
)
{
// We are close to the target, so we can stop snapping now!
if
(
AngleDistance
(
currentAngle
,
originalTargetAngle
)
<
3.0
)
snap
=
false
;
currentAngle
=
Mathf
.
SmoothDampAngle
(
currentAngle
,
targetAngle
,
ref
angleVelocity
,
snapSmoothLag
,
snapMaxSpeed
)
;
}
// Normal camera motion
else
{
if
(
controller
.
GetLockCameraTimer
(
)
<
lockCameraTimeout
)
{
targetAngle
=
currentAngle
;
}
// Lock the camera when moving backwards!
// * It is really confusing to do 180 degree spins when turning around.
if
(
AngleDistance
(
currentAngle
,
targetAngle
)
>
160
&&
controller
.
IsMovingBackwards
(
)
)
targetAngle
+=
180
;
currentAngle
=
Mathf
.
SmoothDampAngle
(
currentAngle
,
targetAngle
,
ref
angleVelocity
,
angularSmoothLag
,
angularMaxSpeed
)
;
}
// When jumping don't move camera upwards but only down!
if
(
controller
.
IsJumping
(
)
)
{
// We'd be moving the camera upwards, do that only if it's really high
float
newTargetHeight
=
targetCenter
.
y
+
height
;
if
(
newTargetHeight
<
targetHeight
||
newTargetHeight
-
targetHeight
>
5
)
targetHeight
=
targetCenter
.
y
+
height
;
}
// When walking always update the target height
else
{
targetHeight
=
targetCenter
.
y
+
height
;
}
// Damp the height
float
currentHeight
=
cameraTransform
.
position
.
y
;
currentHeight
=
Mathf
.
SmoothDamp
(
currentHeight
,
targetHeight
,
ref
heightVelocity
,
heightSmoothLag
)
;
// Convert the angle into a rotation, by which we then reposition the camera
Quaternion
currentRotation
=
Quaternion
.
Euler
(
0
,
currentAngle
,
0
)
;
// Set the position of the camera on the x-z plane to:
// distance meters behind the target
cameraTransform
.
position
=
targetCenter
;
cameraTransform
.
position
+=
currentRotation
*
Vector3
.
back
*
distance
;
// Set the height of the camera
cameraTransform
.
position
=
new
Vector3
(
cameraTransform
.
position
.
x
,
currentHeight
,
cameraTransform
.
position
.
z
)
;
// Always look at the target
SetUpRotation
(
targetCenter
,
targetHead
)
;
}
void
LateUpdate
(
)
{
Apply
(
transform
,
Vector3
.
zero
)
;
}
void
Cut
(
Transform
dummyTarget
,
Vector3
dummyCenter
)
{
float
oldHeightSmooth
=
heightSmoothLag
;
float
oldSnapMaxSpeed
=
snapMaxSpeed
;
float
oldSnapSmooth
=
snapSmoothLag
;
snapMaxSpeed
=
10000
;
snapSmoothLag
=
0.001f
;
heightSmoothLag
=
0.001f
;
snap
=
true
;
Apply
(
transform
,
Vector3
.
zero
)
;
heightSmoothLag
=
oldHeightSmooth
;
snapMaxSpeed
=
oldSnapMaxSpeed
;
snapSmoothLag
=
oldSnapSmooth
;
}
void
SetUpRotation
(
Vector3
centerPos
,
Vector3
headPos
)
{
// Now it's getting hairy. The devil is in the details here, the big issue is jumping of course.
// * When jumping up and down we don't want to center the guy in screen space.
// This is important to give a feel for how high you jump and avoiding large camera movements.
//
// * At the same time we dont want him to ever go out of screen and we want all rotations to be totally smooth.
//
// So here is what we will do:
//
// 1. We first find the rotation around the y axis. Thus he is always centered on the y-axis
// 2. When grounded we make him be centered
// 3. When jumping we keep the camera rotation but rotate the camera to get him back into view if his head is above some threshold
// 4. When landing we smoothly interpolate towards centering him on screen
Vector3
cameraPos
=
cameraTransform
.
position
;
Vector3
offsetToCenter
=
centerPos
-
cameraPos
;
// Generate base rotation only around y-axis
Quaternion
yRotation
=
Quaternion
.
LookRotation
(
new
Vector3
(
offsetToCenter
.
x
,
0
,
offsetToCenter
.
z
)
)
;
Vector3
relativeOffset
=
Vector3
.
forward
*
distance
+
Vector3
.
down
*
height
;
cameraTransform
.
rotation
=
yRotation
*
Quaternion
.
LookRotation
(
relativeOffset
)
;
// Calculate the projected center position and top position in world space
Ray
centerRay
=
cameraTransform
.
camera
.
ViewportPointToRay
(
new
Vector3
(
0.5f
,
0.5f
,
1f
)
)
;
Ray
topRay
=
cameraTransform
.
camera
.
ViewportPointToRay
(
new
Vector3
(
0.5f
,
clampHeadPositionScreenSpace
,
1f
)
)
;
Vector3
centerRayPos
=
centerRay
.
GetPoint
(
distance
)
;
Vector3
topRayPos
=
topRay
.
GetPoint
(
distance
)
;
float
centerToTopAngle
=
Vector3
.
Angle
(
centerRay
.
direction
,
topRay
.
direction
)
;
float
heightToAngle
=
centerToTopAngle
/
(
centerRayPos
.
y
-
topRayPos
.
y
)
;
float
extraLookAngle
=
heightToAngle
*
(
centerRayPos
.
y
-
centerPos
.
y
)
;
if
(
extraLookAngle
<
centerToTopAngle
)
{
extraLookAngle
=
0
;
}
else
{
extraLookAngle
=
extraLookAngle
-
centerToTopAngle
;
cameraTransform
.
rotation
*=
Quaternion
.
Euler
(
-
extraLookAngle
,
0
,
0
)
;
}
}
Vector3
GetCenterOffset
(
)
{
return
centerOffset
;
}
}
|
最后一个是ThirdPersonController我们同样把它修改成C#语言,它主要更新第三人称视角控制主角时播放的各种动画,主角移动,等等。
ThirdPersonController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
|
using
UnityEngine
;
using
System
.
Collections
;
/**
* @Author : www.xuanyusong.com
*/
[
RequireComponent
(
typeof
(
CharacterController
)
)
]
public
class
ThirdPersonController
:
MonoBehaviour
{
public
AnimationClip
idleAnimation
;
public
AnimationClip
walkAnimation
;
public
AnimationClip
runAnimation
;
public
AnimationClip
jumpPoseAnimation
;
public
float
walkMaxAnimationSpeed
=
0.75f
;
public
float
trotMaxAnimationSpeed
=
1.0f
;
public
float
runMaxAnimationSpeed
=
1.0f
;
public
float
jumpAnimationSpeed
=
1.15f
;
public
float
landAnimationSpeed
=
1.0f
;
private
Animation
_animation
;
enum
CharacterState
{
Idle
=
0
,
Walking
=
1
,
Trotting
=
2
,
Running
=
3
,
Jumping
=
4
,
}
private
CharacterState
_characterState
;
// The speed when walking
float
walkSpeed
=
2.0f
;
// after trotAfterSeconds of walking we trot with trotSpeed
float
trotSpeed
=
4.0f
;
// when pressing "Fire3" button (cmd) we start running
float
runSpeed
=
6.0f
;
float
inAirControlAcceleration
=
3.0f
;
// How high do we jump when pressing jump and letting go immediately
float
jumpHeight
=
0.5f
;
// The gravity for the character
float
gravity
=
20.0f
;
// The gravity in controlled descent mode
float
speedSmoothing
=
10.0f
;
float
rotateSpeed
=
500.0f
;
float
trotAfterSeconds
=
3.0f
;
bool
canJump
=
true
;
private
float
jumpRepeatTime
=
0.05f
;
private
float
jumpTimeout
=
0.15f
;
private
float
groundedTimeout
=
0.25f
;
// The camera doesnt start following the target immediately but waits for a split second to avoid too much waving around.
private
float
lockCameraTimer
=
0.0f
;
// The current move direction in x-z
private
Vector3
moveDirection
=
Vector3
.
zero
;
// The current vertical speed
private
float
verticalSpeed
=
0.0f
;
// The current x-z move speed
private
float
moveSpeed
=
0.0f
;
// The last collision flags returned from controller.Move
private
CollisionFlags
collisionFlags
;
// Are we jumping? (Initiated with jump button and not grounded yet)
private
bool
jumping
=
false
;
private
bool
jumpingReachedApex
=
false
;
// Are we moving backwards (This locks the camera to not do a 180 degree spin)
private
bool
movingBack
=
false
;
// Is the user pressing any keys?
private
bool
isMoving
=
false
;
// When did the user start walking (Used for going into trot after a while)
private
float
walkTimeStart
=
0.0f
;
// Last time the jump button was clicked down
private
float
lastJumpButtonTime
=
-
10.0f
;
// Last time we performed a jump
private
float
lastJumpTime
=
-
1.0f
;
// the height we jumped from (Used to determine for how long to apply extra jump power after jumping.)
private
float
lastJumpStartHeight
=
0.0f
;
private
Vector3
inAirVelocity
=
Vector3
.
zero
;
private
float
lastGroundedTime
=
0.0f
;
private
bool
isControllable
=
true
;
void
Awake
(
)
{
moveDirection
=
transform
.
TransformDirection
(
Vector3
.
forward
)
;
_animation
=
GetComponent
<
Animation
>
(
)
;
if
(
!
_animation
)
Debug
.
Log
(
"The character you would like to control doesn't have animations. Moving her might look weird."
)
;
/*
public var idleAnimation : AnimationClip;
public var walkAnimation : AnimationClip;
public var runAnimation : AnimationClip;
public var jumpPoseAnimation : AnimationClip;
*/
if
(
!
idleAnimation
)
{
_animation
=
null
;
Debug
.
Log
(
"No idle animation found. Turning off animations."
)
;
}
if
(
!
walkAnimation
)
{
_animation
=
null
;
Debug
.
Log
(
"No walk animation found. Turning off animations."
)
;
}
if
(
!
runAnimation
)
{
_animation
=
null
;
Debug
.
Log
(
"No run animation found. Turning off animations."
)
;
}
if
(
!
jumpPoseAnimation
&&
canJump
)
{
_animation
=
null
;
Debug
.
Log
(
"No jump animation found and the character has canJump enabled. Turning off animations."
)
;
}
}
void
UpdateSmoothedMovementDirection
(
)
{
Transform
cameraTransform
=
Camera
.
main
.
transform
;
bool
grounded
=
IsGrounded
(
)
;
// Forward vector relative to the camera along the x-z plane
Vector3
forward
=
cameraTransform
.
TransformDirection
(
Vector3
.
forward
)
;
forward
.
y
=
0
;
forward
=
forward
.
normalized
;
// Right vector relative to the camera
// Always orthogonal to the forward vector
Vector3
right
=
new
Vector3
(
forward
.
z
,
0
,
-
forward
.
x
)
;
float
v
=
Input
.
GetAxisRaw
(
"Vertical"
)
;
float
h
=
Input
.
GetAxisRaw
(
"Horizontal"
)
;
// Are we moving backwards or looking backwards
if
(
v
<
-
0.2f
)
movingBack
=
true
;
else
movingBack
=
false
;
bool
wasMoving
=
isMoving
;
isMoving
=
Mathf
.
Abs
(
h
)
>
0.1f
||
Mathf
.
Abs
(
v
)
>
0.1f
;
// Target direction relative to the camera
Vector3
targetDirection
=
h
*
right
+
v
*
forward
;
// Grounded controls
if
(
grounded
)
{
// Lock camera for short period when transitioning moving & standing still
lockCameraTimer
+=
Time
.
deltaTime
;
if
(
isMoving
!=
wasMoving
)
lockCameraTimer
=
0.0f
;
// We store speed and direction seperately,
// so that when the character stands still we still have a valid forward direction
// moveDirection is always normalized, and we only update it if there is user input.
if
(
targetDirection
!=
Vector3
.
zero
)
{
// If we are really slow, just snap to the target direction
if
(
moveSpeed
<
walkSpeed
*
0.9f
&&
grounded
)
{
moveDirection
=
targetDirection
.
normalized
;
}
// Otherwise smoothly turn towards it
else
{
moveDirection
=
Vector3
.
RotateTowards
(
moveDirection
,
targetDirection
,
rotateSpeed
*
Mathf
.
Deg2Rad
*
Time
.
deltaTime
,
1000
)
;
moveDirection
=
moveDirection
.
normalized
;
}
}
// Smooth the speed based on the current target direction
float
curSmooth
=
speedSmoothing
*
Time
.
deltaTime
;
// Choose target speed
//* We want to support analog input but make sure you cant walk faster diagonally than just forward or sideways
float
targetSpeed
=
Mathf
.
Min
(
targetDirection
.
magnitude
,
1.0f
)
;
_characterState
=
CharacterState
.
Idle
;
// Pick speed modifier
if
(
Input
.
GetKey
(
KeyCode
.
LeftShift
)
|
Input
.
GetKey
(
KeyCode
.
RightShift
)
)
{
targetSpeed
*=
runSpeed
;
_characterState
=
CharacterState
.
Running
;
}
else
if
(
Time
.
time
-
trotAfterSeconds
>
walkTimeStart
)
{
targetSpeed
*=
trotSpeed
;
_characterState
=
CharacterState
.
Trotting
;
}
else
{
targetSpeed
*=
walkSpeed
;
_characterState
=
CharacterState
.
Walking
;
}
moveSpeed
=
Mathf
.
Lerp
(
moveSpeed
,
targetSpeed
,
curSmooth
)
;
// Reset walk time start when we slow down
if
(
moveSpeed
<
walkSpeed
*
0.3f
)
walkTimeStart
=
Time
.
time
;
}
// In air controls
else
{
// Lock camera while in air
if
(
jumping
)
lockCameraTimer
=
0.0f
;
if
(
isMoving
)
inAirVelocity
+=
targetDirection
.
normalized
*
Time
.
deltaTime
*
inAirControlAcceleration
;
}
}
void
ApplyJumping
(
)
{
// Prevent jumping too fast after each other
if
(
lastJumpTime
+
jumpRepeatTime
>
Time
.
time
)
return
;
if
(
IsGrounded
(
)
)
{
// Jump
// - Only when pressing the button down
// - With a timeout so you can press the button slightly before landing
if
(
canJump
&&
Time
.
time
<
lastJumpButtonTime
+
jumpTimeout
)
{
verticalSpeed
=
CalculateJumpVerticalSpeed
(
jumpHeight
)
;
SendMessage
(
"DidJump"
,
SendMessageOptions
.
DontRequireReceiver
)
;
}
}
}
void
ApplyGravity
(
)
{
if
(
isControllable
)
// don't move player at all if not controllable.
{
// Apply gravity
bool
jumpButton
=
Input
.
GetButton
(
"Jump"
)
;
// When we reach the apex of the jump we send out a message
if
(
jumping
&&
!
jumpingReachedApex
&&
verticalSpeed
<=
0.0f
)
{
jumpingReachedApex
=
true
;
SendMessage
(
"DidJumpReachApex"
,
SendMessageOptions
.
DontRequireReceiver
)
;
}
if
(
IsGrounded
(
)
)
verticalSpeed
=
0.0f
;
else
verticalSpeed
-=
gravity
*
Time
.
deltaTime
;
}
}
float
CalculateJumpVerticalSpeed
(
float
targetJumpHeight
)
{
// From the jump height and gravity we deduce the upwards speed
// for the character to reach at the apex.
return
Mathf
.
Sqrt
(
2
*
targetJumpHeight
*
gravity
)
;
}
void
DidJump
(
)
{
jumping
=
true
;
jumpingReachedApex
=
false
;
lastJumpTime
=
Time
.
time
;
lastJumpStartHeight
=
transform
.
position
.
y
;
lastJumpButtonTime
=
-
10
;
_characterState
=
CharacterState
.
Jumping
;
}
void
Update
(
)
{
if
(
!
isControllable
)
{
// kill all inputs if not controllable.
Input
.
ResetInputAxes
(
)
;
}
if
(
Input
.
GetButtonDown
(
"Jump"
)
)
{
lastJumpButtonTime
=
Time
.
time
;
}
UpdateSmoothedMovementDirection
(
)
;
// Apply gravity
// - extra power jump modifies gravity
// - controlledDescent mode modifies gravity
ApplyGravity
(
)
;
// Apply jumping logic
ApplyJumping
(
)
;
// Calculate actual motion
Vector3
movement
=
moveDirection
*
moveSpeed
+
new
Vector3
(
0
,
verticalSpeed
,
0
)
+
inAirVelocity
;
movement
*=
Time
.
deltaTime
;
// Move the controller
CharacterController
controller
=
GetComponent
<
CharacterController
>
(
)
;
collisionFlags
=
controller
.
Move
(
movement
)
;
// ANIMATION sector
if
(
_animation
)
{
if
(
_characterState
==
CharacterState
.
Jumping
)
{
if
(
!
jumpingReachedApex
)
{
_animation
[
jumpPoseAnimation
.
name
]
.
speed
=
jumpAnimationSpeed
;
_animation
[
jumpPoseAnimation
.
name
]
.
wrapMode
=
WrapMode
.
ClampForever
;
_animation
.
CrossFade
(
jumpPoseAnimation
.
name
)
;
}
else
{
_animation
[
jumpPoseAnimation
.
name
]
.
speed
=
-
landAnimationSpeed
;
_animation
[
jumpPoseAnimation
.
name
]
.
wrapMode
=
WrapMode
.
ClampForever
;
_animation
.
CrossFade
(
jumpPoseAnimation
.
name
)
;
}
}
else
{
if
(
controller
.
velocity
.
sqrMagnitude
<
0.1f
)
{
_animation
.
CrossFade
(
idleAnimation
.
name
)
;
}
else
{
if
(
_characterState
==
CharacterState
.
Running
)
{
_animation
[
runAnimation
.
name
]
.
speed
=
Mathf
.
Clamp
(
controller
.
velocity
.
magnitude
,
0.0f
,
runMaxAnimationSpeed
)
;
_animation
.
CrossFade
(
runAnimation
.
name
)
;
}
else
if
(
_characterState
==
CharacterState
.
Trotting
)
{
_animation
[
walkAnimation
.
name
]
.
speed
=
Mathf
.
Clamp
(
controller
.
velocity
.
magnitude
,
0.0f
,
trotMaxAnimationSpeed
)
;
_animation
.
CrossFade
(
walkAnimation
.
name
)
;
}
else
if
(
_characterState
==
CharacterState
.
Walking
)
{
_animation
[
walkAnimation
.
name
]
.
speed
=
Mathf
.
Clamp
(
controller
.
velocity
.
magnitude
,
0.0f
,
walkMaxAnimationSpeed
)
;
_animation
.
CrossFade
(
walkAnimation
.
name
)
;
}
}
}
}
// ANIMATION sector
// Set rotation to the move direction
if
(
IsGrounded
(
)
)
{
transform
.
rotation
=
Quaternion
.
LookRotation
(
moveDirection
)
;
}
else
{
Vector3
xzMove
=
movement
;
xzMove
.
y
=
0
;
if
(
xzMove
.
sqrMagnitude
>
0.001f
)
{
transform
.
rotation
=
Quaternion
.
LookRotation
(
xzMove
)
;
}
}
// We are in jump mode but just became grounded
if
(
IsGrounded
(
)
)
{
lastGroundedTime
=
Time
.
time
;
inAirVelocity
=
Vector3
.
zero
;
if
(
jumping
)
{
jumping
=
false
;
SendMessage
(
"DidLand"
,
SendMessageOptions
.
DontRequireReceiver
)
;
}
}
}
void
OnControllerColliderHit
(
ControllerColliderHit
hit
)
{
// Debug.DrawRay(hit.point, hit.normal);
if
(
hit
.
moveDirection
.
y
>
0.01f
)
return
;
}
float
GetSpeed
(
)
{
return
moveSpeed
;
}
public
bool
IsJumping
(
)
{
return
jumping
;
}
bool
IsGrounded
(
)
{
return
(
collisionFlags
&
CollisionFlags
.
CollidedBelow
)
!=
0
;
}
Vector3
GetDirection
(
)
{
return
moveDirection
;
}
public
bool
IsMovingBackwards
(
)
{
return
movingBack
;
}
public
float
GetLockCameraTimer
(
)
{
return
lockCameraTimer
;
}
bool
IsMoving
(
)
{
return
Mathf
.
Abs
(
Input
.
GetAxisRaw
(
"Vertical"
)
)
+
Mathf
.
Abs
(
Input
.
GetAxisRaw
(
"Horizontal"
)
)
>
0.5f
;
}
bool
HasJumpReachedApex
(
)
{
return
jumpingReachedApex
;
}
bool
IsGroundedWithTimeout
(
)
{
return
lastGroundedTime
+
groundedTimeout
>
Time
.
time
;
}
void
Reset
(
)
{
gameObject
.
tag
=
"Player"
;
}
}
|
最后我们用修改的脚本来控制第三人称视角主角的移动,直接上图大家仔细看看监测面板视图中的脚本绑定,这里问题不大,图片如果看不清点击即可查看大图。乖~~~
脚本终于全部翻译完毕,其实在开发中大家可以去丰富这些脚本,灵活的运用它们。感谢Unity圣典的站长专门花钱买模型让我来写教程与录制视频教程。嚯嚯 既然是买的模型 我就不提供下载了 哇咔咔。
最后MOMO祝大家学习愉快。下回写点游戏AI的东西,让游戏中的怪物在更加聪明点吧。