Godot C# 自定义摄像机

news2024/11/17 9:48:18

前言

        说起来,Unity的社区环境跟插件支持确实要比Godot好很多,比如我们Unity最喜欢的Cinemachine插件,只需要动动手指就能轻松实现很多高级的摄像机动效。

        所以一转到Godot就有一种力不从心的感觉,于是既然动不了手指我们就动手。自己做一个想要的摄像机。

        Godot版本:4.3 mono

思路

        其实没什么好说的,一开始只是想做一个能跟着某个节点移动的摄像机,至于为什么不直接把摄像机作为被跟随节点的子节点呢?因为Godot中的摄像机是按照节点树层次找到并展示最近的一个父级Viewport的,我觉得如果一个场景中有多个摄像机的情况,再加上被跟随节点自带的摄像机可能就不好管理了。还有用Unity习惯了,就这样考虑了。

        人都是贪得无厌的,一开始还是只想做一个跟随就好了,结果为了满足自己的奇葩需求,索性就加了一些其他功能。

源码   

using System.Collections;
using Godot;

namespace GoDogKit
{
    /// <summary>
    /// A highly customizable camera that automatically follows a target.
    /// </summary>
    public partial class AutoCamera2D : Camera2D
    {
        /// <summary>
        /// The target to follow.
        /// </summary>
        [Export] public Node2D FollowTarget { get; set; } = null;
        /// <summary>
        /// Defines the maximum distance from the target to follow. Does not effect to the predict behaviour.
        /// </summary>
        [Export] public float FollowClamp { get; set; } = 1.0f;

        /// <summary>
        /// Defines the camera's behaviour when following the target.
        /// </summary>
        public enum BehaviourType
        {
            /// <summary>
            /// Follows the target normally. Results in global position copying.
            /// </summary>
            Normal,
            /// <summary>
            /// Smoothly follows the target in a given duration. Results in global position interpolation.
            /// </summary>            
            Inching,
            /// <summary>
            /// Follow the target with a constant speed. It can be faster or slower than the target's speed.
            /// If the follow speed equals or exceeds the target's speed, results just like the Normal behaviour.
            /// If the follow speed is slower than the target's speed, the camera will
            /// be clamped within a given distance from the target aka max distance.
            /// </summary>            
            Slow,
            /// <summary>
            /// Follow the target with predictive behaviour.
            /// It predicts the target's movement based on its last position.
            /// And moves the camera towards the predicted position which 
            /// determined by predict distance with a constant speed.
            /// </summary>
            Predict
        }
        [Export] public BehaviourType Behaviour { get; set; } = BehaviourType.Normal;

        [ExportGroup("Inching Properties")]
        [Export]
        public float InchingDuration
        {
            get => m_inchingDuration;
            set => m_inchingDuration = value;
        }
        private float m_inchingDuration = 1.0f;
        private float m_inchingTimer = 0.0f;

        [ExportGroup("Slow Properties")]
        [Export] public float SlowFollowSpeed { get; set; } = 100.0f;
        [Export] public float SlowFollowMaxDistance { get; set; } = 100.0f;

        [ExportGroup("Predict Properties")]
        [Export] public float PredictFollowSpeed { get; set; } = 100.0f;
        [Export] public float PredictDistance { get; set; } = 100.0f;

        private Vector2 m_targetLastPos = Vector2.Zero;

        public override void _Ready()
        {
            m_inchingTimer = m_inchingDuration;
            m_targetLastPos = Vector2.Zero;
        }

        private void NormalFollow(double delta)
        {
            GlobalPosition = FollowTarget.GlobalPosition;
        }

        private void InchingFollow(double delta)
        {
            float distance = GlobalPosition.DistanceTo(FollowTarget.GlobalPosition);

            // If the target is too close, stop inching.
            if (distance < FollowClamp)
            {
                m_inchingTimer = m_inchingDuration;
                return;
            }

            m_inchingTimer -= (float)delta;

            // If the inching timer has reached 0, reset it and start inching again.
            float rate = m_inchingTimer <= 0.0f ? 1.0f : 1.0f - m_inchingTimer / m_inchingDuration;

            var _x = Mathf.Lerp(GlobalPosition.X, FollowTarget.GlobalPosition.X, rate);
            var _y = Mathf.Lerp(GlobalPosition.Y, FollowTarget.GlobalPosition.Y, rate);

            GlobalPosition = new Vector2(_x, _y);
        }

