【Unity3D实验作业】第一人称十字弩射击游戏

游戏截图展示:

第一人称视角设计:

        建立一个空的GameObject,将之命名为Player。再把一个摄像机挂载在Player下面,再把预设好的CrossBow挂载到Camera下。

        在Camera下挂载CameraControl类,在Player下挂载PlayMove类。

代码如下:

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

public class CameraControl : MonoBehaviour
{
    public Transform player;//获取玩家
    private float mouseX, mouseY;//获取鼠标位置
    public float mouseSensitivity;//鼠标灵敏度
    private float xRotation;
    private void Update()
    {
        mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
        mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
        xRotation -= mouseY;//在上下移动视角后,防止Y的值反弹回0(若不加这一条,每次上下移动视角移动一下后,视角都会弹回原位置,不会固定)
        xRotation = Mathf.Clamp(xRotation, -70f, 70f);//摄像机上下视角限制,对于Player(主角)来说,视角上下是有限制的,不然会很诡异
        player.Rotate(Vector3.up * mouseX);//玩家随鼠标旋转
        transform.localRotation = Quaternion.Euler(xRotation, 0, 0);//摄像机旋转
    }
}

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

public class SSDirector : System.Object
{
    static SSDirector _instance;
    public IUserAction CurrentUserController { get; set; }
    public static SSDirector GetInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

public class PlayerMove : MonoBehaviour, IUserAction
{

    // Start is called before the first frame update
    private float walk;
    private float run;
    private float speed;
    private Vector3 dir;
    private CharacterController playerController;
    private ScoreControl playersc;
    public GameObject fp;
    private float power;
    private Animator ani;
    private AnimatorStateInfo aniState;
    public GameObject Cb;

    void Start()
    {
        SSDirector.GetInstance().CurrentUserController = this;
        this.gameObject.AddComponent<GameGUI>();
        walk = 25.0f;
        run = 50.0f;
        playerController = GetComponent<CharacterController>();
        playersc = new ScoreControl();
        power = 0.0f;
        ani = Cb.GetComponent<Animator>();
    }

    void Update()
    {
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        if (Input.GetKey(KeyCode.Space))
            speed = run;
        else
            speed = walk;
        dir = transform.forward * vertical + transform.right * horizontal;
        playerController.Move(dir * speed * Time.deltaTime);

        if (Input.GetMouseButtonDown(0))
        {
            if (playersc.NoP || playersc.HaP)
            {
                if(playersc.Bullet > 0)
                {
                    Shoot();
                    playersc.Bullet--;
                    ani.SetTrigger("shoot");
                    power = 0.0f;
                    ani.SetFloat("hold_power", 0.0f);
                    ani.SetFloat("power", 0.0f);
                }
            }
            
        }

        if (Input.GetMouseButtonDown(2))
        {
            aniState = ani.GetCurrentAnimatorStateInfo(0);
            if (aniState.IsName("hold_n"))
            {
                ani.Play("Empty");
            }
            StartCoroutine("CheckForLongPress");
        }

        if (Input.GetMouseButtonUp(2))
        {
            StopCoroutine("CheckForLongPress");
            ani.SetFloat("hold_power", power);
            ani.SetTrigger("hold");
        }
    }

    IEnumerator CheckForLongPress()
    {
        ani.SetTrigger("start");
        power = 0;
        float longPressDuration = 2.0f;

        float timer = 0.0f;

        while(timer < longPressDuration)
        {
            timer += Time.deltaTime;
            power += 0.01f;
            if(power >= 1)
            {
                power = 1;
            }
            ani.SetFloat("power", power);

            yield return null;
        }
    }

    public void Check()
    {
        float dn = (transform.localPosition.x - 5) * (transform.localPosition.x - 5) + (transform.localPosition.z + 10) * (transform.localPosition.z + 10);
        float dh = (transform.localPosition.x + 10) * (transform.localPosition.x + 10) + (transform.localPosition.z + 10) * (transform.localPosition.z + 10);
        if (dn <= 25)
        {
            if(!playersc.NoP)
            {
                playersc.NoP = true;
                playersc.CBEnter();
            }
        }
        else if (dh <= 25)
        {
            if(!playersc.HaP)
            {
                playersc.HaP = true;
                playersc.CBEnter();
            }
        }
        else
        {
            playersc.CBLeave();
        }
        GetComponent<GameGUI>().GM = playersc.GM;
        //GetComponent<GameGUI>().Score = playersc.Score;
        GetComponent<GameGUI>().NoP = playersc.NoP;
        GetComponent<GameGUI>().HaP = playersc.HaP;
        GetComponent<GameGUI>().Bullet = playersc.Bullet;
    }

