Unity游戏基础-2(初识场景~项目构建)

本教程开始分享教学内容:Unity游戏基础,部分内容会包括Blender建模基础,Shader基础,动画基础等等,教程面向:建模、技术美术、游戏前端、游戏后端基础,适合美术与设计(数媒、环设、动画、产设、视传、绘画)专业、计算机(计算机科学与技术、软件工程、数媒)的同学学习。

场景是游戏或者3D程序的基础,内容在此显示,摄像机是输出场景的物体

加入3D对象(3D Object)

在层级Hierarchy里面,打开SampleSence,右键空白,选择3D对象,点击平面(Plane)

添加一个胶囊Capsule,重命名为Player,把MainCamera拖到Palyer上面,这样player旋转camera也会旋转

添加一个空物体,重命名为GroundCheck,移动到Player,并且调整位置到player胶囊体的下面(悬空),就像这样:

添加一个立方体,面朝Z轴正方向,放在胶囊体前面,摄像机放在Z轴负方向

控制角色

在Assets下新建一个文件夹,重命名为Script,我们要在里面编写脚本

新建2个脚本,重命名为CameraController和PlayerMovement,顾名思义了就是控制摄像机的和控制角色的,后期在添加UI设置时会用到本教程第三个脚本

编写脚本:(完整移动,缩放,旋转,碰撞)

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEditor.Rendering;
using UnityEngine;


public class PlayerMovement : MonoBehaviour
{
    public float moveSpeed = 5f;
    public float rotateSpeed = 10f;
    public float jumpHeight = 1f;
    public float gravity = -9.8f;
    public float pushPower = 3f;

    public Transform groundCheck; // 将你在脚底创建的空物体拖拽到这里
    public float groundCheckRadius = 0.2f; // 检测范围的半径
    public LayerMask groundMask; // 在Inspector中设置为"Ground"层

    private CharacterController controller;
    private Vector3 playerVelocity;
    private bool isGrounded;
    private Transform cameraTransform;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        cameraTransform = Camera.main.transform; // 获取主摄像机变换
    }

    void Update()
    {
        if (CameraController.isleave)
            return;

        // 检查角色是否着地
        isGrounded = Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask);

        if (controller.isGrounded)
        {
            HandleJump();
            CheckSpeed();
        }
        HandleMovement();
    }
    void CheckSpeed()
    {
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            moveSpeed = 15f;
        }
        else if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            moveSpeed = 5f;
        }
    }

    void HandleMovement()
    {
        // 获取输入
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        // 根据摄像机方向计算移动方向
        Vector3 camForward = Vector3.Scale(cameraTransform.forward, new Vector3(1, 0, 1)).normalized; // 忽略摄像机俯仰角
        Vector3 moveDirection = (camForward * vertical + cameraTransform.right * horizontal).normalized;

        // 应用移动速度
        Vector3 movement = moveDirection * moveSpeed * Time.deltaTime;
        controller.Move(movement);

        // 如果需要角色朝向移动方向,可以取消注释下面的代码
        if (moveDirection.magnitude > 0.1f)
        {
            Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime);
        }

        // 应用重力
        playerVelocity.y += gravity * Time.deltaTime;
        controller.Move(playerVelocity * Time.deltaTime);
    }
    void OnControllerColliderHit(ControllerColliderHit hit)
    {
        Rigidbody hitRigidbody = hit.collider.attachedRigidbody;

        // 如果碰撞的对象没有刚体、是运动学刚体,或者被卡在下面,则不推动
        if (hitRigidbody == null || hitRigidbody.isKinematic || hit.moveDirection.y < -0.3f)
        {
            return;
        }

        // 计算推动方向(主要在X和Z轴)
        Vector3 pushDir = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);

        // 给被碰撞的刚体一个速度来实现推动效果
        hitRigidbody.velocity = pushDir * pushPower;
    }
    void HandleJump()
    {
        if (Input.GetButtonDown("Jump") && isGrounded)
        {
            playerVelocity.y = Mathf.Sqrt(jumpHeight * -1f * gravity); // 计算跳跃速度
        }
    }

}
cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor.Rendering;
using UnityEngine;

public class CameraController : MonoBehaviour
{
    public Transform target; // 玩家角色
    public float distance = 5.0f; // 默认相机距离
    public float minDistance = 1.5f; // 最小相机距离
    public float maxDistance = 15.0f; // 最大相机距离
    public float zoomSpeed = 2.0f; // 缩放速度
    public float rotationSpeed = 5.0f; // 旋转速度
    public float minVerticalAngle = -20.0f; // 垂直旋转最小角度
    public float maxVerticalAngle = 80.0f; // 垂直旋转最大角度
    public float collisionOffset = 0.2f; // 碰撞偏移量
    public LayerMask collisionLayers = ~0; // 碰撞检测层(默认所有层)
    public static bool isleave;

