实现攻击扣血的流程

这个流程主要实现以下功能:界面上显示玩家当前的总血量,当被其他玩家发射的子弹打中后,会减1格血,血量为0时判定为死亡,会重置玩家的位置到出生点,并且重设血量为最大血量。

实现这个逻辑,分了以下几个步骤。

1.维护玩家血量相关信息。

首先玩家血量要用图形化在界面上显示,首先需要在场景中建立一个GUITexture,这里起名为GUI_heart,来表示当前血量图片。为了控制血量的显示,为GUI_heartf附加了一个控制脚本,名为HeartControl.cs。这个脚本的内容如下:

01.using UnityEngine;

02.using System.Collections;

03.

04.public class HeartControl : MonoBehaviour {

05.    public Texture2D[] heartImageArray;

06.    private const int maxLives = 5;

07.    private static int lives = maxLives;

08.

09.    public static void DecreaseLive()

10.    {

11.        lives--;

12.        if(lives==0)

13.        {

14.            lives=maxLives;

15.            PlayerManager.selfPlayer.position = GameObject.Find("SpwanPlayer").transform.position;

16.        }

17.    }

18.    // Use this for initialization

19.    void Start () {

20.

21.    }

22.

23.    // Update is called once per frame

24.    void Update () {

25.        if(PlayerManager.selfPlayer!=null)

26.        {

27.            guiTexture.enabled = true;

28.            guiTexture.texture = heartImageArray[lives-1];

29.        }

30.        else

31.        {

32.            guiTexture.enabled = false;

33.        }

34.

35.

36.    }

37.}

这里heartImageArray表示不同血量所对应的血量图片,血量用心型图标来显示,5格血就是5颗心。把这个数组声明为public,可以在编辑器中,设置这个数组的尺寸,以及每个入口点所对应的图片,十分方便。

maxLives就是最大血量有5格血,lives是当前人物的血量。

在每帧更新函数Update()中,首先检查当前玩家的变量是否为空,为空可能是因为是Server端,这种情况下就不需要显示血量,因此将图片设为false:guiTexture.enabled = false;

如果不为空,那么将根据当前血量的值,把对应数组中的图片赋值给这个guiTexture当前的texture,这样就实现图片随血量值的变化而变化。

这里PlayerManager.selfPlayer中的PlayerManager我自己建立的一个全局的静态类,维护了唯一的静态变量private static Transform _selfplayer,代表客户端所对应的玩家自己。这个类可以被所有脚本所访问,访问起来十分方便,实现如下:

01.using UnityEngine;

02.using System.Collections;

03.using System;

04.

05.public static class PlayerManager  {

06.

07.    private static Transform _selfplayer = null;

08.    public static Transform selfPlayer

09.    {

10.        get

11.        {

12.            return _selfplayer;

13.        }

14.        set

15.        {

16.            _selfplayer = value;

17.

18.        }

19.    }

20.

21.}

后期随着逻辑的复杂,可以为这个脚本的玩家增加更多的数据来维护。

      然后是DecreaseLive函数,这个函数可以被外部所调用,给玩家减血,调用后,自动扣除一滴血,为0时,正如规定的一样,重设玩家血量为最大血量,并且找到重生点的位置,将玩家位置重置。

2.控制子弹打中玩家的逻辑

实现大体流程是先给玩家的人物身上附加一个释放子弹的脚本,当客户端自己的玩家按键触发技能时,就调用一个网络上的RPC,告诉所有客户端和服务器在本地创建一个飞行的子弹。并且在这个人物脚本里添加一个响应人物碰撞的事件函数,然后检查是不是碰撞的对象是子弹,而且不是这个人物所发射的,如果检测成功,就调用HeartControl脚本的减血函数。

这个脚本名为Shoot.cs,实现如下:

001.using UnityEngine;

002.using System.Collections;

003.

004.public class Shoot : MonoBehaviour

