研究归属: Project Vampirefall - Tech/Mechanics
创建日期: 2025-12-04
优先级: ⭐⭐⭐⭐ (中高)
瞄准辅助系统 (Aim Assist) 是一种游戏系统,用于在玩家瞄准时提供一定程度的自动化帮助,以降低操作难度、提升打击感和游戏体验。它在主机游戏和移动游戏中尤为重要,因为手柄和触屏的精度天然低于鼠标键盘。
核心功能包括:
MagnetismRadius = BaseRadius * (1 + RangeFactor * (1 - NormalizedDistance))
其中:
- BaseRadius: 基础磁吸半径(屏幕空间像素)
- RangeFactor: 距离衰减系数(0-1)
- NormalizedDistance: 归一化距离(0 = 近距离, 1 = 最大射程)
示例:
Radius = 100 * (1 + 0.5 * (1 - 0.2)) = 140pxRadius = 100 * (1 + 0.5 * (1 - 1.0)) = 100px使用 Sigmoid 函数平滑过渡:
AttractStrength = MaxStrength / (1 + e^(-k * (Distance - Threshold)))
参数说明:
- MaxStrength: 最大拉力系数(0-1)
- k: 曲线陡峭度(越大越陡)
- Distance: 准星与目标中心距离
- Threshold: 触发瞄准辅助的阈值
graph LR
A[准星位置] --> B{距离检测}
B -->|距离 < 阈值| C[计算磁吸向量]
B -->|距离 >= 阈值| D[无辅助]
C --> E[应用 Sigmoid 平滑]
E --> F[调整准星速度]
瞄准辅助的设计必须平衡以下三要素:
[!IMPORTANT] 好的瞄准辅助应该让玩家觉得”自己打得很准”,而不是”系统在帮我瞄准”。这是一种心理学欺骗 (Perceptual Deception)。
瞄准辅助可以通过预测性补偿 (Predictive Compensation) 缓解这一问题:
// 预测目标未来位置
Vector3 predictedPos = target.position + target.velocity * inputLatency;
Vector3 aimDirection = (predictedPos - player.position).normalized;
Vampirefall 的特殊性在于塔防自动索敌 + 玩家手动射击的混合模式:
flowchart TD
A[玩家输入] --> B{射击模式}
B -->|手动模式| C[完整瞄准辅助]
B -->|半自动模式| D[仅磁吸 + 优先级提示]
B -->|塔防模式| E[完全自动化]
C --> F[计算最近敌人]
D --> G[高亮优先目标]
E --> H[塔自动索敌]
F --> I[应用磁吸]
G --> J[玩家决策]
I --> K[射击]
J --> K
| 难度等级 | 磁吸半径 | 子弹磁吸 | 自动旋转 | 锁定持续时间 |
|---|---|---|---|---|
| 简单 | 150px | 强 (20°) | 弱 (10%) | 1.5s |
| 普通 | 100px | 中 (10°) | 无 | 1.0s |
| 困难 | 60px | 弱 (5°) | 无 | 0.5s |
| 专家 | 30px | 无 | 无 | 0.2s |
[System.Serializable]
public class AimAssistConfig
{
[Header("磁吸参数")]
[Tooltip("基础磁吸半径(屏幕空间)")]
[Range(0, 200)]
public float baseMagnetismRadius = 100f;
[Tooltip("距离衰减系数")]
[Range(0, 1)]
public float rangeFalloff = 0.5f;
[Header("强度控制")]
[Tooltip("最大拉力系数")]
[Range(0, 1)]
public float maxAttractionStrength = 0.7f;
[Tooltip("Sigmoid 曲线陡峭度")]
[Range(1, 10)]
public float sigmoidSteepness = 5f;
[Header("输入补偿")]
[Tooltip("预测延迟时间(秒)")]
[Range(0, 0.2f)]
public float predictionLatency = 0.08f;
[Header("子弹磁吸")]
[Tooltip("子弹偏转最大角度")]
[Range(0, 30)]
public float bulletMagnetismAngle = 10f;
[Header("难度系数")]
[Tooltip("当前难度等级 (1-4)")]
[Range(1, 4)]
public int difficultyLevel = 2;
}
public class AimAssistController : MonoBehaviour
{
public AimAssistConfig config;
private Camera mainCam;
void Start()
{
mainCam = Camera.main;
}
/// <summary>
/// 获取最佳磁吸目标
/// </summary>
public Transform GetBestTarget(Vector3 aimPoint, LayerMask targetLayer)
{
Collider[] targets = Physics.OverlapSphere(
transform.position,
50f, // 最大检测距离
targetLayer
);
Transform bestTarget = null;
float minScore = float.MaxValue;
foreach (var target in targets)
{
// 计算屏幕空间距离
Vector2 screenPos = mainCam.WorldToScreenPoint(target.transform.position);
Vector2 aimScreenPos = mainCam.WorldToScreenPoint(aimPoint);
float screenDist = Vector2.Distance(screenPos, aimScreenPos);
// 计算世界空间距离
float worldDist = Vector3.Distance(transform.position, target.transform.position);
// 动态磁吸半径(距离越远半径越小)
float radius = CalculateMagnetismRadius(worldDist);
// 不在磁吸范围内则跳过
if (screenDist > radius) continue;
// 评分:屏幕距离 + 优先级权重
float priority = GetTargetPriority(target);
float score = screenDist / radius + (1f - priority) * 100f;
if (score < minScore)
{
minScore = score;
bestTarget = target.transform;
}
}
return bestTarget;
}
/// <summary>
/// 计算动态磁吸半径
/// </summary>
float CalculateMagnetismRadius(float worldDistance)
{
float normalizedDist = Mathf.Clamp01(worldDistance / 50f); // 假设最大射程 50m
return config.baseMagnetismRadius * (1 + config.rangeFalloff * (1 - normalizedDist));
}
/// <summary>
/// 获取目标优先级(Boss > 精英 > 普通小怪)
/// </summary>
float GetTargetPriority(Collider target)
{
var enemy = target.GetComponent<EnemyController>();
if (enemy == null) return 0.3f;
return enemy.type switch
{
EnemyType.Boss => 1.0f,
EnemyType.Elite => 0.7f,
EnemyType.Normal => 0.5f,
_ => 0.3f
};
}
}
/// <summary>
/// 应用磁吸到准星移动
/// </summary>
public Vector2 ApplyMagnetism(Vector2 rawInput, Transform target)
{
if (target == null) return rawInput;
// 计算目标在屏幕空间的方向
Vector2 targetScreenPos = mainCam.WorldToScreenPoint(target.position);
Vector2 currentCrosshair = new Vector2(Screen.width / 2, Screen.height / 2);
Vector2 toTarget = (targetScreenPos - currentCrosshair).normalized;
// 计算距离
float distance = Vector2.Distance(targetScreenPos, currentCrosshair);
// Sigmoid 强度计算
float strength = CalculateSigmoidStrength(distance);
// 混合原始输入和磁吸向量
Vector2 assistedInput = Vector2.Lerp(rawInput, toTarget, strength * config.maxAttractionStrength);
return assistedInput;
}
/// <summary>
/// Sigmoid 强度曲线
/// </summary>
float CalculateSigmoidStrength(float distance)
{
float threshold = config.baseMagnetismRadius * 0.5f; // 磁吸半径的一半为中心点
float x = config.sigmoidSteepness * (distance - threshold) / threshold;
return 1f / (1f + Mathf.Exp(x)); // 距离越近,强度越大
}
public class BulletMagnetism : MonoBehaviour
{
public float magnetismAngle = 10f; // 最大偏转角度
public LayerMask targetLayer;
void FixedUpdate()
{
// 检测前方锥形范围内的敌人
Collider[] hits = Physics.OverlapSphere(transform.position, 5f, targetLayer);
Transform closestTarget = null;
float minAngle = magnetismAngle;
foreach (var hit in hits)
{
Vector3 toTarget = (hit.transform.position - transform.position).normalized;
float angle = Vector3.Angle(transform.forward, toTarget);
if (angle < minAngle)
{
minAngle = angle;
closestTarget = hit.transform;
}
}
if (closestTarget != null)
{
// 平滑转向目标
Vector3 newDirection = Vector3.RotateTowards(
transform.forward,
(closestTarget.position - transform.position).normalized,
magnetismAngle * Mathf.Deg2Rad * Time.fixedDeltaTime,
0f
);
transform.rotation = Quaternion.LookRotation(newDirection);
}
}
}
public class TouchAimController : MonoBehaviour
{
[Header("触屏死区")]
public float deadZoneRadius = 30f; // 中心死区半径(像素)
public AnimationCurve sensitivityCurve; // 灵敏度曲线
Vector2 ProcessTouchInput(Vector2 touchDelta)
{
float magnitude = touchDelta.magnitude;
// 死区内忽略输入
if (magnitude < deadZoneRadius)
return Vector2.zero;
// 减去死区后重新归一化
float adjustedMagnitude = (magnitude - deadZoneRadius) / (Screen.width * 0.5f - deadZoneRadius);
// 应用灵敏度曲线
float sensitivity = sensitivityCurve.Evaluate(adjustedMagnitude);
return touchDelta.normalized * sensitivity;
}
}
public class GyroscopeAim : MonoBehaviour
{
public float gyroSensitivity = 2f;
private Quaternion gyroInitialRotation;
void Start()
{
Input.gyro.enabled = true;
gyroInitialRotation = Input.gyro.attitude;
}
void Update()
{
if (!Input.gyro.enabled) return;
// 获取陀螺仪相对旋转
Quaternion gyroRotation = Quaternion.Inverse(gyroInitialRotation) * Input.gyro.attitude;
// 转换为欧拉角(仅使用 Yaw 和 Pitch)
Vector3 euler = gyroRotation.eulerAngles;
// 应用到相机旋转(微调瞄准)
transform.Rotate(new Vector3(-euler.x, euler.y, 0) * gyroSensitivity * Time.deltaTime);
}
}
[CreateAssetMenu(fileName = "AimAssistPreset", menuName = "Vampirefall/Aim Assist Preset")]
public class AimAssistPreset : ScriptableObject
{
public string presetName = "Default";
public AimAssistConfig config;
[Header("难度适配表")]
public AimAssistConfig easyConfig;
public AimAssistConfig normalConfig;
public AimAssistConfig hardConfig;
public AimAssistConfig expertConfig;
public AimAssistConfig GetConfigForDifficulty(int difficulty)
{
return difficulty switch
{
1 => easyConfig,
2 => normalConfig,
3 => hardConfig,
4 => expertConfig,
_ => normalConfig
};
}
}
启发:
文档版本: v1.0
最后更新: 2025-12-04
维护者: Vampirefall Tech Team