ui控制系统

This commit is contained in:
unanmed 2022-11-14 17:11:23 +08:00
parent f3c96f7a07
commit da771dae91
23 changed files with 6559 additions and 3014 deletions

View File

@ -1,2 +1 @@
vite.config.ts
*.js

14
components.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
BookOne: typeof import('./src/components/bookOne.vue')['default']
BoxAnimate: typeof import('./src/components/boxAnimate.vue')['default']
Scroll: typeof import('./src/components/scroll.vue')['default']
}
}

46
idea.md Normal file
View File

@ -0,0 +1,46 @@
## 怪物
### 第二章 智慧
- 怪物拥有火、水等属性,在不同天气下属性不同
- 同化
- 同化+阻击
- 电摇嘲讽:到同行或同列直接怼过去,门和墙撞碎,不消耗钥匙,攻击怪物,捡道具,改变 bgm可吃补给用
- 抢夺:抢夺你的装备并使用(简单难度中,怪物使用时属性降低)
- 抢夺 2.0:抢夺你的道具(怪物手册和楼传除外),并按照左上、右上、右下、左下的顺序依次放到地图的四角(简单无效)
- 抢夺 3.0:抢夺装备,但每过 10 回合便按照 2.0 方法扔至地图四角(因为怪物觉得装备不好使),然后重新抢夺你的装备(简单无效)
- 天气之子:控制天气,包括下雨、下雪、晴天、阴天等,
#### Boss
音游,音乐为一个被遗忘的夜晚,可选简单与困难,困难可获得成就冰与火之舞
玩法一个会转动的圆盘带有一个伸出去的把手boss 从四面八方射子弹,当子弹恰好落到把手前端时,点击按键或屏幕,可以抵挡子弹,简单难度 3 条命,困难 1 条。简单难度音符密度低。困难难度为冰与火之舞的节奏。简单难度判定时间为前后各 150ms困难为 75ms
## 主角
- 反抢夺1 级反 1.02 级反 2.03 级反 3.0,用智慧点点
- 学习:学习选定怪物的选定技能(不能学光环),消耗智慧点,初始 400但每次消耗点数+100持续 3 场战斗
- 铸剑为盾:主动技能,减少攻击,增加防御
- 血之代偿:消耗一定血量,战前对怪物造成同等数值的伤害
## 机制
### 通用
- 实时天气
- 成就系统
- 装备合成、装备(孔)强化
### 第二章 智慧
- 按下一个开关,所有红门变黄门,黄门变蓝门,蓝门变红门
## 成就
- 学坏了:学习电摇嘲讽
- 我就是傻子:不学习反抢夺通过第二章
- 真能刷:勇气之路的刷血怪刷到 15w 以上的血
- 满腹经纶:把所有怪物技能都学一遍
- 冰与火之舞:通过第二章特殊战的困难难度

View File

