Add worldbuilder player controls and build HUD tools
This commit is contained in:
116
Assets/Scripts/Building/BuildTool.cs
Normal file
116
Assets/Scripts/Building/BuildTool.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using BlackRoad.Worldbuilder.Environment;
|
||||||
|
using BlackRoad.Worldbuilder.Player;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.Building
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lets the player place and remove prefabs on terrain using raycasts.
|
||||||
|
/// Left click: place selected prefab (snapped to grid).
|
||||||
|
/// Right click: remove hit buildable object.
|
||||||
|
/// Number keys 1..N: select prefab.
|
||||||
|
/// </summary>
|
||||||
|
public class BuildTool : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Refs")]
|
||||||
|
[SerializeField] private Camera playerCamera;
|
||||||
|
[SerializeField] private PlayerController playerController;
|
||||||
|
[SerializeField] private float maxPlaceDistance = 80f;
|
||||||
|
[SerializeField] private LayerMask placementMask = ~0;
|
||||||
|
|
||||||
|
[Header("Building")]
|
||||||
|
[SerializeField] private float gridSize = 2f;
|
||||||
|
[SerializeField] private List<GameObject> buildPrefabs = new List<GameObject>();
|
||||||
|
|
||||||
|
[Header("Debug")]
|
||||||
|
[SerializeField] private Color previewColor = new Color(0f, 1f, 0f, 0.4f);
|
||||||
|
|
||||||
|
public int CurrentIndex { get; private set; } = 0;
|
||||||
|
public GameObject CurrentPrefab =>
|
||||||
|
buildPrefabs != null && buildPrefabs.Count > 0 && CurrentIndex >= 0 && CurrentIndex < buildPrefabs.Count
|
||||||
|
? buildPrefabs[CurrentIndex]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
private Vector3 _lastPreviewPos;
|
||||||
|
private bool _hasPreview;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (playerCamera == null)
|
||||||
|
playerCamera = Camera.main;
|
||||||
|
if (playerController == null)
|
||||||
|
playerController = FindObjectOfType<PlayerController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
HandleSelection();
|
||||||
|
HandlePlacement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSelection()
|
||||||
|
{
|
||||||
|
// 1..9 selects prefabs
|
||||||
|
for (int i = 0; i < buildPrefabs.Count && i < 9; i++)
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
|
||||||
|
{
|
||||||
|
CurrentIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePlacement()
|
||||||
|
{
|
||||||
|
if (playerCamera == null || CurrentPrefab == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||||
|
if (Physics.Raycast(ray, out RaycastHit hit, maxPlaceDistance, placementMask))
|
||||||
|
{
|
||||||
|
Vector3 pos = hit.point;
|
||||||
|
|
||||||
|
// Snap to grid horizontally, keep terrain height
|
||||||
|
pos.x = Mathf.Round(pos.x / gridSize) * gridSize;
|
||||||
|
pos.z = Mathf.Round(pos.z / gridSize) * gridSize;
|
||||||
|
|
||||||
|
_lastPreviewPos = pos;
|
||||||
|
_hasPreview = true;
|
||||||
|
|
||||||
|
// Place
|
||||||
|
if (Input.GetMouseButtonDown(0))
|
||||||
|
{
|
||||||
|
Instantiate(CurrentPrefab, pos, Quaternion.identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
if (Input.GetMouseButtonDown(1))
|
||||||
|
{
|
||||||
|
if (hit.collider != null)
|
||||||
|
{
|
||||||
|
// Simple rule: destroy hit object if it has tag "Buildable" or no Rigidbody.
|
||||||
|
var go = hit.collider.gameObject;
|
||||||
|
if (go.CompareTag("Buildable") || go.GetComponent<Rigidbody>() == null)
|
||||||
|
{
|
||||||
|
Destroy(go);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hasPreview = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDrawGizmos()
|
||||||
|
{
|
||||||
|
if (!_hasPreview || CurrentPrefab == null) return;
|
||||||
|
|
||||||
|
Gizmos.color = previewColor;
|
||||||
|
Vector3 size = Vector3.one * gridSize;
|
||||||
|
Gizmos.DrawCube(_lastPreviewPos, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
Assets/Scripts/Player/PlayerController.cs
Normal file
135
Assets/Scripts/Player/PlayerController.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.Player
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// First-person controller with walk/run/jump and a fly-mode toggle.
|
||||||
|
/// Attach to a GameObject with CharacterController and a child Camera.
|
||||||
|
/// </summary>
|
||||||
|
[RequireComponent(typeof(CharacterController))]
|
||||||
|
public class PlayerController : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("View")]
|
||||||
|
[SerializeField] private Camera playerCamera;
|
||||||
|
[SerializeField] private float mouseSensitivity = 2f;
|
||||||
|
[SerializeField] private float minPitch = -80f;
|
||||||
|
[SerializeField] private float maxPitch = 80f;
|
||||||
|
|
||||||
|
[Header("Movement")]
|
||||||
|
[SerializeField] private float walkSpeed = 5f;
|
||||||
|
[SerializeField] private float runMultiplier = 1.8f;
|
||||||
|
[SerializeField] private float jumpForce = 5f;
|
||||||
|
[SerializeField] private float gravity = 9.81f;
|
||||||
|
|
||||||
|
[Header("Fly Mode")]
|
||||||
|
[SerializeField] private bool startInFlyMode = false;
|
||||||
|
[SerializeField] private float flySpeedMultiplier = 2f;
|
||||||
|
[SerializeField] private KeyCode flyToggleKey = KeyCode.F;
|
||||||
|
|
||||||
|
public bool IsFlying { get; private set; }
|
||||||
|
|
||||||
|
private CharacterController _controller;
|
||||||
|
private float _yaw;
|
||||||
|
private float _pitch;
|
||||||
|
private Vector3 _velocity;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
_controller = GetComponent<CharacterController>();
|
||||||
|
if (playerCamera == null)
|
||||||
|
playerCamera = GetComponentInChildren<Camera>();
|
||||||
|
|
||||||
|
Vector3 euler = transform.localEulerAngles;
|
||||||
|
_yaw = euler.y;
|
||||||
|
_pitch = euler.x;
|
||||||
|
|
||||||
|
Cursor.lockState = CursorLockMode.Locked;
|
||||||
|
Cursor.visible = false;
|
||||||
|
|
||||||
|
IsFlying = startInFlyMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
HandleLook();
|
||||||
|
HandleModeToggle();
|
||||||
|
HandleMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleLook()
|
||||||
|
{
|
||||||
|
if (Cursor.lockState != CursorLockMode.Locked || playerCamera == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
|
||||||
|
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
|
||||||
|
|
||||||
|
_yaw += mouseX;
|
||||||
|
_pitch -= mouseY;
|
||||||
|
_pitch = Mathf.Clamp(_pitch, minPitch, maxPitch);
|
||||||
|
|
||||||
|
transform.rotation = Quaternion.Euler(0f, _yaw, 0f);
|
||||||
|
playerCamera.transform.localRotation = Quaternion.Euler(_pitch, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleModeToggle()
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(flyToggleKey))
|
||||||
|
{
|
||||||
|
IsFlying = !IsFlying;
|
||||||
|
// Reset vertical velocity when switching modes
|
||||||
|
_velocity.y = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleMove()
|
||||||
|
{
|
||||||
|
Vector3 input = new Vector3(
|
||||||
|
Input.GetAxisRaw("Horizontal"),
|
||||||
|
0f,
|
||||||
|
Input.GetAxisRaw("Vertical")
|
||||||
|
).normalized;
|
||||||
|
|
||||||
|
float speed = walkSpeed;
|
||||||
|
if (Input.GetKey(KeyCode.LeftShift))
|
||||||
|
speed *= runMultiplier;
|
||||||
|
|
||||||
|
if (IsFlying)
|
||||||
|
speed *= flySpeedMultiplier;
|
||||||
|
|
||||||
|
Vector3 move = transform.TransformDirection(input) * speed;
|
||||||
|
|
||||||
|
if (IsFlying)
|
||||||
|
{
|
||||||
|
// Vertical fly
|
||||||
|
if (Input.GetKey(KeyCode.Space))
|
||||||
|
move.y += speed;
|
||||||
|
if (Input.GetKey(KeyCode.LeftControl))
|
||||||
|
move.y -= speed;
|
||||||
|
|
||||||
|
// No gravity
|
||||||
|
_controller.Move(move * Time.deltaTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Grounded mode with gravity / jump
|
||||||
|
if (_controller.isGrounded)
|
||||||
|
{
|
||||||
|
_velocity.y = -1f; // small downward force
|
||||||
|
|
||||||
|
if (Input.GetKeyDown(KeyCode.Space))
|
||||||
|
{
|
||||||
|
_velocity.y = jumpForce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_velocity.y -= gravity * Time.deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 finalMove = new Vector3(move.x, _velocity.y, move.z);
|
||||||
|
_controller.Move(finalMove * Time.deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Assets/Scripts/UI/DebugHUD.cs
Normal file
68
Assets/Scripts/UI/DebugHUD.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using BlackRoad.Worldbuilder.Environment;
|
||||||
|
using BlackRoad.Worldbuilder.Building;
|
||||||
|
using BlackRoad.Worldbuilder.Player;
|
||||||
|
|
||||||
|
namespace BlackRoad.Worldbuilder.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Very simple on-screen HUD for development:
|
||||||
|
/// shows time of day, current build prefab, and fly/walk mode.
|
||||||
|
/// </summary>
|
||||||
|
public class DebugHUD : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private DayNightCycle dayNight;
|
||||||
|
[SerializeField] private BuildTool buildTool;
|
||||||
|
[SerializeField] private PlayerController playerController;
|
||||||
|
|
||||||
|
[Header("Style")]
|
||||||
|
[SerializeField] private int fontSize = 14;
|
||||||
|
[SerializeField] private Color textColor = Color.white;
|
||||||
|
[SerializeField] private Vector2 margin = new Vector2(10f, 10f);
|
||||||
|
|
||||||
|
private GUIStyle _style;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (dayNight == null)
|
||||||
|
dayNight = FindObjectOfType<DayNightCycle>();
|
||||||
|
if (buildTool == null)
|
||||||
|
buildTool = FindObjectOfType<BuildTool>();
|
||||||
|
if (playerController == null)
|
||||||
|
playerController = FindObjectOfType<PlayerController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGUI()
|
||||||
|
{
|
||||||
|
if (_style == null)
|
||||||
|
{
|
||||||
|
_style = new GUIStyle(GUI.skin.label)
|
||||||
|
{
|
||||||
|
fontSize = fontSize,
|
||||||
|
normal = { textColor = textColor }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
float x = margin.x;
|
||||||
|
float y = margin.y;
|
||||||
|
|
||||||
|
string mode = playerController != null && playerController.IsFlying ? "FLY" : "WALK";
|
||||||
|
string blockName = buildTool != null && buildTool.CurrentPrefab != null
|
||||||
|
? buildTool.CurrentPrefab.name
|
||||||
|
: "(none)";
|
||||||
|
|
||||||
|
string timeStr = dayNight != null
|
||||||
|
? $"{(dayNight.timeOfDay * 24f):0.0}h"
|
||||||
|
: "n/a";
|
||||||
|
|
||||||
|
GUI.Label(new Rect(x, y, 400f, 24f),
|
||||||
|
$"Time: {timeStr} Mode: {mode} Block: {blockName}",
|
||||||
|
_style);
|
||||||
|
|
||||||
|
y += 22f;
|
||||||
|
GUI.Label(new Rect(x, y, 400f, 24f),
|
||||||
|
"LMB: place RMB: remove 1–9: select prefab F: toggle fly",
|
||||||
|
_style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user