mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 04:19:30 +08:00
点光源
This commit is contained in:
parent
3c7b5906f6
commit
5e0d8ac7d4
4
idea.md
4
idea.md
@ -26,6 +26,10 @@
|
|||||||
|
|
||||||
### 第三章 战争
|
### 第三章 战争
|
||||||
|
|
||||||
|
#### 技能
|
||||||
|
|
||||||
|
闪避:每 M 回合闪避一次,减少 N%的伤害
|
||||||
|
|
||||||
## 机制
|
## 机制
|
||||||
|
|
||||||
### 通用
|
### 通用
|
||||||
|
@ -3202,6 +3202,7 @@ maps.prototype.removeBlock = function (x, y, floorId) {
|
|||||||
const block = blocks[i];
|
const block = blocks[i];
|
||||||
this.removeBlockByIndex(i, floorId);
|
this.removeBlockByIndex(i, floorId);
|
||||||
this._removeBlockFromMap(floorId, block);
|
this._removeBlockFromMap(floorId, block);
|
||||||
|
core.updateShadow(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -3364,6 +3365,7 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
core.updateShadow(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
maps.prototype.animateSetBlock = function (
|
maps.prototype.animateSetBlock = function (
|
||||||
|
@ -4226,7 +4226,8 @@ ui.prototype.deleteCanvas = function (name) {
|
|||||||
|
|
||||||
////// 删除所有动态canvas //////
|
////// 删除所有动态canvas //////
|
||||||
ui.prototype.deleteAllCanvas = function () {
|
ui.prototype.deleteAllCanvas = function () {
|
||||||
return this.deleteCanvas(function () {
|
this.deleteCanvas(function () {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
if (main.mode === 'play' && !core.isReplaying()) core.initShadowCanvas();
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,13 @@ main.floors.MT42=
|
|||||||
5,
|
5,
|
||||||
0
|
0
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"8,12": {
|
||||||
|
"floorId": "MT46",
|
||||||
|
"loc": [
|
||||||
|
7,
|
||||||
|
8
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"beforeBattle": {},
|
"beforeBattle": {},
|
||||||
|
@ -1,45 +1,71 @@
|
|||||||
main.floors.MT46=
|
main.floors.MT46=
|
||||||
{
|
{
|
||||||
"floorId": "MT46",
|
"floorId": "MT46",
|
||||||
"title": "冰封高原",
|
"title": "冰封高原",
|
||||||
"name": "46",
|
"name": "46",
|
||||||
"width": 15,
|
"width": 15,
|
||||||
"height": 15,
|
"height": 15,
|
||||||
"canFlyTo": true,
|
"canFlyTo": true,
|
||||||
"canFlyFrom": true,
|
"canFlyFrom": true,
|
||||||
"canUseQuickShop": true,
|
"canUseQuickShop": true,
|
||||||
"cannotViewMap": false,
|
"cannotViewMap": false,
|
||||||
"images": [],
|
"images": [],
|
||||||
"ratio": 8,
|
"ratio": 8,
|
||||||
"defaultGround": "T580",
|
"defaultGround": "T580",
|
||||||
"bgm": "winter.mp3",
|
"bgm": "winter.mp3",
|
||||||
"firstArrive": [],
|
"firstArrive": [],
|
||||||
"eachArrive": [],
|
"eachArrive": [],
|
||||||
"parallelDo": "",
|
"parallelDo": "",
|
||||||
"events": {},
|
"events": {},
|
||||||
"changeFloor": {},
|
"changeFloor": {},
|
||||||
"beforeBattle": {},
|
"beforeBattle": {},
|
||||||
"afterBattle": {},
|
"afterBattle": {},
|
||||||
"afterGetItem": {},
|
"afterGetItem": {},
|
||||||
"afterOpenDoor": {},
|
"afterOpenDoor": {},
|
||||||
"autoEvent": {},
|
"autoEvent": {},
|
||||||
"cannotMove": {},
|
"cannotMove": {},
|
||||||
"cannotMoveIn": {},
|
"cannotMoveIn": {},
|
||||||
"map": [
|
"map": [
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0]
|
||||||
],
|
],
|
||||||
|
"bgmap": [
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300],
|
||||||
|
[300,300,300,300,300,300,300,300,300,300,300,300,300,300,300]
|
||||||
|
],
|
||||||
|
"fgmap": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"bg2map": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"fg2map": [
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
@ -150,6 +150,8 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
|||||||
// ---------- 重绘新地图;这一步将会设置core.status.floorId ---------- //
|
// ---------- 重绘新地图;这一步将会设置core.status.floorId ---------- //
|
||||||
core.drawMap(floorId);
|
core.drawMap(floorId);
|
||||||
|
|
||||||
|
core.updateShadow();
|
||||||
|
|
||||||
// 切换楼层BGM
|
// 切换楼层BGM
|
||||||
if (core.status.maps[floorId].bgm) {
|
if (core.status.maps[floorId].bgm) {
|
||||||
var bgm = core.status.maps[floorId].bgm;
|
var bgm = core.status.maps[floorId].bgm;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
|
var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
|
||||||
init: function () {
|
init: function () {
|
||||||
|
// 只看插件没用,插件是与vite样板高度融合的,所以要看的话就在游戏内的百科全书-关于游戏内点那个开源地址吧
|
||||||
this._afterLoadResources = function () {
|
this._afterLoadResources = function () {
|
||||||
if (!main.replayChecking && main.mode === 'play') {
|
if (!main.replayChecking && main.mode === 'play') {
|
||||||
main.forward();
|
main.forward();
|
||||||
@ -770,12 +771,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = {
|
|||||||
core.status.hero = new Proxy(hero, handler);
|
core.status.hero = new Proxy(hero, handler);
|
||||||
|
|
||||||
core.status.maps[floorId].blocks.forEach(function (block) {
|
core.status.maps[floorId].blocks.forEach(function (block) {
|
||||||
if (
|
if (block.event.cls !== 'items' || block.disable) return;
|
||||||
block.event.cls !== 'items' ||
|
|
||||||
block.event.id === 'superPotion' ||
|
|
||||||
block.disable
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
const x = block.x,
|
const x = block.x,
|
||||||
y = block.y;
|
y = block.y;
|
||||||
// v2优化,只绘制范围内的部分
|
// v2优化,只绘制范围内的部分
|
||||||
|
@ -11,6 +11,9 @@ import chapter from './plugin/ui/chapter';
|
|||||||
import fly from './plugin/ui/fly';
|
import fly from './plugin/ui/fly';
|
||||||
import chase from './plugin/chase/chase';
|
import chase from './plugin/chase/chase';
|
||||||
import fixed from './plugin/ui/fixed';
|
import fixed from './plugin/ui/fixed';
|
||||||
|
import webglUtils from './plugin/webgl/utils';
|
||||||
|
import shadow from './plugin/webgl/shadow';
|
||||||
|
import gameShadow from './plugin/webgl/gameShadow';
|
||||||
|
|
||||||
function forward() {
|
function forward() {
|
||||||
// 每个引入的插件都要在这里执行,否则不会被转发
|
// 每个引入的插件都要在这里执行,否则不会被转发
|
||||||
@ -26,7 +29,10 @@ function forward() {
|
|||||||
chapter(),
|
chapter(),
|
||||||
fly(),
|
fly(),
|
||||||
chase(),
|
chase(),
|
||||||
fixed()
|
fixed(),
|
||||||
|
webglUtils(),
|
||||||
|
shadow(),
|
||||||
|
gameShadow()
|
||||||
];
|
];
|
||||||
|
|
||||||
// 初始化所有插件,并转发到core上
|
// 初始化所有插件,并转发到core上
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/// <reference path="../types/core.d.ts" />
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { MessageApi } from 'ant-design-vue/lib/message';
|
import { MessageApi } from 'ant-design-vue/lib/message';
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
@ -69,7 +70,7 @@ export function keycode(key: number) {
|
|||||||
* @param css 要解析的css字符串
|
* @param css 要解析的css字符串
|
||||||
*/
|
*/
|
||||||
export function parseCss(css: string): Partial<Record<CanParseCss, string>> {
|
export function parseCss(css: string): Partial<Record<CanParseCss, string>> {
|
||||||
const str = css.replace(/[\n\s\t]*/g, '').replace(/[;,]*/g, ';');
|
const str = css.replace(/[\n\s\t]*/g, '').replace(/;*/g, ';');
|
||||||
const styles = str.split(';');
|
const styles = str.split(';');
|
||||||
const res: Partial<Record<CanParseCss, string>> = {};
|
const res: Partial<Record<CanParseCss, string>> = {};
|
||||||
|
|
||||||
|
123
src/plugin/webgl/canvas.ts
Normal file
123
src/plugin/webgl/canvas.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
const glMap: Record<string, WebGLRenderingContext> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个以webgl为绘制上下文的画布
|
||||||
|
* @param id 画布id
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param w 宽度
|
||||||
|
* @param h 高度
|
||||||
|
* @param z 纵深
|
||||||
|
*/
|
||||||
|
export function createWebGLCanvas(
|
||||||
|
id: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
z: number
|
||||||
|
) {
|
||||||
|
if (id in glMap) {
|
||||||
|
deleteWebGLCanvas(id);
|
||||||
|
}
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const gl = canvas.getContext('webgl')!;
|
||||||
|
const s = core.domStyle.scale;
|
||||||
|
canvas.style.left = `${x * s}px`;
|
||||||
|
canvas.style.top = `${y * s}px`;
|
||||||
|
canvas.style.width = `${w * s}px`;
|
||||||
|
canvas.style.height = `${h * s}px`;
|
||||||
|
canvas.style.zIndex = `${z}`;
|
||||||
|
canvas.width = w * s * devicePixelRatio;
|
||||||
|
canvas.height = h * s * devicePixelRatio;
|
||||||
|
core.dom.gameDraw.appendChild(canvas);
|
||||||
|
return gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一个webgl画布
|
||||||
|
* @param id 画布id
|
||||||
|
*/
|
||||||
|
export function deleteWebGLCanvas(id: string) {
|
||||||
|
const gl = glMap[id];
|
||||||
|
if (!gl) return;
|
||||||
|
const canvas = gl.canvas as HTMLCanvasElement;
|
||||||
|
canvas.remove();
|
||||||
|
delete glMap[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取webgl画布上下文
|
||||||
|
* @param id 画布id
|
||||||
|
*/
|
||||||
|
export function getWebGLCanvas(id: string): WebGLRenderingContext | null {
|
||||||
|
return glMap[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建webgl程序对象
|
||||||
|
* @param gl 画布webgl上下文
|
||||||
|
* @param vshader 顶点着色器
|
||||||
|
* @param fshader 片元着色器
|
||||||
|
*/
|
||||||
|
export function createProgram(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
vshader: string,
|
||||||
|
fshader: string
|
||||||
|
) {
|
||||||
|
// 创建着色器
|
||||||
|
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
|
||||||
|
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
|
||||||
|
|
||||||
|
// 创建program
|
||||||
|
const program = gl.createProgram();
|
||||||
|
if (!program) {
|
||||||
|
throw new Error(`Create webgl program fail!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配和连接program
|
||||||
|
gl.attachShader(program, vertexShader);
|
||||||
|
gl.attachShader(program, fragmentShader);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
|
||||||
|
// 检查连接是否成功
|
||||||
|
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||||
|
if (!linked) {
|
||||||
|
const err = gl.getProgramInfoLog(program);
|
||||||
|
throw new Error(`Program link fail: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载着色器
|
||||||
|
* @param gl 画布的webgl上下文
|
||||||
|
* @param type 着色器类型,顶点着色器还是片元着色器
|
||||||
|
* @param source 着色器源码
|
||||||
|
*/
|
||||||
|
export function loadShader(
|
||||||
|
gl: WebGLRenderingContext,
|
||||||
|
type: number,
|
||||||
|
source: string
|
||||||
|
) {
|
||||||
|
// 创建着色器
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
if (!shader) {
|
||||||
|
throw new ReferenceError(
|
||||||
|
`Your device or browser does not support webgl!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 引入并编译着色器
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
|
||||||
|
// 检查是否编译成功
|
||||||
|
const compiled = gl.getShaderParameter(gl, gl.COMPILE_STATUS);
|
||||||
|
if (!compiled) {
|
||||||
|
const err = gl.getShaderInfoLog(shader);
|
||||||
|
throw new Error(`Shader compile fail: ${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
131
src/plugin/webgl/gameShadow.ts
Normal file
131
src/plugin/webgl/gameShadow.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { Polygon } from './polygon';
|
||||||
|
import {
|
||||||
|
Light,
|
||||||
|
removeAllLights,
|
||||||
|
setBackground,
|
||||||
|
setBlur,
|
||||||
|
setLightList,
|
||||||
|
setShadowNodes
|
||||||
|
} from './shadow';
|
||||||
|
|
||||||
|
export default function init() {
|
||||||
|
return { updateShadow, clearShadowCache, setCalShadow };
|
||||||
|
}
|
||||||
|
|
||||||
|
const shadowInfo: Partial<Record<FloorIds, Light[]>> = {
|
||||||
|
MT46: [
|
||||||
|
{
|
||||||
|
id: 'mt42_1',
|
||||||
|
x: 85,
|
||||||
|
y: 85,
|
||||||
|
decay: 100,
|
||||||
|
r: 300,
|
||||||
|
color: '#0000'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const backgroundInfo: Partial<Record<FloorIds, Color>> = {
|
||||||
|
MT46: '#0008'
|
||||||
|
};
|
||||||
|
const blurInfo: Partial<Record<FloorIds, number>> = {
|
||||||
|
MT46: 4
|
||||||
|
};
|
||||||
|
const immersionInfo: Partial<Record<FloorIds, number>> = {
|
||||||
|
MT46: 8
|
||||||
|
};
|
||||||
|
const shadowCache: Partial<Record<FloorIds, Polygon[]>> = {};
|
||||||
|
|
||||||
|
let calMapShadow = true;
|
||||||
|
|
||||||
|
export function updateShadow(nocache: boolean = false) {
|
||||||
|
// 需要优化,优化成bfs
|
||||||
|
const floor = core.status.floorId;
|
||||||
|
if (!shadowInfo[floor] || !backgroundInfo[floor]) {
|
||||||
|
removeAllLights();
|
||||||
|
setShadowNodes([]);
|
||||||
|
setBackground('#0000');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const f = core.status.thisMap;
|
||||||
|
const w = f.width;
|
||||||
|
const h = f.height;
|
||||||
|
const nodes: Polygon[] = [];
|
||||||
|
if (calMapShadow) {
|
||||||
|
if (shadowCache[floor] && !nocache) {
|
||||||
|
setShadowNodes(shadowCache[floor]!);
|
||||||
|
} else {
|
||||||
|
core.extractBlocks();
|
||||||
|
const blocks = core.getMapBlocksObj();
|
||||||
|
core.status.maps[floor].blocks.forEach(v => {
|
||||||
|
if (
|
||||||
|
!['terrains', 'autotile', 'tileset', 'animates'].includes(
|
||||||
|
v.event.cls
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v.event.noPass) {
|
||||||
|
const immerse = immersionInfo[floor] ?? 4;
|
||||||
|
const x = v.x;
|
||||||
|
const y = v.y;
|
||||||
|
let left = x * 32 + immerse;
|
||||||
|
let top = y * 32 + immerse;
|
||||||
|
let right = left + 32 - immerse * 2;
|
||||||
|
let bottom = top + 32 - immerse * 2;
|
||||||
|
const l: LocString = `${x - 1},${y}`;
|
||||||
|
const r: LocString = `${x + 1},${y}`;
|
||||||
|
const t: LocString = `${x},${y - 1}`;
|
||||||
|
const b: LocString = `${x},${y + 1}`;
|
||||||
|
|
||||||
|
if (x === 0 || (blocks[l] && blocks[l].event.noPass)) {
|
||||||
|
left -= immerse;
|
||||||
|
}
|
||||||
|
if (x + 1 === w || (blocks[r] && blocks[r].event.noPass)) {
|
||||||
|
right += immerse;
|
||||||
|
}
|
||||||
|
if (y === 0 || (blocks[t] && blocks[t].event.noPass)) {
|
||||||
|
top -= immerse;
|
||||||
|
}
|
||||||
|
if (y + 1 === h || (blocks[b] && blocks[b].event.noPass)) {
|
||||||
|
bottom += immerse;
|
||||||
|
}
|
||||||
|
nodes.push(
|
||||||
|
new Polygon([
|
||||||
|
[left, top],
|
||||||
|
[right, top],
|
||||||
|
[right, bottom],
|
||||||
|
[left, bottom]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
shadowCache[floor] = nodes;
|
||||||
|
setShadowNodes(nodes);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setShadowNodes([]);
|
||||||
|
setBlur(0);
|
||||||
|
}
|
||||||
|
setLightList(shadowInfo[floor]!);
|
||||||
|
setBackground(backgroundInfo[floor]!);
|
||||||
|
setBlur(blurInfo[floor] ?? 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除某一层的墙壁缓存
|
||||||
|
* @param floorId 楼层id
|
||||||
|
*/
|
||||||
|
export function clearShadowCache(floorId: FloorIds) {
|
||||||
|
delete shadowCache[floorId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否不计算墙壁遮挡,对所有灯光有效
|
||||||
|
* @param n 目标值
|
||||||
|
*/
|
||||||
|
export function setCalShadow(n: boolean) {
|
||||||
|
calMapShadow = n;
|
||||||
|
updateShadow();
|
||||||
|
}
|
160
src/plugin/webgl/martrix.ts
Normal file
160
src/plugin/webgl/martrix.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { has } from '../utils';
|
||||||
|
|
||||||
|
export class Matrix extends Array<number[]> {
|
||||||
|
constructor(...n: number[][]) {
|
||||||
|
if (n.length !== n[0]?.length) {
|
||||||
|
throw new TypeError(
|
||||||
|
`The array delivered to Matrix must has the same length of its item and itself.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super(...n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加上某个方阵
|
||||||
|
* @param matrix 要加上的方阵
|
||||||
|
*/
|
||||||
|
add(matrix: number[][]): Matrix {
|
||||||
|
if (matrix.length !== this.length) {
|
||||||
|
throw new TypeError(
|
||||||
|
`To add a martrix, the be-added-matrix's size must equal to the to-add-matrix's.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const length = matrix.length;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
this[i][j] += matrix[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 让该方阵与另一个方阵相乘
|
||||||
|
* @param matrix 要相乘的方阵
|
||||||
|
*/
|
||||||
|
multipy(matrix: number[][]): Matrix {
|
||||||
|
if (matrix.length !== this.length) {
|
||||||
|
throw new TypeError(
|
||||||
|
`To multipy a martrix, the be-multipied-matrix's size must equal to the to-multipy-matrix's.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const n = this.length;
|
||||||
|
const arr = this.map(v => v.slice());
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
for (let j = 0; j < n; j++) {
|
||||||
|
for (let k = 0; k < n; k++) {
|
||||||
|
this[i][j] = arr[i][k] * matrix[k][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Matrix4 extends Matrix {
|
||||||
|
constructor(...n: number[][]) {
|
||||||
|
n ??= [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
];
|
||||||
|
if (n.length !== 4) {
|
||||||
|
throw new TypeError(`The length of delivered array must be 4.`);
|
||||||
|
}
|
||||||
|
super(...n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平移变换
|
||||||
|
* @param x 平移横坐标
|
||||||
|
* @param y 平移纵坐标
|
||||||
|
* @param z 平移竖坐标
|
||||||
|
*/
|
||||||
|
translation(x: number, y: number, z: number) {
|
||||||
|
this.multipy([
|
||||||
|
[1, 0, 0, x],
|
||||||
|
[0, 1, 0, y],
|
||||||
|
[0, 0, 1, z],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放变换
|
||||||
|
* @param x 沿x轴的缩放比例
|
||||||
|
* @param y 沿y轴的缩放比例
|
||||||
|
* @param z 沿z轴的缩放比例
|
||||||
|
*/
|
||||||
|
scale(x: number, y: number, z: number) {
|
||||||
|
this.multipy([
|
||||||
|
[x, 0, 0, 0],
|
||||||
|
[0, y, 0, 0],
|
||||||
|
[0, 0, z, 0],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转变换
|
||||||
|
* @param x 绕x轴的旋转角度
|
||||||
|
* @param y 绕y轴的旋转角度
|
||||||
|
* @param z 绕z轴的旋转角度
|
||||||
|
*/
|
||||||
|
rotate(x?: number, y?: number, z?: number): Matrix4 {
|
||||||
|
if (has(x) && x !== 0) {
|
||||||
|
const sin = Math.sin(x);
|
||||||
|
const cos = Math.cos(x);
|
||||||
|
this.multipy([
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, cos, sin, 0],
|
||||||
|
[0, -sin, cos, 0],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (has(y) && y !== 0) {
|
||||||
|
const sin = Math.sin(y);
|
||||||
|
const cos = Math.cos(y);
|
||||||
|
this.multipy([
|
||||||
|
[cos, 0, -sin, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[sin, 0, cos, 0],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (has(z) && z !== 0) {
|
||||||
|
const sin = Math.sin(z);
|
||||||
|
const cos = Math.cos(z);
|
||||||
|
this.multipy([
|
||||||
|
[cos, sin, 0, 0],
|
||||||
|
[-sin, cos, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转置矩阵
|
||||||
|
* @param target 转置目标,是赋给原矩阵还是新建一个矩阵
|
||||||
|
*/
|
||||||
|
transpose(target: 'this' | 'new' = 'new'): Matrix4 {
|
||||||
|
const t = target === 'this' ? this : new Matrix4();
|
||||||
|
const arr = this.map(v => v.slice());
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
t[i][j] = arr[j][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换成列主序的Float32Array,用于webgl
|
||||||
|
*/
|
||||||
|
toWebGLFloat32Array(): Float32Array {
|
||||||
|
return new Float32Array(this.transpose().flat());
|
||||||
|
}
|
||||||
|
}
|
89
src/plugin/webgl/polygon.ts
Normal file
89
src/plugin/webgl/polygon.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
export class Polygon {
|
||||||
|
/**
|
||||||
|
* 多边形的节点
|
||||||
|
*/
|
||||||
|
nodes: LocArr[];
|
||||||
|
|
||||||
|
private cache: Record<string, LocArr[][]> = {};
|
||||||
|
|
||||||
|
static from(...polygons: LocArr[][]) {
|
||||||
|
return polygons.map(v => new Polygon(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(nodes: LocArr[]) {
|
||||||
|
if (nodes.length < 3) {
|
||||||
|
throw new Error(`Nodes number delivered is less than 3!`);
|
||||||
|
}
|
||||||
|
this.nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个点光源下的阴影
|
||||||
|
*/
|
||||||
|
shadowArea(x: number, y: number, r: number): LocArr[][] {
|
||||||
|
const id = `${x},${y}`;
|
||||||
|
if (this.cache[id]) return this.cache[id];
|
||||||
|
const res: LocArr[][] = [];
|
||||||
|
const w = core._PX_ ?? core.__PIXELS__;
|
||||||
|
const h = core._PY_ ?? core.__PIXELS__;
|
||||||
|
|
||||||
|
const intersect = (nx: number, ny: number): LocArr => {
|
||||||
|
const k = (ny - y) / (nx - x);
|
||||||
|
if (k > 1 || k < -1) {
|
||||||
|
if (ny < y) {
|
||||||
|
const ix = x + y / k;
|
||||||
|
return [2 * x - ix, 0];
|
||||||
|
} else {
|
||||||
|
const ix = x + (h - y) / k;
|
||||||
|
return [ix, h];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (nx < x) {
|
||||||
|
const iy = y + k * x;
|
||||||
|
return [0, 2 * y - iy];
|
||||||
|
} else {
|
||||||
|
const iy = y + k * (w - x);
|
||||||
|
return [w, iy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const l = this.nodes.length;
|
||||||
|
let now = intersect(...this.nodes[0]);
|
||||||
|
for (let i = 0; i < l; i++) {
|
||||||
|
const next = (i + 1) % l;
|
||||||
|
const nextInter = intersect(...this.nodes[next]);
|
||||||
|
const start = [this.nodes[i], now];
|
||||||
|
const end = [nextInter, this.nodes[next]];
|
||||||
|
let path: LocArr[];
|
||||||
|
if (
|
||||||
|
(now[0] === 0 && nextInter[1] === 0) ||
|
||||||
|
(now[1] === 0 && nextInter[0] === 0)
|
||||||
|
) {
|
||||||
|
path = [...start, [0, 0], ...end];
|
||||||
|
} else if (
|
||||||
|
(now[0] === 0 && nextInter[1] === h) ||
|
||||||
|
(now[1] === h && nextInter[0] === 0)
|
||||||
|
) {
|
||||||
|
path = [...start, [0, h], ...end];
|
||||||
|
} else if (
|
||||||
|
(now[0] === w && nextInter[1] === 0) ||
|
||||||
|
(now[1] === 0 && nextInter[0] === w)
|
||||||
|
) {
|
||||||
|
path = [...start, [w, 0], ...end];
|
||||||
|
} else if (
|
||||||
|
(now[0] === w && nextInter[1] === h) ||
|
||||||
|
(now[1] === h && nextInter[0] === w)
|
||||||
|
) {
|
||||||
|
path = [...start, [w, h], ...end];
|
||||||
|
} else {
|
||||||
|
path = [...start, ...end];
|
||||||
|
}
|
||||||
|
res.push(path);
|
||||||
|
now = nextInter;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cache[id] = res;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
468
src/plugin/webgl/shadow.ts
Normal file
468
src/plugin/webgl/shadow.ts
Normal file
@ -0,0 +1,468 @@
|
|||||||
|
import {
|
||||||
|
Animation,
|
||||||
|
linear,
|
||||||
|
PathFn,
|
||||||
|
TimingFn,
|
||||||
|
Transition
|
||||||
|
} from 'mutate-animate';
|
||||||
|
import { has } from '../utils';
|
||||||
|
import { Polygon } from './polygon';
|
||||||
|
|
||||||
|
interface TransitionInfo {
|
||||||
|
time: number;
|
||||||
|
mode: TimingFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Light {
|
||||||
|
id: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
r: number;
|
||||||
|
/** 衰减开始半径 */
|
||||||
|
decay: number;
|
||||||
|
/** 颜色,每个值的范围0.0~1.0 */
|
||||||
|
color: Color;
|
||||||
|
/** 是否可以被物体遮挡 */
|
||||||
|
noShelter?: boolean;
|
||||||
|
/** 正在动画的属性 */
|
||||||
|
_animating?: Record<string, boolean>;
|
||||||
|
/** 执行渐变的属性 */
|
||||||
|
_transition?: Record<string, TransitionInfo>;
|
||||||
|
/** 表示是否是代理,只有设置渐变后才会变为true */
|
||||||
|
_isProxy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function init() {
|
||||||
|
core.registerAnimationFrame('shadow', true, () => {
|
||||||
|
if (!needRefresh) return;
|
||||||
|
drawShadow();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
initShadowCanvas,
|
||||||
|
drawShadow,
|
||||||
|
addLight,
|
||||||
|
removeLight,
|
||||||
|
setLight,
|
||||||
|
setShadowNodes,
|
||||||
|
setBackground,
|
||||||
|
animateLight,
|
||||||
|
transitionLight,
|
||||||
|
moveLightAs,
|
||||||
|
getAllLights
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let canvas: HTMLCanvasElement;
|
||||||
|
let ctx: CanvasRenderingContext2D;
|
||||||
|
let lights: Light[] = [];
|
||||||
|
let needRefresh = false;
|
||||||
|
let shadowNodes: Polygon[] = [];
|
||||||
|
let background: Color;
|
||||||
|
let blur = 3;
|
||||||
|
const temp1 = document.createElement('canvas');
|
||||||
|
const temp2 = document.createElement('canvas');
|
||||||
|
const temp3 = document.createElement('canvas');
|
||||||
|
const ct1 = temp1.getContext('2d')!;
|
||||||
|
const ct2 = temp2.getContext('2d')!;
|
||||||
|
const ct3 = temp3.getContext('2d')!;
|
||||||
|
|
||||||
|
const animationList: Record<string, Animation> = {};
|
||||||
|
const transitionList: Record<string, Transition> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化阴影画布
|
||||||
|
*/
|
||||||
|
export function initShadowCanvas() {
|
||||||
|
const w = core._PX_ ?? core.__PIXELS__;
|
||||||
|
const h = core._PY_ ?? core.__PIXELS__;
|
||||||
|
ctx = core.createCanvas('shadow', 0, 0, w, h, 55);
|
||||||
|
canvas = ctx.canvas;
|
||||||
|
const s = core.domStyle.scale * devicePixelRatio;
|
||||||
|
temp1.width = w * s;
|
||||||
|
temp1.height = h * s;
|
||||||
|
temp2.width = w * s;
|
||||||
|
temp2.height = h * s;
|
||||||
|
temp3.width = w * s;
|
||||||
|
temp3.height = h * s;
|
||||||
|
ct1.scale(s, s);
|
||||||
|
ct2.scale(s, s);
|
||||||
|
ct3.scale(s, s);
|
||||||
|
canvas.style.filter = `blur(${blur}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个光源
|
||||||
|
* @param info 光源信息
|
||||||
|
*/
|
||||||
|
export function addLight(info: Light) {
|
||||||
|
lights.push(info);
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除一个光源
|
||||||
|
* @param id 光源id
|
||||||
|
*/
|
||||||
|
export function removeLight(id: string) {
|
||||||
|
const index = lights.findIndex(v => v.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new ReferenceError(`You are going to remove nonexistent light!`);
|
||||||
|
}
|
||||||
|
lights.splice(index, 1);
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个光源的信息
|
||||||
|
* @param id 光源id
|
||||||
|
* @param info 光源信息
|
||||||
|
*/
|
||||||
|
export function setLight(id: string, info: Partial<Light>) {
|
||||||
|
if (has(info.id)) delete info.id;
|
||||||
|
const light = lights.find(v => v.id === id);
|
||||||
|
if (!light) {
|
||||||
|
throw new ReferenceError(`You are going to set nonexistent light!`);
|
||||||
|
}
|
||||||
|
for (const [p, v] of Object.entries(info)) {
|
||||||
|
light[p as SelectKey<Light, number>] = v as number;
|
||||||
|
}
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前的光源列表
|
||||||
|
* @param list 光源列表
|
||||||
|
*/
|
||||||
|
export function setLightList(list: Light[]) {
|
||||||
|
lights = list;
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去除所有的光源
|
||||||
|
*/
|
||||||
|
export function removeAllLights() {
|
||||||
|
lights = [];
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个灯光
|
||||||
|
* @param id 灯光id
|
||||||
|
*/
|
||||||
|
export function getLight(id: string) {
|
||||||
|
return lights.find(v => v.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有灯光
|
||||||
|
*/
|
||||||
|
export function getAllLights() {
|
||||||
|
return lights;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置背景色
|
||||||
|
* @param color 背景色
|
||||||
|
*/
|
||||||
|
export function setBackground(color: Color) {
|
||||||
|
background = color;
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动画改变一个属性的值
|
||||||
|
* @param id 灯光id
|
||||||
|
* @param key 动画属性,x,y,r,decay,颜色请使用animateLightColor(下个版本会加)
|
||||||
|
* @param n 目标值
|
||||||
|
* @param time 动画时间
|
||||||
|
* @param mode 动画方式,渐变函数,高级动画提供了大量内置的渐变函数
|
||||||
|
* @param relative 相对方式,是绝对还是相对
|
||||||
|
*/
|
||||||
|
export function animateLight<K extends Exclude<keyof Light, 'id'>>(
|
||||||
|
id: string,
|
||||||
|
key: K,
|
||||||
|
n: Light[K],
|
||||||
|
time: number = 1000,
|
||||||
|
mode: TimingFn = linear(),
|
||||||
|
relative: boolean = false
|
||||||
|
) {
|
||||||
|
const light = getLight(id);
|
||||||
|
if (!has(light)) {
|
||||||
|
throw new ReferenceError(`You are going to animate nonexistent light`);
|
||||||
|
}
|
||||||
|
if (typeof n !== 'number') {
|
||||||
|
light[key] = n;
|
||||||
|
}
|
||||||
|
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
||||||
|
if (typeof ani.value[key] !== 'number') {
|
||||||
|
ani.register(key, light[key] as number);
|
||||||
|
} else {
|
||||||
|
ani.time(0)
|
||||||
|
.mode(linear())
|
||||||
|
.absolute()
|
||||||
|
.apply(key, light[key] as number);
|
||||||
|
}
|
||||||
|
ani.time(time)
|
||||||
|
.mode(mode)
|
||||||
|
[relative ? 'relative' : 'absolute']()
|
||||||
|
.apply(key, n as number);
|
||||||
|
const start = Date.now();
|
||||||
|
const fn = () => {
|
||||||
|
if (Date.now() - start > time + 50) {
|
||||||
|
ani.ticker.remove(fn);
|
||||||
|
light._animating![key] = false;
|
||||||
|
}
|
||||||
|
needRefresh = true;
|
||||||
|
light[key as SelectKey<Light, number>] = ani.value[key];
|
||||||
|
};
|
||||||
|
ani.ticker.add(fn);
|
||||||
|
light._animating ??= {};
|
||||||
|
light._animating[key] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把一个属性设置为渐变模式
|
||||||
|
* @param id 灯光id
|
||||||
|
* @param key 渐变的属性
|
||||||
|
* @param time 渐变时长
|
||||||
|
* @param mode 渐变方式,渐变函数,高级动画提供了大量内置的渐变函数
|
||||||
|
*/
|
||||||
|
export function transitionLight<K extends Exclude<keyof Light, 'id'>>(
|
||||||
|
id: string,
|
||||||
|
key: K,
|
||||||
|
time: number = 1000,
|
||||||
|
mode: TimingFn = linear()
|
||||||
|
) {
|
||||||
|
const index = lights.findIndex(v => v.id === id);
|
||||||
|
if (index === -1) {
|
||||||
|
throw new ReferenceError(`You are going to transite nonexistent light`);
|
||||||
|
}
|
||||||
|
const light = lights[index];
|
||||||
|
if (typeof light[key] !== 'number') return;
|
||||||
|
light._transition ??= {};
|
||||||
|
light._transition[key] = { time, mode };
|
||||||
|
const tran = transitionList[id] ?? (transitionList[id] = new Transition());
|
||||||
|
tran.value[key] = light[key] as number;
|
||||||
|
if (!light._isProxy) {
|
||||||
|
const handler: ProxyHandler<Light> = {
|
||||||
|
set(t, p, v) {
|
||||||
|
if (typeof p === 'symbol') return false;
|
||||||
|
const start = Date.now();
|
||||||
|
if (
|
||||||
|
!light._transition![p] ||
|
||||||
|
light._animating?.[key] ||
|
||||||
|
typeof v !== 'number'
|
||||||
|
) {
|
||||||
|
t[p as SelectKey<Light, number>] = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
t[p] = light[p];
|
||||||
|
const info = light._transition![p];
|
||||||
|
tran.mode(info.mode).time(info.time);
|
||||||
|
const fn = () => {
|
||||||
|
if (Date.now() - start > info.time + 50) {
|
||||||
|
tran.ticker.remove(fn);
|
||||||
|
}
|
||||||
|
needRefresh = true;
|
||||||
|
t[p as SelectKey<Light, number>] = tran.value[key];
|
||||||
|
};
|
||||||
|
tran.ticker.add(fn);
|
||||||
|
tran.transition(p, v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
lights[index] = new Proxy(light, handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动一个灯光
|
||||||
|
* @param id 灯光id
|
||||||
|
* @param x 目标横坐标
|
||||||
|
* @param y 目标纵坐标
|
||||||
|
* @param time 移动时间
|
||||||
|
* @param mode 移动方式,渐变函数
|
||||||
|
* @param relative 相对模式,相对还是绝对
|
||||||
|
*/
|
||||||
|
export function moveLight(
|
||||||
|
id: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
time: number = 1000,
|
||||||
|
mode: TimingFn = linear(),
|
||||||
|
relative: boolean = false
|
||||||
|
) {
|
||||||
|
animateLight(id, 'x', x, time, mode, relative);
|
||||||
|
animateLight(id, 'y', y, time, mode, relative);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以一个路径移动光源
|
||||||
|
* @param id 灯光id
|
||||||
|
* @param time 移动时长
|
||||||
|
* @param path 移动路径
|
||||||
|
* @param mode 移动方式,渐变函数,表示移动的完成度
|
||||||
|
* @param relative 相对模式,相对还是绝对
|
||||||
|
*/
|
||||||
|
export function moveLightAs(
|
||||||
|
id: string,
|
||||||
|
time: number,
|
||||||
|
path: PathFn,
|
||||||
|
mode: TimingFn = linear(),
|
||||||
|
relative: boolean = true
|
||||||
|
) {
|
||||||
|
const light = getLight(id);
|
||||||
|
if (!has(light)) {
|
||||||
|
throw new ReferenceError(`You are going to animate nonexistent light`);
|
||||||
|
}
|
||||||
|
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
||||||
|
ani.mode(linear()).time(0).move(light.x, light.y);
|
||||||
|
ani.time(time)
|
||||||
|
.mode(mode)
|
||||||
|
[relative ? 'relative' : 'absolute']()
|
||||||
|
.moveAs(path);
|
||||||
|
const start = Date.now();
|
||||||
|
const fn = () => {
|
||||||
|
if (Date.now() - start > time + 50) {
|
||||||
|
ani.ticker.remove(fn);
|
||||||
|
light._animating!.x = false;
|
||||||
|
light._animating!.y = false;
|
||||||
|
}
|
||||||
|
needRefresh = true;
|
||||||
|
light.x = ani.x;
|
||||||
|
light.y = ani.y;
|
||||||
|
};
|
||||||
|
ani.ticker.add(fn);
|
||||||
|
light._animating ??= {};
|
||||||
|
light._animating.x = true;
|
||||||
|
light._animating.y = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function animateLightColor(
|
||||||
|
id: string,
|
||||||
|
target: Color,
|
||||||
|
time: number = 1000,
|
||||||
|
mode: TimingFn = linear()
|
||||||
|
) {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据坐标数组设置物体节点
|
||||||
|
* @param nodes 坐标数组
|
||||||
|
*/
|
||||||
|
export function setShadowNodes(nodes: LocArr[][]): void;
|
||||||
|
/**
|
||||||
|
* 根据多边形数组设置物体节点
|
||||||
|
* @param nodes 多边形数组
|
||||||
|
*/
|
||||||
|
export function setShadowNodes(nodes: Polygon[]): void;
|
||||||
|
export function setShadowNodes(nodes: LocArr[][] | Polygon[]) {
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
shadowNodes = [];
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
if (nodes[0] instanceof Polygon) shadowNodes = nodes as Polygon[];
|
||||||
|
else shadowNodes = Polygon.from(...(nodes as LocArr[][]));
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据坐标数组添加物体节点
|
||||||
|
* @param polygons 坐标数组
|
||||||
|
*/
|
||||||
|
export function addPolygon(...polygons: LocArr[][]): void;
|
||||||
|
/**
|
||||||
|
* 根据多边形数组添加物体节点
|
||||||
|
* @param polygons 多边形数组
|
||||||
|
*/
|
||||||
|
export function addPolygon(...polygons: Polygon[]): void;
|
||||||
|
export function addPolygon(...polygons: Polygon[] | LocArr[][]) {
|
||||||
|
if (polygons.length === 0) return;
|
||||||
|
if (polygons[0] instanceof Polygon)
|
||||||
|
shadowNodes.push(...(polygons as Polygon[]));
|
||||||
|
else shadowNodes.push(...Polygon.from(...(polygons as LocArr[][])));
|
||||||
|
needRefresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置光源的虚化程度
|
||||||
|
* @param n 虚化程度
|
||||||
|
*/
|
||||||
|
export function setBlur(n: number) {
|
||||||
|
blur = n;
|
||||||
|
canvas.style.filter = `blur(${n}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制阴影
|
||||||
|
*/
|
||||||
|
export function drawShadow() {
|
||||||
|
const w = core._PX_ ?? core.__PIXELS__;
|
||||||
|
const h = core._PY_ ?? core.__PIXELS__;
|
||||||
|
needRefresh = false;
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
ct1.clearRect(0, 0, w, h);
|
||||||
|
ct2.clearRect(0, 0, w, h);
|
||||||
|
ct3.clearRect(0, 0, w, h);
|
||||||
|
|
||||||
|
const b = core.arrayToRGBA(background);
|
||||||
|
ctx.globalCompositeOperation = 'source-over';
|
||||||
|
ct3.globalCompositeOperation = 'source-over';
|
||||||
|
|
||||||
|
// 绘制阴影,一个光源一个光源地绘制,然后source-out获得光,然后把光叠加,再source-out获得最终阴影
|
||||||
|
for (let i = 0; i < lights.length; i++) {
|
||||||
|
const { x, y, r, decay, color, noShelter } = lights[i];
|
||||||
|
// 绘制阴影
|
||||||
|
ct1.clearRect(0, 0, w, h);
|
||||||
|
ct2.clearRect(0, 0, w, h);
|
||||||
|
if (!noShelter) {
|
||||||
|
for (const polygon of shadowNodes) {
|
||||||
|
const area = polygon.shadowArea(x, y, r);
|
||||||
|
area.forEach(v => {
|
||||||
|
ct1.beginPath();
|
||||||
|
ct1.moveTo(v[0][0], v[0][1]);
|
||||||
|
for (let i = 1; i < v.length; i++) {
|
||||||
|
ct1.lineTo(v[i][0], v[i][1]);
|
||||||
|
}
|
||||||
|
ct1.closePath();
|
||||||
|
ct1.fillStyle = '#000';
|
||||||
|
ct1.globalCompositeOperation = 'source-over';
|
||||||
|
ct1.fill();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 存入ct2,用于绘制真实阴影
|
||||||
|
ct2.globalCompositeOperation = 'source-over';
|
||||||
|
ct2.drawImage(temp1, 0, 0, w, h);
|
||||||
|
ct2.globalCompositeOperation = 'source-out';
|
||||||
|
const gra = ct2.createRadialGradient(x, y, decay, x, y, r);
|
||||||
|
gra.addColorStop(0, core.arrayToRGBA(color));
|
||||||
|
gra.addColorStop(1, 'transparent');
|
||||||
|
ct2.fillStyle = gra;
|
||||||
|
ct2.beginPath();
|
||||||
|
ct2.arc(x, y, r, 0, Math.PI * 2);
|
||||||
|
ct2.fill();
|
||||||
|
ctx.drawImage(temp2, 0, 0, w, h);
|
||||||
|
// 再绘制ct1的阴影,然后绘制到ct3叠加
|
||||||
|
ct1.globalCompositeOperation = 'source-out';
|
||||||
|
const gra2 = ct1.createRadialGradient(x, y, decay, x, y, r);
|
||||||
|
gra2.addColorStop(0, '#fff');
|
||||||
|
gra2.addColorStop(1, '#fff0');
|
||||||
|
ct1.beginPath();
|
||||||
|
ct1.arc(x, y, r, 0, Math.PI * 2);
|
||||||
|
ct1.fillStyle = gra2;
|
||||||
|
ct1.fill();
|
||||||
|
// 绘制到ct3上
|
||||||
|
ct3.drawImage(temp1, 0, 0, w, h);
|
||||||
|
}
|
||||||
|
// 绘制真实阴影
|
||||||
|
ct3.globalCompositeOperation = 'source-out';
|
||||||
|
ct3.fillStyle = b;
|
||||||
|
ct3.fillRect(0, 0, w, h);
|
||||||
|
ctx.globalCompositeOperation = 'destination-over';
|
||||||
|
ctx.drawImage(temp3, 0, 0, w, h);
|
||||||
|
}
|
8
src/plugin/webgl/utils.ts
Normal file
8
src/plugin/webgl/utils.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default function init() {
|
||||||
|
return { isWebGLSupported };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isWebGLSupported = (function () {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
return !!canvas.getContext('webgl');
|
||||||
|
})();
|
2
src/types/map.d.ts
vendored
2
src/types/map.d.ts
vendored
@ -61,7 +61,7 @@ interface Block<N extends Exclude<AllNumbers, 0> = Exclude<AllNumbers, 0>> {
|
|||||||
/**
|
/**
|
||||||
* 图块是否不可通行
|
* 图块是否不可通行
|
||||||
*/
|
*/
|
||||||
nopass: boolean;
|
noPass: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图块高度
|
* 图块高度
|
||||||
|
7
src/types/util.d.ts
vendored
7
src/types/util.d.ts
vendored
@ -862,6 +862,11 @@ type SelectType<R, T> = {
|
|||||||
[P in keyof R as R[P] extends T ? P : never]: R[P];
|
[P in keyof R as R[P] extends T ? P : never]: R[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从一个对象中选择类型是目标属性的键名
|
||||||
|
*/
|
||||||
|
type SelectKey<R, T> = keyof SelectType<R, T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取一段字符串的第一个字符
|
* 获取一段字符串的第一个字符
|
||||||
*/
|
*/
|
||||||
@ -883,3 +888,5 @@ type NonObjectOf<T> = SelectType<T, NonObject>;
|
|||||||
* 以一个字符串结尾
|
* 以一个字符串结尾
|
||||||
*/
|
*/
|
||||||
type EndsWith<T extends string> = `${string}${T}`;
|
type EndsWith<T extends string> = `${string}${T}`;
|
||||||
|
|
||||||
|
type KeyExcludesUnderline<T> = Excluede<keyof T, `_${string}`>;
|
||||||
|
Loading…
Reference in New Issue
Block a user