和解的实现

This commit is contained in:
k8w 2021-12-02 20:05:32 +08:00
parent 49f8c94974
commit 23186001db
52 changed files with 2792 additions and 1164 deletions

View File

@ -0,0 +1,12 @@
import { ApiCallWs } from "tsrpc";
import { roomInstance } from "..";
import { ReqJoin, ResJoin } from "../shared/protocols/PtlJoin";
export async function ApiJoin(call: ApiCallWs<ReqJoin, ResJoin>) {
let playerId = roomInstance.join(call.req, call.conn);
call.succ({
playerId: playerId,
gameState: roomInstance.gameSystem.state
})
}

View File

@ -1,12 +0,0 @@
import { ApiCallWs } from "tsrpc";
import { roomInstance } from "..";
import { ReqJoinRoom, ResJoinRoom } from "../shared/protocols/PtlJoinRoom";
export async function ApiJoinRoom(call: ApiCallWs<ReqJoinRoom, ResJoinRoom>) {
let uid = roomInstance.join(call.req, call.conn);
call.succ({
uid: uid,
roomState: roomInstance.state
})
}

View File

@ -1,17 +1,36 @@
import 'k8w-extend-native';
import * as path from "path";
import { WsServer } from "tsrpc";
import { WsConnection, WsServer } from "tsrpc";
import { Room } from './models/Room';
import { serviceProto } from './shared/protocols/serviceProto';
import { serviceProto, ServiceType } from './shared/protocols/serviceProto';
// Create the Server
export const server = new WsServer(serviceProto, {
port: 3000,
// Remove this to use binary mode (remove from the client too)
json: true
// json: true
});
// 测试,只有一个房间
// 断开连接后退出房间
server.flows.postDisconnectFlow.push(v => {
let conn = v.conn as WsConnection<ServiceType>;
if (conn.playerId) {
roomInstance.leave(conn.playerId, conn);
}
return v;
})
// 模拟网络延迟
server.flows.preRecvDataFlow.push(async v => {
await new Promise(rs => { setTimeout(rs, 300) })
return v;
})
server.flows.preSendDataFlow.push(async v => {
await new Promise(rs => { setTimeout(rs, 300) })
return v;
})
export const roomInstance = new Room(server);
// Initialize before server start

View File

