【Godot】在 Godot 中实现 RPG 游戏的射箭机制:抛物线箭矢轨迹
在 Godot 中实现 RPG 游戏的射箭功能,包括抛物线运动控制箭矢飞行、角色动画的变化,以及如何计算箭矢的落点和限制攻速
·
完整项目地址:https://gitee.com/wagio_admin/godot_demo1
视频教程:https://www.bilibili.com/video/BV1RT1mYkEV5
Player节点代码
class_name Player
extends CharacterBody2D
const SPEED = 150.0
const ARROW_SCENE = preload("res://Arrow.tscn") # 预加载箭矢场景
enum Action {
Idle, # 站立
Running, # 奔跑
Arrow, # 射箭
}
@onready var animation_player: AnimationPlayer = $AnimationPlayer
var player_direction = Vector2.DOWN #默认站立朝向
var attacking = false # 攻击状态
func _physics_process(delta: float) -> void:
var direction_x = Input.get_axis("ui_left", "ui_right")
var direction_y = Input.get_axis("ui_up", "ui_down")
velocity.x = direction_x * SPEED
velocity.y = direction_y * SPEED
# 归一化速度
if velocity.length() > SPEED:
velocity = velocity.normalized() * SPEED
# 处理站立和奔跑动画
if !attacking:
if velocity != Vector2.ZERO:
if direction_y != 0:
player_direction = Vector2.DOWN if direction_y > 0 else Vector2.UP
change_animation(Action.Running) # 启动跑步动画
if direction_x != 0:
player_direction = Vector2.RIGHT if direction_x > 0 else Vector2.LEFT
change_animation(Action.Running) # 启动跑步动画
else:
change_animation(Action.Idle) # 启动站立
else:
# 攻击时,减慢速度
velocity = velocity * 0.4
move_and_slide()
# 处理射箭输入
if Input.is_action_just_pressed("ui_select"):
shoot_arrow()
func shoot_arrow() -> void:
if attacking:
return # 如果正在攻击,忽略输入
# 添加箭矢
var arrow_instance = ARROW_SCENE.instantiate()
get_tree().root.add_child(arrow_instance) # 将箭矢添加到场景中
# 计算射箭方向
var mouse_position = get_global_mouse_position() # 鼠标位置
var direction = (mouse_position - position).normalized() # 计算方向向量
var offset = Vector2.ZERO # 偏移量(使弓箭出手位置更加自然)
# 计算玩家的方向
if abs(direction.x) > abs(direction.y):
player_direction.y = 0 # 保留 x,将 y 设置为 0
player_direction.x = direction.x
offset = Vector2(16, -5) if direction.x > 0 else Vector2(-16, -5) # 具体值可以根据实际情况调整
else:
player_direction.y = direction.y
player_direction.x = 0 # 保留 y,将 x 设置为 0
offset = Vector2(0, 10) if direction.y > 0 else Vector2(0, -20) # 具体值可以根据实际情况调整
player_direction = player_direction.normalized()
var arrow_start_point = position + offset # 玩家位置 + 偏移量(使弓箭出手位置更加自然)
var arrow_impact_point = mouse_position # 弓箭的落点
var distance = mouse_position.distance_to(arrow_start_point) # 计算距离
if distance > 150:
# 计算落点在 arrow_start_point 到 mouse_position 线段上,150像素的位置
arrow_impact_point = arrow_start_point + direction * 150 # 落点为 arrow_start_point 加上方向向量乘以 150
change_animation(Action.Arrow) # 启动射箭动画
arrow_instance.shoot(arrow_start_point, arrow_impact_point) # 启动箭矢
attacking = true # 设置攻击状态
# 设定攻击动画结束后重置攻击状态
# 假设攻击动画持续时间为 0.2 秒,你可以根据实际情况调整(如果游戏设计中不同的弓箭射速不一样,可以调整这里)
await get_tree().create_timer(0.2).timeout
attacking = false # 重置攻击状态
# 切换动画效果
func change_animation(action:Action):
# 判断站立动画
if action == Action.Idle:
if player_direction == Vector2.DOWN:
animation_player.play("idle_down")
elif player_direction == Vector2.UP:
animation_player.play("idle_up")
elif player_direction == Vector2.RIGHT:
animation_player.play("idle_right")
elif player_direction == Vector2.LEFT:
animation_player.play("idle_left")
# 判断跑步动画
elif action == Action.Running:
if player_direction == Vector2.DOWN:
animation_player.play("running_down")
elif player_direction == Vector2.UP:
animation_player.play("running_up")
elif player_direction == Vector2.RIGHT:
animation_player.play("running_right")
elif player_direction == Vector2.LEFT:
animation_player.play("running_left")
# 判断射箭动画
elif action == Action.Arrow:
if player_direction == Vector2.DOWN:
animation_player.play("arrow_down")
elif player_direction == Vector2.UP:
animation_player.play("arrow_up")
elif player_direction == Vector2.RIGHT:
animation_player.play("arrow_right")
elif player_direction == Vector2.LEFT:
animation_player.play("arrow_left")
Arrow 节点代码
extends Node2D
const SPEED = 200.0 # 箭矢飞行速度
const ARC_HEIGHT = 20.0 # 控制抛物线拱形高度
@onready var sprite_2d: Sprite2D = $Sprite2D
var start_position: Vector2 # 箭矢起始位置
var target_position: Vector2 # 箭矢目标位置
var moving: bool = false # 是否正在移动
var time_passed: float = 0.0 # 飞行时间累积
var flight_duration: float # 箭矢总飞行时间
# 发射箭矢,设置起始和目标位置
func shoot(arrow_start_point: Vector2, arrow_impact_point: Vector2) -> void:
position = arrow_start_point # 设置初始位置
start_position = arrow_start_point # 记录起始位置
target_position = arrow_impact_point # 记录目标位置
flight_duration = start_position.distance_to(target_position) / SPEED # 计算飞行总时长
time_passed = 0.0 # 重置飞行时间
moving = true # 标记箭矢开始移动
# 在每帧更新位置和角度
func _process(delta: float) -> void:
if not moving:
return # 不移动时直接返回
time_passed = min(time_passed + delta, flight_duration) # 累加经过时间,限制最大值为总飞行时间
var t = time_passed / flight_duration # 计算归一化时间 t (0 到 1)
# x 坐标线性插值
var x = lerp(start_position.x, target_position.x, t)
# y 坐标在线性插值的基础上,结合抛物线偏移
var y = lerp(start_position.y, target_position.y, t) - ARC_HEIGHT * 4 * t * (1 - t)
position = Vector2(x, y) # 更新箭矢当前位置
# 计算角度:箭头对齐运动方向
if t < 1.0:
var next_position = Vector2(
lerp(start_position.x, target_position.x, t + delta / flight_duration),
lerp(start_position.y, target_position.y, t + delta / flight_duration) - ARC_HEIGHT * 4 * (t + delta / flight_duration) * (1 - (t + delta / flight_duration))
) # 计算下一帧位置
sprite_2d.rotation = (next_position - position).angle() # 设置箭矢角度朝向为曲线的切线方向
# 到达目标后释放箭矢实例
if t >= 1.0:
queue_free()
更多推荐
所有评论(0)