Unity--第一人称射箭游戏

2024-03-22 00:20

本文主要是介绍Unity--第一人称射箭游戏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、简介

1. 项目要求:

2. 游戏视频

二、游戏设计

1. 游戏对象:

(1)地形:

(2)天空盒

(3)固定靶

(4)运动靶

(5)箭矢

(6)围墙

(7)文本

(8)弓弩(玩家)

2. 文件组织形式

3. 动画控制器

(1)弓弩射箭的动画控制器

(2)运动靶的动画控制器(两个)

三、代码介绍

1. 动作部分Actions

(1)ISSCallback 回调函数接口

(2)SSAction 动作基类

(3)SSActionManager 动作管理器的基类

(4)CCShootAction 射箭动作

(5)CCShootManager 动作管理器

2. Controllers

(1)Singleton 场景单实例

(2)SSDirector 导演类

(3)ScoreRecorder 计分器

(4)ArrowFactory 箭矢工厂

(5)TargetController 靶子控制器

(6)BowController 弓弩控制器

(7)ISceneController 场景控制器接口

(8)ArrowController 主场景控制器

3. Views

(1)IUserAction 用户交互的接口

(2)UserGUI 用户交互界面

四、代码链接


一、简介

        这是一个第一人称射击游戏。你将以第一人称视角操控弓弩,移动到指定的射击位,使用弩箭射击标靶从而得到相应的分数。

1. 项目要求:

  •  地形:使用地形组件,上面有草、树;
  •  天空盒:使用天空盒,天空可随玩家位置 或 时间变化 或 按特定按键切换天空盒;
  •  固定靶:有一个以上固定的靶标;
  •  运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
  •  射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击,每个位置有 n 次机会;
  •  驽弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
  •  游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
  •  碰撞与计分:在射击位,射中靶标的相应分数,规则自定;

2. 游戏视频

Unity--第一人称射箭游戏

Unity--第一人称射箭游戏_演示 (bilibili.com)icon-default.png?t=N7T8https://www.bilibili.com/video/BV1xw41187ig/

二、游戏设计

1. 游戏对象:

(1)地形:

        使用地形组件,且上面有树和草。

(2)天空盒

        天空盒会随着玩家的位置跳转(按下1、2、3)而改变。

(3)固定靶

        小靶子target和大靶子big_larget做成预制,属性如下所示:

        巨型靶子large_target也做成了预制,但是其每一个环都有自己的碰撞体积,在后续的代码中会为每一个环添加碰撞检测组件:

(4)运动靶

        包含了target_move和target_quick两种运动靶,都包含自己的动作以及相应的动作控制器。

(5)箭矢

        在箭头处包含一个盒型碰撞体。

        需要为其添加三个tag,分别是ground、arrow和onTarget。

(6)围墙

        围墙用于划分不同的区域。其碰撞体积大于物体的真实体积,可以防止箭矢射到其他区域(即所谓的空气墙)。

(7)文本

        用于显示游戏介绍信息和提示信息。

(8)弓弩(玩家)

        为了方便控制视角的转换,这里将弓弩作为一个方块(或其他3D模型)的子对象,该方块可以当作玩家。同时,将主相机作为弓弩的子对象,跟随弓弩一起移动。由于全局只有一个弓弩对象,因此这里没有将其作为预制。

        弓弩对象需要添加动画控制器,用来演示射箭的动作。同时,还需要将弓弩控制器脚本和场景控制器脚本都挂载在弓弩对象上。还需要添加盒装碰撞体来防止与其他游戏对象发生碰撞。

2. 文件组织形式

Assets目录下有如下的文件及文件夹:

Fantasy Skybox FREE是在unity store下载的天空盒,RyuGiKen是在unity store下载的弓弩和箭矢,Tree9是在unity store下载的树的模型,TextMesh Pro是添加Text组件时下载的依赖包。

Animations目录下是创建的动画效果和动画控制器。包含了移动靶子的两种动画以及它们相应的动画控制器,以及一个弩射箭的动画控制器:

Resources目录包含了两个子目录Materials和Prefabs。其中Materials目录包含了用于制作靶子预制的材质、墙的材质和天空盒的材质;Prefabs目录包含了箭矢的预制以及多种不同的靶子的预制。如下图所示:

Scenes目录是当前的场景:

Scripts目录下包含了游戏的脚本。其中根据MVC模式和动作分离的规则,将脚本分成了三个子目录:Actions目录下包含动作以及动作控制器的脚本;Controllers目录下包含了各个控制器脚本;Views目录下包括了与用户交互相关的脚本。

