Godot游戏练习01-第7节-添加敌人

0 阅读3分钟

下班感觉身体被掏空, 时间也少得可怜, 做不了多少东西, 每天向前拱一点吧! 加油!

我们已经实现了多人玩家移动同步, 射击同步, 子弹的生成与同步, 但是没有东西可以打, 接下来实现一个靶子吧

敌人像素资源

Aseprite启动, 涂一个脏包, 完毕!

0310_1.png

导出png, 导入Godot

敌人场景

和之前一样, 创建敌人场景res://entities/enemy/enemy.tscn, 根节点为CharacterBody2D, 添加Sprite2D显示资源, 添加CollisionShape2D处理移动碰撞(蓝色碰撞体)

新增的Area2D用于处理子弹检测(紫色碰撞体), 最后的MultiplayerSynchronizer用于同步敌人的初始化位置

0310_2.png

这里可以看到两个碰撞体的大小设置不同, 一般来说我们在游戏开发中遵循以下规则:

  • 对于移动碰撞可以稍微小一些, 允许少许重叠, 减少玩家被卡住的概率
  • 对于伤害检测可以稍微宽松一些, 将"擦边"的情况也算作击中, 增加玩家的体验

总之, 在合理的范围内, 要让游戏尽可能玩起来舒服一点

对于伤害检测的Area2D的碰撞层, 我们取消所有Layer的选中, Mask仅勾选Bullet所属的层(我定义为9层), 表示该Area2D不触发其他碰撞体的事件, 仅检测来自Bullet层的碰撞事件

选中Enemy根节点, 在右侧Groups栏中, 创建一个group, 取名"enemy", 意味着所有敌人实例都属于"enemy"分组

注意: 多人游戏中, 仅服务器(authority)来处理真正的碰撞事件, 处理场景销毁, Client Peer仅负责同步展示, 所以事件监听仅在authority peer执行

以下为Enemy脚本

extends CharacterBody2D

var current_health : int = 5

@onready var area_2d: Area2D = $Area2D

func _ready() -> void:
	if is_multiplayer_authority():
		area_2d.area_entered.connect(_on_area_entered)


func _handle_hit() -> void:
	current_health -= 1
	if current_health <= 0:
		queue_free()


func _on_area_entered(area: Area2D) -> void:
	if not area.owner is Bullet:
		return
	var bullet := area.owner as Bullet
	bullet.register_collision()
	_handle_hit()

子弹的碰撞检测

在Enemy脚本中已经写好了碰撞检测逻辑, 如果碰撞体所属场景不是Bullet类型, 则不处理, 否则让子弹实例处理碰撞事件, 自身也扣减生命值, 若生命值不再大于0, 则释放自身

在子弹场景中, 我们需要新增Area2D, 让Enemy的Area2D能检测到, 子弹的Area2D碰撞设置与Enemy相反, 仅在Layer勾选9层(Bullet层), 取消Mask所有选中, 表示仅触发第9层的其他碰撞体事件, 不检测任何碰撞事件

0310_3.png

最后是子弹碰撞的处理, register_collision中简单释放自身实例

敌人的生成

来到Main场景中完成以下任务

  • 敌人每间隔一段时间随机生成, 位置随机
  • 敌人生成同步, 场景生成同步, 初始位置同步

0310_4.png

在MultiplayerSynchronizer的自动生成列表中新增enemy.tscn, 并且在start中判断, 若是authority, 则开始生成敌人逻辑

生成敌人暂时简单实现, 无线循环生成, 每次间隔随机时间(1 ~ 6秒), 每次生成在随机位置(依据设计尺寸计算)

看看效果

今天比较顺利, 一次跑起来

0310_5.gif

敌人的生成, 子弹的生成, 碰撞处理, 多人同步, 都完美运行