seperated client and server state, input mapper

This commit is contained in:
Reid 2023-07-20 14:48:17 -07:00
parent 44f63f8deb
commit d5b77a9044
10 changed files with 104 additions and 16 deletions

View file

@ -37,6 +37,7 @@
"@rbxts/plasma": "^0.4.1-ts.1", "@rbxts/plasma": "^0.4.1-ts.1",
"@rbxts/rewire": "^0.3.0", "@rbxts/rewire": "^0.3.0",
"@rbxts/services": "^1.5.1", "@rbxts/services": "^1.5.1",
"@rbxts/testez": "^0.4.2-ts.0" "@rbxts/testez": "^0.4.2-ts.0",
"@rbxts/variant": "^1.0.2"
} }
} }

View file

@ -8,7 +8,7 @@ An in-dev game that I plan to make a shooter game out of.
— reidlab — reidlab
# Hacks # Hacks
* I get a strange error about private identifiers in [`./src/ReplicatedStorage/ecs/state.ts:7`](./src/ReplicatedStorage/ecs/state.ts) * I get a strange error about private identifiers in [`./src/ReplicatedStorage/ecs/state.ts`](./src/ReplicatedStorage/ecs/state.ts)
* I decided to omit the "TS" folder from [`./default.project.json:40`](./default.project.json) due to the script override not working in Health.server.ts in StarterCharacterScripts. * I decided to omit the "TS" folder from [`./default.project.json:40`](./default.project.json) due to the script override not working in Health.server.ts in StarterCharacterScripts.
# Todo # Todo

View file

@ -6,7 +6,7 @@ import { Host } from "ReplicatedStorage/hosts"
import { tags } from "./boundTags" import { tags } from "./boundTags"
import { Model } from "./components" import { Model } from "./components"
import { start as startReplication, stop as stopReplication } from "./replication" import { start as startReplication, stop as stopReplication } from "./replication"
import { State } from "./state" import { clientState, serverState } from "./state"
import { start as startSystems, stop as stopSystems } from "./systems" import { start as startSystems, stop as stopSystems } from "./systems"
import { start as startTags, stop as stopTags } from "./tags" import { start as startTags, stop as stopTags } from "./tags"
@ -22,10 +22,9 @@ let connections:
} }
| undefined | undefined
export function start(host: Host): [World, State] { export function start<S extends object>(host: Host, state: S): [World, S] {
if (connections) throw "ECS already running." if (connections) throw "ECS already running."
const state: State = new State()
const world = new World() const world = new World()
const debug = new Debugger(Plasma) const debug = new Debugger(Plasma)
debug.authorize = authorize debug.authorize = authorize
@ -46,11 +45,15 @@ export function start(host: Host): [World, State] {
}) })
if (host === Host.All || host === Host.Server) { if (host === Host.All || host === Host.Server) {
const _ServerState = state as serverState
startTags(world, tags) startTags(world, tags)
} }
if (host === Host.All || host === Host.Client) { if (host === Host.All || host === Host.Client) {
startReplication(world, state) const ClientState = state as clientState
startReplication(world, ClientState)
const serverDebugger = ReplicatedStorage.FindFirstChild("MatterDebugger") const serverDebugger = ReplicatedStorage.FindFirstChild("MatterDebugger")
if (serverDebugger && serverDebugger.IsA("ScreenGui")) { if (serverDebugger && serverDebugger.IsA("ScreenGui")) {
@ -65,7 +68,7 @@ export function start(host: Host): [World, State] {
UserInputService.InputBegan.Connect((input) => { UserInputService.InputBegan.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.F4 && authorize(Players.LocalPlayer)) { if (input.KeyCode === Enum.KeyCode.F4 && authorize(Players.LocalPlayer)) {
debug.toggle() debug.toggle()
state.debugEnabled = debug.enabled ClientState.debugEnabled = debug.enabled
} }
}) })
} }

View file