3. 动画控制器

(1)弓弩射箭的动画控制器

(2)运动靶的动画控制器(两个)

​​​​​​​

三、代码介绍

1. 动作部分Actions

(1)ISSCallback 回调函数接口

        动作分离模式的模板函数,详情见前几篇博客,不再赘述。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public enum SSActionEventType:int {Started, Completed}   // 枚举动作事件类型
public interface ISSCallback
{//回调函数void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Completed,int intParam = 0,string strParam = null,Object objectParam = null);
}

(2)SSAction 动作基类

        动作分离的模板函数,不再赘述。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 模板代码,不用管
// 动作的基类
public class SSAction : ScriptableObject
{public bool enable = true;                      //是否正在进行此动作public bool destroy = false;                    //是否需要被销毁public GameObject gameobject;                   //动作对象public Transform transform;                     //动作对象的transformpublic ISSCallback callback;              //动作完成后的消息通知者protected SSAction() { }//子类可以使用下面这两个函数public virtual void Start(){throw new System.NotImplementedException();}public virtual void Update(){throw new System.NotImplementedException();}public virtual void FixedUpdate(){throw new System.NotImplementedException();}
}

(3)SSActionManager 动作管理器的基类

        模板函数,不再赘述。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 模板代码,不用管
// 动作管理器的基类
public class SSActionManager : MonoBehaviour
{private Dictionary<int,SSAction> actions = new Dictionary<int,SSAction>();  //执行动作的字典集//等待添加的动作private List<SSAction> waitingAdd = new List<SSAction>();//等待销毁的动作private List<int> waitingDelete = new List<int>();protected void Start(){}// Update每一帧都执行,执行的帧率取决于电脑性能protected void Update() {//添加新增的动作for(int i = 0; i < waitingAdd.Count; ++i){actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i];}waitingAdd.Clear();//销毁需要删除的动作foreach (KeyValuePair<int,SSAction> kv in actions){SSAction ac = kv.Value;if(ac.destroy){waitingDelete.Add(ac.GetInstanceID());}//动作不需要销毁则更新else if(ac.enable){ac.Update();}}//销毁动作foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Object.Destroy(ac);}waitingDelete.Clear();}// FixedUpdate每秒调用50次,适合用来写物理引擎相关的代码protected void FixedUpdate(){for(int i = 0; i < waitingAdd.Count; ++i){actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i];}waitingAdd.Clear();foreach (KeyValuePair<int,SSAction> kv in actions){SSAction ac = kv.Value;if(ac.destroy){waitingDelete.Add(ac.GetInstanceID());}else if(ac.enable){ac.FixedUpdate();}}foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Object.Destroy(ac);}waitingDelete.Clear();}//新增一个动作,运行该动作public void RunAction(GameObject gameobject, SSAction  action, ISSCallback manager){action.gameobject = gameobject;action.transform = gameobject.transform;action.callback = manager;waitingAdd.Add(action);action.Start();}//回调新的动作类型public void SSActionEvent(SSAction source, int param = 0,GameObject arrow = null){//执行完飞行动作}
}

(4)CCShootAction 射箭动作

        该脚本是射箭的具体动作,挂载在箭矢上,实现于动作基类SSAction。

        Start()函数用于初始化,由于箭没射出时是跟随弓移动的,因此需要解除与弓的绑定;然后,设置箭使用重力;为了符合现实规律,这里设置箭的初始速度为0,然后为其添加一个特定方向的冲量;最后关闭箭的运动学控制,使其受物理引擎的控制从而能够运动。

        GetSSAction()函数用于获得一个动作。该函数以冲量的方向impulseDirection和射箭的力power作为参数,然后创建一个动作实例并为其添加指定方向和大小的冲量,最后返回该动作实例。

        FixdUpdate()函数用来不断判断箭矢是否飞出场景、落在地上或者射在靶子上。如果满足上述的一种,则说明该动作完成了,销毁该动作即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 射箭动作