    private float currentX = 0.0f;
    private float currentY = 0.0f;
    private float targetDistance;
    private Vector3 cameraDirection;
    /// <summary>
    /// 鼠标
    /// </summary>
    private void CursorController()
    {        

        // 优先判断:只要UI界面打开,就解锁并显示鼠标

        if (ButtonsEventHandler.isopen)
        {
            Cursor.lockState = CursorLockMode.None;
            isleave = true;
            return;
        }
        

        if (Input.GetKeyDown(KeyCode.LeftAlt))
        {
            Cursor.lockState = CursorLockMode.None;
            isleave = true;
        }
        if (Input.GetKeyUp(KeyCode.LeftAlt))
        {
            //Debug.Log(ButtonsEventHandler.isopen);
            Cursor.lockState = CursorLockMode.Locked;
            isleave = false;
        }

    }

    void Start()
    {
        // 初始化当前旋转角度
        Cursor.lockState = CursorLockMode.Locked;
        Vector3 angles = transform.eulerAngles;
        currentX = angles.y;
        currentY = angles.x;
        targetDistance = distance;
        cameraDirection = (transform.position - target.position).normalized;
    }
     void Update()
    {
        CursorController();

    }
    void LateUpdate()
    {
        
        if (target == null)
            return;
        if (isleave)
            return;
        // 处理鼠标输入
        HandleInput();

        // 计算相机旋转和方向
        Quaternion rotation = Quaternion.Euler(currentY, currentX, 0);
        Vector3 direction = rotation * Vector3.back;

        // 碰撞检测和避免
        HandleCameraCollision(direction);

        // 应用相机位置和旋转
        transform.position = target.position + direction * targetDistance;
        transform.LookAt(target.position);
    }

    void HandleInput()
    {
        // 鼠标旋转控制
        //if (Input.GetMouseButton(0)) // 右键按住旋转
        //{
        currentX += Input.GetAxis("Mouse X") * rotationSpeed;
        currentY -= Input.GetAxis("Mouse Y") * rotationSpeed;
        currentY = Mathf.Clamp(currentY, minVerticalAngle, maxVerticalAngle);
        //}

        // 鼠标滚轮缩放
        float scroll = Input.GetAxis("Mouse ScrollWheel");
        if (scroll != 0)
        {
            distance -= scroll * zoomSpeed;
            distance = Mathf.Clamp(distance, minDistance, maxDistance);
        }
    }
    void HandleCameraCollision(Vector3 direction)
    {
        RaycastHit hit;
        // 定义一个小的偏移量,将射线起点从角色中心向后移动,避免与自身碰撞
        float selfAvoidanceOffset = 1f;
        Vector3 rayStartPoint = target.position + direction * selfAvoidanceOffset;
        float rayLength = distance - selfAvoidanceOffset;

        // 从调整后的起点发射射线
        if (Physics.Raycast(rayStartPoint, direction, out hit, rayLength, collisionLayers))
        {
            // 如果检测到碰撞,调整相机距离
            targetDistance = Mathf.Clamp(hit.distance - collisionOffset, minDistance, maxDistance);
        }
        else
        {
            // 没有碰撞则使用预设距离
            targetDistance = distance;
        }
    }

}

现在会处于一个报错的状态,没事,我们再来写一个脚本ButtonsEventHandler

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

[Serializable]
public class Config
{
    private static int SelectIndex;
    private static bool isfull;
}

public class ButtonsEventHandler : MonoBehaviour
{
    public static Config cfg;

    public  TMP_Dropdown resolutionDropdown; // 在Inspector中关联你的ResolutionDropdown
    public Toggle fullscreenToggle; // 在Inspector中关联你的FullscreenToggle
    public GameObject SeetingForm;
    /// <summary>
    /// 指示设置窗口是否开启
    /// </summary>
    public static bool isopen = false;

    private static int SelectIndex = 0;

    private static bool isfull = false;
    /// <summary>
    /// 获取所有支持的分辨率
    /// </summary>
    private static List<Resolution> sizes = new List<Resolution>();

