【Unity造轮子】制作一个简单的2d抓勾效果(类似蜘蛛侠的技能)

news2024/12/26 21:16:50

前言

欢迎阅读本文,本文将向您介绍如何使用Unity游戏引擎来实现一个简单而有趣的2D抓勾效果,类似于蜘蛛侠的独特能力。抓勾效果是许多动作游戏和平台游戏中的常见元素,给玩家带来了无限的想象和挑战。
请添加图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

不需要担心,即使您是一位新手,也可以轻松跟随本文学习。我们将从头开始,从创建一个新的Unity项目开始,一直到最终的完成效果。

借助本文提供的步骤和技巧,您将能够为您的游戏增添一个的特色。希望您能享受这个过程,并从中获得灵感,探索更多关于游戏开发的乐趣。

照例,我们先来看看本文实现的最终效果,以决定你是否继续往下看
在这里插入图片描述
在这里插入图片描述

源码我放在文章末尾了

开始

1. 实现简单的抓勾效果

新建一个2d项目,添加一个2对象作为我们的角色物体,并挂载rigidbody 2d、碰撞器、Distance Joint 2d、Line Renderer(记得配置好材质和线宽)
在这里插入图片描述
书写脚本代码,代码已经加了详细的解释了,这里就不得过多介绍了

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

// Grappler类,用于处理角色的抓取动作
public class Grappler : MonoBehaviour
{
    // 主摄像机
    public Camera mainCamera;
    // 线渲染器,用于渲染抓取线
    public LineRenderer _lineRenderer;
    // 距离关节,用于处理抓取物体的物理效果
    public DistanceJoint2D _distanceJoint;

    void Start()
    {
        // 初始化时,禁用距离关节
        _distanceJoint.enabled = false;
    }

    void Update()
    {
        // 检测鼠标左键是否按下
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            // 获取鼠标在世界坐标中的位置
            Vector2 mousePos = (Vector2)mainCamera.ScreenToWorldPoint(Input.mousePosition);
            // 设置线渲染器的起始和结束位置
            _lineRenderer.SetPosition(0, mousePos);
            _lineRenderer.SetPosition(1, transform.position);
            // 设置距离关节的连接点
            _distanceJoint.connectedAnchor = mousePos;
            // 启用距离关节和线渲染器
            _distanceJoint.enabled = true;
            _lineRenderer.enabled = true;
        }
        // 检测鼠标左键是否松开
        else if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            // 禁用距离关节和线渲染器
            _distanceJoint.enabled = false;
            _lineRenderer.enabled = false;
        }
        // 如果距离关节启用
        if (_distanceJoint.enabled)
        {
            // 更新线渲染器的结束位置
            _lineRenderer.SetPosition(1, transform.position);
        }
    }
}

挂载脚本和绑定对象
在这里插入图片描述
简单配置一下环境
在这里插入图片描述
运行效果
在这里插入图片描述

可以看到,如果连线时碰撞会出现问题,如果你先实现好的碰撞效果,可以勾选Distance Joint 2d的Enable Collision及开启碰撞
在这里插入图片描述
效果
在这里插入图片描述

2. 高阶钩爪效果

在场景中创建GameObject如下(由父对象到子对象一一进行讲解):
在这里插入图片描述

Player:(示例中是一个 圆形的sprite)对其添加RigidBody2D、Circle Collider2D 、Spring Joint2D组件(跟前面一样Spring Joint2D组件开启Enable Collision碰撞)

Gunpivot:钩锁枪的锚点,其为空对象,位置设置在Player的中心即0.0位置(后续用于实现钩锁枪随着鼠标旋转的效果)

GrapplingGun:(示例中为一个长方形的sprite)钩锁枪,用于后期实现发射钩锁,仅添加Box Collider2D即可

FirePoint:钩锁的发射点,空对象,即钩锁发射的起始位置,设置在钩锁枪的边缘即可