public class CCShootAction : SSAction
{private Vector3 pulseForce;  //射箭提供的冲量private CCShootAction(){}public static CCShootAction GetSSAction(Vector3 impulseDirection,float power)      //获得一个射箭动作实例{CCShootAction shootarrow = CreateInstance<CCShootAction>();shootarrow.pulseForce = impulseDirection.normalized * power;          // 射箭提供的冲量(方向*力度)return shootarrow;}public override void Start(){gameobject.transform.parent = null;        // 摆脱跟随弓移动gameobject.GetComponent<Rigidbody>().useGravity = true;                  //  发射的时候才开启重力gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero;               // 初速度为0gameobject.GetComponent<Rigidbody>().AddForce(pulseForce,ForceMode.Impulse);        //添加冲量//关闭运动学控制gameobject.GetComponent<Rigidbody>().isKinematic = false;      // 此时箭的运动由物理引擎控制}public  override void Update(){}public override void FixedUpdate(){   // 判断箭是否飞出场景、落在地上或者射中靶子if(!gameobject || this.gameobject.tag == "ground" || this.gameobject.tag == "onTarget"  )  {this.destroy = true;     // 摧毁动作this.callback.SSActionEvent(this);}}
}

(5)CCShootManager 动作管理器

         该脚本是射箭动作的管理器,实现于动作管理器基类SSActionManager。

          ArrowShoot()函数以弓箭对象、冲量方向和力的大小为参数。然后,使用冲量方向和力创建一个射箭的动作。最后调用父类的RunAction()函数运行该动作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 射箭动作的管理器
public class CCShootManager : SSActionManager, ISSCallback
{//箭飞行的动作private CCShootAction shoot;                                protected new void Start(){}public void SSActionEvent(SSAction source,  SSActionEventType events = SSActionEventType.Completed,int intParam = 0,string strParam = null,Object objectParam = null) {}//箭飞行public void ArrowShoot(GameObject arrow, Vector3 impulseDirection, float power)          // 游戏对象、力的方向、力的大小{shoot = CCShootAction.GetSSAction(impulseDirection, power);        //实例化一个射箭动作。RunAction(arrow, shoot, this);                  //调用SSActionmanager的方法运行动作。}
}

2. Controllers

(1)Singleton 场景单实例

        场景单实例的模板,前几篇博客讲过,不再赘述。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 场景单实例模板,不用管
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{protected static T instance;public static T Instance {  get {  if (instance == null) { instance = (T)FindObjectOfType (typeof(T));  if (instance == null) {  Debug.LogError ("An instance of " + typeof(T) +" is needed in the scene, but there is none.");  }  }  return instance;  }  }
}

(2)SSDirector 导演类

        导演模板,不再赘述。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 导演类,控制场景切换,模板不用管
public class SSDirector : System.Object
{private static SSDirector _instance;public ISceneController currentSceneController {get; set;}public static SSDirector getInstance() {          // 获取导演实例if (_instance == null) {_instance = new SSDirector();}return _instance;}
}

(3)ScoreRecorder 计分器

        用于记录射箭的总得分。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 计分器类
public class ScoreRecorder : MonoBehaviour
{public int score;void Start(){score = 0;}public void RecordScore(int ringscore){score += ringscore;        //增加新的值}
}

(4)ArrowFactory 箭矢工厂

        工厂模式的脚本,用于生产箭矢和回收箭矢。根据该游戏的需求,这里对工厂模式进行了修改,取消了对象池,将其抽象成加载预制和删除游戏对象的简单工厂。

        GetArrow()函数。该函数从Prefabs目录中获取箭矢的预制。由于采用了动画和动作分开的设计方法,弓弩子对象有一个用于动画演示的箭矢,因此可以把动画箭矢的位置作为动作箭矢的位置,并且设置动作箭矢的旋转角度与弓弩一致。然后将动作箭矢作为弓弩的子对象,以便动作箭矢和弓弩一起移动。为了防止动作箭矢和弓弩产生碰撞而产生错误,这里可以先把动作箭矢隐藏起来。最后,把该动作箭矢返回即可。

        RecycleArrow()函数。该函数以需要回收的箭矢为输入参数,然后将箭矢隐藏起来,并立即删除该箭矢对象即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 箭的工厂,用于创建箭和回收箭
public class ArrowFactory : MonoBehaviour
{public ArrowController arrowCtrl;public GameObject arrow;void Start(){arrowCtrl = (ArrowController)SSDirector.getInstance().currentSceneController;}void Update(){}public GameObject GetArrow(){    // 获取空闲的箭arrow = GameObject.Instantiate(Resources.Load("Prefabs/Arrow", typeof(GameObject))) as GameObject;//得到弓箭上搭箭的位置                       Transform bow_mid = arrowCtrl.bow.transform.GetChild(4);   // 获得箭应该放置的位置arrow.transform.position = bow_mid.transform.position;               //将箭的位置设置为弓中间的位置arrow.transform.rotation = arrowCtrl.bow.transform.rotation;    // 将箭的旋转角度设置为弓的旋转角度arrow.transform.parent = arrowCtrl.bow.transform;        //箭随弓的位置变化arrow.gameObject.SetActive(false);return arrow;}public void RecycleArrow(GameObject arrow)        // 回收箭{arrow.SetActive(false);DestroyImmediate(arrow);}
}

(5)TargetController 靶子控制器

