Skip to content

Commit 0509251

Browse files
committed
feat: 2048
1 parent 65bca8a commit 0509251

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1457
-223
lines changed

.firebaserc

-5
This file was deleted.

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
.vscode
22
.idea
3-
.firebase
43
.DS_Store
54

5+
.firebase
6+
.firebaserc
7+
firebase.json
8+
69
node_modules
710
package-lock.json
811
public
912
dist
13+

components/hoc/DirectControl.tsx

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as React from 'react'
2+
import {ComponentType, useEffect, useState} from "react";
3+
import {TouchEvent, useCallback} from "react";
4+
5+
6+
interface DirectControlPropsType {
7+
directKey?: string[],
8+
fireKey?: string[],
9+
10+
11+
onTouchStart?(e?: TouchEvent): void;
12+
onTouchMove?(e?: TouchEvent): void;
13+
onTouchEnd?(e?: TouchEvent): void;
14+
}
15+
16+
17+
const NORMAL = 'normal';
18+
export default function <P = any>(Component: ComponentType<{ direct: string, fire: boolean }>) {
19+
let start = {x: 0, y: 0};
20+
return ({directKey, fireKey, ...props}: P & DirectControlPropsType) => {
21+
directKey = directKey || ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
22+
fireKey = fireKey || [' '];
23+
const [pad, setPadState] = useState(NORMAL);
24+
const [fire, setFireState] = useState(false);
25+
26+
/**
27+
* 键盘按键
28+
*/
29+
useEffect(() => {
30+
const keys = [];
31+
const move = (direction: string) => {
32+
setPadState(direction);
33+
if (keys.indexOf(direction) < 0) keys.push(direction);
34+
};
35+
36+
const handleKeyDown = (e: KeyboardEvent) => {
37+
if (directKey.indexOf(e.key) >= 0) return move(e.key);
38+
if (fireKey.indexOf(e.key) >= 0) return setFireState(true);
39+
};
40+
41+
const handleKeyUp = (e: KeyboardEvent) => {
42+
if (directKey.indexOf(e.key) >= 0) {
43+
const i = keys.indexOf(e.key);
44+
if (i >= 0) keys.splice(i, 1);
45+
if (keys.length <= 0) setPadState(NORMAL);
46+
else setPadState(keys[keys.length - 1]);
47+
return;
48+
}
49+
if (fireKey.indexOf(e.key) >= 0) return setFireState(false);
50+
};
51+
52+
53+
window.addEventListener('keydown', handleKeyDown);
54+
window.addEventListener('keyup', handleKeyUp);
55+
return () => {
56+
window.removeEventListener('keydown', handleKeyDown);
57+
window.removeEventListener('keyup', handleKeyUp);
58+
};
59+
}, []);
60+
61+
/**
62+
* 手势移动
63+
*/
64+
const handleTouchStart = useCallback(({touches}: TouchEvent) => {
65+
const {clientX: x, clientY: y} = touches[0];
66+
start = {x, y};
67+
}, []);
68+
const handleTouchMove = useCallback(({touches}: TouchEvent) => {
69+
const {x, y} = start;
70+
const {clientX, clientY} = touches[0];
71+
const moveX = clientX - x;
72+
const moveY = clientY - y;
73+
if (Math.abs(moveX) <= 30 && Math.abs(moveY) <= 30) return setPadState('normal');
74+
if (Math.abs(moveX) > Math.abs(moveY)) setPadState(moveX > 0 ? 'ArrowRight' : 'ArrowLeft');
75+
else setPadState(moveY > 0 ? 'ArrowDown' : 'ArrowUp');
76+
}, []);
77+
const handleTouchEnd = useCallback(() => setPadState('normal'), []);
78+
79+
80+
return <Component {...props}
81+
direct={pad}
82+
fire={fire}
83+
onTouchStart={handleTouchStart}
84+
onTouchMove={handleTouchMove}
85+
onTouchEnd={handleTouchEnd}/>;
86+
}
87+
};

components/normal/game-pad/index.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react'
2+
import './style.less'
3+
import {TouchEvent, useCallback, useEffect, useState} from "react";
4+
import DirectControl from '~components/hoc/DirectControl'
5+
6+
7+
interface GamepadPropsType {
8+
direct?: string,
9+
fire?: boolean,
10+
onDirectChange?(d: string): void;
11+
onConfirm?(c: boolean): void;
12+
}
13+
14+
15+
const Gamepad = ({direct, fire, onDirectChange, onConfirm, ...other}: GamepadPropsType) => {
16+
useEffect(() => onDirectChange && onDirectChange(direct), [direct]);
17+
useEffect(() => onConfirm && onConfirm(fire), [fire]);
18+
19+
return <div className="game-pad">
20+
<div className="bottom"/>
21+
<div className="circle-out"/>
22+
<div className="circle-in"/>
23+
<div className="rocker-bottom"/>
24+
<div className={`rocker ${direct}`}>
25+
<div className="m"/>
26+
<div className="t" {...other}/>
27+
</div>
28+
<div className={`button${fire ? ' active' : ''}`}/>
29+
</div>
30+
};
31+
32+
export default DirectControl<GamepadPropsType>(Gamepad);