Rope:在空对象上添加LineRenderer并适当改变宽度和材质即可。

下有两个脚本分别添加给GrapplingGun和Rope即可

脚本1:Perfecter_Grapple添加给GrapplingGun

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

public class Perfecter_Grapple : MonoBehaviour
{
    [Header("脚本引用:")]
    public Grappling_Rope grappleRope;

    [Header("层设置:")]
    [SerializeField] private bool grappleToAll = false;
    [SerializeField] private int grappableLayerNumber = 9;

    [Header("主摄像机:")]
    public Camera m_camera;

    [Header("变换引用:")]
    public Transform gunHolder;
    public Transform gunPivot;
    public Transform firePoint;

    [Header("物理引用:")]
    public SpringJoint2D m_springJoint2D;
    public Rigidbody2D m_rigidbody;

    [Header("旋转:")]
    [SerializeField] private bool rotateOverTime = true;
    [Range(0, 60)] [SerializeField] private float rotationSpeed = 4;

    [Header("距离:")]
    [SerializeField] private bool hasMaxDistance = false;
    [SerializeField] private float maxDistnace = 20;

    private enum LaunchType //发射类型
    {
        Transform_Launch,
        Physics_Launch
    }

    [Header("发射:")]
    [SerializeField] private bool launchToPoint = true;
    [SerializeField] private LaunchType launchType = LaunchType.Physics_Launch;
    [SerializeField] private float launchSpeed = 1;

    [Header("无发射点")]
    [SerializeField] private bool autoConfigureDistance = false;
    [SerializeField] private float targetDistance = 3;
    [SerializeField] private float targetFrequncy = 1;

    [HideInInspector] public Vector2 grapplePoint;
    [HideInInspector] public Vector2 grappleDistanceVector;

    private void Start() //开始
    {
        grappleRope.enabled = false;
        m_springJoint2D.enabled = false;
         
    }