        private void SlowFollow(double delta)
        {
            float distance = GlobalPosition.DistanceTo(FollowTarget.GlobalPosition);

            // If the target is too close, stop following.
            if (distance < FollowClamp)
            {
                return;
            }

            // If the target is too far, move it to max distance position.
            if (distance > SlowFollowMaxDistance)
            {
                Vector2 distanceVec = (FollowTarget.GlobalPosition - GlobalPosition).Normalized() * SlowFollowMaxDistance;
                GlobalPosition = FollowTarget.GlobalPosition - distanceVec;
                return;
            }

            var _x = Mathf.MoveToward(GlobalPosition.X, FollowTarget.GlobalPosition.X, (float)delta * SlowFollowSpeed);
            var _y = Mathf.MoveToward(GlobalPosition.Y, FollowTarget.GlobalPosition.Y, (float)delta * SlowFollowSpeed);

            GlobalPosition = new Vector2(_x, _y);
        }

        private void PredictFollow(double delta)
        {
            // Predict the direction of the target based on its last position.
            Vector2 predictedDir = (FollowTarget.GlobalPosition - m_targetLastPos).Normalized();

            Vector2 predictedPos = FollowTarget.GlobalPosition + predictedDir * PredictDistance;

            var _x = Mathf.MoveToward(GlobalPosition.X, predictedPos.X, (float)delta * PredictFollowSpeed);
            var _y = Mathf.MoveToward(GlobalPosition.Y, predictedPos.Y, (float)delta * PredictFollowSpeed);

            GlobalPosition = new Vector2(_x, _y);

            // Record the last position of the target for the next prediction.
            m_targetLastPos = FollowTarget.GlobalPosition;
        }       

        public override void _PhysicsProcess(double delta)
        {
            // If there is no target, do nothing.
            if (FollowTarget == null) return;

            switch (Behaviour)
            {
                case BehaviourType.Normal: NormalFollow(delta); break;
                case BehaviourType.Inching: InchingFollow(delta); break;
                case BehaviourType.Slow: SlowFollow(delta); break;
                case BehaviourType.Predict: PredictFollow(delta); break;
            }
        }
    }
}

           其实结构还是非常简单明了的(因为我也写不出很复杂的东西)。通过预设值决定摄像机的具体行为逻辑,就是这么简单。

        哦对了,Godot的2D和3D的区别跟Unity不一样,Unity的2D是伪2D,而Godot的2D是真2D,

所以2D跟3D之间的沟壑可能比Unity大。所以我先做了2D的相机。

        这个的操作方式应该跟Unity的差不多,就是调整数值还有选模式。主要这些模式都是我硬编的,其实我也不知道应该怎么为这些模式命名:

        1.Normal,普通行为,就一直跟着,其实就是复制位置。

        2.Inching,我管它叫缓动,从代码可以看出,这玩意跟时间有关,设计之初是想实现“在规定时间结束时,镜头恰好到达物体位置”,结构因为插值插的太快了,所以只能看出一点点效果,所以之后应该会大改或者直接砍掉;

        3.Slow,慢跟随。其实也可以快,通过控制跟随速度营造出“镜头和物体相对运动的效果”。

实际上镜头跟随太慢会被限制在一个距离内,从而避免物体跑太快了以至于跑出镜头外。

            // If the target is too far, move it to max distance position.

            if (distance > SlowFollowMaxDistance)

            {

                Vector2 distanceVec = (FollowTarget.GlobalPosition -                 GlobalPosition).Normalized() * SlowFollowMaxDistance;

                GlobalPosition = FollowTarget.GlobalPosition - distanceVec;

                return;

            }

        限制手段就是这个:当相对距离超过限定距离时,根据等式关系减去偏移量。

        为什么要单独拿出来记录呢?因为我之前写那个Untiy卡牌拖拽模型的时候,就是遇到了这种“锁定偏移量”的类似问题,当时还强调了一下,结果现在做开发的时候又又又错了。

        4.Predict,预测跟随。这个比较有意思,我忘了Cinemachine有没有,印象中好像就是没有的。因为感觉很多游戏都会有这么一个“根据玩家移动方向适当移动镜头”的操作,那么我也尽量用自己的手段实现:很简单,根据上下帧得出运动方向的预测值,然后朝那个方向运动预设的一段距离。

        然后其实没什么了,我记得Cinemachine可以设置帧处理方式,比如Update和FixedUpdate,但是在这里我就索性扔到物理帧处理中了。

        所谓的什么模式,只是打开一个DIY思路,后面有什么需求再自己修改就好了。

