6边形网格地图,格子间的距离计算

这样一个6边形网格地图,有没有公式计算两个格子间的距离呢?
比如(0,0)走到(1,1)需要走几步,我们看图可以发现走两步就行了,但是有公式可以计算就更好了。
下面跟群里的小伙伴讨论之后的出的结果。
我们需要借助三维坐标

像这样的坐标体系,有x,y,z三个维度,x,y,z的和始终是0,我们称之为cube坐标,距离计算公式是
distance=(abs(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))/2
或者
diatance=max(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))
上面两个公式都能算出距离,至于具体原理我就不懂了,反正可以这么用
然后要做的就是把二维坐标转成三维坐标
其实很简单
我们原来的坐标是这样的
0,0 --- 1,0 --- 2,0 --- 3,0
--- 0,1 --- 1,1 --- 2,1 --- 2,2
0,2 --- 1,2 --- 2,2 --- 3,2
--- 0,3 --- 1,3 --- 2,3 --- 3,3
如果调整成这样
0,0 --- 1,0 --- 2,0 --- 3,0
--- 0,1 --- 1,1 --- 2,1 --- 3,1
--- --- 0,2 --- 1,2 --- 2,2 --- 3,2
--- --- --- 0,3 --- 1,3 --- 2,3 --- 3,3
这样二维中的x,y就对应了三维中的x,y。至于三维的z,因为x,y,z的和始终是0,只要z=-x-y就行了
调整过程其实就是x=x-int(y/2)
最终我们得到二维坐标转三维坐标的函数
func getCubePosFromPos(pos):
var x=pos.x-int(pos.y)/2
var y=pos.y
var z=-x-y
return Vector3(x,y,z)计算距离的函数
func posDistance(pos1,pos2):
var cubePos1=getCubePosFromPos(pos1)
var cubePos2=getCubePosFromPos(pos2)
var distance=(abs(cubePos1.x-cubePos2.x)+abs(cubePos1.y-cubePos2.y)+abs(cubePos1.z-cubePos2.z))/ 2
return distance把这个公式放到游戏中试验下

黑色小圆圈里的数字表示这个格子到主角的距离,计算很准确
顺便说下从三维坐标转回到二维坐标的算法
func getPosFromCubePos(cubePos):
var y=cubePos.y
var x=cubePos.x+int(y)/2
return Vector2(x,y)参考资料
https://www.redblobgames.com/grids/hexagons/
笔记:个人对于godot中yield的一些理解
研究了一下yield,做个笔记
- yield是关键字。
- yield会返回一个对象,类型是GDScriptFunctionStat
这个对象保存了中断时函数的状态,这个对象有个函数resume(),可以继续执行中断的函数。
这个对象运行完成会发出completed信号,有了这个信号就可以实现yield的嵌套。
extends Node #嵌套演示
func _ready():
funA()
func funA():
print("a_start")
yield(funB(),"completed")
print("a_end")
func funB():
print("b_start")
yield(get_tree().create_timer(2),"timeout")
print("b_end")结果是先输出a_start,b_start,等待2秒后输出b_end,a_end
- yield(target,signal)
目前猜测,由于yield返回的是一个GDScriptFunctionStat实例,该类继承reference,没有引用会被回收。
所以必须有个对象保持它的引用,由于信号没有保持它的引用,所以是target保持了它的引用。
target在emit_signal时会顺便检测有没有GDScriptFunctionStat的实例需要resume,如果有就resume它。
这就实现了yield等待信号的效果。
yield可以传递参数
extends Node
signal sig
func _ready():
var a=yield(self,"sig")
print(a)
func _process(delta):
if Input.is_action_just_pressed("ui_accept"):
emit_signal("sig",10)输出结果是10.
yield也可以用来传递参数,可以实现选择的效果。
更加灵活的yield方式,可以被打断或者做其他操作
extends Node
var funcState
var die=false
func _ready():
funcState=testFunc()
func testFunc():
print(1)
wait(2)
yield()
print(2)
wait(2)
yield()
print(3)
func wait(time):
get_tree().create_timer(time).connect("timeout",self,"onTimeOut")
func onTimeOut():
if !die:
funcState=funcState.resume() //死亡就不会继续运行,被打断了目前想到这些,yield用来做剧情应该是没问题的
做技能系统还有待研究
欢迎讨论
Godot的Shader入门+制作像素文字Shader
Shader在哪写?