@ -16,6 +16,7 @@
"axios": "^1.1.3",
"lodash": "^4.17.21",
"lz-string": "^1.4.4",
"mutate-animate": "^0.1.0",
"vue": "^3.2.41"
},
"devDependencies": {

View File

@ -18,6 +18,7 @@ specifiers:
less: ^4.1.3
lodash: ^4.17.21
lz-string: ^1.4.4
mutate-animate: ^0.1.0
terser: ^5.15.1
ts-node: ^10.9.1
typescript: ^4.6.4
@ -32,6 +33,7 @@ dependencies:
axios: 1.1.3
lodash: 4.17.21
lz-string: 1.4.4
mutate-animate: 0.1.0
vue: 3.2.45
devDependencies:
@ -2112,6 +2114,10 @@ packages:
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
dev: true
/mutate-animate/0.1.0:
resolution: {integrity: sha512-XOdyX/PDFYZDpUpaqPeLOfdeDYXQcThWsuLUQpp3z6FbReDV23jIN0Vzv5QLUy+6CDMHEBZHfsumODrlKJva+g==}
dev: false
/nan/2.17.0:
resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==}
dev: true

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
## 怪物
### 第二章 智慧
- 怪物拥有火、水等属性,在不同天气下属性不同
- 同化
- 同化+阻击
- 电摇嘲讽到同行或同列直接怼过去门和墙撞碎不消耗钥匙攻击怪物捡道具改变bgm可吃补给用
- 抢夺:抢夺你的装备并使用(简单难度中,怪物使用时属性降低)
- 抢夺2.0:抢夺你的道具(怪物手册和楼传除外),并按照左上、右上、右下、左下的顺序依次放到地图的四角(简单无效)
- 抢夺3.0抢夺装备但每过10回合便按照2.0方法扔至地图四角(因为怪物觉得装备不好使),然后重新抢夺你的装备(简单无效)
- 天气之子:控制天气,包括下雨、下雪、晴天、阴天等,
#### Boss
音游,音乐为一个被遗忘的夜晚,可选简单与困难,困难可获得成就冰与火之舞
玩法一个会转动的圆盘带有一个伸出去的把手boss从四面八方射子弹当子弹恰好落到把手前端时点击按键或屏幕可以抵挡子弹简单难度3条命困难1条。简单难度音符密度低。困难难度为冰与火之舞的节奏。简单难度判定时间为前后各150ms困难为75ms
## 主角
- 反抢夺1级反1.02级反2.03级反3.0,用智慧点点
- 学习学习选定怪物的选定技能不能学光环消耗智慧点初始400但每次消耗点数+100持续3场战斗
- 铸剑为盾:主动技能,减少攻击,增加防御
- 血之代偿:消耗一定血量,战前对怪物造成同等数值的伤害
## 机制
### 通用
- 实时天气
- 成就系统
- 装备合成、装备(孔)强化
### 第二章 智慧
- 按下一个开关,所有红门变黄门,黄门变蓝门,蓝门变红门
## 成就
- 学坏了:学习电摇嘲讽
- 我就是傻子:不学习反抢夺通过第二章
- 真能刷勇气之路的刷血怪刷到15w以上的血
- 满腹经纶:把所有技能都学一遍
- 冰与火之舞:通过第二章特殊战的困难难度

View File

@ -1,39 +1,22 @@
<!-- 这里是vue的入口在这里可以修改你的ui -->
<!-- 示例显示一个响应式文字 -->
<template>
<div id="root">
<span @click="add" id="cnt"
>点我自增&nbsp;&nbsp;&nbsp;&nbsp;{{ cnt }}</span
>
<div id="ui">
<template v-for="com of uiStack">
<component :is="com"></component>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const cnt = ref(0);
/**
* 点击后自增1
*/
function add() {
cnt.value++;
}
import { uiStack } from './plugin/uiController';
</script>
<style lang="less" scoped>
#root {
width: 100%;
#ui {
width: 90%;
height: 90%;
display: flex;
justify-content: center;
}
#cnt {
font-size: 3em;
text-align: center;
cursor: pointer;
color: white;
overflow: hidden;
}
</style>

View File

View File

154
src/components/scroll.vue Normal file
View File