005.{

006.    public Texture skillTex1;

007.    public Texture skillTex2;

008.    public Transform bulletPrefab;

009.    public Transform explosionEffect;

010.    public AnimationClip attackAnim;

011.

012.    // Use this for initialization

013.    void Start ()

014.    {

015.

016.    }

017.

018.    // Update is called once per frame

019.    void Update ()

020.    {

021.        if (networkView.isMine && Input.GetKeyUp ("2")) {

022.            ShootBullet ();

023.

024.        }

025.            if (networkView.isMine && Input.GetKeyUp ("3")) {

026.            ExplosiveEffect();

027.

028.        }

029.

030.    }

031.

032.    void OnGUI ()

033.    {

034.        if (networkView.isMine)

035.        {

036.            if(GUI.Button (new Rect ((float)(0.5*Screen.width-60), (float)(Screen.height-60), 60, 60), skillTex1))

037.                {

038.            ShootBullet();}

039.

040.            if(GUI.Button(new Rect((float)0.5*Screen.width,(float)Screen.height-60,60,60),skillTex2))

041.            {

042.                ExplosiveEffect();

043.            }

044.        }

045.

046.    }

047.

048.    void OnCollisionEnter(Collision collisionInfo)

049.    {

050.

051.

052.        if(collisionInfo.gameObject.tag=="bullet" )

053.        {

054.            BulletScript bs = (BulletScript)collisionInfo.gameObject.GetComponent("BulletScript");

055.            if(bs.Owner != gameObject)

056.            {

057.                print("OnCollisionEnter"+collisionInfo.gameObject.name);

058.                Destroy(collisionInfo.gameObject);

059.

060.

061.                if(!networkView.isMine)

062.                    return;

063.                HeartControl.DecreaseLive();

064.            }

065.

066.

067.

068.        }

069.    }

070.

071.

072.

073.    void ExplosiveEffect()

074.    {

075.

076.        networkView.RPC("SpwanExplosion",RPCMode.All);

077.        SendMessage("PlayAnimation_Attack");

078.    }

079.

080.    void ShootBullet()

081.    {

082.        networkView.RPC("SpawnBullet",RPCMode.All);

083.    }

084.

085.    [RPC]

086.    void SpawnBullet ()

087.    {

088.

089.

090.        Vector3 forward = transform.TransformDirection (Vector3.forward);

091.        Transform effectPoint = transform.Find ("effectPoint");

092.        PrefabDepends preScript = (PrefabDepends)GameObject.Find("GameObject_GlobalController").GetComponent("PrefabDepends");

093.        if (effectPoint) {

094.

095.            Transform bullet = (Transform)Instantiate (preScript.bulletPrefab, effectPoint.position, Quaternion.identity);

096.            bullet.rigidbody.AddForce (forward * 2000);

097.            BulletScript bs = (BulletScript)bullet.GetComponent("BulletScript");

098.            bs.Owner = gameObject;

099.        }

100.

101.    }

102.

103.    [RPC]

104.    void SpwanExplosion()

105.    {

106.        Vector3 forward = transform.TransformDirection (Vector3.forward);

107.        Transform effectPoint = transform.Find ("effectPoint2");

108.        if (effectPoint) {

109.            //Object temp = Network.Instantiate (bulletPrefab, effectPoint.position, Quaternion.identity, 0);

110.

111.            Transform explosion = (Transform)Instantiate (explosionEffect, effectPoint.position, Quaternion.identity);

112.            //Network.Instantiate(bulletPrefab,transform.Find("effectPoint").position,Quaternion.identity,0);

113.        }

114.    }

115.

116.}

SpawnBullet就是调用的RPC call,在这里除了用子弹的prefab创建出子弹附加初始速度,还给子弹的脚本BulletScript的一个变量Owner赋值为脚本所附加到的玩家,这是为了判断这个子弹是哪个玩家发出用的。

碰撞检测的函数是OnCollisionEnter,要想触发这个函数,人物身上需要有一个Collider控件,而CharacterController是没有用的。所有的子弹都设为了名为"bullet"的tag,所以在这里检测,如果碰撞的对象是bullet,并且子弹上的脚本的变量Owner不是这个玩家自己,说明被别人的子弹击中,就先销毁本地创建出来的子弹,然后判断如果这个玩家是这个客户端所对应的,就进行减血。这个判断是必要的,因为这里的血量是全局静态的,只有一份代表自己,其他玩家被子弹攻击到了,不需要更新自己的血量。






人物的名称与血条的绘制方法很简单,但是我们需要解决的问题是如何在3D世界中寻找合适的坐标。因为3D世界中的人物是会移动的,它是在3D世界中移动,并不是在2D平面中移动,但是我们需要将3D的人物坐标换算成2D平面中的坐标,继而找到人物头顶在屏幕中的2D坐标最后使用GUI将名称与血条绘制出来。

首先学习本文的重点内容,如何将游戏世界中任意3D坐标转换成屏幕中的2D坐标。根据这个方法计算出的2D坐标屏幕左下角的点为0.0 ,屏幕右上角的坐标为1.1 所以真实的2D坐标还得通过Screen.height 与Screen.width计算一下才行。

1 Vector2 position = camera.WorldToScreenPoint (worldPosition);

 

       在Unity工程导入角色控制器组件,不知道角色控制器的朋友请阅读我之前的文章哈。创建一个Plane做为游戏的地面,然后利用角色控制器组件创建两个模型,一个做为主角,一个作为NPC,主角可以通过控制来移动从四周来观察NPC对象。由于地面的面积比较小移动主角时为了避免主角越界掉下去,我们做一个边界的物理层。物理层其实很简单,就是给平面四周放置四个平面在四周将平面包围着,给四周的四个平面绑定上Box Collider组件,这样主角就不会越界掉下去啦。因为没有给贴图所以效果上看不到这四个对象。哇咔咔~ 如下图所示,在场景是途中主角被四个平面包围这,即时它拼命的想往外条但是还是跳不出去,哈哈。

 

 

创建脚本NPC.cs 然后把脚本挂在NPC对象身上,在脚本中我们绘制主角的血条以及名称。

NPC.cs

