现在我拥有了一个可以游玩和分发的简单贪吃蛇。但是它很简陋,很无聊。在考虑美术——以及精细的数值调整之前,我想先继续优化玩法。

【加速】

长按鼠标左键加速——先实现这个简单的功能。

【按键绑定】

  1. Project Settings-Input Map,在Add New Action处输入“speed_up”,点击Add。在新出现的动作项中点击右边的加号,在弹出窗口中找到Mouse Buttons-Left Mouse Button,点ok。请添加图片描述
    请添加图片描述

  2. 在snake.gd中新增两个常量var DEFAULT_SPEED : int = 300var 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
  1. 在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()
  1. 现在运行游戏(单独运行player场景可能因为缺乏参照不明显),可以看到按下鼠标左键时蛇加速,松开左键则恢复正常速度。

【能量条】

如果允许蛇随意使用加速,这个功能就毫无意义了。能量条是一种常见的限制措施,我希望蛇加速时消耗能量,能量耗尽后无法加速。

  1. 在snake.gd中新增两个变量var energy : int = 100var accelerating : bool = false,两个常量var ENERGY_COST : int = 5var 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(),方便判断是否还可以继续加速。

  1. 回到Level场景,为UI新增一个ProgressBar结点,重命名为EnergyBar。注意,这个能量条应该直接在UI结点下。设置为唯一名称访问(旁边出现%),方便之后调整布局。
    请添加图片描述

    拖动改变形状为一个长条形,anchor暂时设置为center left。在Inspector中设置Fill Mode为“Bottom to Top”,取消勾选“Show Percentage”,Value设置为100。
    请添加图片描述

  2. 在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,这样能量条变化就比较平滑。

  1. level.gd的_ready()中,在玩家初始化之后连接能量变化信号:
    player.energy_changed.connect($UI.update_energy_bar)

  2. 运行游戏,可以看到玩家蛇在长按左键时加速,松开左键或能量条见底时恢复原本速度,且能量条见底后无法再加速。
    请添加图片描述

    能量条的反应似乎有延迟(松开左键时依旧在下降),这是因为能量条下降的动画有延迟。可以增加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)
  1. 运行游戏,可以感觉到延迟有好转。

  2. 现在来调整布局,不再让能量条紧贴边框。使用MarginContainer是一种方式,不过还可以直接简单地调整anchor。点击EnergyBar,在Inspector中找到Layout-Anchors Preset,设置为Custom,进行如下调整:
    请添加图片描述

    现在,能量条拥有了合适的边距。
    请添加图片描述

【能量恢复】

目前,我希望能量只是简单地随时间流逝缓慢回复。

  1. 在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()
  1. 在速度复位时调用该方法。
func handle_speed_restore():
	accelerating = false
	$SnakeHead.speed = DEFAULT_SPEED
	recover_energy() #每次加速结束后,开始恢复
  1. 运行游戏,可以看到结束加速(松开左键)后能量条开始缓慢上升,升满则停止。但是在蛇死亡冷却期间能量条也在恢复,这样不太好。

  2. 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:
  1. 来逐一解决这些问题。首先回到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。

  1. 接下来是死亡重生后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
  1. 现在重新运行游戏,效果基本令人满意:蛇在死亡冷却中能量不增加,重生后能量才会继续增加,并且不会因为重生跳变到满值。
推荐内容

【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)

重新运行游戏,尝试撞墙,玩家在死亡冷却期间晃动鼠标,摄像机不会跟着乱晃。

点击阅读全文
Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