@ -0,0 +1,154 @@
<template>
<div id="scroll">
<div>
<div :id="`content-${id}`" class="content">
<slot></slot>
</div>
</div>
<canvas :id="`scroll-${id}`" class="scroll"></canvas>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUpdated } from 'vue';
import { useDrag, useWheel } from '../plugin/use';
let now = 0;
let total = 0;
const id = (1e8 * Math.random()).toFixed(0);
const scale = window.devicePixelRatio;
let ctx: CanvasRenderingContext2D;
let content: HTMLDivElement;
/** 绘制 */
function draw() {
if (total === 0) return;
if (now > total - ctx.canvas.height / scale) {
now = total - ctx.canvas.height / scale;
} else if (now < 0) {
now = 0;
}
const length =
Math.min(ctx.canvas.height / total / scale, 1) * ctx.canvas.height;
const py = (now / total) * ctx.canvas.height;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.moveTo(20, Math.max(py + 5, 5));
ctx.lineTo(20, Math.min(py + length - 5, ctx.canvas.height - 5));
ctx.lineCap = 'round';
ctx.lineWidth = 6;
ctx.strokeStyle = '#fff';
ctx.stroke();
}
/**
* 计算元素总长度
*/
function calHeight() {
const div = document.getElementById(`content-${id}`) as HTMLDivElement;
content = div;
const style = getComputedStyle(div);
total = parseFloat(style.height);
}
function scroll() {
draw();
content.style.top = `${-now}px`;
}
onUpdated(() => {
calHeight();
draw();
});
onMounted(() => {
calHeight();
const canvas = document.getElementById(`scroll-${id}`) as HTMLCanvasElement;
ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
const style = getComputedStyle(canvas);
canvas.width = 40 * scale;
canvas.height = parseFloat(style.height) * scale;
draw();
let lastY: number;
let contentLast: number;
//
useDrag(
canvas,
(x, y) => {
const dy = y - lastY;
lastY = y;
if (canvas.height < total * scale)
now += ((dy * total) / canvas.height) * scale;
content.style.transition = '';
scroll();
},
(x, y) => {
lastY = y;
},
true
);
//
useDrag(
content,
(x, y) => {
const dy = y - contentLast;
contentLast = y;
if (canvas.height < total * scale) now -= dy;
content.style.transition = '';
scroll();
},
(x, y) => {
contentLast = y;
},
true
);
useWheel(content, (x, y) => {
if (!core.domStyle.isVertical && Math.abs(y) > 50) {
content.style.transition = 'top 0.2s ease-out';
} else {
content.style.transition = '';
}
now += y;
scroll();
});
});
</script>
<style lang="less" scoped>
.scroll {
opacity: 0.2;
width: 40px;
height: 98%;
margin-top: 1%;
margin-bottom: 1%;
transition: opacity 0.2s linear;
}
.scroll:hover {
opacity: 0.4;
}
.scroll:active {
opacity: 0.6;
}
#scroll {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-basis: content;
}
.content {
position: relative;
}
</style>

View File

