Unity之闪电侠大战蓝毒兽(简陋的战斗系统)

目录

🎨一、创建地形

🎮二、创建角色

🏃2.1 动画

🏃2.2 拖尾 

🏃2.3 角色控制 

🏃2.4 技能释放

🏃2.5 准星

📱三、创建敌人

🐲3.1 选择模型

🐲3.2 敌人动画器 

🐲3.3 敌人脚本

💤四、杂谈


新年好啊大家,开工大吉,一起摸鱼!经过小编一个月摸鱼的努力,本篇给自己学到的知识做一个汇总和应用。效果如下动图所示:

小编加入了丑陋的蓝毒兽,并让它看起来不那么傻。它会追击、会进攻、还会死(doge.jpg)。

小编给了它十滴血,当我们控制闪电侠攻击它的时候会实时判断血量HP,HP小于等于0就会触发死亡动画。


当然也有很多不足之处,所以才叫“简陋的战斗系统。。。”我们控制的角色没有加HP,蓝毒兽攻击角色是没有反应的,死了后还在地上滑动起来了。。。估计是爆炸后呲的它来回撞。有空的话小编还会慢慢完善,废话不多说,下面带大家实现这个实例。


一、创建地形

首先地形是小编自己绘制的,教程可以看下面这篇文章。 

Unity之地形的构建-CSDN博客文章浏览阅读1.1k次,点赞16次,收藏19次。PS:公司没活干,好无聊偷偷摸鱼学Unity,害怕自己学完之后忘记,写下这一篇博客https://blog.csdn.net/qq_48512649/article/details/135267070资源商店也有很多好的地形场景,大家可以用自己喜欢的来做。

二、创建角色

角色模型的创建和动作可以参考下面这篇文章。大家选一个自己喜欢的人物模型来做。。。小编当初做练习选的这个就拿来用了也没换,后悔死了,后面都懒得换了。

Unity之动画和角色控制_unity 角色控制-CSDN博客文章浏览阅读1k次,点赞24次,收藏19次。《绿洲大镖客之重生我是闪电侠》😋_unity 角色控制https://blog.csdn.net/qq_48512649/article/details/135775295

作为一个初学者偶然知道的这个模型素材网站,但是我还没用过(简单瞅了瞅里面有闪电侠的模型doge.jpg)爱给网_音效配乐_3D模型_视频素材_免费下载 


Player的标签设置为Player我们后面要用到:

2.1 动画

这里角色的动画器小编分了两个图层:

  1. Base Layer图层是 静止<——>跑步——>起跳——>空中静止——>落地动作,动作的转换参数小编用的BoolTrigger居多。

  2. 第二个图层用来释放技能的,这里小编设计了两个技能

这里讲一下,权重表示图层的权重,跑跳等动作权重(1)比技能权重(0.9)高,播放动画就会优先播放跑跳的动作。

因为释放技能我们只用手来播放动画,这里的遮罩wave, 释放技能时保证手部动作流畅(新建一个遮罩并保留手部为绿色即可,然后加到图层中)。


2.2 拖尾 

 同样在之前的博客闪电侠的拖尾我们是由拖尾组件TrailRenderer实现的,但是实现效果拖尾只出现在了角色脚下那部分。这和我们所熟悉的闪电侠不一样啊。

 为了把拖尾效果覆盖到全身小编就想到了一个很笨很笨的方法。

新建好多个空对象把它们放到角色身上,再把拖尾组件都挂到这些空对象上,这样拖尾效果就覆盖到全身了。(大佬有好的方案教教我o(╥﹏╥)o) 

来看下效果: 

2.3 角色控制 

接下来轮到脚本了,编写FPSMove脚本控制玩家移动和动画之间的转换:

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

//给玩家挂载 Character Controller 组件
public class FPSMove : MonoBehaviour
{
    private CharacterController cc;
    //移动速度
    public float moveSpeed;
    //跳跃速度
    public float jumpSpeed;
    //定义获得按键值的两个变量
    private float horizontalMove, verticalMove;
    private Vector3 dir;

    //手动实现重力
    public float gravity;
    private Vector3 velocity;
   
