展示链接:打靶子(ShootTarget)_网络游戏热门视频
源码链接:传送门
游戏玩法
玩家将操控十字弩在游戏场景中移动,如果移动到了指定场景,玩家可以通过点击鼠标左键蓄力射出弓箭,当弓箭射中靶子之后,玩家可以获得相应的分数。值得注意的是,每个指定位置可以使用的弓箭的数量是有限的,当箭被射完后,玩家将无法再射箭。按R键可以重置游戏。
游戏画面
游戏场景的构建
地形构建
在层级选项卡中点击鼠标右键,选中3D对象,找到并创建地形
选中创建的地形,在检查器中找到Terrain属性,将这个位置调整为Raise or Lower Terrain
然后使用笔刷在画面中点点画画,以创造不同的地形
然后在检查器中找到Terrain属性,将这个位置调整为Paint Texture,选择编辑地形层,然后导入图片材料
在绘制好的地形上的不同位置涂上不同的图片。
生成树
选中创建的地形,在检查器中找到Terrain属性,选择箭头所指的绘制树,再点击编辑树以导入树的预制体,然后在地形上画出预制体即可。
生成草
和生成树一样。
制作靶子
创建一个空对象,然后在空对象中创建五个圆柱体,5个圆柱体数据如下:
记得给每个圆柱体加上Mesh Collider的碰撞组件。
游戏代码实现
首先我们创建了一个随时间改变天空盒的脚本ChangeSky.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChangeSky : MonoBehaviour
{
public Material[] skyboxMaterials;
int index;
// Start is called before the first frame update
void Start()
{
index = skyboxMaterials.Length - 1;
StartCoroutine(ChangeSkyBox());
}
IEnumerator ChangeSkyBox()
{
while (true)
{
// 随机选择一个天空盒子材质
index = (index + 1) % skyboxMaterials.Length;
RenderSettings.skybox = skyboxMaterials[index];
// 等待一段时间后再切换天空盒子
yield return new WaitForSeconds(10);
}
}
}
生成靶子和射击区域TargetGenerator.cs
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class TargetGenerator : MonoBehaviour
{
public GameObject targetPrefab; // 目标对象的预制体
public GameObject shootAreaPrefab; // 设计区域预制体
public int numberOfTargets = 6; // 要生成的目标数量
public int ArrowCount = 6;
public List<int> arrows = new List<int>();
void Start()
{
GenerateTargets();
}
void GenerateTargets()
{
for (int i = 0; i < numberOfTargets; i++)
{
// 随机生成目标的位置
Vector3 randomPosition;
randomPosition = new Vector3((i + 1) * 10 + Random.Range(-2.5f, 2.5f) - 60, Random.Range(2f, 2.5f), Random.Range(-17f, 3f));
// 使用预制体在随机位置生成目标
GameObject target = Instantiate(targetPrefab, randomPosition, Quaternion.identity);
GameObject shootArea = Instantiate(shootAreaPrefab, new Vector3(randomPosition.x, 0, randomPosition.z - 15f), Quaternion.identity);
// 设置目标的名称
target.name = "Target" + (i + 1);
shootArea.name = "ShootArea" + (i + 1);
if(i % 2 != 0)
{
// 添加 Animator 组件并设置动画控制器
Animator targetAnimator = target.GetComponent<Animator>();
if (targetAnimator != null)
{
// 这里假设你有一个名为 "TargetController" 的动画控制器
if(i == 1)
{
targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target");
shootArea.transform.position = new Vector3(0, 0, -14f);
}else if(i == 3)
{
targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target1");
shootArea.transform.position = new Vector3(25f, 0, -7f);
}
else
{
targetAnimator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>("Animation/Target2");
shootArea.transform.position = new Vector3(10f, 0, -16f);
}
}
}
arrows.Add(ArrowCount);
}
}
// 调用这个函数来删除指定标签的游戏对象
private void DeleteObjectsWithLabel(string label)
{
// 获取场景中所有的游戏对象
GameObject[] allObjects = GameObject.FindObjectsOfType<GameObject>();
// 遍历游戏对象数组
foreach (GameObject obj in allObjects)
{
// 检查游戏对象是否有标签,并且标签匹配
if (obj.CompareTag(label))
{
// 删除游戏对象
Destroy(obj);
}
}
}
public void Reset()
{
arrows.Clear();
DeleteObjectsWithLabel("Target");
DeleteObjectsWithLabel("ShootArea");
GenerateTargets();
}
}
控制十字弩的运动,射击CrossbowMove.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CrossbowMove : MonoBehaviour
{
Camera cam;
CharacterController playerController;
Vector3 direction;
public float speed = 5;
public float jumpPower = 3;
public float gravity = 9.8f;
public float mousespeed = 10f;
public float minmouseY = -45f;
public float maxmouseY = 45f;
float RotationY = 0f;
float RotationX = 0f;
float time;
bool canShoot = false;
int pos = -1;
// Start is called before the first frame update
void Awake()
{
cam = Camera.main;
cam.transform.parent = this.transform;
cam.transform.localPosition = new Vector3(0, 1, 0);
cam.transform.localRotation = Quaternion.Euler(45, 0, 0);
playerController = this.GetComponent<CharacterController>();
canShoot = false;
}
// Update is called once per frame
void Update()
{
HandleRotationInput();
// Handle ground movement and jumping
if (IsGrounded())
{
HandleGroundMovement();
}
ApplyGravity();
// Move the player
playerController.Move(direction * Time.deltaTime * speed);
// Shoot
Shoot();
}
void HandleRotationInput()
{
float mouseX = Input.GetAxis("Mouse X") * mousespeed;
float mouseY = -Input.GetAxis("Mouse Y") * mousespeed; // Inverted for more intuitive camera control
RotationX += mouseX;
RotationY = Mathf.Clamp(RotationY + mouseY, minmouseY, maxmouseY);
// Apply rotation using Quaternion
transform.rotation = Quaternion.Euler(RotationY, RotationX, 0);
cam.transform.localRotation = Quaternion.Euler(RotationY / 4, 0, 0);
}
bool IsGrounded()
{
float raycastDistance = 5f; // 调整为适当的值
float characterHeight = 1.1f; // 角色的高度,调整为适当的值
// 发射射线向下检测地面
if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, raycastDistance))
{
// 计算地面和角色的高度差
float groundHeight = hit.point.y;
float heightDifference = transform.position.y - groundHeight;
// 如果高度差小于等于角色高度的一半,则认为角色在地面上
return heightDifference <= characterHeight;
}
// 如果射线未击中地面,则认为角色不在地面上
return false;
}
void HandleGroundMovement()
{
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// Calculate movement direction based on rotation
Vector3 forward = transform.forward;
Vector3 right = transform.right;
// Calculate movement input
Vector3 moveDirection = horizontal * right + vertical * forward;
moveDirection.Normalize();
// Jumping
if (Input.GetKeyDown(KeyCode.Space))
{
moveDirection.y = jumpPower;
}
// Set the direction
direction = moveDirection;
}
void ApplyGravity()
{
// Apply gravity
direction.y -= gravity * Time.deltaTime;
}
void Shoot()
{
TargetGenerator t = FindObjectOfType<TargetGenerator>();
if (canShoot && t.arrows[pos] > 0)
{
View view = FindObjectOfType<View>();
if (Input.GetMouseButtonDown(0))
{
time = 0;
}
else if (Input.GetMouseButton(0))
{
time += Time.deltaTime;
float power = Mathf.Clamp01(GetComponent<Animator>().GetFloat("Power") + time / 100);
GetComponent<Animator>().SetFloat("Power", power);
view.UpdatePower(power);
}
else if (Input.GetMouseButtonUp(0))
{
GetComponent<Animator>().SetTrigger("Shoot");
GetComponent<Animator>().SetFloat("Power", 0.5f);
view.UpdatePower(0f);
// Instantiate arrow with a slight offset in the Y-axis
GameObject arrowPrefab = Resources.Load<GameObject>("Prefabs/Arrow");
Vector3 arrowSpawnPosition = transform.position + new Vector3(0, 1.0f, 0); // Adjust the Y-axis offset
GameObject arrow = Instantiate(arrowPrefab, arrowSpawnPosition, transform.rotation);
GameObject subCamPrefab = Resources.Load<GameObject>("Prefabs/subCamera");
Vector3 subCamSpawnPosition = arrow.transform.position + new Vector3(0, 0.2f, -0.5f);
Quaternion rotation = Quaternion.Euler(0, 0, 0);
GameObject subCam = Instantiate(subCamPrefab, subCamSpawnPosition, rotation);
subCam.transform.SetParent(arrow.transform);
// Add Rigidbody component to the arrow
Rigidbody arrowRigidbody = arrow.GetComponent<Rigidbody>();
// Set velocity to the arrow based on the character's forward direction and power
float arrowSpeed = Mathf.Min(10.0f + time * 5, 20f); // Adjust the speed as needed
arrowRigidbody.velocity = transform.forward * arrowSpeed;
t.arrows[pos]--;
}
}
}
private void OnTriggerStay(Collider other)
{
if (other.CompareTag("ShootArea"))
{
// 获取碰撞的具体圆柱体名称
string shootAreaName = other.name.Substring("ShootArea".Length);
// 提取圆柱体名称中的数字
int shootAreaNumber;
if (int.TryParse(shootAreaName, out shootAreaNumber))
{
pos = shootAreaNumber - 1;
}
canShoot = true;
View view = FindObjectOfType<View>();
TargetGenerator t = FindObjectOfType<TargetGenerator>();
view.UpdateShoot(canShoot);
view.UpdateArrowCount(t.arrows[pos]);
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("ShootArea"))
{
pos = -1;
canShoot = false;
View view = FindObjectOfType<View>();
view.UpdateShoot(canShoot);
}
}
}
对箭的控制ArrowBehavior.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.WebCam;
public class ArrowBehavior : MonoBehaviour
{
private bool isFixed = false; // 用于标记箭是否已经固定在胶囊体上
private void OnCollisionStay(Collision collision)
{
if (!isFixed)
{
// 检测碰撞对象是否是圆柱体
if (collision.collider.CompareTag("Target"))
{
// 获取碰撞的具体圆柱体名称
string cylinderName = collision.collider.name;
// 提取圆柱体名称中的数字
int cylinderNumber;
if (int.TryParse(cylinderName, out cylinderNumber))
{
// 在这里可以根据具体的圆柱体编号执行相应的逻辑
Debug.Log("Arrow hit capsule: " + cylinderNumber);
transform.SetParent(collision.transform.parent.transform);
// 固定箭在圆柱体上
FixArrowToCapsule(collision.contacts[0].point);
StartCoroutine(DisableSubCamAfterDelay(transform.GetChild(3).gameObject, 2.0f));
// 获取记分系统并调用加分方法
ScoreManager scoreManager = FindObjectOfType<ScoreManager>();
View view = FindObjectOfType<View>();
if (scoreManager != null)
{
string TargetName = collision.transform.parent.name.Substring("Target".Length);
int TargetNumber;
if (int.TryParse(TargetName, out TargetNumber))
{
if(TargetNumber %2 == 0)
{
scoreManager.IncreaseScore(cylinderNumber + TargetNumber);
}
else
{
scoreManager.IncreaseScore(cylinderNumber);
}
view.UpdateScore(scoreManager.score);
view.UpdateMaxScore(scoreManager.score);
view.AddHitTarget(collision.transform.parent.name, cylinderNumber);
}
}
}
}
else if (collision.transform.name == "Ground")
{
Destroy(this.gameObject);
}
}
}
private void FixArrowToCapsule(Vector3 contactPoint)
{
// 固定标记设为 true
isFixed = true;
// 可以执行其他固定逻辑,例如禁用箭的 Rigidbody 组件等
Rigidbody arrowRigidbody = GetComponent<Rigidbody>();
if (arrowRigidbody != null)
{
arrowRigidbody.isKinematic = true; // 禁用 Rigidbody 的运动
}
// 将箭的位置调整到碰撞点
transform.GetChild(2).position = contactPoint;
transform.GetChild(1).gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
}
private IEnumerator DisableSubCamAfterDelay(GameObject subCam, float delay)
{
yield return new WaitForSeconds(delay);
// 禁用subCam
subCam.gameObject.SetActive(false);
}
}
记分ScoreManager.cs
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public int score;
public ScoreManager()
{
score = 0;
}
public void IncreaseScore(int s)
{
// 箭射中目标,增加分数
score += s;
}
public void Reset()
{
score = 0;
}
}
界面View.cs
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class View : MonoBehaviour
{
private int score = 0;
private float power = 0.0f;
private float maxPower = 1.0f;
private bool shoot = false;
private int arrowCount = 6;
private int maxScore = 0;
// 记录击中箭靶的列表
private List<string> hitTargets = new List<string>();
private List<int> hitPoints = new List<int>();
// 用于滚动的位置
private Vector2 scrollPosition = Vector2.zero;
private void OnGUI()
{
// 设置GUI样式
GUIStyle style = new GUIStyle();
style.fontSize = 24; // 设置默认字体大小
style.normal.textColor = Color.black;
// 显示分数
GUI.Label(new Rect(10, 10, 200, 30), "得分: " + score, style);
// 显示最高分数
GUI.Label(new Rect(120, 10, 200, 30), "最高分: " + maxScore, style);
// 显示蓄力条
DrawPowerBar();
// 绘制红点在屏幕中央
float dotSize = 10f;
float dotPositionX = Screen.width / 2 - dotSize / 2;
float dotPositionY = Screen.height / 2 - dotSize / 2;
Texture2D redDotTexture = new Texture2D(1, 1);
redDotTexture.SetPixel(0, 0, Color.red);
redDotTexture.Apply();
GUI.DrawTexture(new Rect(dotPositionX, dotPositionY, dotSize, dotSize), redDotTexture);
// 重置游戏
GUI.Label(new Rect(Screen.width - 210, Screen.height - 55, 200, 30), "按R键重置游戏", style);
// 设置列表的位置和大小
float listPositionX = 10f;
float listPositionY = 60f;
float listItemHeight = 20f; // 设置列表项默认高度
float listHeight = 8 * listItemHeight; // 固定列表高度,超过这个高度将出现滚动条
// 使用ScrollView实现滚动
scrollPosition = GUI.BeginScrollView(new Rect(listPositionX, listPositionY, 200, listHeight), scrollPosition, new Rect(0, 0, 200, hitTargets.Count * listItemHeight));
// 遍历击中箭靶的列表,并显示记录
for (int i = 0; i < hitTargets.Count; i++)
{
// 设置记录的字体大小为16
GUIStyle recordStyle = new GUIStyle(style);
recordStyle.fontSize = 16;
recordStyle.normal.textColor = Color.black;
GUI.Label(new Rect(0, i * listItemHeight, 200, listItemHeight), "恭喜射中" + hitTargets[i] + "的" + hitPoints[i] + "环", recordStyle);
}
// 结束ScrollView
GUI.EndScrollView();
if (!shoot)
{
// 设置字体
GUIStyle shootStyle = new GUIStyle(style);
shootStyle.fontSize = 16;
shootStyle.normal.textColor = Color.red;
GUI.Label(new Rect(10, Screen.height - 30f, 90.0f, 30.0f), "您现在不能射击,请前往指定区域", shootStyle);
}
else
{
// 设置字体
GUIStyle shootStyle = new GUIStyle(style);
shootStyle.fontSize = 16;
shootStyle.normal.textColor = Color.magenta;
GUI.Label(new Rect(10, Screen.height - 30f, 90.0f, 30.0f), "您现在能射击了!!!", shootStyle);
shootStyle.normal.textColor = Color.yellow;
GUI.Label(new Rect(Screen.width / 2 - 45.0f, 30.0f, 90.0f, 30.0f), "此处还剩箭:" + arrowCount + "只", shootStyle);
}
}
// 提供一个方法用于更新分数
public void UpdateScore(int newScore)
{
score = newScore;
}
public void UpdateMaxScore(int newScore)
{
if(score > maxScore)
{
maxScore = score;
}
}
public void UpdatePower(float newPower)
{
power = newPower;
}
public void UpdateShoot(bool newShoot)
{
shoot = newShoot;
}
public void UpdateArrowCount(int newArrowCount)
{
arrowCount = newArrowCount;
}
// 添加箭靶击中记录
public void AddHitTarget(string targetName,int hitpos)
{
hitTargets.Add(targetName);
hitPoints.Add(hitpos);
}
// 绘制蓄力条
private void DrawPowerBar()
{
float barLength = 200f;
float barHeight = 20f;
float barPositionX = (Screen.width - barLength) - 10;
float barPositionY = Screen.height - barHeight - 5;
// 绘制蓄力条的背景
GUI.Box(new Rect(barPositionX, barPositionY, barLength, barHeight), "Power");
// 计算蓄力条的填充比例
float fillRatio = power / maxPower;
// 绘制蓄力条的填充部分
GUI.Box(new Rect(barPositionX, barPositionY, barLength * fillRatio, barHeight), GUIContent.none);
}
public void Reset()
{
score = 0;
power = 0;
maxPower = 1.0f;
shoot = false;
arrowCount = 6;
hitTargets.Clear();
hitPoints.Clear();
scrollPosition = Vector2.zero;
}
}
重置游戏ResetManager.cs
using UnityEngine;
public class ResetManager : MonoBehaviour
{
// 引用View、ScoreManager和TargetGenerator脚本
private View view;
private ScoreManager scoreManager;
private TargetGenerator targetGenerator;
private void Awake()
{
view = FindObjectOfType<View>();
scoreManager = FindObjectOfType<ScoreManager>();
targetGenerator = FindObjectOfType<TargetGenerator>();
}
void Update()
{
// 当按下R键时触发Reset函数
if (Input.GetKeyDown(KeyCode.R))
{
// 调用View.cs中的Reset函数
if (view != null)
{
view.Reset();
}
// 调用ScoreManager.cs中的Reset函数
if (scoreManager != null)
{
scoreManager.Reset();
}
// 调用TargetGenerator.cs中的Reset函数
if (targetGenerator != null)
{
targetGenerator.Reset();
}
}
}
}
动画制作
CTRL+6唤出动画选项卡
选中要添加动画的游戏对象,编辑即可