unity3D学习6 模型与动画

模型与动画


设计要求

游戏设计要求

  • 创建一个地图和若干巡逻兵;
  • 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址。即每次 确定下一个目标位置,用自己当前位置为原点计算;
  • 巡逻兵碰撞到障碍物如树,则会自动选下一个点为目标;
  • 巡逻兵在设定范围内感知到玩家,会自动追击玩家;
  • 失去玩家目标后,继续巡逻;
  • 计分:每次甩掉一个巡逻兵计一分,与巡逻兵碰撞游戏结束;

程序设计要求

  • 必须使用订阅与发布模式传消息、工厂模式生产巡逻兵

游戏设计过程

订阅与发布模式简介

发布订阅模式又叫做观察者模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察着对象。它是由两类对象组成,主题和观察者,主题负责发布事件,同时观察者通过订阅这些事件来观察该主体,发布者和订阅者是完全解耦的,彼此不知道对方的存在,两者仅仅共享一个自定义事件的名称。
在这里插入图片描述
发布—订阅模式可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起,虽然不太清楚彼此的细节,但这不影响它们之间相互通信。当有新的订阅者出现时,发布者的代码不需要任何修改;同样发布者需要改变时,也不会影响到之前的订阅者。只要之前约定的事件名没有变化,就可以自由地改变它们。

订阅发布关系

这次任务的关键就是使用订阅与发布模式。在开始实际编写程序之前,我们首先明确游戏中的订阅发布关系。订阅发布关系如下:(箭头根部为发布者,箭头所指处为订阅者)
在这里插入图片描述
其中巡逻兵(patroller)订阅由游戏角色发布的位置信息。当游戏角色(Me)被巡逻兵抓到后就会发布collision的信息,当场景控制器接到这个信息之后就会进行结束游戏操作,当其他巡逻兵接收到之后就会停止运动,从而游戏进入一个停止的状态。当用户界面点下重新游戏按键后会向所有组件发布重启的信息,收到这个信息的组件就会执行自己的重置操作。
在明确这个游戏中的订阅发布关系之后,我们开始来实现我们的游戏。

具体实现

整个游戏的框架都沿用了之前游戏的框架,这次作业的主要改变就是对订阅发布模式的使用。
MeController
MeController负责控制游戏角色Me,也就是控制玩家操控的角色。在游戏角色部分要发布CatchPerson和PublishZone两个信息。
当游戏角色在运动过程中撞到了patroller游戏对象的时候,就会触发OnCollisionEnter函数,并进一步发布CatchPerson事件,通知场景控制器游戏角色被巡逻兵抓到了,然后场景控制器就会进行游戏结束的操作。
在Update函数中,游戏角色一直通过PublishZone向外发布自己当前所在的区域以及自己当前的位置。巡逻兵可以通过订阅PublishZone获取当前角色的区域,从而对自己当前行为进行修改。

public class MeController : MonoBehaviour
{
    private bool stop;
    private FirstController action;
    public delegate void Catch_person(string info);
    public static event Catch_person OnCatch;
    public delegate void PublishZone(int zone, Vector3 me_position);
    public static event PublishZone OnZone;
    private GameObject me;
    private int curr_zone;

    // Start is called before the first frame update
    void Start()
    {
        action = SSDirector.GetInstance().CurrentScenceController as FirstController;
        me = action.GetMe();
        stop = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (stop) return;
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        Vector3 velocity = new Vector3(x, 0f, z);
        Rigidbody playerRigidbody = me.GetComponent<Rigidbody>();
        playerRigidbody.freezeRotation = true;
        playerRigidbody.MovePosition(me.transform.position + velocity * Time.deltaTime * 2);

        int curr_zone = 0;
        if (me.transform.position.x <= 0f)
        {
            if (me.transform.position.z <= 0f)
            {
                curr_zone = 2;
            }
            else
            {
                curr_zone = 0;
            }
        }
        else
        {
            if (me.transform.position.z >= 0f)
            {
                curr_zone = 1;
            }
            else
            {
                curr_zone = 3;
            }
        }
        OnZone(curr_zone, me.transform.position);
    }