如图,新建一个sprite,在它的Meterial属性里点击新建shaderMaerial,在shaderMateril里点击新建shader,就会弹出着色器面板,可以在里面写shader了。
Shader格式
第一行我们先写上:
shader_type canvas_item;
void fragment(){
}shader_type是shader的类型,有spatial,canvas_item和particles 三种。必须在第一行指定shader_type。
spatial: 用来渲染3D
canvas_item: 用来渲染2D
particles:用来渲染粒子
fragment函数里面就是我们要写处理逻辑的地方。
fragment()是shader的回调函数,
每一个像素点都会运行一次fragment函数。
每一个像素点都会运行一次fragment函数。
每一个像素点都会运行一次fragment函数。
重要的事说三遍。
当然你会发现语法和gdscript有些不同,语句结尾要加分号,函数要用大括号括起来。
详细的请参考官方文档。
Shader基本逻辑
全白效果
先从简单的开始
shader_type canvas_item;
void fragment(){
COLOR=vec4(1.0,1.0,1.0,1.0);
}
效果如图,整个sprite都变白了。
COLOR是shader内置属性,它表示像素点的颜色。
vec4 是shader的数据类型,4维向量,可以用来表示颜色。
vec4有四个属性,x,y,z,w。每个属性都是float类型(所以用1.0而不是1)。
vec4的四个属性也可以用r,g,b,a来表示,就是说vec4.x和vec4.r是完全一样的。
shader常用类型有float,vec2,vec3,vec4等。
现在我们来看COLOR=vec4(1.0,1.0,1.0,1.0);这句话,
vec4(1.0,1.0,1.0,1.0)表示rgba都是1.0的颜色,就是白色。
每个像素点都会运行一次这个语句,所以每个像素点的颜色都变成了白色。
剪影效果
我们先写上
shader_type canvas_item;
void fragment(){
COLOR=texture(TEXTURE,UV);
}
效果如图
你可能会想:咦?这不是和原来一样吗。
是的,确实和原来一样。这句话是什么意思呢?
UV 是shader内置属性,用来表示当前像素点的坐标。它是一个vec2。

每个像素点都有一个UV值,每个像素点的UV值不一样。
左上角的像素点UV值是(0.0,0.0),
右下角的像素点UV值是(1.0,1.0),
正中心的像素点UV值是(0.5,0.5)。
TEXTURE是shader的内置属性(反正这些全大写的都是内置属性)。它表示原始的纹理。
texture函数是shader的内置函数,传入纹理和uv值,可以返回纹理上uv坐标的颜色(vec4)。
我们来看:COLOR=texture(TEXTURE,UV);
就是每个像素获取自己原来的颜色,赋值给COLOR。当然不会有任何变化。
接下来我们写
shader_type canvas_item;
void fragment(){
COLOR=texture(TEXTURE,UV);
COLOR.rgb=vec3(1.0,1.0,1.0);
}效果如图,成功实现了剪影效果。

COLOR.rgb=xxx 这是shader中的语法,可以同时给r,g,b三个值赋值,非常方便。
你可能会说,为什么要写COLOR=texture(TEXTURE,UV);?
我不能直接写 COLOR.rgb=vec3(1.0,1.0,1.0);吗?
这是不行的,COLOR默认值其实是(1.0,1.0,1.0,1.0)。
只要调用了COLOR,COLOR就会变成(1.0,1.0,1.0,1.0)。
你可以试试这样
shader_type canvas_item;
void fragment(){
COLOR;
}如图,也能让图片变全白。

制作像素化文字shader
我这里用的字体是思源黑体Medium。新建一个label,字体大小设成12,我们看看默认的效果。

放大后:

为什么看上去这么不像素,主要是字体的抗锯齿,使它有半透明的部分。
所以要做的就是去掉半透明的部分。
学习了上面的知识,你应该知道怎么做了。
在label的Meterial属性里点击,新建ShaderMeterial,然后新建Shader
Shader代码如下
shader_type canvas_item;
void fragment(){
COLOR=texture(TEXTURE,UV);
if(COLOR.a<0.5){
COLOR.a=0.0;
}else{
COLOR.a=1.0;
}
}如果像素的alpha<0.5,就把alpha变成0。如果像素的alpha>=0.5,就把alpha变成1。
看看效果

放大后:

