Unity3D学习笔记(7)—— 击球游戏

来源:互联网 发布:c语言奇数阶幻方 编辑:程序博客网 时间:2024/05/18 12:37

        在下Simba,有何贵干!新一期的游戏又出炉了(明明就是个Demo啊喂),这次的游戏比以往都简单(你别骗我),但是比以往的都好看(Excuse me?),没错,那就是动画!这一期的游戏使用了以往都没有使用过的动画系统,而且用了别人模型(不要脸)。

        Garen模型:http://pan.baidu.com/s/1i5exKNV

        使用前请将所有模型和动作设置成Legacy动画, inspector -> Rig -> Animation type -> Legacy -> Apply button。

        先来看看这酷炫的效果吧:(这...这莫非是...盖伦?)


        游戏的规则很简单,玩家控制盖伦击打完场上的7个球游戏结束,并给出最终用时。

        游戏的类图:


        (说好的很简单呢?我怎么看不懂)不急不急,看上去很复杂,其实只有3个脚本要写,话说这图可能还有错,刚学的UML作图。

         步骤说明:

一、场景布置

        场景布置总是游戏开始制作时必须考虑和完善的事情,在打代码前得脑补出游戏正常运行时的情景。我设计的游戏摄像机是不动的,玩家可以在固定区域内通过WASD移动,点击鼠标左键进行攻击。当区域内所有小球都被击打消失后,游戏结束,显示玩家用时。

        首先把玩家Garen拖入场景中,reset位置。新建4个正方体和1个平面,通过拉伸和位移组成一个方块区域:


        给玩家添加刚体组件和碰撞盒,碰撞盒采用胶囊体:




        新建1个UI Text对象,并将其定位在屏幕中央,用于显示玩家的最终用时:


        场景中的所有对象:


二、玩家动作

        OK,可以开始写代码了。想从最直观的写起,那么就先写玩家的控制吧。玩家有3个动作,分别是Idle、Run、Attack。Idle在Start时就播放,Run需要在Update中检测键盘输入,可以使用GetAxisRaw获得WASD的方向。Attack是比较复杂的动作,通过在动作过程中注册回调函数,可以使得在攻击动画播放过程中触发这些函数,比如AttackHit函数,以及StopAttack函数。前者判断有没有击中物体,并广播击中消息。后者使玩家播放站立动画(攻击动画结束后站立)。

