3D游戏编程 作业五 枪打恶鬼(打飞碟)

前言

	这次的作业个人感觉挺好玩的,我给做成了射击游戏。同时我也在其中学到了unity的一些有趣应用,
下面先把大的框架给讲一下,然后再着重讲一下我个人的收获吧。

Asset文件:github
参考博客:学长博客

一、任务要求

在这里插入图片描述

游戏规则(自定义)

控制你的超进化版鼠标枪射击飞行玩偶(飞碟),击中玩偶或者放任玩偶下落都会有相应的得分,得分如表。游戏共十回合,随回合数增加,玩偶数跟飞行速度增加。若得分为负,游戏失败;若回合进行完毕,游戏胜利。

飞碟击中掉落
蝙蝠5-2
恶鬼10-2
软泥怪20-2
兔子-105

二、 项目展示

这里不得不提一下,我特别喜欢作业三的mvc框架,只需三个文件(Model、Controllor、UserGUI)就可以将结构讲明白。所以我这次也是在那个框架的基础上进行修改的。

1.mvc架构

Model 模型(其实把大部分类都给汇总了)

这里的导演类等都是以前作业内容就不再赘述

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

namespace mygame
{

    public interface ISceneController                      //加载场景
    public interface IUserAction                          //用户互动会发生的事件
    public class SSDirector : System.Object//导演类
    public class ScoreController : System.Object{//分数控制器
    public class DiskFactory : System.Object {//飞碟工厂
    public class DiskModel//飞碟模型
    public class Diskbehaviour : MonoBehaviour//飞碟行为
    public class GunModel//枪模型
    public class Gunbehaviour: MonoBehaviour//枪行为
 }

View 交互界面

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using mygame;
public class UserGUI : MonoBehaviour {

    private IUserAction action;
    public int sign = 0;

    bool isShow = false;
    void Start()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
    }
    void OnGUI()
    {
        //规则展示
        if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", new GUIStyle("button")))
        {
            if (isShow)
                isShow = false;
            else
                isShow = true;
        }
        if(isShow)
        {
            GUI.Label(new Rect(Screen.width / 2 - 85, 10, 200, 50), "击败恶魔,保护小兔子");
            GUI.Label(new Rect(Screen.width / 2 - 150, 30, 350, 50), "子弹打中恶魔得分,让恶魔逃跑丢分,打中小兔子丢分");
            GUI.Label(new Rect(Screen.width / 2 - 85, 50, 250, 50), "点击屏幕进行射击");
        }
        //游戏准备
        if (sign == 0)
        {
            if (GUI.Button (new Rect (Screen.width / 2 - 80, Screen.height / 2, 160, 20), "开始游戏")){
                action.gamestart();
            }
        }
        //游戏中
        else if(sign == 1){
            string say = "当前回合回合数为" + action.getround().ToString() + 
            ",分数为" + ScoreController.GetScoreController().getscore().ToString() +"分";
            GUI.Box (new Rect (Screen.width / 2 - 100, 70, 200,30), say);
        }
        //游戏结束
        else if (sign == 2||sign == 3)
        {
            string say;
            if(sign == 2)say = "你输了,一共坚持了" + action.getround().ToString() + "局";
            else say = "你赢了,一共得了" + ScoreController.GetScoreController().getscore().ToString() +"分";
            GUI.Box (new Rect (Screen.width / 2 - 100, Screen.height / 2 + 50, 200, 30), say);
            if (GUI.Button (new Rect (Screen.width / 2 - 80, Screen.height / 2, 160, 20), "重开")){
                action.gamestart();
                sign = 1;
            }
        }
    }
}

场记 Controllor

其实这里再写一个游戏控制的类,或者把游戏规则丢进分数控制器里应该会好一点,但是本人有点懒,就把场景控制全丢这了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using mygame;
public class Controllor : MonoBehaviour, ISceneController, IUserAction
{
    public static System.Random rand;
    UserGUI user_gui;
    DiskFactory diskFactory;
    ScoreController scoreController;
    List<DiskModel> diskModels;
    public int round;
    GunModel gun;