结语

        这里不得不提一嘴,Godot开发插件的方式真的极其简单,基本上直接把源码拿进去就行,所以我索性就把学习开发过程中造的轮子搞成一堆插件扔在Github上了,这几天Unity转Godot就一直在更新:

MOWEIII/GoDogKit: A Plugin kit used by Godot which personally used and maybe continue to be update. (github.com)icon-default.png?t=O83Ahttps://github.com/MOWEIII/GoDogKit

        有需要的同志可以看看,虽然我的水平很低就是了。

摄像机震动???

        我在Unity开发中曾做个一个相机震动的效果,就很简单的在一个圆形范围内随机点,赋值给摄像机位置,只要频率够快,就能模拟出震动效果。

        虽然逻辑简单,但处理起来还要考虑很多东西,如果用Timer的方式(就是声明计时用的一堆变量)就需要很复杂的启动逻辑和变量管理。幸好Unity为我们提供了协程,我们可以轻松实现延迟和计时等等。

        那么问题来了,Godot C# 也没有协程啊(GDScript好像有)。那没办法了,只能自己做去罢。结果这一做不得了,又发现很多好玩的东西。留到下一章单独细说吧。

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

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

相关文章

凤凰模拟器V6中无人机如何设置“有头模式”

凤凰模拟器是一款专为航模新手设计的飞行模拟器&#xff0c;它能够模拟大疆无人机、各种穿越机、固定翼等多种飞行器&#xff0c;提供逼真的飞行体验。该软件的操作简单易懂&#xff0c;适合新手练习使用。 一般来说&#xff0c;打开凤凰模拟器&#xff0c;选择好机型&#xf…

快速上手 Hugging Face Transformers:完整模型微调训练步骤全攻略

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;模型微调&#xff08;Fine-Tuning&#xff09;是提升预训练模型在特定任务上表现的关键步骤。本文将详细介绍如何使用 Hugging Face Transformers 库进行模型微调训练&#xff0c;涵盖数据集下载、数据预处理、训练配…

Python发送邮件教程:如何实现自动化发信?

Python发送邮件有哪些方法&#xff1f;如何利用python发送邮件&#xff1f; 无论是工作汇报、客户通知还是个人提醒&#xff0c;邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件&#xff0c;实现自动化发信&#x…

Mysql 删除表的所有数据

在 MySQL 中&#xff0c;如果你想要删除一个表中的所有数据&#xff0c;可以使用 TRUNCATE TABLE 命令或者 DELETE 语句。下面是两种方法的对比以及如何使用它们&#xff1a; 使用 TRUNCATE TABLE TRUNCATE TABLE 是一个非常快速的方法来删除表中的所有记录&#xff0c;并且它…

我的领域-关怀三次元成长的二次元虚拟陪伴 | OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面

【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面 废话几句废话不多说&#xff0c;直接上源码目录结构init.pysimulator.pysimple_simulator_app.pyvehicle_config.json 废话几句 自动驾驶开发离不开仿真软件成品仿真软件种类多https://zhuanlan.zhihu.com/p/3…

介绍篇| 爬虫工具介绍

什么是网络爬虫 网络爬虫工具本质上是自动化从网站提取数据的软硬件或服务。它简化了网络爬虫&#xff0c;使信息收集变得更加容易。如今是数据和智能化时代, 如何快速、自动化获取数据, 成了个人或者企业进入智能化时代的第一步. 选择最佳网络爬虫工具时的关键因素 在选择最…

Apache DolphinScheduler-1.3.9源码分析(一)

