feat: keyboard类 & todo

This commit is contained in:
unanmed 2024-01-18 15:25:33 +08:00
parent 60d708e5e3
commit e77ca3e376
14 changed files with 262 additions and 45 deletions

13
idea.md
View File

@ -100,9 +100,18 @@ dam4.png ---- 存档 59
[] 优化各种 ui
[] 怪物脚下加入阴影
[x] 着色器特效
[] 完全删除 core.plugin?(待定)
[] 完全删除 core.plugin,采用 Plugin.register 的形式进行插件编写
[] 通用 Addon 接口
[] 完善加载系统
[] 不同怪物可以在怪物手册中添加一些不同的边框
[] 按住一个按键时显示怪物的攻防血
[] 新的事件系统?
[] 新的事件系统,并丰富自定义事件
[] 事件、eval 内容预编译
[] 新的存档系统,可以注册存档项
[] 渐变切 bgm
[] 新的 Flag 系统,使用 for 申请变量,dispose 释放变量,可设为渲染进程与游戏进程共通变量
[] 注册可录像操作,比如可以在点击的时候执行,自动计入录像
[] 机关门显示绑定怪物
[] 自定义状态栏,通过申请空间进行布局
[] 复写 api,rewrite()
[] 对 vnode 进行简单的包装,提供出显示文字、显示图片等 api 以及修改 css 的 api

View File