    private void Update() //更新函数,控制输入
    {
        if (Input.GetKeyDown(KeyCode.Mouse0)) //通过Input的顺序设定函数的执行顺序,先进行钩爪选取点的定位
        {
            SetGrapplePoint();          
        }
        else if (Input.GetKey(KeyCode.Mouse0)) //从上一步的定位中把grapplerope启用
        {
            if (grappleRope.enabled)
            {
                RotateGun(grapplePoint, false); //进行钩锁枪的旋转
            }
            else
            {
                Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);
                RotateGun(mousePos, true);
            }
                    
            if (launchToPoint && grappleRope.isGrappling) //如果选择点对点发射且正在钩中目标
            {
                if (launchType == LaunchType.Transform_Launch) //如果发射类型是使用Transform类型发射
                {
                    Vector2 firePointDistnace = firePoint.position - gunHolder.localPosition;
                    Vector2 targetPos = grapplePoint - firePointDistnace;
                    gunHolder.position = Vector2.Lerp(gunHolder.position, targetPos, Time.deltaTime * launchSpeed); //采用插值的形式,模拟绳索命中的物理效果
                }
            }
        }
        else if (Input.GetKeyUp(KeyCode.Mouse0)) //若抬起左键,则将一切启用的相关布尔置否,恢复原状
        {
            grappleRope.enabled = false;
            m_springJoint2D.enabled = false;
            m_rigidbody.gravityScale = 1;
        }
        else //时刻获取鼠标的屏幕信息位置
        {
            Vector2 mousePos = m_camera.ScreenToWorldPoint(Input.mousePosition);
            RotateGun(mousePos, true);
        }
    }

    void RotateGun(Vector3 lookPoint, bool allowRotationOverTime) //实现绳索枪根据鼠标进行旋转功能
    {
        Vector3 distanceVector = lookPoint - gunPivot.position; //定义三维距离向量=朝向点-枪锚点位置

        float angle = Mathf.Atan2(distanceVector.y, distanceVector.x) * Mathf.Rad2Deg; //定义一个角度,其值等于距离向量tan所对应的弧度值*弧度值转化为角度值的常量


        if (rotateOverTime && allowRotationOverTime) //当采用根据时间延迟旋转时,采用四元数的插值旋转,在原本的旋转角和获得的绕轴的新角度中进行随时间
        {
            gunPivot.rotation = Quaternion.Lerp(gunPivot.rotation, Quaternion.AngleAxis(angle, Vector3.forward), Time.deltaTime * rotationSpeed);
        }
        else
        {
            gunPivot.rotation = Quaternion.AngleAxis(angle, Vector3.forward); //不采用时间插值变化时时,直接让强旋转角角度等于计算出的角度绕轴的四元数即可
        }
    }

    void SetGrapplePoint() //设定钩取点(主要是位置的计算和注意某些添加的限定条件)
    {
        Vector2 distanceVector = m_camera.ScreenToWorldPoint(Input.mousePosition) - gunPivot.position; //设置一个二维向量distance用于记录鼠标点击的点和枪锚点之间的距离
        if (Physics2D.Raycast(firePoint.position, distanceVector.normalized)) //发射一条射线,起始点为开火点,方向为distance的方向向量
        {
            RaycastHit2D _hit = Physics2D.Raycast(firePoint.position, distanceVector.normalized); //保存刚才的射线为hit
            if (_hit.transform.gameObject.layer == grappableLayerNumber || grappleToAll) //选择是否选中任意的可抓取图层或是某一指定图层
            {
                if (Vector2.Distance(_hit.point, firePoint.position) <= maxDistnace || !hasMaxDistance) //当命中点和开火电站之间的距离小于最大距离或者不限定最大距离时
                {
                    grapplePoint = _hit.point; //将命中点位置赋予抓取点位置
                    grappleDistanceVector = grapplePoint - (Vector2)gunPivot.position; //抓钩的距离向量等于钩锁点减去钩锁枪的锚点位置
                    grappleRope.enabled = true; //打开绳索变量
                }
            }
        }
    }

    public void Grapple() //钩锁执行(真正决定移动)                              
    {
        m_springJoint2D.autoConfigureDistance = false; //设定弹簧关节组建的自动计算距离属性为假
        if (!launchToPoint && !autoConfigureDistance) //当对点发射和自动计算距离均为假时,将目标距离和目标频率赋给弹簧组件的属性
        {
            m_springJoint2D.distance = targetDistance;
            m_springJoint2D.frequency = targetFrequncy;
        }
        if (!launchToPoint) //如果仅为不对点发射
        {
            if (autoConfigureDistance) //若自动计算距离
            {
                m_springJoint2D.autoConfigureDistance = true;
                m_springJoint2D.frequency = 0; //弹簧组件频率属性为0,该值越大,弹簧越硬
            }

            m_springJoint2D.connectedAnchor = grapplePoint; //不自动计算距离且不对点发射时
            m_springJoint2D.enabled = true;
        }
        else //对点发射时,选择发射类型,有物理类发射和Transform类发射
        {
            switch (launchType)
            {
                case LaunchType.Physics_Launch:
                    m_springJoint2D.connectedAnchor = grapplePoint; //当使用物理发射时,将钩取点赋予弹簧的连接锚点

                    Vector2 distanceVector = firePoint.position - gunHolder.position; //长度变量等于开火点距离减去持枪距离

                    m_springJoint2D.distance = distanceVector.magnitude; //将长度变量赋给弹簧组建的距离属性,保证钩爪拉到尽头时有一定的距离
                    m_springJoint2D.frequency = launchSpeed; //弹簧频率(强度)等于发射速度
                    m_springJoint2D.enabled = true; //打开弹簧组件,进行拉伸
                    break;
                case LaunchType.Transform_Launch:
                    m_rigidbody.gravityScale = 0; //当使用Transform发射时,将物体的重力设置为0
                    m_rigidbody.velocity = Vector2.zero; //启动钩爪时,将物体速度清零
                    break;
            }
        }
    }

    private void OnDrawGizmosSelected() //始终在场景中绘制可视的Gizmo,On方法
    {
        if (firePoint != null && hasMaxDistance)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireSphere(firePoint.position, maxDistnace);
        }
    }

}

