下班感觉身体被掏空, 时间也少得可怜, 做不了多少东西, 每天向前拱一点吧! 加油!
我们已经实现了多人玩家移动同步, 射击同步, 子弹的生成与同步, 但是没有东西可以打, 接下来实现一个靶子吧
敌人像素资源
Aseprite启动, 涂一个脏包, 完毕!
导出png, 导入Godot
敌人场景
和之前一样, 创建敌人场景res://entities/enemy/enemy.tscn, 根节点为CharacterBody2D, 添加Sprite2D显示资源, 添加CollisionShape2D处理移动碰撞(蓝色碰撞体)
新增的Area2D用于处理子弹检测(紫色碰撞体), 最后的MultiplayerSynchronizer用于同步敌人的初始化位置
这里可以看到两个碰撞体的大小设置不同, 一般来说我们在游戏开发中遵循以下规则:
- 对于移动碰撞可以稍微小一些, 允许少许重叠, 减少玩家被卡住的概率
- 对于伤害检测可以稍微宽松一些, 将"擦边"的情况也算作击中, 增加玩家的体验
总之, 在合理的范围内, 要让游戏尽可能玩起来舒服一点
对于伤害检测的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层的其他碰撞体事件, 不检测任何碰撞事件
最后是子弹碰撞的处理, register_collision中简单释放自身实例
敌人的生成
来到Main场景中完成以下任务
- 敌人每间隔一段时间随机生成, 位置随机
- 敌人生成同步, 场景生成同步, 初始位置同步
在MultiplayerSynchronizer的自动生成列表中新增enemy.tscn, 并且在start中判断, 若是authority, 则开始生成敌人逻辑
生成敌人暂时简单实现, 无线循环生成, 每次间隔随机时间(1 ~ 6秒), 每次生成在随机位置(依据设计尺寸计算)
看看效果
今天比较顺利, 一次跑起来
敌人的生成, 子弹的生成, 碰撞处理, 多人同步, 都完美运行