Unity3D设计实现的第一人称射击打靶游戏

一.项目目标

1. 游戏场景

  • 地形(2分):使用地形组件,上面有山、路、草、树;(可使用第三方资源改造)
    •  声音(2分):使用声音组件,播放背景音 与 箭射出的声效;
    •  摄像机(2分):使用多摄像机,制作 鸟瞰图 或 瞄准镜图 使得游戏更加易于操控;
    •  射击位(2分):地图上应标记若干射击位,仅在射击位附近或区域可以拉弓射击,每个位置有 n 次机会;
    •  运动靶(2分):使用动画运动,有一个以上运动靶标,运动轨迹,速度使用动画控制;(注:射中后需要有效果或自然落下)
    •  固定靶(2分):使用静态物体,有一个以上固定的靶标;(注:射中后状态不会变化)
    •  天空盒(2分):使用天空盒,天空可随 玩家位置 或 时间变化 或 按特定按键切换天空盒

2.运动与物理与动画

  •  驽弓动画(2分):使用 动画机 与 动画融合, 实现十字驽蓄力半拉弓,然后 hold,择机 shoot。
  • 碰撞与计分(2分):使用 计分类 管理规则,在射击位射中靶标得相应分数,规则自定;(注:应具有现场修改游戏规则能力)
  •  射击效果(2分):使用 物理引擎 或 动画 或 粒子,运动靶被射中后产生适当效果。
  •  游走(2分):使用第一人称组件,玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;(注:建议使用 unity 官方案例

项目演示视频

第一人称射击打靶游戏演示视频


二.项目设计

1. 角色设计

        从表面上看,游戏的主角是一只十字弩;但从本质上说,游戏的主角是一个白色方块。十字弩是白色方块的子对象,而主摄像机又是十字弩的子对象,两两锚定一个固定的位置偏移量。玩家通过控制白色方块的移动来控制十字弩和主摄像机的移动,从而实现“第一人称”的游戏效果。

2. 标靶设计

(1)固定靶:分大小两种,集中在射击区域1,击中加分,可多次击中。

(2)移动靶:分为快速移动靶和慢速移动靶,由动画控制器控制移动,集中在射击区域2。快速移动靶的移动轨迹是不规则的曲线,慢速移动靶的移动轨迹是折线。移动靶被击中之后会加大量分数,同时爆炸消失。

(3)可爱的史莱姆:仅生成在射击区域3,击中会有特殊音效,同时减分。可多次击中。 

(4)飞龙(在代码状被称为“Bird”):生成在中心区域和射击区域4。飞龙们会随机在场上飞行(但不会飞到场外)。击中飞龙会加分,同时引发爆炸。

3. 场景设计

        自己手搓的游戏场景,整体类似于一个盆地,群山环绕(四周都是山体墙),有路有草有树。内部又被山体分为了四个部分,对应四个射击场地。地形资源,包括树、草和纹理贴图都是从Unity资源商城免费获取的。

        有天空盒,可以通过数字键1~5切换。

4. 动作设计

(1)角色动作设计:角色可以WASD移动,空格跳跃,R键装填弓箭,Q键放大视角瞄准,F键突进(跳跃加突进效果更好),R键释放技能(只有粒子效果和音效无实际作用)。移动和释放技能时会有音效。

(2)十字弩射击设计:十字弩可以左键蓄力,射击的力度由蓄力时间决定。蓄力没有达到极限时,放开左键即可射出弩箭;蓄力达到极限时,弩箭会进入挂起状态,按下右键即可射出弩箭。射击时会有音效。

(3)传送设计:按下数字键1~4可以传送到对应的射击区域。

5. 效果设计

(1)粒子效果

(2)准星效果

(3)音效设计


三.项目实现

 1. 文件组织形式

       套用经典的动作分离模式,代码部分分为三部分——动作部分(主要是蓄力射箭)Actions,控制器部分Controllers,视图部分Views。

2. 重点代码介绍

 (1)BirdMovementControl:控制飞龙(鸟)在场景内的随机移动,在移动参数上进行加权,从而尽可能避免产生可能出界的动作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BirdMovementController : MonoBehaviour
{
    public float moveSpeed = 6f; // 移动速度  
    public float moveRange = 20f; // 水平移动范围  
    public float moveRange_2 = 5f; // 垂直移动范围
    public Vector3 targetPosition ; // 目标位置  
    // public Vector3 offset = new Vector3(40f, 0f, 40f);

    void Start()
    {
        // 初始目标位置  
        SetNewTargetPosition();
    }

    void Update()
    {
        // 移动到目标位置  
        MoveTowardsTarget();

        // 如果接近目标位置,则设置新的目标位置  
        if (Vector3.Distance(transform.position, targetPosition) < 0.1f)
        {
            SetNewTargetPosition();
        }
    }

    void SetNewTargetPosition()
    {
        // 在指定范围内随机生成新的目标位置  
        float x = Random.Range(-moveRange, moveRange - 15);
        float z = Random.Range(-moveRange + 15, moveRange);
        float y = Random.Range( 0 , moveRange_2);
        targetPosition = new Vector3(x, y, z) + transform.position;
    }

    void MoveTowardsTarget()
    {
        // 朝向目标
        transform.LookAt(targetPosition);
        // 平滑移动  
        transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
    }
}

(2)BirdControl:飞龙的控制器,用于控制弩箭与飞龙发生碰撞之后的操作,包括更改弩箭速度、状态和标签,最后销毁弩箭和飞龙对象并在原地生成爆炸粒子特效。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BirdController : MonoBehaviour
{
    public int RingScore = 10; // 鸟的分值
    public ScoreRecorder sc_recorder;
    private bool isHit = false; // 标记鸟是否被射中
    public GameObject explosion;

    void Start()
    {
        explosion = GameObject.Instantiate(Resources.Load("Prefabs/explosionPrefab", typeof(GameObject))) as GameObject;
        sc_recorder = Singleton<ScoreRecorder>.Instance;
    }

    void Update()
    {
    }

    public void Hit()
    {
        if (isHit) return; // 如果已经被射中,返回  
        isHit = true; // 设置为被射中状态  

        // 在原地生成爆炸特效
        Instantiate(explosion, transform.position, Quaternion.identity);
        // 销毁鸟  
        Destroy(gameObject);
    }

    void OnCollisionEnter(Collision collision)    // 检测碰撞
    {
        Transform arrow = collision.gameObject.transform;        // 得到箭身
        if (arrow == null) return;
        if (arrow.tag == "arrow")
        {
            //将箭的速度设为0
            arrow.GetComponent<Rigidbody>().velocity = new Vector3(0, 0, 0);
            //使用运动学运动控制
            arrow.GetComponent<Rigidbody>().isKinematic = true;
            arrow.transform.rotation = Quaternion.Euler(0, 0, 0);    // 使箭的旋转角度为0
            arrow.transform.parent = this.transform;                // 将箭和靶子绑定
            sc_recorder.RecordScore(RingScore);         //计分
            arrow.tag = "onTarget";            //标记箭为中靶

            Hit();      // 调用Hit()函数
        }
    }
}

(3)CrosshairController:准星控制器。将准星所在的UI对象与主摄像机绑定,从而实现正确的准星效果。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CrosshairController : MonoBehaviour
{
    public Camera mainCamera;       // 要跟随的摄像机  
    public Vector3 offset;      // UI 相对于摄像机的位置偏移  

    void Start()
    {
        if (mainCamera == null)
        {
            mainCamera = Camera.main;       // 如果没有指定摄像机,则使用主摄像机  
        }

        if (offset == Vector3.zero)
        {
            offset = new Vector3(10, -10, 10); // 调整这个偏移量来控制 UI 与摄像机的位置关系  
        }
    }

    void Update()
    {
        // 设置 Canvas 的位置为摄像机的位置加上偏移  
        transform.position = mainCamera.transform.position + offset;

        // 使 Canvas 始终面朝摄像机  
        transform.LookAt(mainCamera.transform.position);
        transform.forward = -transform.forward; // 使 UI 面向摄像机  
    }
}

(4)BowController:最重要的代码文件之一,用于控制十字弩的运动,包括移动、跳跃、转动、传送、冲刺突进 等。其实,按照规范的动作分离模型,动作类应该另起一文件写在Actions文件夹内,但出于控制文件总数和方便后期修改的考虑,我并没有这么做(全部分离的话光脚本文件总数就会达到几十个,对于一个轻量级的小项目来说不方便修改)。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 挂载在弩上,控制弩的移动和视角的旋转、以及传送到指定的射击位置
public class BowController : MonoBehaviour     
{
    public float moveSpeed = 10;    //速度:每秒移动10个单位长度
    public float angularSpeed = 30;    //角速度
    public float jumpForce = 200f;    //跳跃冲量
    public float dashForce = 200f;    //冲刺冲量
    public float horizontalRotateSensitivity = 3;    //水平视角灵敏度
    public float verticalRotateSensitivity = 3;    //垂直视角灵敏度
    private float xRotation = 0f;         // x旋转角度
    public bool canshoot = false;        // 是否可以射箭
    public bool isAiming = false;
    private Rigidbody rb;       // 创建刚体组件
    public Vector3 offset = new Vector3(0f, 8f, 0f);      // 子对象偏移量
    public Vector3 offset_Camera = new Vector3(0.5f, -0.5f, 0f);      // 瞄准视角偏移量


    void Start()    
    {
        rb = transform.parent.GetComponent<Rigidbody>();    // 获取实例
    }

    void FixedUpdate()        
    {
        transform.position = transform.parent.position + offset;      // 绑定父子对象位置
        Move();
        View();
        transport();
        aimFocus();
    }
    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);

        if (Input.GetKeyDown(KeyCode.Space))    // 跳跃
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
        }

        if (Input.GetKeyDown(KeyCode.F))    // 冲刺
        {
            // 根据当前朝向施加冲量  
            Vector3 force = transform.forward * dashForce; // 计算冲量方向  
            rb.AddForce(force, ForceMode.Impulse);
        }
    }

    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 aimFocus() 
    {
        if (Input.GetKey(KeyCode.Q))
        {
            if (!isAiming)
            {
                isAiming = true;    // 设置瞄准状态
                Camera.main.fieldOfView = 20;     // 设置瞄准视野
            }
        }
        else
        {
            if (isAiming)
            {
                isAiming = false;
                Camera.main.fieldOfView = 40;
            }
        }
    }

    void transport()     // 传送到指定的射击位置
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))            // 静态靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 0, 0);
            transform.parent.position = new Vector3(120, 1, 70);          
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial1");
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))            // 动态靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 180, 0);
            transform.parent.position = new Vector3(-120, 1, -70);       
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial2");
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))            // 静态大靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 0, 0);
            transform.parent.position = new Vector3(-120, 1, 70);       
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial3");
        }
        if (Input.GetKeyDown(KeyCode.Alpha4))           // 打鸟区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 180, 0);
            transform.parent.position = new Vector3(120, 1, -70);       
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial4");
        }
        if (Input.GetKeyDown(KeyCode.Alpha5))           // 切换天空盒和解除射击锁定状态
        {
            canshoot = true;
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial5");
        }
        if (Input.GetKeyDown(KeyCode.B))                // 回到原始位置
        {
            canshoot = false;
            transform.parent.position = new Vector3(0, 1, -8);
        }
    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值