        该脚本作为组件挂载在每一个靶子对象上。该类具有变量RingScore用来表示这个靶子对象的分值(对于那个巨型靶子而言,就是每一个环的分值)。

        该脚本具有碰撞检测函数。当发生碰撞时,先获取碰撞的对象,检测其是否为空以及是否为箭矢对象。当碰撞对象是箭矢时,为了能够将箭矢保存在靶子上,这里设置箭矢的速度为0,然后关闭物理引擎的控制,将旋转角度恢复,并将箭矢作为靶子的子对象,将箭矢的tag标记为“OnTarget”。最后使用计分器记录下该靶子对应的分值。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 靶子的控制器
public class TargetController : MonoBehaviour        // 用于处理靶子的碰撞
{public int RingScore = 0;    //当前靶子或环的分值public ScoreRecorder sc_recorder;void Start(){sc_recorder = Singleton<ScoreRecorder>.Instance;}void Update(){      }void OnCollisionEnter(Collision collision)    // 检测碰撞{Transform arrow = collision.gameObject.transform;        // 得到箭身if(arrow == null) return;if(arrow.tag == "arrow"){//将箭的速度设为0arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);//使用运动学运动控制arrow.GetComponent<Rigidbody>().isKinematic = true;arrow.transform.rotation = Quaternion.Euler(0, 0, 0);    // 使箭的旋转角度为0arrow.transform.parent = this.transform;                // 将箭和靶子绑定sc_recorder.RecordScore(RingScore);         //计分arrow.tag = "onTarget";            //标记箭为中靶}}
}

(6)BowController 弓弩控制器

        该脚本挂载在弓弩对象上,用于控制弩的移动、视角的旋转、以及将玩家传送到指定的射击位置。

        Move()函数读取方向键的输入并以此移动游戏对象。由于弓弩是游戏玩家(这里用方块表示)的一个子对象,因此位置移动时根据方块进行的,需要调用transform.parent。

        SetCursorToCentre()函数用来将鼠标移动到屏幕中心并隐藏。

        View()函数用于控制视野。其中分为水平转动视角和垂直转动视角。水平方向的旋转是针对弓弩的父对象(玩家)进行的,这样弓弩会随着玩家一起水平旋转;垂直方向的旋转是针对弓弩进行的,这样当弓弩进行俯仰操作的时候玩家不会受影响。

        transport()函数用于将玩家传送到指定的射击位置。当玩家按下按键“1、2、3”时,会被传送到不同的射箭区域,同时会改变当前的天空盒;同时由于canshoot变量的存在,只有在这些射箭区域才能射箭。按下按键“B”时会回到原来的位置(出生点)。

        FixedUpdate()函数会不断执行上述的几个函数。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 挂载在弩上,控制弩的移动和视角的旋转、以及传送到指定的射击位置
public class BowController : MonoBehaviour     
{public float moveSpeed = 6;    //速度:每秒移动6个单位长度public float angularSpeed = 90;    //角速度:每秒旋转90度public float jumpForce = 200f;    //跳跃参数public float horizontalRotateSensitivity = 5;    //水平视角灵敏度public float verticalRotateSensitivity = 5;    //垂直视角灵敏度private float xRotation = 0f;         // x旋转角度public bool canshoot = false;        // 是否可以射箭void Start(){}void FixedUpdate()        {Move();View();transport();}void Move()      // 移动{float v = Input.GetAxis("Vertical");float h = Input.GetAxis("Horizontal");transform.parent.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed);transform.parent.Translate(Vector3.right * h * Time.deltaTime * moveSpeed);}void SetCursorToCentre()   // 锁定鼠标到屏幕中心{//锁定鼠标后再解锁,鼠标将自动回到屏幕中心Cursor.lockState = CursorLockMode.Locked;Cursor.lockState = CursorLockMode.None;//隐藏鼠标Cursor.visible = false;}void View()    // 控制视角{SetCursorToCentre();        //锁定鼠标到屏幕中心float mouseX = Input.GetAxis("Mouse X") * Time.deltaTime * angularSpeed* horizontalRotateSensitivity;transform.parent.Rotate(Vector3.up * mouseX);     // "人"水平旋转float mouseY = Input.GetAxis("Mouse Y") * Time.deltaTime * angularSpeed * verticalRotateSensitivity;xRotation -= mouseY;xRotation = Mathf.Clamp(xRotation, -45f, 45f);          // 限制上下视角transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);     // "人"不动,弓上下移动}void transport()     // 传送到指定的射击位置{if (Input.GetKeyDown(KeyCode.Alpha1))            // 静态靶子区域{canshoot = true;transform.parent.position = new Vector3(120, 1, 70);          RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial1");}if (Input.GetKeyDown(KeyCode.Alpha2))            // 动态靶子区域{canshoot = true;transform.parent.position = new Vector3(-120, 1, -70);       RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial2");}if (Input.GetKeyDown(KeyCode.Alpha3))            // 静态大靶子区域{canshoot = true;transform.parent.position = new Vector3(-120, 1, 70);       RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial3");}if (Input.GetKeyDown(KeyCode.Alpha4))           // 未完待续、、、{canshoot = true;transform.parent.position = new Vector3(120, 1, -70);       RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial2");}if (Input.GetKeyDown(KeyCode.B))                // 回到原始位置{canshoot = false;transform.parent.position = new Vector3(0, 1, -8);}}}

(7)ISceneController 场景控制器接口