脚本2:Grappling_Rope 添加给Rope即可

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

public class Grappling_Rope : MonoBehaviour
{
    [Header("一般引用:")]
    public Perfecter_Grapple grapplingGun; //抓钩枪
    public LineRenderer m_lineRenderer; //线渲染器

    [Header("一般设置:")]
    [SerializeField] private int percision = 40; //精度
    [Range(0, 20)] [SerializeField] private float straightenLineSpeed = 5; //直线速度

    [Header("绳索动画设置:")]
    public AnimationCurve ropeAnimationCurve; //绳索动画曲线
    [Range(0.01f, 4)] [SerializeField] private float StartWaveSize = 2; //起始波动大小
    float waveSize = 0; //波动大小

    [Header("绳索进度:")]
    public AnimationCurve ropeProgressionCurve; //绳索进度曲线
    [SerializeField] [Range(1, 50)] private float ropeProgressionSpeed = 1; //绳索进度速度

    float moveTime = 0; //移动时间

    [HideInInspector] public bool isGrappling = true; //是否正在抓取

    bool strightLine = true; //是否为直线

    private void OnEnable() //启用时执行
    {
        moveTime = 0;
        m_lineRenderer.positionCount = percision;
        waveSize = StartWaveSize;
        strightLine = false;

        LinePointsToFirePoint(); //线点对准发射点

        m_lineRenderer.enabled = true;
    }

    private void OnDisable() //禁用时执行
    {
        m_lineRenderer.enabled = false;
        isGrappling = false;
    }

    private void LinePointsToFirePoint() //线点对准发射点
    {
        for (int i = 0; i < percision; i++)
        {
            m_lineRenderer.SetPosition(i, grapplingGun.firePoint.position); //绘制连接抓取点和抓钩枪位置的绳子
        }
    }

    private void Update() //更新函数
    {
        moveTime += Time.deltaTime;
        DrawRope(); //绘制绳索
    }

    void DrawRope() //绘制绳索
    {
        if (!strightLine)
        {
            if (m_lineRenderer.GetPosition(percision - 1).x == grapplingGun.grapplePoint.x)
            {
                strightLine = true;
            }
            else
            {
                DrawRopeWaves(); //绘制绳索波动
            }
        }
        else
        {
            if (!isGrappling)
            {
                grapplingGun.Grapple(); //抓取
                isGrappling = true;
            }
            if (waveSize > 0)
            {
                waveSize -= Time.deltaTime * straightenLineSpeed;
                DrawRopeWaves(); //绘制绳索波动
            }
            else
            {
                waveSize = 0;

                if (m_lineRenderer.positionCount != 2) { m_lineRenderer.positionCount = 2; }

                DrawRopeNoWaves(); //绘制无波动的绳索
            }
        }
    }

    void DrawRopeWaves() //绘制绳索波动
    {
        for (int i = 0; i < percision; i++)
        {
            float delta = (float)i / ((float)percision - 1f);
            Vector2 offset = Vector2.Perpendicular(grapplingGun.grappleDistanceVector).normalized * ropeAnimationCurve.Evaluate(delta) * waveSize; //计算偏移量
            Vector2 targetPosition = Vector2.Lerp(grapplingGun.firePoint.position, grapplingGun.grapplePoint, delta) + offset; //目标位置
            Vector2 currentPosition = Vector2.Lerp(grapplingGun.firePoint.position, targetPosition, ropeProgressionCurve.Evaluate(moveTime) * ropeProgressionSpeed); //当前位置

            m_lineRenderer.SetPosition(i, currentPosition); //设置线的位置
        }
    }