001 using UnityEngine;
002 using System.Collections;
003  
004 public class NPC : MonoBehaviour {
005  
006     //主摄像机对象
007     private Camera camera;
008     //NPC名称
009     private string name = "我是雨松MOMO";
010  
011     //主角对象
012     GameObject hero;
013     //NPC模型高度
014     float npcHeight;
015     //红色血条贴图
016     public Texture2D blood_red;
017     //黑色血条贴图
018     public Texture2D blood_black;
019     //默认NPC血值
020     private int HP = 100;
021  
022     void Start ()
023     {
024         //根据Tag得到主角对象
025         hero = GameObject.FindGameObjectWithTag("Player");
026         //得到摄像机对象
027         camera = Camera.main;
028  
029         //注解1
030         //得到模型原始高度
031         float size_y = collider.bounds.size.y;
032         //得到模型缩放比例
033         float scal_y = transform.localScale.y;
034         //它们的乘积就是高度
035         npcHeight = (size_y *scal_y) ;
036  
037     }
038  
039     void Update ()
040     {
041         //保持NPC一直面朝主角
042         transform.LookAt(hero.transform);
043     }
044  
045     void OnGUI()
046     {
047         //得到NPC头顶在3D世界中的坐标
048         //默认NPC坐标点在脚底下,所以这里加上npcHeight它模型的高度即可
049         Vector3 worldPosition = new Vector3 (transform.position.x , transform.position.y + npcHeight,transform.position.z);
050         //根据NPC头顶的3D坐标换算成它在2D屏幕中的坐标
051         Vector2 position = camera.WorldToScreenPoint (worldPosition);
052         //得到真实NPC头顶的2D坐标
053         position = new Vector2 (position.x, Screen.height - position.y);
054         //注解2
055         //计算出血条的宽高
056         Vector2 bloodSize = GUI.skin.label.CalcSize (new GUIContent(blood_red));
057  
058         //通过血值计算红色血条显示区域
059         int blood_width = blood_red.width * HP/100;
060         //先绘制黑色血条
061         GUI.DrawTexture(new Rect(position.x - (bloodSize.x/2),position.y - bloodSize.y ,bloodSize.x,bloodSize.y),blood_black);
062         //在绘制红色血条
063         GUI.DrawTexture(new Rect(position.x - (bloodSize.x/2),position.y - bloodSize.y ,blood_width,bloodSize.y),blood_red);
064  
065         //注解3
066         //计算NPC名称的宽高
067         Vector2 nameSize = GUI.skin.label.CalcSize (new GUIContent(name));
068         //设置显示颜色为黄色
069         GUI.color  = Color.yellow;
070         //绘制NPC名称
071         GUI.Label(new Rect(position.x - (nameSize.x/2),position.y - nameSize.y - bloodSize.y ,nameSize.x,nameSize.y), name);
072  
073     }
074  
075     //下面是经典鼠标点击对象的事件,大家看一下就应该知道是什么意思啦。
076     void OnMouseDrag ()
077     {
078         Debug.Log("鼠标拖动该模型区域时");
079     }
080  
081     void OnMouseDown()
082     {
083         Debug.Log("鼠标按下时");
084  
085         if(HP >0)
086         {
087             HP -=5 ;
088         }
089  
090     }
091     void OnMouseUp()
092     {
093         Debug.Log("鼠标抬起时");
094     }
095  
096     void OnMouseEnter()
097     {
098         Debug.Log("鼠标进入该对象区域时");
099     }
100     void OnMouseExit()
101     {
102         Debug.Log("鼠标离开该模型区域时");
103     }
104     void OnMouseOver()
105     {
106         Debug.Log("鼠标停留在该对象区域时");
107     }
108  
109 }

 

注解1:通过collider.bounds.size 可以拿到模型对应三个轴向的高度,但是模型是可以缩放的,所以真实的模型高度应当是原始高度乘以缩放系数才行。 transform.localScale可以拿到模型对应三个轴向的缩放系数,因为这里我们需要模型的高度,所以忽略X轴与Z轴。

注解2:在这里我们计算血条的宽度,GUI.skin.label.Calcsize()这个方法是以默认的皮肤对象Label对象去参数对象的宽高。参数是new GUIContent(blood_Red)意思是拿红色血条的贴图的宽高,它将保存在返回的size中。最后以宽高将血条绘制在屏幕中,我们的血条采取两层。背景是黑色的,前面是红色的,当人物费血时红色血条减少。

注解3: 这里通过字符串来获取它整体的宽度与高度,因为NPC的名称是可变的,所以我们需要动态的获取整体的显示区域。同样是以GUI.skin.label对象去调用CalcSize。

 

如下图所示,当使用鼠标点击NPC对象时,NPC头顶的血条将开始发生减血。这个例子我使用OnGUI绘制当然大家也可以在Hierarchy 视图中的创建GUI Texture 或者GUI Text对象 来实现,不过原理都是这样的 大家可以试试 哇咔咔。

 

 

最后雨松MOMO希望和大家一起进步,一起学习,哇咔咔~    


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值