using UnityEngine;using System.Collections;public class GarenMovement : MonoBehaviour {    Animation ani;    AnimationState idle;    AnimationState run;    AnimationState attack;    public float speed = 5f;    Vector3 movement;    Rigidbody playerRigidbody;    bool isAttacking = false;    float rayLength = 1.8f;    public delegate void AttackHitHandler(GameObject obj);    public static event AttackHitHandler OnAttackHit;    void Start()    {        playerRigidbody = this.GetComponent<Rigidbody>();        ani = this.GetComponent<Animation>();        idle = ani["Idle"];        run = ani["Run"];        attack = ani["Attack1"];        // 默认播放站立动画        idle.wrapMode = WrapMode.Loop;        ani.Play(idle.clip.name);    }void FixedUpdate ()    {        if (!isAttacking)        {            float h = Input.GetAxisRaw("Horizontal");            float v = Input.GetAxisRaw("Vertical");            Move(h, v);        }        if (Input.GetMouseButtonDown(0))        {            Attack();        }}    void Move(float h, float v)    {        if (h != 0 || v != 0)        {            movement.Set(h, 0f, v);            // 移动玩家位置            movement = movement.normalized * speed * Time.deltaTime;            playerRigidbody.MovePosition(transform.position + movement);            // 旋转玩家角度            Quaternion newRotation = Quaternion.LookRotation(movement);            playerRigidbody.MoveRotation(newRotation);            // 播放跑步动画            run.wrapMode = WrapMode.Loop;            ani.CrossFade(run.clip.name, 0.1f);        }        else         {            ani.CrossFade(idle.clip.name, 0.1f);        }    }    void Attack()    {        isAttacking = true;        if (attack.clip.events.Length == 0)        {            // 添加攻击动画结束后的回调函数            AnimationEvent endEvent = new AnimationEvent();            endEvent.functionName = "StopAttack";            endEvent.time = attack.clip.length - 0.2f;            attack.clip.AddEvent(endEvent);            // 添加攻击动画中的回调函数            AnimationEvent hitEvent = new AnimationEvent();            hitEvent.functionName = "AttackHit";            hitEvent.time = 0.5f;            attack.clip.AddEvent(hitEvent);        }        ani.Play(attack.clip.name);    }    void StopAttack()    {        isAttacking = false;    }    void AttackHit()    {        // 射线判断打击物        GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC");        Ray ray = new Ray(obj.transform.position, movement);        RaycastHit hit;        if (Physics.Raycast(ray, out hit, rayLength))        {            Debug.DrawLine(ray.origin, hit.point);            OnAttackHit(hit.collider.gameObject);        }    }}

        射线碰撞检测非常有意思,如果单纯的靠网格碰撞器来检测击打物的话,至少我是没想到什么好方法,貌似网格碰撞检测都是被动的,主动的只有射线检测。这里的Trick是把射线的长度设置为刀的长度,然后发出点设为Garen的腰部,这就可以很好地检测Garen的刀是否“砍”中了物体。

        另外,Delegate对象完成了UML图中的AttackHitHandler和HitEvent类的工作,因此实际上代码并没有那么复杂。

        什么是Delegate呢?这好比是一个公众号,任何人都可以关注它。突然某一天,公众号宣布科比退役了!如果是关注了这个公众号的人就可以立马知道这个新闻,但是每个人都可以做出不同的反应,或伤心或开心,因人而因,和公众号就没有关系了。Delegate只负责广播新闻,却不去追究新闻发布后的结果。


三、裁判类

        现在玩家可以执行各种动作了,可是我还不知道我的AttackHitHandler是否能正常工作,于是赶忙写了裁判Judge类来验证一下:

using UnityEngine;using System.Collections;using Com.mygame;public class Judge : MonoBehaviour {    public int count = 7;void Start () {        GarenMovement.OnAttackHit += HitEvent;}    void HitEvent(GameObject obj)    {        if(obj.tag.Contains("Ball"))        {            obj.SetActive(false);            if (--count == 0)            {                MyUI.GetInstance().Display(Time.time.ToString());            }        }    }}

        其中,UI是后来改的,没写时可以用print或Debug来测试。记得添加Tag。


四、工厂类

        现在得考虑球体了,创建一个工厂来管理这些球体是个不错的方法,新建BaseCode脚本用来写单例类吧,顺便定义一个命名空间:

using UnityEngine;using UnityEngine.UI;  using System.Collections;using System.Collections.Generic;using Com.mygame;namespace Com.mygame{    public class BallFactory : System.Object    {        static BallFactory instance;        static List<GameObject> ballList;        public static BallFactory GetInstance()        {            if (instance == null)            {                instance = new BallFactory();                ballList = new List<GameObject>();            }            return instance;        }        public GameObject GetBall()        {            for (int i = 0; i < ballList.Count; ++i)            {                if (!ballList[i].activeInHierarchy)                {                    return ballList[i];                }            }            GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);            newObj.GetComponent<Renderer>().material.color = Color.green;            newObj.tag = "Ball";            ballList.Add(newObj);            return newObj;        }    }}
        考虑到回收完全可以通过ball.SetActive(false)完成,就让Judge来完成了。

五、UI

        UI这么重要的东西这么可以忘掉,在命名空间内加上:

    public class MyUI : System.Object    {        static MyUI instance;        public Text mainText;                public static MyUI GetInstance()        {            if (instance == null)            {                instance = new MyUI();            }            return instance;        }        public void Display(string info)        {            mainText.text = info;        }    }

六、场景初始化

        万事俱备,只欠初始了。BaseCode刚好可以用来初始化场景。Start在区域内随机位置生成7个球,并把MyUI的mainText对象赋值:

public class BaseCode : MonoBehaviour {    public int balls = 7;    public Text text;    void Start()    {        MyUI.GetInstance().mainText = text;        for (int i = 0; i < balls; ++i)        {            GameObject ball = BallFactory.GetInstance().GetBall();            ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f));        }    }}

        代码写完了(简单吧,才200行),得把它们挂载在游戏物体上,我把GarenMovement脚本挂载在了玩家Garen上,把BallFactory和BaseCode挂载在了MainCamera上,运行一下,OK!


全部代码

GarenMovement.cs

