Godot游戏引擎学习 第六课
今天学的是如何添加怪物,并让怪物移动。
这节课UP讲了好几个方面的东西,有点杂,我这里根据个人理解做点学习笔记。
一、 怪物对象化
怪物是有很多种类的,而且怪物具有很多相同过的特性和不同的特性。为了以后好开发将一些共有属性提取出来做个父类是很有必要的。
这里UP就讲了一个继承脚本、场景的的东西,
先建立一个Enemy场景,设定一些共有节点
建立对应的脚本,独立出共有属性
extends KinematicBody2D
# 共有属性重力
const gravity = 2000
# 因为要处理精灵方向的问题,所以做个预载变量。
onready var sprite: Sprite = $Sprite
然后点击编辑器菜单上的场景按钮,选择建立继承场景,父场景选择Enemy.tscn
,就会实例化出一个具有父场景所有属性的子场景,命名为Slime.tscn
。然后在这个里面设置Slime的特有属性,如碰撞体、精灵等。
脚本也同理,选中父节点,按右键选择打开脚本
,UP住说这是一个翻译错误,弹幕里也有大佬说这里术语原文是 Extend,呼应脚本里的 extends 关键词
,我的理解其实就是父类脚本。新建好的脚本如下,代码我都写了备注就不在这里说了:
extends "res://src/enemies/Enemy.gd"
# 定义枚举,用于指定怪物朝向
enum Direction { Left = -1, Right = 1 }
# 定义移动速度和加速度,这个其实可以提取出来到父脚本去
const max_speed = 50
const acceleration = max_speed / 2
# 这里用到一个新东西:export变量,
# 一个变量加上export就成为外部变量了,可以通过编辑器设置他的值
# 括号里是指定这个变量的展示类型为上面设置的枚举
export(Direction) var direction = Direction.Left
# 速度
var velocity = Vector2.ZERO
func _physics_process(_delta):
# 每一个物理帧检查怪物是不是碰到墙了
var was_on_wall = is_on_wall()
# 怪物不会跳,所以把他们吸附在地板上
var snap = Vector2.DOWN * 16
# 进行移动操作
velocity = move_and_slide_with_snap(velocity, snap, Vector2.UP)
# 如果碰到墙就转身
if is_on_wall() and not was_on_wall:
direction *= -1
func _process(delta):
# 进行加速移动
velocity.x = move_toward(velocity.x, max_speed * direction, acceleration * delta)
velocity.y += gravity * delta
# 进行转身
sprite.flip_h = velocity.x > 0
这里UP主还说了一个BUG,就是在更改了父场景的层归属后子场景不会更新,实例化出来的场景也不会更新,要把主场景关掉后重开就正常了。
说到精灵这里插一下,有些精灵素材是整合在一起的,所以整个图片会很大,像下图这样,所以我们设置好精灵素材后,启用Region
,然后可以先设置Scale
把图片缩放到合适的大小,再设置v和hframes,开启网格吸附,将步长设置成每一帧图片的大小,再框选图片就好了。
好了,建立好怪物之后就可以实例化到场景里UP又说了昨天碰撞层的问题,现在状态是怪物碰到玩家就不动了,他想让怪物穿过玩家,所以,我们再命名一个怪物层,把实例化的Slime的碰撞和遮罩层分配到这个里面。
这样因为玩家和怪物不在一个层,所以碰不到怪物,但是怪物们再一个层,他们会互相碰撞。
怪物笼
其实就是怪物刷新点啦,让怪物定时刷新的功能。
我们可以利用Position2D
的和Timer
节点实现怪物笼效果,第一个用于定位怪物刷新出来的位置,第二个控制多常时间刷新。
(这里说下,所有的信号时间必须在编辑器里注册,不然直接写进代码是没有效果的,我前几天说的那个可以手动写信号的话暂时先放一边,目前了解到的暂时不可以)
代码如下:
extends Position2D
# 让外部可以设置打包场景
export var enemy_scene: PackedScene
# 刷新速度
export var interval = 1.0
#定时器
onready var timer =$Timer
func _ready():
# 如果设置了打包场景
if enemy_scene:
# 开始计时
timer.start(interval)
# 每进行一次循环完毕的信号
func _on_Timer_timeout():
# 根据设置的打包场景实例化新的怪物对象
var enemy: Node2D = enemy_scene.instance()
# 将实例化后的怪物添加到父节点中
get_parent().add_child(enemy)
#并将位置设置为Position的节点
enemy.global_position = global_position;
信号的使用和攻击、受伤、攻击特效
这里UP提出了攻击盒子和伤害盒子的概念,我因为不太理解的缘故中间代码和设置都整错了一部分,一直不太对,最后把添加的相关场景和代码全部删掉之后重写才正常实现。
攻击盒子和伤害盒子和场景内角色和怪物都有的一个特性,所以为了复用代码就单独做了两个场景,Hitbox
,Hurtbox
,
这里我看的时候理解混乱了好一会儿,后来算是明白了,其实一个攻击或受伤的步骤分为两步,比如攻击
- 我感觉到我打到你了
- 你感受到我打到你了,你叫了出来。
其实理解后就很简单了,我的攻击碰到了你的受伤盒子,触发信号,我先执行攻击你的函数,再通知你受伤的信号,执行受伤的函数。
这里制作了踩中怪物是的攻击特效和怪物的受伤特效
# Player.gd > 省略前面的代码
# 如果被怪物攻击了,怪物那边的攻击函数就会调用角色的受伤信号
func _on_Hurtbox_hurt():
# 重新加载当前场景。
get_tree().reload_current_scene()
# 如果踩中敌人了则自己处理自己的攻击过程,并通知对方受伤了。
func _on_Hitbox_hit():
velocity.y = -jump_force / 2
# Enemy.gd > 省略了一部分代码
# 编写信号链接函数
func _on_Hurtbox_area_entered(hitbox):
# 如果检测到碰着,也就是受伤
# 首先调用受伤的信号
emit_signal("hurt")
# 再调用攻击信号
hitbox.emit_signal("hit")
大概就是这样把,感觉自己也没讲的很明白。
总体代码如下:
extends Area2D
# 自定义攻击信号,方便实例后调用
signal hit
# 检测攻击状态
func _on_Hitbox_area_entered(hurtbox):
# 攻击到敌人先调用攻击信号
emit_signal("hit")
# 再调用对方的受伤信号
hurtbox.emit_signal("hurt")
extends Area2D
# 自定义受伤信号,用于实例化调用
signal hurt
# 编写信号链接函数
func _on_Hurtbox_area_entered(hitbox):
# 如果检测到碰着,也就是受伤
# 首先调用受伤的信号
emit_signal("hurt")
# 再调用攻击信号
hitbox.emit_signal("hit")
加入评论