    //创建空对象放到玩家最底部用来检测碰没碰到地面,把空对象挂上来
    //检测点的中心位置
    public Transform groundCheck;  
    //检测点的半径
    public float checkRadius;
    //需要检测的图层
    public LayerMask groundLayer;
    //布尔值存储Physics.CheckSphere的返回值
    public bool isGround;
    
    //定义动画变量
    private Animator animator;
    
    private void Start()
    { 
        //获取角色控制组件
        cc = GetComponent<CharacterController>();
        //获取动画组件
        animator = GetComponent<Animator>();
    }

    private void Update()
    {
        //检测玩家是否在地面上,如果定义的球体和物体发生碰撞返回true
        isGround = Physics.CheckSphere(groundCheck.position, checkRadius, groundLayer);
        if (isGround && velocity.y < 0)
        {
            velocity.y = -2f;
        }

        //获得移动按键的值
        horizontalMove = Input.GetAxis("Horizontal") * moveSpeed;
        verticalMove   = Input.GetAxis("Vertical") * moveSpeed;
        dir = transform.forward * verticalMove + transform.right * horizontalMove;
        //实现移动
        cc.Move(dir * Time.deltaTime);
          
        //每秒减去重力的值不断下降,手动实现重力
        velocity.y -= gravity * Time.deltaTime;
        cc.Move(velocity * Time.deltaTime);
          
        //判断是否按下跳跃键
        if (Input.GetButtonDown("Jump") && isGround) 
        {
            //给玩家一个向上的速度
            velocity.y = jumpSpeed;
            //播放跳跃动画
            animator.SetTrigger("Jump");
        }
        isGroundTest();
        
        //如果向量不等于空就证明我们按了方向键了
        if (dir != Vector3.zero)
        {
            //播放跑步动画
            animator.SetBool("IsRun",true);
        }
        //没有按方向键
        else
        {
            //播放站立动画
            animator.SetBool("IsRun",false);
        }
    }

    private void isGroundTest()
    {
        if (isGround == false)
        {
            //播放落地动画
            animator.SetBool("down",true);
        }
        else if (isGround == true)
        {
            //播放落地动画
            animator.SetBool("down",false);
        }
    }
}

 老样子空对象GroundCheck放在角色脚底用来检测角色碰没碰到地面,把地形的图层设置为Ground

2.4 技能释放

技能的模型和发生碰撞后爆炸的原理大家可以根据下面文章回顾一下,小编技能用的模型也是在这里面挑选的:

Unity之物理系统_unity 物理系统-CSDN博客文章浏览阅读1.2k次,点赞21次,收藏20次。专栏的上一篇角色控制器控制角色移动跳崖,这一篇来说说Unity的物理系统。本篇小编还要带大家做一个碰撞检测效果实例,先放效果图:流星撞击地面产生爆炸效果。_unity 物理系统https://blog.csdn.net/qq_48512649/article/details/135600857

编写Gun脚本来控制技能的生成并挂到角色身上:

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

public class Gun : MonoBehaviour
{
    //子弹预设体序列化
    [SerializeField]
    private GameObject bulletPrefab;
    
    //游戏对象子弹发射点
    [SerializeField]
    private GameObject bulletPoint;
    
    //子弹速度
    [SerializeField] 
    private float bulletSpeed = 1200;
    
    //定义动画变量
    private Animator animator;
    
    [SerializeField] 
    //获取技能对象
    private GameObject RotatorPS2;

    [SerializeField] 
    private GameObject PS2Point;
    
    // Start is called before the first frame update
    void Start()
    {
        //获取动画组件
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //判断是否按下技能键
        if (Input.GetButtonDown("Fire1"))
        {
            //播放跳跃动画
            animator.SetTrigger("wave");
            PlayerFireEffectTest();
        }

        //判断是否按下技能键
        if (Input.GetButtonDown("Fire2"))
        {
            //播放技能动画
            PlayerFireEffect();
            //播放技能动画
            animator.SetTrigger("pickUp");
        }
    }
    