@ -1,9 +1,10 @@
// 需要引入所有的插件
import pop from './plugin/template';
import pop from './plugin/pop';
import ui from './plugin/uiController';
window.addEventListener('load', () => {
// 每个引入的插件都要在这里执行,否则不会被转发
const toForward = [pop()];
const toForward: any[] = [pop(), ui()];
// 初始化所有插件并转发到core上
(async function () {

View File

@ -1,4 +1,5 @@
import { createApp } from 'vue';
import App from './App.vue';
import './styles.less';
createApp(App).mount('#root');

View File

@ -0,0 +1,46 @@
import { Component, markRaw, ref, Ref, watch } from 'vue';
import Book from '../ui/book.vue';
import BookDetail from '../ui/bookDetail.vue';
export const bookOpened = ref(false);
export const bookDetail = ref(false);
let app: HTMLDivElement;
/** ui声明列表 */
const UI_LIST: [Ref<boolean>, Component][] = [
[bookOpened, Book],
[bookDetail, BookDetail]
];
/** ui栈 */
export const uiStack = ref<Component[]>([]);
export default function init() {
app = document.getElementById('root') as HTMLDivElement;
UI_LIST.forEach(([ref, com]) => {
watch(ref, n => {
if (n === true) {
uiStack.value.push(markRaw(com));
showApp();
} else {
const index = uiStack.value.findIndex(v => v === com);
uiStack.value.splice(index);
if (uiStack.value.length === 0) {
hideApp();
}
}
});
});
return { uiStack, bookDetail, bookOpened };
}
function showApp() {
app.style.display = 'flex';
core.lockControl();
}
function hideApp() {
app.style.display = 'none';
core.unlockControl();
}

57
src/plugin/use.ts Normal file
View File

@ -0,0 +1,57 @@
export default function init() {
return { useDrag, useWheel };
}
/**
*
* @param ele
* @param fn x y和鼠标事件或点击事件
* @param ondown
* @param global
*/
export function useDrag(
ele: HTMLElement,
fn: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
ondown?: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
global: boolean = false
) {
let down = false;
ele.addEventListener('mousedown', e => {
down = true;
if (ondown) ondown(e.clientX, e.clientY, e);
});
ele.addEventListener('touchstart', e => {
down = true;
if (ondown) ondown(e.touches[0].clientX, e.touches[0].clientY, e);
});
document.addEventListener('mouseup', () => (down = false));
document.addEventListener('touchend', () => (down = false));
const target = global ? document : ele;
target.addEventListener('mousemove', e => {
if (!down) return;
const ee = e as MouseEvent;
fn(ee.clientX, ee.clientY, ee);
});
target.addEventListener('touchmove', e => {
if (!down) return;
const ee = e as TouchEvent;
fn(ee.touches[0].clientX, ee.touches[0].clientY, ee);
});
}
/**
*
* @param ele
* @param fn
*/
export function useWheel(
ele: HTMLElement,
fn: (x: number, y: number, z: number, e: WheelEvent) => void
): void {
ele.addEventListener('wheel', e => {
fn(e.deltaX, e.deltaY, e.deltaZ, e);
});
}

10
src/styles.less Normal file
View File

@ -0,0 +1,10 @@
#root {
position: absolute;
display: none;
width: 100%;
height: 100%;
z-index: 9999;
justify-content: center;
align-items: center;
overflow: hidden;
}

14
src/types/core.d.ts vendored
View File

@ -1,4 +1,4 @@
type core = {
type Core = {
/** 地图的格子宽度 */
readonly _WIDTH_: number;
/** 地图的格子高度 */
@ -186,10 +186,10 @@ type core = {
actions &
Forward<PluginDeclaration>;
type main = {
type Main = {
/** 是否在录像验证中 */
readonly replayChecking: boolean;
readonly core: core;
readonly core: Core;
readonly dom: { [key: string]: HTMLElement };
/** 游戏版本,发布后会被随机,请勿使用该属性 */
readonly version: string;
@ -313,7 +313,7 @@ declare class Sprite {
removeEventListenr: HTMLCanvasElement['addEventListener'];
}
declare let main: main;
declare let core: core;
declare let flags: { [x: string]: any };
declare let hero: HeroStatus;
declare const main: main;
declare const core: Core;
declare const flags: { [x: string]: any };
declare const hero: HeroStatus;

37
src/types/plugin.d.ts vendored
View File

@ -1,5 +1,9 @@
// 这里包含所有插件导出的函数及变量声明声明的函数会在类型标注中标注到core上
type Ref<T> = {
value: T;
};
interface PluginDeclaration {
/**
* 使core.addPop或core.plugin.addPop调用
@ -11,6 +15,39 @@ interface PluginDeclaration {
/** 添加变量 例所有的正在弹出的文字像这个就可以使用core.plugin.pop获取 */
pop: any[];
/** 手册是否打开 */
readonly bookOpened: Ref<boolean>;
/** 手册详细信息 */
readonly bookDetail: Ref<boolean>;
/** ui栈 */
readonly uiStack: Ref<Component[]>;
/**
*
* @param ele
* @param fn x y和鼠标事件或点击事件
* @param ondown
* @param global
*/
useDrag(
ele: HTMLElement,
fn: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
ondown?: (x: number, y: number, e: MouseEvent | TouchEvent) => void,
global: boolean = false
): void;
/**
*
* @param ele
* @param fn
*/
useWheel(
ele: HTMLElement,
fn: (x: number, y: number, z: number, e: WheelEvent) => void
): void;
}
type Forward<T> = {

20
src/ui/book.vue Normal file
View File

@ -0,0 +1,20 @@
<!-- 怪物手册ui -->
<template>
<Scroll>
<div v-for="i of 100" class="test">test{{ i }}</div>
</Scroll>
</template>
<script setup lang="tsx">
import Scroll from '../components/scroll.vue';
const floorId = core.floorIds[core.status.event?.ui] ?? core.status.floorId;
const enemy = core.getCurrentEnemys(floorId);
</script>
<style lang="less" scoped>
.test {
color: white;
}
</style>

7
src/ui/bookDetail.vue Normal file
View File

@ -0,0 +1,7 @@
<!-- 怪物详细信息 -->
<template></template>
<script lang="tsx" setup></script>
<style lang="less" scoped></style>