    void Restart()
    {
        stop = false;
    }
    void OnEnable()
    {
        UserGUI.OnRestart += Restart;
    }
    void OnDisable()
    {
        UserGUI.OnRestart -= Restart;
    }

    void OnCollisionEnter(Collision collision)
    {
        Debug.Log(collision.gameObject.name);
        if (collision.gameObject.name.Substring(0, 4) == "patr")
            if (OnCatch != null)
            {
                Debug.Log("catch");
                OnCatch("catch me");
                stop = true;
            }
    }
}

PatrollerController
PatrollerController负责控制场景中的巡逻兵(patroller)。在游戏中总共有4个区域,巡逻兵根据自己的区域标志zone值确定自己在哪个区域活动。巡逻兵同时订阅了游戏角色发布的位置信息,包含游戏角色当前所在区域以及游戏角色的具体位置。当巡逻兵发现游戏角色在自己的区域中时,就将当前运动的目标位置设置为游戏角色位置。如果游戏角色没有进入巡逻兵的范围,巡逻兵就在自己的区域内随机运动。

public class PatrollerController : MonoBehaviour
{
    public int zone = 0;
    public Vector3 direction;
    public GameObject patroller;
    private Vector3 rand_target;
    private int me_zone;
    private Vector3 me_position;
    private bool stop;

    void Start()
    {
        rand_target = patroller.transform.position;
        stop = false;
    }

    void Update()
    {
        if (stop) return;
        Vector3 curr_target;
        if (me_zone == zone)
            curr_target = me_position;
        else
            curr_target = rand_target;

        if (curr_target != patroller.transform.position)
        {
            patroller.transform.position = Vector3.MoveTowards(patroller.transform.position, curr_target, 1f * Time.deltaTime);
        }
        else
        {
            float ran_x, ran_z;
            if (zone == 0)
            {
                ran_x = Random.Range(-0.2f, -4.8f);
                ran_z = Random.Range(0.2f, 4.8f);
            }
            else if (zone == 1)
            {
                ran_x = Random.Range(0.2f, 4.8f);
                ran_z = Random.Range(0.2f, 4.8f);
            }
            else
            {
                ran_x = Random.Range(-0.2f, -4.8f);
                ran_z = Random.Range(-0.2f, -4.8f);
            }
            rand_target = new Vector3(ran_x, 0.5f, ran_z);
        }
    }

    void End(string info)
    {
        stop = true;
    }

    void Restart()
    {
        stop = false;
    }

    void SetZone(int Me_zone, Vector3 Me_pos)
    {
        me_zone = Me_zone;
        me_position = Me_pos;
    }
    void OnEnable()
    {
        MeController.OnCatch += End;
        MeController.OnZone += SetZone;
        UserGUI.OnRestart += Restart;
    }
    void OnDisable()
    {
        MeController.OnCatch -= End;
        MeController.OnZone -= SetZone;
        UserGUI.OnRestart -= Restart;
    }
}