    //播放技能特效
    public void PlayerFireEffect()
    {
        Debug.Log("shoot!!!!");
        //生成一个子弹实例,每点击射击一次则生成一枚子弹
        GameObject bullet = Instantiate(RotatorPS2, bulletPoint.transform.position, transform.rotation);
        //启动特效
        bullet.SetActive(true);
        //给子弹一个向前的力
        bullet.GetComponent<Rigidbody>().AddForce(transform.forward * bulletSpeed);
        //8秒关闭
        //Invoke("UnEffect", 8f);
        Destroy(bullet,8);
    }

    public void PlayerFireEffectTest()
    {
        //生成一个子弹实例,每点击射击一次则生成一枚子弹
        GameObject bullet = Instantiate(bulletPrefab, PS2Point.transform.position, transform.rotation);
        //启动特效
        bullet.SetActive(true);
        //给子弹一个向前的力
        bullet.GetComponent<Rigidbody>().AddForce(transform.forward * bulletSpeed);
    }

    // 取消播放技能特效
    private void UnEffect()
    {
        RotatorPS2.SetActive(false);
    }
}

要给你选择的技能特效加上标签,用来识别是否打到蓝毒兽身上。

 这里FirePointPS2Point是一二技能的发射点,把你喜欢的技能特效挂在Gun脚本上,小编这里放了Magic fire2RotatorPS2,别忘了给技能特效加刚体和碰撞组件。

 有了技能还不够,技能发射后碰到物体产生爆炸效果,编写fire脚本挂载到你所选的技能特效预设体上。

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

public class Fire : MonoBehaviour
{
    [SerializeField]
    //创建一个爆炸的预设体
    private GameObject Explosion;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    //监听发生碰撞: 只监听发生碰撞的一瞬间
    private void OnCollisionEnter(Collision collision)  //参数 collision 就是你碰撞到的物体的碰撞信息
    {
        //创建一个爆炸物体
        GameObject explosion =Instantiate(Explosion, transform.position, Quaternion.identity);   //参数: 预设体,爆炸位置是自身火焰位置,不旋转
        //销毁自身(流星): 流星撞击地面爆炸物产生流星销毁
        Destroy(gameObject);
        explosion.SetActive(true);
        //获取碰撞到物体看一下参数collision —— 打印一下物体名称,这里打印的是地面的名称
        Debug.Log("撞到了"+collision.gameObject.name);
        
        //实用小技巧
        //给地面一个图层"Ground"   collision.collider.tag == "Ground"   可以判断物体是否踩在地面上
    }
    
    //结束碰撞检测方法
    private void OnCollisionExit(Collision other)
    {
        //collision.collider.tag == "Ground"  判断物体是否离开地面
    }
    
    //监听触发检测
    private void OnTriggerEnter(Collider other)
    {
        
    }
}

比如我这里选的RotatorPS2特效 ,我把fire脚本挂到它身上,ExplosionVariant是爆炸特效。

技能撞倒物体消失产生爆炸效果,爆炸后也会消失,编写 ExplosionTest 脚本挂到爆炸特效上来实现这一功能。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class ExplosionTest : MonoBehaviour
{
    float timer = 0;
    // Start is called before the first frame update
    void Start()
    {
        
    }
 
    // Update is called once per frame
    void Update()
    {
        Debug.Log("============这里是explosion");
        //如果时间大于一秒就销毁爆炸物
        timer += Time.deltaTime;
        if (timer > 1)
        {
            Destroy(gameObject);
        }
    }
}

2.5 准星

小编盗用自己的图片来加准星(doge.jpg)

新建一个图像,然后根据下图的数据设置好,还可以选择自己喜欢的颜色,这样准星就设置好了。

 到这里玩家的功能差不多都实现了,下面我们来实现丑陋的蓝毒兽。

三、创建敌人

3.1 选择模型

首先我们去资源商店淘一下敌人的免费模型,毕竟小编很穷o(╥﹏╥)o。搜索monster点击免费资源就会找到下面这个,这个居然是1月26号发布的,当然也有很多其他的优秀资源看个人喜好选择。