using UnityEngine;using System.Collections;public class GarenMovement : MonoBehaviour {    Animation ani;    AnimationState idle;    AnimationState run;    AnimationState attack;    public float speed = 5f;    Vector3 movement;    Rigidbody playerRigidbody;    bool isAttacking = false;    float rayLength = 1.8f;    public delegate void AttackHitHandler(GameObject obj);    public static event AttackHitHandler OnAttackHit;    void Start()    {        playerRigidbody = this.GetComponent<Rigidbody>();        ani = this.GetComponent<Animation>();        idle = ani["Idle"];        run = ani["Run"];        attack = ani["Attack1"];        // 默认播放站立动画        idle.wrapMode = WrapMode.Loop;        ani.Play(idle.clip.name);    }void FixedUpdate ()    {        if (!isAttacking)        {            float h = Input.GetAxisRaw("Horizontal");            float v = Input.GetAxisRaw("Vertical");            Move(h, v);        }        if (Input.GetMouseButtonDown(0))        {            Attack();        }}    void Move(float h, float v)    {        if (h != 0 || v != 0)        {            movement.Set(h, 0f, v);            // 移动玩家位置            movement = movement.normalized * speed * Time.deltaTime;            playerRigidbody.MovePosition(transform.position + movement);            // 旋转玩家角度            Quaternion newRotation = Quaternion.LookRotation(movement);            playerRigidbody.MoveRotation(newRotation);            // 播放跑步动画            run.wrapMode = WrapMode.Loop;            ani.CrossFade(run.clip.name, 0.1f);        }        else         {            ani.CrossFade(idle.clip.name, 0.1f);        }    }    void Attack()    {        isAttacking = true;        if (attack.clip.events.Length == 0)        {            // 添加攻击动画结束后的回调函数            AnimationEvent endEvent = new AnimationEvent();            endEvent.functionName = "StopAttack";            endEvent.time = attack.clip.length - 0.2f;            attack.clip.AddEvent(endEvent);            // 添加攻击动画中的回调函数            AnimationEvent hitEvent = new AnimationEvent();            hitEvent.functionName = "AttackHit";            hitEvent.time = 0.5f;            attack.clip.AddEvent(hitEvent);        }        ani.Play(attack.clip.name);    }    void StopAttack()    {        isAttacking = false;    }    void AttackHit()    {        // 射线判断打击物        GameObject obj = GameObject.Find("C_BUFFBONE_GLB_CENTER_LOC");        Ray ray = new Ray(obj.transform.position, movement);        RaycastHit hit;        if (Physics.Raycast(ray, out hit, rayLength))        {            Debug.DrawLine(ray.origin, hit.point);            OnAttackHit(hit.collider.gameObject);        }    }}

Judge.cs

using UnityEngine;using System.Collections;using Com.mygame;public class Judge : MonoBehaviour {    public int count = 7;void Start () {        GarenMovement.OnAttackHit += HitEvent;}    void HitEvent(GameObject obj)    {        if(obj.tag.Contains("Ball"))        {            obj.SetActive(false);            if (--count == 0)            {                MyUI.GetInstance().Display(Time.time.ToString());            }        }    }}

BaseCode.cs

using UnityEngine;using UnityEngine.UI;  using System.Collections;using System.Collections.Generic;using Com.mygame;namespace Com.mygame{    public class MyUI : System.Object    {        static MyUI instance;        public Text mainText;                public static MyUI GetInstance()        {            if (instance == null)            {                instance = new MyUI();            }            return instance;        }        public void Display(string info)        {            mainText.text = info;        }    }    public class BallFactory : System.Object    {        static BallFactory instance;        static List<GameObject> ballList;        public static BallFactory GetInstance()        {            if (instance == null)            {                instance = new BallFactory();                ballList = new List<GameObject>();            }            return instance;        }        public GameObject GetBall()        {            for (int i = 0; i < ballList.Count; ++i)            {                if (!ballList[i].activeInHierarchy)                {                    return ballList[i];                }            }            GameObject newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);            newObj.GetComponent<Renderer>().material.color = Color.green;            newObj.tag = "Ball";            ballList.Add(newObj);            return newObj;        }    }}public class BaseCode : MonoBehaviour {    public int balls = 7;    public Text text;    void Start()    {        MyUI.GetInstance().mainText = text;        for (int i = 0; i < balls; ++i)        {            GameObject ball = BallFactory.GetInstance().GetBall();            ball.transform.position = new Vector3(Random.Range(-10f, 10f), 1f, Random.Range(-10f, 10f));        }    }}

0 0
原创粉丝点击