上一讲完成了基本项目的创建,这一讲会带着大家快速的实现角色之间的交互,Godot中角色之间的交互使用信号来完成。
一、角色之间的简单交互
按照上一篇文章创建一个新的项目,添加assets和scene两个文件夹,在assets中加入一些图片资源。
首先创建一个新的2D场景,在2D场景中添加Sprite和一个Button。
在Sprite中添加相应的代码,让角色开始运动
extends Sprite
var flag = false
func _process(delta):
if flag :
rotation += PI/2 * delta
设置了一个flag来控制角色的运动,之后需要为按钮添加相应的信号
添加成功之后有如下一些显示
以上代码代表着只要按下按钮,之后就发_on_Button_pressed()
,这个信号是传递给这个节点的Sprite,在这个信号里面修改了flag的值。此时只要点击按钮,就可以角色就开始运动了。也可以直接使用set_process方法来进行处理,此时就可以不需要flag标签了。
extends Sprite
#var flag = false
func _process(delta):
# if flag :
rotation += PI/2 * delta
func _on_Button_pressed():
# print("ok")
# flag = !flag
set_process(!is_processing())
二、实现角色之间的复杂交互
下面将会实现一个Tank和齿轮之间的交互,通过这个例子让我一起来体会一下信号的特点。
-
Saw角色的创建
为了进行碰撞,角色就不能仅仅只是Sprite了,但是依然需要Sprite,通过Sprite可以插入Texture(图片素材)。按照流程添加角色的Scene(名称是Saw),此时要添加碰撞检测,需要选择Area2D。这个是带碰撞检测节点需要添加Sprite和CollistionShape2D并且为后者添加碰撞区域。
为Saw添加saw.gd的脚本代码
extends Area2D
#export导出的变量可以在配置项中设置
export var max_dis = 100 #最大的距离,默认是100
export var move_dir = "h" #转动的方向是水平 v是垂直
var dis = 0 #记录移动的距离
var dir = 1 #移动的方向
func _process(delta):
dis += dir * 1
if dis >= max_dis*delta or dis <= -1 * max_dis*delta:
dir *= -1
if move_dir == "h":
position.x += 200 * dir * delta
if move_dir == "v":
position.y += 200 * dir * delta
rotation += -PI * delta #移动的过程中不断进行旋转。
以上代码中创建了两个变量max_dis和move_dir,一个用来存储最大的移动距离,另一个用来确定移动方向,是水平还是垂直(在后续讲解完成向量之后,会有更好的控制方法)。这两个变量都设置为export,这表示可以在Saw的属性栏进行值的修改。代码的基本思路是,让Saw不断反复移动,如果移动距离大于max_dis或者小于-max_dis,就变向,相当于修改dir的值,在移动端过程中不断的旋转。
- 创建一个可以上下移动的角色
按照同样一种方式创建一个Area2D,之后添加两个节点(Sprite和CollisionShape2D),Sprite为了加入材质,CollisionShape2D加入碰撞检测区域
var speed = 200
func _process(delta):
if Input.is_action_pressed("ui_up"):
position.y -= speed*delta
if Input.is_action_pressed("ui_down"):
position.y += speed*delta
if Input.is_action_pressed("ui_left"):
position.x -= speed*delta
if Input.is_action_pressed("ui_right"):
position.x += speed*delta
- 将角色添加到舞台
启动游戏之后,Saw开始运行,角色也开始根据键盘控制进行移动,但是角色现在和电锯接触之后没有任何反应,下一步就要添加碰撞检测。
- 添加碰撞检测
只要是Area2D的角色都可以进行碰撞检测,碰撞检测需要使用到信号(Signal),具体的操作流程如下所示:
area_entered就是我们需要使用的信号,接下来,实现的效果是当tank碰到saw之后,有个生命,生命会减少。所以首先为Tank增加一个life的变量。
var life = 100
此时如果把信号添加在其中的一个Saw上会有一些问题,如图所示
运行发现,只有第一个Saw节点能够出发信号。其他节点都没有办法触发。这样就需要为每个节点都添加。所以信号必须在tank上添加,让Saw节点来接收信号,此时就可以正常运行,但是代码却是在Saw中编写,这样就需要在Saw中获得Tank节点。
具体的代码如下所示
func _on_Tank_area_entered(area):
# print("hit")
var tankNode = get_node("../Tank")
# print(tankNode.life)
if tankNode.life >= 0:
tankNode.life -= 10
print(tankNode.life)
需要强调一下,此处获取节点使用了get_node(../Tank)
,../
表示的是上一级路径,这种方式是非常不合理的一种获取节点方法,在实际的应用中都不会使用,但是由于本章节的内容只是让大家概览一下流程,所以先使用这种不合理的方案,在后续的内容中会更新更合理的处理方案。
接下来,坦克受伤的代码客观来说应该在tank中编写更合理一些,所以可以在坦克中创建一个方法,injured方法。
#Tank.gd中添加代码
func injured(lost_life):
life -= lost_life
if life <= 0:
life = 0
print(life)
改造Saw中的代码
func _on_Tank_area_entered(area):
var tankNode = get_node("../Tank")
# print(tankNode.life)
# if tankNode.life >= 0:
# tankNode.life -= 10
# print(tankNode.life)
tankNode.injured(10)#让tank损失10点生命
接着来实现Tank受伤之后,有一些反馈,改变一下颜色。首先为tank节点添加一个动画。
点击 Add Track添加一个轨道,选择Property Track,选择Sprite节点,之后添加modulate,通过这个可以设置Sprite的基本属性。之后将帧调整为0.4,意味着可以设置4帧的动画。
modulate可以调整节点的颜色和透明度,之后添加五个关键帧,分别在0.1和0.3部分设置值即可。
播放之后会发现tank已经开始闪动了,之后在tank的受伤的代码中添加这个播放效果即可。
func injured(lost_life):
life -= lost_life
if life <= 0:
life = 0
# print(life)
get_node("AnimationPlayer").play("injured")
最后添加一个血条,血条需要使用TexureProcess节点,所以先创建一个新的lifebar的scene,之后在assets中添加血条的图片
下一步就是当收到伤害的时候通知血条减少值即可,此时需要创建一个自定义的信号来进行处理。
signal life_change(final_life)
在Main场景中,选中Tank,之后会发现多了一个信号life_change,之后将Lifebar拖入到主场景中,这个信号连接到Lifebar。连接Lifebar之前需要为Lifebar增加相应的gdscript代码
extends TextureProgress
func _on_Tank_life_change(final_life):
value = final_life
最后一步就是在Tank中发信号
func injured(lost_life):
life -= lost_life
if life <= 0:
life = 0
# print(life)
get_node("AnimationPlayer").play("injured")
emit_signal("life_change",life)#发信号life_change,传入最终的值
实例中,各个节点的代码
#Tank
extends Area2D
var speed = 200
var life = 100
signal life_change(final_life)
func _ready():
position.x = 500
position.y = 200
func _process(delta):
if Input.is_action_pressed("ui_left"):
position.x -= speed * delta
if Input.is_action_pressed("ui_right"):
position.x += speed * delta
if Input.is_action_pressed("ui_up"):
position.y -= speed * delta
if Input.is_action_pressed("ui_down"):
position.y += speed * delta
func injured(lost_life):
life -= lost_life
if life <= 0:
life = 0
# print(life)
get_node("AnimationPlayer").play("injured")
emit_signal("life_change",life)#发信号life_change,传入最终的值
Saw的代码
extends Area2D
#export导出的变量可以在配置项中设置
export var max_dis = 100 #最大的距离,默认是100
export var move_dir = "h" #转动的方向是水平 v是垂直
var dis = 0 #记录移动的距离
var dir = 1 #移动的方向
func _process(delta):
dis += dir * 1
if dis >= max_dis or dis <= -1 * max_dis:
dir *= -1
if move_dir == "h":
position.x += 200 * dir * delta
if move_dir == "v":
position.y += 200 * dir * delta
rotation += -PI * delta
func _on_Tank_area_entered(area):
# print("hit")
var tankNode = get_node("../Tank")
# print(tankNode.life)
# if tankNode.life >= 0:
# tankNode.life -= 10
# print(tankNode.life)
tankNode.injured(10)#让tank损失10点生命
Lifebar的代码
extends TextureProgress
func _on_Tank_life_change(final_life):
value = final_life
通过这个案例,可以让大家感受得到Godot编写一个简单游戏的流程,下一讲,将会专门介绍向量数学方面的知识。