很好很像素。
当然临界值可以不是0.5,如果我们希望可以微调临界值该怎么办?
这时要声明变量,代码如下
shader_type canvas_item;
uniform float limit=0.5;//声明变量 limit,默认值是0.5
void fragment(){
COLOR=texture(TEXTURE,UV);
if(COLOR.a<limit){ //把0.5替换成limit
COLOR.a=0.0;
}else{
COLOR.a=1.0;
}
}用uniform指定的变量可以在外部修改,可以微调像素化的效果。
最后点击保存按钮把shader保存成文件,就可以重复使用了
更多关于shader的信息请参考官方文档:
http://docs.godotengine.org/zh_CN/latest/tutorials/shading/shading_language.html
Godot中锚点(anchor)和边界(margin)的用法
在godot中,Control类都有anchor和margin属性。

这两个属性是给ui自适配用的。
每个control都有四个边,上(top),下(bottom),左(left),右(right)
anchor(锚点):表示每条边的参照值。这个参照值是相对于父节点的百分比取值,一般会取0,0.5,1这三个值。
以anchor_left属性为例,
anchor_left=0表示当前节点的左边界以父节点的左边界为参照值,
archor_left=0.5表示当前节点的左边界以父节点的中心为参照值,
archor_left=1表示当前节点的左边界以父节点的右边界为参照值。
margin(边界):表示每条边和参照值得相对位置,这个值的就是像素距离。
举例来说明比较好理解,我新建了一个Panel,一个TextureRect


1.我希望TextureRect和Panel的边界始终保持50个像素

TextureRect的Anchor和Margin要这么设置

2.接下来新建个按钮,是它始终位于右上方,长度保持100,高度保持50

按钮的anchor和margin属性

3.接下来新建个开始游戏按钮,长100,宽50,x方向居中,y方向距离底面50的距离

开始游戏按钮的anchor和margin

顺便说一下布局其实就是一些常用的anchor和margin的组合
在Godot中制作杀戮尖塔的箭头
杀戮尖塔里面使用卡牌时的箭头是这样的:

箭头的形态非常符合贝塞尔曲线
PS中的钢笔工具就是用的贝塞尔曲线

如图,一条贝塞尔曲线需要用四个点来确定,一个起点,一个终点,加上两个控制点。
我们把四个点分别命名:起点(startPos),终点(endPos),控制点A(ctrlAPos),控制点B(ctrlBPos)
贝塞尔曲线的公式是:
position=startPos*(1-t)*(1-t)*(1-t)+3*ctrlAPos*t*(1-t)*(1-t)+3*ctrlBPos*t*t*(1-t)+endPos*t*t*t
四个点都确定后,公式里的t就是唯一的变量,t是指从起点到终点的百分比,取值是0-1。
比如0代表曲线起点,0.2代表曲线从起点开始20%的位置,0.5代表曲线中间位置,1代表曲线终点。
公式的计算结果position就是当前t值所对应的曲线上的点。
不过在游戏中我们是使用两个点来确定曲线的,卡牌所在位置是曲线的起点,鼠标所在位置是曲线的终点。
那么两个控制点就要根据起点和终点来进行计算。
杀戮尖塔里的曲线大致是这样:

我们可以大致的写出控制点的计算公式:
ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.2
ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2
ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.2
ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.2
当然这个计算公式可以自己微调,使曲线更符合自己想要的形态
理解了曲线的原理,现在开始在godot中实现。
我画了两个箭头,箭头1和箭头2,如图。


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

开始写脚本。首先我们的箭头有20节,我们需要在初始化的时候准备好。
之后更新箭头时重新排好每一节就能形成一条曲线。
extends Node2D
var list=[] #数组,用来保存20节小箭头
func _ready():
#生成19节尾巴小箭头,用箭头1的图片
for i in range(19):
var sprite=Sprite.new() #新建Sprite节点
add_child(sprite) #添加到场景里
list.append(sprite) #添加到数组里
sprite.texture=load("res://Sprites/箭头1.png") #把图片换成箭头1
sprite.scale=Vector2(1,1)*(0.2+float(i)/18*0.8) #改变缩放,根据杀戮尖塔,箭头是一节节越来越大的
sprite.offset=Vector2(-25,0) #由于我画的图片中心点在箭头中间,
#这里改变一下图片偏移,把图片中心点移动到箭头头部
#最后生成终点的箭头,用箭头2的图片
var sprite=Sprite.new()
add_child(sprite)
list.append(sprite)
sprite.texture=load("res://Sprites/箭头2.png")
sprite.offset=Vector2(-25,0)然后我们需要一个函数来设置箭头的起点和终点
func reset(startPos,endPos):
#根据传入的起点和终点来计算两个控制点
var ctrlAPos=Vector2()
var ctrlBPos=Vector2()
ctrlAPos.x=startPos.x+(startPos.x-endPos.x)*0.1 #这里我把参数做了微调,感觉这样更加符合杀戮尖塔的效果
ctrlAPos.y=endPos.y-(endPos.y-startPos.y)*0.2
ctrlBPos.y=endPos.y+(endPos.y-startPos.y)*0.3
ctrlBPos.x=startPos.x-(startPos.x-endPos.x)*0.3
#根据贝塞尔曲线重新设置所有小箭头的位置
for i in range(20):
var t=float(i)/19
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
list[i].position=pos
#虽然更改了箭头的位置,不过还需要重新计算箭头的方向
updateAngle() #重新计算所有箭头的方向接下来我们需要完成updateAngle()这个函数
思路是每个小箭头根据前一个箭头和自己的位置来计算角度
func updateAngle():
for i in range(20):
if i==0:
list[0].rotation_degrees=270 #第一个小箭头就让他固定朝上好了
else:
var current=list[i] #当前的小箭头
var last=list[i-1] #前一个小箭头
var lenVec=current.position-last.position #两个箭头连线的向量
var a=lenVec.angle() #计算这个向量的角度,这个angle()返回值是弧度
a=rad2deg(a) #弧度转成角度
current.rotation_degrees=a #更新小箭头的方向我们的贝塞尔箭头就做好了,外界只要调用reset方法就能更新箭头,
不需要用的时候可以用visible=false把它隐藏掉
我把它放到游戏中看看效果