    void DrawRopeNoWaves() //绘制无波动的绳索
    {
        m_lineRenderer.SetPosition(0, grapplingGun.firePoint.position); //设置线的起始位置
        m_lineRenderer.SetPosition(1, grapplingGun.grapplePoint); //设置线的结束位置
    }
}

注意:脚本赋予对象后注意赋值,在Rope中的函数曲线绘制以及添加点时一定要注意不能逾越(0,0)以及(1,1)否则会出现钩锁无法发射的问题。

代码赋值部分参数可以参照:
在这里插入图片描述

在这里插入图片描述

绳索发射曲线样式参照:
在这里插入图片描述
最终效果
在这里插入图片描述

源码

https://gitcode.net/unity1/unity2d-clawhook
在这里插入图片描述

参考

【视频】https://www.youtube.com/watch?v=dnNCVcVS6uw

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/878812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务器扩展未生效

服务器扩容未生效 在阿里云付费扩容后&#xff0c;在服务器里面看未生效。 阿里云->实例与镜像->实例->选择实例->云盘->扩容进入linux服务器查看&#xff1a; df -h vda1扩容未生效。原40g->扩容后100g 解决方法&#xff1a; 1、安装growpart yum inst…

chatgpt和xmind结合起来帮你制作精美的思维导图

介绍 chatgpt和xmind结合起来帮你制作精美的思维导图。 1.输出Markdown格式 2.xmind导入.md文件

自动驾驶技术:改变交通出行的未来

自动驾驶技术&#xff0c;这个让人充满期待的技术&#xff0c;正在改变我们的交通方式&#xff0c;带来一种全新的出行体验。它可以让汽车、无人机等交通工具像人类驾驶一样自主行驶&#xff0c;通过人工智能、视觉计算、雷达、监控装置和全球定位系统协同合作&#xff0c;实现…

Handler详解

跟Handler有关系的&#xff0c;包括Thread&#xff0c;Looper&#xff0c;Handler&#xff0c;MessageQueue Looper: 由于Looper是android包加入的类&#xff0c;而Thread是java包的类&#xff0c;所以&#xff0c;想要为Thread创建一个Looper&#xff0c;需要在线程内部调用…

ChatGPT能代替搜索引擎吗?ChatGPT和搜索引擎有什么区别?

ChatGPT和搜索引擎是两种在信息获取和交流中常用的工具&#xff0c;ChatGPT是一种基于人工智能技术的聊天机器人&#xff0c;而搜索引擎是一种在互联网上搜索信息的工具。尽管它们都是依托互联网与信息获取和交流有关&#xff0c;部分功能重合&#xff0c;但在很多方面存在着明…

名侦探番外——Arduino“炸弹”引爆摩天大楼

名侦探番外——Arduino“炸弹”引爆摩天大楼 硬件准备1.材料准备2.模块介绍 电路设计1.硬件接线 程序设计1.设计思路2.部分程序3.功能优化 总结 好久不见&#xff0c;童鞋们&#xff01;小编突然想到很久以前看的柯南剧场版——计时引爆摩天大楼的情景&#xff0c;对剧里的“炸…

TEXTure环境配置,跑通inference的demo

TEXTure 环境配置安装kaolin这个包,这里可能会遇到各种问题配置huggingface的访问令牌 运行Text Conditioned Texture Generation指令报错1报错2成功运行 查看结果查看贴图后的三维网格模型 环境配置 # 创建一个名为texture的环境 conda create -n texture python3.9 -y# 激活…

【算法——双指针】LeetCode 202 快乐数

题目描述&#xff1a; 思路&#xff1a;快慢指针 看到循环&#xff0c;我就想起了快慢指针的方法&#xff0c;从题目我们可以看出&#xff0c;我们需要模拟一个过程&#xff1a;不断用当前的数去生成下一个数&#xff0c;生成的规则就是将当前数的各位的平方累加&#xff1b; …