        包含了场景控制相关的函数。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 场景控制器接口,与场景控制有关的函数都在这里
public interface ISceneController
{void LoadResources();    // 加载资源
}

(8)ArrowController 主场景控制器

        该脚本挂载在弓弩对象上,是整个游戏的主场景控制器,用于将动作、场景、用户交互界面关联起来。

        LoadResources()用于加载各种资源。首先从弓弩的子对象处获得主相机。然后加载靶子的预制。靶子一共有5种:对于两种静态靶子,每种静态靶子加载3个对象,这些对象的位置都不一样,分数也随着距离的增加而增加,且小靶子的得分高于大靶子;对于两种动态靶子,由于动画效果有固定的位置,因此不需要设定位置信息,并且每种动态靶子只能加载一个对象;对于巨型靶子,由于其每一个环都有不同的分值,因此在加载完巨型靶子后,还需要给它的每一个环都挂载靶子控制器的组件并为它们赋值不同的分值。

        LoadTarget()函数。由于上述几种靶子的加载都是同样的逻辑,因此可以抽象出一个通用的加载靶子的函数。该函数以预制的名字、分数和位置作为参数,根据这些参数从prefabs目录中获取预制并给靶子对象添加位置信息、得分信息并加载控制器组件。最后返回该靶子对象即可。

        Start()函数进行各种初始化工作。如给弓弩对象添加各种的组件等,详情见代码。

        Shoot()函数用于触发射击的动作。首先需要确保箭矢队列里面还有箭,否则提示玩家按“R”键装载弓箭。在有箭的基础上,如果当前处在不可射击的区域时,当玩家点击鼠标时提示玩家到特定的区域射击。当箭矢队列中有箭并且玩家处在可射击区域时,触发射击的动作并通过射击动作控制器来执行动作。

        aniShoot()函数根据玩家不同的鼠标点击动作,执行蓄力、等待、射击的动画效果。为了确保动作是按照蓄力、等待、射击的顺序来完成的,这里引入了一个变量state用来表示当前的状态,只有在正确状态下按下鼠标,才会执行相应的动画效果。对于“蓄力”操作,根据玩家按下鼠标左键的时间长短,弓箭的力也会改变,直到蓄力到达2秒时到达最大的力;不同大小的力量,对应的动画的效果和射箭的力度也会不同。当处于等待状态并按下鼠标右键时,执行“射击”状态,在播放动画的同时,从箭矢队列中取出一支箭,将其加入已发射flyed队列中,并通过射击动作管理器为这支箭执行射击动作。至此就完成了箭矢的射击动作和动画效果。

        LoadArrow()函数用于获得箭矢(相当于fps游戏中的换弹)。当调用该函数后,首先清空原来的箭矢队列,然后通过箭矢工厂获得10支箭并添加到箭矢队列中。等到需要使用的时候,就从队列中取出箭矢即可。

        HitGround()函数通过射线检测的方法检测箭矢是否射中地面。如果箭矢和地面的距离小于设定的阈值(如代码中的2f)时,则认为箭矢碰撞到了地面,将箭矢的tag标记为ground,速度设置为0并取消物理引擎的控制。

        GetScore()函数用来获取计分器的得分;GetArrowNum()函数用来获取剩余的箭矢的数量;GetMessage()函数用来获取需要显示在屏幕上的信息。

