3D游戏作业7:巡逻兵

3D游戏作业7:巡逻兵

思路

本次作业还是采用MVC结构进行设计。不同的是,这次作业引用了一些动画。需要自定义Animator Controller以及相应的算法。

另外,为了使运动看上去更加“丝滑”,这次项目所有物体都使用物理引擎

成果

在这里插入图片描述

代码及解析

Director.cs

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

public class Director : System.Object
{
    public static ISceneController current;
    public static ISceneController GetCurrentScene(){
        return current;
    }

}

public interface ISceneController{
    void LoadResources();
    void GameOver();
    void GetScore();
}

public interface DataSource{
    int GetStatus();
    Vector3 MePosition();
}

Director类是导演,用于管理当前工作的场记,这里只有FirstController。

ISceneController是场景管理接口。由FirstController继承。里面有一些场景重要的函数。LoadResources()用于生成资源,GameOver结束游戏,GetScore增加得分。

DateSource是体现订阅与发布模式编程用的。FirstController产生的信息,通过DataSource接口,传递给所有的巡逻兵和主角。GetStatus是获取游戏状态,游戏结束后一切停止。MePosition()获得主角的位置,巡逻兵距离主角较近就会追逐主角。

FirstController.cs

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

public class FirstController : MonoBehaviour,ISceneController,DataSource
{
    public GameObject me;
    public GameObject camera;
    public GameObject wall;
    public GameObject patrol;
    public GameObject hwall;
    public GameObject light;
    public Texture2D clo;
    public int patrolNum=17;
    public int wallNum=34;
    public int score=0;
    public int status=1;
    

    public GUIStyle f;

    // Start is called before the first frame update
    void Start()
    {
        Random.InitState((int)System.DateTime.Now.Ticks);
        Director.current=this;
        LoadResources();

        f.fontSize=50;
        f.normal.textColor=new Color(255,255,255);
        f.alignment=TextAnchor.MiddleCenter;
    }

    // Update is called once per frame
    void Update()
    {
        camera.transform.position=me.transform.position+ new Vector3(0,12,-12);
        light.transform.position=me.transform.position+new Vector3(0,5,-5);
    }

    void OnGUI(){
        if (GUI.Button(new Rect(Screen.width-100,0,100,100),clo)){
            Application.Quit();
        }
        GUI.Label(new Rect(0,Screen.height-100,Screen.width,100),"Score : "+score.ToString(),f);
        
        if (status==0){
            GUI.Label(new Rect(Screen.width/2,Screen.height/2,200,100),"Game Over !",f);
        }
    }

    public void LoadResources(){
        Instantiate(Resources.Load<GameObject>("Terrain"),new Vector3(150,-0.5f,150),Quaternion.identity).name="terrain";
        me=Instantiate(Resources.Load<GameObject>("me"),new Vector3(150,1f,150),Quaternion.identity);
        me.name="me";
        me.AddComponent(typeof(MeController));
        camera=this.gameObject;
        wall=Resources.Load<GameObject>("wall");
        light=Instantiate(Resources.Load<GameObject>("light"),me.transform.position,Quaternion.identity);
        clo=Resources.Load<Texture2D>("15");

        for(int i=1;i<=101;++i){
            Instantiate(wall,new Vector3(99,1,99+i-1),Quaternion.identity).name="bianyuan";
            Instantiate(wall,new Vector3(99+i-1,1,200),Quaternion.identity).name="bianyuan";
            Instantiate(wall,new Vector3(200,1,200-i+1),Quaternion.identity).name="bianyuan";
            Instantiate(wall,new Vector3(200-i+1,1,99),Quaternion.identity).name="bianyuan";
        }

        patrol=Resources.Load<GameObject>("patrol");
        


        float[] x=new float[patrolNum+1];
        float[] z=new float[patrolNum+1];
        x[0]=150;z[0]=150;
        for (int i=1;i<=patrolNum;++i){
            while(true){
                x[i]=Random.Range(105f,194f);
                z[i]=Random.Range(105f,194f);

                bool can=true;
                for (int j=0;j<i;++j){
                    if ( (x[i]-x[j])*(x[i]-x[j])  +  (z[i]-z[j])*(z[i]-z[j]) <=3 ){
                        can=false;
                        break;
                    }
                }

                if (can==true){
                    GameObject t=Instantiate(patrol,new Vector3(x[i],1f,z[i]),Quaternion.identity);
                    t.name="patrol";
                    t.AddComponent(typeof(PatrolController));
                    break;
                }
            }
           
        }


        hwall=Resources.Load<GameObject>("h");
        for (int i=1;i<=wallNum;++i){
            float xx=Random.Range(101f,198f);
            float zz=Random.Range(101f,198f);

            GameObject t=Instantiate(hwall,new Vector3(xx,1,zz),Quaternion.identity);
            t.name="wall";
            if (Random.Range(0f,100f)<=50){
                t.transform.LookAt(t.transform.position+new Vector3(-1,0,0));
                
            }
        }

        
    }

