Initial commit

This commit is contained in:
2025-12-17 17:34:04 +00:00
commit f5f40a85ee
13029 changed files with 2212323 additions and 0 deletions

12
Assets/Scripts/Item.cs Normal file
View File

@@ -0,0 +1,12 @@
using UnityEngine;
[System.Serializable]
public class Item : MonoBehaviour
{
public static Item instance { get; private set; }
public string itemName; // Name of the item
public int itemPrice; // Price of the item
public Sprite itemSprite; // Optional: Sprite for the item
// Optional: Add more properties as needed, such as description, rarity, etc.
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 76e5542449aa29b46a79572a89069f91

View File

@@ -0,0 +1,138 @@
using UnityEngine;
using UnityEngine.InputSystem;
public class ItemPickup : MonoBehaviour
{
[Header("Layer Settings")]
public LayerMask pickupLayer;
[Header("Input Settings")]
public InputActionAsset inputActionAsset;
[Header("Pickup Settings")]
public float pickupSpeed = 10f; // How fast it snaps to your hand
public float holdDistance = 3f; // Default distance
private InputAction pickUpAction;
private Camera mainCamera;
// We store the Rigidbody instead of the GameObject
private Rigidbody currentPickedRB = null;
void Awake()
{
mainCamera = Camera.main;
}
private void OnEnable()
{
if (inputActionAsset != null)
{
pickUpAction = inputActionAsset.FindAction("PickUp");
if (pickUpAction != null)
{
pickUpAction.Enable();
pickUpAction.started += OnPickUpStarted;
pickUpAction.canceled += OnPickUpCanceled;
}
}
}
private void OnDisable()
{
if (pickUpAction != null)
{
pickUpAction.started -= OnPickUpStarted;
pickUpAction.canceled -= OnPickUpCanceled;
pickUpAction.Disable();
}
}
private void OnPickUpStarted(InputAction.CallbackContext context)
{
// 1. Raycast to find object
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = mainCamera.ScreenPointToRay(mousePos);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// Check if it has a Rigidbody and match layer
if (hit.collider.TryGetComponent(out Rigidbody rb) &&
((1 << hit.collider.gameObject.layer) & pickupLayer) != 0)
{
currentPickedRB = rb;
// --- CRITICAL FIX START ---
// If we pick it off a Shelf, it is frozen (Kinematic).
// We must unfreeze it immediately so we can apply velocity.
currentPickedRB.isKinematic = false;
// --- CRITICAL FIX END ---
// 2. Setup Physics for "Holding"
currentPickedRB.useGravity = false;
// Add damping so it doesn't drift around like it's on ice
currentPickedRB.linearDamping = 10;
currentPickedRB.angularDamping = 5;
// Optional: Calculate distance to keep it where we clicked
holdDistance = hit.distance;
// Notify RayCastSystem
RayCastSystem rayCastSystem = GetComponent<RayCastSystem>();
if (rayCastSystem != null)
{
rayCastSystem.SetCurrentPickedRB(rb);
}
}
}
}
private void OnPickUpCanceled(InputAction.CallbackContext context)
{
if (currentPickedRB != null)
{
// 3. Reset Physics properties
currentPickedRB.useGravity = true;
currentPickedRB.linearDamping = 0; // Reset to default (usually 0)
currentPickedRB.angularDamping = 0.05f;
currentPickedRB = null;
// Notify RayCastSystem that an object is no longer picked up
RayCastSystem rayCastSystem = GetComponent<RayCastSystem>();
if (rayCastSystem != null)
{
rayCastSystem.SetCurrentPickedRB(null);
}
}
}
// Physics movement must be in FixedUpdate
private void FixedUpdate()
{
if (currentPickedRB != null)
{
MoveObjectPhysics();
}
}
private void MoveObjectPhysics()
{
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = mainCamera.ScreenPointToRay(mousePos);
// Where we want the object to be
Vector3 targetPosition = ray.GetPoint(holdDistance);
// Calculate direction vector: Target - Current
Vector3 direction = targetPosition - currentPickedRB.position;
// Apply Velocity
// We multiply by pickupSpeed to make it responsive
// The physics engine handles the rest (Mass 1 vs Mass 100 collisions)
currentPickedRB.linearVelocity = direction * pickupSpeed;
// Note: In Unity versions older than 2023.3, use 'velocity' instead of 'linearVelocity'
// currentPickedRB.velocity = direction * pickupSpeed;
}
}

View File

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

View File