    public void Shoot()
    {
        GameObject arrow = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Arrow"));
        arrow.gameObject.tag = "arrow";
        arrow.transform.position = fp.transform.position;
        arrow.transform.rotation = fp.transform.rotation;
        Rigidbody rd = arrow.GetComponent<Rigidbody>();
        if (rd!=null)
        {
            rd.AddForce(fp.transform.forward * 100 * power);
        }
    }
}

这样就实现了第一人称的视角移动,实现了十字弩在地图上的游走。

地形与天空盒设计:

        在Scene中创建Terrain(地形)组件,将预设好的资源(树、草、地皮)布置在地形组件上。

特别地,为了使树与十字弩发生碰撞。需要将预置的树模型加上碰撞器组件。

        在Camera下面创建一个空的Game Object,将之命名为SkyBox,在SkyBox中添加Skybox组件,并挂载ChangeSkyBox类的脚本,实现每10秒种切换天空背景。

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

public class ChangeSkyBox : MonoBehaviour
{
    public Material[] skyboxMaterials;

    void Start()
    {
        StartCoroutine(ChangeSkybox());
    }

    IEnumerator ChangeSkybox()
    {
        while (true)
        {
            // 随机选择一个天空盒子材质
            int index = Random.Range(0, skyboxMaterials.Length);
            RenderSettings.skybox = skyboxMaterials[index];

            // 等待一段时间后再切换天空盒子
            yield return new WaitForSeconds(10);
        }
    }
}

        接着,在Unity界面右下角挂载下载好的天空盒资源。

设计移动靶子动画:

        双击要创建移动动画的游戏对象,在Animation界面开始设计移动动画,首先点击界面左侧,增加游戏对象的position属性,接着点击左上角红点进行录制,以一定时间间隔设置三维坐标位置,形成动画。为保证动画的流畅性,应保持一轮动画开始的三维坐标与一轮动画结束的三维坐标一致。

设计弓弩动画:

        为实现蓄力拉弓的功能,将原先的fill和hold换成混合树。

在滚轮按下时蓄力并开始记时,在滚轮松开时,停止记时,并将hold的power记录下来。

        if (Input.GetMouseButtonDown(2))
        {
            aniState = ani.GetCurrentAnimatorStateInfo(0);
            if (aniState.IsName("hold_n"))
            {
                ani.Play("Empty");
            }
            StartCoroutine("CheckForLongPress");
        }

        if (Input.GetMouseButtonUp(2))
        {
            StopCoroutine("CheckForLongPress");
            ani.SetFloat("hold_power", power);
            ani.SetTrigger("hold");
        }
    IEnumerator CheckForLongPress()
    {
        ani.SetTrigger("start");
        power = 0;
        float longPressDuration = 2.0f;

        float timer = 0.0f;

        while(timer < longPressDuration)
        {
            timer += Time.deltaTime;
            power += 0.01f;
            if(power >= 1)
            {
                power = 1;
            }
            ani.SetFloat("power", power);

            yield return null;
        }
    }

GUI与计分:

        将GameGUI类脚本挂载到相机下面。

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

public class GameGUI : MonoBehaviour
{
    IUserAction userAction;
    public string GM;
    public int Score;
    public bool NoP;
    public bool HaP;
    public int Bullet;

    // Start is called before the first frame update
    void Start()
    {
        userAction = SSDirector.GetInstance().CurrentUserController as IUserAction;
    }

    // Update is called once per frame
    void OnGUI()
    {
        userAction.Check();
        GUI.Box(new Rect(0, 0, 150, 150), "");
        GUI.Label(new Rect(10, 0, 120, 30), GM);
        GUI.Label(new Rect(10, 50, 120, 30), "Score:" + Score);
        GUI.Label(new Rect(10, 90, 120, 30), "子弹数量:" + Bullet);
    }
}

        最后通过左上角Box提示游戏信息、玩家所得分数,剩余子弹数量。

        将Freeze类挂载到固定靶和移动靶上,其中移动靶被击中后会停止移动。

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

public class Freeze : MonoBehaviour
{
    public GameObject Player;
    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("arrow"))
        {
            gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
            if (gameObject.GetComponent<Animator>() != null)
                gameObject.GetComponent<Animator>().enabled = false;
            collision.gameObject.GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeAll;
            Player.GetComponent<GameGUI>().Score += 10;
        }
    }
}

计分类与接口等脚本无需挂载,将他们放到同一个脚本中即可。

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

public interface IUserAction
{
    void Check();
}

public class ScoreControl
{
    public string GM;
    public int Score;
    public bool NoP;
    public bool HaP;
    public int Bullet;

    public ScoreControl()
    {
        GM = "未在可射击区域内";
        Score = 0;
        NoP = false;
        HaP = false;
        Bullet = 0;
    }

    public void CBEnter()
    {

        if (NoP)
        {
            Bullet = 10;
            GM = "你已处于固定靶场";
        }
        else
        {
            Bullet = 20;
            GM = "你已处于移动靶场";
        }
    }

    public void CBLeave()
    {
        GM = "未在可射击区域内";
        NoP = false;
        HaP = false;
        Bullet = 0;
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值