@ -3,6 +3,8 @@ import { AudioParamOf, AudioPlayer } from './audio';
import resource from '@/data/resource.json';
import { ResourceController } from '../loader/controller';
// todo: 立体声,可设置音源位置
type Panner = AudioParamOf<PannerNode>;
export class SoundEffect extends AudioPlayer {

View File

@ -1,3 +1,4 @@
// todo: 更改utils.ts的形式,使common文件夹可以同时在渲染进程和游戏进程使用
import { has } from '@/plugin/utils';
export interface EmitableEvent {

View File

@ -6,6 +6,8 @@ import JSZip from 'jszip';
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
import { loading } from './load';
// todo: 应当用register去注册资源类型,然后进行分块处理
interface ResourceData {
image: HTMLImageElement;
arraybuffer: ArrayBuffer;

View File

@ -2,6 +2,8 @@ import { KeyCode } from '@/plugin/keyCodes';
import { deleteWith, generateBinary, spliceBy } from '@/plugin/utils';
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
// todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发
interface HotkeyEvent extends EmitableEvent {
set: (id: string, key: KeyCode, assist: number) => void;
emit: (key: KeyCode, assist: number, type: KeyEmitType) => void;

View File

@ -0,0 +1,122 @@
import { EmitableEvent, EventEmitter } from '@/core/common/eventEmitter';
import { KeyCode } from '@/plugin/keyCodes';
import { gameKey } from '../init/hotkey';
import { unwarpBinary } from './hotkey';
import { deleteWith } from '@/plugin/utils';
import { cloneDeep } from 'lodash-es';
export interface KeyboardEmits {
key: KeyCode;
assist: number;
}
interface KeyboardItem {
key: KeyCode;
text?: string;
x: number;
y: number;
width: number;
height: number;
}
interface AssistManager {
end(): void;
}
interface VirtualKeyboardEvent extends EmitableEvent {
add: (item: KeyboardItem) => void;
remove: (item: KeyboardItem) => void;
emit: (item: KeyboardItem, assist: number, index: number) => void;
}
/**
* 虚拟按键,同一个虚拟按键实例应当只能同时操作一个,但可以显示多个
*/
export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
static list: Keyboard[] = [];
id: string;
keys: KeyboardItem[] = [];
assist: number = 0;
constructor(id: string) {
super();
this.id = id;
Keyboard.list.push(this);
}
/**
* 给虚拟键盘添加一个按键
* @param item 按键信息
*/
add(item: KeyboardItem) {
this.keys.push(item);
this.emit('add', item);
}
/**
* 移除一个按键
* @param item 按键信息
*/
remove(item: KeyboardItem) {
deleteWith(this.keys, item);
this.emit('remove', item);
}
/**
* 创造一个在某些辅助按键已经按下的情况下的作用域,这些被按下的辅助按键还可以被玩家手动取消
* @param assist 辅助按键
* @returns 作用域控制器,用于结束此作用域
*/
withAssist(assist: number): AssistManager {
const thisAssist = this.assist;
this.assist = assist;
return {
end: () => {
this.assist = thisAssist;
}
};
}
/**
* 继承一个按键的按键信息
* @param keyboard 要被继承的按键
* @param offsetX 被继承的按键的横坐标偏移量
* @param offsetY 被继承的按键的纵坐标偏移量
*/
extend(keyboard: Keyboard, offsetX: number = 0, offsetY: number = 0) {
const toClone = cloneDeep(keyboard.keys);
toClone.forEach(v => {
v.x += offsetX;
v.y += offsetY;
});
this.keys.push(...toClone);
}
/**
* 触发按键
* @param key 要触发的按键
*/
emitKey(key: KeyboardItem, index: number) {
const ev = generateKeyboardEvent(key.key, this.assist);
gameKey.emitKey(key.key, this.assist, 'up', ev);
this.emit('emit', key, this.assist, index);
}
static get(id: string) {
return this.list.find(v => v.id === id);
}
}
export function generateKeyboardEvent(key: KeyCode, assist: number) {
const { ctrl, alt, shift } = unwarpBinary(assist);
const ev = new KeyboardEvent('keyup', {
ctrlKey: ctrl,
shiftKey: shift,
altKey: alt
});
return ev;
}

View File

@ -9,6 +9,7 @@ import { GameStorage } from '../storage';
export const mainScope = Symbol.for('@key_main');
export const gameKey = new Hotkey('gameKey', '游戏按键');
// todo: 读取上一个手动存档,存档至下一个存档栏
// ----- Register
gameKey
// --------------------

View File

@ -3,19 +3,21 @@ import { Button, InputNumber } from 'ant-design-vue';
import { mainUi } from './ui';
import { gameKey } from './hotkey';
// todo: 数字类型改为一个输入框,一个加按钮一个减按钮;新增单选框
interface Components {
DefaultSetting: SettingComponent;
BooleanSetting: SettingComponent;
NumberSetting: SettingComponent;
Default: SettingComponent;
Boolean: SettingComponent;
Number: SettingComponent;
HotkeySetting: SettingComponent;
ToolbarEditor: SettingComponent;
}
export function createSettingComponents() {
const com: Components = {
DefaultSetting,
BooleanSetting,
NumberSetting,
Default: DefaultSetting,
Boolean: BooleanSetting,
Number: NumberSetting,
HotkeySetting,
ToolbarEditor
};

View File

@ -6,6 +6,8 @@ import type {
import BoxAnimate from '@/components/boxAnimate.vue';
import { checkAssist } from '../custom/hotkey';
// todo: 新增更改设置的ToolItem
interface Components {
DefaultTool: CustomToolbarComponent;
KeyTool: CustomToolbarComponent<'hotkey'>;

View File

@ -85,7 +85,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
key: string,
name: string,
value: MotaSettingType,
com: SettingComponent = COM.DefaultSetting,
com: SettingComponent = COM.Default,
step: [number, number, number] = [0, 100, 1]
) {
const setting: MotaSettingItem = {
@ -274,11 +274,34 @@ export class SettingDisplayer extends EventEmitter<SettingDisplayerEvent> {
}
}
// todo: 优化存储方式
export const mainSetting = new MotaSetting();
interface SettingStorage {
showHalo: boolean;
frag: boolean;
itemDetail: boolean;
transition: boolean;
antiAlias: boolean;
fontSize: number;
smoothView: boolean;
criticalGem: boolean;
fixed: boolean;
betterLoad: boolean;
autoScale: boolean;
paraLight: boolean;
heroDetail: boolean;
}
const storage = new GameStorage<SettingStorage>(
GameStorage.fromAuthor('AncTe', 'setting')
);
// ----- 监听设置修改
mainSetting.on('valueChange', (key, n, o) => {
const [root, setting] = key.split('.');
if (root === 'screen') {
handleScreenSetting(setting, n, o);
} else if (root === 'action') {
@ -367,28 +390,30 @@ function handleUtilsSetting<T extends number | boolean>(
}
// ----- 游戏的所有设置项
// todo: 虚拟键盘缩放,小地图楼传缩放
mainSetting
.register(
'screen',
'显示设置',
new MotaSetting()
.register('fullscreen', '全屏游戏', false, COM.BooleanSetting)
.register('halo', '光环显示', true, COM.BooleanSetting)
.register('itemDetail', '宝石血瓶显伤', true, COM.BooleanSetting)
.register('heroDetail', '勇士显伤', false, COM.BooleanSetting)
.register('transition', '界面动画', false, COM.BooleanSetting)
.register('antiAlias', '抗锯齿', false, COM.BooleanSetting)
.register('fontSize', '字体大小', 16, COM.NumberSetting, [8, 28, 1])
.register('smoothView', '平滑镜头', true, COM.BooleanSetting)
.register('criticalGem', '临界显示方式', false, COM.BooleanSetting)
.register('fullscreen', '全屏游戏', false, COM.Boolean)
.register('halo', '光环显示', true, COM.Boolean)
.register('itemDetail', '宝石血瓶显伤', true, COM.Boolean)
.register('heroDetail', '勇士显伤', false, COM.Boolean)
.register('transition', '界面动画', false, COM.Boolean)
.register('antiAlias', '抗锯齿', false, COM.Boolean)
.register('fontSize', '字体大小', 16, COM.Number, [8, 28, 1])
.register('smoothView', '平滑镜头', true, COM.Boolean)
.register('criticalGem', '临界显示方式', false, COM.Boolean)
.setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击'))
.register('keyScale', '虚拟键盘缩放', 100, COM.Number, [25, 5, 500])
)
.register(
'action',
'操作设置',
new MotaSetting()
.register('autoSkill', '自动切换技能', true, COM.BooleanSetting)
.register('fixed', '定点查看', true, COM.BooleanSetting)
.register('autoSkill', '自动切换技能', true, COM.Boolean)
.register('fixed', '定点查看', true, COM.Boolean)
.register('hotkey', '快捷键', false, COM.HotkeySetting)
.setDisplayFunc('hotkey', () => '')
.register('toolbar', '自定义工具栏', false, COM.ToolbarEditor)
@ -398,37 +423,28 @@ mainSetting
'utils',
'系统设置',
new MotaSetting()
.register('betterLoad', '优化加载', true, COM.BooleanSetting)
.register('autoScale', '自动放缩', true, COM.BooleanSetting)
.register('betterLoad', '优化加载', true, COM.Boolean)
.register('autoScale', '自动放缩', true, COM.Boolean)
)
.register(
'fx',
'特效设置',
new MotaSetting()
.register('paraLight', '野外阴影', true, COM.BooleanSetting)
.register('frag', '打怪特效', true, COM.BooleanSetting)
.register('paraLight', '野外阴影', true, COM.Boolean)
.register('frag', '打怪特效', true, COM.Boolean)
)
.register(
'ui',
'ui设置',
new MotaSetting().register(
'mapScale',
'小地图楼传缩放',
300,
COM.Number,
[50, 50, 1000]
)
);
interface SettingStorage {
showHalo: boolean;
frag: boolean;
itemDetail: boolean;
transition: boolean;
antiAlias: boolean;
fontSize: number;
smoothView: boolean;
criticalGem: boolean;
fixed: boolean;
betterLoad: boolean;
autoScale: boolean;
paraLight: boolean;
heroDetail: boolean;
}
const storage = new GameStorage<SettingStorage>(
GameStorage.fromAuthor('AncTe', 'setting')
);
loading.once('coreInit', () => {
mainSetting.reset({
'screen.fullscreen': !!document.fullscreenElement,

View File

@ -30,12 +30,15 @@ export class GameStorage<T> {
* @param key 存储的名称
* @param value 存储的值
*/
setValue<K extends keyof T>(key: K, value: T[K]): void;
setValue(key: string, value: any): void;
setValue<K extends keyof T>(key: K, value: T[K]) {
this.data[key] = value;
}
getValue<K extends keyof T>(key: K): T[K] | null;
getValue<K extends keyof T>(key: K, defaults: T[K]): T[K];
getValue<T>(key: string, defaults?: T): T;
getValue<K extends keyof T>(key: K, defaults?: T[K]) {
if (this.data[key]) return this.data[key];
else {

View File

@ -18,6 +18,8 @@ import smooth from '@/plugin/fx/smoothView';
import frag from '@/plugin/fx/frag';
import { Mota } from '.';
// todo: 将插件更改为注册形式,分为渲染进程和游戏进程两部分,同时分配优先级
export function resolvePlugin() {
const toForward: [keyof Mota['plugin'], any][] = [
['pop', pop()],

32
src/panel/keyboard.vue Normal file
View File

@ -0,0 +1,32 @@
<template>
<div class="keyboard-container"></div>
</template>
<script lang="ts" setup>
import { Keyboard } from '@/core/main/custom/keyboard';
import { KeyboardEmits } from '@/core/main/custom/keyboard';
import { ref } from 'vue';
const props = defineProps<{
keyboard: Keyboard;
}>();
const [width, height] = (() => {
const key = props.keyboard;
const mw = Math.max(...key.keys.map(v => v.x));
const mh = Math.max(...key.keys.map(v => v.y));
return [mw, mh];
})();
const emits = defineEmits<{
(e: 'keyup', data: KeyboardEmits): void;
}>();
</script>
<style lang="less" scoped>
.keyboard-container {
width: v-bind(width);
height: v-bind(height);
}
</style>

View File

@ -3,10 +3,11 @@ import { MessageApi } from 'ant-design-vue/lib/message';
import { isNil } from 'lodash-es';
import { Animation, sleep, TimingFn } from 'mutate-animate';
import { ref } from 'vue';
import { EVENT_KEY_CODE_MAP } from './keyCodes';
import { EVENT_KEY_CODE_MAP, KeyCode } from './keyCodes';
import axios from 'axios';
import { decompressFromBase64 } from 'lz-string';
import { parseColor } from './webgl/utils';
import { KeyboardEmits } from '@/core/main/custom/keyboard';
type CanParseCss = keyof {
[P in keyof CSSStyleDeclaration as CSSStyleDeclaration[P] extends string
@ -264,6 +265,11 @@ export function pColor(color: string) {
return `rgba(${arr.join(',')})` as Color;
}
/**
* 删除数组内的某个项,返回删除后的数组
* @param arr 要操作的数组
* @param ele 要删除的项
*/
export function deleteWith<T>(arr: T[], ele: T): T[] {
const index = arr.indexOf(ele);
if (index === -1) return arr;
@ -339,3 +345,18 @@ export function flipBinary(num: number, col: number) {
if (num & n) return num & ~n;
else return num | n;
}
/**
* 唤起虚拟键盘,并获取到一次按键操作
* @param emitAssist 是否可以获取辅助按键,为true时,如果按下辅助按键,那么会立刻返回该按键,
* 否则会视为开关辅助按键
* @param assist 初始化的辅助按键
*/
export function getVitualKeyOnce(
emitAssist: boolean = false,
assist: number = 0
): Promise<KeyboardEmits> {
return new Promise(res => {
res({ key: KeyCode.Unknown, assist: 0 });
});
}