1
1
mirror of https://github.com/Rundll86/Dog-Lynx-And-HCN.git synced 2026-06-04 02:37:12 +08:00

feat: 添加新武器彩虹旗和紫水晶簇,调整饲料属性和数值

refactor: 重构子弹生成逻辑,支持分裂和折射效果

fix: 修复掉落物拾取范围和碰撞检测问题

style: 优化UI显示,添加武器品质和类型标签

docs: 更新字段描述,调整部分饲料名称和分类

perf: 优化数学工具函数,添加随机数处理工具

test: 调整波次生成逻辑,添加新敌人类型

build: 添加新资源文件和相关导入配置
This commit is contained in:
2025-09-05 22:23:41 +08:00
parent bb279c99b0
commit d4501ae45d
54 changed files with 974 additions and 109 deletions
+27 -2
View File
@@ -25,6 +25,8 @@ var launcher: EntityBase = null
var spawnInWhen: float = 0
var spawnInWhere: Vector2 = Vector2.ZERO
var destroying: bool = false
var isChildSplit: bool = false
var isChildRefract: bool = false
func _ready():
register()
@@ -59,6 +61,8 @@ func _physics_process(_delta: float) -> void:
if is_instance_valid(launcher) and (launcher.isPlayer() or is_instance_valid(launcher.currentFocusedBoss)):
launcher.position -= Vector2.from_angle(rotation) * recoil
ai()
else:
tryDestroy()
func hit(target: Node):
var entity: EntityBase = EntityTool.fromHurtbox(target)
@@ -84,11 +88,24 @@ func dotLoop():
func tryDestroy(becauseMap: bool = false):
if destroying: return
destroying = true
trySplit()
tryRefract()
await destroy(becauseMap)
if autoDestroyAnimation:
animator.play("destroy")
await animator.animation_finished
queue_free()
func trySplit():
if is_instance_valid(launcher) and !isChildSplit:
var launcherSplit = launcher.fields.get(FieldStore.Entity.BULLET_SPLIT)
for i in range(MathTool.shrimpRate(launcherSplit)):
split(i, launcherSplit, launcherSplit - floor(launcherSplit))
func tryRefract():
if is_instance_valid(launcher) and !isChildRefract:
var value = launcher.fields.get(FieldStore.Entity.BULLET_REFRACTION)
var entity = EntityTool.findClosetEntity(position, get_tree(), !launcher.isPlayer(), launcher.isPlayer())
for i in range(MathTool.shrimpRate(value)):
refract(entity, i, value, value - floor(value))
# 抽象方法
func ai():
@@ -103,24 +120,32 @@ func succeedToHit(_dmg: float):
pass
func register():
pass
func split(_index: int, _total: int, _lastBullet: float):
pass
func refract(_entity: EntityBase, _index: int, _total: int, _lastBullet: float):
pass
static func generate(
bullet: PackedScene,
launchBy: EntityBase,
spawnPosition: Vector2,
spawnRotation: float,
asChildSplit: bool = false,
asChildRefract: bool = false,
addToWorld: bool = true
):
var extraCount = launchBy.fields.get(FieldStore.Entity.EXTRA_BULLET_COUNT)
var count = 1 + floor(extraCount) + int(MathTool.rate(extraCount - floor(extraCount)))
var count = 1 + MathTool.shrimpRate(extraCount)
var instances = []
for i in range(count):
var instance: BulletBase = bullet.instantiate()
if launchBy.useEnergy(instance.needEnergy):
instance.isChildSplit = asChildSplit
instance.isChildRefract = asChildRefract
instance.launcher = launchBy
instance.position = spawnPosition
instance.rotation = spawnRotation + deg_to_rad(randf_range(-launchBy.fields.get(FieldStore.Entity.OFFSET_SHOOT), launchBy.fields.get(FieldStore.Entity.OFFSET_SHOOT)))
if addToWorld:
WorldManager.rootNode.add_child(instance)
WorldManager.rootNode.call_deferred("add_child", instance)
instances.append(instance)
return len(instances)
+27 -23
View File
@@ -7,37 +7,39 @@ signal healthChanged(health: float)
signal energyChanged(energy: float)
const TITLE_FLAG = INF
var fields = {
# 数值上限
"生存": TITLE_FLAG,
FieldStore.Entity.HEAL_ABILITY: 1,
FieldStore.Entity.MAX_HEALTH: 100,
FieldStore.Entity.MAX_ENERGY: 200,
FieldStore.Entity.EXTRA_APPLE_MAX: 0,
FieldStore.Entity.EXTRA_BULLET_COUNT: 0,
# 速度
FieldStore.Entity.MOVEMENT_SPEED: 1,
FieldStore.Entity.ATTACK_SPEED: 1,
# 伤害
FieldStore.Entity.DAMAGE_MULTIPILER: 1,
FieldStore.Entity.CRIT_DAMAGE: 1,
# 概率相关
FieldStore.Entity.CRIT_RATE: 0.05,
FieldStore.Entity.PENERATE: 0,
FieldStore.Entity.OFFSET_SHOOT: 3,
FieldStore.Entity.DROP_APPLE_RATE: 0,
FieldStore.Entity.PENARATION_RESISTANCE: 0,
FieldStore.Entity.LUCK_VALUE: 1,
# 治疗
FieldStore.Entity.HEAL_ABILITY: 1,
# 价格减免
FieldStore.Entity.PRICE_REDUCTION: 0,
# 饲料
FieldStore.Entity.FEED_COUNT_SHOW: 3,
FieldStore.Entity.FEED_COUNT_CAN_MADE: 1,
# 储能
"储能": TITLE_FLAG,
FieldStore.Entity.ENERGY_MULTIPILER: 1,
FieldStore.Entity.SAVE_ENERGY: 1,
FieldStore.Entity.ENERGY_REGENERATION: 1,
# 掉落物
FieldStore.Entity.MAX_ENERGY: 200,
"子弹": TITLE_FLAG,
FieldStore.Entity.PENERATE: 0,
FieldStore.Entity.OFFSET_SHOOT: 3,
FieldStore.Entity.EXTRA_BULLET_COUNT: 0,
FieldStore.Entity.BULLET_SPLIT: 0,
FieldStore.Entity.BULLET_REFRACTION: 0,
"速度": TITLE_FLAG,
FieldStore.Entity.MOVEMENT_SPEED: 1,
FieldStore.Entity.ATTACK_SPEED: 1,
"伤害": TITLE_FLAG,
FieldStore.Entity.DAMAGE_MULTIPILER: 1,
FieldStore.Entity.CRIT_RATE: 0.05,
FieldStore.Entity.CRIT_DAMAGE: 1,
"概率": TITLE_FLAG,
FieldStore.Entity.LUCK_VALUE: 1,
"饲料": TITLE_FLAG,
FieldStore.Entity.PRICE_REDUCTION: 0,
FieldStore.Entity.FEED_COUNT_SHOW: 3,
FieldStore.Entity.FEED_COUNT_CAN_MADE: 1,
"掉落物": TITLE_FLAG,
FieldStore.Entity.DROPPED_ITEM_COLLECT_RADIUS: 60,
}
var attackCooldownMap = {
@@ -211,6 +213,7 @@ func trySprint():
await TickTool.until(func(): return !sprinting)
trailing = false
func tryDie(by: BulletBase):
if is_queued_for_deletion(): return
for drop in range(min(len(drops), len(dropCounts))):
var item = drops[drop]
var count = ceil(randf_range(dropCounts[drop].x, dropCounts[drop].y))
@@ -233,6 +236,7 @@ func tryHeal(count: float):
playSound("heal")
healed.emit(heal(count * fields.get(FieldStore.Entity.HEAL_ABILITY)))
healthChanged.emit(health)
func findWeaponAnchor(weaponName: String):
var anchor = $"%weapons".get_node(weaponName)
if anchor is Node2D:
+40 -3
View File
@@ -9,9 +9,20 @@ enum Quality {
EPIC,
LEGENDARY,
}
enum Topic {
SURVIVAL,
ENERGY,
BULLET,
SPEED,
DAMAGE,
PROBABILITY,
FEED,
DROP,
}
@export var displayName: String = "未命名饲料"
@export var quality: Quality = Quality.COMMON
@export var topic: Topic = Topic.SURVIVAL
@export var qualityColorMap = {
Quality.WASTE: Color(),
Quality.COMMON: Color(),
@@ -38,19 +49,45 @@ enum Quality {
Quality.COMMON: - 1,
Quality.RARE: 0,
Quality.EPIC: 1,
Quality.LEGENDARY: 2
Quality.LEGENDARY: 2,
}
@export var topicNameMap = {
Topic.SURVIVAL: "生存",
Topic.ENERGY: "储能",
Topic.BULLET: "子弹",
Topic.SPEED: "速度",
Topic.DAMAGE: "伤害",
Topic.PROBABILITY: "概率",
Topic.FEED: "饲料",
Topic.DROP: "掉落物",
}
@export var topicColorMap = {
Topic.SURVIVAL: Color(),
Topic.ENERGY: Color(),
Topic.BULLET: Color(),
Topic.SPEED: Color(),
Topic.DAMAGE: Color(),
Topic.PROBABILITY: Color(),
Topic.FEED: Color(),
Topic.DROP: Color(),
}
@onready var qualityLabel: Label = $"%quality"
@onready var topicLabel: Label = $"%topic"
@onready var nameLabel: RichTextLabel = $"%label"
func _ready():
qualityLabel.label_settings = qualityLabel.label_settings.duplicate()
topicLabel.label_settings = topicLabel.label_settings.duplicate()
func _physics_process(_delta):
qualityLabel.text = "[%s]" % qualityNameMap[quality]
qualityLabel.label_settings.font_color = color()
qualityLabel.label_settings.font_color = qualityColor()
topicLabel.text = "[%s]" % topicNameMap[topic]
topicLabel.label_settings.font_color = topicColor()
nameLabel.text = "[b]%s[/b]" % displayName
func color():
func qualityColor():
return qualityColorMap[quality] as Color
func topicColor():
return topicColorMap[topic] as Color
func weight(player: EntityBase) -> int:
return floor(clamp(qualityRandomWeight[quality] + luckInfluence[quality] * player.fields[FieldStore.Entity.LUCK_VALUE], 1, INF))
+16 -14
View File
@@ -10,32 +10,34 @@ var collecting: bool = false
@onready var animator: AnimationPlayer = $"%animator"
func _ready():
apply_force(MathTool.randv2_range(30000), MathTool.randv2_range(10))
await TickTool.millseconds(100)
body_entered.connect(
func(body):
if body is ItemDropped and !body.collecting:
if body.item == item:
body.stackCount += stackCount
collect()
)
func _process(_delta):
texture.texture = ItemStore.getTexture(item)
func _physics_process(_delta):
if !is_instance_valid(targetPlayer):
targetPlayer = findPlayer()
targetPlayer = EntityTool.findClosetPlayer(position, WorldManager.tree)
if is_instance_valid(targetPlayer):
if collecting:
linear_velocity = Vector2.ZERO
else:
var direction = (targetPlayer.position - position).normalized()
var speed = 5000.0 / ((targetPlayer.position - position).length() ** (1 / 3.0))
var speed = 10000.0 / ((targetPlayer.position - position).length() ** (1 / 3.0))
apply_central_force(direction * speed)
angular_velocity = linear_velocity.length() ** (1.0 / 2.25) # 角速度=线速度的2.25次根号
if position.distance_to(targetPlayer.position) < targetPlayer.fields.get(FieldStore.Entity.DROPPED_ITEM_COLLECT_RADIUS):
targetPlayer.collectItem(item, stackCount)
collect()
if targetPlayer.sprinting:
apply_central_force((position - targetPlayer.texture.global_position).normalized() * targetPlayer.velocity.length() * 10)
else:
targetPlayer.collectItem(item, stackCount)
collect()
func findPlayer() -> EntityBase:
var result = null
var lastDistance = INF
for player in get_tree().get_nodes_in_group("players"):
if player is EntityBase:
if position.distance_to(player.position) < lastDistance:
lastDistance = position.distance_to(player.position)
result = player
return result
func collect():
collecting = true
animator.play("collect")
+4 -1
View File
@@ -39,7 +39,10 @@ func _physics_process(_delta):
for i in fields.get_children():
fields.remove_child(i)
for i in player.fields:
fields.add_child(FieldShow.create(i, player.fields[i], false, player, true))
if player.fields[i] == EntityBase.TITLE_FLAG:
fields.add_child(QuickUI.graySmallText(i))
else:
fields.add_child(FieldShow.create(i, player.fields[i], false, player, true))
fieldsAnimator.play("show")
if Input.is_action_just_released("showFields"):
fieldsAnimator.play("hide")
+78
View File
@@ -0,0 +1,78 @@
@tool
extends HBoxContainer
class_name WeaponName
enum Quality {
WASTE,
COMMON,
RARE,
EPIC,
LEGENDARY,
}
enum TypeTopic {
IMPACT,
ENERGY,
TEMPERATURE,
}
@export var displayName: String = "未命名武器"
@export var quality: Quality = Quality.COMMON
@export var typeTopic: TypeTopic = TypeTopic.IMPACT
@export var qualityColorMap = {
Quality.WASTE: Color(),
Quality.COMMON: Color(),
Quality.RARE: Color(),
Quality.EPIC: Color(),
Quality.LEGENDARY: Color()
}
@export var qualityNameMap = {
Quality.WASTE: "常见",
Quality.COMMON: "普通",
Quality.RARE: "稀有",
Quality.EPIC: "史诗",
Quality.LEGENDARY: "传说"
}
@export var qualityRandomWeight = {
Quality.WASTE: 20,
Quality.COMMON: 100,
Quality.RARE: 30,
Quality.EPIC: 10,
Quality.LEGENDARY: 5
}
@export var luckInfluence = {
Quality.WASTE: - 0.5,
Quality.COMMON: - 1,
Quality.RARE: 0,
Quality.EPIC: 1,
Quality.LEGENDARY: 2,
}
@export var typeTopicNameMap = {
TypeTopic.IMPACT: "冲击",
TypeTopic.ENERGY: "能量",
TypeTopic.TEMPERATURE: "熔融",
}
@export var typeTopicColorMap = {
TypeTopic.IMPACT: Color(),
TypeTopic.ENERGY: Color(),
TypeTopic.TEMPERATURE: Color(),
}
@onready var qualityLabel: Label = $"%quality"
@onready var typeTopicLabel: Label = $"%typeTopic"
@onready var nameLabel: RichTextLabel = $"%label"
func _ready():
qualityLabel.label_settings = qualityLabel.label_settings.duplicate()
typeTopicLabel.label_settings = typeTopicLabel.label_settings.duplicate()
func _physics_process(_delta):
qualityLabel.text = "[%s]" % qualityNameMap[quality]
qualityLabel.label_settings.font_color = qualityColor()
typeTopicLabel.text = "[%s]" % typeTopicNameMap[typeTopic]
typeTopicLabel.label_settings.font_color = typeTopicColor()
nameLabel.text = "[b]%s[/b]" % displayName
func qualityColor():
return qualityColorMap[quality] as Color
func typeTopicColor():
return typeTopicColorMap[typeTopic] as Color
func weight(player: EntityBase) -> int:
return floor(clamp(qualityRandomWeight[quality] + luckInfluence[quality] * player.fields[FieldStore.Entity.LUCK_VALUE], 1, INF))