微信小程序(原生)搜索功能实现

一、效果图 二、代码 wxml <van-searchvalue"{{ keyword }}"shape"round"background"#000"placeholder"请输入关键词"use-action-slotbind:change"onChange"bind:search"onSearch"bind:clear"onClear&q…

Python——添加照片边框

原图&#xff1a; 添加边框后&#xff1a; 添加边框会读取照片的exif信息如时间、相机型号、品牌以及快门焦段等信息&#xff0c;将他们显示在下面的边框中。 获取当前py文件路径 import os #get path that py file located def Get_Currentpath():file_path os.path.abspa…

mysql主从复制搭建

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言MySQL复制过程分为三部&#xff1a; 一、准备工作二、配置>主库Master三、配置>从库SlaveSlave_IO_Running: YesSlave_SQL_Running: Yes 四、测试至此&am…

htmlCSS-----弹性布局案例展示

目录 前言 效果展示 ​编辑 代码 思路分析 前言 上一期我们学习了弹性布局&#xff0c;那么这一期我们用弹性布局来写一个小案例&#xff0c;下面看代码&#xff08;上一期链接html&CSS-----弹性布局_灰勒塔德的博客-CSDN博客&#xff09; 效果展示 代码 html代码&am…

BeanFactoryApplicationContext之间的关系

1**.BeanFactory与ApplicationContext之间的关系** &#xff08;1&#xff09;从继承关系上来看&#xff1a; ​ BeanFactory它是ApplicationContext 的父接口 &#xff08;2&#xff09;从功能上来看&#xff1a; ​ BeanFactory才是spring中的核心容器&#xff0c;而Appli…

使用AffNet和HardNet进行图像匹配

一、说明 我们有一个任务是找到与给定查询图像最匹配的图像。首先&#xff0c;我们在OpenCV中尝试了使用SIFT描述符和基于Flann的匹配器的经典图像匹配。结果是完全错误的。然后是词袋...最后&#xff0c;找到了AffNet和HardNet。 二、关于AffNet和HardNet 本文专门介绍如何进…

什么是浮动(float)?如何清除浮动?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 浮动&#xff08;Float&#xff09;和清除浮动⭐ 浮动的使用⭐ 清除浮动1. 空元素法&#xff08;Empty Element Method&#xff09;2. 使用 Clearfix Hack3. 使用 Overflow ⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发…

《Java-SE-第三十七章》之反射

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

【Vue-Router】嵌套路由

footer.vue <template><div><router-view></router-view><hr><h1>我是父路由</h1><div><router-link to"/user">Login</router-link><router-link to"/user/reg" style"margin-left…

Selenium 测试用例编写

编写Selenium测试用例就是模拟用户在浏览器上的一系列操作&#xff0c;通过脚本来完成自动化测试。 编写测试用例的优势&#xff1a; 开源&#xff0c;免费。 支持多种浏览器 IE&#xff0c;Firefox&#xff0c;Chrome&#xff0c;Safari。 支持多平台 Windows&#xff0c;Li…

【C语言】const修饰普通变量和指针

大家好&#xff0c;我是苏貝&#xff0c;本篇博客是系列博客每日一题的第一篇&#xff0c;本系列的题都不会太难&#xff0c;如果大家对这种系列的博客感兴趣的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 文章目录 一.const修饰普通变量二.const修饰指…

Spring事务控制

目录 1、什么是事务控制 2、编程式事务控制 2.1、简介 2.2、相关对象 2.2.1、PlatformTransactionManager 2.2.2、TransactionDefinition 2.2.2.1、事务隔离级别 2.2.2.2、事务传播行为 2.2.3、TransactionStatus 3、声明式事务控制 3.1、简介 3.2、区别 3.3、⭐作…