diff options
| author | Mike Vink <mike1994vink@gmail.com> | 2021-07-20 00:02:30 +0200 |
|---|---|---|
| committer | Mike Vink <mike1994vink@gmail.com> | 2021-07-20 00:02:30 +0200 |
| commit | 2384eb32984a17ae00bd894d81e5f7b4eb40bffa (patch) | |
| tree | f70914732be0185d7da9d149536ae506a098d982 /client | |
| parent | a159975d09c9160b89a733f2434b406d8f99dbb4 (diff) | |
feat(client): grid-based physics local
Diffstat (limited to 'client')
| -rw-r--r-- | client/dist/assets/tilemaps/akkamon-demo-tilemap.json | 6 | ||||
| -rw-r--r-- | client/src/Direction.ts | 7 | ||||
| -rw-r--r-- | client/src/GridControls.ts | 24 | ||||
| -rw-r--r-- | client/src/GridPhysics.ts | 148 | ||||
| -rw-r--r-- | client/src/game.ts | 203 | ||||
| -rw-r--r-- | client/src/sprite.ts | 56 |
6 files changed, 279 insertions, 165 deletions
diff --git a/client/dist/assets/tilemaps/akkamon-demo-tilemap.json b/client/dist/assets/tilemaps/akkamon-demo-tilemap.json index 981bdc1..90b3091 100644 --- a/client/dist/assets/tilemaps/akkamon-demo-tilemap.json +++ b/client/dist/assets/tilemaps/akkamon-demo-tilemap.json @@ -3,7 +3,7 @@ "infinite":false, "layers":[ { - "data":[126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126], + "data":[126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126], "height":40, "id":1, "name":"Below Player", @@ -52,8 +52,8 @@ "type":"", "visible":true, "width":0, - "x":576, - "y":852 + "x":592.25, + "y":853 }], "opacity":1, "type":"objectgroup", diff --git a/client/src/Direction.ts b/client/src/Direction.ts new file mode 100644 index 0000000..87484ad --- /dev/null +++ b/client/src/Direction.ts @@ -0,0 +1,7 @@ +export enum Direction { + NONE = "none", + LEFT = "left", + UP = "up", + RIGHT = "right", + DOWN = "down", +} diff --git a/client/src/GridControls.ts b/client/src/GridControls.ts new file mode 100644 index 0000000..9986b1f --- /dev/null +++ b/client/src/GridControls.ts @@ -0,0 +1,24 @@ +import { Direction } from './Direction'; +import type { GridPhysics } from './GridPhysics'; + +export class GridControls { + constructor( + private input: Phaser.Input.InputPlugin, + private gridPhysics: GridPhysics + ) { } + + update() { + const cursors = this.input.keyboard.createCursorKeys(); + if (cursors.left.isDown) { + this.gridPhysics.movePlayerSprite(Direction.LEFT); + } else if (cursors.right.isDown) { + this.gridPhysics.movePlayerSprite(Direction.RIGHT); + } else if (cursors.up.isDown) { + this.gridPhysics.movePlayerSprite(Direction.UP); + } else if (cursors.down.isDown) { + this.gridPhysics.movePlayerSprite(Direction.DOWN); + } + } + + +} diff --git a/client/src/GridPhysics.ts b/client/src/GridPhysics.ts new file mode 100644 index 0000000..5fca10a --- /dev/null +++ b/client/src/GridPhysics.ts @@ -0,0 +1,148 @@ +import Phaser from 'phaser'; +import type PlayerSprite from './sprite'; +import { Direction } from './Direction'; +import AkkamonStartScene from './game'; + +export class GridPhysics { + + private movementDirectionVectors: { + [key in Direction]?: Phaser.Math.Vector2; + } = { + [Direction.UP]: Phaser.Math.Vector2.UP, + [Direction.DOWN]: Phaser.Math.Vector2.DOWN, + [Direction.LEFT]: Phaser.Math.Vector2.LEFT, + [Direction.RIGHT]: Phaser.Math.Vector2.RIGHT, + } + + private movementDirection: Direction = Direction.NONE; + private readonly speedPixelsPerSecond: number = AkkamonStartScene.TILE_SIZE * 4; + + private tileSizePixelsWalked: number = 0; + + private lastMovementIntent = Direction.NONE; + + constructor( + private playerSprite: PlayerSprite, + private tileMap: Phaser.Tilemaps.Tilemap + ) { } + + movePlayerSprite(direction: Direction): void { + this.lastMovementIntent = direction; + + if (this.isMoving()) return; + + if (this.isBlockingDirection(direction)) { + this.playerSprite.stopAnimation(direction); + } else { + this.startMoving(direction); + } + } + + private isMoving(): boolean { + return this.movementDirection != Direction.NONE; + } + + private startMoving(direction: Direction): void { + this.playerSprite.startAnimation(direction); + this.movementDirection = direction; + this.updatePlayerSpriteTilePosition(); + } + + update(delta: number): void { + if (this.isMoving()) { + this.updatePlayerSpritePosition(delta); + } + this.lastMovementIntent = Direction.NONE; + } + + private updatePlayerSpritePosition(delta: number) { + const pixelsToWalkThisUpdate = this.getPixelsToWalkThisUpdate(delta); + + if (!this.willCrossTileBorderThisUpdate(pixelsToWalkThisUpdate)) { + this.spriteMovement(pixelsToWalkThisUpdate); + } else if (this.shouldContinueMoving()) { + this.spriteMovement(pixelsToWalkThisUpdate); + this.updatePlayerSpriteTilePosition(); + } else { + this.spriteMovement(AkkamonStartScene.TILE_SIZE - this.tileSizePixelsWalked); + this.stopMoving(); + } + } + + private updatePlayerSpriteTilePosition() { + this.playerSprite.setTilePos( + this.playerSprite + .getTilePos() + .add(this.movementDirectionVectors[this.movementDirection]!) + ); + } + + private shouldContinueMoving(): boolean { + return ( + this.movementDirection == this.lastMovementIntent && + !this.isBlockingDirection(this.lastMovementIntent) + + ); + } + + private spriteMovement(pixelsToMove: number) { + + this.tileSizePixelsWalked += pixelsToMove; + this.tileSizePixelsWalked %= AkkamonStartScene.TILE_SIZE; + + + const directionVec = this.movementDirectionVectors[ + this.movementDirection + ]!.clone(); + + const movementDistance = directionVec.multiply( + new Phaser.Math.Vector2(pixelsToMove) + ); + + const newPlayerPos = this.playerSprite.getPosition().add(movementDistance); + this.playerSprite.newPosition(newPlayerPos); + } + + private willCrossTileBorderThisUpdate( + pixelsToWalkThisUpdate: number + ): boolean { + return ( + this.tileSizePixelsWalked + pixelsToWalkThisUpdate >= AkkamonStartScene.TILE_SIZE + ); + } + + private getPixelsToWalkThisUpdate(delta: number): number { + const deltaInSeconds = delta / 1000; + return this.speedPixelsPerSecond * deltaInSeconds; + } + + private stopMoving(): void { + this.playerSprite.stopAnimation(this.movementDirection); + this.movementDirection = Direction.NONE; + } + + private isBlockingDirection(direction: Direction): boolean { + return this.hasBlockingTile(this.tilePosInDirection(direction)); + } + + private tilePosInDirection(direction: Direction): Phaser.Math.Vector2 { + return this.playerSprite + .getTilePos() + .add(this.movementDirectionVectors[direction]!); + } + + private hasBlockingTile(pos: Phaser.Math.Vector2): boolean { + if (this.hasNoTile(pos)) return true; + return this.tileMap.layers.some((layer) => { + const tile = this.tileMap.getTileAt(pos.x, pos.y, false, layer.name); + return tile && tile.properties.collides; + }); + } + + private hasNoTile(pos: Phaser.Math.Vector2): boolean { + return !this.tileMap.layers.some((layer) => + this.tileMap.hasTileAt(pos.x, pos.y, layer.name)); + } + +} + diff --git a/client/src/game.ts b/client/src/game.ts index b9e19a0..6413aa0 100644 --- a/client/src/game.ts +++ b/client/src/game.ts @@ -3,6 +3,10 @@ import type Player from './player'; import Client from './client'; import GameState from './GameState'; import PlayerSprite from './sprite'; +import { GridControls } from './GridControls'; +import { GridPhysics } from './GridPhysics'; +import { Direction } from './Direction'; + type RemotePlayerStates = { [name: string]: Player @@ -10,10 +14,26 @@ type RemotePlayerStates = { export default class AkkamonStartScene extends Phaser.Scene { - currentPlayerSprite: PlayerSprite | undefined; + + static readonly TILE_SIZE = 32; + + private gridPhysics?: GridPhysics + private gridControls?: GridControls + + directionToAnimation: { + [key in Direction]: string + } = { + [Direction.UP]: "misa-back-walk", + [Direction.DOWN]: "misa-front-walk", + [Direction.LEFT]: "misa-left-walk", + [Direction.RIGHT]: "misa-right-walk", + [Direction.NONE]: "misa-front-walk" + } + remotePlayerSprites: {[name: string]: PlayerSprite} = {}; spawnPoint: Phaser.Types.Tilemaps.TiledObject | undefined; + constructor () { super('akkamonStartScene'); @@ -40,190 +60,77 @@ export default class AkkamonStartScene extends Phaser.Scene create () { const map = this.make.tilemap({ key: "map" }); - // Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in // Phaser's cache (i.e. the name you used in preload) const tileset = map.addTilesetImage("akkamon-demo-extruded", "tiles"); - // Parameters: layer name (or index) from Tiled, tileset, x, y const belowLayer = map.createLayer("Below Player", tileset, 0, 0); const worldLayer = map.createLayer("World", tileset, 0, 0); const aboveLayer = map.createLayer("Above Player", tileset, 0, 0); - - worldLayer.setCollisionByProperty({collides: true}); - // By default, everything gets depth sorted on the screen in the order we created things. Here, we // want the "Above Player" layer to sit on top of the player, so we explicitly give it a depth. // Higher depths will sit on top of lower depth objects. aboveLayer.setDepth(10); - const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point"); - this.spawnPoint = spawnPoint; + this.spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point"); + + //this.createPlayerAnimation(Direction.UP); + // Create a sprite with physics enabled via the physics system. The image used for the sprite has // a bit of whitespace, so I'm using setSize & setOffset to control the size of the player's body. - let player = new PlayerSprite({ scene: this, - x: spawnPoint.x!, - y: spawnPoint.y!, + tilePos: new Phaser.Math.Vector2( + Math.floor(this.spawnPoint.x! / AkkamonStartScene.TILE_SIZE), + Math.floor(this.spawnPoint.y! / AkkamonStartScene.TILE_SIZE), + ), texture: this.textures.get("atlas"), frame: "misa-front", player: GameState.getInstance().currentPlayer!, - moveControls: this.input.keyboard.createCursorKeys() }); - this.currentPlayerSprite = player; - - player - .setSize(30, 40) - .setOffset(0, 24); this.add.existing(player); - this.physics.add.existing(player); - // GameState.getInstance().currentPlayer!.setSprite(player); - - this.physics.add.collider(player, worldLayer); - console.log(player); - - // Create the player's walking animations from the texture atlas. These are stored in the global - // animation manager so any sprite can access them. - const anims = this.anims; - anims.create({ - key: "misa-left-walk", - frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }), - frameRate: 10, - repeat: -1 - }); - anims.create({ - key: "misa-right-walk", - frames: anims.generateFrameNames("atlas", { prefix: "misa-right-walk.", start: 0, end: 3, zeroPad: 3 }), - frameRate: 10, - repeat: -1 - }); - anims.create({ - key: "misa-front-walk", - frames: anims.generateFrameNames("atlas", { prefix: "misa-front-walk.", start: 0, end: 3, zeroPad: 3 }), - frameRate: 10, - repeat: -1 - }); - anims.create({ - key: "misa-back-walk", - frames: anims.generateFrameNames("atlas", { prefix: "misa-back-walk.", start: 0, end: 3, zeroPad: 3 }), - frameRate: 10, - repeat: -1 - }); + this.gridPhysics = new GridPhysics(player, map); + this.gridControls = new GridControls( + this.input, + this.gridPhysics + ); + this.createPlayerAnimation(Direction.LEFT, 0, 3); + this.createPlayerAnimation(Direction.RIGHT, 0, 3); + this.createPlayerAnimation(Direction.UP, 0, 3); + this.createPlayerAnimation(Direction.DOWN, 0, 3); // Phaser supports multiple cameras, but you can access the default camera like this: const camera = this.cameras.main; camera.startFollow(player); - camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels); - - // Debug graphics - this.input.keyboard.once("keydown_D", (event: Event) => { - // Turn on physics debugging to show player's hitbox - this.physics.world.createDebugGraphic(); - - // Create worldLayer collision graphic above the player, but below the help text - const graphics = this.add - .graphics() - .setAlpha(0.75) - .setDepth(20); - - worldLayer.renderDebug(graphics, { - tileColor: null, // Color of non-colliding tiles - collidingTileColor: new Phaser.Display.Color(243, 134, 48, 255), // Color of colliding tiles - faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Color of colliding face edges - }); - - }); - - // Constrain the camera so that it isn't allowed to move outside the width/height of tilemap + camera.roundPixels = true; camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels); } - update(time: Number, delta: Number) { - let playerSprite = this.currentPlayerSprite!; - - const speed = 175; - const prevVelocity = playerSprite.body.velocity.clone(); - - // Stop any previous movement from the last frame - playerSprite.body.setVelocity(0); - - this.moveSprite(playerSprite, speed, prevVelocity) - - // update player state - playerSprite.player.position = { - x: playerSprite.x, - y: playerSprite.y - } - - if (GameState.getInstance().remotePlayers !== undefined) { - this.renderRemotePlayers(GameState.getInstance().remotePlayers!); - } + update(time: number, delta: number) { + this.gridControls!.update(); + this.gridPhysics!.update(delta); } - renderRemotePlayers(remotePlayers: RemotePlayerStates) { - console.log(remotePlayers); - - for (let playerName in remotePlayers) { - if (playerName in this.remotePlayerSprites) { - this.remotePlayerSprites[playerName].renderUpdate(remotePlayers[playerName].position); - } else { - let remotePlayer = remotePlayers[playerName]; - let remotePlayerSprite = new PlayerSprite({ - scene: this, - x: remotePlayer.position.x, - y: remotePlayer.position.y, - texture: this.textures.get("atlas"), - player: remotePlayer, - }); - this.add.existing(remotePlayerSprite); - this.remotePlayerSprites[playerName] = remotePlayerSprite; - } - } - } + private createPlayerAnimation(direction: Direction, start: number, end: number) { + this.anims.create({ + key: direction, // "misa-left-walk", + frames: this.anims.generateFrameNames("atlas", { prefix: this.directionToAnimation[direction] + ".", start: start, end: end, zeroPad: 3 }), + frameRate: 10, + repeat: -1 + }); + +// anims.create({ +// key: "misa-left-walk", +// frames: anims.generateFrameNames("atlas", { prefix: "misa-left-walk.", start: 0, end: 3, zeroPad: 3 }), +// frameRate: 10, +// repeat: -1 - moveSprite(playerSprite: PlayerSprite, speed: number, prevVelocity: {x: number, y:number}) { - let moveControls = playerSprite.moveControls!; - // Horizontal movement - if (moveControls.left.isDown) { - playerSprite.body.setVelocityX(-speed); - } else if (moveControls.right.isDown) { - playerSprite.body.setVelocityX(speed); - } - - // Vertical movement - if (moveControls.up.isDown) { - playerSprite.body.setVelocityY(-speed); - } else if (moveControls.down.isDown) { - playerSprite.body.setVelocityY(speed); - } - - // Normalize and scale the velocity so that playerSprite can't move faster along a diagonal - playerSprite.body.velocity.normalize().scale(speed); - - // Update the animation last and give left/right animations precedence over up/down animations - if (moveControls.left.isDown) { - playerSprite.anims.play("misa-left-walk", true); - } else if (moveControls.right.isDown) { - playerSprite.anims.play("misa-right-walk", true); - } else if (moveControls.up.isDown) { - playerSprite.anims.play("misa-back-walk", true); - } else if (moveControls.down.isDown) { - playerSprite.anims.play("misa-front-walk", true); - } else { - playerSprite.anims.stop(); - // If we were moving, pick and idle frame to use - if (prevVelocity.x < 0) playerSprite.setTexture("atlas", "misa-left"); - else if (prevVelocity.x > 0) playerSprite.setTexture("atlas", "misa-right"); - else if (prevVelocity.y < 0) playerSprite.setTexture("atlas", "misa-back"); - else if (prevVelocity.y > 0) playerSprite.setTexture("atlas", "misa-front"); - } } } diff --git a/client/src/sprite.ts b/client/src/sprite.ts index a47d617..cf25ba0 100644 --- a/client/src/sprite.ts +++ b/client/src/sprite.ts @@ -1,38 +1,66 @@ import Phaser from 'phaser'; +import AkkamonStartScene from './game'; import type Player from './player'; +import type { Direction } from './Direction'; type PlayerSpriteConfig = { scene: Phaser.Scene, - x: number, - y: number, + tilePos: Phaser.Math.Vector2, texture: Phaser.Textures.Texture | string, frame?: string, player: Player, - moveControls?: Phaser.Types.Input.Keyboard.CursorKeys; } -interface AkkamonDynamicSprite extends Phaser.Types.Physics.Arcade.SpriteWithDynamicBody { +interface AkkamonPlayerSprite extends Phaser.GameObjects.Sprite { player: Player - moveControls: Phaser.Types.Input.Keyboard.CursorKeys | undefined } -export default class PlayerSprite extends Phaser.Physics.Arcade.Sprite implements AkkamonDynamicSprite { +export default class PlayerSprite extends Phaser.GameObjects.Sprite implements AkkamonPlayerSprite { - body: Phaser.Physics.Arcade.Body; player: Player; - moveControls: Phaser.Types.Input.Keyboard.CursorKeys | undefined; + tilePos: Phaser.Math.Vector2; constructor(config: PlayerSpriteConfig) { - super(config.scene, config.x, config.y, config.texture, config.frame!); + const offsetX = AkkamonStartScene.TILE_SIZE / 2; + const offsetY = AkkamonStartScene.TILE_SIZE; + + super(config.scene, + config.tilePos.x * AkkamonStartScene.TILE_SIZE + offsetX, + config.tilePos.y * AkkamonStartScene.TILE_SIZE + offsetY, + config.texture, + config.frame); - this.body = new Phaser.Physics.Arcade.Body(config.scene.physics.world, this); this.player = config.player; + this.tilePos = new Phaser.Math.Vector2(config.tilePos.x, config.tilePos.y); + + this.setOrigin(0.5, 1); + } + + getPosition(): Phaser.Math.Vector2 { + return this.getBottomCenter(); + } + + newPosition(position: Phaser.Math.Vector2): void { + super.setPosition(position.x, position.y); + } + + stopAnimation(direction: Direction) { + const animationManager = this.anims.animationManager; + const standingFrame = animationManager.get(direction).frames[1].frame.name; + + this.anims.stop(); + this.setFrame(standingFrame); + } + + startAnimation(direction: Direction) { + this.anims.play(direction); + } - if (config.moveControls) this.moveControls = config.moveControls; + getTilePos(): Phaser.Math.Vector2 { + return this.tilePos.clone(); } - renderUpdate({x, y}: Player["position"]) { - this.x = x; - this.y = y; + setTilePos(tilePosition: Phaser.Math.Vector2): void { + this.tilePos = tilePosition.clone(); } } |