引言 随着大数据的发展&#xff0c;任务调度系统成为了数据处理和管理中至关重要的部分。Apache DolphinScheduler 是一款优秀的开源分布式工作流调度平台&#xff0c;在大数据场景中得到广泛应用。 在本文中&#xff0c;我们将对 Apache DolphinScheduler 1.3.9 版本的源码进…

[uni-app]小兔鲜-02项目首页

轮播图 轮播图组件需要在首页和分类页使用, 封装成通用组件 准备轮播图组件 <script setup lang"ts"> import type { BannerItem } from /types/home import { ref } from vue // 父组件的数据 defineProps<{list: BannerItem[] }>()// 高亮下标 const…

2000-2022年上市公司人工智能词频统计(年报词频统计)/上市公司人工智能水平

2000-2022年上市公司人工智能词频统计&#xff08;年报词频统计&#xff09;/上市公司人工智能水平 1、时间&#xff1a;2000-2022年 2、来源&#xff1a;上市公司年报 3、范围&#xff1a;A股上市公司 4、指标&#xff1a;股票代码、股票简称、年报标题、年份、行业名称、…

火车票有电子发票吗?没纸质火车票怎么报销?

火车票有电子发票吗&#xff1f; 火车票、高铁票目前没有电子发票&#xff0c;但是现在已经实行电子客票&#xff0c;车票即购票证件&#xff0c;乘车时&#xff0c;只需购票证件原件&#xff08;如身份证、护照、临时身份证等&#xff09;即可乘车。 没纸质火车票怎么报销&am…

【视频讲解】非参数重采样bootstrap逻辑回归Logistic应用及模型差异Python实现

全文链接&#xff1a;https://tecdat.cn/?p37759 分析师&#xff1a;Anting Li 本文将深入探讨逻辑回归在心脏病预测中的应用与优化。通过对加州大学欧文分校提供的心脏病数据集进行分析&#xff0c;我们将揭示逻辑回归模型的原理、实现过程以及其在实际应用中的优势和不足…

【JS】图片裁剪上传

前言 流程如下&#xff1a;本地预览 > 裁剪 > 上传 实现 1. 本地预览 将数据读为 dataurl 赋值给 img 标签的 src <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" con…

近两年ATECLOUD都更新过哪些功能模块?

ATECLOUD作为一款智能化测试平台&#xff0c;不仅可以满足开关电源自动测试的需求&#xff0c;还可以基于平台搭建电源芯片、射频组件以及定制化测试方案&#xff0c;为了满足各类方案的测试需求&#xff0c;ATECLOUD平台也在不断功能更新迭代中&#xff0c;从2018年诞生以来&a…

Java抽象类与接口详解

目录 &#x1f54a;抽象类&#x1f4da;1.概念&#x1f4da;2.语法&#x1f4da;3.特性&#x1f4da;4.作用 &#x1f54a;接口&#x1f334;1.概念引入&#x1f334;2.语法规则&#x1f334;3.特性&#x1f334;4.使用&#x1f33b;5.实现多个接口&#x1f33b;6.接口间的继承…

[数据集][目标检测]手机识别检测数据集VOC+YOLO格式9997张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;9997 标注数量(xml文件个数)&#xff1a;9997 标注数量(txt文件个数)&#xff1a;9997 标注…

祝贺!这些学校新增测绘、遥感、地理、城乡规划硕博学位点!

国务院学位委员会办公室近日公示2024年新增博士硕士学位授权审核专家核查及评议结果&#xff0c;其中涉及测绘科学与技术、遥感科学与技术、地理学、城乡规划、地理学、资源与环境专业院校名单如下&#xff1a; 新增硕士学位授权点审核结果 测绘科学与技术 南京林业大学 西南…

【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题

前言&#xff1a; &#x1f308;上期博客&#xff1a;【后端开发】JavaEE初阶—线程安全问题与加锁原理&#xff08;超详解&#xff09;-CSDN博客 &#x1f525;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 ⭐️小编会在后端开发的学习中不断更新~~~ &#…

进程的那些事--进程控制

目录 前言 一、创建进程 二、退出进程 void exit (int retval) 三、进程等待 四、进程替换 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 前面我们认识了进程&#xff0c;现在让我们认识几个进程的接口 提示&#xff1a;以下是本篇文章正文内容…