@musashishao/agent-kit 1.9.0 → 1.9.1
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/.agent/agents/ai-asset-factory.md +700 -0
- package/.agent/agents/ai-audio-factory.md +503 -0
- package/.agent/agents/game-developer.md +4 -4
- package/.agent/agents/orchestrator.md +113 -3
- package/.agent/agents/project-planner.md +67 -0
- package/.agent/agents/unity-mobile-master.md +949 -0
- package/.agent/mcp/config/registry.json +65 -51
- package/.agent/mcp/servers/notebooklm/README.md +114 -0
- package/.agent/mcp/servers/notebooklm/package.json +35 -0
- package/.agent/mcp/servers/notebooklm/src/auth/chrome.ts +225 -0
- package/.agent/mcp/servers/notebooklm/src/auth/index.ts +1 -0
- package/.agent/mcp/servers/notebooklm/src/index.ts +516 -0
- package/.agent/mcp/servers/notebooklm/src/services/index.ts +3 -0
- package/.agent/mcp/servers/notebooklm/src/services/library.ts +217 -0
- package/.agent/mcp/servers/notebooklm/src/services/notebooklm.ts +380 -0
- package/.agent/mcp/servers/notebooklm/tsconfig.json +15 -0
- package/.agent/mcp-gateway/README.md +169 -20
- package/.agent/mcp-gateway/package.json +22 -7
- package/.agent/mcp-gateway/src/auth/index.ts +55 -0
- package/.agent/mcp-gateway/src/auth/middleware.ts +242 -0
- package/.agent/mcp-gateway/src/auth/oauth.ts +462 -0
- package/.agent/mcp-gateway/src/auth/scopes.ts +227 -0
- package/.agent/mcp-gateway/src/index.ts +252 -105
- package/.agent/mcp-gateway/src/observability/index.ts +5 -0
- package/.agent/mcp-gateway/src/observability/otel.ts +405 -0
- package/.agent/mcp-gateway/src/transports/index.ts +5 -0
- package/.agent/mcp-gateway/src/transports/streamableHttp.ts +235 -0
- package/.agent/rules/CODEX.md +89 -0
- package/.agent/rules/CODE_RULES.md +73 -0
- package/.agent/rules/GEMINI.md +25 -0
- package/.agent/rules/MEMORY_STATE.md +110 -0
- package/.agent/rules/REFERENCE.md +33 -141
- package/.agent/rules/REF_SKILLS.md +116 -0
- package/.agent/rules/REF_WORKFLOWS.md +81 -0
- package/.agent/scripts/ak_cli.py +106 -5
- package/.agent/scripts/memory_manager.py +48 -9
- package/.agent/skills/anti-hallucination/SKILL.md +295 -0
- package/.agent/skills/anti-hallucination/scripts/check_hallucination.py +299 -0
- package/.agent/skills/bifurcation-analysis/SKILL.md +56 -0
- package/.agent/skills/brainstorming/SKILL.md +80 -6
- package/.agent/skills/decision-memory/SKILL.md +317 -0
- package/.agent/skills/emergence-detector/SKILL.md +230 -0
- package/.agent/skills/emergence-detector/scripts/check_emergence.py +265 -0
- package/.agent/skills/explained-qa/SKILL.md +142 -0
- package/.agent/skills/explained-qa/game-terminology.md +214 -0
- package/.agent/skills/game-development/ai-dialogue-engine/SKILL.md +442 -0
- package/.agent/skills/game-development/ai-graphics-generator/SKILL.md +463 -0
- package/.agent/skills/game-development/ai-playtest-framework/SKILL.md +570 -0
- package/.agent/skills/game-development/camera-systems/SKILL.md +607 -0
- package/.agent/skills/game-development/card-battle-engine/SKILL.md +618 -0
- package/.agent/skills/game-development/character-controller-3d/SKILL.md +908 -0
- package/.agent/skills/game-development/cloud-save-sync/SKILL.md +527 -0
- package/.agent/skills/game-development/combat-system/SKILL.md +748 -0
- package/.agent/skills/game-development/compliance-rating/SKILL.md +277 -0
- package/.agent/skills/game-development/crossplatform-build/SKILL.md +386 -0
- package/.agent/skills/game-development/cultivation-progression/SKILL.md +520 -0
- package/.agent/skills/game-development/data-driven-balance/SKILL.md +535 -0
- package/.agent/skills/game-development/game-analytics-integrator/SKILL.md +410 -0
- package/.agent/skills/game-development/game-audio-advanced/SKILL.md +646 -0
- package/.agent/skills/game-development/game-economy-designer/SKILL.md +375 -0
- package/.agent/skills/game-development/game-marketing/SKILL.md +85 -0
- package/.agent/skills/game-development/game-state-manager/SKILL.md +883 -0
- package/.agent/skills/game-development/hybrid-game-spec/SKILL.md +220 -0
- package/.agent/skills/game-development/inventory-quest/SKILL.md +747 -0
- package/.agent/skills/game-development/liveops/SKILL.md +308 -0
- package/.agent/skills/game-development/localization/SKILL.md +286 -0
- package/.agent/skills/game-development/mobile-input-patterns/SKILL.md +343 -0
- package/.agent/skills/game-development/monetization-strategy/SKILL.md +94 -0
- package/.agent/skills/game-development/multiplayer-master/SKILL.md +727 -0
- package/.agent/skills/game-development/narrative-branching/SKILL.md +593 -0
- package/.agent/skills/game-development/procedural-level-ai/SKILL.md +367 -0
- package/.agent/skills/game-development/prototyping-rapid/SKILL.md +205 -0
- package/.agent/skills/game-development/spec-ecosystem/SKILL.md +155 -0
- package/.agent/skills/game-development/spec-ecosystem/decision-log-format.md +129 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/PLAN-template.md +178 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/SPEC-template.md +110 -0
- package/.agent/skills/game-development/spec-ecosystem/templates/TASKS-template.md +156 -0
- package/.agent/skills/game-development/survival-systems/SKILL.md +493 -0
- package/.agent/skills/game-development/testing-qa/SKILL.md +270 -0
- package/.agent/skills/game-development/unity-mobile-optimization/SKILL.md +271 -0
- package/.agent/skills/intent-capture/SKILL.md +65 -0
- package/.agent/skills/mcp-composition/SKILL.md +362 -0
- package/.agent/skills/mcp-observability/SKILL.md +323 -0
- package/.agent/skills/mcp-security/SKILL.md +314 -0
- package/.agent/skills/trust-spectrum/SKILL.md +291 -0
- package/.agent/skills/vibe-coding-guard/SKILL.md +328 -0
- package/.agent/templates/AGENTS.game.md +63 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.en.md +100 -0
- package/.agent/templates/docs/WORKFLOW_GUIDE.vi.md +100 -0
- package/.agent/workflows/ai-agent.md +2 -0
- package/.agent/workflows/autofix.md +1 -0
- package/.agent/workflows/brainstorm.md +1 -0
- package/.agent/workflows/context.md +1 -0
- package/.agent/workflows/create.md +39 -8
- package/.agent/workflows/dashboard.md +1 -0
- package/.agent/workflows/debug.md +14 -0
- package/.agent/workflows/deploy.md +14 -0
- package/.agent/workflows/enhance.md +44 -0
- package/.agent/workflows/gamekit-init.md +177 -0
- package/.agent/workflows/gamekit-launch.md +338 -0
- package/.agent/workflows/gamekit-plan.md +204 -0
- package/.agent/workflows/gamekit-qa.md +153 -0
- package/.agent/workflows/gamekit-spec.md +243 -0
- package/.agent/workflows/gamekit-tasks.md +208 -0
- package/.agent/workflows/marketing.md +2 -0
- package/.agent/workflows/next.md +1 -0
- package/.agent/workflows/orchestrate.md +12 -0
- package/.agent/workflows/pentest.md +2 -0
- package/.agent/workflows/plan.md +42 -0
- package/.agent/workflows/preview.md +1 -0
- package/.agent/workflows/quality.md +1 -0
- package/.agent/workflows/saas.md +2 -0
- package/.agent/workflows/spec.md +42 -0
- package/.agent/workflows/status.md +1 -0
- package/.agent/workflows/test.md +14 -0
- package/.agent/workflows/ui-ux-pro-max.md +1 -0
- package/bin/cli.js +411 -111
- package/package.json +1 -2
- package/.agent/agents/game-asset-curator.md +0 -317
- package/.agent/agents/game-narrative-designer.md +0 -310
- package/.agent/agents/game-qa-agent.md +0 -441
- package/.agent/workflows/game-prototype.md +0 -154
- package/docs/AI_DATA_INFRASTRUCTURE.md +0 -288
- package/docs/CHANGELOG_AI_INFRA.md +0 -141
- package/docs/MIGRATION_GUIDE_V1.9.md +0 -55
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: character-controller-3d
|
|
3
|
+
description: Advanced 3D character movement. First-person, third-person controllers, advanced mechanics like dash, wall-run, swimming. Complete implementation patterns.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, WebSearch
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 3D Character Controller Skill
|
|
8
|
+
|
|
9
|
+
> Build responsive, polished character movement for any 3D game.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## When to Use
|
|
14
|
+
|
|
15
|
+
- Implementing 3D player movement
|
|
16
|
+
- Building FPS or TPS controllers
|
|
17
|
+
- Advanced mechanics (dash, wall-run, climb)
|
|
18
|
+
- Root motion integration
|
|
19
|
+
- Platformer movement
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Controller Type Selection
|
|
24
|
+
|
|
25
|
+
| Type | Best For | Pros | Cons |
|
|
26
|
+
|------|----------|------|------|
|
|
27
|
+
| **CharacterController** | Most games | Simple, built-in | No physics reactions |
|
|
28
|
+
| **Rigidbody** | Physics puzzles | Reacts to forces | Harder to control |
|
|
29
|
+
| **Custom** | Specific needs | Full control | Time investment |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Third-Person Controller
|
|
34
|
+
|
|
35
|
+
### Complete Implementation
|
|
36
|
+
|
|
37
|
+
```csharp
|
|
38
|
+
[RequireComponent(typeof(CharacterController))]
|
|
39
|
+
public class ThirdPersonController : MonoBehaviour
|
|
40
|
+
{
|
|
41
|
+
[Header("Movement")]
|
|
42
|
+
[SerializeField] private float walkSpeed = 4f;
|
|
43
|
+
[SerializeField] private float runSpeed = 8f;
|
|
44
|
+
[SerializeField] private float rotationSpeed = 10f;
|
|
45
|
+
[SerializeField] private float acceleration = 10f;
|
|
46
|
+
|
|
47
|
+
[Header("Jump")]
|
|
48
|
+
[SerializeField] private float jumpHeight = 1.5f;
|
|
49
|
+
[SerializeField] private float gravity = -20f;
|
|
50
|
+
[SerializeField] private float coyoteTime = 0.15f;
|
|
51
|
+
[SerializeField] private float jumpBufferTime = 0.15f;
|
|
52
|
+
|
|
53
|
+
[Header("Ground Check")]
|
|
54
|
+
[SerializeField] private LayerMask groundMask;
|
|
55
|
+
[SerializeField] private float groundCheckRadius = 0.3f;
|
|
56
|
+
[SerializeField] private Transform groundCheck;
|
|
57
|
+
|
|
58
|
+
[Header("Slope Handling")]
|
|
59
|
+
[SerializeField] private float maxSlopeAngle = 45f;
|
|
60
|
+
[SerializeField] private float slopeSlideSpeed = 5f;
|
|
61
|
+
|
|
62
|
+
[Header("Camera")]
|
|
63
|
+
[SerializeField] private Transform cameraTransform;
|
|
64
|
+
|
|
65
|
+
// Components
|
|
66
|
+
private CharacterController _controller;
|
|
67
|
+
private Animator _animator;
|
|
68
|
+
|
|
69
|
+
// State
|
|
70
|
+
private Vector3 _velocity;
|
|
71
|
+
private Vector3 _moveDirection;
|
|
72
|
+
private float _currentSpeed;
|
|
73
|
+
private bool _isGrounded;
|
|
74
|
+
private bool _wasGrounded;
|
|
75
|
+
private float _lastGroundedTime;
|
|
76
|
+
private float _jumpBufferedTime;
|
|
77
|
+
private bool _isRunning;
|
|
78
|
+
|
|
79
|
+
// Animator hashes
|
|
80
|
+
private static readonly int SpeedHash = Animator.StringToHash("Speed");
|
|
81
|
+
private static readonly int GroundedHash = Animator.StringToHash("Grounded");
|
|
82
|
+
private static readonly int JumpHash = Animator.StringToHash("Jump");
|
|
83
|
+
private static readonly int FallHash = Animator.StringToHash("Fall");
|
|
84
|
+
|
|
85
|
+
void Awake()
|
|
86
|
+
{
|
|
87
|
+
_controller = GetComponent<CharacterController>();
|
|
88
|
+
_animator = GetComponent<Animator>();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
void Update()
|
|
92
|
+
{
|
|
93
|
+
GroundCheck();
|
|
94
|
+
HandleMovement();
|
|
95
|
+
HandleJump();
|
|
96
|
+
ApplyGravity();
|
|
97
|
+
HandleSlope();
|
|
98
|
+
|
|
99
|
+
// Apply final movement
|
|
100
|
+
_controller.Move(_velocity * Time.deltaTime);
|
|
101
|
+
|
|
102
|
+
UpdateAnimator();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#region Ground Check
|
|
106
|
+
|
|
107
|
+
private void GroundCheck()
|
|
108
|
+
{
|
|
109
|
+
_wasGrounded = _isGrounded;
|
|
110
|
+
_isGrounded = Physics.CheckSphere(
|
|
111
|
+
groundCheck.position,
|
|
112
|
+
groundCheckRadius,
|
|
113
|
+
groundMask
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Update coyote time
|
|
117
|
+
if (_isGrounded)
|
|
118
|
+
{
|
|
119
|
+
_lastGroundedTime = Time.time;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Landing
|
|
123
|
+
if (!_wasGrounded && _isGrounded && _velocity.y < 0)
|
|
124
|
+
{
|
|
125
|
+
OnLand();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private bool CanCoyoteJump()
|
|
130
|
+
{
|
|
131
|
+
return Time.time - _lastGroundedTime <= coyoteTime;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#endregion
|
|
135
|
+
|
|
136
|
+
#region Movement
|
|
137
|
+
|
|
138
|
+
private void HandleMovement()
|
|
139
|
+
{
|
|
140
|
+
// Input
|
|
141
|
+
float h = Input.GetAxisRaw("Horizontal");
|
|
142
|
+
float v = Input.GetAxisRaw("Vertical");
|
|
143
|
+
Vector3 inputDir = new Vector3(h, 0, v).normalized;
|
|
144
|
+
|
|
145
|
+
_isRunning = Input.GetKey(KeyCode.LeftShift);
|
|
146
|
+
float targetSpeed = inputDir.magnitude > 0.1f
|
|
147
|
+
? (_isRunning ? runSpeed : walkSpeed)
|
|
148
|
+
: 0f;
|
|
149
|
+
|
|
150
|
+
// Smooth speed transition
|
|
151
|
+
_currentSpeed = Mathf.MoveTowards(_currentSpeed, targetSpeed, acceleration * Time.deltaTime);
|
|
152
|
+
|
|
153
|
+
if (inputDir.magnitude > 0.1f)
|
|
154
|
+
{
|
|
155
|
+
// Calculate move direction relative to camera
|
|
156
|
+
float targetAngle = Mathf.Atan2(inputDir.x, inputDir.z) * Mathf.Rad2Deg
|
|
157
|
+
+ cameraTransform.eulerAngles.y;
|
|
158
|
+
|
|
159
|
+
// Smooth rotation
|
|
160
|
+
float angle = Mathf.SmoothDampAngle(
|
|
161
|
+
transform.eulerAngles.y,
|
|
162
|
+
targetAngle,
|
|
163
|
+
ref _rotationVelocity,
|
|
164
|
+
0.1f
|
|
165
|
+
);
|
|
166
|
+
transform.rotation = Quaternion.Euler(0, angle, 0);
|
|
167
|
+
|
|
168
|
+
_moveDirection = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Apply horizontal movement
|
|
172
|
+
Vector3 move = _moveDirection * _currentSpeed;
|
|
173
|
+
_velocity.x = move.x;
|
|
174
|
+
_velocity.z = move.z;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private float _rotationVelocity;
|
|
178
|
+
|
|
179
|
+
#endregion
|
|
180
|
+
|
|
181
|
+
#region Jump
|
|
182
|
+
|
|
183
|
+
private void HandleJump()
|
|
184
|
+
{
|
|
185
|
+
// Buffer jump input
|
|
186
|
+
if (Input.GetButtonDown("Jump"))
|
|
187
|
+
{
|
|
188
|
+
_jumpBufferedTime = Time.time;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
bool jumpBuffered = Time.time - _jumpBufferedTime <= jumpBufferTime;
|
|
192
|
+
bool canJump = (_isGrounded || CanCoyoteJump()) && jumpBuffered;
|
|
193
|
+
|
|
194
|
+
if (canJump && _velocity.y <= 0)
|
|
195
|
+
{
|
|
196
|
+
// Calculate jump velocity from height
|
|
197
|
+
_velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
|
198
|
+
_jumpBufferedTime = 0;
|
|
199
|
+
|
|
200
|
+
_animator?.SetTrigger(JumpHash);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Variable jump height (release early = lower jump)
|
|
204
|
+
if (Input.GetButtonUp("Jump") && _velocity.y > 0)
|
|
205
|
+
{
|
|
206
|
+
_velocity.y *= 0.5f;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private void OnLand()
|
|
211
|
+
{
|
|
212
|
+
// Landing effects
|
|
213
|
+
// Play sound, particles, etc.
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#endregion
|
|
217
|
+
|
|
218
|
+
#region Gravity & Slope
|
|
219
|
+
|
|
220
|
+
private void ApplyGravity()
|
|
221
|
+
{
|
|
222
|
+
if (_isGrounded && _velocity.y < 0)
|
|
223
|
+
{
|
|
224
|
+
_velocity.y = -2f; // Small downward force to stay grounded
|
|
225
|
+
}
|
|
226
|
+
else
|
|
227
|
+
{
|
|
228
|
+
_velocity.y += gravity * Time.deltaTime;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private void HandleSlope()
|
|
233
|
+
{
|
|
234
|
+
if (!_isGrounded) return;
|
|
235
|
+
|
|
236
|
+
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 2f, groundMask))
|
|
237
|
+
{
|
|
238
|
+
float angle = Vector3.Angle(hit.normal, Vector3.up);
|
|
239
|
+
|
|
240
|
+
if (angle > maxSlopeAngle)
|
|
241
|
+
{
|
|
242
|
+
// Slide down steep slopes
|
|
243
|
+
Vector3 slideDirection = Vector3.ProjectOnPlane(Vector3.down, hit.normal).normalized;
|
|
244
|
+
_velocity += slideDirection * slopeSlideSpeed * Time.deltaTime;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#endregion
|
|
250
|
+
|
|
251
|
+
#region Animation
|
|
252
|
+
|
|
253
|
+
private void UpdateAnimator()
|
|
254
|
+
{
|
|
255
|
+
if (_animator == null) return;
|
|
256
|
+
|
|
257
|
+
_animator.SetFloat(SpeedHash, _currentSpeed / runSpeed);
|
|
258
|
+
_animator.SetBool(GroundedHash, _isGrounded);
|
|
259
|
+
|
|
260
|
+
if (!_isGrounded && _velocity.y < -1f)
|
|
261
|
+
{
|
|
262
|
+
_animator.SetBool(FallHash, true);
|
|
263
|
+
}
|
|
264
|
+
else
|
|
265
|
+
{
|
|
266
|
+
_animator.SetBool(FallHash, false);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#endregion
|
|
271
|
+
|
|
272
|
+
void OnDrawGizmosSelected()
|
|
273
|
+
{
|
|
274
|
+
if (groundCheck != null)
|
|
275
|
+
{
|
|
276
|
+
Gizmos.color = _isGrounded ? Color.green : Color.red;
|
|
277
|
+
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## First-Person Controller
|
|
286
|
+
|
|
287
|
+
```csharp
|
|
288
|
+
[RequireComponent(typeof(CharacterController))]
|
|
289
|
+
public class FirstPersonController : MonoBehaviour
|
|
290
|
+
{
|
|
291
|
+
[Header("Movement")]
|
|
292
|
+
[SerializeField] private float walkSpeed = 5f;
|
|
293
|
+
[SerializeField] private float sprintSpeed = 8f;
|
|
294
|
+
[SerializeField] private float crouchSpeed = 2.5f;
|
|
295
|
+
|
|
296
|
+
[Header("Jump")]
|
|
297
|
+
[SerializeField] private float jumpForce = 8f;
|
|
298
|
+
[SerializeField] private float gravity = -20f;
|
|
299
|
+
|
|
300
|
+
[Header("Look")]
|
|
301
|
+
[SerializeField] private float mouseSensitivity = 2f;
|
|
302
|
+
[SerializeField] private float maxLookAngle = 85f;
|
|
303
|
+
[SerializeField] private Transform cameraHolder;
|
|
304
|
+
|
|
305
|
+
[Header("Crouch")]
|
|
306
|
+
[SerializeField] private float standHeight = 2f;
|
|
307
|
+
[SerializeField] private float crouchHeight = 1f;
|
|
308
|
+
[SerializeField] private float crouchTransitionSpeed = 10f;
|
|
309
|
+
|
|
310
|
+
[Header("Head Bob")]
|
|
311
|
+
[SerializeField] private bool enableHeadBob = true;
|
|
312
|
+
[SerializeField] private float bobFrequency = 1.5f;
|
|
313
|
+
[SerializeField] private float bobAmplitude = 0.05f;
|
|
314
|
+
|
|
315
|
+
private CharacterController _controller;
|
|
316
|
+
private Vector3 _velocity;
|
|
317
|
+
private float _xRotation;
|
|
318
|
+
private bool _isCrouching;
|
|
319
|
+
private float _currentHeight;
|
|
320
|
+
private float _bobTimer;
|
|
321
|
+
private Vector3 _cameraDefaultPos;
|
|
322
|
+
|
|
323
|
+
void Start()
|
|
324
|
+
{
|
|
325
|
+
_controller = GetComponent<CharacterController>();
|
|
326
|
+
_currentHeight = standHeight;
|
|
327
|
+
_cameraDefaultPos = cameraHolder.localPosition;
|
|
328
|
+
|
|
329
|
+
Cursor.lockState = CursorLockMode.Locked;
|
|
330
|
+
Cursor.visible = false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
void Update()
|
|
334
|
+
{
|
|
335
|
+
HandleLook();
|
|
336
|
+
HandleMovement();
|
|
337
|
+
HandleCrouch();
|
|
338
|
+
HandleHeadBob();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private void HandleLook()
|
|
342
|
+
{
|
|
343
|
+
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
|
|
344
|
+
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
|
|
345
|
+
|
|
346
|
+
// Apply sensitivity from settings
|
|
347
|
+
mouseX *= SettingsManager.Instance?.Settings.MouseSensitivity ?? 1f;
|
|
348
|
+
mouseY *= SettingsManager.Instance?.Settings.MouseSensitivity ?? 1f;
|
|
349
|
+
|
|
350
|
+
// Invert Y if setting enabled
|
|
351
|
+
if (SettingsManager.Instance?.Settings.InvertY ?? false)
|
|
352
|
+
{
|
|
353
|
+
mouseY = -mouseY;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Vertical rotation (camera)
|
|
357
|
+
_xRotation -= mouseY;
|
|
358
|
+
_xRotation = Mathf.Clamp(_xRotation, -maxLookAngle, maxLookAngle);
|
|
359
|
+
cameraHolder.localRotation = Quaternion.Euler(_xRotation, 0, 0);
|
|
360
|
+
|
|
361
|
+
// Horizontal rotation (body)
|
|
362
|
+
transform.Rotate(Vector3.up * mouseX);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private void HandleMovement()
|
|
366
|
+
{
|
|
367
|
+
bool isGrounded = _controller.isGrounded;
|
|
368
|
+
|
|
369
|
+
if (isGrounded && _velocity.y < 0)
|
|
370
|
+
{
|
|
371
|
+
_velocity.y = -2f;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Input
|
|
375
|
+
float h = Input.GetAxisRaw("Horizontal");
|
|
376
|
+
float v = Input.GetAxisRaw("Vertical");
|
|
377
|
+
|
|
378
|
+
Vector3 move = transform.right * h + transform.forward * v;
|
|
379
|
+
move = move.normalized;
|
|
380
|
+
|
|
381
|
+
// Speed selection
|
|
382
|
+
float speed = walkSpeed;
|
|
383
|
+
if (_isCrouching)
|
|
384
|
+
speed = crouchSpeed;
|
|
385
|
+
else if (Input.GetKey(KeyCode.LeftShift) && v > 0)
|
|
386
|
+
speed = sprintSpeed;
|
|
387
|
+
|
|
388
|
+
_controller.Move(move * speed * Time.deltaTime);
|
|
389
|
+
|
|
390
|
+
// Jump
|
|
391
|
+
if (Input.GetButtonDown("Jump") && isGrounded && !_isCrouching)
|
|
392
|
+
{
|
|
393
|
+
_velocity.y = Mathf.Sqrt(jumpForce * -2f * gravity);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Gravity
|
|
397
|
+
_velocity.y += gravity * Time.deltaTime;
|
|
398
|
+
_controller.Move(_velocity * Time.deltaTime);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private void HandleCrouch()
|
|
402
|
+
{
|
|
403
|
+
if (Input.GetKeyDown(KeyCode.C) || Input.GetKeyDown(KeyCode.LeftControl))
|
|
404
|
+
{
|
|
405
|
+
_isCrouching = !_isCrouching;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
float targetHeight = _isCrouching ? crouchHeight : standHeight;
|
|
409
|
+
|
|
410
|
+
// Check ceiling before standing
|
|
411
|
+
if (!_isCrouching && _currentHeight < standHeight)
|
|
412
|
+
{
|
|
413
|
+
if (Physics.Raycast(transform.position, Vector3.up, standHeight - crouchHeight + 0.1f))
|
|
414
|
+
{
|
|
415
|
+
_isCrouching = true;
|
|
416
|
+
targetHeight = crouchHeight;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
_currentHeight = Mathf.Lerp(_currentHeight, targetHeight, crouchTransitionSpeed * Time.deltaTime);
|
|
421
|
+
_controller.height = _currentHeight;
|
|
422
|
+
_controller.center = new Vector3(0, _currentHeight / 2f, 0);
|
|
423
|
+
|
|
424
|
+
// Adjust camera
|
|
425
|
+
cameraHolder.localPosition = new Vector3(
|
|
426
|
+
_cameraDefaultPos.x,
|
|
427
|
+
_currentHeight - 0.2f,
|
|
428
|
+
_cameraDefaultPos.z
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private void HandleHeadBob()
|
|
433
|
+
{
|
|
434
|
+
if (!enableHeadBob) return;
|
|
435
|
+
|
|
436
|
+
bool isMoving = _controller.velocity.magnitude > 0.1f && _controller.isGrounded;
|
|
437
|
+
|
|
438
|
+
if (isMoving)
|
|
439
|
+
{
|
|
440
|
+
_bobTimer += Time.deltaTime * bobFrequency * (_isCrouching ? 0.5f : 1f);
|
|
441
|
+
|
|
442
|
+
float bobOffsetY = Mathf.Sin(_bobTimer * Mathf.PI * 2) * bobAmplitude;
|
|
443
|
+
float bobOffsetX = Mathf.Cos(_bobTimer * Mathf.PI) * bobAmplitude * 0.5f;
|
|
444
|
+
|
|
445
|
+
cameraHolder.localPosition += new Vector3(bobOffsetX, bobOffsetY, 0);
|
|
446
|
+
}
|
|
447
|
+
else
|
|
448
|
+
{
|
|
449
|
+
_bobTimer = 0;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Advanced Mechanics
|
|
458
|
+
|
|
459
|
+
### Dash/Dodge
|
|
460
|
+
|
|
461
|
+
```csharp
|
|
462
|
+
public class DashController : MonoBehaviour
|
|
463
|
+
{
|
|
464
|
+
[SerializeField] private float dashSpeed = 20f;
|
|
465
|
+
[SerializeField] private float dashDuration = 0.2f;
|
|
466
|
+
[SerializeField] private float dashCooldown = 1f;
|
|
467
|
+
[SerializeField] private int maxDashCharges = 2;
|
|
468
|
+
[SerializeField] private float chargeRegenTime = 2f;
|
|
469
|
+
|
|
470
|
+
[SerializeField] private AnimationCurve dashCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
|
|
471
|
+
|
|
472
|
+
private CharacterController _controller;
|
|
473
|
+
private int _dashCharges;
|
|
474
|
+
private float _lastDashTime;
|
|
475
|
+
private float _chargeRegenTimer;
|
|
476
|
+
private bool _isDashing;
|
|
477
|
+
|
|
478
|
+
void Start()
|
|
479
|
+
{
|
|
480
|
+
_controller = GetComponent<CharacterController>();
|
|
481
|
+
_dashCharges = maxDashCharges;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
void Update()
|
|
485
|
+
{
|
|
486
|
+
RegenerateDashCharges();
|
|
487
|
+
|
|
488
|
+
if (Input.GetKeyDown(KeyCode.Space) && CanDash())
|
|
489
|
+
{
|
|
490
|
+
StartCoroutine(Dash());
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private bool CanDash()
|
|
495
|
+
{
|
|
496
|
+
return !_isDashing
|
|
497
|
+
&& _dashCharges > 0
|
|
498
|
+
&& Time.time - _lastDashTime >= dashCooldown;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private IEnumerator Dash()
|
|
502
|
+
{
|
|
503
|
+
_isDashing = true;
|
|
504
|
+
_dashCharges--;
|
|
505
|
+
_lastDashTime = Time.time;
|
|
506
|
+
|
|
507
|
+
// Determine dash direction
|
|
508
|
+
Vector3 dashDir = GetDashDirection();
|
|
509
|
+
|
|
510
|
+
// Invincibility during dash (optional)
|
|
511
|
+
// GetComponent<Health>().SetInvincible(true);
|
|
512
|
+
|
|
513
|
+
float elapsed = 0f;
|
|
514
|
+
|
|
515
|
+
while (elapsed < dashDuration)
|
|
516
|
+
{
|
|
517
|
+
float curveValue = dashCurve.Evaluate(elapsed / dashDuration);
|
|
518
|
+
Vector3 move = dashDir * dashSpeed * curveValue * Time.deltaTime;
|
|
519
|
+
|
|
520
|
+
_controller.Move(move);
|
|
521
|
+
|
|
522
|
+
elapsed += Time.deltaTime;
|
|
523
|
+
yield return null;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// GetComponent<Health>().SetInvincible(false);
|
|
527
|
+
_isDashing = false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private Vector3 GetDashDirection()
|
|
531
|
+
{
|
|
532
|
+
float h = Input.GetAxisRaw("Horizontal");
|
|
533
|
+
float v = Input.GetAxisRaw("Vertical");
|
|
534
|
+
|
|
535
|
+
Vector3 inputDir = new Vector3(h, 0, v).normalized;
|
|
536
|
+
|
|
537
|
+
if (inputDir.magnitude < 0.1f)
|
|
538
|
+
{
|
|
539
|
+
// Dash forward if no input
|
|
540
|
+
return transform.forward;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Dash in input direction relative to camera
|
|
544
|
+
return Camera.main.transform.TransformDirection(inputDir);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private void RegenerateDashCharges()
|
|
548
|
+
{
|
|
549
|
+
if (_dashCharges < maxDashCharges)
|
|
550
|
+
{
|
|
551
|
+
_chargeRegenTimer += Time.deltaTime;
|
|
552
|
+
|
|
553
|
+
if (_chargeRegenTimer >= chargeRegenTime)
|
|
554
|
+
{
|
|
555
|
+
_dashCharges++;
|
|
556
|
+
_chargeRegenTimer = 0f;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Wall Running
|
|
564
|
+
|
|
565
|
+
```csharp
|
|
566
|
+
public class WallRunController : MonoBehaviour
|
|
567
|
+
{
|
|
568
|
+
[Header("Wall Detection")]
|
|
569
|
+
[SerializeField] private float wallCheckDistance = 0.7f;
|
|
570
|
+
[SerializeField] private LayerMask wallMask;
|
|
571
|
+
[SerializeField] private float minWallRunHeight = 1f;
|
|
572
|
+
|
|
573
|
+
[Header("Wall Run")]
|
|
574
|
+
[SerializeField] private float wallRunSpeed = 10f;
|
|
575
|
+
[SerializeField] private float wallRunGravity = -2f;
|
|
576
|
+
[SerializeField] private float maxWallRunTime = 2f;
|
|
577
|
+
[SerializeField] private float wallJumpForce = 12f;
|
|
578
|
+
[SerializeField] private float wallJumpSideForce = 8f;
|
|
579
|
+
|
|
580
|
+
[Header("Camera")]
|
|
581
|
+
[SerializeField] private float cameraTiltAngle = 15f;
|
|
582
|
+
[SerializeField] private float tiltSpeed = 10f;
|
|
583
|
+
|
|
584
|
+
private CharacterController _controller;
|
|
585
|
+
private bool _isWallRunning;
|
|
586
|
+
private bool _isWallLeft;
|
|
587
|
+
private bool _isWallRight;
|
|
588
|
+
private float _wallRunTimer;
|
|
589
|
+
private RaycastHit _wallHit;
|
|
590
|
+
|
|
591
|
+
void Update()
|
|
592
|
+
{
|
|
593
|
+
CheckWalls();
|
|
594
|
+
HandleWallRun();
|
|
595
|
+
UpdateCameraTilt();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private void CheckWalls()
|
|
599
|
+
{
|
|
600
|
+
_isWallLeft = Physics.Raycast(
|
|
601
|
+
transform.position,
|
|
602
|
+
-transform.right,
|
|
603
|
+
out _wallHit,
|
|
604
|
+
wallCheckDistance,
|
|
605
|
+
wallMask
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
if (!_isWallLeft)
|
|
609
|
+
{
|
|
610
|
+
_isWallRight = Physics.Raycast(
|
|
611
|
+
transform.position,
|
|
612
|
+
transform.right,
|
|
613
|
+
out _wallHit,
|
|
614
|
+
wallCheckDistance,
|
|
615
|
+
wallMask
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
else
|
|
619
|
+
{
|
|
620
|
+
_isWallRight = false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private void HandleWallRun()
|
|
625
|
+
{
|
|
626
|
+
bool canWallRun = (_isWallLeft || _isWallRight)
|
|
627
|
+
&& !_controller.isGrounded
|
|
628
|
+
&& transform.position.y > minWallRunHeight
|
|
629
|
+
&& Input.GetAxisRaw("Vertical") > 0;
|
|
630
|
+
|
|
631
|
+
if (canWallRun && !_isWallRunning)
|
|
632
|
+
{
|
|
633
|
+
StartWallRun();
|
|
634
|
+
}
|
|
635
|
+
else if (!canWallRun && _isWallRunning)
|
|
636
|
+
{
|
|
637
|
+
StopWallRun();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (_isWallRunning)
|
|
641
|
+
{
|
|
642
|
+
_wallRunTimer += Time.deltaTime;
|
|
643
|
+
|
|
644
|
+
if (_wallRunTimer >= maxWallRunTime)
|
|
645
|
+
{
|
|
646
|
+
StopWallRun();
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Wall run movement
|
|
651
|
+
Vector3 wallForward = Vector3.Cross(_wallHit.normal, Vector3.up);
|
|
652
|
+
|
|
653
|
+
// Ensure we're running in the right direction
|
|
654
|
+
if (Vector3.Dot(wallForward, transform.forward) < 0)
|
|
655
|
+
{
|
|
656
|
+
wallForward = -wallForward;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
_controller.Move(wallForward * wallRunSpeed * Time.deltaTime);
|
|
660
|
+
|
|
661
|
+
// Reduced gravity
|
|
662
|
+
_controller.Move(Vector3.up * wallRunGravity * Time.deltaTime);
|
|
663
|
+
|
|
664
|
+
// Wall jump
|
|
665
|
+
if (Input.GetButtonDown("Jump"))
|
|
666
|
+
{
|
|
667
|
+
WallJump();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private void StartWallRun()
|
|
673
|
+
{
|
|
674
|
+
_isWallRunning = true;
|
|
675
|
+
_wallRunTimer = 0f;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
private void StopWallRun()
|
|
679
|
+
{
|
|
680
|
+
_isWallRunning = false;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private void WallJump()
|
|
684
|
+
{
|
|
685
|
+
Vector3 jumpDirection = _wallHit.normal * wallJumpSideForce + Vector3.up * wallJumpForce;
|
|
686
|
+
|
|
687
|
+
// Apply force through velocity (you'll need to integrate this with your main controller)
|
|
688
|
+
GetComponent<ThirdPersonController>().SetVelocity(jumpDirection);
|
|
689
|
+
|
|
690
|
+
StopWallRun();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private void UpdateCameraTilt()
|
|
694
|
+
{
|
|
695
|
+
float targetTilt = 0f;
|
|
696
|
+
|
|
697
|
+
if (_isWallRunning)
|
|
698
|
+
{
|
|
699
|
+
targetTilt = _isWallLeft ? cameraTiltAngle : -cameraTiltAngle;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Smooth camera tilt
|
|
703
|
+
// camera.transform.localRotation = ...
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Ledge Climbing
|
|
709
|
+
|
|
710
|
+
```csharp
|
|
711
|
+
public class LedgeClimbController : MonoBehaviour
|
|
712
|
+
{
|
|
713
|
+
[Header("Detection")]
|
|
714
|
+
[SerializeField] private float ledgeCheckDistance = 1f;
|
|
715
|
+
[SerializeField] private float ledgeCheckHeight = 2f;
|
|
716
|
+
[SerializeField] private LayerMask climbMask;
|
|
717
|
+
|
|
718
|
+
[Header("Climb")]
|
|
719
|
+
[SerializeField] private float climbDuration = 0.5f;
|
|
720
|
+
[SerializeField] private AnimationCurve climbCurve;
|
|
721
|
+
|
|
722
|
+
private CharacterController _controller;
|
|
723
|
+
private bool _isClimbing;
|
|
724
|
+
|
|
725
|
+
void Update()
|
|
726
|
+
{
|
|
727
|
+
if (!_isClimbing && CanClimb())
|
|
728
|
+
{
|
|
729
|
+
if (Input.GetKeyDown(KeyCode.E) || (Input.GetAxisRaw("Vertical") > 0 && Input.GetButtonDown("Jump")))
|
|
730
|
+
{
|
|
731
|
+
StartCoroutine(ClimbLedge());
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
private bool CanClimb()
|
|
737
|
+
{
|
|
738
|
+
if (_controller.isGrounded) return false;
|
|
739
|
+
|
|
740
|
+
// Check for wall in front
|
|
741
|
+
if (!Physics.Raycast(transform.position + Vector3.up * 0.5f, transform.forward, ledgeCheckDistance, climbMask))
|
|
742
|
+
{
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Check for ledge above
|
|
747
|
+
Vector3 ledgeCheckStart = transform.position + Vector3.up * ledgeCheckHeight + transform.forward * ledgeCheckDistance;
|
|
748
|
+
if (!Physics.Raycast(ledgeCheckStart, Vector3.down, out RaycastHit hit, ledgeCheckHeight - 0.5f, climbMask))
|
|
749
|
+
{
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Check if there's space on top
|
|
754
|
+
Vector3 targetPos = hit.point + Vector3.up * 0.1f;
|
|
755
|
+
return !Physics.CheckSphere(targetPos, 0.3f, climbMask);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private IEnumerator ClimbLedge()
|
|
759
|
+
{
|
|
760
|
+
_isClimbing = true;
|
|
761
|
+
_controller.enabled = false;
|
|
762
|
+
|
|
763
|
+
// Find target position
|
|
764
|
+
Vector3 ledgeCheckStart = transform.position + Vector3.up * ledgeCheckHeight + transform.forward * ledgeCheckDistance;
|
|
765
|
+
Physics.Raycast(ledgeCheckStart, Vector3.down, out RaycastHit hit, ledgeCheckHeight - 0.5f, climbMask);
|
|
766
|
+
|
|
767
|
+
Vector3 startPos = transform.position;
|
|
768
|
+
Vector3 targetPos = hit.point + Vector3.up * 0.1f + transform.forward * 0.5f;
|
|
769
|
+
|
|
770
|
+
// Intermediate position (hands on ledge)
|
|
771
|
+
Vector3 hangPos = new Vector3(
|
|
772
|
+
targetPos.x - transform.forward.x * 0.3f,
|
|
773
|
+
targetPos.y - 0.5f,
|
|
774
|
+
targetPos.z - transform.forward.z * 0.3f
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
float elapsed = 0f;
|
|
778
|
+
float halfDuration = climbDuration * 0.5f;
|
|
779
|
+
|
|
780
|
+
// Rise to hang position
|
|
781
|
+
while (elapsed < halfDuration)
|
|
782
|
+
{
|
|
783
|
+
float t = elapsed / halfDuration;
|
|
784
|
+
transform.position = Vector3.Lerp(startPos, hangPos, climbCurve.Evaluate(t));
|
|
785
|
+
elapsed += Time.deltaTime;
|
|
786
|
+
yield return null;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Climb up
|
|
790
|
+
elapsed = 0f;
|
|
791
|
+
while (elapsed < halfDuration)
|
|
792
|
+
{
|
|
793
|
+
float t = elapsed / halfDuration;
|
|
794
|
+
transform.position = Vector3.Lerp(hangPos, targetPos, climbCurve.Evaluate(t));
|
|
795
|
+
elapsed += Time.deltaTime;
|
|
796
|
+
yield return null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
transform.position = targetPos;
|
|
800
|
+
_controller.enabled = true;
|
|
801
|
+
_isClimbing = false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## Input Buffering
|
|
809
|
+
|
|
810
|
+
```csharp
|
|
811
|
+
public class InputBuffer
|
|
812
|
+
{
|
|
813
|
+
private struct BufferedInput
|
|
814
|
+
{
|
|
815
|
+
public string Action;
|
|
816
|
+
public float Timestamp;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private Queue<BufferedInput> _buffer = new();
|
|
820
|
+
private float _bufferWindow = 0.15f;
|
|
821
|
+
|
|
822
|
+
public void BufferInput(string action)
|
|
823
|
+
{
|
|
824
|
+
_buffer.Enqueue(new BufferedInput
|
|
825
|
+
{
|
|
826
|
+
Action = action,
|
|
827
|
+
Timestamp = Time.time
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
public bool ConsumeInput(string action)
|
|
832
|
+
{
|
|
833
|
+
// Clean expired inputs
|
|
834
|
+
while (_buffer.Count > 0 && Time.time - _buffer.Peek().Timestamp > _bufferWindow)
|
|
835
|
+
{
|
|
836
|
+
_buffer.Dequeue();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Check for action
|
|
840
|
+
foreach (var input in _buffer)
|
|
841
|
+
{
|
|
842
|
+
if (input.Action == action)
|
|
843
|
+
{
|
|
844
|
+
// Remove consumed input
|
|
845
|
+
var temp = new Queue<BufferedInput>();
|
|
846
|
+
while (_buffer.Count > 0)
|
|
847
|
+
{
|
|
848
|
+
var item = _buffer.Dequeue();
|
|
849
|
+
if (item.Action != action || temp.Count > 0)
|
|
850
|
+
{
|
|
851
|
+
temp.Enqueue(item);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
_buffer = temp;
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Usage in controller
|
|
864
|
+
private InputBuffer _inputBuffer = new();
|
|
865
|
+
|
|
866
|
+
void Update()
|
|
867
|
+
{
|
|
868
|
+
if (Input.GetButtonDown("Jump"))
|
|
869
|
+
{
|
|
870
|
+
_inputBuffer.BufferInput("Jump");
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Later, when checking if we can jump:
|
|
874
|
+
if (_isGrounded && _inputBuffer.ConsumeInput("Jump"))
|
|
875
|
+
{
|
|
876
|
+
DoJump();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## Quick Reference
|
|
884
|
+
|
|
885
|
+
| Mechanic | Key Values |
|
|
886
|
+
|----------|------------|
|
|
887
|
+
| **Walk Speed** | 4-5 m/s |
|
|
888
|
+
| **Run Speed** | 8-10 m/s |
|
|
889
|
+
| **Jump Height** | 1-2 m |
|
|
890
|
+
| **Gravity** | -20 to -30 |
|
|
891
|
+
| **Coyote Time** | 0.1-0.2 s |
|
|
892
|
+
| **Jump Buffer** | 0.1-0.15 s |
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## Anti-Patterns
|
|
897
|
+
|
|
898
|
+
| ❌ Don't | ✅ Do |
|
|
899
|
+
|----------|-------|
|
|
900
|
+
| Check `Input.GetKey` directly | Use input buffering |
|
|
901
|
+
| Hard-coded values | SerializeField everything |
|
|
902
|
+
| No coyote time | Always add tolerance |
|
|
903
|
+
| Physics in Update | Use FixedUpdate for physics |
|
|
904
|
+
| Instant state changes | Smooth transitions |
|
|
905
|
+
|
|
906
|
+
---
|
|
907
|
+
|
|
908
|
+
> **Remember:** Good movement feels responsive but forgiving. Add tiny cheats for the player.
|