@@ -0,0 +1,159 @@
using UnityEngine;
using UnityEngine.InputSystem;
public class FirstPersonController : MonoBehaviour
{
[Header("Movement Settings")]
public float speed = 5.0f;
public float mouseSensitivity = 10.0f;
[Header("Camera Settings")]
public Transform cameraTarget;
public float minVerticalAngle = -90.0f;
public float maxVerticalAngle = 90.0f;
private CharacterController controller;
private Vector2 moveInput;
private Vector2 lookInput;
private float verticalRotation = 0f;
private Vector3 velocity = Vector3.zero;
private float gravity = 9.81f;
[Header("Input System")]
public InputActionAsset inputActionAsset;
private InputAction moveAction;
private InputAction lookAction;
private InputAction toggleMenuAction; // Single action for TAB
[Header("Shop Menu")]
[SerializeField] GameObject inventoryMenuUI; // Current inventory
[SerializeField] GameObject buyMenuUI; // Buy inventory
private bool isPaused = false; // Master state for Cursor + Camera
void Start()
{
controller = GetComponent<CharacterController>();
if (cameraTarget == null) Debug.LogError("Assign 'Camera Target'!");
// Initialize state: Locked cursor, Camera active
SetPauseState(false);
inventoryMenuUI.SetActive(false);
buyMenuUI.SetActive(false);
// Initialize shop menu UI
}
void OnEnable()
{
if (inputActionAsset != null)
{
// Find Actions
moveAction = inputActionAsset.FindAction("Move");
lookAction = inputActionAsset.FindAction("Look");
// Make sure this name matches your Input Action Asset exactly!
toggleMenuAction = inputActionAsset.FindAction("ToggleMenu");
// Enable Actions
moveAction?.Enable();
lookAction?.Enable();
toggleMenuAction?.Enable();
// Subscribe to Events
if (moveAction != null)
{
moveAction.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
moveAction.canceled += ctx => moveInput = Vector2.zero;
}
if (lookAction != null)
{
lookAction.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
lookAction.canceled += ctx => lookInput = Vector2.zero;
}
if (toggleMenuAction != null)
{
toggleMenuAction.performed += OnToggleMenuPerformed;
}
}
}
void OnDisable()
{
moveAction?.Disable();
lookAction?.Disable();
toggleMenuAction?.Disable();
}
// Triggered when TAB is pressed
void OnToggleMenuPerformed(InputAction.CallbackContext context)
{
// Toggle the boolean state
isPaused = !isPaused;
// Apply the new state
SetPauseState(isPaused);
}
// Central function to handle what happens when paused/unpaused
void SetPauseState(bool paused)
{
if (paused)
{
// FREEZE: Show cursor, unlock mouse, stop camera
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
inventoryMenuUI.SetActive(true);
buyMenuUI.SetActive(true);
}
else
{
// RESUME: Hide cursor, lock mouse, allow camera
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
inventoryMenuUI.SetActive(false);
buyMenuUI.SetActive(false);
}
}
void Update()
{
// Always allow movement? Or restrict movement when paused?
// If you want to stop walking when TAB is pressed, wrap this in "if (!isPaused)"
HandleMovement();
// Only rotate camera if NOT paused
if (!isPaused)
{
HandleRotation();
}
}
void HandleMovement()
{
Vector3 move = transform.right * moveInput.x + transform.forward * moveInput.y;
velocity.y -= gravity * Time.deltaTime;
move.y = velocity.y;
controller.Move(move * speed * Time.deltaTime);
}
void HandleRotation()
{
// Horizontal (Body)
float mouseX = lookInput.x * mouseSensitivity * Time.deltaTime;
transform.Rotate(Vector3.up * mouseX);
// Vertical (Camera Target)
if (cameraTarget != null)
{
float mouseY = lookInput.y * mouseSensitivity * Time.deltaTime;
verticalRotation -= mouseY;
verticalRotation = Mathf.Clamp(verticalRotation, minVerticalAngle, maxVerticalAngle);
cameraTarget.localRotation = Quaternion.Euler(verticalRotation, 0f, 0f);
}
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 120fca8f71f02a54d8e053397b5a0c4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- inputActionAsset: {fileID: -944628639613478452, guid: 052faaac586de48259a63d0c4782560b, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,152 @@
using UnityEngine;
using TMPro;
public class RayCastSystem : MonoBehaviour
{
[Header("Layer Settings")]
public LayerMask interactableLayers;
[Header("Highlight Settings")]
public Color highlightColor = new Color(1f, 1f, 0f, 0.35f);
public float padding = 1.02f;
private Camera mainCamera;
private GameObject currentSelection;
private GameObject outlineObject;
private MeshFilter outlineFilter;
private MeshRenderer outlineRenderer;
private Rigidbody currentPickedRB = null;
[Header("UI Elements")]
public TMP_Text itemCountText;
void Start()
{
mainCamera = Camera.main;
CreateOutlineObject();
if(itemCountText) itemCountText.gameObject.SetActive(false);
}
void CreateOutlineObject()
{
outlineObject = new GameObject("SelectionHighlight");
outlineFilter = outlineObject.AddComponent<MeshFilter>();
outlineRenderer = outlineObject.AddComponent<MeshRenderer>();
Material ghostMat = new Material(Shader.Find("Standard"));
// Optimize Material: Enable GPU Instancing
ghostMat.enableInstancing = true;
ghostMat.SetFloat("_Mode", 3); // Transparent
ghostMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
ghostMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
ghostMat.SetInt("_ZWrite", 0);
ghostMat.DisableKeyword("_ALPHATEST_ON");
ghostMat.EnableKeyword("_ALPHABLEND_ON");
ghostMat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
ghostMat.renderQueue = 3000;
ghostMat.color = highlightColor;
ghostMat.EnableKeyword("_EMISSION");
ghostMat.SetColor("_EmissionColor", highlightColor * 0.5f);
outlineRenderer.material = ghostMat;
outlineObject.SetActive(false);
// OPTIMIZATION: Do not parent to the player.
// Keeping it in the root ensures Scale calculations are always 1:1 with the target.
outlineObject.transform.SetParent(null);
}
void Update()
{
RaycastHit hit;
// Perform Raycast
if (Physics.Raycast(mainCamera.transform.position, mainCamera.transform.forward, out hit, 100f, interactableLayers))
{
GameObject hitObject = hit.collider.gameObject;
// Only run expensive logic if the selection ACTUALLY changed
if (currentSelection != hitObject)
{
currentSelection = hitObject;
UpdateOutline(currentSelection);
UpdateUI(currentSelection); // Separated UI logic for cleanliness
}
// Keep outline snapped
if (currentSelection != null && outlineObject.activeSelf)
{
FollowTarget(currentSelection);
}
}
else
{
if (currentSelection != null) ClearSelection();
}
}
void UpdateUI(GameObject target)
{
if (itemCountText == null) return;
Shelf shelf = target.GetComponent<Shelf>();
if (shelf != null)
{
// PERFORMANCE FIX:
// Instead of getting a whole dictionary, we just ask for the count directly.
// You need to add 'public int ItemCount' or similar to your Shelf script.
itemCountText.text = $"Items: {shelf.GetTotalItemCount()}";
itemCountText.gameObject.SetActive(true);
}
else
{
itemCountText.gameObject.SetActive(false);
}
}
void UpdateOutline(GameObject target)
{
if(currentPickedRB != null) {
outlineObject.SetActive(false);
return;
}
MeshFilter targetMeshFilter = target.GetComponent<MeshFilter>();
if (targetMeshFilter == null) targetMeshFilter = target.GetComponentInChildren<MeshFilter>();
if (targetMeshFilter == null)
{
outlineObject.SetActive(false);
return;
}
// Lightweight: Just swapping the reference, not creating new meshes
outlineFilter.sharedMesh = targetMeshFilter.sharedMesh;
FollowTarget(target);
outlineObject.SetActive(true);
}
void FollowTarget(GameObject target)
{
outlineObject.transform.position = target.transform.position;
outlineObject.transform.rotation = target.transform.rotation;
// This math is safer now that outlineObject is not a child of the Player
outlineObject.transform.localScale = target.transform.localScale * padding;
}
void ClearSelection()
{
currentSelection = null;
outlineObject.SetActive(false);
if (itemCountText) itemCountText.gameObject.SetActive(false);
}
public void SetCurrentPickedRB(Rigidbody rb)
{
currentPickedRB = rb;
if (currentPickedRB != null) ClearSelection();
}
}

View File

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

215
Assets/Scripts/Shelf.cs Normal file
View File

@@ -0,0 +1,215 @@
using UnityEngine;
using System.Collections.Generic;
[RequireComponent(typeof(BoxCollider))]
public class Shelf : MonoBehaviour
{
[Header("Grid Settings")]
[Tooltip("How many items fit horizontally")]
public int columns = 5;
[Tooltip("How many items fit vertically/deep")]
public int rows = 2;
[Header("Snap Settings")]
// Lift item slightly so it sits ON the shelf, not IN it
public Vector3 offset = new Vector3(0, 0.1f, 0);
// Track which object is in which slot. Index 0 = Slot 0.
private GameObject[] slotOccupants;
private Vector3[] localGridPositions;
private BoxCollider shelfCollider;
void Start()
{
shelfCollider = GetComponent<BoxCollider>();
// Ensure the collider is a trigger so items can pass through to be detected
shelfCollider.isTrigger = true;
CalculateGridPositions();
// Initialize occupancy array
slotOccupants = new GameObject[columns * rows];
}
[ContextMenu("Recalculate Grid")]
void CalculateGridPositions()
{
if(shelfCollider == null) shelfCollider = GetComponent<BoxCollider>();
int totalSlots = columns * rows;
localGridPositions = new Vector3[totalSlots];
// Get dimensions of the collider
float width = shelfCollider.size.x;
float depth = shelfCollider.size.z; // Assuming Y is Up, Z is depth
// Calculate cell size
float cellWidth = width / columns;
float cellDepth = depth / rows;
// Calculate starting point (Bottom-Left of the shelf surface)
Vector3 startPos = shelfCollider.center - new Vector3(width / 2, -shelfCollider.size.y/2, depth / 2);
startPos += new Vector3(cellWidth / 2, 0, cellDepth / 2);
int index = 0;
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < columns; c++)
{
// Calculate local position
Vector3 pos = startPos + new Vector3(c * cellWidth, 0, r * cellDepth);
localGridPositions[index] = pos;
index++;
}
}
}
void OnTriggerEnter(Collider other)
{
// 1. Check if it is an Item
// 2. Check if we aren't ALREADY tracking it (prevents glitching)
if (other.GetComponent<Item>() != null && !IsItemOnShelf(other.gameObject))
{
TrySnapItem(other.gameObject);
}
}
void OnTriggerExit(Collider other)
{
if (other.GetComponent<Item>() != null)
{
RemoveItem(other.gameObject);
}
}
void TrySnapItem(GameObject itemObj)
{
int bestSlotIndex = -1;
float closestDistance = float.MaxValue;
// --- NEW LOGIC: FIND CLOSEST EMPTY SLOT ---
// Instead of grabbing the first empty one (which causes teleporting),
// we check distance to find the slot closest to where you are holding the item.
for (int i = 0; i < localGridPositions.Length; i++)
{
// Only check EMPTY slots
if (slotOccupants[i] == null)
{
// Convert local grid point to world space to compare distance
Vector3 worldSlotPos = transform.TransformPoint(localGridPositions[i]);
float dist = Vector3.Distance(itemObj.transform.position, worldSlotPos);
// If this slot is closer than the last one we found, pick it
if (dist < closestDistance)
{
closestDistance = dist;
bestSlotIndex = i;
}
}
}
// If shelf is full, do nothing
if (bestSlotIndex == -1) return;
// --- SNAP LOGIC ---
slotOccupants[bestSlotIndex] = itemObj;
// --- PHYSICS FIX ---
Rigidbody rb = itemObj.GetComponent<Rigidbody>();
if(rb != null)
{
// 1. Stop the object FIRST
rb.linearVelocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
// 2. THEN disable physics interactions
rb.isKinematic = true;
}
// Move item to the best slot found
Vector3 targetPos = transform.TransformPoint(localGridPositions[bestSlotIndex]) + offset;
itemObj.transform.position = targetPos;
itemObj.transform.rotation = transform.rotation;
}
void RemoveItem(GameObject itemObj)
{
// Find where this item was and clear the slot
for (int i = 0; i < slotOccupants.Length; i++)
{
if (slotOccupants[i] == itemObj)
{
slotOccupants[i] = null;
// Re-enable physics when leaving
Rigidbody rb = itemObj.GetComponent<Rigidbody>();
if (rb != null) rb.isKinematic = false;
break;
}
}
}
// Helper to check if item is already physically tracked
bool IsItemOnShelf(GameObject item)
{
for (int i = 0; i < slotOccupants.Length; i++)
{
if (slotOccupants[i] == item) return true;
}
return false;
}
// --- INVENTORY LOGIC MERGED ---
// This replaces your old Dictionary. It counts the actual physical items.
// This is safer because it can never get "out of sync" with what you see.
public int GetTotalItemCount()
{
int count = 0;
if (slotOccupants != null)
{
foreach (var item in slotOccupants)
{
if (item != null) count++;
}
}
return count;
}
// DRAW THE GRID IN EDITOR SO WE CAN SEE IT
void OnDrawGizmos()
{
if (shelfCollider == null) shelfCollider = GetComponent<BoxCollider>();
// Visualize the cached positions if game is running
if (Application.isPlaying && localGridPositions != null)
{
Gizmos.color = Color.green;
foreach (Vector3 localPos in localGridPositions)
{
Vector3 worldPos = transform.TransformPoint(localPos);
Gizmos.DrawWireSphere(worldPos, 0.05f);
Gizmos.DrawWireCube(worldPos + offset, new Vector3(0.1f, 0.1f, 0.1f));
}
}
else
{
// Fallback for Editor Mode (Logic repeated for visual aid before playing)
Gizmos.color = Color.yellow;
// ... Simple calc for editor preview only ...
float w = shelfCollider.size.x;
float d = shelfCollider.size.z;
float cw = w / columns;
float cd = d / rows;
Vector3 start = shelfCollider.center - new Vector3(w/2, -shelfCollider.size.y/2, d/2) + new Vector3(cw/2, 0, cd/2);
for(int r=0; r<rows; r++) {
for(int c=0; c<columns; c++) {
Vector3 pos = start + new Vector3(c*cw, 0, r*cd);
Vector3 worldPos = transform.TransformPoint(pos);
Gizmos.DrawWireSphere(worldPos, 0.05f);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2979aa091d840f243b5ebda40c3ee41a

View File

@@ -0,0 +1,159 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using TMPro;
public class ShopManager : MonoBehaviour
{
[Header("Shop Settings")]
public int startingMoney = 100;
public Item[] availableItems; // Assumes Item is a MonoBehaviour attached to a Prefab
[Header("UI Elements")]
public TMP_Text moneyText;
public Button[] buyButtons;
[Header("Inventory Settings")]
public Transform storeRoomTransform; // Where physical items spawn
public Transform inventoryUI; // The Panel with the Grid Layout Group
public GameObject inventoryTextTemplate; // Drag a Prefab with TMP_Text here!
private int currentMoney;
private Dictionary<string, int> inventory = new Dictionary<string, int>();
void Start()
{
currentMoney = startingMoney;
// Initialize dictionary once at start
PopulateInventoryDictionary();
InitializeShopButtons();
UpdateAllUI();
}
void InitializeShopButtons()
{
for (int i = 0; i < buyButtons.Length; i++)
{
if (i < availableItems.Length)
{
int index = i;
Item item = availableItems[index];
Button button = buyButtons[index];
// Set Button Text
TMP_Text btnText = button.GetComponentInChildren<TMP_Text>();
if (btnText != null)
{
btnText.text = $"{item.itemName} - ${item.itemPrice}";
}
// Add Click Listener
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => BuyItem(item, button));
}
else
{
buyButtons[i].gameObject.SetActive(false);
}
}
}
void BuyItem(Item item, Button button)
{
if (currentMoney >= item.itemPrice)
{
// 1. Deduct Money
currentMoney -= item.itemPrice;
// 2. Instantiate physical item in the game world
if(item.gameObject != null)
{
Instantiate(item.gameObject, storeRoomTransform.position, Quaternion.identity);
}
// 3. Logic: Add to dictionary
if (!inventory.ContainsKey(item.itemName))
{
inventory[item.itemName] = 0;
}
inventory[item.itemName]++;
Debug.Log($"Purchased {item.itemName}");
// 4. Update Button Feedback (Optional)
TMP_Text btnText = button.GetComponentInChildren<TMP_Text>();
if (btnText != null)
{
// Temporarily show "Bought" or keep price?
// Usually shops keep the price so you can buy again.
// If you want it to flash green, you'd need a Coroutine,
// but for now let's keep it simple.
}
// 5. Refresh UI
UpdateAllUI();
}
else
{
Debug.Log("Not enough money!");
}
}
void UpdateAllUI()
{
// Update Money Text
moneyText.text = "Money: $" + currentMoney;
// Update Button Interactability
for (int i = 0; i < buyButtons.Length; i++)
{
if (i < availableItems.Length)
{
buyButtons[i].interactable = (currentMoney >= availableItems[i].itemPrice);
}
}
// Update Inventory List
UpdateInventoryUI();
}
void PopulateInventoryDictionary()
{
foreach (Item item in availableItems)
{
if (!inventory.ContainsKey(item.itemName))
{
inventory[item.itemName] = 0;
}
}
}
void UpdateInventoryUI()
{
// 1. Clear existing list items
foreach (Transform child in inventoryUI)
{
Destroy(child.gameObject);
}
// 2. Create new list items from the Template
foreach (KeyValuePair<string, int> entry in inventory)
{
// Only show items we actually have (Count > 0)
if(entry.Value > 0)
{
// Instantiate the Template (which has the TMP_Text component)
GameObject newItem = Instantiate(inventoryTextTemplate, inventoryUI);
// Get the text component and set the string
TMP_Text textComp = newItem.GetComponent<TMP_Text>();
if (textComp != null)
{
textComp.text = $"{entry.Key}: {entry.Value}";
}
}
}
}
}

View File

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