    void Start ()
    {
        //设置导演
        SSDirector director = SSDirector.GetInstance();
        //设置场景
        director.CurrentScenceController = this;
        //新建View
        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
        //设置分数控制器
        scoreController = ScoreController.GetScoreController();
        //设置飞碟工厂
        diskFactory =DiskFactory.getFactory();
        //设置飞碟模型
        diskModels = diskFactory.GetDiskModels();
        //设置随机种子
        rand = new System.Random((int) System.DateTime.Now.Ticks & 0x0000FFFF);
        LoadResources();
    }
	这里稍微讲一下上面倒数第二行。这个随机搞了我半天,因为这里的C#调用了unity的库,会出现重载等情况,
导致一些常用的类诸如DateTime、Random不能跟平常一样使用。这时候我们可以通过在类前面添加System来进
行调用。
	然后在使用C#随机数时存在伪随机数的情况,这里用时间做种子避免了。然后unity本身也是用random的,不过
用法跟原来得就不一样了,有无伪随机我没有试过。
	最后,下面的代码无缝衔接上文
    public void LoadResources()
    {
        gun = new GunModel();
    }
    public void gamestart(){
        round = 0;
        scoreController.gamestart();
        gun.gamestart();
        user_gui.sign = 1;
    }

    public void gamestop(){
        diskFactory.recycleall();
        gun.gameend();
    }
    
    void Update(){
        if(user_gui.sign==1){
            checkdisk();
            //检查是否分数为负数
            user_gui.sign = Check();
            if(user_gui.sign==1&&diskModels.Count==0){
            	//当前回合结束,增加回合并发射飞碟
                round++;
                if(round<=10)sentdisk();
            }
            //检查是否超过十局
            user_gui.sign = Check();
            if(user_gui.sign!=1)gamestop();
        }
    }

    public void checkdisk(){
        for(int i = diskModels.Count-1;i>=0;i--){
            if(diskModels[i].status()==0)continue;
            string effect;
            if(diskModels[i].status()==1){//掉出空间
                scoreController.addscore(diskModels[i].getdiegrade());
                effect = "disappear";
            }
            else{//被打中
                scoreController.addscore(diskModels[i].getgrade());
                if(diskModels[i].getkind()==3)effect = "death";
                else effect = "ok";
            }
            //网上下好的特效预设
            Destroy(Object.Instantiate(Resources.Load(effect, typeof(GameObject)),diskModels[i].getpos(),Quaternion.identity) as GameObject,3);
            //回收模型
            diskModels[i].destroy();
            DiskFactory.getFactory().recycleDisk(i);
        }
    }

    public void sentdisk(){
        int num = round/3+1;//飞碟数量
        int speed = round/2+1;//飞碟速度
        diskFactory.prepareDisks(num,rand);
        for(int i = 0;i< diskModels.Count;i++){
            diskModels[i].setdisk(speed,rand);
        }

    }

    public int Check()
    {
    	//检测游戏状态
        if(scoreController.getscore()<0)return 2;
        if(round>10)return 3;
        return 1;
    }

    public int getround(){
        return round;
    }
}


2.分数控制器、飞碟工厂和飞碟模型

ScoreController 分数控制器

    public class ScoreController : System.Object{//分数控制器
        private static ScoreController scoreController;
        int score;
        public static ScoreController GetScoreController () {
            if (scoreController == null) {
                scoreController = new ScoreController ();
            }

            return scoreController;
        }
        
        public int getscore(){
            return score;
        }

        public void addscore(int point){
            score += point;
        }

        public void gamestart(){
            score  = 0;
        }

    }

DiskFactory 飞碟工厂

这里的实现是参考学长博客的,个人觉得把基本的逻辑都实现了,但不是很清楚具体实现是不是这样。

    public class DiskFactory : System.Object {//飞碟工厂
        public DiskModel diskPrefab;
        private static DiskFactory diskFactory;
        List<DiskModel> usingDisks;
        List<DiskModel>[] uselessDisks;

        public static DiskFactory getFactory () {
            if (diskFactory == null) {
                diskFactory = new DiskFactory ();
                diskFactory.uselessDisks = new List<DiskModel>[4];
                diskFactory.usingDisks = new List<DiskModel>();
                for(int i = 0;i<4;i++){
                    diskFactory.uselessDisks[i] = new List<DiskModel>();
                }
            }

            return diskFactory;
        }