    // Start is called before the first frame update
    void Start()
    {        
        SeetingForm.SetActive(false);
        LoadSize();
        resolutionDropdown.onValueChanged.RemoveListener(OnDropdownValueChanged);
        resolutionDropdown.onValueChanged.AddListener(OnDropdownValueChanged);

        fullscreenToggle.onValueChanged.RemoveListener(onToggleValueChanged);
        fullscreenToggle.onValueChanged.AddListener(onToggleValueChanged);
    }
    private void onToggleValueChanged(bool check)
    {
        isfull = check;
    }
    private void OnDropdownValueChanged(int newIndex)
    {
        SelectIndex = newIndex;
    }
    void LoadSize()//加载所有分辨率
    {
        resolutionDropdown.ClearOptions();//清空列表
        sizes.Clear();

        List<string> size1 = new List<string>();

        foreach (var s in Screen.resolutions)
        {
            sizes.Add(s);
            size1.Add(s.width + "x" + s.height + " " + s.refreshRateRatio);
            Debug.Log(s.width + "x" + s.height + " " + s.refreshRateRatio);
        }

        resolutionDropdown.AddOptions(size1);//全部加载分辨率
    }


    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            OnButtonPress();
        }
    }

    public void OnButtonPress()//打开或关闭设置界面,打开就加载分辨率
    {
        if (isopen)
        {
            SeetingForm.SetActive(false);
            isopen = false;
            Cursor.lockState = CursorLockMode.Locked;
            CameraController.isleave = false;
        }
        else
        {
            SeetingForm.SetActive(true);
            isopen = true;
            Cursor.lockState = CursorLockMode.None;
            CameraController.isleave = true;
            LoadSize();
        }

    }
    public void Apply()
    {

        FullScreenMode mode;
        if (isfull)
        {
            mode = FullScreenMode.FullScreenWindow;
        }
        else
        {
            mode = FullScreenMode.Windowed;
        }
        Screen.SetResolution(sizes[SelectIndex].width, sizes[SelectIndex].height, mode, sizes[SelectIndex].refreshRateRatio);
    }


    public void Cancel()
    {
        SeetingForm.SetActive(false);
        isopen = false;
    }


    public void Close()
    {
        Application.Quit();
    }



}

挂载脚本

将 PlayerMovement直接拖到Hierarchy窗口里面的Player上面,然后在Inspector窗口里发现了你的脚本

修改Plane的图层,点击Plane,在inspector里面找到图层下拉列表,添加一个Ground层

点击Player,把groundcheck空物体拖到脚本的Ground Check

设置GroundMask为Ground

设置成功的样子Inspector

player的

Plane的

挂载CameraController

把CameraController拖到Main Camera上,把目标(Target)设置为Player

为Player添加组件(Add Component),搜索Character Controller

完成!

点击上面的小三角开始调试

现在按WASD就可以移动你的胶囊体了

构建项目:Ctrl+B开始游戏(直接新建文件夹Bin)

(报错的话删除脚本里面没有使用的命名空间,通常是灰色的)

生成设置Ctrl+Shift+B,下一节我们教学分辨率自定义设置

相关推荐
utmhikari7 小时前
【测试人生】LLM赋能游戏自动化测试的一些想法
自动化测试·游戏·ai·大模型·llm·游戏测试
maki0777 小时前
虚幻版Pico大空间VR入门教程 04 —— PicoOpenXR和PicoXR插件对于PDC串流、SteamVR串流、OpenXR串流对比 和 手势追踪对比
游戏引擎·vr·虚幻·pico·手势追踪·串流
maki0778 小时前
虚幻版Pico大空间VR入门教程 03 —— PicoXR插件和PicoOpenXR插件的文档对比记录
游戏引擎·虚幻·pico
maki07712 小时前
虚幻版Pico大空间VR入门教程 02 —— Pico设备选择
游戏引擎·vr·虚幻·pico
gopyer12 小时前
180课时吃透Go语言游戏后端开发7:Go语言中的函数
开发语言·游戏·golang·go·函数
taulee0115 小时前
在云服务器搭建部署私人饥荒联机版游戏服务器 [2025.10.3][ubuntu 24.04][腾讯云2核2G服务器]
服务器·ubuntu·游戏
HELLOMILI15 小时前
[UnrealEngine] 虚幻引擎UE5地形入门指南 | UE5地形教程(UE5 Terrain)
游戏·ue5·游戏引擎·虚幻·虚幻引擎·unreal engine
爱吃小胖橘1 天前
Lua语法
开发语言·unity·lua
qq_205279051 天前
unity 读取PPT显示到屏幕功能
unity·游戏引擎·powerpoint