
Godot实战-SnakeWar(9)
加速功能
现在我拥有了一个可以游玩和分发的简单贪吃蛇。但是它很简陋,很无聊。在考虑美术——以及精细的数值调整之前,我想先继续优化玩法。
【加速】
长按鼠标左键加速——先实现这个简单的功能。
【按键绑定】
-
Project Settings-Input Map,在Add New Action处输入“speed_up”,点击Add。在新出现的动作项中点击右边的加号,在弹出窗口中找到Mouse Buttons-Left Mouse Button,点ok。
-
在snake.gd中新增两个常量
var DEFAULT_SPEED : int = 300
,var SPEEDUP_RATIO : float = 2
。_ready()
里初始化蛇头时设置蛇头速度为DEFAULT_SPEED
,然后新增两个方法:
func handle_speed_up():
$SnakeHead.speed = SPEEDUP_RATIO * DEFAULT_SPEED
func handle_speed_restore():
$SnakeHead.speed = DEFAULT_SPEED
- 在player.gd的_process()中监听按键并调用速度操纵方法:
func _process(delta):
if Input.is_action_just_pressed("speed_up"):
handle_speed_up()
if Input.is_action_just_released("speed_up"):
handle_speed_restore()
- 现在运行游戏(单独运行player场景可能因为缺乏参照不明显),可以看到按下鼠标左键时蛇加速,松开左键则恢复正常速度。
【能量条】
如果允许蛇随意使用加速,这个功能就毫无意义了。能量条是一种常见的限制措施,我希望蛇加速时消耗能量,能量耗尽后无法加速。
-
在snake.gd中新增两个变量
var energy : int = 100
,var accelerating : bool = false
,两个常量var ENERGY_COST : int = 5
,var ENERGY_RECOVERY : int = 1
。能量变化通过信号传递:signal energy_changed(energy:int)
。修改处理加速和速度复位的方法,并新增一个处理能量消耗的方法:
func handle_speed_up():
if energy > 0:
accelerating = true
$SnakeHead.speed = SPEEDUP_RATIO * DEFAULT_SPEED
consume_energy()
else:
handle_speed_restore()
func handle_speed_restore():
accelerating = false
$SnakeHead.speed = DEFAULT_SPEED
func consume_energy():
if accelerating && energy > 0:
energy -= ENERGY_COST
energy = 0 if energy < 0 else energy
energy_changed.emit(energy)
await get_tree().create_timer(1).timeout
if accelerating:
consume_energy()
else:
handle_speed_restore()
类似倒计时的处理,每秒减少一次能量。但是这次不是调用自身,而是调用handle_speed_up()
,方便判断是否还可以继续加速。
-
回到Level场景,为UI新增一个ProgressBar结点,重命名为EnergyBar。注意,这个能量条应该直接在UI结点下。设置为唯一名称访问(旁边出现%),方便之后调整布局。
拖动改变形状为一个长条形,anchor暂时设置为center left。在Inspector中设置Fill Mode为“Bottom to Top”,取消勾选“Show Percentage”,Value设置为100。
-
在ui.gd中写能量变化的方法:
func update_energy_bar(new_energy : int):
var tween = get_tree().create_tween()
tween.tween_property(%EnergyBar, "value", new_energy, 1)
使用tween而不是直接更改%EnergyBar.value
,这样能量条变化就比较平滑。
-
level.gd的
_ready()
中,在玩家初始化之后连接能量变化信号:
player.energy_changed.connect($UI.update_energy_bar)
-
运行游戏,可以看到玩家蛇在长按左键时加速,松开左键或能量条见底时恢复原本速度,且能量条见底后无法再加速。
能量条的反应似乎有延迟(松开左键时依旧在下降),这是因为能量条下降的动画有延迟。可以增加
consume_energy()
的调用频率(即减少await时间)来减少这种延迟,不过需要同步更改UI里tween的持续时间。为了方便维护,最好在发射信号时把等待时间也一并发送:signal energy_changed(energy:int, wait_time:float)
。
func consume_energy():
if accelerating && energy > 0:
energy -= ENERGY_COST
energy = 0 if energy < 0 else energy
var wait_time = 0.5
energy_changed.emit(energy, wait_time)
await get_tree().create_timer(wait_time).timeout
if accelerating:
consume_energy()
else:
handle_speed_restore()
为了达到和之前一样的效果,需要更改ENERGY_COST:
var ENERGY_COST : int = 5
ui.gd中同步修改:
func update_energy_bar(new_energy : int, wait_time):
var tween = get_tree().create_tween()
tween.tween_property(%EnergyBar, "value", new_energy, wait_time)
-
运行游戏,可以感觉到延迟有好转。
-
现在来调整布局,不再让能量条紧贴边框。使用MarginContainer是一种方式,不过还可以直接简单地调整anchor。点击EnergyBar,在Inspector中找到Layout-Anchors Preset,设置为Custom,进行如下调整:
现在,能量条拥有了合适的边距。
【能量恢复】
目前,我希望能量只是简单地随时间流逝缓慢回复。
- 在snake.gd中增加恢复能量的方法,和消耗能量的写法很像:
func recover_energy():
if !accelerating:
energy += ENERGY_RECOVERY
energy = 100 if energy > 100 else energy
var wait_time = 1
energy_changed.emit(energy, wait_time)
await get_tree().create_timer(wait_time).timeout
recover_energy()
- 在速度复位时调用该方法。
func handle_speed_restore():
accelerating = false
$SnakeHead.speed = DEFAULT_SPEED
recover_energy() #每次加速结束后,开始恢复
-
运行游戏,可以看到结束加速(松开左键)后能量条开始缓慢上升,升满则停止。但是在蛇死亡冷却期间能量条也在恢复,这样不太好。
-
为
recover_energy()
方法增加一些条件:if alive && energy < 100 && !accelerating:
,然后在_ready()
里调用它:recover_energy() #重新创建蛇时启动能量恢复
。尝试运行游戏,会发现死亡冷却中能量条不再恢复,但重生后能量恢复并未继续。如下加一句打印,可以发现重生后能量被重设为100(尽管UI中没有跟着更新)。
func recover_energy():
print("try recover, energy="+str(energy))
if alive && energy < 100 && !accelerating:
- 来逐一解决这些问题。首先回到level.gd,在处理死亡时记录该死蛇的当前能量值:
func handle_death(isEnemy:bool, id:int):
var energy = snake_list[id].energy #记录死亡时的能量值
snake_list[id].queue_free()
snake_list[id] = create_snake(isEnemy, id, energy)
在create_snake()
中要加一句修改energy的语句。当然,在_ready()
中对create_snake()
有两次调用,要随之增加参数:初始状态energy应当是100。
- 接下来是死亡重生后UI不更新的问题。这是因为连接信号被放在
_ready()
中,实际上它应该在create_snake()
里:
func create_snake(isEnemy:bool, id:int, energy:int):
var snake = enemy_scene.instantiate() if isEnemy else player_scene.instantiate()
snake.id = id
snake.isEnemy = isEnemy
snake.color = Global.color_list[id]
snake.energy = energy
snake.global_position = $SpawnPoints.get_child(id).global_position
add_child(snake)
snake.score_changed.connect($UI.update_board)
snake.burst_to_foods.connect(handle_burst)
snake.died.connect(handle_death)
if !isEnemy:
snake.energy_changed.connect($UI.update_energy_bar) #只为玩家连接到能量条UI
return snake
- 现在重新运行游戏,效果基本令人满意:蛇在死亡冷却中能量不增加,重生后能量才会继续增加,并且不会因为重生跳变到满值。
【Bug处理:蛇头死亡后镜头乱动】
原因是摄像机挂在玩家的蛇头上,而玩家死亡后的冷却时间里蛇头只是隐藏(为了不销毁摄像机),并设置speed=0让蛇头无法移动。这个方式是有风险的,比如现在,加速相关的方法修改了速度,让蛇头在死亡冷却(隐藏)期间可以应用加速和速度复位。
解决这个问题很简单。检查player.gd,移动蛇头是在_physics_process(delta)
中完成的,所以可以直接禁用physics process:set_physics_process(false)
。现在player.gd中蛇头死亡处理是这样的:
func handle_head_death():
$SnakeHead.hide()
$SnakeHead/CollisionShape2D.queue_free() #禁用碰撞检测
set_physics_process(false)
重新运行游戏,尝试撞墙,玩家在死亡冷却期间晃动鼠标,摄像机不会跟着乱晃。
更多推荐





所有评论(0)