components/normal/game-pad/style.less

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
.set-side(@side, @color) {
2+
position: absolute;
3+
width: @side;
4+
height: @side;
5+
border-radius: @side;
6+
background-color: @color;
7+
}
8+
9+
.game-pad {
10+
position: relative;
11+
width: 4em;
12+
height: 4em;
13+
display: flex;
14+
justify-content: center;
15+
align-items: center;
16+
17+
.bottom {
18+
.set-side(100%, #a8aeb4);
19+
border-radius: 0.5em;
20+
box-shadow: 0.05em 0.05em 0.1em #aaaaaa;
21+
}
22+
.circle-out { .set-side(2.8em, #516471); }
23+
.circle-in { .set-side(2em, #36424b); }
24+
.button {
25+
.set-side(0.5em, #da3732);
26+
top: 3.2em;
27+
left: 0.4em;
28+
box-shadow: 0 0.05em 0.05em #a81510;
29+
&.active {
30+
top: 3.25em;
31+
box-shadow: 0 0.01em 0.01em #a81510;
32+
}
33+
}
34+
35+
@rocker-side: 0.5em;
36+
.rocker-bottom { .set-side(@rocker-side, #ffffff); }
37+
.rocker {
38+
position: absolute;
39+
width: 100%;
40+
height: 100%;
41+
display: flex;
42+
justify-content: center;
43+
align-items: center;
44+
.m {
45+
position: absolute;
46+
width: @rocker-side;
47+
height: 3*@rocker-side;
48+
background-color: #ffffff;
49+
}
50+
.t {
51+
.set-side(4*@rocker-side, #da3732);
52+
box-shadow: 0.01em 0.01em 0.1em 0.01em #000000;
53+
}
54+
}
55+
56+
@move-side: @rocker-side*2.5;
57+
.rocker.ArrowUp, .rocker.w {
58+
.t {top: -@move-side;}
59+
}
60+
.rocker.ArrowDown, .rocker.s {
61+
.t {bottom: -@move-side;}
62+
}
63+
.rocker.ArrowLeft, .rocker.a {
64+
.t {left: -@move-side;}
65+
}
66+
.rocker.ArrowRight, .rocker.d {
67+
.t {right: -@move-side;}
68+
}
69+
70+
.rocker.ArrowUp, .rocker.w {
71+
.m {top: @rocker-side;}
72+
}
73+
.rocker.ArrowDown, .rocker.s {
74+
.m {bottom: @rocker-side;}
75+
}
76+
.rocker.ArrowLeft, .rocker.a {
77+
.m {left: 2*@rocker-side;transform: rotate(90deg);}
78+
}
79+
.rocker.ArrowRight, .rocker.d {
80+
.m {right: 2*@rocker-side;transform: rotate(90deg);}
81+
}
82+
}

components/normal/overlay/index.tsx

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as React from 'react'
2+
import {HTMLProps, ReactElement, useEffect, useState} from "react";
3+
import './style.less'
4+
5+
6+
interface OverlayPropsType extends HTMLProps<HTMLDivElement> {
7+
active?: boolean;
8+
defaultActive?: boolean;
9+
children?: string | ReactElement | ReactElement[],
10+
}
11+
12+
13+
export default ({className, active, defaultActive, children, ...other}: OverlayPropsType) => {
14+
const [__active, setActive] = useState(defaultActive);
15+
useEffect(() => {
16+
if (typeof active === 'undefined') return;
17+
setActive(active);
18+
}, [active]);
19+
20+
21+
return <div className={`overlay ${__active ? 'active' : ''} ${className || ''}`}
22+
{...other}>
23+
{children}
24+
</div>
25+
}

components/normal/overlay/style.less

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.overlay {
2+
position: absolute;
3+
z-index: -1;
4+
opacity: 0;
5+
width: 100%;
6+
height: 100%;
7+
background-color: rgba(0, 0, 0, .6);
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
color: white;
12+
font-size: 2em;
13+
transition: all .3s linear;
14+
}
15+
.overlay.active {
16+
z-index: 999;
17+
opacity: 1;
18+
}

0 commit comments

Comments
 (0)