这次作业是要做第一人称射击游戏,我按要求下载了CrossBow的asset,另外下载了场景、天空盒和靶子的资源,整体场景设置如下所示,红色区域是射击区域,右上角显示分数、剩余子弹数量和状态(是否允许射击),按p可切换天空盒,以显示不同时间,主角碰到靶子会死亡。
下面是每一环节具体的代码
1.主角移动
我把弓箭资源和main camera都放在一个空对象player下,以实现相机的移动跟随
角色移动逻辑,挂载在player对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public CharacterController controller;
public float speed = 5;
public float gravity = -9.18f;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
Vector3 velocity;
bool isGrounded;
bool allowMovement = true;
void Update()
{
if (!allowMovement) return;
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
if (Input.GetKey("left shift") && isGrounded)
{
speed = 10;
}
else
{
speed = 5;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
if (Input.GetButtonDown("Jump") && isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
public void DisableMovement()
{
allowMovement = false;
}
}
弓箭控制逻辑,挂载在弓箭对象上,并将prefab中的arrow拖入arrowPrefab中
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class BowController : MonoBehaviour
{
private Animator animator;
private bool isHoldingBow = false;
public GameObject arrowPrefab; // 预制体
public static BowController instance;
public int scoreValue = 0;
public int num = 0;
public bool isAtk;
public GameObject panel;
private void Awake()
{
instance = this;
}
public void AddScore()
{
scoreValue++;
UiController uiController = FindObjectOfType<UiController>();
if (uiController != null)
{
uiController.UpdateScoreAndBullets(scoreValue, num);
}
else
{
Debug.LogError("UiController not found in the scene.");
}
}
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
animator = GetComponent<Animator>();
UiController uiController = FindObjectOfType<UiController>();
if (uiController != null)
{
uiController.UpdateScoreAndBullets(scoreValue, num);
uiController.UpdateState(isAtk);
}
else
{
Debug.LogError("UiController not found in the scene.");
}
}
public void RestartGame()
{
SceneManager.LoadScene(0);
}
void Update()
{
UiController uiController = FindObjectOfType<UiController>();
uiController.UpdateScoreAndBullets(scoreValue, num);
if (!isAtk || num <= 0) { return; }
if (Input.GetMouseButtonDown(0))
{
animator.SetBool("Fire", true);
isHoldingBow = true;
}
if (Input.GetMouseButtonUp(0))
{
if (isHoldingBow)
{
isHoldingBow = false;
animator.SetBool("Fire", false);
ShootArrow();
num--;
}
}
}
void ShootArrow()
{
if (arrowPrefab != null)
{
// 实例化箭矢
GameObject arrow = Instantiate(arrowPrefab);
arrow.transform.SetParent(transform);
arrow.transform.localPosition = transform.GetChild(4).transform.localPosition;
arrow.transform.localRotation = transform.GetChild(4).transform.localRotation;
// 获取箭矢上的刚体组件
Rigidbody arrowRigidbody = arrow.GetComponent<Rigidbody>();
//Destroy(arrow, 10f);
arrowRigidbody.useGravity = true;
// 检查刚体组件是否存在
if (arrowRigidbody != null)
{
arrowRigidbody.AddForce(transform.up * 60f, ForceMode.Impulse);
}
}
}
}
2. 设计区域逻辑
根据要求,在固定红色区域才可以射击,别的地方禁止射击,这部分代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShootPoint : MonoBehaviour
{
public int atkNum = 10;
void Start()
{
}
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
Debug.Log("OnTriggerEnter: " + other.name);
if (other.gameObject.tag=="Player")
{
UiController uiController = FindObjectOfType<UiController>();
uiController.UpdateState(true);
BowController.instance.isAtk = true;
BowController.instance.num = atkNum;
}
}
private void OnTriggerExit(Collider other)
{
Debug.Log("OnTriggerExit: " + other.name);
if (other.gameObject.tag=="Player")
{
UiController uiController = FindObjectOfType<UiController>();
uiController.UpdateState(false);
atkNum = BowController.instance.num;
BowController.instance.isAtk = false;
}
}
}
3. 靶子销毁逻辑
弓箭射中靶子后,会加分并销毁靶子,挂载在靶子上面
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TargetShoot : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Arrow")
{
BowController.instance.AddScore();
Destroy(gameObject);
Destroy(other.gameObject);
}
}
}
4. 死亡逻辑
角色触碰树后触发死亡逻辑,也挂载在树上
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class TriggerAC : MonoBehaviour
{
public GameObject win;
// Start is called before the first frame update
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Debug.Log("trigger");
UiController uiController = FindObjectOfType<UiController>();
uiController.GameOver(BowController.instance.scoreValue);
Cursor.lockState = CursorLockMode.Confined;
BowController.instance.enabled = false;
BowController.instance.panel.SetActive(true);
PlayerMovement playerMovement = other.GetComponent<PlayerMovement>();
if (playerMovement != null)
{
playerMovement.DisableMovement();
}
}
else print(other.name);
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Player"))
{
Debug.Log("collision");
UiController uiController = FindObjectOfType<UiController>();
uiController.GameOver(BowController.instance.scoreValue);
Cursor.lockState = CursorLockMode.Confined;
}
else print("???");
}
public void LoadS()
{
SceneManager.LoadScene(0);
}
}
5 ui控制逻辑
实现在对应区域更新分数、子弹数量、状态信息,死亡出现“Game over”提示
using UnityEngine.UI;
using UnityEngine;
using TMPro;
using UnityEngine.SocialPlatforms.Impl;
public class UiController : MonoBehaviour
{
public TextMeshProUGUI scoreText;
public TextMeshProUGUI bulletsText;
public TextMeshProUGUI stateText;
public TextMeshProUGUI overText;
void UpdateUI(int score, int bullets)
{
scoreText.text = "Score: " + score.ToString();
bulletsText.text = "Bullets: " + bullets.ToString();
}
public void UpdateScoreAndBullets(int score, int bullets)
{
UpdateUI(score, bullets);
}
public void UpdateState(bool st)
{
if (st)
stateText.text = "State: OK";
else
stateText.text = "State: illegal";
}
public void GameOver(int score)
{
overText.text = "Game over!\nYour final score is" + score.ToString();
}
}
6. 天空盒逻辑
5s一次天空盒的转化,要将天空盒材质提前拖入预设的列表中
using System.Collections;
using UnityEngine;
public class SkyboxSwitcher : MonoBehaviour
{
public Material[] skyboxMaterials;
private int currentSkyboxIndex = 0;
void Start()
{
RenderSettings.skybox = skyboxMaterials[currentSkyboxIndex]; // 初始设置天空盒
StartCoroutine(AutoSwitchSkybox(5f)); // 启动协程,每5秒切换一次
}
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
SwitchSkybox();
}
}
void SwitchSkybox()
{
currentSkyboxIndex = (currentSkyboxIndex + 1) % skyboxMaterials.Length;
RenderSettings.skybox = skyboxMaterials[currentSkyboxIndex];
}
IEnumerator AutoSwitchSkybox(float interval)
{
while (true)
{
yield return new WaitForSeconds(interval);
SwitchSkybox();
}
}
}