        public void prepareDisks (int diskCount,System.Random r) {
            for (int i = 0; i < diskCount; i++) {
                int kind = r.Next()%4;//飞碟种类随机
                if (uselessDisks[kind].Count == 0) {
                    DiskModel disk = new DiskModel(kind);
                    usingDisks.Add (disk);
                } else {
                    DiskModel disk = uselessDisks[kind][0];
                    uselessDisks[kind].RemoveAt (0);
                    usingDisks.Add (disk);
                }
            }

        }

        public void recycleall(){
            for(int i = usingDisks.Count-1;i>=0;i--)recycleDisk(i);
        }

        public void recycleDisk (int index) {
            uselessDisks[usingDisks[index].getkind()].Add (usingDisks[index]);
            usingDisks.RemoveAt (index);
        }

        public List<DiskModel> GetDiskModels(){
            return usingDisks;
        }
    }

DiskModel 飞碟模型

这里首先开了个静态数组,存储了累死累活测出来的可行的位置、施加力、扭矩力对应数组。

  public class DiskModel{
        GameObject disk;
        static Vector3[] position = {new Vector3(-20,10,20),new Vector3(15,5,10),new Vector3(-20,0,0),new Vector3(20,20,15),new Vector3(0,0,10)};
        static Vector3[] force = {new Vector3(5,5,-4),new Vector3(-8,5,0),new Vector3(8,8,5),new Vector3(-5,0,-1.5f),new Vector3(0,8,0)};
        static Vector3[] rota = {new Vector3(5,5,-5),new Vector3(10,0,1),new Vector3(-3,5,4),new Vector3(1,-12,-8),new Vector3(-3,-6,4)};
        int grade;
        int diegrade;
        int kind;
        public int statu;
        Diskbehaviour diskbehaviour;

        public DiskModel(int k){
            kind = k;
            string kstr = "disk";
            kstr += kind.ToString();
            disk = Object.Instantiate(Resources.Load(kstr, typeof(GameObject))) as GameObject;
            if(kind== 0){
                grade = 5;
                diegrade = -2;
            }
            else if(kind == 1){
                grade = 10;
                diegrade = -2;
            }
            else if(kind == 2){
                grade = 20;
                diegrade = -2;
            }
            else if(kind == 3){
                grade = -10;
                diegrade = 5;
            }
            statu = 0;
            diskbehaviour = disk.AddComponent(typeof(Diskbehaviour)) as Diskbehaviour;
            diskbehaviour.setdisk(this);
        }

这里对飞碟运动的处理是我这次很受益的地方,本来我已经写好了自制的move类了,但是因为数据的原因,会显得很僵硬。然后回头一看学长博客,只能说这个力的运用秒不可言。其中扭矩力的添加,单纯是我觉得物体转起来会更3D一些

        public void setdisk(int sp,System.Random r){//改变飞碟速度
            int rp = r.Next()%position.GetLength(0);
            disk.transform.position = position[rp];
            Rigidbody rigidbody;
            rigidbody = disk.GetComponent<Rigidbody>();
            //启动刚体
            rigidbody.WakeUp();
            rigidbody.useGravity = true;
            //添加瞬间力
            float disksp = 1;
            for(int i = 1;i<sp;i++)disksp *= 1.1f;
            rigidbody.AddForce(force[rp]*Random.Range(5, 8)*disksp/5, ForceMode.Impulse);
            //添加旋转力
            rigidbody.AddTorque(rota[rp] * 10);
        }

        public int getgrade(){
            return grade;
        }
        
        public int getdiegrade(){
            return diegrade;
        }

        public int getkind(){
            return kind;
        }

        public Vector3 getpos(){
            return disk.transform.position;
        }

        public int status(){
            if(disk.transform.position.y<-3)statu = 1;
            return statu;
        }

