This commit is contained in:
Scove
2026-03-27 11:21:12 +07:00
parent db89523199
commit 7b27ae51bc
26 changed files with 305 additions and 859 deletions

View File

@@ -1,49 +0,0 @@
using UnityEngine;
namespace OnlyScove.Scripts
{
/// <summary>
/// A version of PlayerStateMachine that simulates constant forward or backward input.
/// </summary>
public class AutoPlayerStateMachine : PlayerStateMachine
{
[Header("Auto Pilot Settings")]
public bool alwaysMoveForward = true;
public bool alwaysRun = true;
public bool isRed = false; // New property to differentiate movement direction
private class FakeInputReader : InputReader
{
public Vector2 ForcedMove { get; set; }
public bool ForcedSprint { get; set; }
public override Vector2 MoveInput => ForcedMove;
public override bool IsSprintHeld => ForcedSprint;
}
private FakeInputReader fakeInput;
public override InputReader Input => fakeInput;
protected override void Awake()
{
fakeInput = gameObject.AddComponent<FakeInputReader>();
base.Awake();
}
protected override void Update()
{
if (fakeInput != null)
{
// Logic updated: isRed moves backward (Vector2.down), others move forward (Vector2.up)
fakeInput.ForcedMove = (isRed)
? (alwaysMoveForward ? Vector2.down : Vector2.zero)
: (alwaysMoveForward ? Vector2.up : Vector2.zero);
fakeInput.ForcedSprint = alwaysRun;
}
base.Update();
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: dbe8532b2e328a249bf61ae957c92486

View File

@@ -1,129 +0,0 @@
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine.Jobs;
using Unity.Burst;
namespace Elbyss.Optimization
{
/// <summary>
/// Manages 10,000+ objects using C# Job System and Burst Compiler.
/// This avoids the overhead of 10,000 individual Update() calls.
/// </summary>
public class JobsMovementManager : MonoBehaviour
{
[Header("Spawn Settings")]
public GameObject prefab;
public int objectCount = 10000;
public float spacing = 1.5f;
[Header("Movement Settings")]
public float speed = 5f;
private TransformAccessArray transformAccessArray;
private NativeArray<Vector3> directions;
private bool isInitialized = false;
private void Start()
{
// Optional: Start automatically or via Context Menu
// Setup(objectCount);
}
[ContextMenu("Setup 10k Objects")]
public void InitialSetup()
{
Setup(objectCount);
}
public void Setup(int count)
{
if (isInitialized) Cleanup();
objectCount = count;
Transform[] transforms = new Transform[objectCount];
directions = new NativeArray<Vector3>(objectCount, Allocator.Persistent);
int rowSize = Mathf.CeilToInt(Mathf.Sqrt(objectCount));
for (int i = 0; i < objectCount; i++)
{
float x = (i % rowSize) * spacing;
float z = (i / rowSize) * spacing;
Vector3 pos = transform.position + new Vector3(x, 0, z);
GameObject go = Instantiate(prefab, pos, Quaternion.identity, this.transform);
transforms[i] = go.transform;
// Set alternating directions
directions[i] = (i % 2 == 0) ? Vector3.forward : Vector3.back;
// CRITICAL OPTIMIZATION: Disable components that are too heavy for 10k objects
if (go.TryGetComponent<Animator>(out var anim)) anim.enabled = false;
if (go.TryGetComponent<CharacterController>(out var cc)) cc.enabled = false;
// Disable all other custom scripts
MonoBehaviour[] scripts = go.GetComponents<MonoBehaviour>();
foreach (var s in scripts)
{
if (s != this) s.enabled = false;
}
}
transformAccessArray = new TransformAccessArray(transforms);
isInitialized = true;
Debug.Log($"Initialized {objectCount} objects with Job System.");
}
private void Update()
{
if (!isInitialized) return;
// Create the movement job
var job = new MovementJob
{
DeltaTime = Time.deltaTime,
Speed = speed,
Directions = directions
};
// Schedule the job to run in parallel on all available CPU cores
// transformAccessArray allows the job to modify Transform data directly
JobHandle handle = job.Schedule(transformAccessArray);
// This ensures the job is finished before the frame ends
// In a real scenario, you might want to call Complete() in LateUpdate or next frame
// but for simple movement, scheduling and completing in Update is fine.
handle.Complete();
}
private void OnDestroy()
{
Cleanup();
}
private void Cleanup()
{
if (isInitialized)
{
if (transformAccessArray.isCreated) transformAccessArray.Dispose();
if (directions.IsCreated) directions.Dispose();
isInitialized = false;
}
}
[BurstCompile] // This attribute tells the Burst compiler to optimize this job into machine code
struct MovementJob : IJobParallelForTransform
{
public float DeltaTime;
public float Speed;
[ReadOnly] public NativeArray<Vector3> Directions;
public void Execute(int index, TransformAccess transform)
{
// Directly modify the transform position in parallel
transform.position += Directions[index] * Speed * DeltaTime;
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 41899df442467714dbee462ae451773a

View File

@@ -1,153 +0,0 @@
// using GPUInstancerPro;
// using Unity.Burst;
// using Unity.Collections;
// using Unity.Jobs;
// using Unity.Mathematics;
// using UnityEngine;
//
// namespace Elbyss.Optimization
// {
// public class MassiveSpawner : MonoBehaviour
// {
// [Header("Spawn Settings")]
// public GameObject prefab;
// public GPUIProfile profile;
// public int instanceCount = 100000;
// public float spacing = 1.5f;
//
// [Header("Update Settings")]
// public bool runUpdate = true;
// public float movementSpeed = 1.0f;
// public float amplitude = 2.0f;
//
// private int _rendererKey;
// private NativeArray<Matrix4x4> _matrices;
// private JobHandle _jobHandle;
// private bool _isInitialized;
//
// private void OnEnable()
// {
// Initialize();
// }
//
// private void OnDisable()
// {
// Dispose();
// }
//
// private void OnValidate()
// {
// if (Application.isPlaying && _isInitialized)
// {
// // Re-initialize if count changes during play (optional, but good for testing)
// Initialize();
// }
// }
//
// public void Initialize()
// {
// Dispose();
// if (prefab == null) return;
//
// if (GPUICoreAPI.RegisterRenderer(this, prefab, profile, out _rendererKey))
// {
// _matrices = new NativeArray<Matrix4x4>(instanceCount, Allocator.Persistent);
//
// // Initial generation
// GenerateMatrices(0);
// _jobHandle.Complete();
// GPUICoreAPI.SetTransformBufferData(_rendererKey, _matrices);
//
// _isInitialized = true;
// Debug.Log($"[MassiveSpawner] Registered {instanceCount} instances with key {_rendererKey}");
// }
// else
// {
// Debug.LogError("[MassiveSpawner] Failed to register renderer!");
// }
// }
//
// private void Update()
// {
// if (!_isInitialized || _rendererKey == 0) return;
//
// if (runUpdate)
// {
// // Complete previous frame's work if any
// _jobHandle.Complete();
//
// // Apply updated matrices to GPUI
// GPUICoreAPI.SetTransformBufferData(_rendererKey, _matrices);
//
// // Schedule next frame's work
// GenerateMatrices(Time.time);
// }
// }
//
// private void GenerateMatrices(float time)
// {
// int side = Mathf.CeilToInt(Mathf.Sqrt(instanceCount));
//
// var job = new MatrixUpdateJob
// {
// matrices = _matrices,
// side = side,
// spacing = spacing,
// time = time,
// speed = movementSpeed,
// amplitude = amplitude,
// origin = transform.position
// };
//
// _jobHandle = job.Schedule(instanceCount, 64);
// }
//
// public void Dispose()
// {
// _jobHandle.Complete();
// if (_rendererKey != 0)
// {
// GPUICoreAPI.DisposeRenderer(_rendererKey);
// _rendererKey = 0;
// }
//
// if (_matrices.IsCreated)
// {
// _matrices.Dispose();
// }
// _isInitialized = false;
// }
//
// [BurstCompile]
// struct MatrixUpdateJob : IJobParallelFor
// {
// public NativeArray<Matrix4x4> matrices;
// public int side;
// public float spacing;
// public float time;
// public float speed;
// public float amplitude;
// public Vector3 origin;
//
// public void Execute(int index)
// {
// int x = index % side;
// int z = index / side;
//
// float xPos = x * spacing;
// float zPos = z * spacing;
//
// // Add some animation to prove it's updating
// float yPos = math.sin(time * speed + (xPos + zPos) * 0.1f) * amplitude;
//
// Vector3 pos = origin + new Vector3(xPos, yPos, zPos);
//
// // Simple rotation based on time
// float angle = (time * speed * 10f + index) % 360f;
// Quaternion rot = Quaternion.Euler(0, angle, 0);
//
// matrices[index] = Matrix4x4.TRS(pos, rot, Vector3.one);
// }
// }
// }
// }

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 50831f537cbccac4c9bf4067b6b158c7

View File

@@ -1,34 +0,0 @@
using UnityEngine;
using TMPro;
namespace Elbyss.Optimization
{
public class PerformanceHUD : MonoBehaviour
{
private float deltaTime = 0.0f;
private string displayFormat = "{0:0.0} ms ({1:0.} fps)";
private void Update()
{
deltaTime += (Time.unscaledDeltaTime - deltaTime) * 0.1f;
}
private void OnGUI()
{
int w = Screen.width, h = Screen.height;
GUIStyle style = new GUIStyle();
Rect rect = new Rect(10, 10, w, h * 2 / 100);
style.alignment = TextAnchor.UpperLeft;
style.fontSize = h * 2 / 50;
style.normal.textColor = new Color(0.0f, 1.0f, 0.5f, 1.0f);
float msec = deltaTime * 1000.0f;
float fps = 1.0f / deltaTime;
string text = string.Format(displayFormat, msec, fps);
GUI.Label(rect, text, style);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 9c9a374a3ab089d41a6784b1ffad3b6f

View File

@@ -1,85 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
using OnlyScove.Scripts;
namespace Elbyss.Optimization
{
public class StressTestSpawner : MonoBehaviour
{
[Header("Spawn Settings")]
public GameObject prefabToSpawn;
public int spawnLimit = 1000;
public int spawnsPerFrame = 10;
public float spacing = 2.0f;
[Header("Testing Mode")]
public bool useAutoStateMachine = true;
[Header("Optimization Options")]
public bool stripHeavyComponents = false;
private List<GameObject> spawnedObjects = new List<GameObject>();
private int currentSpawnCount = 0;
private bool isSpawning = false;
private void Update()
{
if (isSpawning && currentSpawnCount < spawnLimit)
{
for (int i = 0; i < spawnsPerFrame && currentSpawnCount < spawnLimit; i++)
{
SpawnObject();
}
}
}
[ContextMenu("Start Stress Test")]
public void StartStressTest() => isSpawning = true;
[ContextMenu("Clear All")]
public void ClearAll()
{
foreach (var obj in spawnedObjects) if (obj != null) Destroy(obj);
spawnedObjects.Clear();
currentSpawnCount = 0;
isSpawning = false;
}
private void SpawnObject()
{
if (prefabToSpawn == null) return;
int rowSize = Mathf.CeilToInt(Mathf.Sqrt(spawnLimit));
float x = (currentSpawnCount % rowSize) * spacing;
float z = (currentSpawnCount / rowSize) * spacing;
Vector3 spawnPos = transform.position + new Vector3(x, 0, z);
GameObject newObj = Instantiate(prefabToSpawn, spawnPos, transform.rotation);
if (useAutoStateMachine)
{
var realInput = newObj.GetComponent<InputReader>();
if (realInput != null) realInput.enabled = false;
var originalSM = newObj.GetComponent<PlayerStateMachine>();
if (originalSM != null)
{
DestroyImmediate(originalSM);
var autoSM = newObj.AddComponent<AutoPlayerStateMachine>();
autoSM.alwaysMoveForward = true;
autoSM.alwaysRun = true;
}
}
if (stripHeavyComponents)
{
if (newObj.TryGetComponent<Animator>(out var anim)) anim.enabled = false;
if (newObj.TryGetComponent<CharacterController>(out var cc)) cc.enabled = false;
if (newObj.TryGetComponent<PlayerStateMachine>(out var sm)) sm.enabled = false;
}
spawnedObjects.Add(newObj);
currentSpawnCount++;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4a7c5ef310b7f354685dc6706be2d530

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections;
using UnityEngine;
namespace OnlyScove.Scripts
{
public class ParkourController : MonoBehaviour
{
[SerializeField] private InputReader inputReader;
EnvironmentScanner environmentScanner;
Animator animator;
PlayerController playerController;
bool inAction;
private void Awake()
{
inputReader = GetComponent<InputReader>();
environmentScanner = GetComponent<EnvironmentScanner>();
animator = GetComponent<Animator>();
playerController = GetComponent<PlayerController>();
}
private void OnEnable()
{
inputReader.OnJumpEvent += HandleParkour;
}
private void OnDisable()
{
inputReader.OnJumpEvent -= HandleParkour;
}
private void HandleParkour()
{
var hitData = environmentScanner.ObstacleCheck();
if (hitData.forwardHitFound)
{
StartCoroutine(DoParkourAction());
}
}
IEnumerator DoParkourAction()
{
inAction = true;
playerController.SetControl(false);
animator.CrossFade("Step Up", 0.1f);
yield return null;
var animationState = animator.GetNextAnimatorStateInfo(0);
yield return new WaitForSeconds(animationState.length);
playerController.SetControl(true);
inAction = false;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 0407ea3fbe445ac43b4f1ca3077ce283

View File

@@ -1,134 +0,0 @@
using System;
using Unity.VisualScripting;
using UnityEngine;
namespace OnlyScove.Scripts
{
public class PlayerController : MonoBehaviour
{
private static readonly int MoveAmount = Animator.StringToHash("moveAmount");
[SerializeField] private InputReader inputReader;
[SerializeField] private float rotationSpeed = 500f;
[SerializeField] private float moveSpeed = 10f;
[SerializeField] private float jumpHeight = 2f;
[SerializeField] private float animationDamping = 0.2f;
[SerializeField] private float groundCheckRadius = 0.2f;
[SerializeField] private Vector3 groundCheckOffset;
[SerializeField] private LayerMask groundMask;
CameraController cameraController;
Animator animator;
private CharacterController characterController;
Quaternion targetRotation;
private float horizontal;
private float vertical;
bool isGrounded;
private bool wasGrounded;
private bool hasControl = true;
private float ySpeed;
private void Awake()
{
if (Camera.main != null) cameraController = Camera.main.GetComponent<CameraController>();
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
}
private void OnEnable()
{
inputReader.OnJumpEvent += HandleJump;
}
private void OnDisable()
{
inputReader.OnJumpEvent -= HandleJump;
}
private void HandleJump()
{
if (isGrounded && hasControl)
{
// Công thức tính vận tốc nhảy: v = sqrt(h * -2 * g)
ySpeed = Mathf.Sqrt(jumpHeight * -2f * Physics.gravity.y);
}
}
private void Update()
{
horizontal = inputReader.MoveInput.x;
vertical = inputReader.MoveInput.y;
float moveAmount = Mathf.Clamp01(Math.Abs(horizontal) + Math.Abs(vertical));
var moveInput = (new Vector3(horizontal, 0, vertical)).normalized;
var moveDirection = cameraController.PlanarRotation * moveInput;
if (!hasControl)
return;
wasGrounded = isGrounded;
GroundCheck();
// Phát hiện tiếp đất (Landing)
if (isGrounded && !wasGrounded && ySpeed < -1f)
{
// Rung camera khi tiếp đất mạnh
if (cameraController != null)
{
cameraController.Shake(0.2f, 0.15f);
}
}
if (isGrounded && ySpeed < 0)
{
ySpeed = -2f; // Giữ nhân vật dính xuống mặt đất
}
else
{
ySpeed += Physics.gravity.y * Time.deltaTime;
}
var velocity = moveDirection * moveSpeed;
velocity.y = ySpeed;
characterController.Move(velocity * Time.deltaTime);
if (moveAmount > 0)
{
targetRotation = Quaternion.LookRotation(moveDirection);
}
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,
Time.deltaTime * rotationSpeed);
animator.SetFloat(MoveAmount, moveAmount, animationDamping, Time.deltaTime);
}
void GroundCheck()
{
isGrounded =
Physics.CheckSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius, groundMask);
}
public void SetControl(bool control)
{
this.hasControl = control;
characterController.enabled = hasControl;
if (!hasControl)
{
animator.SetFloat(MoveAmount, 0f);
targetRotation = transform.rotation;
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(0, 1, 0, 0.5f);
Gizmos.DrawSphere(transform.TransformPoint(groundCheckOffset), groundCheckRadius);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: cd897e8bcaabdc8408bb6aaf7c037537

View File

@@ -1,65 +0,0 @@
// -- SPINE PROXY 1.0 | Kevin Iglesias --
// This script ensures correct animation display when mixing upper and lower body animations using Unity Avatar Masks.
// Attach this script to the 'B-spineProxy' transform, which is a sibling of the 'B-hips' bone.
// In the 'originalSpine' field, assign the 'B-spine' bone (child of 'B-hips' and parent of 'B-chest').
// By default it will automatically find the 'B-spine' and assign it to the 'originalSpine' field (OnValidate).
// When using a different character rig, manually assign the corresponding spine bone to the 'originalSpine' field and recreate
// 'Rig > B-root > B-spine' structure in your character hierarchy with empty GameObjects.
// More information: https://www.keviniglesias.com/spine-proxy.html
// Contact Support: support@keviniglesias.com
using UnityEngine;
namespace KevinIglesias
{
public class SpineProxy : MonoBehaviour
{
//Assign 'B-spine' (or equivalent) here:
[SerializeField] private Transform originalSpine;
private Quaternion rotationOffset = Quaternion.identity;
#if UNITY_EDITOR
//Attempting to find the original spine bone.
void OnValidate()
{
if(originalSpine == null)
{
Transform parent = transform.parent;
if(parent != null)
{
Transform hips = parent.Find("B-hips");
if(hips != null)
{
Transform spine = hips.Find("B-spine");
if(spine != null)
{
originalSpine = spine;
}
}
}
}
}
#endif
//Match correct orientation on different character rigs
void Awake()
{
if(originalSpine != null)
{//originalSpine.rotation must be the default rotation in your character T-pose when this happens:
rotationOffset = Quaternion.Inverse(transform.rotation) * originalSpine.rotation;
}
}
//Copy rotations from spine proxy bone to the original spine bone.
void LateUpdate()
{
if(originalSpine == null)
{
return;
}
originalSpine.rotation = transform.rotation * rotationOffset;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 682245d9ac89ba4409aa3a92f17f5c6c