游戏开发论坛

 找回密码
 立即注册
搜索
查看: 4279|回复: 0

在Godot中制作杀戮尖塔的箭头

[复制链接]

8723

主题

8783

帖子

1万

积分

版主

Rank: 7Rank: 7Rank: 7

积分
11952
发表于 2018-10-9 10:22:24 | 显示全部楼层 |阅读模式
文/logoss

来源:indienova

原地址:https://www.indienova.com/indie-game-development/making-slay-the-spire-arrow-with-godot/

杀戮尖塔的卡牌箭头

杀戮尖塔里面使用卡牌时的箭头是这样的:

20181009101639.gif

贝塞尔曲线

箭头的形态非常符合贝塞尔曲线。

PS 中的钢笔工具就是用的贝塞尔曲线:

85458-1538136023.gif

如图,一条贝塞尔曲线需要用四个点来确定,一个起点,一个终点,加上两个控制点。

我们把四个点分别命名:起点(startPos),终点(endPos),控制点 A(ctrlAPos),控制点 B(ctrlBPos)

贝塞尔曲线的公式是:

  1. position = startPos*(1-t)*(1-t)*(1-t)
  2.     + 3*ctrlAPos*t*(1-t)*(1-t)
  3.     + 3*ctrlBPos*t*t*(1-t)
  4.     + endPos*t*t*t
复制代码

四个点都确定后,公式里的 t 就是唯一的变量,t 是指从起点到终点的百分比,取值是 0~1。

比如 0 代表曲线起点,0.2 代表曲线从起点开始 20% 的位置,0.5 代表曲线中间位置,1 代表曲线终点。

公式的计算结果 position 就是当前 t 值所对应的曲线上的点。

不过在游戏中我们是使用两个点来确定曲线的,卡牌所在位置是曲线的起点,鼠标所在位置是曲线的终点。

那么两个控制点就要根据起点和终点来进行计算。

杀戮尖塔里的曲线大致是这样:

85458-1538137298.png

我们可以大致的写出控制点的计算公式:

  1. ctrlAPos.x = startPos.x + (startPos.x - endPos.x) * 0.2
  2. ctrlAPos.y = endPos.y - (endPos.y - startPos.y) * 0.2

  3. ctrlBPos.x = startPos.x - (startPos.x - endPos.x) * 0.2
  4. ctrlBPos.y = endPos.y + (endPos.y - startPos.y) * 0.2
复制代码

当然这个计算公式可以自己微调,使曲线更符合自己想要的形态

Godot 中的实现

理解了曲线的原理,现在开始在 godot 中实现。

我画了两个箭头,箭头 1 和箭头 2,如图。

85458-1538137614.png

85458-1538137626.png

在 godot 中新建一个场景,新建 Node2D,命名为贝塞尔箭头。添加脚本。

85458-1538137997.png

开始写脚本。首先我们的箭头有20节,我们需要在初始化的时候准备好。

之后更新箭头时重新排好每一节就能形成一条曲线。

  1. extends Node2D
  2. var list=[] #数组,用来保存20节小箭头
  3. func _ready():
  4.     #生成19节尾巴小箭头,用箭头1的图片
  5.     for i in range(19):
  6.         var sprite=Sprite.new()    #新建 Sprite 节点
  7.         add_child(sprite)          #添加到场景里
  8.         list.append(sprite)        #添加到数组里
  9.         sprite.texture=load("res://Sprites/箭头1.png")  #把图片换成箭头1
  10.         sprite.scale=Vector2(1,1)*(0.2+float(i)/18*0.8) #改变缩放,根据杀戮尖塔,箭头是一节节越来越大的
  11.         sprite.offset=Vector2(-25,0)  #由于我画的图片中心点在箭头中间,
  12.                                       #这里改变一下图片偏移,把图片中心点移动到箭头头部
  13.     #最后生成终点的箭头,用箭头2的图片
  14.     var sprite=Sprite.new()   
  15.     add_child(sprite)
  16.     list.append(sprite)
  17.     sprite.texture=load("res://Sprites/箭头2.png")
  18.     sprite.offset=Vector2(-25,0)
复制代码

然后我们需要一个函数来设置箭头的起点和终点

  1. func reset(startPos,endPos):
  2.     #根据传入的起点和终点来计算两个控制点
  3.     var ctrlAPos=Vector2()
  4.     var ctrlBPos=Vector2()
  5.     ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.1 #这里我把参数做了微调,感觉这样更加符合杀戮尖塔的效果
  6.     ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2
  7.     ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.3
  8.     ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.3
  9.    #根据贝塞尔曲线重新设置所有小箭头的位置
  10.     for i in range(20):
  11.         var t=float(i)/19
  12.         var pos=startPos*(1-t)*(1-t)*(1-t)+3*ctrlAPos*t*(1-t)*(1-t)+3*ctrlBPos*t*t*(1-t)+endPos*t*t*t
  13.         list[i].position=pos
  14.    #虽然更改了箭头的位置,不过还需要重新计算箭头的方向   
  15.    updateAngle()   #重新计算所有箭头的方向
复制代码

接下来我们需要完成 updateAngle()这个函数

思路是每个小箭头根据前一个箭头和自己的位置来计算角度

  1. func updateAngle():
  2.     for i in range(20):
  3.         if i==0:
  4.             list[0].rotation_degrees=270    #第一个小箭头就让他固定朝上好了
  5.         else:
  6.             var current=list[i]    #当前的小箭头
  7.             var last=list[i-1]     #前一个小箭头
  8.             var lenVec=current.position-last.position      #两个箭头连线的向量
  9.             var a=lenVec.angle()       #计算这个向量的角度,这个 angle()返回值是弧度
  10.             a=rad2deg(a)               #弧度转成角度
  11.            
  12.             current.rotation_degrees=a #更新小箭头的方向
复制代码

效果

我们的贝塞尔箭头就做好了,外界只要调用 reset 方法就能更新箭头,

不需要用的时候可以用 visible = false 把它隐藏掉

我把它放到游戏中看看效果

85458-1538139752.gif

非常完美!

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2024-4-20 02:32

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表