        Update()函数用于不断调用Shoot()函数来执行射击、调用HitGround()函数来检测当前飞出去的箭是否碰撞到地面。同时不断检测已经射出去的箭的队列flyed,当该队列的数量大于10时、或者当前遍历到的箭矢的y轴值小于-10(可以认为已经飞出了地图外,因为地图的高度y是0)时,删除遍历到的箭以释放空间。(也就是说,可以同时存在场上的、射出去的箭矢的数量最多为10支,这样设计可以释放空间、减少内存压力)。同时,当玩家按下“R”键换弹时,调用LoadArrow()函数获得箭矢,更新提示信息。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ArrowController : MonoBehaviour, ISceneController, IUserAction
{public CCShootManager arrow_manager;    //箭的动作管理者public ArrowFactory factory;    //箭的工厂public GameObject main_camera;       // 主相机public ScoreRecorder recorder;        // 计分对象public GameObject bow;     // 弓弩public GameObject target1, target2, target3, target4, large_target; // 四种不同的靶子和一个巨型靶子public GameObject arrow;       // 当前射出的箭public string message = "";   // 用于显示的信息private int arrow_num = 0;    // 装载的箭的数量public Animator ani;          // 动画控制器private List<GameObject> arrows = new List<GameObject>();    // 箭的队列private List<GameObject> flyed = new List<GameObject>();    // 飞出去的箭的队列public float longPressDuration = 2.0f; // 设置长按持续时间private bool isLongPressing = false;       // 是否正在长按private float pressTime = 0f;         // 长按的时间public int state = 0;  // 0-普通状态,1-拉弓状态,2-蓄力状态public float power = 0f;  // 拉弓的力度//加载箭和靶子public void LoadResources(){main_camera = transform.GetChild(5).gameObject;   // 获取摄像机bow = this.gameObject;// 加载靶子for(int i = 0; i < 3; ++i)    // 3 * 2 = 6个靶子{target1 = LoadTarget("target", 10 + i, new Vector3(100+20*i, 20, 140+5*i)); // 静态小靶子target2 = LoadTarget("big_target", 5-i, new Vector3(100+20*i, 10, 140-5*i)); // 静态大靶子}target3 = LoadTarget("target_move", 15, new Vector3(0,0,0)); // 慢速移动靶子, 使用动画,自带位置target4 = LoadTarget("target_quick", 20, new Vector3(0,0,0)); // 快速移动靶子,使用动画,自带位置large_target = LoadTarget("large_target", 6, new Vector3(-100, 25, 180)); // 静态大靶子for(int i = 0; i < 4; ++i)        // 给子对象添加TargetController组件{Transform child = large_target.transform.GetChild(i);child.gameObject.AddComponent<TargetController>();child.gameObject.GetComponent<TargetController>().RingScore = 7 + i;       // 不同的环数分数不同}}GameObject LoadTarget(string name, int score, Vector3 pos)    // 加载靶子{GameObject target = GameObject.Instantiate(Resources.Load("Prefabs/"+name, typeof(GameObject))) as GameObject;    // 从预制中获得靶子target.transform.position = pos;target.AddComponent<TargetController>();                 // 给靶子添加TargetController组件target.GetComponent<TargetController>().RingScore = score;return target;}//进行资源加载void Start(){arrow = null;   SSDirector director = SSDirector.getInstance();director.currentSceneController = this;director.currentSceneController.LoadResources();gameObject.AddComponent<ArrowFactory>();gameObject.AddComponent<ScoreRecorder>();gameObject.AddComponent<UserGUI>(); gameObject.AddComponent<BowController>();factory = Singleton<ArrowFactory>.Instance;recorder = Singleton<ScoreRecorder>.Instance;arrow_manager = this.gameObject.AddComponent<CCShootManager>() as CCShootManager; ani = GetComponent<Animator>();}void Update(){Shoot();              // 射击HitGround();           // 检测箭是否射中地面for(int i = 0; i < flyed.Count; ++i)         // 用于维护已经射出去的箭的队列{   GameObject t_arrow = flyed[i];if(flyed.Count > 10 || t_arrow.transform.position.y < -10)   // 箭的数量大于10或者箭的位置低于-10{   // 删除箭flyed.RemoveAt(i);factory.RecycleArrow(t_arrow);}}if(Input.GetKeyDown(KeyCode.R))         // 按R换弹{   //从工厂得到箭LoadArrow();message = "弩箭已填充完毕";}}public void Shoot()   // 射击{if(arrows.Count > 0){    // 确保有箭if(!gameObject.GetComponent<BowController>().canshoot && (Input.GetMouseButtonDown(0) || Input.GetButtonDown("Fire2"))){message = "请按1、2、3到指定地点射击";}else{aniShoot();} }else{message = "请按R键以装载弓箭";}}public void aniShoot(){    // 射击的动画if (Input.GetMouseButtonDown(0) && state==0) // 监测鼠标左键按下{message = "";transform.GetChild(4).gameObject.SetActive(true);           // 设置动作中的箭可见isLongPressing = true;ani.SetTrigger("pull");pressTime = Time.time;state = 1;}if (Input.GetMouseButtonUp(0) && state==1) // 监测鼠标左键抬起{isLongPressing = false;float duration = Time.time - pressTime;if (duration < longPressDuration){                  // 执行普通点击操作power = duration/2;}else{                    // 拉满了power = 1.0f;}ani.SetFloat("power", power);ani.SetTrigger("hold");state = 2;}if (isLongPressing && Time.time - pressTime > longPressDuration)   // 长按但是未抬起,且持续时间超过设定值{// 长按操作isLongPressing = false;power = 1.0f;ani.SetFloat("power", power);ani.SetTrigger("hold");}if (Input.GetButtonDown("Fire2") && state==2){       // 鼠标右键,攻击2transform.GetChild(4).gameObject.SetActive(false);          // 设置动作中的箭不可见ani.SetTrigger("shoot");arrow = arrows[0];arrow.SetActive(true);flyed.Add(arrow);arrows.RemoveAt(0);arrow_manager.ArrowShoot(arrow, main_camera.transform.forward,power);ani.SetFloat("power", 1.0f);        // 恢复力度arrow_num -= 1;state = 0;}}public void LoadArrow(){          // 获得10支箭arrow_num = 10;while(arrows.Count!=0){     // 清空队列   factory.RecycleArrow(arrows[0]);arrows.RemoveAt(0);}for(int i=0;i<10;i++){GameObject arrow = factory.GetArrow();arrows.Add(arrow);}}public void HitGround(){               // 检测箭是否射中地面RaycastHit hit;if (arrow!=null && Physics.Raycast(arrow.transform.position, Vector3.down, out hit, 2f)){// 如果射线与地面相交if (hit.collider.gameObject.name == "Terrain"){arrow.tag = "ground";//将箭的速度设为0arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);//使用运动学运动控制arrow.GetComponent<Rigidbody>().isKinematic = true;}}}//返回当前分数public int GetScore(){return recorder.score;}//得到剩余的箭的数量public int GetArrowNum(){return arrow_num;}// 显示的信息public string GetMessage(){return message;}
}

3. Views

(1)IUserAction 用户交互的接口

