Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aim assist #405

Merged
merged 12 commits into from
Apr 13, 2023
29 changes: 29 additions & 0 deletions docs/general_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,35 @@ There are 2 primary differences between questions with `type` of `MultipleChoice

Questions are recorded in the results database along with a timestamp, the question prompt, a `responseArray` indicating available options and the `response` the user provided. If multiple choice, a `keyArray` is included with `optionKeys` values (as specified) and the `presentedResponses` array provides the response/key pairs in the order they were presented to the user for that question, which is particularly helpful when the order is randomized.

## Aim Assist seetigns
| Parameter Name |Units |Description |
|-----------------------|---------------|---------------------------------------------------------------------------------------------------|
|`aimAssistFoV` |degrees |The field of view in which to assist aim (either on button press or on fire) |
|`aimAssistShowFoV` |`bool` |Show the aim assist field of view on-screen? |
|`aimAssistFoVColor` |`Color4` |The color to draw the aim assist field of view on screen with (if enabled) |
|`aimAssistFoVWidth` |percent |The width of the aim assist indicator drawn on screen (if enabled) |
|`aimAssistSnapOnFire` |`bool` |Does the aim assist snap to targets (if within the FoV) just before a fire event? |
|`aimAssistOnReference` |`bool` |Does the aim assist snap to reference targets (both for snap on fire and button press)? |
|`aimAssistOnReal` |`bool` |Does the aim assist snap to non-reference targets (both for snap on fire and button press)? |
|`aimAssistMaxSpeed` |deg/s |The maximum (per frame) speed that the aim assist is allowed to rotate the view at |

Aim assist in FPSci has 2 primary modes. In push-button aim assist, the key bound to `autoAim` in the [key map](keymap.md) automatically snaps the aim to the center of the nearest target to the current crosshair position. When `aimAssistSnapOnFire` is set to `true` the application automatically runs the same aim assist snapping method just prior to whenever the weapon is fired (at a maximum of once per frame).

Note that the `aimAssistFoV` is measured in all directions from the current aim direction (i.e., a 30° `aimAssistFoV` value implies a ±30° range of target that can be found by aim assist). Additionally when `aimAssistMaxSpeed` is specified fire events are not delayed, so when used with `aimAssistSnapOnFire` fire events may still result in a miss (if the displacement of the `aimAssistMaxSpeed` over a frame time is less than what is required to reach the target).

Defaults for all values above are provided in the example below:

```
"aimAssistFoV" : 0, // Defaults to aim assist off (0 FoV)
"aimAssistShowFoV": false, // Don't draw the aim assist FoV to the screen by default
"aimAssistFoVColor": Color4(0,0,0,1); // Default to an opaque black FoV indicator when enabled
"aimAsssistFoVWidth": 1; // Default to 1% of the screen as the width of the FoV indicator
"aimAssistSnapOnFire": false, // Default to not snapping aim when firing (aim assist can be activated when FoV > 0 using the "aimAssist" key bind instead)
"aimAssistOnReference": true, // Default to allowing aim assist on reference targets (when enabled)
"aimAssistOnReal": true, // Default to allowing aim assist on real targets (when enabled)
"aimAssistMaxSpeed": 1000000 // Default max speed is high enough to do full rotations every frame (360deg * 1000Hz = 360k deg/s < 1 Mdeg/s)
```

## HUD settings
| Parameter Name |Units | Description |
|-----------------------|-----------|-----------------------------------------------------------------------------------------------------------------------|
Expand Down
8 changes: 5 additions & 3 deletions docs/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ When `developerMode=false` the following keys are used:
|`D` |Move backward |
|`L Ctrl` |Crouch |
|`Space` |Jump |
|`Esc` or `Tab` |Open in-game menu |
|`Esc` |Open in-game menu |
|`L Mouse` |Fire the weapon during trial |
|`R Shift` |Fire the weapon pre-trial |
|`L Shift` |Fire the weapon pre-trial |
|`Tab` |Aim assist snap to target (if enabled) |
|`-` |Quit the application |

### Developer Mode
Expand Down Expand Up @@ -57,7 +58,8 @@ This file associates each of the map names outlined below to an array of `GKey`
|Open in-game menu |`openMenu` |`["Esc"]` |
|Fire the weapon during trial |`shoot` |`["L Mouse"]` |
|Use a scope |`scope` |`["R Mouse"]` |
|Fire the weapon pre-trial |`dummyShoot` |`["R Shift"]` |
|Fire the weapon pre-trial |`dummyShoot` |`["L Shift"]` |
|Snap aim to nearest target |`autoAim` |`["Tab"]` |
|Quit the application |`quit` |`["Keypad -", "Pause"]`|
|Select a waypoint |`selectWaypoint` |`["L Mouse"]` |
|Drop a waypoint |`dropWaypoint` |`["Q"]` |
Expand Down
62 changes: 60 additions & 2 deletions source/FPSciApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,8 @@ void FPSciApp::onNetwork() {


void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) {
const shared_ptr<PlayerEntity>& player = scene()->typedEntity<PlayerEntity>("player");

// TODO: this should eventually probably use sdt instead of rdt
RealTime currentRealTime;
if (m_lastOnSimulationRealTime == 0) {
Expand Down Expand Up @@ -907,6 +909,11 @@ void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) {
damagePerShot = (float)fireDuration * weapon->config()->damagePerSecond;
}

// Auto aim if requested
if (numShots > 0 && trialConfig->aimAssist.snapOnFire && canAutoAim()) {
assistAim(sess->hittableTargets(), sdt);
}

// Actually shoot here
m_currentWeaponDamage = damagePerShot; // pass this to the callback where weapon damage is applied
bool shotFired = false;
Expand Down Expand Up @@ -983,7 +990,7 @@ void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) {
// Handle highlighting for selected target
waypointManager->updateSelected();
// Handle player motion recording here
waypointManager->updatePlayerPosition(p->getCameraFrame().translation);
waypointManager->updatePlayerPosition(player->getCameraFrame().translation);
}

// Example GUI dynamic layout code. Resize the debugWindow to fill
Expand All @@ -1001,6 +1008,7 @@ void FPSciApp::onSimulation(RealTime rdt, SimTime sdt, SimTime idt) {
m_lastOnSimulationRealTime = m_lastOnSimulationRealTime + rdt;
m_lastOnSimulationSimTime = m_lastOnSimulationSimTime + sdt;
m_lastOnSimulationIdealSimTime = m_lastOnSimulationIdealSimTime + idt;
m_lastSdt = sdt;

// Clear button press state
shootButtonJustPressed = false;
Expand Down Expand Up @@ -1237,6 +1245,44 @@ void FPSciApp::setScopeView(bool scoped) {
player->turnScale = currentTurnScale(); // Scale sensitivity based on the field of view change here
}

inline bool FPSciApp::canAutoAim() {
// Check auto aim enabled (FoV > 0) and in a valid state
return trialConfig->aimAssist.fov > 0 &&
((sess->currentState == PresentationState::referenceTarget && trialConfig->aimAssist.allowOnReference) ||
(sess->currentState == PresentationState::trialTask && trialConfig->aimAssist.allowOnReal));
}

void FPSciApp::assistAim(const Array<shared_ptr<TargetEntity>>& targets, const SimTime dt) {
if (!canAutoAim()) return; // Early exit for no aim assist
const shared_ptr<PlayerEntity>& player = scene()->typedEntity<PlayerEntity>("player");

// Iterate through targets to find closest
float minAngle = trialConfig->aimAssist.fov;
shared_ptr<TargetEntity> closestTarget;
for (shared_ptr<TargetEntity> t : targets) {
Vector3 dir = player->frame().lookVector();
Vector3 diff = t->frame().translation - player->frame().translation;
float angle = 180 / pif() * acosf(dot(dir, diff)/(dir.magnitude()*diff.magnitude()));
if (angle < minAngle) {
minAngle = angle;
closestTarget = t; // Record closest targets within the FoV
}
}
if (isNull(closestTarget)) return; // No valid target in FoV, leave here

// Check for speed violation
float interp = 1.f;
const float maxAngle = trialConfig->aimAssist.maxSpeedDegS * dt;
if (minAngle > maxAngle) {
// We are over max speed, move towards the target but not all the way there
interp = maxAngle / minAngle;
}

// Aim at the closest target in the FoV
player->lookAt(closestTarget->frame().translation, interp);
playerCamera->setFrame(player->getCameraFrame()); // Update camera from player early
}

void FPSciApp::hitTarget(shared_ptr<TargetEntity> target) {
// Damage the target
float damage = m_currentWeaponDamage;
Expand Down Expand Up @@ -1346,7 +1392,7 @@ void FPSciApp::onUserInput(UserInput* ui) {
}
else if (notNull(player)) { // Zero the player velocity and rotation when in the setting menu
player->setDesiredOSVelocity(Vector3::zero());
player->setDesiredAngularVelocity(0.0, 0.0);
player->setDesiredRotationChange(0.0, 0.0);
}

// Handle scope behavior
Expand Down Expand Up @@ -1390,6 +1436,9 @@ void FPSciApp::onUserInput(UserInput* ui) {

for (GKey dummyShoot : keyMap.map["dummyShoot"]) {
if (ui->keyPressed(dummyShoot) && (sess->currentState == PresentationState::referenceTarget) && !m_userSettingsWindow->visible()) {
if (canAutoAim() && trialConfig->aimAssist.snapOnFire) {
assistAim(sess->hittableTargets(), m_lastSdt);
}
Array<shared_ptr<Entity>> dontHit;
dontHit.append(m_explosions);
dontHit.append(sess->unhittableTargets());
Expand All @@ -1403,6 +1452,15 @@ void FPSciApp::onUserInput(UserInput* ui) {
}
}

// Handle auto aim if requested
if (canAutoAim()) {
for (GKey autoAim : keyMap.map["autoAim"]) {
if (ui->keyDown(autoAim)) {
assistAim(sess->hittableTargets(), m_lastSdt);
}
}
}

// Update reticle from user settings change (if needed)
if (reticleConfig != currentUser()->reticle || m_userSettingsWindow->visible()) {
bool updateReticlePreview = false;
Expand Down
7 changes: 6 additions & 1 deletion source/FPSciApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class FPSciApp : public GApp {
float m_debugMenuHeight = 0.0f; ///< Height of the debug menu when in developer mode

RealTime m_lastJumpTime = 0.0f; ///< Time of last jump
SimTime m_lastSdt; ///< Last sim delta time
public:
RealTime m_lastOnSimulationRealTime = 0.0f; ///< Wall clock time last onSimulation finished
SimTime m_lastOnSimulationSimTime = 0.0f; ///< Simulation time last onSimulation finished
Expand Down Expand Up @@ -277,7 +278,11 @@ class FPSciApp : public GApp {
void setMouseInputMode(MouseInputMode mode = MOUSE_FPM);
/** reads current user settings to update sensitivity in the controller */
void updateMouseSensitivity();


inline bool canAutoAim();
void assistAim(const Array<shared_ptr<TargetEntity>>& targets, const SimTime dt); // Automatically aim at the nearest (valid) target
void drawAimAssistFov(RenderDevice* rd, Vector2 resolution); // Draw the aim assist FoV to the screen as a ring

/** Initialize an experiment */
void initExperiment();

Expand Down
32 changes: 32 additions & 0 deletions source/FPSciGraphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ void FPSciApp::drawDelayed2DElements(RenderDevice* rd, Vector2 resolution) {
if (trialConfig->hud.enable) {
drawHUD(rd, resolution);
}

if (trialConfig->aimAssist.showFoV && trialConfig->aimAssist.fov > 0) {
drawAimAssistFov(rd, resolution);
}
}

void FPSciApp::drawClickIndicator(RenderDevice* rd, String mode, Vector2 resolution) {
Expand Down Expand Up @@ -550,3 +554,31 @@ void FPSciApp::drawHUD(RenderDevice *rd, Vector2 resolution) {
}
}

void FPSciApp::drawAimAssistFov(RenderDevice* rd, Vector2 resolution) {
const int segments = 50;
const float fov = trialConfig->aimAssist.fov * pif() / 180.f;
const float radScale = 1 - trialConfig->aimAssist.fovWidth / 100.f;

// Use a copy of the camera to get SS coords of radius
Camera copyCam = *playerCamera;
copyCam.lookAt(copyCam.frame().translation + Vector3(0.f, 0.f, 1.0f)); // Redirect camera to get look vector into the XZ plane (z axis)
Vector3 viewDir = -copyCam.frame().lookVector(); // Get view direction vector
viewDir = viewDir * Matrix3::fromAxisAngle(Vector3::unitY(), fov); // Rotate about Y-axis (yaw rotation)
const Vector3 loc = copyCam.project(copyCam.frame().translation + viewDir, resolution); // Project yaw rotation into camera screen space
const float R = loc.x - resolution.x / 2.f; // Find radius by subtracting SS coordinate from center of screen x-coordinate

// Create the segments
for (int i = 0; i < segments; i++) {
if (i % 2 == 1) continue;
const float inc = static_cast<float>(2 * pif() / segments);
const float theta = -i * inc;
Vector2 center = resolution / 2.0f;
Array<Vector2> verts = {
center + Vector2(R * sinf(theta), -R * cosf(theta)),
center + Vector2(R * sinf(theta + inc), -R * cosf(theta + inc)),
center + Vector2(radScale * R * sinf(theta + inc), -radScale * R * cosf(theta + inc)),
center + Vector2(radScale * R * sinf(theta), -radScale * R * cosf(theta))
};
Draw::poly2D(verts, rd, trialConfig->aimAssist.fovColor);
}
}
31 changes: 31 additions & 0 deletions source/FpsConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,37 @@ Any CommandConfig::addToAny(Any a, const bool forceAll) const {
return a;
}

void AimAssistConfig::load(AnyTableReader reader, int settingsVersion) {
switch (settingsVersion) {
case 1:
reader.getIfPresent("aimAssistFoV", fov);
reader.getIfPresent("aimAssistShowFoV", showFoV);
reader.getIfPresent("aimAssistFoVColor", fovColor);
reader.getIfPresent("aimAssistFoVWidth", fovWidth);
reader.getIfPresent("aimAssistSnapOnFire", snapOnFire);
reader.getIfPresent("aimAssistOnReference", allowOnReference);
reader.getIfPresent("aimAssistOnReal", allowOnReal);
reader.getIfPresent("aimAssistMaxSpeed", maxSpeedDegS);
break;
default:
throw format("Did not recognize settings version: %d", settingsVersion);
break;
}
}

Any AimAssistConfig::addToAny(Any a, const bool forceAll) const{
AimAssistConfig def;
if (forceAll || fov != def.fov) a["aimAssistFoV"] = fov;
if (forceAll || showFoV != def.showFoV) a["aimAssistShowFoV"] = showFoV;
if (forceAll || fovColor != def.fovColor) a["aimAssistFoVColor"] = fovColor;
if (forceAll || fovWidth != def.fovWidth) a["aimAssistFoVWidth"] = fovWidth;
if (forceAll || snapOnFire != def.snapOnFire) a["aimAssistSnapOnFire"] = snapOnFire;
if (forceAll || allowOnReference != def.allowOnReference) a["aimAsssistOnReference"] = allowOnReference;
if (forceAll || allowOnReal != def.allowOnReal) a["aimAssistOnReal"] = allowOnReal;
if (forceAll || maxSpeedDegS != def.maxSpeedDegS) a["aimAssistMaxSpeed"] = maxSpeedDegS;
return a;
}

Question::Question(const Any& any) {
int settingsVersion = 1;
FPSciAnyTableReader reader(any);
Expand Down
22 changes: 22 additions & 0 deletions source/FpsConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,25 @@ class CommandConfig {
Any addToAny(Any a, const bool forceAll = false) const;
};

class AimAssistConfig {
public:
float fov = 0.f; ///< Field of view for aim assist, set to 0 to disable

bool showFoV = false; ///< Display the aim assist FoV as a HUD element
Color4 fovColor = Color4(0, 0, 0, 1); ///< Color for FoV display (when shown)
float fovWidth = 1; ///< Width of the FoV display (as a percentage of the screen)

bool snapOnFire = true; ///< Snap the aim to the target on fire (if within the aim assist fov)
bool allowOnReference = true; ///< Allow aim assist on reference target
bool allowOnReal = true; ///< Allow aim assist on real targets

float maxSpeedDegS = 1000000.f; ///< Maximum aim assist speed in degrees/s

AimAssistConfig() {};
void load(AnyTableReader reader, int settingsVersion = 1);
Any addToAny(Any a, const bool forceAll = false) const;
};

class Question {
public:
enum Type {
Expand Down Expand Up @@ -374,6 +393,7 @@ class FpsConfig : public ReferenceCountedObject {
WeaponConfig weapon; ///< Weapon to be used
MenuConfig menu; ///< User settings window configuration
CommandConfig commands; ///< Commands to run during execution
AimAssistConfig aimAssist; ///< Aim assist configuration
Array<Question> questionArray; ///< Array of questions for this experiment/trial

// Constructors
Expand Down Expand Up @@ -403,6 +423,7 @@ class FpsConfig : public ReferenceCountedObject {
logger.load(reader, settingsVersion);
menu.load(reader, settingsVersion);
commands.load(reader, settingsVersion);
aimAssist.load(reader, settingsVersion);
switch (settingsVersion) {
case 1:
reader.getIfPresent("scene", scene);
Expand Down Expand Up @@ -439,6 +460,7 @@ class FpsConfig : public ReferenceCountedObject {
a = logger.addToAny(a, forceAll);
a = menu.addToAny(a, forceAll);
a = commands.addToAny(a, forceAll);
a = aimAssist.addToAny(a, forceAll);
a["questions"] = questionArray;
a["weapon"] = weapon.toAny(forceAll);
return a;
Expand Down
1 change: 1 addition & 0 deletions source/KeyMapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ KeyMapping::KeyMapping() {
map.set("shoot", Array<GKey>{ GKey::LEFT_MOUSE });
map.set("scope", Array<GKey>{ GKey::RIGHT_MOUSE});
map.set("dummyShoot", Array<GKey>{ GKey::LSHIFT });
map.set("autoAim", Array<GKey>{GKey::TAB});
map.set("dropWaypoint", Array<GKey>{ (GKey)'q' });
map.set("toggleRecording", Array<GKey>{ (GKey)'r' });
map.set("toggleRenderWindow", Array<GKey>{ (GKey)'1' });
Expand Down
Loading