目标

本节所要实现的是一个简单的基于鼠标点击的移动,就像我们在很多RTS游戏中的移动方式。当然我们的示例要简单的多,不涉及框选多个对象,也不涉及过多的效果。
当然这也可以作为你的RTS游戏的基础。

image.png 没错,场景结构还是不变,基础部分就是要看看用一个Sprite我们到底能实现些什么。

代码

我们为Sprite添加如下代码:

  1. extends Sprite
  2. var target_pos = Vector2.ZERO # 目标位置
  3. var speed = 20.0 # 移动速度
  4. var can_move = false # 标志变量 - 能否移动
  5. func _ready():
  6. set_process(false) # 停止_process的执行
  7. func _input(event):
  8. if event is InputEventMouseButton: # 鼠标按键
  9. if event.button_index == BUTTON_LEFT: # 鼠标左键
  10. if event.pressed: # 按下
  11. target_pos = get_global_mouse_position() # 记录点击的鼠标位置
  12. can_move = true # 允许向目标移动
  13. set_process(true) # 启动_process的执行
  14. func _process(delta):
  15. if can_move:
  16. if target_pos.distance_to(global_position) >0:
  17. global_position = global_position.move_toward(target_pos,speed)
  18. else:
  19. can_move = false # 停止向目标移动
  20. set_process(false) # 停止_process的执行

运行效果

基础动画示例2.gif
看完效果我们来分析代码。本例的代码相对复杂一些,涉及的知识点也比较多,所以我们采用思路和编码步骤分解的形式来看。

第一步 记录目标位置

我们想要实现鼠标点击,获取要移动到的目标位置。

  1. extends Sprite
  2. var target_pos = Vector2.ZERO # 目标位置
  3. # 事件处理方法
  4. func _input(event):
  5. if event is InputEventMouseButton: # 鼠标按键
  6. if event.button_index == BUTTON_LEFT: # 鼠标左键
  7. if event.pressed: # 按下
  8. target_pos = get_global_mouse_position() # 记录点击的鼠标全局坐标位置

代码解析

我们在顶部申明一个变量target_pos,来存储我们鼠标点击时的全局坐标位置。
然后我们在_input中进行一系列对其参数event的判断,比如我们首先判断,event是不是InputEventMouseButton类型,也就是鼠标按键事件,如果是则执行下一个判断,第二个判断是判断event的button_index是不是等于BUTTON_LEFT,也就是鼠标左键,如果是继续执行下一行判断,第三个判断是event的pressed属性,它代表按键被按下。
这样三层判断的意思就非常明了了,合起来意思就是如果鼠标左键按下。
最后我们给target_pos赋值当前点击时获取的鼠标全局坐标位置。

第二步 实现移动

  1. var speed = 20.0 # 移动速度
  2. func _process(delta): # 每帧执行如下代码
  3. if target_pos.distance_to(global_position) >0:
  4. global_position = global_position.move_toward(target_pos,speed)

代码解析

这里我们只看核心的几行代码。
因为我们不能是瞬移到鼠标点击的位置,所以我们必须有一个移动过程,这就需要用_process实现了。它可以每秒执行60多次,用于实现顺滑的移动绝好不过了。
这里我们判断了当前Sprite的全局位置global_position到目标位置target_pos的距离。我们使用的是Vector2类型的distance_to()方法。所有Vector2类型的变量都可以直接调用。
image.png
这里我们判断只要目标位置和Sprite的当前位置之间距离大于0,也就是Sprite尚未达到目标位置时,执行它的语句块中的内容。
这里我们再次使用了一个Vector2类型的方法叫做move_toward(),它的含义就是返回一个向量到另一个目标向量移动一个指定长度后的向量。
在这里就是将当前Sprite全局位置到目标位置的向量上,移动speed也就是20像素距离后的向量再次赋值给Sprite的global_position属性,从而实现Sprite的移动。

第三步 状态的判断和控制

  1. extends Sprite
  2. var speed = 20.0 # 移动速度
  3. var target_pos = Vector2.ZERO # 目标位置
  4. # 事件处理方法
  5. func _input(event):
  6. if event is InputEventMouseButton: # 鼠标按键
  7. if event.button_index == BUTTON_LEFT: # 鼠标左键
  8. if event.pressed: # 按下
  9. target_pos = get_global_mouse_position() # 记录点击的鼠标全局坐标位置
  10. func _process(delta): # 每帧执行如下代码
  11. if target_pos.distance_to(global_position) >0:
  12. global_position = global_position.move_toward(target_pos,speed)

代码分析

如果是单单将前面两步的代码整合起来,就是这样。
上面的代码运行后已经可以实现鼠标点击移动的效果了,但是还存在一些问题。
比如,由于我们为目标位置的存储变量target_pos赋值Vector2.ZERO也就是(0,0),因此,场景运行时,就会首先将Sprite移动到屏幕左上角,然后才是按照我们点击的位置进行移动。

改进

  1. extends Sprite
  2. var speed = 20.0 # 移动速度
  3. var target_pos # 目标位置
  4. # 事件处理方法
  5. func _input(event):
  6. if event is InputEventMouseButton: # 鼠标按键
  7. if event.button_index == BUTTON_LEFT: # 鼠标左键
  8. if event.pressed: # 按下
  9. target_pos = get_global_mouse_position() # 记录点击的鼠标全局坐标位置
  10. func _process(delta): # 每帧执行如下代码
  11. if target_pos:
  12. if target_pos.distance_to(global_position) >0:
  13. global_position = global_position.move_toward(target_pos,speed)

代码分析

而如果我们不为target_pos赋初始值,并且在_process多加一层判断,判断target_pos是否为空(也就是Null)。
如果为空,也就是没有任何赋值时,就不执行移动了。
上面的代码在执行上是完全没有问题的。

繁琐的实现

  1. extends Sprite
  2. var target_pos = Vector2.ZERO # 目标位置
  3. var speed = 20.0 # 移动速度
  4. var can_move = false # 标志变量 - 能否移动
  5. func _ready():
  6. set_process(false) # 停止_process的执行
  7. func _input(event):
  8. if event is InputEventMouseButton: # 鼠标按键
  9. if event.button_index == BUTTON_LEFT: # 鼠标左键
  10. if event.pressed: # 按下
  11. target_pos = get_global_mouse_position() # 记录点击的鼠标位置
  12. can_move = true # 允许向目标移动
  13. set_process(true) # 启动_process的执行
  14. func _process(delta):
  15. if can_move:
  16. if target_pos.distance_to(global_position) >0:
  17. global_position = global_position.move_toward(target_pos,speed)
  18. else:
  19. can_move = false # 停止向目标移动
  20. set_process(false) # 停止_process的执行

代码分析

当然我们最初实现的版本比上面的这种形式稍显复杂,可以看到我们多申明了一个标志变量can_move来标记当前是否可以移动。并且使用到了Node的set_process()方法实现_process的开启或禁用。
image.png
这种写法的好处是更精确,但是由于涉及_process的整体开启或禁用,在实现其他功能代码时可能会造成一些干扰。所以本着怎么简单怎么来的原则,还是推荐我们的简单实现版本。