【unity demo】使用unity制作射击游戏demo (上) | 您所在的位置:网站首页 › 自制一个小武器 › 【unity demo】使用unity制作射击游戏demo (上) |
1.配置vs code开发环境
主要是安装unity对应的版本,并配置相应的ide,目前我用的是unity 2021.3.21。 通过edit-prefreneces面板,external tools选项中配置ide环境,自动使用vs code来打开工程中的代码文档。 即游戏设计文档(Game Design Document, GDD),我们需要预先对待实现的完整demo进行设计,包括5个部分: 概念 : 一个通过躲避场景中巡逻和警惕的敌人,并能够进行第三人称视角射击的demo。 机制 : 1)敌人会在场景中沿指定路线巡逻,并存在警惕范围,当主角进入到敌人的警惕范围后,敌人会自动改变巡逻路线,向主角移动 2)敌人接触到主角后,会减扣主角的生命值 3)主角能够通过射击抵御接近的敌人,能拾取物品 用户接口 : 1)通过WSAD控制角色移动,鼠标控制摄像机方向,使用左键射击 2)角色通过接触,拾取物体 3)简单HUD(抬头显示,Head Up Display),显示玩家的血量和剩余的子弹数 剧情 : demo暂无剧情 表现风格: 使用unity的默认3d模型进行搭建,不使用自定义shader和贴图作为额外材质 3.搭建关卡 3.1. 创建基本场景我们使用默认的平面和立方体,通过缩放,搭建基本的场地模型。
接下来我们将在场景的四个角,放置四个用阻挡敌人,并提供射击窗口的掩体。 我们同样使用unity的基本模型去搭建这四个掩体,但假定四个掩体是完全相同的结构,我们同样的搭建操作要重复执行四次,实在是有点太折磨了。 这里我们使用到unity的pregab机制,通过空物体创建预制件,并进行保存,这样我们下一次需要搭建同样的掩体时,直接使用保存好的预制件即可。 在上一步中我们加入了胶囊体作为拾取物,现在我们希望这个拾取物能够在场景中动起来。 通过window-animation-animation,打开动画面板,并固定到console面板旁。 创建particle system对象,将其位置调整到与胶囊体一致。 首先我们建立代表玩家控制角色的淡蓝色胶囊体,挂上rigidbody组件,并在rigidbody组件内设置xy轴的旋转约束。 通过rigidbody组件,我们能让挂在其的胶囊体与unity的物理系统进行交互。 首先我们需要获取到玩家的输入,若需要自定控制移动方向的按键,则需要通过input manager面板,完成简单的输入配置。 这里我们就使用fps中绝对经典的wasd移动模型(这里与认知中的有所不同,ws为前后移动,ad为旋转,已在unity中默认配置好),并赋予一个跟随玩家移动的摄像机,通过鼠标的指向来控制跟随相机的朝向。
我们很快发现,在测试中会存在玩家角色很容易倒地开摆的问题。 新建脚本,并挂载给摄像机。 using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraBehavior : MonoBehaviour { //camOffset:相对于玩家角色的位置,摄像机位置的偏移值 public Vector3 camOffset = new Vector3(0f, 1.2f, -2.6f); private Transform target; void Start() { target = GameObject.Find("Player").transform; } // 注意lateupdate也是monoBehavior提供的默认方法,其更新频率与帧率一致,但更新顺序在update之后 // 使用LateUpdate以确保在玩家更新位置后,摄像机能及时跟上 void LateUpdate() { this.transform.position = target.TransformPoint(camOffset); this.transform.LookAt(target); } } 4.3. 使用rigidbody组件实现玩家角色移动在unity的运动类型中,主要包括两种: kinematic运动:即运动学运动,非物理运动。即该类运动不会受到unity物理机制的影响,一般常用于骨骼控制,而非game object在场景中的位置和旋转变化。non-kinematic运动:即物理运动,非运动学运动。该类运动主要通过向挂载了rigidbody的物体施加力来实现,而非修改transform。在4.1中,我们主要实现了一种混合了transform和rigidbody的移动机制,即一种混合了kinematic和non-kinematic的移动机制,unity也不建议我们对这两种运动机制进行混用。 接下来我们使用rigidbody提供的方法,实现玩家角色的移动。 public class PlayerBehavior : MonoBehaviour { public float moveSpeed = 1.0f; public float rotateSpeed = 60f; private float vInput; private float hInput; private Rigidbody _rb; // Start is called before the first frame update void Start() { _rb = GetComponent(); } // Update is called once per frame void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; // this.transform.Translate(Vector3.forward * vInput * Time.deltaTime); // this.transform.Rotate(Vector3.up * hInput * Time.deltaTime); } //面向物理系统专门的update方法FixedUpdate,该方法独立于帧率 void FixedUpdate() { Vector3 rotation = Vector3.up * hInput; //Time.fixedDeltaTime:用于求解两次fixedupdate之间的时间差 Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); } } 4.4. 设置接触拾取在物理系统的运用中,我们发现除了rigidbody组件外,game object往往还包含了一个collider组件。 collider组件规定了物体的碰撞体,物理材质,重心,半径,高度等信息。
现在我们需要在场景中添加第一个敌人,让他充当哨兵的职责。 虽然目前这个敌人尚不能主动移动,但它有一个很重要的职责,发现进入到侦查范围内的玩家角色。 我们刚刚提到collider组件有一个isTrigger属性: 当其没有开启时,collider会执行正常的碰撞检测,碰撞体间想相互碰撞的物体会被弹开(或发生体积间的交互),从而改变物体的运动状态,并调用OnCollisionEnter方法。 当其开启时,该collider则会成为无实体的状态,碰撞体发生碰撞后不会发生体积上的交互,物体能够按照其原有的运动状态进行运动。此外发送的消息也会发生变化,对应物体从进入collider到离开collider的过程,依次触发的消息(方法)为:OnTriggerEnter, OnTriggerStay, OnTriggerExit。
更新玩家控制脚本如下: public class PlayerBehavior : MonoBehaviour { public float moveSpeed = 1.0f; public float rotateSpeed = 60f; //新增跳跃速度public变量 public float jumpVelocity = 5.0f; private float vInput; private float hInput; private Rigidbody _rb; void Start() { _rb = GetComponent(); } void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; } void FixedUpdate() { //跳跃控制逻辑 if(Input.GetKeyDown(KeyCode.Space)) { _rb.AddForce(Vector3.up * jumpVelocity, ForceMode.Impulse); } Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); } }很快我们发现,跳是可以跳的,但是这个跳跃成功的情况非常的偶发,这个小比(可)崽(爱)子怎么完全不听空格的指挥?
显然这是因为FixedUpdate是不像Update那样是按每游戏帧的顺序执行的。所以我们这里要再调整一下逻辑,把监测space键的部分逻辑移动到update里面。 public class PlayerBehavior : MonoBehaviour { public float moveSpeed = 1.0f; public float rotateSpeed = 60f; public float jumpVelocity = 5.0f; private float vInput; private float hInput; private float JInput; private Rigidbody _rb; void Start() { _rb = GetComponent(); } void Update() { vInput = Input.GetAxis("Vertical") * moveSpeed; hInput = Input.GetAxis("Horizontal" ) * rotateSpeed; if (Input.GetKeyDown(KeyCode.Space)) { JInput = jumpVelocity; Debug.Log("jump jump jump!"); } } void FixedUpdate() { _rb.AddForce(Vector3.up * JInput, ForceMode.Impulse); JInput = 0f; Vector3 rotation = Vector3.up * hInput; Quaternion angleRot = Quaternion.Euler(rotation * Time.fixedDeltaTime); _rb.MovePosition(this.transform.position + this.transform.forward * vInput * Time.fixedDeltaTime); _rb.MoveRotation(_rb.rotation * angleRot); } }
接下来我们通过添加一个独立的layer,并将environment指派给该层,我们将把该层作为遮罩层,判断玩家角色是否触地。 首先我们随意点击一个game object实体,添加一个名为Ground的layer。 确保全部子级的所属层都进行了调整。
至此连跳的问题被解决了,我们的角色再也无法奋力空蹬了。 4.7. 实现子弹发射既然是做FPS的demo,那最关键的必然得是S的环节,接下来我们通过实例化的方式实现射击功能。 逻辑上也很简单: 触发射击后,我们会在指定位置实例化子弹的实体,并使之向固定方向移动。 实现上,我们使用Instantiate方法,并给其提供子弹的预制件对象,生成位置和旋转。 首先我们创建子弹实体的预制件,记得要添加rigidbody组件。
我们可以明显的看到,玩家角色自己的存在反而挡住了子弹。所以我们需要对摄像机跟随的脚本做一些调整: 主要的改动就是,摄像机的偏移和观察方向,以及子弹的生成位置。 相机的脚本更新如下: public class CameraBehavior : MonoBehaviour { //在inspector面板中配置偏移量,使摄像机一直在玩家角色前方 public Vector3 camOffset = new Vector3(0f, 1.0f, 1.0f); private Transform target; void Start() { target = GameObject.Find("Player").transform; } // 注意lateupdate也是monoBehavior提供的默认方法,其更新频率与帧率一致,但更新顺序在update之后 void LateUpdate() { this.transform.position = target.TransformPoint(camOffset); // 这里更新为,观察当前朝向远处的一个位置 this.transform.LookAt(target.position + target.transform.forward * 10); } }另外,我们还对玩家角色的预制件进行了一些调整,增加了一个胶囊体作为武器的表示。 现在虽然子弹和视角都正确了,但是胡乱射击一通上地上会有很多子弹实体在滚来滚去,一方面很容易误触发碰撞事件,且大量子弹实体也会占用多余的内存和物理计算消耗。 通过设置定时消失,我们让系统定时去自动销毁场景中已经生成的子弹实体。 给子弹预制件挂载一个脚本:根据延迟自动摧毁生成的实体 public class BulletBehavior : MonoBehaviour { public float onscreenDelay = 3f; void Start() { Destroy(this.gameObject, onscreenDelay); } }
|
CopyRight 2018-2019 实验室设备网 版权所有 |