在选模型的过程中还闹过笑话,之前选了一个类似大猩猩的模型下半身陷进去了眼睛却还是在原来的位置(这是加了寻路组件之后才这样的),然后在寻路组件的作用下保持这个姿势向我靠近。。。后来也没改过来

 3.2 敌人动画器 

 动画器这里小编编辑的有静止、跑步、攻击、死亡。attack1和attack2小编直接连一块了就,形成一个连击。


至于这里的run和run0,我不知道是模型还是哪出问题了跑步的动画循环播放不了只播放一遍,所以我又又想出了一个很笨很笨的方法让俩跑步动画来回切换达到循环的效果。。。大家不要笑话我。

关于导航网格Nav Mesh Agent组件的使用大家可以回顾这篇文章,使用Nav Mesh Agent组件记得烘焙地形,这里我们用作敌人的寻路功能。

Unity之射线检测和导航系统-CSDN博客文章浏览阅读558次,点赞5次,收藏7次。不知道大家有没有玩过红色警戒 —— 一款即时战略游戏,和罪恶都市一样小编小学的时候就开始玩了,这款游戏控制单位角色移动是通过鼠标的点击来实现。同样的操作方法还有英雄联盟等很多游戏,那本篇文章小编就通过简单小实例来讲解这种操作在Unity中是如何实现的。小编先展示一下实例的演示动图:红色球体表示我们控制的角色,鼠标左键点击小球就会到相应的坐标位置并打印坐标。https://blog.csdn.net/qq_48512649/article/details/135674470这是蓝毒兽挂载的组件大家参考一下:

3.3 敌人脚本

好了接下来我们让丑陋的蓝毒兽变得聪明一点,创建脚本AI挂载到蓝毒兽上:


这里用枚举的方式来描述蓝毒兽的状态。

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

//状态
public enum EnemyState
{
    idle,   //静止状态
    run,    //逃跑状态
    attack, //攻击状态
    die     //死亡状态
}

public class AI : MonoBehaviour
{
    //状态
    public EnemyState CurrentState = EnemyState.idle;
    //动画控制器
    private Animator animator;
    //玩家
    private Transform player;
    //导航
    private NavMeshAgent agent;
    //怪兽血量
    private int hp = 10;
    // Start is called before the first frame update
    void Start()
    {
        //获取动画组件
        animator = GetComponent<Animator>();
        agent = GetComponent<NavMeshAgent>();
        player = GameObject.FindWithTag("Player").transform;
    }

    // Update is called once per frame
    void Update()
    {
        float distance = Vector3.Distance(player.position, transform.position);
        //判断状态
        switch (CurrentState)
        {
            case EnemyState.idle:
                idle(distance);
                break;
            case EnemyState.run:
                run(distance);
                break;
            case EnemyState.attack:
                attack(distance);
                break;
            case EnemyState.die:
                die();
                break;
        }
    }

    void idle(float distance)
    {
        //站立状态,如果和玩家距离为5到200米变为跑步
        if (distance >= 5 && distance <= 200)
        {
            CurrentState = EnemyState.run;
        }
        //播放站立动画
        animator.SetBool("IsRun", false);        
        //导航停止
        agent.isStopped = true;
    }
    
    void run(float distance)
    {
        //如果离玩家大于200米,则变为站立
        if (distance >= 200)
        {
            CurrentState = EnemyState.idle;
        }else if (distance < 9)
        {
            CurrentState = EnemyState.attack;
        }
        //播放跑步动画
        animator.SetBool("IsRun", true); 
        animator.SetTrigger("Run1");
        animator.SetTrigger("Run2");
        //导航开始
        agent.isStopped = false;
        agent.SetDestination(player.position);
    }

    void attack(float distance)
    {
        //如果离玩家大于10米,则变为站立
        if (distance >= 10)
        {
            CurrentState = EnemyState.run;
        }
        //播放攻击动画
        animator.SetTrigger("Attack");
    }

    void die()
    {
        //播放死亡动画
        animator.SetTrigger("die");
    }
    
    //监听碰撞检测
    private void OnCollisionEnter(Collision collision)
    {
        //判断是否碰到技能
        if (collision.collider.tag == "Skill")
        {
            //血量减少
            hp--;
            //如果血量少于等于0,敌人死亡
            if (hp <= 0)
            {
                Debug.Log("==========HP:" + hp);
                CurrentState = EnemyState.die;
                //Destroy(gameObject);
            }
        }
    }
}