非常完美!
Godot中如何用数字表示collision_mask
先用二进制写出来,1代表激活,0代表没激活,最右边代表第一层。然后转换成十进制。
举例说明:

二进制是1,转成十进制是1,collision_mask=1。

二进制是1101,转成十进制是13,collision_mask=13。

二进制1000001100001转成十进制是4193,collision_mask=4193。
其他类似的比如light_mask,collision_layer都是这种写法。
Godot中的分贝和音量换算
Godot中不管是音频播放器还是音频总线都是用分贝来表示音量大小的,如图


我们希望能用0-1来表示音量大小,如何转换呢?
我们用v表示音量大小(0-1),以下是转换公式
V转换成volume_db的公式:
volume_db=10*log(v)/log(10)
但是log的参数必须是大于0的,我们要做一下特殊判断,最终公式为:
If v<=0:
volume_db=-60
else:
volume_db=10*log(v)/log(10)
Volume_db只要不大于-60,系统就不会发出声音了。这个在设置里可以设定不发出声音的临界值,默认是-60,所以这里就让volume_db=-60。

一般我们会用全局变量来保存v的值,不需要用volume_db转v的公式,不过还是说一下
volume_db转换成v,公式是:
v=pow(10,(float(volume_db)/10))
注意里面的float(),这里我额外说一下Godot里计算的坑。
举例:
var a=1
print(a/10)
输出结果为0,因为a是整数,计算结果默认取整了。
如果加上float先把a变成浮点数:
Var a=1
print(float(a)/10)
输出结果就是0.1。
Godot使用Light2D实现遮罩效果
先来看看效果:

用鼠标拖动圆,数字和血手印只会显示在圆的范围内。
用light2D很简单能做出这个效果。
这是demo中用到的素材,如图

首先我们新建一个项目,导入素材,新建场景,新建sprite,把背景放进去

然后放入图片2333,这时候2333是正常显示的

在2333的属性面版Material这里选择新建CanvasItemMaterial

新建后点进去,设置CanvasItemMaterial,把LightMode改成Light Only

然后我们会发现2333已经看不到了,应为设置了LightOnly,2333只会在光照时显示,如图

接下来我们新建一个light2D,Texture设置为白色的圆


效果如图:

这时候还是显示白色的圆。我们更改一下光照的Mode为Mix,效果就出来了


不过仔细观察会发现,光照不只是照到了2333,也照到了背景的墙壁,和背景的墙壁混合产生了一个圆形的边缘。
如何才能让light2d只照到2333,而不照到背景呢?
答案是LightMask
选中2333,把LightMask设置成第二层,如图。

选中Light2D,把ItemCullMask设置成第二层

由于背景墙壁LightMask是默认的第一层,所以light2d只会照到2333.
如图,效果很不错

这时候我们就可以拖动light2d来观察了
现在我们来给他加上黑色边框
新建sprite,把素材黑圆边框放进去,把light2d拉到黑圆边框下面

把light2d的位置设为0,0

这样就完成了

源文件和素材我放到百度网盘了,有兴趣的可以自己做一下
https://pan.baidu.com/s/1s6AKTfCpgxiqrp4QP-oe0Q


