当前位置: 首页 > news >正文

Godot C# 自定义摄像机

前言

        说起来,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好像有)。那没办法了,只能自己做去罢。结果这一做不得了,又发现很多好玩的东西。留到下一章单独细说吧。

相关文章:

  • 企业级-pdf预览-前后端
  • Qt 常用数据类型
  • 【github remote: Access denied等问题的通用解决方案】
  • EHS管理系统设备安全设施安全监控模块
  • 目标检测-数据集
  • Win11家庭版找不到gpedit.msc文件怎么办
  • Apache APISIX学习(1):介绍、docker启动
  • docker - 迁移和备份
  • DasViewer浏览器中的格式转换,与网格大师的转换有什么区别?
  • 如何将 Apifox 的自动化测试与 Jenkins 集成?
  • iOS开发工程师面试
  • 使用 Llama 3.1 和 Qdrant 构建多语言医疗保健聊天机器人的步骤
  • 【Hadoop】一、Hadoop入门:基础配置、集群配置、常用脚本
  • 爬虫逆向学习(九):记录一个集cookie、请求参数、请求体、响应文本加密的站点反爬
  • Cpp内存管理(7)
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • const let
  • css的样式优先级
  • js继承的实现方法
  • vue-cli在webpack的配置文件探究
  • 高性能JavaScript阅读简记(三)
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 坑!为什么View.startAnimation不起作用?
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 七牛云假注销小指南
  • 如何解决微信端直接跳WAP端
  • 探索 JS 中的模块化
  • 新版博客前端前瞻
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • ​业务双活的数据切换思路设计(下)
  • #### golang中【堆】的使用及底层 ####
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (zt)最盛行的警世狂言(爆笑)
  • (附源码)spring boot智能服药提醒app 毕业设计 102151
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (转)Android学习笔记 --- android任务栈和启动模式
  • .[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞
  • .net Stream篇(六)
  • .NET 中创建支持集合初始化器的类型
  • .Net 中的反射(动态创建类型实例) - Part.4(转自http://www.tracefact.net/CLR-and-Framework/Reflection-Part4.aspx)...
  • .NET技术成长路线架构图
  • .NET下ASPX编程的几个小问题
  • @Controller和@RestController的区别?
  • @Validated和@Valid校验参数区别
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [ C++ ] STL---string类的使用指南
  • [ vulhub漏洞复现篇 ] AppWeb认证绕过漏洞(CVE-2018-8715)
  • [20171113]修改表结构删除列相关问题4.txt
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [ACM] hdu 1201 18岁生日
  • [AI aider] 打造终端AI搭档:Aider让编程更智能更有趣!
  • [Angular 基础] - 表单:响应式表单