        包含了与用户交互界面相关的函数。这些函数在ArrowController中实现了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;// 与用户交互的接口
public interface IUserAction
{int GetScore();int GetArrowNum();string GetMessage();
}

(2)UserGUI 用户交互界面

        用于显示与用户交互相关的信息。

        Start()函数用于初始化字体信息,并获得用户交互接口的实例。

        Update()函数用于实时获取屏幕的宽度和高度。

        OnGUI()用于显示玩家的得分信息、剩余箭矢数、以及用于提示玩家的信息。同时这里还在屏幕中央加了一个圆圈,用来作为射击的准星,提高玩家游戏体验。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class UserGUI : MonoBehaviour
{private IUserAction action;GUIStyle style1 = new GUIStyle();GUIStyle style2 = new GUIStyle();public string message = "";        // 显示的信息int screenWidth, screenHeight;void Start(){action = this.transform.gameObject.GetComponent<IUserAction>();style1.normal.textColor = Color.red;style1.fontSize = 30;style2.normal.textColor = Color.yellow;style2.fontSize = 40;}void Update(){screenWidth = Screen.width;screenHeight = Screen.height;}private void OnGUI(){//显示各种信息GUI.Label(new Rect(20,10,150,50),"得分: ",style1);GUI.Label(new Rect(100,10,150,50),action.GetScore().ToString(),style2);GUI.Label(new Rect(20,50,150,50),"剩余箭矢数: ",style1);GUI.Label(new Rect(200,50,150,50),action.GetArrowNum().ToString(),style2);GUI.Label(new Rect(150,100,150,50),action.GetMessage(),style1);GUI.Label(new Rect(screenWidth/2+10,screenHeight/2,150,50),"o",style2);}
}

四、代码链接

Unity-FPS: 这是一个第一人称射击游戏。你将以第一人称视角操控弓弩,移动到指定的射击位,使用弩箭射击标靶从而得到相应的分数。

这篇关于Unity--第一人称射箭游戏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/834156

相关文章

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载

剑指offer(C++)--孩子们的游戏(圆圈中最后剩下的数)

题目 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去

【Unity Shader】片段着色器(Fragment Shader)的概念及其使用方法

在Unity和图形编程中,片段着色器(Fragment Shader)是渲染管线中的一个阶段,负责计算屏幕上每个像素(片段)的颜色和特性。片段着色器通常在顶点着色器和任何几何处理之后运行,是决定最终像素颜色的关键步骤。 Fragment Shader的概念: 像素处理:片段着色器处理经过顶点着色器和几何着色器处理后,映射到屏幕空间的像素。颜色计算:它计算每个像素的颜色值,这可能包括纹理采样、光

【Unity Shader】Alpha Blend(Alpha混合)的概念及其使用示例

在Unity和图形编程中,Alpha Blend(也称为Alpha混合)是一种用于处理像素透明度的技术。它允许像素与背景像素融合,从而实现透明或半透明的效果。Alpha Blend在渲染具有透明度的物体(如窗户、玻璃、水、雾等)时非常重要。 Alpha Blend的概念: Alpha值:Alpha值是一个介于0(完全透明)和1(完全不透明)的数值,用于表示像素的透明度。混合模式:Alpha B

【服务器08】之【游戏框架】之【加载主角】

首先简单了解一下帧率 FixedUpdate( )   >   Update( )   >   LateUpdate( ) 首先FixedUpdate的设置值 默认一秒运行50次 虽然默认是0.02秒,但FiexedUpdate并不是真的0.02秒调用一次,因为在脚本的生命周期内,FixedUpdate有一个小循环,这个循环也是通过物理时间累计看是不是大于0.02了,然后调用一次。有

2024年6月24日-6月30日(ue独立游戏为核心)

试过重点放在独立游戏上,有个indienova独立游戏团队是全职的,由于他们干了几个月,节奏暂时跟不上,紧张焦虑了。五一时也有点自暴自弃了,实在没必要,按照自己的节奏走即可。精力和时间也有限,放在周末进行即可。除非哪天失业了,再也找不到工作了,再把重心放在独立游戏上。 另外,找到一个同样业余的美术,从头做肉鸽游戏,两周一次正式交流即可。节奏一定要放慢,不能影响正常工作生活。如果影响到了,还不如自

植物大战僵尸杂交版2.1版本终于来啦!游戏完全免费

在这个喧嚣的城市里,我找到了一片神奇的绿色世界——植物大战僵尸杂交版。它不仅是一款游戏,更像是一扇打开自然奥秘的窗户,让我重新认识了植物和自然的力量。 植物大战僵尸杂交版最新绿色版下载链接: https://pan.quark.cn/s/d60ed6e4791c 🌱 🔥 激情介绍:不只是游戏,更是生态课 植物大战僵尸杂交版将经典的策略塔防游戏带入了一个全新的维度。这里,每一种植物都拥

游戏高度可配置化(一)通用数据引擎(data-e)及其在模块化游戏开发中的应用构想图解

游戏高度可配置化(一)通用数据引擎(data-e)及其在模块化游戏开发中的应用构想图解 码客 卢益贵 ygluu 关键词:游戏策划 可配置化 模块化配置 数据引擎 条件系统 红点系统 一、前言 在插件式模块化软件开发当中,既要模块高度独立(解耦)又要共享模块数据,最好的方法是有个中间平台(中间件)提供标准的接口来进行数据的交换,这在很多行业软件开发中已经广泛应用。但是,由于中间件的抽象和封

Unity Meta Quest 开发:关闭 MR 应用的安全边界

社区链接: SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子 📕教程说明 这期教程我将介绍如何在应用中关闭 Quest 系统的安全边界。 视频讲解: https://www.bilibili.com/video/BV1Gm42157Zi 在 Unity 中导入 Meta XR SDK,进行环境配置后,打开 Assets > Plugins > An

力扣SQL50 游戏玩法分析 IV 子查询

Problem: 550. 游戏玩法分析 IV 👨‍🏫 参考题解 这个SQL查询的目的是计算每个玩家在登录后的第二天参与活动的比例。查询使用了子查询和左连接来实现这一目的。下面是查询的详细解释,包括每个部分的作用和注释: -- 计算每个玩家登录后第二天参与活动的比例select round(avg(a.event_date is not null), 2) as fractio