FirstController
场景控制器FirstController也订阅了来自游戏角色的OnCatch和OnZone。当游戏角色被巡逻兵抓到的时候就调用裁判类的接口来记录,当然被抓住的结果就是游戏直接结束了。FirstController同时也会记录游戏角色的区域变化,当游戏角色所处的区域变化的时候,如果游戏角色这个时候也没有被抓住那么就可以视为游戏角色成功的甩掉了巡逻兵,这是就调用裁判类进行计分。

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    public Factory disk_factory;
    public UserGUI user_gui;
    public CCJudgement judgement;

    private GameObject me;
    private int curr_zone;
    private List<GameObject> patrol_list  = new List<GameObject>(); 
    private bool playing_game = true;

    void Start ()
    {
        SSDirector director = SSDirector.GetInstance();     
        director.CurrentScenceController = this;             
        disk_factory = Singleton<Factory>.Instance;
        judgement = Singleton<CCJudgement>.Instance;
        user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
        curr_zone = 3;
        this.LoadResources();
    }
	
	void Update ()
    {
    }

    public GameObject GetMe()
    {
        return me;
    }

    void ChangeZone(int Me_zone, Vector3 Me_pos)
    {
        if(Me_zone!=curr_zone && curr_zone!=3)
            judgement.Record();
        curr_zone = Me_zone;
    }

    void End(string info)
    {
        playing_game = false;
        judgement.Catch();
    }

    void OnEnable()
    {
        MeController.OnCatch += End;
        MeController.OnZone += ChangeZone;
    }
    void OnDisable()
    {
        MeController.OnCatch -= End;
        MeController.OnZone -= ChangeZone;
    }

    public void LoadResources()
    {
        me = Instantiate(Resources.Load<GameObject>("Prefabs/me"), new Vector3(2.5f, 0.5f, -2.5f), Quaternion.identity);
        for (int i = 0; i < 3; i++)
        {
            patrol_list.Add(disk_factory.Produce(i));
        }
    }

    public int GetScore()
    {
        return judgement.score;
    }
    public int GetLife()
    {
        return judgement.life;
    }
    public void ReStart()
    {
        playing_game = true;
        judgement.Reset();
        for(int i = 0; i < 3; i++)
        {
            if (i == 0)
            {
                patrol_list[i].transform.position = new Vector3(-2.5f, 0.5f, 2.5f);
            }
            else if (i == 1)
            {
                patrol_list[i].transform.position = new Vector3(2.5f, 0.5f, 2.5f);
            }
            else
            {
                patrol_list[i].transform.position = new Vector3(-2.5f, 0.5f, -2.5f);
            }
        }
        me.transform.position = new Vector3(2.5f, 0.5f, -2.5f);
    }
}

UserGUI
在UserGUI中我也定义一个Restart信息的发布,当用户点击Restart按键之后,UserGUI就同时向订阅了这个事件的游戏角色(Me)、侦察兵(Patroller)和场景控制器(FirstController)发布重启信息。游戏角色、侦察兵和场景控制器收到这个事件信息之后就按照自己的Restart函数将自己的状态、位置和标志设置到初始状态。

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    public delegate void Restart();
    public static event Restart OnRestart;

    void Start ()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
    }
	
	void OnGUI ()
    {
        GUISkin skin = GUI.skin;
        skin.button.normal.textColor = Color.black;
        skin.label.normal.textColor = Color.black;
        skin.button.fontSize = 20;
        skin.label.fontSize = 20;
        GUI.skin = skin;
        int life = action.GetLife();

        if(life > 0)
        {
            GUI.Label(new Rect(0, Screen.height / 16, Screen.width / 8, Screen.height / 16), "Score:" + action.GetScore().ToString());
        }
        else
        {
            GUI.Label(new Rect(Screen.width/2-60, Screen.height*5/16, 120, Screen.height / 8), "Game Over!");
            GUI.Label(new Rect(Screen.width/2-75, Screen.height*7/16, 200, Screen.height / 8), "Your score is:"+ action.GetScore().ToString());
            if (GUI.Button(new Rect(Screen.width * 3 / 8, Screen.height * 9 / 16, Screen.width / 4, Screen.height / 8), "Restart"))
            {
                OnRestart();
                action.ReStart();
                return;
            }
        }
    }
}

在实现了订阅发布模式之后,当一个事件发生之后我们不再需要像以前一样通过显式的调用其他类的接口来实现消息的传播,现在我们只需要将这个消息发布,需要对这个事件做出响应的类就订阅这个消息。这样可以很好的解除订阅者和发布者之间的耦合。总的来说这次游戏实现过程我也是收获满满。

游戏运行效果

在这里插入图片描述
详细工程代码请查看我的github:https://github.com/Eric3778/Patroller,如果有什么问题请大家及时指出,谢谢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值