@ -1,102 +1,100 @@
import { WsConnection, WsServer } from "tsrpc";
import { ReqJoinRoom } from "../shared/protocols/PtlJoinRoom";
import { MsgFrame } from "../shared/protocols/serverMsgs/MsgFrame";
import { GameSystem, GameSystemInput, PlayerJoin } from "../shared/game/GameSystem";
import { ReqJoin } from "../shared/protocols/PtlJoin";
import { ServiceType } from "../shared/protocols/serviceProto";
import { applyPlayerInput, PlayerInput, PlayerState } from "../shared/states/Player";
import { RoomState } from "../shared/states/RoomState";
/**
* - -
*/
export class Room {
state: RoomState = {
players: []
}
// 次数/秒
syncRate = 10;
lastUid = 0;
syncRate = 3;
nextPlayerId = 1;
gameSystem = new GameSystem();
server: WsServer<ServiceType>;
conns: WsConnection[] = [];
pendingInputs: MsgFrame['inputs'] = [];
conns: WsConnection<ServiceType>[] = [];
pendingInputs: GameSystemInput[] = [];
playerLastSn: { [playerId: number]: number | undefined } = {};
lastSyncTime?: number;
constructor(server: WsServer<ServiceType>) {
this.server = server;
setInterval(() => { this.sendSyncFrame() }, 1000 / this.syncRate);
}
sendSyncFrame() {
// 发送同步帧
this.server.broadcastMsg('serverMsgs/Frame', {
inputs: this.pendingInputs
}, this.conns);
this.pendingInputs = [];
setInterval(() => { this.sync() }, 1000 / this.syncRate);
}
/** 加入房间 */
join(req: ReqJoinRoom, conn: WsConnection<ServiceType>) {
let player: PlayerState = {
...req,
uid: ++this.lastUid,
join(req: ReqJoin, conn: WsConnection<ServiceType>) {
let input: PlayerJoin = {
type: 'PlayerJoin',
playerId: this.nextPlayerId++,
// 初始位置随机
pos: {
x: Math.random() * 10,
y: Math.random() * 10
}
}
this.conns.push(conn);
this.state.players.push(player);
this.applyInput(input);
conn.uid = player.uid;
conn.listenMsg('clientMsgs/Input', call => {
this.pendingInputs.push({
uid: player.uid,
msgInput: call.msg
});
this.applyInput({
uid: player.uid,
input: call.msg
});
this.conns.push(conn);
conn.playerId = input.playerId;
conn.listenMsg('client/ClientInput', call => {
this.playerLastSn[input.playerId] = call.msg.sn;
call.msg.inputs.forEach(v => {
this.applyInput({
...v,
playerId: input.playerId
});
})
});
this.server.broadcastMsg('serverMsgs/Join', {
player: player
}, this.conns);
return input.playerId;
}
return player.uid;
applyInput(input: GameSystemInput) {
this.pendingInputs.push(input);
}
sync() {
let inputs = this.pendingInputs;
this.pendingInputs = [];
// Apply inputs
inputs.forEach(v => {
this.gameSystem.applyInput(v)
});
// TimePast
let now = process.uptime();
this.applyInput({
type: 'TimePast',
dt: now - (this.lastSyncTime ?? now)
});
this.lastSyncTime = now;
// 发送同步帧
this.conns.forEach(v => {
v.sendMsg('server/Frame', {
inputs: inputs,
lastSn: this.playerLastSn[v.playerId!]
})
});
}
/** 离开房间 */
leave(uid: number, conn: WsConnection<ServiceType>) {
this.conns.removeOne(v => v.uid === uid);
this.state.players.removeOne(v => v.uid === uid);
conn.unlistenMsgAll('clientMsgs/Input');
this.server.broadcastMsg('serverMsgs/Leave', {
uid: uid
}, this.conns);
leave(playerId: number, conn: WsConnection<ServiceType>) {
this.conns.removeOne(v => v.playerId === playerId);
this.applyInput({
type: 'PlayerLeave',
playerId: playerId
});
}
applyInput(input: RoomInput) {
let playerIndex = this.state.players.findIndex(v => v.uid === input.uid);
if (playerIndex > -1) {
this.state.players[playerIndex] = applyPlayerInput(this.state.players[playerIndex], input.input);
}
}
}
export interface RoomInput {
uid: number,
input: PlayerInput
}
declare module 'tsrpc' {
export interface WsConnection {
uid?: number;
playerId?: number;
}
}

View File

@ -0,0 +1,124 @@
import { gameConfig } from "./gameConfig";
import { ArrowState } from "./state/ArrowState";
import { PlayerState } from "./state/PlayerState";
export interface GameSystemState {
now: number,
players: PlayerState[],
arrow: ArrowState[],
nextArrowId: number
}
export class GameSystem {
// State (Render Pull)
private _state: GameSystemState = {
now: 0,
players: [],
arrow: [],
nextArrowId: 1
}
get state(): Readonly<GameSystemState> {
return this._state
}
reset(state: GameSystemState) {
this._state = Object.merge({}, state);
}
// Input
applyInput(input: GameSystemInput) {
if (input.type === 'PlayerMove') {
let player = this._state.players.find(v => v.id === input.playerId);
if (!player) {
return;
}
if (player.dizzyEndTime && player.dizzyEndTime > this._state.now) {
return;
}
player.pos.x += input.speed.x * input.dt;
player.pos.y += input.speed.y * input.dt;
}
else if (input.type === 'PlayerAttack') {
let player = this._state.players.find(v => v.id === input.playerId);
if (player) {
this._state.arrow.push({
id: this._state.nextArrowId++,
fromPlayerId: input.playerId,
startPos: { ...player.pos },
startTime: this._state.now,
targetPos: {
x: player.pos.x + input.direction.x * gameConfig.arrowDistance,
y: player.pos.y + input.direction.y * gameConfig.arrowDistance
},
targetTime: this._state.now + gameConfig.arrowFlyTime
});
}
}
else if (input.type === 'PlayerJoin') {
this.state.players.push({
id: input.playerId,
pos: { ...input.pos }
})
}
else if (input.type === 'PlayerLeave') {
this.state.players.remove(v => v.id === input.playerId);
}
else if (input.type === 'TimePast') {
this._state.now += input.dt;
// 落地的 Arrow
for (let i = this._state.arrow.length - 1; i > -1; --i) {
let arrow = this._state.arrow[i];
if (arrow.targetTime <= this._state.now) {
// 伤害判定
let damagedPlayers = this._state.players.filter(v => {
return (v.pos.x - arrow.targetPos.x) * (v.pos.x - arrow.targetPos.x) + (v.pos.y - arrow.targetPos.y) * (v.pos.y - arrow.targetPos.y) <= gameConfig.arrowDistance * gameConfig.arrowDistance
});
damagedPlayers.forEach(p => {
// 设置击晕状态
p.dizzyEndTime = this._state.now + gameConfig.arrowDizzyTime;
// Event
this.onDamage.forEach(h => h({
fromPlayerId: arrow.fromPlayerId,
toPlayerId: p.id
}))
})
}
}
}
}
// Events (Game Push)
onDamage: ((e: { fromPlayerId: number, toPlayerId: number }) => void)[] = [];
}
export interface PlayerMove {
type: 'PlayerMove',
playerId: number,
speed: { x: number, y: number },
// 移动的时间 (秒)
dt: number,
}
export interface PlayerAttack {
type: 'PlayerAttack',
playerId: number,
direction: { x: number, y: number },
}
export interface PlayerJoin {
type: 'PlayerJoin',
playerId: number,
pos: { x: number, y: number }
}
export interface PlayerLeave {
type: 'PlayerLeave',
playerId: number
}
export interface TimePast {
type: 'TimePast',
dt: number
}
export type GameSystemInput = PlayerMove | PlayerAttack | PlayerJoin | PlayerLeave | TimePast;

View File

@ -0,0 +1,10 @@
export const gameConfig = {
// 攻击技能的冷却时间(毫秒)
attackCD: 1000,
moveSpeed: 10,
arrowFlyTime: 1000,
arrowDistance: 2,
arrowDizzyTime: 1000
}

View File

@ -0,0 +1,8 @@
export type ArrowState = {
id: number,
fromPlayerId: number,
startTime: number,
startPos: { x: number, y: number },
targetTime: number,
targetPos: { x: number, y: number }
}

View File

@ -0,0 +1,7 @@
export interface PlayerState {
id: number,
// 位置
pos: { x: number, y: number },
// 晕眩结束时间
dizzyEndTime?: number,
}

View File

@ -0,0 +1,12 @@
import { GameSystemState } from "../game/GameSystem";
export interface ReqJoin {
}
export interface ResJoin {
playerId: number,
gameState: GameSystemState
}
// export const conf = {}

View File

@ -1,14 +0,0 @@
import { PlayerState } from "../states/Player";
import { RoomState } from "../states/RoomState";
export interface ReqJoinRoom {
nickname: string;
skinId: number;
}
export interface ResJoinRoom {
uid: number,
roomState: RoomState
}
// export const conf = {}

View File

@ -0,0 +1,8 @@
import { PlayerAttack, PlayerMove } from "../../game/GameSystem";
export interface MsgClientInput {
sn: number,
inputs: ClientInput[]
};
export type ClientInput = Omit<PlayerMove, 'playerId'> | Omit<PlayerAttack, 'playerId'>;

View File

@ -1,3 +0,0 @@
import { PlayerInput } from "../../states/Player";
export type MsgInput = { sn: number } & PlayerInput;

View File

@ -0,0 +1,6 @@
import { GameSystemInput } from "../../game/GameSystem";
export interface MsgFrame {
inputs: GameSystemInput[],
lastSn?: number
}

View File

@ -1,8 +0,0 @@
import { MsgInput } from "../clientMsgs/MsgInput";
export interface MsgFrame {
inputs: {
uid: number,
msgInput: MsgInput
}[]
}

View File

@ -1,5 +0,0 @@
import { PlayerState } from "../../states/Player";
export interface MsgJoin {
player: PlayerState;
}

View File

@ -1,3 +0,0 @@
export interface MsgLeave {
uid: number;
}

View File

@ -1,66 +1,128 @@
import { ServiceProto } from 'tsrpc-proto';
import { MsgInput } from './clientMsgs/MsgInput';
import { ReqJoinRoom, ResJoinRoom } from './PtlJoinRoom';
import { MsgFrame } from './serverMsgs/MsgFrame';
import { MsgJoin } from './serverMsgs/MsgJoin';
import { MsgLeave } from './serverMsgs/MsgLeave';
import { MsgClientInput } from './client/MsgClientInput';
import { ReqJoin, ResJoin } from './PtlJoin';
import { MsgFrame } from './server/MsgFrame';
export interface ServiceType {
api: {
"JoinRoom": {
req: ReqJoinRoom,
res: ResJoinRoom
"Join": {
req: ReqJoin,
res: ResJoin
}
},
msg: {
"clientMsgs/Input": MsgInput,
"serverMsgs/Frame": MsgFrame,
"serverMsgs/Join": MsgJoin,
"serverMsgs/Leave": MsgLeave
"client/ClientInput": MsgClientInput,
"server/Frame": MsgFrame
}
}
export const serviceProto: ServiceProto<ServiceType> = {
"version": 3,
"services": [
{
"id": 2,
"name": "clientMsgs/Input",
"id": 0,
"name": "client/ClientInput",
"type": "msg"
},
{
"id": 3,
"name": "JoinRoom",
"id": 1,
"name": "Join",
"type": "api"
},
{
"id": 7,
"name": "serverMsgs/Frame",
"type": "msg"
},
{
"id": 4,
"name": "serverMsgs/Join",
"type": "msg"
},
{
"id": 5,
"name": "serverMsgs/Leave",
"id": 2,
"name": "server/Frame",
"type": "msg"
}
],
"types": {
"clientMsgs/MsgInput/MsgInput": {
"type": "Intersection",
"client/MsgClientInput/MsgClientInput": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "sn",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "inputs",
"type": {
"type": "Array",
"elementType": {
"type": "Reference",
"target": "client/MsgClientInput/ClientInput"
}
}
}
]
},
"client/MsgClientInput/ClientInput": {
"type": "Union",
"members": [
{
"id": 0,
"type": {
"target": {
"type": "Reference",
"target": "../game/GameSystem/PlayerMove"
},
"keys": [
"playerId"
],
"type": "Omit"
}
},
{
"id": 1,
"type": {
"target": {
"type": "Reference",
"target": "../game/GameSystem/PlayerAttack"
},
"keys": [
"playerId"
],
"type": "Omit"
}
}
]
},
"../game/GameSystem/PlayerMove": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "type",
"type": {
"type": "Literal",
"literal": "PlayerMove"
}
},
{
"id": 1,
"name": "playerId",
"type": {
"type": "Number"
}
},
{
"id": 2,
"name": "speed",
"type": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "sn",
"name": "x",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "y",
"type": {
"type": "Number"
}
@ -69,19 +131,15 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
},
{
"id": 1,
"id": 3,
"name": "dt",
"type": {
"type": "Reference",
"target": "../states/Player/PlayerInput"
"type": "Number"
}
}
]
},
"../states/Player/PlayerInput": {
"type": "Reference",
"target": "../states/Player/PlayerMove"
},
"../states/Player/PlayerMove": {
"../game/GameSystem/PlayerAttack": {
"type": "Interface",
"properties": [
{
@ -89,12 +147,19 @@ export const serviceProto: ServiceProto<ServiceType> = {
"name": "type",
"type": {
"type": "Literal",
"literal": "move"
"literal": "PlayerAttack"
}
},
{
"id": 1,
"name": "offset",
"name": "playerId",
"type": {
"type": "Number"
}
},
{
"id": 2,
"name": "direction",
"type": {
"type": "Interface",
"properties": [
@ -117,87 +182,276 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
]
},
"PtlJoinRoom/ReqJoinRoom": {
"PtlJoin/ReqJoin": {
"type": "Interface"
},
"PtlJoin/ResJoin": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "nickname",
"name": "playerId",
"type": {
"type": "String"
"type": "Number"
}
},
{
"id": 1,
"name": "skinId",
"name": "gameState",
"type": {
"type": "Number"
"type": "Reference",
"target": "../game/GameSystem/GameSystemState"
}
}
]
},
"PtlJoinRoom/ResJoinRoom": {
"../game/GameSystem/GameSystemState": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "uid",
"name": "now",
"type": {
"type": "Number"
}
},
{
"id": 3,
"name": "roomState",
"type": {
"type": "Reference",
"target": "../states/RoomState/RoomState"
}
}
]
},
"../states/RoomState/RoomState": {
"type": "Interface",
"properties": [
{
"id": 0,
"id": 1,
"name": "players",
"type": {
"type": "Array",
"elementType": {
"type": "Reference",
"target": "../states/Player/PlayerState"
"target": "../game/state/PlayerState/PlayerState"
}
}
},
{
"id": 2,
"name": "arrow",
"type": {
"type": "Array",
"elementType": {
"type": "Reference",
"target": "../game/state/ArrowState/ArrowState"
}
}
},
{
"id": 3,
"name": "nextArrowId",
"type": {
"type": "Number"
}
}
]
},
"../states/Player/PlayerState": {
"../game/state/PlayerState/PlayerState": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "uid",
"name": "id",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "nickname",
"name": "pos",
"type": {
"type": "String"
"type": "Interface",
"properties": [
{
"id": 0,
"name": "x",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "y",
"type": {
"type": "Number"
}
}
]
}
},
{
"id": 2,
"name": "skinId",
"name": "dizzyEndTime",
"type": {
"type": "Number"
},
"optional": true
}
]
},
"../game/state/ArrowState/ArrowState": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "id",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "fromPlayerId",
"type": {
"type": "Number"
}
},
{
"id": 2,
"name": "startTime",
"type": {
"type": "Number"
}
},
{
"id": 3,
"name": "startPos",
"type": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "x",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "y",
"type": {
"type": "Number"
}
}
]
}
},
{
"id": 4,
"name": "targetTime",
"type": {
"type": "Number"
}
},
{
"id": 5,
"name": "targetPos",
"type": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "x",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "y",
"type": {
"type": "Number"
}
}
]
}
}
]
},
"server/MsgFrame/MsgFrame": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "inputs",
"type": {
"type": "Array",
"elementType": {
"type": "Reference",
"target": "../game/GameSystem/GameSystemInput"
}
}
},
{
"id": 1,
"name": "lastSn",
"type": {
"type": "Number"
},
"optional": true
}
]
},
"../game/GameSystem/GameSystemInput": {
"type": "Union",
"members": [
{
"id": 0,
"type": {
"type": "Reference",
"target": "../game/GameSystem/PlayerMove"
}
},
{
"id": 1,
"type": {
"type": "Reference",
"target": "../game/GameSystem/PlayerAttack"
}
},
{
"id": 2,
"type": {
"type": "Reference",
"target": "../game/GameSystem/PlayerJoin"
}
},
{
"id": 3,
"type": {
"type": "Reference",
"target": "../game/GameSystem/PlayerLeave"
}
},
{
"id": 4,
"type": {
"type": "Reference",
"target": "../game/GameSystem/TimePast"
}
}
]
},
"../game/GameSystem/PlayerJoin": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "type",
"type": {
"type": "Literal",
"literal": "PlayerJoin"
}
},
{
"id": 1,
"name": "playerId",
"type": {
"type": "Number"
}
},
{
"id": 2,
"name": "pos",
"type": {
"type": "Interface",
@ -221,57 +475,40 @@ export const serviceProto: ServiceProto<ServiceType> = {
}
]
},
"serverMsgs/MsgFrame/MsgFrame": {
"../game/GameSystem/PlayerLeave": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "inputs",
"name": "type",
"type": {
"type": "Array",
"elementType": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "uid",
"type": {
"type": "Number"
}
},
{
"id": 1,
"name": "msgInput",
"type": {
"type": "Reference",
"target": "clientMsgs/MsgInput/MsgInput"
}
}
]
}
"type": "Literal",
"literal": "PlayerLeave"
}
},
{
"id": 1,
"name": "playerId",
"type": {
"type": "Number"
}
}
]
},
"serverMsgs/MsgJoin/MsgJoin": {
"../game/GameSystem/TimePast": {
"type": "Interface",
"properties": [
{
"id": 0,
"name": "player",
"name": "type",
"type": {
"type": "Reference",
"target": "../states/Player/PlayerState"
"type": "Literal",
"literal": "TimePast"
}
}
]
},
"serverMsgs/MsgLeave/MsgLeave": {
"type": "Interface",
"properties": [
},
{
"id": 0,
"name": "uid",
"id": 1,
"name": "dt",
"type": {
"type": "Number"
}

View File

@ -1,30 +0,0 @@
export interface PlayerState {
uid: number,
nickname: string,
skinId: number,
// 可变状态
pos: {
x: number,
y: number
}
}
export interface PlayerMove {
type: 'move',
// 位移距离
offset: {
x: number,
y: number
}
}
export type PlayerInput = PlayerMove;
export function applyPlayerInput(state: PlayerState, input: PlayerInput): PlayerState {
if (input.type === 'move') {
state.pos.x += input.offset.x;
state.pos.y += input.offset.y;
}
return state;
}

View File

@ -1,5 +0,0 @@
import { PlayerState } from "./Player";
export interface RoomState {
players: PlayerState[]
}

View File

@ -4,52 +4,32 @@
"_objFlags": 0,
"_native": "",
"_effectAsset": {
"__uuid__": "1baf0fc9-befa-459c-8bdd-af1a450a0319"
"__uuid__": "a3cd009f-0ab0-420d-9278-b9fdab939bbc",
"__expectedType__": "cc.EffectAsset"
},
"_techIdx": 0,
"_defines": [
{
"USE_ALBEDO_MAP": true,
"ROUGHNESS_CHANNEL": "g",
"METALLIC_CHANNEL": "b",
"OCCLUSION_CHANNEL": "r"
"USE_TEXTURE": true
}
],
"_states": [
{
"rasterizerState": {},
"depthStencilState": {},
"blendState": {
"targets": [
{}
]
},
"depthStencilState": {},
"rasterizerState": {}
}
}
],
"_props": [
{
"albedoScale": {
"__type__": "cc.Vec4",
"x": 1,
"y": 1,
"z": 1,
"w": 1
},
"pbrScale": {
"__type__": "cc.Vec4",
"x": 1,
"y": 0.242535620927811,
"z": 0.400000005960464,
"w": 1
},
"alphaThreshold": 1,
"normalStrenth": 1,
"mainTexture": {
"__uuid__": "27e1fcb7-5016-4252-8c43-ac9c1c97308e@6c48a"
},
"occlusion": 1,
"roughness": 0.242535620927811,
"metallic": 0.400000005960464
"__uuid__": "27e1fcb7-5016-4252-8c43-ac9c1c97308e@6c48a",
"__expectedType__": "cc.Texture2D"
}
}
]
}