        public void destroy(){
            statu = 0;
            disk.GetComponent<Rigidbody>().Sleep();
            disk.GetComponent<Rigidbody>().useGravity = false;
            disk.transform.position = new Vector3(0f, -99f, 0f);
        }
    }

最后这里对飞碟刚体的处理更让我大为叹服,把物体的刚体运算关掉,再放到看不到的角落,确实是个好方法

3.枪与子弹的实现

这一部分,我学会了如何让枪口随鼠标移动,子弹发射,与子弹碰撞这三点。个人觉得这应该是游戏制作中挺还是实用的。

GunModel 枪模型

    public class GunModel{
        GameObject gun;
        Gunbehaviour gunbehaviour;
        public GunModel(){
            gun = Object.Instantiate(Resources.Load("gun", typeof(GameObject)),new Vector3(1,0,-9),Quaternion.identity) as GameObject;
            gunbehaviour = gun.AddComponent(typeof(Gunbehaviour)) as Gunbehaviour;
            
        }
        public void gamestart(){
            gunbehaviour.setaction(1);
        }
        public void gameend(){
            gunbehaviour.setaction(0);
            gun.transform.rotation = Quaternion.identity;
        }
    }

Gunbehaviour 枪行为

    public class Gunbehaviour: MonoBehaviour{
        public Vector3 mousePos;
        int action = 0;
        float firesp  = 100;
        GameObject bullet;
        GameObject fire;
        void Start(){
            bullet = Object.Instantiate(Resources.Load("bullet", typeof(GameObject)),new Vector3(0f, -99f, -99f),Quaternion.identity) as GameObject;
            bullet.GetComponent<Rigidbody>().useGravity = false;
            fire = transform.GetChild(1).gameObject;
        }

技巧一 :通过脚本来找到子对象
获得对象的transform后可以通过GetChild(index)来找到对应下标的子对象,transform的gameobject可以直接找到对应对象。

        void Update(){
            if(action==1){
            	//枪支移动
                Vector3 offset = mousePos - Input.mousePosition;
                transform.Rotate(Vector3.up * offset.x *-0.1f, Space.World);//左右旋转
                transform.Rotate(Vector3.right * -offset.y *-0.1f, Space.World);//上下旋转
                mousePos = Input.mousePosition;
          		
          		//射击
                if(Input.GetMouseButtonDown(0)){
                    GameObject newbullet = Instantiate(bullet,fire.transform.position,transform.rotation);
                    Destroy(newbullet, 3.0f);
                    Rigidbody clone = newbullet.GetComponent<Rigidbody>();
                    clone.velocity = transform.TransformDirection(Vector3.forward*firesp); 
                }
            }
        }

        public void setaction(int action){
            this.action = action;
            if(action == 1)mousePos = new Vector3(Screen.width / 2,Screen.height/2-10,0);
        }
    }

技巧二 鼠标移动调动物体旋转
这里我们可以通过Input.mousePosition来不断获得鼠标焦点位置,而通过记录鼠标位置的变化,我们可以通过同步移动物体的rotation来实现调动旋转的效果。其中-0.1f相当于灵敏度与方向,可自行调整
技巧三 子弹射击
首先我们在枪口这里设置好空对象,这样我们就可以通过找到预设子对象的位置来找到枪口。
在这里插入图片描述
然后是transform.TransformDirection,这个方法是将局部坐标转为世界坐标,说人话就是你走路只管前后左右(你当前rotation的相对坐标),这个方法会自动帮你转成东南西北(屏幕里看到的世界坐标,即绝对坐标)

子弹碰撞实现

两者使用碰撞体
在这里插入图片描述
其中实现碰撞(要添加脚本的)勾选Is Trigger
脚本实现的函数
在这里插入图片描述

添加脚本如下

    public class Diskbehaviour : MonoBehaviour{
        DiskModel disk;
        public void setdisk(DiskModel disk){
            this.disk = disk;
        }
        void OnTriggerEnter(Collider collider) {
            if(collider.tag == "Finish"){
                disk.statu = 2;
            }
        }
    }

其中我们需要修改被碰撞(没有脚本)的游戏对象的tag,如下
在这里插入图片描述


三、 展示效果

1.展示图

在这里插入图片描述

2.展示视频

枪打恶鬼 演示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值