@ -1,7 +1,7 @@
import { AnyEntity, World } from "@rbxts/matter" import { AnyEntity, World } from "@rbxts/matter"
import { waitForEvent } from "ReplicatedStorage/remotes" import { waitForEvent } from "ReplicatedStorage/remotes"
import * as Components from "./components" import * as Components from "./components"
import { State } from "./state" import { clientState } from "./state"
type ComponentNames = keyof typeof Components type ComponentNames = keyof typeof Components
type ComponentConstructors = (typeof Components)[ComponentNames] type ComponentConstructors = (typeof Components)[ComponentNames]
@ -16,13 +16,13 @@ let connection: RBXScriptConnection | undefined
* Starts the replication receiver. * Starts the replication receiver.
* *
* @param world - The world to replicate components in * @param world - The world to replicate components in
* @param state - The global state for the ECS * @param ClientState - The client state for the ECS
*/ */
export function start(world: World, state: State): void { export function start(world: World, ClientState: clientState): void {
if (connection) return if (connection) return
function debugPrint(message: string, args: () => (string | number)[]): void { function debugPrint(message: string, args: () => (string | number)[]): void {
if (state.debugEnabled) { if (ClientState.debugEnabled) {
print("ECS Replication>", string.format(message, ...args())) print("ECS Replication>", string.format(message, ...args()))
} }
} }

View file

@ -1,8 +1,21 @@
/* eslint-disable roblox-ts/no-private-identifier */
import { InputKind } from "ReplicatedStorage/inputKind"
/** /**
* The global ECS state. * The client ECS state.
*/ */
export class State { export class clientState {
[index: string]: unknown, [index: string]: unknown,
// eslint-disable-next-line roblox-ts/no-private-identifier
debugEnabled = false debugEnabled = false
isJumping = false
isRunning = false
lastProcessedCommand?: InputKind
}
/**
* The server ECS state.
*/
export class serverState {
[index: string]: unknown
} }

View file

@ -0,0 +1,44 @@
import { useDeltaTime, useEvent, useThrottle, World } from "@rbxts/matter"
import { UserInputService } from "@rbxts/services"
import { clientState } from "ReplicatedStorage/ecs/state"
import { InputKind } from "ReplicatedStorage/inputKind"
let holdDuration = 0
function inputMapper(_: World, client: clientState): void {
for (const [, input, gpe] of useEvent(UserInputService, "InputBegan")) {
if (gpe) return undefined
if (input.KeyCode !== Enum.KeyCode.Unknown) {
client.lastProcessedCommand = InputKind.KeyDown(input.KeyCode)
} else if (input.UserInputType === Enum.UserInputType.MouseButton1) {
if (useThrottle(0.5)) {
client.lastProcessedCommand = InputKind.PointerClick
return undefined
}
client.lastProcessedCommand = InputKind.DoubleClick
}
return undefined
}
for (const [, input, gpe] of useEvent(UserInputService, "InputEnded")) {
if (gpe) return undefined
if (input.KeyCode !== Enum.KeyCode.Unknown) {
client.lastProcessedCommand = InputKind.KeyUp(input.KeyCode)
} else if (input.UserInputType === Enum.UserInputType.MouseButton1) {
client.lastProcessedCommand = InputKind.HoldRelease
}
return undefined
}
if (UserInputService.IsMouseButtonPressed(Enum.UserInputType.MouseButton1)) {
holdDuration += useDeltaTime()
client.lastProcessedCommand = InputKind.Hold(holdDuration)
return
}
holdDuration = 0
client.lastProcessedCommand = undefined
return
}
export = inputMapper

View file

@ -0,0 +1,16 @@
import variantModule, { TypeNames, VariantOf } from "@rbxts/variant"
export const InputKind = variantModule({
// Sub-messages
KeyDown: (key: Enum.KeyCode) => ({ key }),
KeyUp: (key: Enum.KeyCode) => ({ key }),
Hold: (duration: number) => ({ duration }),
// Messages
HoldRelease: {},
DoubleClick: {},
PointerMove: {},
PointerClick: {}
})
export type InputKind<T extends TypeNames<typeof InputKind> = undefined> = VariantOf<typeof InputKind, T>

View file

@ -1,13 +1,16 @@
import { start } from "ReplicatedStorage/ecs" import { start } from "ReplicatedStorage/ecs"
import { serverState } from "ReplicatedStorage/ecs/state"
import { Host } from "ReplicatedStorage/hosts" import { Host } from "ReplicatedStorage/hosts"
import { setEnvironment } from "ReplicatedStorage/idAttribute" import { setEnvironment } from "ReplicatedStorage/idAttribute"
import { getEvent } from "ReplicatedStorage/remotes" import { getEvent } from "ReplicatedStorage/remotes"
const HOST = Host.Server const HOST = Host.Server
const ServerState = new serverState()
// We only do this here at the moment to create a dummy event for replication. // We only do this here at the moment to create a dummy event for replication.
// In the future this will be created by the replication system. // In the future this will be created by the replication system.
getEvent("EcsReplication") getEvent("EcsReplication")
setEnvironment(HOST) setEnvironment(HOST)
start(HOST) start(HOST, ServerState)

View file

@ -1,8 +1,11 @@
import { start } from "ReplicatedStorage/ecs" import { start } from "ReplicatedStorage/ecs"
import { clientState } from "ReplicatedStorage/ecs/state"
import { Host } from "ReplicatedStorage/hosts" import { Host } from "ReplicatedStorage/hosts"
import { setEnvironment } from "ReplicatedStorage/idAttribute" import { setEnvironment } from "ReplicatedStorage/idAttribute"
const HOST = Host.Client const HOST = Host.Client
const ClientState = new clientState()
setEnvironment(HOST) setEnvironment(HOST)
start(HOST) start(HOST, ClientState)

View file

@ -121,6 +121,11 @@
resolved "https://registry.yarnpkg.com/@rbxts/validate-tree/-/validate-tree-2.0.2.tgz#2a1807eaa391a482822207177ff5dcd4b8f811b2" resolved "https://registry.yarnpkg.com/@rbxts/validate-tree/-/validate-tree-2.0.2.tgz#2a1807eaa391a482822207177ff5dcd4b8f811b2"
integrity sha512-OA0E9ZjEeOpPa3XeV5/oC51aro2QFcL5dItufbagYZW8TOsX8C+FDXLx+A/ulMwcF42WcFNcQybxzcWSS/QSrA== integrity sha512-OA0E9ZjEeOpPa3XeV5/oC51aro2QFcL5dItufbagYZW8TOsX8C+FDXLx+A/ulMwcF42WcFNcQybxzcWSS/QSrA==
"@rbxts/variant@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@rbxts/variant/-/variant-1.0.2.tgz#91863201d3eb8f7759d7c5719f4af54ae969b8d4"
integrity sha512-cLfjIheMFmII0YMlLbChjD+jj3jcmLhcli2xXdGXCE1WIt6txOYqehaLZcUxyK6D6tFL727txhaseKu+3Hiazg==
"@types/json-schema@^7.0.9": "@types/json-schema@^7.0.9":
version "7.0.12" version "7.0.12"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"