    public int GetStatus(){
        return status;
    }
    public Vector3 MePosition(){
        return me.transform.position;
    }

    public void GameOver(){
        status=0;
    }
    public void GetScore(){
        score++;
    }
}

场记类,用于管理所有的对象,资源。并且记录关键信息,发出游戏结束信号。

在LoadResources()里,加载了所有的巡逻兵,还有主角,地板,墙壁,障碍物等,并把他们放在合适的位置上。为其添加相应的组件。

同时也负责摄像机的移动,和光源位置的切换。

ObjectController.cs

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

public class MeController:MonoBehaviour{
    public GameObject me;
    private float speed=10f;
    private float ac=7;
    private DataSource ds;
    private Animator ani;
    public Vector3 dir;
    public Vector3 v;

    void Start(){
        me=this.gameObject;
        ds=Director.GetCurrentScene() as DataSource;
        ani=me.GetComponent<Animator>();
        dir=new Vector3(0,0,0);
    }

    void FixedUpdate(){
        if (ds.GetStatus()==0) {
            me.GetComponent<Rigidbody>().velocity=new Vector3(0,0,0);
            return;
        }

        if (Input.GetKey(KeyCode.W)){
            if (me.GetComponent<Rigidbody>().velocity.z<=speed)
                me.GetComponent<Rigidbody>().AddForce(new Vector3(0,0,1)*ac);
            ani.SetBool("moving",true);
        }
        else{
           // if (me.GetComponent<Rigidbody>().velocity.z>0)
               // me.GetComponent<Rigidbody>().velocity-=new Vector3(0,0,me.GetComponent<Rigidbody>().velocity.z);
        }

//--------------------------------------------------------------------------------------------------
        if (Input.GetKey(KeyCode.S)){
            if (me.GetComponent<Rigidbody>().velocity.z>=0-speed)
                me.GetComponent<Rigidbody>().AddForce(new Vector3(0,0,-1)*ac);
            ani.SetBool("moving",true);
        }
        else{
           // if (me.GetComponent<Rigidbody>().velocity.z<0)
               // me.GetComponent<Rigidbody>().velocity-=new Vector3(0,0,me.GetComponent<Rigidbody>().velocity.z);
            
        }
//--------------------------------------------------------------------------------------------------------
        if (Input.GetKey(KeyCode.A)){
            if (me.GetComponent<Rigidbody>().velocity.x>=0-speed)
                me.GetComponent<Rigidbody>().AddForce(new Vector3(-1,0,0)*ac);
            ani.SetBool("moving",true);
        }
        else{
          //  if (me.GetComponent<Rigidbody>().velocity.x<0)
              //  me.GetComponent<Rigidbody>().velocity-=new Vector3(me.GetComponent<Rigidbody>().velocity.x,0,0);
        }
//-------------------------------------------------------------------------------------------------------
        if (Input.GetKey(KeyCode.D)){
            if (me.GetComponent<Rigidbody>().velocity.x<=speed)
                me.GetComponent<Rigidbody>().AddForce(new Vector3(1,0,0)*ac);
            ani.SetBool("moving",true);
        }
        else{
          //  if (me.GetComponent<Rigidbody>().velocity.x>0)
               // me.GetComponent<Rigidbody>().velocity-=new Vector3(me.GetComponent<Rigidbody>().velocity.x,0,0);
        }

//----------------------------------------------------------------------------------------------




        
    }

    void LateUpdate(){
        Vector3 s=me.GetComponent<Rigidbody>().velocity;
        if (new Vector3(s.x,0,s.z).magnitude<=5f){
            if (!Input.GetKey(KeyCode.W) && !Input.GetKey(KeyCode.S) &&!Input.GetKey(KeyCode.A) &&!Input.GetKey(KeyCode.D) )
                ani.SetBool("moving",false);
        }

        if ((s.x!=0 || s.z!=0) && s.magnitude>=0.1f){
            dir=new Vector3(s.x,0,s.z);
            dir=dir/dir.magnitude;
        }
        
        
        me.transform.LookAt(me.transform.position+dir-new Vector3(0,dir.y,0));

        v=me.GetComponent<Rigidbody>().velocity;


    }
}

public class PatrolController:MonoBehaviour{
    private GameObject patrol;
    private float scale=5;
    private float speed=5;
    private float ac=6;
    private DataSource ds;
    private ISceneController isc;

    public float[][] route;
    public int next;
    public int follow;

    void Start(){
        patrol=this.gameObject;
        route=CreateRoute();
        next=0;
        follow=0;

        ds=Director.GetCurrentScene() as DataSource;
        isc=Director.GetCurrentScene() as ISceneController;
    }