四、杂谈

实例教程到这里就先结束了,八千多字比我毕业论文字数都多(doge.jpg)。继续扩展的话可以像很多游戏一样设置一个出生点,不断有丑陋的蓝毒兽实例被创建;还可以给我们玩家也加上血条,被攻击也会有击退反应;有想法之后可以进行针对性学习。


更新近战伤害检测思路

如果是做近战的战斗系统的话:比如拿刀砍人_(:з」∠)_

实现思路之一是设置刀的碰撞器大小和刀差不多,根据刀的碰撞检测执行伤害,同样的思路放到蓝毒兽身上,可以给蓝毒兽的爪子加上碰撞器来检测玩家受到攻击。

using UnityEngine;

public class DamageCollider : MonoBehaviour
{
    Collider damageCollider;

    public int currentWeaponDamage = 25;

    private void Awake()
    {
        damageCollider = GetComponent<Collider>();
        damageCollider.gameObject.SetActive(true);
        damageCollider.isTrigger = true;
        damageCollider.enabled = false;
    }

    public void EnableDamageCollider()
    {
        damageCollider.enabled = true;
    }

    public void DisableDamageCollider()
    {
        damageCollider.enabled = false;
    }

    private void OnTriggerEnter(Collider collision)
    {
        if(collision.tag == "Player")
        {
            PlayerStats playerStats = collision.GetComponent<PlayerStats>();

            if(playerStats != null)
            {
                playerStats.TakeDamage(currentWeaponDamage);
            }
        }

        if(collision.tag == "Enemy")
        {
            EnemyStats enemyStats = collision.GetComponent<EnemyStats>();
            if(enemyStats != null)
            {
                enemyStats.TakeDamage(currentWeaponDamage);
            }
        }
    }

}
public class PlayerStats : CharacterStats
{

    public HealthBar healthBar;
    public StaminaBar staminaBar;

    AnimatorHandler animatorHandler;

    private void Awake()
    {
        healthBar = FindObjectOfType<HealthBar>();
        staminaBar = FindObjectOfType<StaminaBar>();
        animatorHandler = GetComponentInChildren<AnimatorHandler>();
    }

    private void Start()
    {
        maxHealth = SetMaxHealthFromHealthLevel();
        currentHealth = maxHealth;
        healthBar.SetMaxHealth(maxHealth);

        maxStamina = SetMaxStaminaFromStaminaLevel();
        currentStamina = maxStamina;
        staminaBar.SetMaxStamina(maxStamina);
    }

    private int SetMaxHealthFromHealthLevel()
    {
        maxHealth = healthLevel * 10;
        return maxHealth;
    }

    private int SetMaxStaminaFromStaminaLevel()
    {
        maxStamina = staminaLevel * 20;
        return maxStamina;
    }

    public void TakeDamage(int damage)
    {
        currentHealth = currentHealth - damage;
        healthBar.SetCurrentHealth(currentHealth);

        animatorHandler.PlayTargetAnimation("Damage_01", true);

        if(currentHealth <= 0)
        {
            currentHealth = 0;
            animatorHandler.PlayTargetAnimation("Dead_01", true);
        }
    }

    public void TakeStaminaDamage(int damage)
    {
        currentStamina = currentStamina - damage;
        staminaBar.SetCurrentStamina(currentStamina);
    }

}

 我还看到过用射线实现近战伤害检测的文章感觉这个很酷。Unity 游戏中近战攻击判定检测——射线检测_unity攻击判定-CSDN博客文章浏览阅读8.9k次,点赞13次,收藏74次。近战攻击基于射线检测是否击中敌人.可以解决:动作帧之间的运动间隔太大了,如果武器绑的碰撞器,就会揍不到敌人,因为武器的碰撞盒直接跳过对象,会导致无法击中._unity攻击判定https://blog.csdn.net/wch3351028/article/details/122326021


总之,自己写的游戏想怎么来就怎么来,拜拜┏(^0^)┛

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值