🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos de Scripts Unity C#
Exemplos de scripts Unity C# cobrindo movimento do jogador, sistemas UI, física, animações e mecânicas de jogo
💻 Controlador de Jogador csharp
🟢 simple
⭐⭐
Scripts básicos de movimento do jogador, pulo e controlador de personagem
⏱️ 20 min
🏷️ unity, csharp, player controller, movement
Prerequisites:
Basic C#, Unity Editor, Physics concepts
// Unity C# Player Controller Script
using UnityEngine;
using UnityEngine.InputSystem;
// 1. Basic Player Movement Controller
public class PlayerMovement : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float jumpForce = 10f;
[SerializeField] private float groundCheckDistance = 0.1f;
[SerializeField] private LayerMask groundLayer;
private Rigidbody rb;
private bool isGrounded;
private Vector2 moveInput;
private bool jumpInput;
private void Awake()
{
rb = GetComponent<Rigidbody>();
// Freeze rotation to prevent tipping over
if (rb != null)
{
rb.constraints = RigidbodyConstraints.FreezeRotation;
}
}
private void Update()
{
CheckGroundStatus();
HandleInput();
}
private void FixedUpdate()
{
MovePlayer();
HandleJump();
}
private void HandleInput()
{
// Get input from Input System
moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
if (Input.GetButtonDown("Jump"))
{
jumpInput = true;
}
}
private void MovePlayer()
{
if (rb == null) return;
// Calculate movement direction
Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y).normalized;
// Apply movement
Vector3 targetVelocity = moveDirection * moveSpeed;
// Smoothly change velocity
rb.velocity = new Vector3(
targetVelocity.x,
rb.velocity.y,
targetVelocity.z
);
// Rotate player to face movement direction
if (moveDirection != Vector3.zero)
{
Quaternion toRotation = Quaternion.LookRotation(moveDirection, Vector3.up);
transform.rotation = Quaternion.Lerp(transform.rotation, toRotation, Time.deltaTime * 10f);
}
}
private void HandleJump()
{
if (jumpInput && isGrounded && rb != null)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
jumpInput = false;
}
}
private void CheckGroundStatus()
{
isGrounded = Physics.CheckSphere(transform.position, groundCheckDistance, groundLayer);
}
private void OnDrawGizmosSelected()
{
// Draw ground check sphere
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, groundCheckDistance);
}
}
// 2. Advanced Player Controller with CharacterController
public class AdvancedPlayerController : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float walkSpeed = 3f;
[SerializeField] private float runSpeed = 6f;
[SerializeField] private float jumpHeight = 2f;
[SerializeField] private float gravity = -9.81f;
[SerializeField] private float groundDistance = 0.4f;
[SerializeField] private Transform groundCheck;
[SerializeField] private LayerMask groundMask;
[Header("Camera Settings")]
[SerializeField] private float mouseSensitivity = 100f;
[SerializeField] private float maxLookAngle = 90f;
private CharacterController controller;
private Vector3 velocity;
private bool isGrounded;
private float xRotation = 0f;
private Camera playerCamera;
private void Start()
{
controller = GetComponent<CharacterController>();
playerCamera = Camera.main;
// Lock cursor for FPS control
if (Application.isPlaying)
{
Cursor.lockState = CursorLockMode.Locked;
}
}
private void Update()
{
HandleMouseLook();
HandleMovement();
HandleJump();
}
private void HandleMouseLook()
{
if (playerCamera == null) return;
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
// Rotate player around Y axis
transform.Rotate(Vector3.up, mouseX);
// Rotate camera around X axis
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle);
playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
}
private void HandleMovement()
{
if (controller == null) return;
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
// Check if running
bool isRunning = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
float currentSpeed = isRunning ? runSpeed : walkSpeed;
controller.Move(move * currentSpeed * Time.deltaTime);
}
private void HandleJump()
{
if (controller == null) return;
if (Input.GetButtonDown("Jump") && isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
// Apply gravity
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
}
// 3. Third Person Controller
public class ThirdPersonController : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 6f;
[SerializeField] private float rotationSpeed = 500f;
[SerializeField] private float jumpForce = 8f;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float groundCheckRadius = 0.5f;
[Header("Camera Settings")]
[SerializeField] private Transform cameraTarget;
[SerializeField] private float cameraDistance = 5f;
[SerializeField] private float cameraHeight = 2f;
[SerializeField] private float cameraSmoothTime = 0.1f;
private Animator animator;
private Rigidbody rb;
private Vector3 moveInput;
private bool isGrounded;
private Vector3 cameraVelocity;
private Vector3 currentCameraOffset;
private void Awake()
{
animator = GetComponent<Animator>();
rb = GetComponent<Rigidbody>();
// Set initial camera position
if (cameraTarget != null)
{
currentCameraOffset = new Vector3(0, cameraHeight, -cameraDistance);
}
}
private void Update()
{
HandleInput();
UpdateAnimations();
UpdateCamera();
}
private void FixedUpdate()
{
MoveCharacter();
CheckGroundStatus();
}
private void HandleInput()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
moveInput = new Vector3(horizontal, 0f, vertical).normalized;
}
private void MoveCharacter()
{
if (rb == null) return;
// Calculate movement direction relative to camera
Vector3 cameraForward = Vector3.ProjectOnPlane(Camera.main.transform.forward, Vector3.up).normalized;
Vector3 cameraRight = Vector3.ProjectOnPlane(Camera.main.transform.right, Vector3.up).normalized;
Vector3 moveDirection = cameraForward * moveInput.z + cameraRight * moveInput.x;
// Apply movement
Vector3 targetVelocity = moveDirection * moveSpeed;
targetVelocity.y = rb.velocity.y;
rb.velocity = targetVelocity;
// Rotate character to face movement direction
if (moveDirection != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.fixedDeltaTime);
}
}
private void UpdateAnimations()
{
if (animator == null) return;
// Update movement parameters
animator.SetFloat("MoveX", moveInput.x, 0.1f, Time.deltaTime);
animator.SetFloat("MoveZ", moveInput.z, 0.1f, Time.deltaTime);
animator.SetBool("IsGrounded", isGrounded);
animator.SetFloat("Speed", moveInput.magnitude, 0.1f, Time.deltaTime);
}
private void UpdateCamera()
{
if (cameraTarget == null || Camera.main == null) return;
// Calculate target camera position
Vector3 targetPosition = cameraTarget.position + currentCameraOffset;
// Smooth camera movement
Camera.main.transform.position = Vector3.SmoothDamp(
Camera.main.transform.position,
targetPosition,
ref cameraVelocity,
cameraSmoothTime
);
// Look at player
Camera.main.transform.LookAt(cameraTarget.position);
}
private void CheckGroundStatus()
{
isGrounded = Physics.CheckSphere(transform.position, groundCheckRadius, groundLayer);
if (isGrounded && Input.GetButtonDown("Jump") && rb != null)
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
if (animator != null)
{
animator.SetTrigger("Jump");
}
}
}
public void SetCameraOffset(Vector3 offset)
{
currentCameraOffset = offset;
}
private void OnDrawGizmosSelected()
{
if (cameraTarget != null)
{
// Draw camera target position
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(cameraTarget.position + currentCameraOffset, 0.2f);
}
// Draw ground check sphere
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, groundCheckRadius);
}
}
// 4. 2D Platformer Controller
public class PlatformerController : MonoBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 8f;
[SerializeField] private float jumpForce = 16f;
[SerializeField] private float coyoteTime = 0.2f;
[SerializeField] private float jumpBufferTime = 0.2f;
[SerializeField] private float fallMultiplier = 2.5f;
[SerializeField] private float lowJumpMultiplier = 2f;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckRadius = 0.1f;
private Rigidbody2D rb;
private bool isGrounded;
private float coyoteTimeCounter;
private float jumpBufferCounter;
private bool jumpInputReleased;
private float horizontalInput;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
HandleInput();
CheckGroundStatus();
HandleJump();
}
private void FixedUpdate()
{
MovePlayer();
ApplyBetterJumping();
}
private void HandleInput()
{
horizontalInput = Input.GetAxisRaw("Horizontal");
if (Input.GetButtonDown("Jump"))
{
jumpBufferCounter = jumpBufferTime;
}
if (Input.GetButtonUp("Jump"))
{
jumpInputReleased = true;
}
}
private void CheckGroundStatus()
{
bool wasGrounded = isGrounded;
isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundLayer);
// Update coyote time
if (isGrounded)
{
coyoteTimeCounter = coyoteTime;
if (!wasGrounded && rb.velocity.y < -0.1f)
{
// Landing
OnLand();
}
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
// Update jump buffer
if (jumpBufferCounter > 0f)
{
jumpBufferCounter -= Time.deltaTime;
}
}
private void HandleJump()
{
if (jumpBufferCounter > 0f && coyoteTimeCounter > 0f)
{
Jump();
jumpBufferCounter = 0f;
}
}
private void Jump()
{
if (rb != null)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
OnJump();
}
}
private void MovePlayer()
{
if (rb == null) return;
// Apply horizontal movement
rb.velocity = new Vector2(horizontalInput * moveSpeed, rb.velocity.y);
}
private void ApplyBetterJumping()
{
if (rb == null || rb.velocity.y < 0f)
{
// Falling - increase gravity
rb.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) * Time.deltaTime;
}
else if (rb.velocity.y > 0f && jumpInputReleased)
{
// Rising but jump button released - decrease gravity for low jump
rb.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) * Time.deltaTime;
}
}
private void OnJump()
{
jumpInputReleased = false;
// Add jump effects, sounds, etc.
Debug.Log("Jump!");
}
private void OnLand()
{
// Add landing effects, sounds, etc.
Debug.Log("Landed!");
}
private void OnDrawGizmosSelected()
{
if (groundCheck != null)
{
Gizmos.color = isGrounded ? Color.green : Color.red;
Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius);
}
}
}
// 5. Vehicle Controller
public class VehicleController : MonoBehaviour
{
[Header("Vehicle Settings")]
[SerializeField] private float maxSpeed = 20f;
[SerializeField] private float acceleration = 10f;
[SerializeField] private float brakeForce = 15f;
[SerializeField] private float turnSpeed = 100f;
[SerializeField] private float reverseSpeed = 10f;
[SerializeField] private float driftFactor = 0.95f;
[Header("Wheels")]
[SerializeField] private Transform[] frontWheels;
[SerializeField] private Transform[] rearWheels;
[SerializeField] private float wheelRotationSpeed = 10f;
private Rigidbody rb;
private float currentSpeed;
private float horizontalInput;
private float verticalInput;
private bool isBraking;
private float steeringAngle;
private void Awake()
{
rb = GetComponent<Rigidbody>();
// Set up rigidbody for vehicle physics
if (rb != null)
{
rb.centerOfMass = new Vector3(0, -0.5f, 0);
}
}
private void Update()
{
HandleInput();
UpdateWheelVisuals();
}
private void FixedUpdate()
{
MoveVehicle();
HandleSteering();
ApplyDrag();
}
private void HandleInput()
{
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
isBraking = Input.GetKey(KeyCode.Space) || Input.GetButton("Brake");
}
private void MoveVehicle()
{
if (rb == null) return;
currentSpeed = Vector3.Dot(transform.forward, rb.velocity);
// Calculate target speed
float targetSpeed = 0f;
if (verticalInput != 0f)
{
targetSpeed = verticalInput > 0f ? maxSpeed : -reverseSpeed;
}
// Apply acceleration or braking
if (isBraking || (verticalInput == 0f && currentSpeed > 0.1f))
{
// Braking
if (currentSpeed > 0.1f)
{
rb.velocity *= (1 - brakeForce * Time.fixedDeltaTime);
}
}
else if (Mathf.Abs(targetSpeed - currentSpeed) > 0.1f)
{
// Acceleration
float forceDirection = targetSpeed > currentSpeed ? 1f : -1f;
Vector3 force = transform.forward * forceDirection * acceleration * rb.mass;
rb.AddForce(force, ForceMode.Force);
}
}
private void HandleSteering()
{
if (rb == null) return;
// Only steer when moving
if (Mathf.Abs(currentSpeed) > 0.1f)
{
steeringAngle = horizontalInput * turnSpeed * Time.fixedDeltaTime;
// Reduce steering at high speeds
float speedFactor = Mathf.InverseLerp(0f, maxSpeed, Mathf.Abs(currentSpeed));
steeringAngle *= (1f - speedFactor * 0.7f);
// Apply rotation
Quaternion turnRotation = Quaternion.Euler(0f, steeringAngle, 0f);
rb.MoveRotation(rb.rotation * turnRotation);
// Apply drift effect when turning at speed
if (Mathf.Abs(steeringAngle) > 0.1f && currentSpeed > maxSpeed * 0.6f)
{
Vector3 driftVelocity = transform.right * steeringAngle * currentSpeed * 0.1f;
rb.velocity = Vector3.Lerp(rb.velocity, rb.velocity + driftVelocity, driftFactor * Time.fixedDeltaTime);
}
}
}
private void ApplyDrag()
{
if (rb == null) return;
// Apply air resistance
Vector3 dragForce = -rb.velocity * 0.5f;
rb.AddForce(dragForce, ForceMode.Force);
}
private void UpdateWheelVisuals()
{
// Rotate wheels based on vehicle speed
float wheelRotation = currentSpeed * wheelRotationSpeed * Time.deltaTime;
foreach (Transform wheel in frontWheels)
{
if (wheel != null)
{
wheel.Rotate(Vector3.right, wheelRotation);
// Steer front wheels
Vector3 currentRotation = wheel.localEulerAngles;
currentRotation.y = steeringAngle * 50f; // Visual steering multiplier
wheel.localEulerAngles = currentRotation;
}
}
foreach (Transform wheel in rearWheels)
{
if (wheel != null)
{
wheel.Rotate(Vector3.right, wheelRotation);
}
}
}
public float GetCurrentSpeed()
{
return currentSpeed;
}
public float GetSpeedPercentage()
{
return Mathf.InverseLerp(-reverseSpeed, maxSpeed, currentSpeed);
}
private void OnDrawGizmosSelected()
{
if (rb != null)
{
// Draw center of mass
Gizmos.color = Color.red;
Gizmos.DrawSphere(rb.worldCenterOfMass, 0.2f);
}
}
}
💻 Sistemas UI csharp
🟡 intermediate
⭐⭐⭐
Gestão UI completa incluindo menus, HUD, barras de vida e sistemas de inventário
⏱️ 30 min
🏷️ unity, csharp, ui, unity ui
Prerequisites:
Intermediate C#, Unity UI System, Event Systems
// Unity C# UI Systems
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
using System.Collections;
using System.Collections.Generic;
// 1. Main Menu Manager
public class MainMenuManager : MonoBehaviour
{
[Header("Menu Panels")]
[SerializeField] private GameObject mainMenuPanel;
[SerializeField] private GameObject settingsPanel;
[SerializeField] private GameObject creditsPanel;
[SerializeField] private GameObject levelSelectPanel;
[Header("Buttons")]
[SerializeField] private Button playButton;
[SerializeField] private Button settingsButton;
[SerializeField] private Button creditsButton;
[SerializeField] private Button exitButton;
[SerializeField] private Button backButton;
[Header("Animation Settings")]
[SerializeField] private float fadeInDuration = 0.5f;
[SerializeField] private float fadeOutDuration = 0.5f;
private Stack<GameObject> menuStack = new Stack<GameObject>();
private CanvasGroup currentCanvasGroup;
private void Start()
{
InitializeButtons();
ShowMainMenu();
}
private void InitializeButtons()
{
if (playButton != null)
playButton.onClick.AddListener(OnPlayClicked);
if (settingsButton != null)
settingsButton.onClick.AddListener(OnSettingsClicked);
if (creditsButton != null)
creditsButton.onClick.AddListener(OnCreditsClicked);
if (exitButton != null)
exitButton.onClick.AddListener(OnExitClicked);
if (backButton != null)
backButton.onClick.AddListener(OnBackClicked);
}
private void ShowMainMenu()
{
ShowPanel(mainMenuPanel);
}
private void ShowPanel(GameObject panel)
{
if (panel == null) return;
// Hide current panel if exists
if (currentCanvasGroup != null)
{
StartCoroutine(FadeOut(currentCanvasGroup));
}
// Show new panel
panel.SetActive(true);
currentCanvasGroup = panel.GetComponent<CanvasGroup>();
if (currentCanvasGroup == null)
{
currentCanvasGroup = panel.AddComponent<CanvasGroup>();
}
StartCoroutine(FadeIn(currentCanvasGroup));
menuStack.Push(panel);
}
private IEnumerator FadeIn(CanvasGroup canvasGroup)
{
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
float elapsedTime = 0f;
while (elapsedTime < fadeInDuration)
{
canvasGroup.alpha = Mathf.Lerp(0f, 1f, elapsedTime / fadeInDuration);
elapsedTime += Time.deltaTime;
yield return null;
}
canvasGroup.alpha = 1f;
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}
private IEnumerator FadeOut(CanvasGroup canvasGroup)
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
float elapsedTime = 0f;
while (elapsedTime < fadeOutDuration)
{
canvasGroup.alpha = Mathf.Lerp(1f, 0f, elapsedTime / fadeOutDuration);
elapsedTime += Time.deltaTime;
yield return null;
}
canvasGroup.alpha = 0f;
canvasGroup.gameObject.SetActive(false);
}
public void OnPlayClicked()
{
Debug.Log("Play clicked");
// Load game scene or show level select
ShowPanel(levelSelectPanel);
}
public void OnSettingsClicked()
{
Debug.Log("Settings clicked");
ShowPanel(settingsPanel);
}
public void OnCreditsClicked()
{
Debug.Log("Credits clicked");
ShowPanel(creditsPanel);
}
public void OnBackClicked()
{
if (menuStack.Count > 1)
{
menuStack.Pop(); // Remove current panel
if (currentCanvasGroup != null)
{
StartCoroutine(FadeOut(currentCanvasGroup));
}
GameObject previousPanel = menuStack.Peek();
previousPanel.SetActive(true);
currentCanvasGroup = previousPanel.GetComponent<CanvasGroup>();
if (currentCanvasGroup == null)
{
currentCanvasGroup = previousPanel.AddComponent<CanvasGroup>();
}
currentCanvasGroup.alpha = 1f;
currentCanvasGroup.interactable = true;
currentCanvasGroup.blocksRaycasts = true;
}
}
public void OnExitClicked()
{
Debug.Log("Exit clicked");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
// 2. HUD Manager
public class HUDManager : MonoBehaviour
{
[Header("Health UI")]
[SerializeField] private Slider healthBar;
[SerializeField] private TextMeshProUGUI healthText;
[SerializeField] private Image healthFill;
[SerializeField] private Gradient healthGradient;
[Header("Stamina UI")]
[SerializeField] private Slider staminaBar;
[SerializeField] private TextMeshProUGUI staminaText;
[Header("Ammo UI")]
[SerializeField] private TextMeshProUGUI ammoText;
[SerializeField] private Image weaponIcon;
[Header("Experience UI")]
[SerializeField] private Slider experienceBar;
[SerializeField] private TextMeshProUGUI levelText;
[Header("Crosshair")]
[SerializeField] private GameObject crosshair;
[Header("Damage Flash")]
[SerializeField] private Image damageFlash;
[SerializeField] private float flashDuration = 0.2f;
private void Awake()
{
// Initialize UI elements
if (healthBar != null)
{
healthBar.minValue = 0;
healthBar.maxValue = 100;
healthBar.value = 100;
}
if (staminaBar != null)
{
staminaBar.minValue = 0;
staminaBar.maxValue = 100;
staminaBar.value = 100;
}
if (experienceBar != null)
{
experienceBar.minValue = 0;
experienceBar.maxValue = 100;
experienceBar.value = 0;
}
}
public void UpdateHealth(float currentHealth, float maxHealth)
{
float healthPercentage = (currentHealth / maxHealth) * 100f;
if (healthBar != null)
{
healthBar.value = healthPercentage;
// Update health fill color
if (healthFill != null && healthGradient != null)
{
healthFill.color = healthGradient.Evaluate(healthPercentage / 100f);
}
}
if (healthText != null)
{
healthText.text = `${Mathf.Round(currentHealth)} / ${maxHealth}`;
}
// Show damage flash when health decreases
if (currentHealth < maxHealth)
{
StartCoroutine(ShowDamageFlash());
}
}
public void UpdateStamina(float currentStamina, float maxStamina)
{
float staminaPercentage = (currentStamina / maxStamina) * 100f;
if (staminaBar != null)
{
staminaBar.value = staminaPercentage;
}
if (staminaText != null)
{
staminaText.text = `${Mathf.Round(currentStamina)}%`;
}
}
public void UpdateAmmo(int currentAmmo, int maxAmmo, string weaponName)
{
if (ammoText != null)
{
ammoText.text = `${currentAmmo} / ${maxAmmo}`;
}
// Update weapon icon based on weapon name
// This would typically load sprites from resources
}
public void UpdateExperience(float currentXP, float maxXp, int level)
{
float xpPercentage = (currentXP / maxXp) * 100f;
if (experienceBar != null)
{
experienceBar.value = xpPercentage;
}
if (levelText != null)
{
levelText.text = `Level ${level}`;
}
}
public void SetCrosshairActive(bool active)
{
if (crosshair != null)
{
crosshair.SetActive(active);
}
}
private IEnumerator ShowDamageFlash()
{
if (damageFlash != null)
{
damageFlash.enabled = true;
Color flashColor = damageFlash.color;
flashColor.a = 0.5f;
damageFlash.color = flashColor;
float elapsedTime = 0f;
while (elapsedTime < flashDuration)
{
float alpha = Mathf.Lerp(0.5f, 0f, elapsedTime / flashDuration);
flashColor.a = alpha;
damageFlash.color = flashColor;
elapsedTime += Time.deltaTime;
yield return null;
}
damageFlash.enabled = false;
}
}
}
// 3. Inventory System
public class InventoryUI : MonoBehaviour
{
[Header("Inventory UI")]
[SerializeField] private GameObject inventoryPanel;
[SerializeField] private Transform itemSlotContainer;
[SerializeField] private GameObject itemSlotPrefab;
[SerializeField] private int inventorySize = 20;
[Header("Item Description")]
[SerializeField] private Image itemIcon;
[SerializeField] private TextMeshProUGUI itemName;
[SerializeField] private TextMeshProUGUI itemDescription;
[SerializeField] private TextMeshProUGUI itemStats;
private List<InventorySlot> itemSlots = new List<InventorySlot>();
private int selectedSlot = -1;
private void Start()
{
InitializeInventorySlots();
CloseInventory();
}
private void InitializeInventorySlots()
{
for (int i = 0; i < inventorySize; i++)
{
GameObject slotObj = Instantiate(itemSlotPrefab, itemSlotContainer);
InventorySlot slot = slotObj.GetComponent<InventorySlot>();
if (slot != null)
{
slot.Initialize(i, this);
itemSlots.Add(slot);
}
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
ToggleInventory();
}
}
public void ToggleInventory()
{
bool isActive = inventoryPanel.activeSelf;
if (isActive)
{
CloseInventory();
}
else
{
OpenInventory();
}
}
public void OpenInventory()
{
inventoryPanel.SetActive(true);
Time.timeScale = 0f; // Pause game
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
// Update slot displays
foreach (var slot in itemSlots)
{
slot.UpdateDisplay();
}
}
public void CloseInventory()
{
inventoryPanel.SetActive(false);
Time.timeScale = 1f; // Resume game
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
public void SelectSlot(int slotIndex)
{
if (selectedSlot == slotIndex)
{
selectedSlot = -1;
ClearItemDescription();
}
else
{
selectedSlot = slotIndex;
UpdateSelectedSlot();
ShowItemDescription(slotIndex);
}
}
private void UpdateSelectedSlot()
{
for (int i = 0; i < itemSlots.Count; i++)
{
itemSlots[i].SetSelected(i == selectedSlot);
}
}
private void ShowItemDescription(int slotIndex)
{
// This would typically get item data from the inventory system
// For now, we'll use placeholder data
InventorySlot slot = itemSlots[slotIndex];
if (slot.HasItem())
{
// Show item details
if (itemName != null)
itemName.text = "Sample Item";
if (itemDescription != null)
itemDescription.text = "This is a sample item description.";
if (itemStats != null)
itemStats.text = "Damage: 10\nDefense: 5\nValue: 100";
}
else
{
ClearItemDescription();
}
}
private void ClearItemDescription()
{
if (itemName != null)
itemName.text = "";
if (itemDescription != null)
itemDescription.text = "";
if (itemStats != null)
itemStats.text = "";
}
public void UseSelectedItem()
{
if (selectedSlot >= 0 && selectedSlot < itemSlots.Count)
{
itemSlots[selectedSlot].UseItem();
}
}
}
// 4. Inventory Slot Component
public class InventorySlot : MonoBehaviour, IPointerClickHandler
{
[Header("Slot UI")]
[SerializeField] private Image itemIcon;
[SerializeField] private Image background;
[SerializeField] private TextMeshProUGUI quantityText;
[SerializeField] private Color selectedColor = Color.yellow;
[SerializeField] private Color normalColor = Color.white;
private int slotIndex;
private InventoryUI inventoryUI;
private bool isSelected;
private ItemData currentItem;
public void Initialize(int index, InventoryUI ui)
{
slotIndex = index;
inventoryUI = ui;
isSelected = false;
UpdateDisplay();
}
public void UpdateDisplay()
{
if (currentItem != null)
{
itemIcon.sprite = currentItem.icon;
itemIcon.enabled = true;
if (currentItem.stackSize > 1)
{
quantityText.text = currentItem.quantity.ToString();
quantityText.gameObject.SetActive(true);
}
else
{
quantityText.gameObject.SetActive(false);
}
}
else
{
itemIcon.enabled = false;
quantityText.gameObject.SetActive(false);
}
}
public bool HasItem()
{
return currentItem != null;
}
public void SetItem(ItemData item)
{
currentItem = item;
UpdateDisplay();
}
public void UseItem()
{
if (currentItem != null)
{
Debug.Log(`Using item: ${currentItem.itemName}`);
// Implement item usage logic here
}
}
public void SetSelected(bool selected)
{
isSelected = selected;
if (background != null)
{
background.color = isSelected ? selectedColor : normalColor;
}
}
public void OnPointerClick(PointerEventData eventData)
{
inventoryUI.SelectSlot(slotIndex);
}
}
// 5. Item Data Structure
[System.Serializable]
public class ItemData
{
public string itemName;
public string description;
public Sprite icon;
public int quantity;
public int maxStackSize;
public int itemID;
public ItemType itemType;
public ItemStats stats;
}
[System.Serializable]
public class ItemStats
{
public int damage;
public int defense;
public int health;
public int mana;
public float speed;
public int value;
}
public enum ItemType
{
Weapon,
Armor,
Consumable,
Material,
Quest
}
// 6. Dialogue System UI
public class DialogueUI : MonoBehaviour
{
[Header("Dialogue UI")]
[SerializeField] private GameObject dialoguePanel;
[SerializeField] private TextMeshProUGUI speakerName;
[SerializeField] private TextMeshProUGUI dialogueText;
[SerializeField] private GameObject responseContainer;
[SerializeField] private GameObject responseButtonPrefab;
[SerializeField] private Image speakerAvatar;
[Header("Typing Effect")]
[SerializeField] private float typingSpeed = 0.05f;
[SerializeField] private AudioClip[] typingSounds;
private DialogueManager dialogueManager;
private AudioSource audioSource;
private bool isTyping;
private bool canSkip;
private void Awake()
{
dialogueManager = GetComponent<DialogueManager>();
audioSource = GetComponent<AudioSource>();
dialoguePanel.SetActive(false);
}
private void Update()
{
// Skip typing on click
if (Input.GetMouseButtonDown(0) && isTyping && canSkip)
{
SkipTyping();
}
}
public void StartDialogue(DialogueData dialogue)
{
dialoguePanel.SetActive(true);
speakerName.text = dialogue.speakerName;
if (speakerAvatar != null && dialogue.speakerAvatar != null)
{
speakerAvatar.sprite = dialogue.speakerAvatar;
}
StartCoroutine(DisplayDialogue(dialogue));
}
private IEnumerator DisplayDialogue(DialogueData dialogue)
{
// Clear previous responses
foreach (Transform child in responseContainer.transform)
{
Destroy(child.gameObject);
}
// Type out dialogue text
yield return StartCoroutine(TypeText(dialogueText, dialogue.dialogueText));
// Wait for player to finish reading
yield return new WaitUntil(() => Input.GetMouseButtonDown(0));
// Show response options if available
if (dialogue.responses != null && dialogue.responses.Count > 0)
{
ShowResponses(dialogue.responses);
}
else
{
EndDialogue();
}
}
private IEnumerator TypeText(TextMeshProUGUI textComponent, string text)
{
isTyping = true;
canSkip = true;
textComponent.text = "";
for (int i = 0; i < text.Length; i++)
{
textComponent.text += text[i];
// Play typing sound occasionally
if (typingSounds != null && typingSounds.Length > 0 && i % 3 == 0)
{
PlayTypingSound();
}
yield return new WaitForSeconds(typingSpeed);
}
isTyping = false;
}
private void SkipTyping()
{
StopAllCoroutines();
isTyping = false;
}
private void ShowResponses(List<DialogueResponse> responses)
{
foreach (var response in responses)
{
GameObject buttonObj = Instantiate(responseButtonPrefab, responseContainer.transform);
Button button = buttonObj.GetComponent<Button>();
TextMeshProUGUI buttonText = buttonObj.GetComponentInChildren<TextMeshProUGUI>();
if (buttonText != null)
{
buttonText.text = response.responseText;
}
button.onClick.AddListener(() => OnResponseSelected(response));
}
}
private void OnResponseSelected(DialogueResponse response)
{
if (dialogueManager != null)
{
dialogueManager.SelectResponse(response);
}
}
private void EndDialogue()
{
dialoguePanel.SetActive(false);
if (dialogueManager != null)
{
dialogueManager.EndDialogue();
}
}
private void PlayTypingSound()
{
if (audioSource != null && typingSounds != null && typingSounds.Length > 0)
{
AudioClip randomSound = typingSounds[Random.Range(0, typingSounds.Length)];
audioSource.PlayOneShot(randomSound, 0.5f);
}
}
}
// 7. Dialogue Data Structure
[System.Serializable]
public class DialogueData
{
public string speakerName;
public string dialogueText;
public Sprite speakerAvatar;
public List<DialogueResponse> responses;
public AudioClip voiceClip;
}
[System.Serializable]
public class DialogueResponse
{
public string responseText;
public int nextDialogueID;
public int relationshipChange;
public bool endConversation;
}
💻 Física e Animações csharp
🔴 complex
⭐⭐⭐⭐
Interações físicas avançadas, controladores de animação e máquinas de estado
⏱️ 40 min
🏷️ unity, csharp, physics, animation
Prerequisites:
Advanced C#, Unity Physics, Animation Systems
// Unity C# Physics and Animations
using UnityEngine;
using System.Collections;
// 1. Advanced Physics Controller
public class AdvancedPhysicsController : MonoBehaviour
{
[Header("Physics Settings")]
[SerializeField] private float mass = 1f;
[SerializeField] private float drag = 0.5f;
[SerializeField] private float angularDrag = 0.5f;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float groundCheckDistance = 0.1f;
[SerializeField] private float slopeLimit = 45f;
[Header("Force Application")]
[SerializeField] private float jumpForce = 10f;
[SerializeField] private float wallJumpForce = 15f;
[SerializeField] private Vector3 centerOfMassOffset = Vector3.zero;
private Rigidbody rb;
private Collider mainCollider;
private bool isGrounded;
private Vector3 groundNormal;
private Vector3 wallNormal;
private void Awake()
{
rb = GetComponent<Rigidbody>();
mainCollider = GetComponent<Collider>();
// Configure rigidbody
if (rb != null)
{
rb.mass = mass;
rb.drag = drag;
rb.angularDrag = angularDrag;
rb.centerOfMass += centerOfMassOffset;
rb.interpolation = RigidbodyInterpolation.Interpolate;
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
}
}
private void FixedUpdate()
{
CheckGroundStatus();
ApplyGravity();
StabilizeRotation();
}
private void CheckGroundStatus()
{
// Sphere cast for ground detection
Vector3 sphereCenter = transform.position + (Vector3.up * groundCheckDistance);
RaycastHit hit;
bool wasGrounded = isGrounded;
isGrounded = Physics.SphereCast(
sphereCenter,
groundCheckDistance,
Vector3.down,
out hit,
groundCheckDistance * 2f,
groundLayer
);
if (isGrounded)
{
groundNormal = hit.normal;
// Check if on a valid slope
float slopeAngle = Vector3.Angle(groundNormal, Vector3.up);
if (slopeAngle > slopeLimit)
{
isGrounded = false;
}
// Snap to ground if close enough
if (hit.distance < groundCheckDistance * 0.5f)
{
Vector3 targetPosition = transform.position;
targetPosition.y = hit.point.y + groundCheckDistance;
if (rb != null)
{
rb.MovePosition(targetPosition);
}
}
}
else
{
groundNormal = Vector3.up;
}
// Handle landing and leaving ground events
if (isGrounded && !wasGrounded)
{
OnLand();
}
else if (!isGrounded && wasGrounded)
{
OnLeaveGround();
}
}
private void ApplyGravity()
{
if (!isGrounded && rb != null)
{
// Apply custom gravity
Vector3 gravity = Physics.gravity;
rb.AddForce(gravity * mass, ForceMode.Acceleration);
}
}
private void StabilizeRotation()
{
// Keep character upright
if (isGrounded && rb != null)
{
Quaternion targetRotation = Quaternion.FromToRotation(transform.up, groundNormal) * transform.rotation;
rb.MoveRotation(Quaternion.Slerp(transform.rotation, targetRotation, Time.fixedDeltaTime * 10f));
}
}
private void OnLand()
{
Debug.Log("Character landed on ground");
// Reset velocity on landing
if (rb != null)
{
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
}
}
private void OnLeaveGround()
{
Debug.Log("Character left ground");
}
public void Jump()
{
if (isGrounded && rb != null)
{
Vector3 jumpDirection = groundNormal * jumpForce;
rb.AddForce(jumpDirection, ForceMode.Impulse);
}
}
public void WallJump(Vector3 wallJumpDirection)
{
if (rb != null)
{
Vector3 jumpVector = (wallJumpDirection + Vector3.up).normalized * wallJumpForce;
rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // Reset vertical velocity
rb.AddForce(jumpVector, ForceMode.Impulse);
}
}
private void OnCollisionEnter(Collision collision)
{
// Detect wall collision for wall jump
if (!isGrounded)
{
foreach (ContactPoint contact in collision.contacts)
{
float angle = Vector3.Angle(contact.normal, Vector3.up);
if (angle > 60f && angle < 120f)
{
wallNormal = contact.normal;
break;
}
}
}
}
private void OnDrawGizmosSelected()
{
// Draw ground check sphere
Gizmos.color = isGrounded ? Color.green : Color.red;
Vector3 sphereCenter = transform.position + (Vector3.up * groundCheckDistance);
Gizmos.DrawWireSphere(sphereCenter, groundCheckDistance);
// Draw center of mass
if (rb != null)
{
Gizmos.color = Color.blue;
Gizmos.DrawSphere(rb.worldCenterOfMass, 0.1f);
}
}
}
// 2. Custom Animation Controller
public class CustomAnimationController : MonoBehaviour
{
[Header("Animation Settings")]
[SerializeField] private Animator animator;
[SerializeField] private float transitionDuration = 0.25f;
[SerializeField] private LayerMask animationLayer;
[Header("Blend Trees")]
[SerializeField] private AnimationCurve movementBlendCurve;
[SerializeField] private AnimationCurve rotationBlendCurve;
private Dictionary<string, int> animationParameters = new Dictionary<string, int>();
private Dictionary<string, AnimationClip> animationClips = new Dictionary<string, AnimationClip>();
// Animation States
private enum AnimationState
{
Idle,
Walk,
Run,
Jump,
Fall,
Land,
Attack,
Hurt,
Death
}
private AnimationState currentState = AnimationState.Idle;
private AnimationState targetState = AnimationState.Idle;
private float stateTransitionTime = 0f;
private void Awake()
{
InitializeAnimator();
}
private void InitializeAnimator()
{
if (animator == null)
{
animator = GetComponent<Animator>();
}
// Cache parameter IDs for performance
if (animator != null)
{
animationParameters["Speed"] = Animator.StringToHash("Speed");
animationParameters["Direction"] = Animator.StringToHash("Direction");
animationParameters["IsGrounded"] = Animator.StringToHash("IsGrounded");
animationParameters["Jump"] = Animator.StringToHash("Jump");
animationParameters["Attack"] = Animator.StringToHash("Attack");
animationParameters["Hurt"] = Animator.StringToHash("Hurt");
animationParameters["Death"] = Animator.StringToHash("Death");
}
}
private void Update()
{
UpdateStateTransitions();
UpdateAnimationParameters();
}
private void UpdateStateTransitions()
{
if (currentState != targetState)
{
stateTransitionTime += Time.deltaTime;
if (stateTransitionTime >= transitionDuration)
{
currentState = targetState;
stateTransitionTime = 0f;
}
}
}
private void UpdateAnimationParameters()
{
if (animator == null) return;
// Update movement parameters
Vector3 moveInput = GetMovementInput();
float speed = moveInput.magnitude;
float direction = GetMovementDirection(moveInput);
animator.SetFloat(animationParameters["Speed"], speed);
animator.SetFloat(animationParameters["Direction"], direction);
// Update boolean parameters
animator.SetBool(animationParameters["IsGrounded"], IsGrounded());
}
private Vector3 GetMovementInput()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
return new Vector3(horizontal, 0f, vertical);
}
private float GetMovementDirection(Vector3 input)
{
if (input == Vector3.zero) return 0f;
Vector3 forward = transform.forward;
Vector3 right = transform.right;
float forwardAmount = Vector3.Dot(input.normalized, forward);
float rightAmount = Vector3.Dot(input.normalized, right);
return Mathf.Atan2(rightAmount, forwardAmount) * Mathf.Rad2Deg;
}
private bool IsGrounded()
{
// Implement ground check logic
return Physics.Raycast(transform.position + Vector3.up * 0.1f, Vector3.down, 0.2f);
}
public void PlayAnimation(string animationName, float transitionTime = 0.25f)
{
if (animator == null) return;
animator.CrossFadeInFixedTime(animationName, transitionTime, animationLayer);
}
public void TriggerJump()
{
if (animator != null && IsGrounded())
{
animator.SetTrigger(animationParameters["Jump"]);
targetState = AnimationState.Jump;
}
}
public void TriggerAttack()
{
if (animator != null)
{
animator.SetTrigger(animationParameters["Attack"]);
targetState = AnimationState.Attack;
}
}
public void TriggerHurt()
{
if (animator != null)
{
animator.SetTrigger(animationParameters["Hurt"]);
targetState = AnimationState.Hurt;
}
}
public void TriggerDeath()
{
if (animator != null)
{
animator.SetTrigger(animationParameters["Death"]);
targetState = AnimationState.Death;
enabled = false; // Disable further updates
}
}
public void OnAnimationEvent(string eventName)
{
// Handle animation events
switch (eventName)
{
case "AttackStart":
OnAttackStart();
break;
case "AttackHit":
OnAttackHit();
break;
case "AttackEnd":
OnAttackEnd();
break;
case "Footstep":
OnFootstep();
break;
}
}
private void OnAttackStart()
{
Debug.Log("Attack animation started");
// Enable attack hitbox, etc.
}
private void OnAttackHit()
{
Debug.Log("Attack hit frame");
// Deal damage to targets
}
private void OnAttackEnd()
{
Debug.Log("Attack animation ended");
// Disable attack hitbox, return to idle
targetState = AnimationState.Idle;
}
private void OnFootstep()
{
Debug.Log("Footstep sound");
// Play footstep sound effect
}
}
// 3. Interactive Object System
public class InteractiveObject : MonoBehaviour, IInteractable
{
[Header("Interaction Settings")]
[SerializeField] private string interactionPrompt = "Press E to interact";
[SerializeField] private float interactionDistance = 3f;
[SerializeField] private bool requiresKey = false;
[SerializeField] private string requiredKey = "Key";
[Header("Visual Feedback")]
[SerializeField] private Material highlightMaterial;
[SerializeField] private ParticleSystem interactionEffect;
[SerializeField] private AudioClip interactionSound;
private Material originalMaterial;
private MeshRenderer meshRenderer;
private bool isHighlighted = false;
private bool isInteractable = true;
public virtual void Awake()
{
meshRenderer = GetComponent<MeshRenderer>();
if (meshRenderer != null)
{
originalMaterial = meshRenderer.material;
}
}
public virtual bool CanInteract(GameObject interactor)
{
return isInteractable && Vector3.Distance(transform.position, interactor.transform.position) <= interactionDistance;
}
public virtual void StartInteraction(GameObject interactor)
{
if (!CanInteract(interactor)) return;
if (requiresKey)
{
Inventory inventory = interactor.GetComponent<Inventory>();
if (inventory == null || !inventory.HasItem(requiredKey))
{
Debug.Log(`Requires key: ${requiredKey}`);
return;
}
}
OnInteraction(interactor);
PlayInteractionEffects();
}
public virtual void StopInteraction(GameObject interactor)
{
// Override in derived classes
}
protected virtual void OnInteraction(GameObject interactor)
{
Debug.Log(`${gameObject.name} interacted with by ${interactor.name}`);
}
public virtual void ShowInteractionPrompt()
{
if (isHighlighted) return;
HighlightObject();
isHighlighted = true;
}
public virtual void HideInteractionPrompt()
{
if (!isHighlighted) return;
RestoreObject();
isHighlighted = false;
}
private void HighlightObject()
{
if (meshRenderer != null && highlightMaterial != null)
{
meshRenderer.material = highlightMaterial;
}
}
private void RestoreObject()
{
if (meshRenderer != null && originalMaterial != null)
{
meshRenderer.material = originalMaterial;
}
}
private void PlayInteractionEffects()
{
// Play particle effect
if (interactionEffect != null)
{
ParticleSystem effect = Instantiate(interactionEffect, transform.position, Quaternion.identity);
Destroy(effect.gameObject, effect.main.duration);
}
// Play sound
if (interactionSound != null)
{
AudioSource.PlayClipAtPoint(interactionSound, transform.position);
}
}
public void SetInteractable(bool interactable)
{
isInteractable = interactable;
}
public string GetInteractionPrompt()
{
return interactionPrompt;
}
}
// 4. Ragdoll System
public class RagdollController : MonoBehaviour
{
[Header("Ragdoll Settings")]
[SerializeField] private bool enableRagdollOnDeath = true;
[SerializeField] private float ragdollForce = 100f;
[SerializeField] private float blendDuration = 0.5f;
[Header("Rigidbody Parts")]
[SerializeField] private Rigidbody[] ragdollRigidbodies;
[SerializeField] private ConfigurableJoint[] ragdollJoints;
private Animator animator;
private bool isRagdoll = false;
private Coroutine ragdollBlendCoroutine;
private void Awake()
{
animator = GetComponent<Animator>();
InitializeRagdoll();
}
private void InitializeRagdoll()
{
// Disable ragdoll parts initially
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
rb.isKinematic = true;
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
}
}
}
public void EnableRagdoll(Vector3 forceDirection)
{
if (isRagdoll) return;
isRagdoll = true;
// Disable animator
if (animator != null)
{
animator.enabled = false;
}
// Enable rigidbodies and apply forces
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
rb.isKinematic = false;
// Apply explosion-like force
rb.AddForce(forceDirection * ragdollForce + Random.insideUnitSphere * ragdollForce * 0.5f,
ForceMode.Impulse);
}
}
// Start blend coroutine
if (ragdollBlendCoroutine != null)
{
StopCoroutine(ragdollBlendCoroutine);
}
ragdollBlendCoroutine = StartCoroutine(BlendToRagdoll());
}
public void DisableRagdoll()
{
if (!isRagdoll) return;
isRagdoll = false;
// Disable rigidbodies
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
rb.isKinematic = true;
}
}
// Enable animator
if (animator != null)
{
animator.enabled = true;
}
// Start blend coroutine
if (ragdollBlendCoroutine != null)
{
StopCoroutine(ragdollBlendCoroutine);
}
ragdollBlendCoroutine = StartCoroutine(BlendToAnimator());
}
private IEnumerator BlendToRagdoll()
{
float elapsedTime = 0f;
while (elapsedTime < blendDuration)
{
elapsedTime += Time.deltaTime;
yield return null;
}
// Clean up any leftover coroutine
if (ragdollBlendCoroutine != null)
{
StopCoroutine(ragdollBlendCoroutine);
}
}
private IEnumerator BlendToAnimator()
{
float elapsedTime = 0f;
// Get ragdoll pose
Dictionary<Transform, Quaternion> ragdollPose = new Dictionary<Transform, Quaternion>();
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
ragdollPose[rb.transform] = rb.transform.rotation;
}
}
while (elapsedTime < blendDuration)
{
elapsedTime += Time.deltaTime;
float blendProgress = elapsedTime / blendDuration;
// Blend back to animator pose
foreach (var pair in ragdollPose)
{
if (animator != null)
{
Transform animatorBone = animator.GetBoneTransform(pair.Key.name);
if (animatorBone != null)
{
pair.Key.rotation = Quaternion.Slerp(pair.Value, animatorBone.rotation, blendProgress);
}
}
}
yield return null;
}
// Clean up any leftover coroutine
if (ragdollBlendCoroutine != null)
{
StopCoroutine(ragdollBlendCoroutine);
}
}
public void ApplyExplosion(Vector3 explosionCenter, float explosionForce, float explosionRadius)
{
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
Vector3 direction = rb.position - explosionCenter;
float distance = direction.magnitude;
if (distance < explosionRadius)
{
float force = (1 - distance / explosionRadius) * explosionForce;
rb.AddForce(direction.normalized * force, ForceMode.Impulse);
}
}
}
}
private void OnDrawGizmosSelected()
{
// Draw ragdoll rigidbodies
Gizmos.color = Color.red;
foreach (Rigidbody rb in ragdollRigidbodies)
{
if (rb != null)
{
Gizmos.DrawWireSphere(rb.position, 0.1f);
}
}
}
}
// 5. Interaction Interface
public interface IInteractable
{
bool CanInteract(GameObject interactor);
void StartInteraction(GameObject interactor);
void StopInteraction(GameObject interactor);
void ShowInteractionPrompt();
void HideInteractionPrompt();
string GetInteractionPrompt();
}
// 6. Interaction Manager
public class InteractionManager : MonoBehaviour
{
[Header("Interaction Settings")]
[SerializeField] private Camera playerCamera;
[SerializeField] private float interactionRange = 3f;
[SerializeField] private LayerMask interactionLayer;
[SerializeField] private KeyCode interactionKey = KeyCode.E;
[Header("UI")]
[SerializeField] private GameObject interactionPrompt;
[SerializeField] private TextMeshProUGUI promptText;
private IInteractable currentInteractable;
private GameObject currentInteractor;
private void Update()
{
CheckForInteractables();
HandleInteractionInput();
}
private void CheckForInteractables()
{
Vector3 rayOrigin = playerCamera.transform.position;
Vector3 rayDirection = playerCamera.transform.forward;
RaycastHit hit;
if (Physics.Raycast(rayOrigin, rayDirection, out hit, interactionRange, interactionLayer))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null && interactable.CanInteractable(currentInteractor))
{
if (currentInteractable != interactable)
{
// Hide previous prompt
if (currentInteractable != null)
{
currentInteractable.HideInteractionPrompt();
}
// Show new prompt
currentInteractable = interactable;
currentInteractable.ShowInteractionPrompt();
UpdateInteractionPrompt();
}
}
else
{
HideCurrentPrompt();
}
}
else
{
HideCurrentPrompt();
}
}
private void HandleInteractionInput()
{
if (Input.GetKeyDown(interactionKey) && currentInteractable != null)
{
currentInteractable.StartInteraction(currentInteractor);
}
else if (Input.GetKeyUp(interactionKey) && currentInteractable != null)
{
currentInteractable.StopInteraction(currentInteractor);
}
}
private void UpdateInteractionPrompt()
{
if (promptText != null && currentInteractable != null)
{
promptText.text = currentInteractable.GetInteractionPrompt();
}
if (interactionPrompt != null)
{
interactionPrompt.SetActive(true);
}
}
private void HideCurrentPrompt()
{
if (currentInteractable != null)
{
currentInteractable.HideInteractionPrompt();
currentInteractable = null;
}
if (interactionPrompt != null)
{
interactionPrompt.SetActive(false);
}
}
}