    void FixedUpdate(){     
        if (ds.GetStatus()==0){
            patrol.GetComponent<Rigidbody>().velocity=new Vector3(0,0,0);
            return;
        }

        Vector3 mp=ds.MePosition();   
        if ((mp-patrol.transform.position).magnitude<=10){
            Move(mp);
            follow=1;
        }
        else{
            if (follow==1){
                route=CreateRoute();
                next=0;
                follow=0;
                isc.GetScore();
            }
            Vector3 tar=new Vector3(route[next][0],patrol.transform.position.y,route[next][1]);
            Move(tar);        

            if ( (tar-patrol.transform.position).magnitude <=0.1f){
                next++;       
                if (next>3) next=0;
                
               
            }
        }



    }

    private float lc=0;
    void OnCollisionStay(Collision c){
        if (c.collider.name=="terrain" || ds.GetStatus()==0) return;

        if (c.collider.name=="wall"){
            if (Time.time-lc>=1f){
                if (follow==0) next++;
                if (next>3) next=0;
                lc=Time.time;
            }
        }

        if (c.collider.name=="me"){
            isc.GameOver();
        }
    }

    public float[][] CreateRoute(){

        float[][] ans;
        ans=new float[4][];
        for (int i=0;i<4;++i)   ans[i]=new float[2];

        float[] x=new float[4];
        float[] z=new float[4];

        x[0]=patrol.transform.position.x-scale; z[0]=patrol.transform.position.z-scale;
        x[1]=patrol.transform.position.x-scale; z[1]=patrol.transform.position.z+scale;
        x[2]=patrol.transform.position.x+scale; z[2]=patrol.transform.position.z-scale;
        x[3]=patrol.transform.position.x+scale; z[3]=patrol.transform.position.z+scale;

        for (int i=0;i<4;++i){
            if (x[i]<100) x[i]=100;
            if (x[i]>199) x[i]=199;

            if (z[i]<100) z[i]=100;
            if (z[i]>199) z[i]=199;
        }

        ans[0][0]=Random.Range(x[0],x[1]); ans[0][1]=Random.Range(z[0],z[1]);
        ans[1][0]=Random.Range(x[1],x[3]); ans[1][1]=Random.Range(z[1],z[3]);
        ans[2][0]=Random.Range(x[2],x[3]); ans[2][1]=Random.Range(z[2],z[3]);
        ans[3][0]=Random.Range(x[0],x[2]); ans[3][1]=Random.Range(z[0],z[2]);
        
        


        return ans;
    }


    public void Move(Vector3 target){

        target=new Vector3(target.x,patrol.transform.position.y,target.z);
        Vector3 dir=target-patrol.transform.position;
        float s=patrol.GetComponent<Rigidbody>().velocity.magnitude;
        if (s<speed)
            patrol.GetComponent<Rigidbody>().AddForce( dir/dir.magnitude *ac  );
        
        patrol.transform.LookAt(new Vector3(target.x,patrol.transform.position.y,target.z));
    }
}

物体管理类,里面是挂载到各个物体上的代码。

MeController是管理主角用的。里面提供了按键移动的函数(WSAD移动)。在LateUpdate中会调节主角的面向,使主角始终面向她移动的方向。

PatrolController类管理所有的巡逻兵。Move函数会让巡逻兵向一个目标移动。

CreateRoute生成路径,生成一个3-5边型的凸多边形,巡逻兵会沿着边巡逻。如果巡逻兵跟丢主角,会在原地周围重新生成一个多边形并且移动。

OnCollisionStay函数处理巡逻兵的碰撞,碰撞到障碍物会前往下一个节点。碰撞到主角后会结束游戏。

项目亮点

可爱,凄美,引人遐想

这是我们的主角:
在这里插入图片描述

效果如下图:
在这里插入图片描述

通过微妙的点光源,让少女在漆黑的迷宫中逃生,黑暗中潜伏着怪物。

让人感觉很有意境,仿佛深夜里XXXXXXXXXXX

全部采用物理引擎,移动更丝滑

主角和巡逻兵,还有障碍物,墙壁都用了物理引擎。

所有角色的移动都是用FixedUpdate里面通过AddForce来实现。

移动时可以清晰地看到加速和减速过程。更符合实际。

对Rigidbody.velocity的新理解

许多新人在用刚体时喜欢直接更改刚体的velocity来改变刚体的速度。这样看上去很方便。

但是在做项目的同时经过实验,发现velocity根本就不是真实的速度

在编程的时候,如果经常使用更改velocity的方法,那么在发生过大量碰撞和摩擦后,velocity的值会偏离真是的速度。也就是说,velocity会被不符合物理逻辑的强制更改打乱

所以在使用物理引擎的时候,更改速度还是用AddForce等符合物理逻辑的方法,尽量不要使用velocity的方法。

代码链接

代码链接

项目下载

下载地址

使用方法:下载,解压。创建新项目,用我的Assets覆盖你的Assets,在unity中打开SampleScene,运行即可。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值