Skip to content

Commit

Permalink
feat: New gamepad/keyboard input system working in more places and fu…
Browse files Browse the repository at this point in the history
…ll viewport support (#120)

* feat(inputs): new inputs usage starting to work in most places

* feat(inputs): WIP selection of viewport elements for interaction

* feat(inputs): WIP fixing screens input
  • Loading branch information
liana-p committed Jul 9, 2023
1 parent 3dc0fc4 commit dacd1da
Show file tree
Hide file tree
Showing 24 changed files with 784 additions and 321 deletions.
9 changes: 9 additions & 0 deletions packages/narrat/examples/games/default/data/screens.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ screens:
x: 0.5
y: 0.5
action: shopButton
- id: tester
enabled: true
text: 'Test'
position:
left: 400
top: 300
width: 200
height: 50
action: tester
- id: parkButton
enabled: false
text: Park
Expand Down
128 changes: 96 additions & 32 deletions packages/narrat/src/components/StartMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,16 @@
<div id="game-title-container">
<h1 id="game-title-text">{{ gameTitle }}</h1>
</div>
<div class="flex flex-col start-menu-buttons-container">
<button
v-if="continueSlot"
class="button menu-button main-menu-button large continue-button override"
@click="continueGame"
>
Continue
</button>
<button
class="button menu-button main-menu-button large start-button override"
@click="startGame"
v-if="hasFreeSlot"
>
New Game
</button>
<button
class="button menu-button main-menu-button large continue-button override"
@click="chooseSaveSlot"
v-if="hasSave"
>
Load Game
</button>
<button
v-for="button in extraButtons"
<div
class="flex flex-col start-menu-buttons-container"
ref="buttonsContainer"
>
<StartMenuButton
v-for="button in buttons"
:key="button.id"
class="button menu-button main-menu-button large override"
:class="`${button.id}-start-menu-button`"
@click="clickExtraButton(button)"
>
{{ button.text }}
</button>
:button="button"
@click="buttonClicked(button)"
/>
</div>
</div>
<Teleport to="#app-container" v-if="popupComponent">
Expand Down Expand Up @@ -81,15 +60,25 @@ import {
getFreeSlot,
findAutoSave,
} from '../utils/save-helpers';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import SaveSlots from './save-slots.vue';
import YesNo from './utils/yes-no.vue';
import StartMenuButton from './start-menu/start-menu-button.vue';
import { StartMenuButtonProps } from './start-menu/start-menu-types';
import { useAudio } from '@/stores/audio-store';
import { inputEvents } from '../utils/InputsListener';
import { useStartMenu } from '@/stores/start-menu-store';
import { CustomStartMenuButton } from '@/exports/plugins';
import ModalWindow from './utils/modal-window.vue';
import { InputListener, useInputs } from '@/stores/inputs-store';
import { useNavigation } from '@/inputs/useNavigation';
const inputListener = ref<InputListener | null>(
useInputs().registerInputListener({}),
);
const buttons = ref<StartMenuButtonProps[]>([]);
const buttonsContainer = ref<HTMLDivElement | null>(null);
const hasSave = ref(false);
const hasFreeSlot = ref(false);
const continueSlot = ref<null | string>(null);
Expand All @@ -102,6 +91,34 @@ const popupComponent = ref<CustomStartMenuButton | false>(false);
const extraButtons = computed(() => startMenuStore.buttons);
const navigation = useNavigation({
mode: 'list',
container: buttonsContainer,
listener: inputListener.value,
onSelected: (index) => {
buttonClicked(buttons.value[index]);
},
})!;
function buttonClicked(button: StartMenuButtonProps) {
switch (button.id) {
case 'continue':
continueGame();
break;
case 'new-game':
startGame();
break;
case 'load-game':
chooseSaveSlot();
break;
case 'exit':
useMain().exitGame();
break;
default:
clickExtraButton(button);
break;
}
}
async function startGame() {
if (hasSave.value && getConfig().saves.mode === 'manual') {
startingGame.value = true;
Expand Down Expand Up @@ -133,7 +150,11 @@ async function confirmStartGame() {
await main.startGame(saveSlot.value!);
}
function clickExtraButton(button: CustomStartMenuButton) {
function clickExtraButton(startMenuButton: StartMenuButtonProps) {
const extraButtonIndex = extraButtons.value.findIndex(
(b) => b.id === startMenuButton.id,
);
const button = extraButtons.value[extraButtonIndex];
if (button.action) {
button.action();
}
Expand Down Expand Up @@ -198,6 +219,7 @@ onMounted(() => {
if (save.lastSaveSlot && getSaveSlot(save.lastSaveSlot)) {
continueSlot.value = save.lastSaveSlot;
}
setupButtons();
listener.value = inputEvents.on('debouncedKeydown', (e) => {
if (e.key === ' ') {
confirmStartGame();
Expand All @@ -206,9 +228,51 @@ onMounted(() => {
continueGame();
}
});
nextTick(() => {
navigation.select(0);
});
});
function setupButtons() {
if (continueSlot.value) {
buttons.value.push({
id: 'continue',
title: 'Continue',
cssClass: 'continue-button',
});
}
if (hasFreeSlot.value) {
buttons.value.push({
id: 'new-game',
title: 'New Game',
cssClass: 'start-button',
});
}
if (hasSave.value) {
buttons.value.push({
id: 'load-game',
title: 'Load Game',
cssClass: 'continue-button',
});
}
for (const button of extraButtons.value) {
buttons.value.push({
id: button.id,
title: button.text,
cssClass: `${button.id}-start-menu-button`,
});
}
buttons.value.push({
id: 'exit',
title: 'Exit',
cssClass: 'exit-button',
});
}
onUnmounted(() => {
if (inputListener.value) {
useInputs().unregisterInputListener(inputListener.value);
}
if (listener.value) {
inputEvents.off('debouncedKeydown', listener.value as any);
}
Expand Down
144 changes: 144 additions & 0 deletions packages/narrat/src/components/debug/debug-jumping.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<template>
<ModalWindow @close="$emit('close')" containerCssClass="jump-menu-container">
<template v-slot:header>
<h3 class="title">Jump to label</h3>
</template>
<template v-slot:body>
<input
type="text"
class="label-input input"
ref="search"
v-model="searchString"
@input="onSearchInput"
/>
<div class="search-results" v-if="matches.length > 0">
<div
class="search-result"
v-for="(match, index) in matches"
:style="getMatchResultStyle(index)"
:key="index"
>
{{ match }}
</div>
</div>
<div v-else><h3>No matches found</h3></div>
</template>
</ModalWindow>
</template>
<script setup lang="ts">
import Fuse from 'fuse.js';
import { useVM } from '@/stores/vm-store';
import { InputListener, useInputs } from '@/stores/inputs-store';
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
import { vm } from '@/vm/vm';
import ModalWindow from '../utils/modal-window.vue';
import { useMain } from '@/lib';
const emit = defineEmits(['close', 'jump']);
let fuse: Fuse<string>;
const inputListener = ref<InputListener | null>(null);
const searchString = ref('');
const matches = ref<string[]>([]);
const matchCursor = ref(0);
const search = ref<HTMLDivElement | null>(null);
function onSearchInput() {
const value = searchString.value;
const result = fuse.search(value);
matches.value = result.map((element) => element.item);
if (
matches.value.length > 0 &&
matchCursor.value > matches.value.length - 1
) {
matchCursor.value = matches.value.length - 1;
}
}
function getMatchResultStyle(index: number) {
if (index === matchCursor.value) {
return {
background: 'var(--light-background)',
};
}
}
const labels = computed(() => {
return Object.keys(vm.script).sort();
});
onMounted(() => {
useMain().debugMode = true;
fuse = new Fuse(labels.value, {
includeScore: true,
});
matches.value = labels.value;
matchCursor.value = 0;
setTimeout(() => {
nextTick(() => {
if (search.value) {
search.value.focus();
}
});
}, 10);
inputListener.value = useInputs().registerInputListener({
up: {
press: () => {
if (matchCursor.value > 0) {
matchCursor.value--;
} else {
matchCursor.value = matches.value.length - 1;
}
},
},
down: {
press: () => {
if (matches.value.length > matchCursor.value + 1) {
matchCursor.value++;
} else {
matchCursor.value = 0;
}
},
},
continue: {
press: () => {
if (
matches.value.length > 0 &&
matchCursor.value < matches.value.length
) {
const match = matches.value[matchCursor.value];
useVM().jumpToLabel(match);
emit('close');
}
},
},
escape: {
press: () => {
emit('close');
},
},
});
});
onUnmounted(() => {
if (inputListener.value) {
useInputs().unregisterInputListener(inputListener.value);
}
useMain().debugMode = false;
});
</script>

<style>
.search-result {
border: 1px solid var(--text-color);
padding: 10px;
font-weight: 700;
font-size: 1.25rem;
width: 100;
}
.jump-menu-container {
width: 80%;
}
</style>
Loading

0 comments on commit dacd1da

Please sign in to comment.