View File

@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "40e7bad4-3fa9-45c6-9d50-ce41c40e8174",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@ -0,0 +1,43 @@
{
"__type__": "cc.Material",
"_name": "Material #7",
"_objFlags": 0,
"_native": "",
"_effectAsset": {
"__uuid__": "1baf0fc9-befa-459c-8bdd-af1a450a0319",
"__expectedType__": "cc.EffectAsset"
},
"_techIdx": 0,
"_defines": [
{
"USE_ALBEDO_MAP": true
}
],
"_states": [
{
"rasterizerState": {},
"blendState": {
"targets": [
{}
]
},
"depthStencilState": {}
}
],
"_props": [
{
"mainTexture": {
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@291d4",
"__expectedType__": "cc.Texture2D"
},
"albedoScale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"metallic": 0.400000005960464,
"roughness": 0.70710676908493
}
]
}

View File

@ -0,0 +1,11 @@
{
"ver": "1.0.9",
"importer": "material",
"imported": true,
"uuid": "bc711895-84b3-4729-85f8-978c46347d19",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@ -0,0 +1,36 @@
{
"__type__": "cc.Material",
"_name": "",
"_objFlags": 0,
"_native": "",
"_effectAsset": {
"__uuid__": "a3cd009f-0ab0-420d-9278-b9fdab939bbc",
"__expectedType__": "cc.EffectAsset"
},
"_techIdx": 0,
"_defines": [
{
"USE_INSTANCING": true,
"USE_TEXTURE": true
}
],
"_states": [
{
"rasterizerState": {},
"depthStencilState": {},
"blendState": {
"targets": [
{}
]
}
}
],
"_props": [
{
"mainTexture": {
"__uuid__": "e90ad74a-b668-4e2c-bf91-51a2a91b02aa@6c48a",
"__expectedType__": "cc.Texture2D"
}
}
]
}

View File

@ -0,0 +1,11 @@
{
"ver": "1.0.9",
"importer": "material",
"imported": true,
"uuid": "5cf1ff47-b589-490a-bc78-a88f743b9798",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@ -72,7 +72,7 @@
"userData": {
"gltfIndex": 0,
"wrapMode": 2,
"sample": 60,
"sample": 30,
"span": {
"from": 0.8,
"to": 1.5
@ -95,7 +95,7 @@
"userData": {
"gltfIndex": 0,
"wrapMode": 2,
"sample": 60,
"sample": 30,
"span": {
"from": 1.5333333333333334,
"to": 2.2
@ -264,14 +264,12 @@
"name": "run",
"from": 0.8,
"to": 1.5,
"fps": 60,
"wrapMode": 2
},
{
"name": "attack",
"from": 1.5333333333333334,
"to": 2.2,
"fps": 60,
"wrapMode": 2
},
{

View File

@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "e8e5dc6d-bdfe-4c40-8e74-4f36361cee55",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "9aaf6024-7754-4b77-b5f5-7ca6d1ef973a",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@ -0,0 +1,40 @@
{
"ver": "1.0.21",
"importer": "image",
"imported": true,
"uuid": "ae68bc97-2fbf-4ff6-a6be-d9375f9a3423",
"files": [
".png",
".json"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "ae68bc97-2fbf-4ff6-a6be-d9375f9a3423@6c48a",
"displayName": "grass",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "repeat",
"wrapModeT": "repeat",
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0,
"isUuid": true,
"imageUuidOrDatabaseUri": "ae68bc97-2fbf-4ff6-a6be-d9375f9a3423"
},
"ver": "1.0.21",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"hasAlpha": false,
"type": "texture",
"redirect": "ae68bc97-2fbf-4ff6-a6be-d9375f9a3423@6c48a"
}
}

View File

@ -0,0 +1,42 @@
{
"__type__": "cc.Material",
"_name": "",
"_objFlags": 0,
"_native": "",
"_effectAsset": {
"__uuid__": "a3cd009f-0ab0-420d-9278-b9fdab939bbc",
"__expectedType__": "cc.EffectAsset"
},
"_techIdx": 0,
"_defines": [
{
"USE_TEXTURE": true
}
],
"_states": [
{
"rasterizerState": {},
"depthStencilState": {},
"blendState": {
"targets": [
{}
]
}
}
],
"_props": [
{
"mainTexture": {
"__uuid__": "ae68bc97-2fbf-4ff6-a6be-d9375f9a3423@6c48a",
"__expectedType__": "cc.Texture2D"
},
"tilingOffset": {
"__type__": "cc.Vec4",
"x": 100,
"y": 100,
"z": 0,
"w": 0
}
}
]
}

View File

@ -0,0 +1,11 @@
{
"ver": "1.0.9",
"importer": "material",
"imported": true,
"uuid": "00f711c1-6f4c-4de3-bc9b-52888e03f3ac",
"files": [
".json"
],
"subMetas": {},
"userData": {}
}

View File

@ -1,10 +1,8 @@
import { _decorator, Component, Node, EventTouch, tween, Vec2, Vec3 } from 'cc';
import { MathUtil } from '../../../scripts/models/MathUtil';
import { Component, EventTouch, Node, Vec2, Vec3, _decorator } from 'cc';
import { MathUtil } from '../../scripts/models/MathUtil';
const { ccclass, property } = _decorator;
const v3_1 = new Vec3;
const v3_2 = new Vec3;
export interface JoystickOptions {
onOperate: (output: JoystickOutput) => void,
onOperateEnd: () => void,
@ -38,7 +36,6 @@ export class Joystick extends Component {
this._options = v;
}
onLoad() {
this.node.on(Node.EventType.TOUCH_START, this.onTouch, this);
this.node.on(Node.EventType.TOUCH_MOVE, this.onTouch, this);
@ -49,6 +46,10 @@ export class Joystick extends Component {
private _touchStartPos?: Vec2;
onTouch(e: EventTouch) {
if (!e.touch) {
return;
}
this.disk.active = true;
let loc = e.touch.getUILocation();

View File

@ -0,0 +1,900 @@
[
{
"__type__": "cc.Prefab",
"_name": "",
"_objFlags": 0,
"_native": "",
"data": {
"__id__": 1
},
"optimizationPolicy": 0,
"asyncLoadAssets": false
},
{
"__type__": "cc.Node",
"_name": "Player",
"_objFlags": 0,
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [
{
"__id__": 31
},
{
"__id__": 33
}
],
"_prefab": {
"__id__": 35
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "RootNode",
"_objFlags": 0,
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 3
},
{
"__id__": 17
},
{
"__id__": 19
},
{
"__id__": 21
},
{
"__id__": 26
},
{
"__id__": 28
}
],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 30
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Dummy01",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [
{
"__id__": 4
},
{
"__id__": 8
},
{
"__id__": 10
},
{
"__id__": 12
},
{
"__id__": 14
}
],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 16
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 4.31247441140831e-32,
"z": 2.27106511374586e-9
},
"_lrot": {
"__type__": "cc.Quat",
"x": 8.146034247147302e-8,
"y": 0,
"z": 0,
"w": 0.9999999999999967
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2.53999996185303,
"y": 2.53999996185303,
"z": 2.53999996185303
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0.000009334667642611398,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "Body",
"_objFlags": 0,
"_parent": {
"__id__": 3
},
"_children": [
{
"__id__": 5
}
],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 7
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -0.000466376630356535,
"y": 0.0555356666445732,
"z": -4.76837147544984e-9
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0.0000013147252531426701,
"y": 5.898311123805427e-7,
"z": 0.7037748466382776,
"w": 0.7104230888964633
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.256809949874878,
"y": 0.439815044403076,
"z": 0.0870304778218269
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0.006324434041980695,
"y": -0.006170109051330866,
"z": 89.46130112452344
},
"_id": ""
},
{
"__type__": "cc.Node",
"_name": "head",
"_objFlags": 0,
"_parent": {
"__id__": 4
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 6
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0.931041061878204,
"y": -1.62124635494365e-7,
"z": 1.28416282052513e-8
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.000007869318285142453,
"y": -2.023334521019378e-7,
"z": -0.001340885290760051,
"w": 0.9999991009819308
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1.24272096157074,
"y": 1.03568971157074,
"z": 1.0696634054184
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -0.0009017909722726095,
"y": -0.00002439492589956044,
"z": -0.15365418180060705
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "1etT/vqTVXbIC6bSTTJON5"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "38UefSlhhbc7fGntsIPZh6"
},
{
"__type__": "cc.Node",
"_name": "legRight",
"_objFlags": 0,
"_parent": {
"__id__": 3
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 9
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -0.15381583571434,
"y": 0.0330642610788345,
"z": -4.76837147544984e-9
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0.6775014926325843,
"y": 0.20089959890094192,
"z": 0.6787423421146606,
"w": -0.19984972269874676
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.184104666113853,
"y": 0.347824722528458,
"z": 0.0710852295160294
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -147.07673505109858,
"y": -90.12052935501679,
"z": 0.05309133906220757
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "a7AVcLwkBZ46dzYAYb4QPx"
},
{
"__type__": "cc.Node",
"_name": "legLeft",
"_objFlags": 0,
"_parent": {
"__id__": 3
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 11
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0.150058135390282,
"y": 0.0330642610788345,
"z": -1.43051144263495e-8
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0.6356643535110694,
"y": 0.3106639432517853,
"z": 0.635291228429095,
"w": -0.3095541941561399
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.184104397892952,
"y": 0.347825288772583,
"z": 0.0710852593183517
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -127.97583425594252,
"y": -90.01226481486562,
"z": 0.09407173384696697
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "6egLmgEjJSxb7mZpFf7FSD"
},
{
"__type__": "cc.Node",
"_name": "handRight",
"_objFlags": 0,
"_parent": {
"__id__": 3
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 13
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -0.204364582896233,
"y": 0.240019470453262,
"z": -1.90734859017994e-8
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.00043949465228992625,
"y": -0.00002823252593632647,
"z": 0.9849262411574669,
"w": -0.17297429151986207
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.184104412794113,
"y": 0.347825288772583,
"z": 0.07108523696661
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 179.9873448759303,
"y": 179.94664434517173,
"z": -19.921588202819525
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "50DRmSLnFemalb9mhuT9XN"
},
{
"__type__": "cc.Node",
"_name": "handLeft",
"_objFlags": 0,
"_parent": {
"__id__": 3
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 15
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0.203091412782669,
"y": 0.240019470453262,
"z": -1.90734859017994e-8
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.00001006337055508986,
"y": -0.0003337875805372209,
"z": -0.17397424484232848,
"w": 0.9847501462889564
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 0.184104338288307,
"y": 0.347825139760971,
"z": 0.0710852667689323
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -0.008291917560534557,
"y": -0.04030648567970078,
"z": -20.037940451355507
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "5eMcps3V1RoqefFsSsm8Sx"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "e14q0XOvhSf7Wwpr+JP8WX"
},
{
"__type__": "cc.Node",
"_name": "Bip001",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 18
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -2.95868706703186,
"y": 0.568015873432159,
"z": 2.59012746810913
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.5000003129242964,
"y": -0.49999968707550774,
"z": -0.49999968707550774,
"w": 0.5000003129242964
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 6.45159912109375,
"y": 6.45159912109375,
"z": 6.45160007476807
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -90,
"y": -89.99992828301158,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "72wx+9XH9fS5Jsw9lb3u/+"
},
{
"__type__": "cc.Node",
"_name": "mixamorig:HeadTop_End",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 20
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -12.6684627532959,
"y": 2.27624535560608,
"z": -0.396418631076813
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0.07992084217915124,
"y": 0.35555240038341984,
"z": 0.03850968875854815,
"w": 0.9304365391786178
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 65.5876159667969,
"y": 65.5876617431641,
"z": 65.587646484375
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 7.027884698082062,
"y": 41.37325087834414,
"z": 7.382559841159056
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "758sU4GMNYEaDn4WLPV8VV"
},
{
"__type__": "cc.Node",
"_name": "soldier01",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 22
}
],
"_prefab": {
"__id__": 25
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.7071067811865476,
"y": 0,
"z": 0,
"w": 0.7071067811865476
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 2.53999996185303,
"y": 2.53999996185303,
"z": 2.53999996185303
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -90.00000000000003,
"y": 0,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.SkinnedMeshRenderer",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 21
},
"_enabled": true,
"__prefab": {
"__id__": 23
},
"_materials": [
{
"__uuid__": "5cf1ff47-b589-490a-bc78-a88f743b9798",
"__expectedType__": "cc.Material"
}
],
"_visFlags": 0,
"lightmapSettings": {
"__id__": 24
},
"_mesh": {
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@6868c",
"__expectedType__": "cc.Mesh"
},
"_shadowCastingMode": 0,
"_shadowReceivingMode": 1,
"_enableMorph": true,
"_skeleton": {
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@438fe",
"__expectedType__": "cc.Skeleton"
},
"_skinningRoot": {
"__id__": 1
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "01bZIFKkBZQ5BDP5OTFTyW"
},
{
"__type__": "cc.ModelLightmapSettings",
"texture": null,
"uvParam": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"_bakeable": false,
"_castShadow": false,
"_receiveShadow": false,
"_recieveShadow": false,
"_lightmapSize": 64
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "61gKhO2zhY0LoTCP8qRD7M"
},
{
"__type__": "cc.Node",
"_name": "Bip001(__autogen 4)",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 27
},
"_lpos": {
"__type__": "cc.Vec3",
"x": 9.61940002441406,
"y": 0.568015873432159,
"z": 1.51320099830627
},
"_lrot": {
"__type__": "cc.Quat",
"x": -0.5000003129242964,
"y": -0.49999968707550774,
"z": -0.49999968707550774,
"w": 0.5000003129242964
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 6.45159912109375,
"y": 6.45159912109375,
"z": 6.45160007476807
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": -90,
"y": -89.99992828301158,
"z": 0
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "ceW5W7cCxbcIDrr7BHLBqe"
},
{
"__type__": "cc.Node",
"_name": "mixamorig:HeadTop_End(__autogen 5)",
"_objFlags": 0,
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [],
"_prefab": {
"__id__": 29
},
"_lpos": {
"__type__": "cc.Vec3",
"x": -0.0903754904866219,
"y": 2.27624559402466,
"z": -1.47334516048431
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0.07992084217915124,
"y": 0.35555240038341984,
"z": 0.03850968875854815,
"w": 0.9304365391786178
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 65.5876159667969,
"y": 65.5876617431641,
"z": 65.587646484375
},
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 7.027884698082062,
"y": 41.37325087834414,
"z": 7.382559841159056
},
"_id": ""
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "9evQBDrbhSio1m7RjvuLjN"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "30u7fnOj9WDp8SK/8khE/U"
},
{
"__type__": "cc.SkeletalAnimation",
"_name": "",
"_objFlags": 0,
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 32
},
"playOnLoad": false,
"_clips": [
{
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@1f586",
"__expectedType__": "cc.AnimationClip"
},
{
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@cf5ee",
"__expectedType__": "cc.AnimationClip"
},
{
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@989ed",
"__expectedType__": "cc.AnimationClip"
},
{
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@ee525",
"__expectedType__": "cc.AnimationClip"
},
{
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@5b2e9",
"__expectedType__": "cc.AnimationClip"
}
],
"_defaultClip": {
"__uuid__": "57d4c4ef-3199-4596-bf79-7c065964ca9c@1f586",
"__expectedType__": "cc.AnimationClip"
},
"_useBakedAnimation": false,
"_sockets": [],
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "71bamidCpRLIBqyoKSaXUX"
},
{
"__type__": "720b7a4EnZJjYpzLWJrmvT/",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 1
},
"_enabled": true,
"__prefab": {
"__id__": 34
},
"_id": ""
},
{
"__type__": "cc.CompPrefabInfo",
"fileId": "fczHMULW9AAIJRjvl2FJiZ"
},
{
"__type__": "cc.PrefabInfo",
"root": {
"__id__": 1
},
"asset": {
"__id__": 0
},
"fileId": "71qfN53ihYmaSuBoR11P0t"
}
]

View File

@ -0,0 +1,13 @@
{
"ver": "1.1.32",
"importer": "prefab",
"imported": true,
"uuid": "28c95a70-5b80-4b72-8f7b-93e2cefacc98",
"files": [
".json"
],
"subMetas": {},
"userData": {
"syncNodeName": "Player"
}
}

View File

@ -0,0 +1,22 @@
import { Component, _decorator } from 'cc';
import { PlayerState } from '../../scripts/shared/game/state/PlayerState';
const { ccclass, property } = _decorator;
@ccclass('Player')
export class Player extends Component {
playerId!: number;
isSelf = false;
init(state: PlayerState) {
this.playerId = state.id;
this.setPos(state.pos);
}
// 把 GameSystem 空间映射到游戏空间
setPos(pos: { x: number, y: number }) {
this.node.setPosition(pos.y, 0, pos.x);
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "720b76b8-1276-498d-8a73-2d626b9af4ff",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -0,0 +1,115 @@
import { Component, instantiate, Node, Prefab, Vec2, _decorator } from 'cc';
import { WsClient } from 'tsrpc-browser';
import { Joystick } from '../../prefabs/Joystick/Joystick';
import { Player } from '../../prefabs/Player/Player';
import { FollowCamera } from '../../scripts/components/FollowCamera';
import { GameManager } from '../../scripts/models/GameManager';
import { gameConfig } from '../../scripts/shared/game/gameConfig';
import { serviceProto, ServiceType } from '../../scripts/shared/protocols/serviceProto';
const { ccclass, property } = _decorator;
/**
* Predefined variables
* Name = GameScene
* DateTime = Thu Dec 02 2021 18:43:36 GMT+0800 ()
* Author = k8w
* FileBasename = GameScene.ts
* FileBasenameNoExtension = GameScene
* URL = db://assets/scenes/GameScene/GameScene.ts
* ManualUrl = https://docs.cocos.com/creator/3.3/manual/zh/
*
*/
@ccclass('GameScene')
export class GameScene extends Component {
@property(Joystick)
joyStick!: Joystick;
@property(Prefab)
prefabPlayer!: Prefab;
@property(Node)
players!: Node;
@property(FollowCamera)
camera: FollowCamera = null as any;
client!: WsClient<ServiceType>;
gameManager!: GameManager;
private _playerInstances: { [playerId: number]: Player } = {};
private _selfSpeed?: Vec2 = new Vec2(0, 0);
onLoad() {
(window as any).game = this;
this.joyStick.options = {
onOperate: v => {
if (!this._selfSpeed) {
this._selfSpeed = new Vec2;
}
this._selfSpeed.set(v.x, v.y);
},
onOperateEnd: () => {
this._selfSpeed = undefined;
}
}
this.client = new WsClient(serviceProto, {
server: 'ws://127.0.0.1:3000',
logger: console
});
this.client.flows.postDisconnectFlow.push(v => {
location.reload()
return v;
})
this.gameManager = new GameManager(this.client);
this.gameManager.join();
}
update(dt: number) {
// Send Inputs
if (this._selfSpeed && this._selfSpeed.lengthSqr()) {
this._selfSpeed.normalize().multiplyScalar(gameConfig.moveSpeed);
this.gameManager.sendClientInput({
type: 'PlayerMove',
speed: {
x: this._selfSpeed.x,
y: this._selfSpeed.y
},
dt: dt
})
}
let gameState = this.gameManager.state;
// console.log('update', gameState.players.length)
// Update pos
for (let playerState of gameState.players) {
let player: Player = this._playerInstances[playerState.id];
if (!player) {
let node = instantiate(this.prefabPlayer);
this.players.addChild(node);
player = node.getComponent(Player)!;
player.init(playerState);
if (playerState.id === this.gameManager.selfPlayerId) {
this.camera.focusTarget = node;
}
this._playerInstances[playerState.id] = player;
}
else {
// console.log('setPos', playerState.id, playerState.pos.x, playerState.pos.y)
player.setPos(playerState.pos);
}
}
// Clear left players
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "d472870e-cf19-4a1c-b32e-e57838f14f14",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -1,101 +0,0 @@
import { WsClient } from 'tsrpc-browser';
import { MsgInput } from './shared/protocols/clientMsgs/MsgInput';
import { MsgFrame } from './shared/protocols/serverMsgs/MsgFrame';
import { ServiceType } from "./shared/protocols/serviceProto";
import { applyPlayerInput, PlayerInput, PlayerState } from "./shared/states/Player";
import { RoomState } from './shared/states/RoomState';
/**
* - -
*/
export class Room {
state: RoomState = {
players: []
}
client: WsClient<ServiceType>;
self?: {
uid: number,
// 最后一次权威状态
lastServerState: PlayerState
}
private _lastSendInputSN = 0;
private _sendingMsgs: MsgInput[] = [];
constructor(client: WsClient<ServiceType>) {
this.client = client;
this.client.listenMsg('serverMsgs/Frame', msg => {
this.applyServerFrame(msg);
});
this.client.listenMsg('serverMsgs/Join', msg => {
this.state.players.push(msg.player);
});
this.client.listenMsg('serverMsgs/Leave', msg => {
this.state.players.removeOne(v => v.uid === msg.uid);
});
}
init(roomState: RoomState) {
}
/** 同步服务端的权威消息 */
applyServerFrame(msg: MsgFrame) {
msg.inputs.forEach(({ uid, msgInput }) => {
let playerIndex = this.state.players.findIndex(v => v.uid === uid);
if (playerIndex === -1) {
return;
}
let newPlayer: PlayerState = this.state.players[playerIndex];
// 自己:和解
if (uid === this.self?.uid) {
this._sendingMsgs.remove(v => v.sn <= msgInput.sn);
this.state.players[playerIndex] = this.self.lastServerState;
// 预测
this._sendingMsgs.forEach(v => {
this.state.players[playerIndex] = applyPlayerInput(this.state.players[playerIndex], v);
})
}
// 其它人:直接同步
else {
this.state.players[playerIndex] = applyPlayerInput(this.state.players[playerIndex], msgInput);
}
});
}
/** 发送客户端输入,并执行本地预测 */
sendInput(input: PlayerInput) {
if (!this.self) {
return;
}
let msg: MsgInput = {
...input,
sn: ++this._lastSendInputSN
}
this._sendingMsgs.push(msg);
this.client.sendMsg('clientMsgs/Input', msg);
}
async joinRoom() {
let ret = await this.client.callApi('JoinRoom', {
nickname: 'xxx',
skinId: 1
});
if (!ret.isSucc) {
alert(ret.err.message);
return;
}
this.self = {
uid: ret.res.uid,
lastServerState: ret.res.roomState.players.find(v => v.uid === ret.res!.uid)!
}
this.state = ret.res.roomState;
}
}

View File

@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "bb0fd50f-b641-4824-a798-1f06417e3e88",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@ -0,0 +1,94 @@
import { _decorator, Component, Node, Vec3, Quat, Tween, tween } from "cc";
import { MathUtil } from '../models/MathUtil';
const { ccclass, property } = _decorator;
const v3_1 = new Vec3;
/**
*
*/
@ccclass
export class FollowCamera extends Component {
@property(Node)
focusTarget: Node = null as any;
@property
speedFactor: number = 0.05;
@property
private _distance: number = 15.0;
@property
public get distance(): number {
return this._distance;
}
public set distance(v: number) {
this._distance = v;
this._updateTargetWorldPos();
}
// Limit range
enableLimit: boolean = true;
minX = -Infinity;
maxX = Infinity;
minZ = -Infinity;
maxZ = Infinity;
protected _tweenFollow?: Tween;
protected _targetWorldPos = new Vec3;
protected _lastTargetPos = new Vec3;
start() {
this._lastTargetPos.set(this.focusTarget.worldPosition);
this._updateTargetWorldPos();
this.node.setWorldPosition(this._targetWorldPos);
}
protected _updateTargetWorldPos() {
this._targetWorldPos.set(this.focusTarget.worldPosition);
if (this.enableLimit) {
this._targetWorldPos.x = MathUtil.limit(this._targetWorldPos.x, this.minX, this.maxX);
this._targetWorldPos.z = MathUtil.limit(this._targetWorldPos.z, this.minZ, this.maxZ);
}
this._targetWorldPos.add(this.node.forward.multiplyScalar(this.distance * -1));
}
update() {
if (!this._lastTargetPos.equals(this.focusTarget.worldPosition)) {
this._lastTargetPos.set(this.focusTarget.worldPosition);
this._updateTargetWorldPos();
}
// 向TargetWorldPos平滑逼近
// Limit Range
v3_1.set(this._targetWorldPos);
let diff = v3_1.subtract(this.node.worldPosition);
if (diff.lengthSqr() > 0.01) {
this.node.worldPosition.add(diff.multiplyScalar(this.speedFactor));
this.node.setWorldPosition(this.node.worldPosition);
}
}
resetPos() {
this._updateTargetWorldPos();
this.node.setWorldPosition(this._targetWorldPos);
}
@property
get preview() {
return false;
}
set preview(v: boolean) {
this.resetPos();
}
@property
get autoSetDistance() {
return false;
}
set autuSetDistance(v: boolean) {
this.distance = this.node.worldPosition.clone().subtract(this.focusTarget.worldPosition).length();
}
}

View File

@ -0,0 +1,13 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "db75b6cd-267c-4760-8771-a3adc24919ef",
"files": [],
"subMetas": {},
"userData": {
"importAsPlugin": false,
"moduleId": "project:///assets/scripts/components/FollowCamera.js",
"simulateGlobals": []
}
}

View File

@ -0,0 +1,12 @@
{
"ver": "1.1.0",
"importer": "directory",
"imported": true,
"uuid": "7d1b93ce-3f28-40b7-9ad5-52f9fd2cae80",
"files": [],
"subMetas": {},
"userData": {
"compressionType": {},
"isRemoteBundle": {}
}
}

View File

@ -0,0 +1,97 @@
import { WsClient } from "tsrpc-browser";
import { GameSystem, GameSystemState } from "../shared/game/GameSystem";
import { ClientInput, MsgClientInput } from "../shared/protocols/client/MsgClientInput";
import { MsgFrame } from "../shared/protocols/server/MsgFrame";
import { ServiceType } from "../shared/protocols/serviceProto";
export class GameManager {
client: WsClient<ServiceType>;
gameSystem = new GameSystem();
lastServerState: GameSystemState = this.gameSystem.state;
selfPlayerId: number = -1;
lastSN = 0;
get state() {
return this.gameSystem.state;
}
constructor(client: WsClient<ServiceType>) {
this.client = client;
client.listenMsg('server/Frame', msg => { this._onServerSync(msg) });
(window as any).gm = this;
}
async join(): Promise<void> {
if (!this.client.isConnected) {
let resConnect = await this.client.connect();
if (!resConnect.isSucc) {
if (confirm('连接到服务器失败,是否重试')) {
return this.join();
}
else {
return;
}
}
}
let ret = await this.client.callApi('Join', {});
if (!ret.isSucc) {
if (confirm(`加入房间失败\n${ret.err.message}\n是否重试 ?`)) {
return this.join();
}
else {
return;
}
}
this.gameSystem.reset(ret.res.gameState);
this.lastServerState = Object.merge(ret.res.gameState);
this.selfPlayerId = ret.res.playerId;
}
private _onServerSync(frame: MsgFrame) {
// 同步权威状态
this.gameSystem.reset(this.lastServerState);
for (let input of frame.inputs) {
this.gameSystem.applyInput(input);
}
this.lastServerState = Object.merge({}, this.gameSystem.state);
// 和解
let lastSn = frame.lastSn ?? -1;
this.pendingInputMsgs.remove(v => v.sn <= lastSn);
this.pendingInputMsgs.forEach(m => {
m.inputs.forEach(v => {
this.gameSystem.applyInput({
...v,
playerId: this.selfPlayerId
});
})
})
}
pendingInputMsgs: MsgClientInput[] = [];
sendClientInput(input: ClientInput) {
if (!this.selfPlayerId) {
return;
}
let msg: MsgClientInput = {
sn: ++this.lastSN,
inputs: [input]
}
this.pendingInputMsgs.push(msg);
this.client.sendMsg('client/ClientInput', msg);
// 预测
this.gameSystem.applyInput({
...input,
playerId: this.selfPlayerId
});
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "977377fa-caaf-4e5e-8dac-0b429df3bdf6",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -0,0 +1,7 @@
export class MathUtil {
static limit(src: number, min: number, max: number) {
return Math.min(max, Math.max(min, src));
}
}

View File

@ -0,0 +1,9 @@
{
"ver": "4.0.22",
"importer": "typescript",
"imported": true,
"uuid": "6fde67c5-0d09-4389-8c51-9b57bce87ee1",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@ -5,5 +5,11 @@
"name": "canvas_19",
"value": 524288
}
]
],
"general": {
"designResolution": {
"width": 750,
"height": 1334
}
}
}

View File

@ -2,5 +2,7 @@
/* Base configuration. Do not edit this field. */
"extends": "./temp/tsconfig.cocos.json",
/* Add your custom configuration here. */
"compilerOptions": {}
"compilerOptions": {
"skipLibCheck": true
}
}