Godot-StartUP

创建于:2018-07-28

创建人: Justus

44 信息 150 成员
讨论基于Godot以及Unity引擎的游戏开发经验,理论和最佳实践。共享一些通用思路以启发另一种生产工具中的实践。独立开发群QQ: 122017359
logoss@Godot-StartUP 的内容(查看所有内容
6边形网格地图,格子间的距离计算
logoss 2018-11-01

Image title

这样一个6边形网格地图,有没有公式计算两个格子间的距离呢?

比如(0,0)走到(1,1)需要走几步,我们看图可以发现走两步就行了,但是有公式可以计算就更好了。

下面跟群里的小伙伴讨论之后的出的结果。

我们需要借助三维坐标

Image title

像这样的坐标体系,有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

把这个公式放到游戏中试验下

Image title

黑色小圆圈里的数字表示这个格子到主角的距离,计算很准确


顺便说下从三维坐标转回到二维坐标的算法

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的一些理解
logoss 2018-10-25

研究了一下yield,做个笔记

这个对象保存了中断时函数的状态,这个对象有个函数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
logoss 2018-10-17

Shader在哪写?

Image title

如图,新建一个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);
}

Image title

效果如图,整个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);
}

Image title

效果如图

你可能会想:咦?这不是和原来一样吗。

是的,确实和原来一样。这句话是什么意思呢?

UV 是shader内置属性,用来表示当前像素点的坐标。它是一个vec2。

Image title

每个像素点都有一个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);
}

效果如图,成功实现了剪影效果。

Image title

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;
}

如图,也能让图片变全白。

Image title

制作像素化文字shader

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

Image title

放大后:

Image title

为什么看上去这么不像素,主要是字体的抗锯齿,使它有半透明的部分。

所以要做的就是去掉半透明的部分。

学习了上面的知识,你应该知道怎么做了。

在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。

看看效果

Image title

放大后:

Image title

很好很像素。

当然临界值可以不是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指定的变量可以在外部修改,可以微调像素化的效果。Image title

最后点击保存按钮把shader保存成文件,就可以重复使用了Image title

更多关于shader的信息请参考官方文档:

http://docs.godotengine.org/zh_CN/latest/tutorials/shading/shading_language.html

(转发自:原日志地址
Godot中锚点(anchor)和边界(margin)的用法
logoss 2018-10-12

在godot中,Control类都有anchor和margin属性。

Image title

这两个属性是给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

Image title

Image title

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

Image title

TextureRect的Anchor和Margin要这么设置

Image title

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

Image title

按钮的anchor和margin属性

Image title

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

Image title

开始游戏按钮的anchor和margin

Image title


顺便说一下布局其实就是一些常用的anchor和margin的组合
Image title

(转发自:原日志地址
在Godot中制作杀戮尖塔的箭头
logoss 2018-09-28

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

Image title

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

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

Image title

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

我们把四个点分别命名:起点(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值所对应的曲线上的点。


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

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

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

Image title

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

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,如图。

Image titleImage title

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

Image title

开始写脚本。首先我们的箭头有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把它隐藏掉

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

Image title

非常完美!





(转发自:原日志地址
Godot中如何用数字表示collision_mask
logoss 2018-08-17

先用二进制写出来,1代表激活,0代表没激活,最右边代表第一层。然后转换成十进制。

举例说明:

Image title

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


Image title

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


Image title

二进制1000001100001转成十进制是4193,collision_mask=4193。

其他类似的比如light_mask,collision_layer都是这种写法。

(转发自:原日志地址
Godot中的分贝和音量换算
logoss 2018-08-16

Godot中不管是音频播放器还是音频总线都是用分贝来表示音量大小的,如图

Image title

Image title

我们希望能用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。

Image title

一般我们会用全局变量来保存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实现遮罩效果
logoss 2018-08-12

先来看看效果:

Image title

用鼠标拖动圆,数字和血手印只会显示在圆的范围内。

用light2D很简单能做出这个效果。

这是demo中用到的素材,如图

Image title

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

Image title

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

Image title

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

Image title

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

Image title

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

Image title

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

Image title

Image title

效果如图:

Image title

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

Image title

Image title

不过仔细观察会发现,光照不只是照到了2333,也照到了背景的墙壁,和背景的墙壁混合产生了一个圆形的边缘。

如何才能让light2d只照到2333,而不照到背景呢?

答案是LightMask

选中2333,把LightMask设置成第二层,如图。

Image title

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

Image title

由于背景墙壁LightMask是默认的第一层,所以light2d只会照到2333.

如图,效果很不错

Image title

这时候我们就可以拖动light2d来观察了

现在我们来给他加上黑色边框

新建sprite,把素材黑圆边框放进去,把light2d拉到黑圆边框下面

Image title

把light2d的位置设为0,0

Image title

这样就完成了

Image title

源文件和素材我放到百度网盘了,有兴趣的可以自己做一下

https://pan.baidu.com/s/1s6AKTfCpgxiqrp4QP-oe0Q

(转发自:原日志地址

加入 indienova

  • 建立个人/工作室档案
  • 建立开发中的游戏档案
  • 关注个人/工作室动态
  • 寻找合作伙伴共同开发
  • 寻求线上发行
  • 更多服务……
登录/注册