这一节,我主要学习用代码调用shader,组合和自由状态机结合到一起来完成怪物的一些功能。其实用代码调用shader实现敌人的闪白效果是怪物里面的一个受伤状态,所以先从怪物受伤状态开始。先看一下效果:
一、新建怪物的受伤状态EnemyHurt
新建脚本文件保存在States->EnemyState文件夹下命名为:EnemyHurt.gd。代码如下:
extends State
class_name EnemyHurt #定义类
@export var enemy:CharacterBody2D #敌人,出现在该类的检查器,可拖入敌人的CharacterBody2D对象
@export var anima:AnimatedSprite2D #敌人播放动画类,出现在该类的检查器
var attack:Attack #定义攻击力,攻击力类在组合中定义
func get_Attack(att): #通过函数设置攻击力,外部可以调用
attack = att
func Physics_Update(delta:float):
enemy.velocity= Vector2() #敌人的速度设为0
if anima:#播放攻击动画
anima.material["shader_parameter/Eable"]=true #开启定义的shader参数,敌人汇变成白色,也就是闪白效果
anima.play("Idle") #播放Idle动画
if attack: #判断攻击力是否设置
#设置怪物的击退效果,先计算怪物的击退方向,归一化后乘以击退的力量
enemy.velocity =(enemy.global_position-attack.attack_position).normalized()*attack.konckback_force
await get_tree().create_timer(0.1).timeout #等待0.1秒
enemy.velocity.x =0 #怪物停止击退效果
anima.material["shader_parameter/Eable"]=false #怪物的闪白效果停止
Transitioned.emit(self,"Idle") #发出进入Idle信号
切换到Monster场景,选择StateMachine节点单击右键选择添加子场景,在对话框中选择EnemyHurt节点,然后重名为Hurt。
二、新建怪物的死亡状态EnemyDeath
新建脚本文件保存在States->EnemyState文件夹下命名为:EnemyDeath.gd。代码如下:
extends State
class_name EnemyDeath #定义类
var coinpre = preload("res://Scenes/Coins.tscn")#预加载金币场景
func Enter():#进入函数,直接进入死亡状态
death()
func death():
#播放死亡动画
Globals.animation_scene_obj.run_animation({
"box":Globals.duplicate_node,
"ani_name":"Dealth",
"position":get_parent().global_position,
"scale":Vector2(1,1)
})
for i in randi_range(1,6):#随机产生1到6个金币
coin_spawn()
var tween = get_tree().create_tween()#在根节点添加tween函数
tween.tween_property(get_parent().get_parent(),"modulate:a",0.2,0.3)#设置怪物的透明度逐渐减少
tween.tween_callback(get_parent().get_parent().queue_free)#怪物透明度达到0.2后释放怪物
func coin_spawn():#产生金币
var coin = coinpre.instantiate() #实例化金币场景
coin.position = get_parent().get_parent().position #设置金币位置等于怪物位置
Globals.duplicate_node.add_child(coin) #将金币添加到主场景的duplicate_node的节点下
切换到Monster场景,选择StateMachine节点单击右键选择添加子场景,在对话框中选择EnemyDeath节点,然后重名为Deaht。
这是敌人的状态节点目录如下:
三、修改完善状态机StateMachine代码
切换到状态机StateMachine的代码状态,添加一个状态转换函数
func _charage_currentstate(new_state_name,attack):
#该函数两个参数,第一个表示要转换到的状态名称,第二个市怪物受到的攻击力
var new_state = states.get(new_state_name.to_lower())#通过状态名称获取状态对象
if !new_state:#如果状态对象不存在,立即退出
return
if attack:#判断攻击力是否存在,该语句表示如果攻击力存在执行
if new_state.has_method("get_Attack"):#判断新状态是否存在get_Attack函数,该语句表示新状态含有get_Attack函数后,执行
new_state.get_Attack(attack)#执行新状态的get_Attack函数
new_state.Enter() #调用状态进入函数
current_state = new_state #将当前状态设置为新状态
这时我们的状态机代码如下:
extends Node
@export var inital_state:State #初始状态
var current_state:State #当前状态
var states:Dictionary={} #状态字典
func _ready():
#完成状态字典数据
for child in get_children():
if child is State:
states[child.name.to_lower()]= child
child.Transitioned.connect(on_child_transition) #连接信号到本页脚本
#设置初始状态
if inital_state:
inital_state.Enter() #调用状态进入函数
current_state = inital_state
pass
func _charage_currentstate(new_state_name,attack):
#该函数两个参数,第一个表示要转换到的状态名称,第二个市怪物受到的攻击力
var new_state = states.get(new_state_name.to_lower())#通过状态名称获取状态对象
if !new_state:#如果状态对象不存在,立即退出
return
if attack:#判断攻击力是否存在,该语句表示如果攻击力存在执行
if new_state.has_method("get_Attack"):#判断新状态是否存在get_Attack函数,该语句表示新状态含有get_Attack函数后,执行
new_state.get_Attack(attack)#执行新状态的get_Attack函数
new_state.Enter() #调用状态进入函数
current_state = new_state #将当前状态设置为新状态
func _process(delta):
#调用当前状态更新函数
if current_state:
current_state.Update(delta)
func _physics_process(delta):
#调用当前状态物理更新函数
if current_state:
current_state.Physics_Update(delta)
#状态改变信号调用函数,第一个参数表示目前处于状态,也就是进入新的状态有哪个状态发起的;第二个参数表示要进行新状态的名称
func on_child_transition(state,new_state_name):
#如果传入的状态部署当前状态,退出信号
if state!=current_state:
return
#根据状态名称调出状态数据字典中对应的状态
var new_state = states.get(new_state_name.to_lower())
#如果状态数据字典中不存在对应的状态退出
if !new_state:
return
#退出当前状态,调用状态退出函数
if current_state:
current_state.Exit()
#进入新的状态,调用进入函数
new_state.Enter()
#将当前状态设置为新的状态
current_state = new_state
四、修改完善受伤组件HealthComponent代码
切换到受伤组件HealthComponent代码状态,在damage函数中将死亡代码进行如下修改:
if health<=0: #如果当前血量小于0,调用状态机的改变状态函数,敌人进入死亡状态
get_parent().get_node("StateMachine")._charage_currentstate("Death",attack)
else:#如果血量不等于0,调用状态机的改变状态函数,敌人进入被攻击状态
get_parent().get_node("StateMachine")._charage_currentstate("Hurt",attack)
这时我们的受伤组件代码如下:
extends Node2D
class_name HealthComponent #类名
@onready var health_bar = $HealthBar #获取血量显示节点
@onready var back_bar = $HealthBar/BackBar #获取血量显示背景节点
@onready var charge_health = $ChargeHealth #获取掉血量显示节点
@onready var animation_player = $AnimationPlayer #获取掉血动画播放器
@export var MAX_HEALTH:=100.0 #定义最大血量
var health:float #定义当前血量
func _ready():
health = MAX_HEALTH #初始化时,当前血量等于最大血量
health_bar.value = health #将血量显示节点设置为当前血量 黄色血条
back_bar.value = health #将血量显示背景节点设置为当前血量 红色血条
### 受伤函数
func damage(attack:Attack):
charge_health.text = str("-") + str(attack.attack_damage) #将当前掉血量赋值给掉血量显示节点
health -=attack.attack_damage #计算当前血量
health_bar.value = health #更新血量条
var tween = get_tree().create_tween() #创建create_tween类
tween.tween_property(back_bar,"value",health,0.5) #将血量显示背景血量条调整到当前血量,0.5秒的一个减血过程
animation_player.play("damage") #播放掉血量动画
if health<=0: #如果当前血量小于0,调用状态机的改变状态函数,敌人进入死亡状态
get_parent().get_node("StateMachine")._charage_currentstate("Death",attack)
else:#如果血量不等于0,调用状态机的改变状态函数,敌人进入被攻击状态
get_parent().get_node("StateMachine")._charage_currentstate("Hurt",attack)
这样就完成了本节的内容,来看一下效果: