From 902c92d1376427094afaecbb7736ceacf3eaae22 Mon Sep 17 00:00:00 2001 From: Cosmo Myzrail Gorynych Date: Sun, 16 Feb 2020 22:21:45 +1200 Subject: [PATCH] :sparkles: ct.camera, UI and game coordinates, and nested rooms --- app/data/ct.libs/fittoscreen/CHANGELOGmd | 5 + app/data/ct.libs/fittoscreen/DOCS.md | 4 - app/data/ct.libs/fittoscreen/README.md | 4 +- app/data/ct.libs/fittoscreen/index.js | 43 +- .../{switch.js => beforeroomoncreate.js} | 0 app/data/ct.libs/fittoscreen/injects/start.js | 1 - app/data/ct.libs/fittoscreen/module.json | 6 +- app/data/ct.libs/mouse/CHANGELOG.md | 7 + app/data/ct.libs/mouse/DOCS.md | 14 +- app/data/ct.libs/mouse/index.js | 49 +- .../ct.libs/mouse/injects/afterroomdraw.js | 3 + app/data/ct.libs/mouse/module.json | 2 +- app/data/ct.libs/mouse/types.d.ts | 14 +- app/data/ct.libs/place/README.md | 10 +- app/data/ct.libs/place/injects/ondestroy.js | 3 - app/data/ct.libs/place/injects/roomonleave.js | 3 +- app/data/ct.libs/place/injects/start.js | 10 - app/data/ct.libs/touch/README.md | 12 +- app/data/ct.libs/touch/index.js | 113 +++-- .../ct.libs/touch/injects/afterroomdraw.js | 2 + app/data/ct.libs/touch/types.d.ts | 66 ++- app/data/ct.libs/tween/injects/roomonleave.js | 16 +- app/data/ct.libs/vkeys/DOCS.md | 14 +- app/data/ct.libs/vkeys/README.md | 29 +- app/data/ct.libs/vkeys/index.js | 34 +- app/data/ct.libs/vkeys/module.json | 2 +- app/data/ct.libs/vkeys/types.d.ts | 4 + app/data/ct.release/.eslintrc.json | 7 +- app/data/ct.release/camera.js | 322 ++++++++++++ app/data/ct.release/main.js | 230 +++++---- app/data/ct.release/rooms.js | 468 +++++++++++------- app/data/ct.release/types.js | 33 +- projects/cameraTest.ict | 244 +++++++++ .../if0f9ff12-35e9-485f-ad49-b87a493be343.png | Bin 0 -> 3185 bytes ...2-35e9-485f-ad49-b87a493be343.png_prev.png | Bin 0 -> 5131 bytes ...35e9-485f-ad49-b87a493be343.png_prev@2.png | Bin 0 -> 7983 bytes projects/cameraTest/img/rcf2550d1386f.png | Bin 0 -> 7715 bytes projects/cameraTest/img/splash.png | Bin 0 -> 7715 bytes src/examples/catformer.ict | 90 +++- src/node_requires/exporter.js | 6 + src/styl/3rdParty/monacoEdits.styl | 15 +- src/styl/tags/rooms/room-events-editor.styl | 4 +- src/typedefs/ct.js/Room.d.ts | 13 - 43 files changed, 1404 insertions(+), 498 deletions(-) create mode 100644 app/data/ct.libs/fittoscreen/CHANGELOGmd rename app/data/ct.libs/fittoscreen/injects/{switch.js => beforeroomoncreate.js} (100%) delete mode 100644 app/data/ct.libs/fittoscreen/injects/start.js create mode 100644 app/data/ct.release/camera.js create mode 100644 projects/cameraTest.ict create mode 100644 projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png create mode 100644 projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png_prev.png create mode 100644 projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png_prev@2.png create mode 100644 projects/cameraTest/img/rcf2550d1386f.png create mode 100644 projects/cameraTest/img/splash.png diff --git a/app/data/ct.libs/fittoscreen/CHANGELOGmd b/app/data/ct.libs/fittoscreen/CHANGELOGmd new file mode 100644 index 000000000..df1864afa --- /dev/null +++ b/app/data/ct.libs/fittoscreen/CHANGELOGmd @@ -0,0 +1,5 @@ +# v3.0.0 + +- Support for the new `ct.camera`. +- Removed `ct.fittoscreen.manageViewport`. +- Removed "Expand, manage viewport" mode, as "Expand" replaces it. \ No newline at end of file diff --git a/app/data/ct.libs/fittoscreen/DOCS.md b/app/data/ct.libs/fittoscreen/DOCS.md index 2fcb294f7..50858c1c9 100644 --- a/app/data/ct.libs/fittoscreen/DOCS.md +++ b/app/data/ct.libs/fittoscreen/DOCS.md @@ -2,10 +2,6 @@ Resizes the canvas immediately. -## `ct.fittoscreen.manageViewport();` - -Shifts the viewport so the previous central point stays in the same place. You usually don't need to call it manually. Works only if "Manage the view" option is enabled. - ## `ct.fittoscreen.toggleFullscreen();` Tries to toggle the fullscreen mode. Errors, if any, will be logged to console. Also, this won't work in the internal ct.js debugger. Instead, test it in your browser. diff --git a/app/data/ct.libs/fittoscreen/README.md b/app/data/ct.libs/fittoscreen/README.md index c3ec1f9ed..70d034475 100644 --- a/app/data/ct.libs/fittoscreen/README.md +++ b/app/data/ct.libs/fittoscreen/README.md @@ -1,3 +1,3 @@ -This module allows you to automagically fit your game to screen, either by resizing the whole drawing canvas or by simple scaling. +This module allows you to automagically fit your game to screen, either by resizing the whole drawing canvas or by simple scaling. See the settings for this module for different scaling methods. -It also gives you functions to enter a real fullscreen mode programmatically. \ No newline at end of file +It also gives you functions to enter a real fullscreen mode programmatically (see the "Reference" tab). \ No newline at end of file diff --git a/app/data/ct.libs/fittoscreen/index.js b/app/data/ct.libs/fittoscreen/index.js index 471efc31c..7862a3715 100644 --- a/app/data/ct.libs/fittoscreen/index.js +++ b/app/data/ct.libs/fittoscreen/index.js @@ -1,20 +1,13 @@ (function (ct) { var width, height; - var oldWidth, oldHeight; var canv = ct.pixiApp.view; - var manageViewport = function (room) { - room = room || ct.room; - room.x -= (width - oldWidth) / 2; - room.y -= (height - oldHeight) / 2; - }; var resize = function() { const {mode} = ct.fittoscreen; width = window.innerWidth; height = window.innerHeight; var kw = width / ct.roomWidth, - kh = height / ct.roomHeight, - minorWidth = kw > kh; + kh = height / ct.roomHeight; var k = Math.min(kw, kh); if (mode === 'fastScale') { canv.style.transform = 'scale(' + k + ')'; @@ -22,35 +15,18 @@ canv.style.left = (width - ct.width) / 2 + 'px'; canv.style.top = (height - ct.height) / 2 + 'px'; } else { - var {room} = ct; - if (!room) { - return; - } - oldWidth = ct.width; - oldHeight = ct.height; if (mode === 'expandViewport' || mode === 'expand') { - for (const bg of ct.types.list.BACKGROUND) { - bg.width = width; - bg.height = height; - } - ct.viewWidth = width; - ct.viewHeight = height; + ct.camera.width = width; + ct.camera.height = height; } if (mode !== 'scaleFit') { ct.pixiApp.renderer.resize(width, height); if (mode === 'scaleFill') { - if (minorWidth) { - ct.viewWidth = Math.ceil(width / k); - } else { - ct.viewHeight = Math.ceil(height / k); - } - for (const bg of ct.types.list.BACKGROUND) { - bg.width = ct.viewWidth; - bg.height = ct.viewHeight; - } + ct.camera.width = Math.ceil(width / k); + ct.camera.height = Math.ceil(height / k); } } else { - ct.pixiApp.renderer.resize(Math.floor(ct.viewWidth * k), Math.floor(ct.viewHeight * k)); + ct.pixiApp.renderer.resize(Math.floor(ct.camera.width * k), Math.floor(ct.camera.height * k)); canv.style.position = 'absolute'; canv.style.left = (width - ct.width) / 2 + 'px'; canv.style.top = (height - ct.height) / 2 + 'px'; @@ -59,9 +35,6 @@ ct.pixiApp.stage.scale.x = k; ct.pixiApp.stage.scale.y = k; } - if (mode === 'expandViewport') { - manageViewport(room); - } } }; var toggleFullscreen = function () { @@ -96,7 +69,6 @@ height = window.innerHeight; window.addEventListener('resize', resize); ct.fittoscreen = resize; - ct.fittoscreen.manageViewport = manageViewport; ct.fittoscreen.toggleFullscreen = queueFullscreen; var $mode = '/*%mode%*/'; Object.defineProperty(ct.fittoscreen, 'mode', { @@ -105,11 +77,8 @@ set(value) { if ($mode === 'fastScale' && value !== 'fastScale') { canv.style.transform = ''; - } else if (value === 'fastScale' || value === 'expand' || value === 'expandViewport') { - ct.pixiApp.stage.scale.x = ct.pixiApp.stage.scale.y = 1; } $mode = value; - ct.fittoscreen(); }, get() { return $mode; diff --git a/app/data/ct.libs/fittoscreen/injects/switch.js b/app/data/ct.libs/fittoscreen/injects/beforeroomoncreate.js similarity index 100% rename from app/data/ct.libs/fittoscreen/injects/switch.js rename to app/data/ct.libs/fittoscreen/injects/beforeroomoncreate.js diff --git a/app/data/ct.libs/fittoscreen/injects/start.js b/app/data/ct.libs/fittoscreen/injects/start.js deleted file mode 100644 index 50c5b7715..000000000 --- a/app/data/ct.libs/fittoscreen/injects/start.js +++ /dev/null @@ -1 +0,0 @@ -setTimeout(ct.fittoscreen, 0); diff --git a/app/data/ct.libs/fittoscreen/module.json b/app/data/ct.libs/fittoscreen/module.json index a6354b076..6bf9b36e5 100644 --- a/app/data/ct.libs/fittoscreen/module.json +++ b/app/data/ct.libs/fittoscreen/module.json @@ -1,7 +1,7 @@ { "main": { "name": "Fit to Screen", - "version": "2.0.0", + "version": "3.0.0", "authors": [{ "name": "Cosmo Myzrail Gorynych", "mail": "admin@nersta.ru" @@ -17,10 +17,6 @@ "value": "expand", "name": "Expand", "help": "Expands the viewport, remaining the scale of the copies untouched" - }, { - "value": "expandViewport", - "name": "Expand, manage viewport", - "help": "Additionally moves the viewport so that the central point always stays in the center of the drawing canvas." }, { "value": "scaleFit", "name": "Scaling with letterboxing", diff --git a/app/data/ct.libs/mouse/CHANGELOG.md b/app/data/ct.libs/mouse/CHANGELOG.md index ad75eeadd..b8de1d462 100644 --- a/app/data/ct.libs/mouse/CHANGELOG.md +++ b/app/data/ct.libs/mouse/CHANGELOG.md @@ -1,3 +1,10 @@ +v3.0.0 + +* Support the new ct.camera object +* Introduce `ct.mouse.xui`, `ct.mouse.yui`, as well as `ct.mouse.xuiprev` and `ct.mouse.yuiprev` +* Remove `ct.mouse.rx` and `ct.mouse.ry +* Fix broken `Wheel` input method + v2.0.0 * Support the new Actions system diff --git a/app/data/ct.libs/mouse/DOCS.md b/app/data/ct.libs/mouse/DOCS.md index b58cc4eed..1cc997b4d 100644 --- a/app/data/ct.libs/mouse/DOCS.md +++ b/app/data/ct.libs/mouse/DOCS.md @@ -1,6 +1,6 @@ ## `ct.mouse.x`, `ct.mouse.y` -Current cursor position at horisontal and vertical axes, in world coordinates. +Current cursor position at horisontal and vertical axes, in game coordinates. **Example: make a copy follow the cursor** @@ -9,11 +9,9 @@ this.x = ct.mouse.x; this.y = ct.mouse.y; ``` -# `ct.mouse.rx`, `ct.mouse.ry` +# `ct.mouse.xui`, `ct.mouse.yui` -A cursor position relative to the current view (or camera), but not relative to the room. - -`ct.mouse.rx` is the same as `ct.mouse.x - ct.room.x`. +A cursor position relative to the current view (UI coordinates), but not relative to the room. ## `ct.mouse.pressed` @@ -43,7 +41,11 @@ Can be either `true` or `false`. Determines whether there is a cursor inside the ## `ct.mouse.hovers(copy)` -Returns `true` if the mouse hovers over a given `copy`. This does **not** take scaling and rotation into account, as well as polygonal shapes (as they are hollow). +Returns `true` if the mouse hovers over a given `copy` in game coordinates. This does **not** take scaling and rotation into account, as well as polygonal shapes (as they are hollow). + +## `ct.mouse.hoversUi(copy)` + +Returns `true` if the mouse hovers over a given `copy` in UI coordinates. This does **not** take scaling and rotation into account, as well as polygonal shapes (as they are hollow). ## `ct.mouse.hide()`, `ct.mouse.show()` Change the visibility of the mouse cursor. diff --git a/app/data/ct.libs/mouse/index.js b/app/data/ct.libs/mouse/index.js index 73c76c120..cc85228c5 100644 --- a/app/data/ct.libs/mouse/index.js +++ b/app/data/ct.libs/mouse/index.js @@ -17,10 +17,10 @@ }; ct.mouse = { - rx: 0, - ry: 0, xprev: 0, yprev: 0, + xuiprev: 0, + yuiprev: 0, inside: false, pressed: false, down: false, @@ -41,11 +41,20 @@ } return false; }, - get x() { - return ct.mouse.rx + ct.rooms.current.x; - }, - get y() { - return ct.mouse.ry + ct.rooms.current.y; + hoversUi(copy) { + if (!copy.shape) { + return false; + } + if (copy.shape.type === 'rect') { + return ct.u.prect(ct.mouse.xui, ct.mouse.yui, copy); + } + if (copy.shape.type === 'circle') { + return ct.u.pcircle(ct.mouse.xui, ct.mouse.yui, copy); + } + if (copy.shape.type === 'point') { + return ct.mouse.xui === copy.x && ct.mouse.yui === copy.y; + } + return false; }, hide() { ct.pixiApp.renderer.view.style.cursor = 'none'; @@ -57,11 +66,10 @@ ct.mouse.listenerMove = function(e) { var rect = ct.pixiApp.view.getBoundingClientRect(); - ct.mouse.rx = (e.clientX - rect.left) * ct.viewWidth / rect.width; - ct.mouse.ry = (e.clientY - rect.top) * ct.viewHeight / rect.height; - ct.mouse.x = ct.mouse.rx + ct.rooms.current.x; - ct.mouse.y = ct.mouse.ry + ct.rooms.current.y; - if (ct.mouse.rx > 0 && ct.mouse.ry > 0 && ct.mouse.ry < ct.viewHeight && ct.mouse.rx < ct.viewWidth) { + ct.mouse.xui = (e.clientX - rect.left) * ct.camera.width / rect.width; + ct.mouse.yui = (e.clientY - rect.top) * ct.camera.height / rect.height; + [ct.mouse.x, ct.mouse.y] = ct.u.uiToGameCoord(ct.mouse.xui, ct.mouse.yui); + if (ct.mouse.xui > 0 && ct.mouse.yui > 0 && ct.mouse.yui < ct.camera.height && ct.mouse.xui < ct.camera.width) { ct.mouse.inside = true; } else { ct.mouse.inside = false; @@ -88,9 +96,8 @@ e.preventDefault(); }; ct.mouse.listenerWheel = function (e) { - ct.mouse.wheel = e.wheelDelta || -e.detail < 0? -1 : 1; - setKey('wheel', ct.mouse.wheel); - e.preventDefault(); + setKey('Wheel', e.wheelDelta || -e.detail < 0? -1 : 1); + //e.preventDefault(); }; ct.mouse.setupListeners = function () { @@ -98,15 +105,19 @@ document.addEventListener('mousemove', ct.mouse.listenerMove, false); document.addEventListener('mouseup', ct.mouse.listenerUp, false); document.addEventListener('mousedown', ct.mouse.listenerDown, false); - document.addEventListener('wheel', ct.mouse.listenerWheel, false); + document.addEventListener('wheel', ct.mouse.listenerWheel, false, { + passive: false + }); document.addEventListener('contextmenu', ct.mouse.listenerContextMenu, false); - document.addEventListener('DOMMouseScroll', ct.mouse.listenerWheel, false); + document.addEventListener('DOMMouseScroll', ct.mouse.listenerWheel, { + passive: false + }); } else { // IE? document.attachEvent('onmousemove', ct.mouse.listenerMove); document.attachEvent('onmouseup', ct.mouse.listenerUp); document.attachEvent('onmousedown', ct.mouse.listenerDown); - document.attachEvent('oncontextmenu', ct.mouse.listenerWheel); - document.attachEvent('onmousewheel', ct.mouse.listenerContextMenu); + document.attachEvent('onmousewheel', ct.mouse.listenerWheel); + document.attachEvent('oncontextmenu', ct.mouse.listenerContextMenu); } }; })(); diff --git a/app/data/ct.libs/mouse/injects/afterroomdraw.js b/app/data/ct.libs/mouse/injects/afterroomdraw.js index d7fe071da..c773a31d8 100644 --- a/app/data/ct.libs/mouse/injects/afterroomdraw.js +++ b/app/data/ct.libs/mouse/injects/afterroomdraw.js @@ -1,3 +1,6 @@ ct.mouse.xprev = ct.mouse.x; ct.mouse.yprev = ct.mouse.y; +ct.mouse.xuiprev = ct.mouse.xui; +ct.mouse.yuiprev = ct.mouse.yui; ct.mouse.pressed = ct.mouse.released = false; +ct.inputs.registry['mouse.Wheel'] = 0; diff --git a/app/data/ct.libs/mouse/module.json b/app/data/ct.libs/mouse/module.json index 60c0b7c15..482bdfefc 100644 --- a/app/data/ct.libs/mouse/module.json +++ b/app/data/ct.libs/mouse/module.json @@ -1,7 +1,7 @@ { "main": { "name": "Mouse Input", - "version": "2.1.0", + "version": "3.0.0", "authors": [{ "name": "Cosmo Myzrail Gorynych", "mail": "admin@nersta.ru" diff --git a/app/data/ct.libs/mouse/types.d.ts b/app/data/ct.libs/mouse/types.d.ts index 1b115ffae..d6880e91d 100644 --- a/app/data/ct.libs/mouse/types.d.ts +++ b/app/data/ct.libs/mouse/types.d.ts @@ -1,14 +1,14 @@ declare namespace ct { /** A module that tells mouse coordinates and integrates with ct.js' Actions system */ namespace mouse { - /** Current cursor position at horisontal and vertical axes, in world coordinates */ + /** Current cursor position at horisontal and vertical axes, in game coordinates */ var x: number; - /** Current cursor position at horisontal and vertical axes, in world coordinates */ + /** Current cursor position at horisontal and vertical axes, in game coordinates */ var y: number; - /** A cursor position relative to the current view (or camera), but not relative to the room. */ - var rx: number; - /** A cursor position relative to the current view (or camera), but not relative to the room. */ - var ry: number; + /** A cursor position in UI coordinates. */ + var xui: number; + /** A cursor position in UI coordinates. */ + var yui: number; /** Can be either `true` or `false`. Determines whether a mouse button was pressed. */ var pressed: boolean; /** Can be either `true` or `false`. Determines whether a mouse button is held down. */ @@ -19,5 +19,7 @@ declare namespace ct { var inside: boolean; /** Returns `true` if the mouse hovers over a given `copy`. This does **not** take scaling and rotation into account, as well as polygonal shapes (as they are hollow). */ function hovers(copy: Copy): boolean; + /** Returns `true` if the mouse hovers over a given `copy`. This does **not** take scaling and rotation into account, as well as polygonal shapes (as they are hollow). */ + function hoversUi(copy: Copy): boolean; } } \ No newline at end of file diff --git a/app/data/ct.libs/place/README.md b/app/data/ct.libs/place/README.md index 4aae78e66..f9d1625f5 100644 --- a/app/data/ct.libs/place/README.md +++ b/app/data/ct.libs/place/README.md @@ -1,4 +1,10 @@ -Checks collisions between copies. +Checks collisions between copies. See the "Reference" tab for the methods. + + +# Collisions in sense of UI and game coordinates + +`ct.place` calculates collisions relative to a copy's parent. As UI layers and game layers live in different coordinates, you cannot reliably check collisions between copies of different coordinate spaces. Due to that, use different collision groups for UI elements and gameplay copies. + # Additions to copies @@ -6,6 +12,7 @@ You can call `this.moveContinuous('CollisionGroup');` at any copy to perform pre Additional field in the type editor "Collision group" defines the `ctype` property used in most ct.place methods. + # Preparing types > This is useful only when you are going to create copies of different shapes dynamically. In other cases, refer to the Docs tab, as ct.IDE defines these shapes itself. @@ -23,6 +30,7 @@ It is important to create a new `shape` object and not to change existing one, b Besides the `shape` parameter, each Copy can have a `ctype` parameter. It is used for grouping Copies into different collision groups, like 'Enemies', 'HeroBullets', 'Obstacles' etc. + # Collision shapes * `'point'` — does not require any additional parameters; diff --git a/app/data/ct.libs/place/injects/ondestroy.js b/app/data/ct.libs/place/injects/ondestroy.js index b3940eb45..1c7cc51da 100644 --- a/app/data/ct.libs/place/injects/ondestroy.js +++ b/app/data/ct.libs/place/injects/ondestroy.js @@ -1,6 +1,3 @@ -if (this.ctype) { - ct.place.ctypeCollections[this.$ctype].splice(ct.place.ctypeCollections[this.$ctype].indexOf(this), 1); -} if (this.$chashes) { for (const hash of this.$chashes) { ct.place.grid[hash].splice(ct.place.grid[hash].indexOf(this), 1); diff --git a/app/data/ct.libs/place/injects/roomonleave.js b/app/data/ct.libs/place/injects/roomonleave.js index cdd5b3885..c3ac2a944 100644 --- a/app/data/ct.libs/place/injects/roomonleave.js +++ b/app/data/ct.libs/place/injects/roomonleave.js @@ -1,2 +1 @@ -ct.place.grid = {}; -ct.place.ctypeCollections = {}; +ct.place.grid = {}; \ No newline at end of file diff --git a/app/data/ct.libs/place/injects/start.js b/app/data/ct.libs/place/injects/start.js index 9dbb4a71f..daafa05f0 100644 --- a/app/data/ct.libs/place/injects/start.js +++ b/app/data/ct.libs/place/injects/start.js @@ -1,15 +1,5 @@ -ct.place.ctypeCollections = {}; Object.defineProperty(ct.types.Copy.prototype, 'ctype', { set: function(value) { - if (this.ctype) { - ct.place.ctypeCollections[this.ctype].splice(ct.place.ctypeCollections[this.ctype].indexOf(this), 1); - } - if (value) { - if (!(value in ct.place.ctypeCollections)) { - ct.place.ctypeCollections[value] = []; - } - ct.place.ctypeCollections[value].push(this); - } this.$ctype = value; }, get: function() { diff --git a/app/data/ct.libs/touch/README.md b/app/data/ct.libs/touch/README.md index 4eb3290ef..c6c13ecad 100644 --- a/app/data/ct.libs/touch/README.md +++ b/app/data/ct.libs/touch/README.md @@ -9,11 +9,15 @@ Note that Internet Explorer, Edge and Safari do not support such events. Mobile * `id` is a unique number that identifies a touch; * `x` is the horizontal position at which a touch occures; * `y` is the vertical position at which a touch occures; +* `xui` is the horizontal position at which a touch occures in UI coordinates; +* `yui` is the vertical position at which a touch occures in UI coordinates; * `r` is the size of a touch, in game pixels. Sometimes this variable is not available and is equal to `0`; * `xprev` is the horizontal position at which a touch occured in the previous frame; * `yprev` is the vertical position at which a touch occured in the previous frame. +* `xuiprev` is the horizontal position at which a touch occured in the previous frame, but in UI coordinates; +* `yuiprev` is the vertical position at which a touch occured in the previous frame, but in UI coordinates. -There are also variables `ct.touch.x` and `ct.touch.y`. They represent the position at which a surface was pressed lastly. +There are also variables `ct.touch.x`, `ct.touch.y`, `ct.touch.xui` and `ct.touch.yui`. They represent the position at which a surface was pressed lastly. Here, the whole process of pressing a surface with one finger (or stylus, whatever), moving it, and finally releasing is called a **touch event**. @@ -33,11 +37,11 @@ You can generalize mouse and touch events by enabling a corresponding option at ## Checking button touches -`ct.touch.collide(copy, id)`, **which is dependant on `ct.place` catmod**, checks whether there is a collision between a copy and a touch event of a particular id. You can also omit `id` to check against all possible touch events. +`ct.touch.collide(copy, id)`, **which is dependant on `ct.place` catmod**, checks whether there is a collision between a copy and a touch event of a particular id. You can also omit `id` to check against all possible touch events. `ct.touch.collideUi` does the same, but in UI coordinates. -A variant of this method is `ct.touch.hovers(copy, id)`, that also checks for mouse. +There are ariants of this method that also check for mouse, `ct.touch.hovers(copy, id)` and `ct.touch.hoversUi(copy, id)`. -`ct.touch.hovers(copy)` and `ct.touch.collide(copy, id)` don't work well with just released touches (because they become inactive), and a special version of `ct.touch.hovers` exists for handling such events: `ct.touch.hovers(copy, id, true)`. You can set `id` to `false` if you don't need it. +`ct.touch.hovers(copy)` and `ct.touch.collide(copy, id)` don't work well with just released touches (because they become inactive), and a special version of `ct.touch.hovers` exists for handling such events: `ct.touch.hovers(copy, id, true)`. You can set `id` to `false` if you don't need it. The same goes for `ct.touch.hoversUi`. As this variant combines both hover event and release one, writing a code for a button is very simple: diff --git a/app/data/ct.libs/touch/index.js b/app/data/ct.libs/touch/index.js index 8ccb9d08c..943f3d676 100644 --- a/app/data/ct.libs/touch/index.js +++ b/app/data/ct.libs/touch/index.js @@ -16,17 +16,22 @@ }; // returns a new object with the necessary information about a touch event var copyTouch = e => { - var rect = ct.pixiApp.view.getBoundingClientRect(); - var touch = { + const rect = ct.pixiApp.view.getBoundingClientRect(); + const xui = (e.clientX - rect.left) / rect.width * ct.camera.width, + yui = (e.clientY - rect.top) / rect.height * ct.camera.height; + const positionGame = ct.u.uiToGameCoord(xui, yui); + const touch = { id: e.identifier, - x: (e.clientX - rect.left) * ct.viewWidth / rect.width + ct.rooms.current.x, - y: (e.clientY - rect.top) * ct.viewHeight / rect.height + ct.rooms.current.y, - clientX: e.clientX, - clientY: e.clientY, + x: positionGame[0], + y: positionGame[1], + xui: xui, + yui: yui, + xprev: positionGame[0], + yprev: positionGame[1], + xuiprev: xui, + yuiprev: yui, r: e.radiusX? Math.max(e.radiusX, e.radiusY) : 0 }; - touch.xprev = touch.x; - touch.yprev = touch.y; return touch; }; var findTouch = id => { @@ -54,6 +59,8 @@ ct.touch.events.push(touch); ct.touch.x = touch.x; ct.touch.y = touch.y; + ct.touch.xui = touch.xui; + ct.touch.yui = touch.yui; } countTouches(); }; @@ -66,13 +73,14 @@ upd = findTouch(e.changedTouches[i].identifier); if (upd) { const rect = ct.pixiApp.view.getBoundingClientRect(); - upd.x = (touch.clientX - rect.left) * ct.viewWidth / rect.width + ct.rooms.current.x; - upd.y = (touch.clientY - rect.top) * ct.viewHeight / rect.height + ct.rooms.current.y; - upd.clientX = touch.clientX; - upd.clientY = touch.clientY; + upd.xui = (touch.clientX - rect.left) / rect.width * ct.camera.width; + upd.yui = (touch.clientY - rect.top) / rect.height * ct.camera.height; + [upd.x, upd.y] = ct.u.uiToGameCoord(upd.xui, upd.yui); upd.r = touch.radiusX? Math.max(touch.radiusX, touch.radiusY) : 0; ct.touch.x = upd.x; ct.touch.y = upd.y; + ct.touch.xui = upd.xui; + ct.touch.yui = upd.yui; } } }; @@ -93,26 +101,28 @@ const rect = ct.pixiApp.view.getBoundingClientRect(); var touch = { id: -1, - x: (e.clientX - rect.left) * ct.viewWidth / rect.width + ct.rooms.current.x, - y: (e.clientY - rect.top) * ct.viewHeight / rect.height + ct.rooms.current.y, - clientX: e.clientX, - clientY: e.clientY, + xui: (e.clientX - rect.left) * ct.camera.width / rect.width, + yui: (e.clientY - rect.top) * ct.camera.height / rect.height, r: 0 }; + [touch.x, touch.y] = ct.u.uiToGameCoord(touch.xui, touch.yui); ct.touch.events.push(touch); ct.touch.x = touch.x; ct.touch.y = touch.y; + ct.touch.xui = touch.xui; + ct.touch.yui = touch.yui; }; var mouseMove = function (e) { const rect = ct.pixiApp.view.getBoundingClientRect(), touch = findTouch(-1); if (touch) { - touch.x = (e.clientX - rect.left) * ct.viewWidth / rect.width + ct.rooms.current.x; - touch.y = (e.clientY - rect.top) * ct.viewHeight / rect.height + ct.rooms.current.y; - touch.clientX = e.clientX; - touch.clientY = e.clientY; + touch.xui = (e.clientX - rect.left) * ct.camera.width / rect.width; + touch.yui = (e.clientY - rect.top) * ct.camera.height / rect.height; + [touch.x, touch.y] = ct.u.uiToGameCoord(touch.xui, touch.yui); ct.touch.x = touch.x; ct.touch.y = touch.y; + ct.touch.xui = touch.xui; + ct.touch.yui = touch.yui; } }; var mouseUp = function () { @@ -143,6 +153,12 @@ events: [], x: 0, y: 0, + xprev: 0, + yprev: 0, + xui: 0, + yui: 0, + xuiprev: 0, + yuiprev: 0, clear() { ct.touch.events.length = 0; ct.touch.clearReleased(); @@ -189,9 +205,50 @@ } return false; }, + collideUi(copy, id, rel) { + var set = rel? ct.touch.released : ct.touch.events; + if (id !== void 0 && id !== false) { + const i = findTouchId(id); + if (i === -1) { + return false; + } + return ct.place.collide(copy, { + x: set[i].xui, + y: set[i].yui, + shape: { + type: set[i].r? 'circle' : 'point', + r: set[i].r + }, + scale: { + x: 1, + y: 1 + } + }); + } + for (let i = 0, l = set.length; i < l; i++) { + if (ct.place.collide(copy, { + x: set[i].xui, + y: set[i].yui, + shape: { + type: set[i].r? 'circle' : 'point', + r: set[i].r + }, + scale: { + x: 1, + y: 1 + } + })) { + return true; + } + } + return false; + }, hovers(copy, id, rel) { return ct.mouse? (ct.mouse.hovers(copy) || ct.touch.collide(copy, id, rel)) : ct.touch.collide(copy, id, rel); }, + hoversUi(copy, id, rel) { + return ct.mouse? (ct.mouse.hoversUi(copy) || ct.touch.collideUi(copy, id, rel)) : ct.touch.collideUi(copy, id, rel); + }, getById: findTouch, updateGestures: function () { let x = 0, @@ -203,7 +260,7 @@ } x /= ct.touch.events.length; y /= ct.touch.events.length; - + let rotation = 0, distance = lastScaleDistance; if (ct.touch.events.length > 1) { @@ -212,17 +269,17 @@ ct.touch.events[1] ].sort((a, b) => a.id - b.id); rotation = ct.u.pdn( - events[0].x, - events[0].y, - events[1].x, + events[0].x, + events[0].y, + events[1].x, events[1].y); distance = ct.u.pdc( - events[0].x, - events[0].y, - events[1].x, + events[0].x, + events[0].y, + events[1].x, events[1].y); } - + if (lastPanNum === ct.touch.events.length) { if (ct.touch.events.length > 1) { setKey('DeltaRotation', (ct.u.degToRad(ct.u.deltaDir(lastRotation, rotation)))); diff --git a/app/data/ct.libs/touch/injects/afterroomdraw.js b/app/data/ct.libs/touch/injects/afterroomdraw.js index 6d3a358ad..d19d4f65a 100644 --- a/app/data/ct.libs/touch/injects/afterroomdraw.js +++ b/app/data/ct.libs/touch/injects/afterroomdraw.js @@ -1,5 +1,7 @@ for (const touch of ct.touch.events) { touch.xprev = touch.x; touch.yprev = touch.y; + touch.xuiprev = touch.x; + touch.yuiprev = touch.y; ct.touch.clearReleased(); } diff --git a/app/data/ct.libs/touch/types.d.ts b/app/data/ct.libs/touch/types.d.ts index 868b4823b..319a8ec38 100644 --- a/app/data/ct.libs/touch/types.d.ts +++ b/app/data/ct.libs/touch/types.d.ts @@ -2,20 +2,32 @@ interface ITouch { /** A unique number that identifies a touch; */ id: number; - /** The horizontal position at which a touch occures; */ + /** The horizontal position at which a touch occures. */ x: number; - /** The vertical position at which a touch occures; */ + /** The vertical position at which a touch occures. */ y: number; - /** The size of a touch, in game pixels. Sometimes this variable is not available and is equal to `0`; */ + /** The horizontal position at which a touch occures, in UI space.*/ + xui: number; + + /** The vertical position at which a touch occures, in UI space.*/ + yui: number; + + /** The size of a touch, in game pixels. Sometimes this variable is not available and is equal to `0`. */ r: number; - /** The horizontal position at which a touch occured in the previous frame; */ + /** The horizontal position at which a touch occured in the previous frame. */ xprev: number; /** The vertical position at which a touch occured in the previous frame. */ yprev: number; + + /** The horizontal position at which a touch occured in the previous frame, in UI coordinates. */ + xuiprev: number; + + /** The vertical position at which a touch occured in the previous frame, in UI coordinates. */ + yuiprev: number; } declare namespace ct { @@ -31,6 +43,24 @@ declare namespace ct { /** The position at which a surface was pressed lastly. */ var y: number; + /** The position at which a surface was pressed lastly, but in UI coordinates. */ + var xui: number; + + /** The position at which a surface was pressed lastly, but in UI coordinates. */ + var yui: number; + + /** The previous position at which a surface was pressed lastly. */ + var xprev: number; + + /** The previous position at which a surface was pressed lastly. */ + var yprev: number; + + /** The previous position at which a surface was pressed lastly, but in UI coordinates. */ + var xuiprev: number; + + /** The previous position at which a surface was pressed lastly, but in UI coordinates. */ + var yuiprev: number; + var events: ITouch[]; function getById(id: number): ITouch | false; @@ -40,21 +70,49 @@ declare namespace ct { * of a particular id. You can also omit `id` to check * against all possible touch events. * + * This is a version that checks for collisions in game coordinates. + * * If `includeReleased` is set to `true`, will check against * not active but just released touch events. */ function collide(copy: Copy, id?: number, includeReleased?: boolean): boolean; + /** + * Checks whether there is a collision between a copy and a touch event + * of a particular id. You can also omit `id` to check + * against all possible touch events. + * + * This is a version that checks for collisions in UI space. + * + * If `includeReleased` is set to `true`, will check against + * not active but just released touch events. + */ + function collideUi(copy: Copy, id?: number, includeReleased?: boolean): boolean; + /** * Checks whether there is a collision between a copy and a touch event * of a particular id or a mouse cursor. You can also omit `id` to check * against all possible touch events. * + * This is a version that checks for collisions in game coordinates. + * * If `includeReleased` is set to `true`, will check against * not active but just released touch events. */ function hovers(copy: Copy, id?: number, includeReleased?: boolean): boolean; + /** + * Checks whether there is a collision between a copy and a touch event + * of a particular id or a mouse cursor. You can also omit `id` to check + * against all possible touch events. + * + * This is a version that checks for collisions in UI space. + * + * If `includeReleased` is set to `true`, will check against + * not active but just released touch events. + */ + function hoversUi(copy: Copy, id?: number, includeReleased?: boolean): boolean; + /** * Returns whether touch events are supported on the current machine. * diff --git a/app/data/ct.libs/tween/injects/roomonleave.js b/app/data/ct.libs/tween/injects/roomonleave.js index fa5b44410..160d3b709 100644 --- a/app/data/ct.libs/tween/injects/roomonleave.js +++ b/app/data/ct.libs/tween/injects/roomonleave.js @@ -1,10 +1,12 @@ /* global ct */ -for (var tween of ct.tween.tweens) { - tween.reject({ - info: 'Room switch', - code: 1, - from: 'ct.tween' - }); +if (!this.kill) { + for (var tween of ct.tween.tweens) { + tween.reject({ + info: 'Room switch', + code: 1, + from: 'ct.tween' + }); + } + ct.tween.tweens = []; } -ct.tween.tweens = []; diff --git a/app/data/ct.libs/vkeys/DOCS.md b/app/data/ct.libs/vkeys/DOCS.md index 7d4748ea6..edb2024d6 100644 --- a/app/data/ct.libs/vkeys/DOCS.md +++ b/app/data/ct.libs/vkeys/DOCS.md @@ -9,7 +9,8 @@ Options include: * `texHover` — the texture for a hover state. If not provided, it will use `texNormal` instead. * `texActive` — the texture for a pressed state. If not provided, it will use `texNormal` instead. * `x` and `y` — number that position a button in the room. If a function is provided, it will update the position every frame. -* `depth` — the depth value. +* `depth` — the depth value; +* `container` — the parent of the created button. It defaults to the current room. Example of a button that self-aligns in the viewport: @@ -18,8 +19,8 @@ var keyLeft = ct.vkeys.button({ key: 'Vk1', texNormal: 'Key_Normal', texHover: 'Key_Active', - x: () => ct.room.x + ct.viewWidth - 130, - y: () => ct.room.y + ct.viewHeight - 130, + x: () => ct.camera.right - 130, + y: () => ct.camera.bottom - 130, depth: 14000 }); ``` @@ -34,7 +35,8 @@ Options include: * `tex` — the texture for the trackpad. Its collision shape is used to calculate joystick's values and to position the trackball. * `trackballTex` — the texture for the trackball. * `x` and `y` — number that position a button in the room. If a function is provided, it will update the position every frame. -* `depth` — the depth value. +* `depth` — the depth value; +* `container` — the parent of the created button. It defaults to the current room. Example of a joystick that self-aligns in the viewport: @@ -43,7 +45,7 @@ ct.vkeys.joystick({ tex: 'TrackPad', trackballTex: 'TrackBall', depth: 14000, - x: () => ct.room.x + 212, - y: () => ct.room.y + ct.viewHeight - 212 + x: () => ct.camera.left + 212, + y: () => ct.camera.bottom - 212 }); ``` diff --git a/app/data/ct.libs/vkeys/README.md b/app/data/ct.libs/vkeys/README.md index 112bc8cb1..b8f4dcf8f 100644 --- a/app/data/ct.libs/vkeys/README.md +++ b/app/data/ct.libs/vkeys/README.md @@ -9,12 +9,12 @@ var keyUp = ct.vkeys.button({ texHover: 'Button_Hover', texActive: 'Button_Active', x: 50, - y: ct.viewHeight - 50, + y: 50, depth: 100 }); ``` -This will create a button in the bottom-left corner of the screen that will be binded to `vkeys.Vk1` input method. Textures will change on hover and press (you should add actual textures for that). +This will create a button in the top-left corner of the screen that will be binded to `vkeys.Vk1` input method. Textures will change on hover and press (you should add actual textures for that). **Example of a virtual joystick:** @@ -25,8 +25,29 @@ ct.vkeys.joystick({ trackballTex: 'TrackBall', depth: 100, x: 128, - y: ct.viewHeight - 128 + y: 128 }); ``` -This joystick will be binded to input methods `vkeys.Vjoy1X` and `vkeys.Vjoy1Y`. \ No newline at end of file +This joystick will be binded to input methods `vkeys.Vjoy1X` and `vkeys.Vjoy1Y`. + +## Using `ct.vkeys` with UI layers + +Each methods' options has a field `container`, that can be set to any parent, including UI rooms: + +```js +this.uiLayer = ct.rooms.append('UI_Layer', { + isUi: true +}); +ct.vkeys.joystick({ + key: 'Vjoy1', + tex: 'TrackPad', + trackballTex: 'TrackBall', + depth: 100, + x: 128, + y: 128, + container: this.uiLayer +}); +``` + +If you set `x` and/or `y` as functions, the created control won't be affected by `ct.camera.realign`. \ No newline at end of file diff --git a/app/data/ct.libs/vkeys/index.js b/app/data/ct.libs/vkeys/index.js index 9fd3d4f5a..73a085589 100644 --- a/app/data/ct.libs/vkeys/index.js +++ b/app/data/ct.libs/vkeys/index.js @@ -5,16 +5,17 @@ key: 'Vk1', depth: 100, texNormal: -1, - x: function() { - return ct.room.x + 128; - }, - y: function() { - return ct.room.y + 128; - } + x: 128, + y: 128, + container: ct.room }, options || {}); - return ct.types.copy('VKEY', 0, 0, { + const copy = ct.types.copy('VKEY', 0, 0, { opts: opts - }); + }, opts.container); + if (typeof options.x === 'function' || typeof options.y === 'function') { + copy.skipRealign = true; + } + return copy; }, joystick(options) { var opts = ct.u.ext({ @@ -22,16 +23,17 @@ depth: 100, tex: -1, trackballTex: -1, - x: function() { - return ct.room.x + 128; - }, - y: function() { - return ct.room.y + 128; - } + x: 128, + y: 128, + container: ct.room }, options || {}); - return ct.types.copy('VJOYSTICK', 0, 0, { + const copy = ct.types.copy('VJOYSTICK', 0, 0, { opts: opts - }); + }, opts.container); + if (typeof options.x === 'function' || typeof options.y === 'function') { + copy.skipRealign = true; + } + return copy; } }; })(); diff --git a/app/data/ct.libs/vkeys/module.json b/app/data/ct.libs/vkeys/module.json index 32bbd5fee..b156a33d8 100644 --- a/app/data/ct.libs/vkeys/module.json +++ b/app/data/ct.libs/vkeys/module.json @@ -1,7 +1,7 @@ { "main": { "name": "Virtual Keys", - "version": "0.0.0", + "version": "1.0.0", "authors": [{ "name": "Cosmo Myzrail Gorynych", "mail": "admin@nersta.ru" diff --git a/app/data/ct.libs/vkeys/types.d.ts b/app/data/ct.libs/vkeys/types.d.ts index 9df312331..145740827 100644 --- a/app/data/ct.libs/vkeys/types.d.ts +++ b/app/data/ct.libs/vkeys/types.d.ts @@ -18,6 +18,8 @@ interface IVkeysButtonOptions { y: number | VkeysReturnNumber; /** The depth value. */ depth: number; + /** The container to attach the button to. Defaults to the current room. */ + container?: PIXI.DisplayObject; } interface VkeysButton extends Copy { opts: IVkeysButtonOptions; @@ -37,6 +39,8 @@ interface IVkeysJoystickOptions { y: number | VkeysReturnNumber; /** The depth value. */ depth: number; + /** The container to attach the joystick to. Defaults to the current room. */ + container?: PIXI.DisplayObject; } interface VkeysJoystick extends Copy { opts: IVkeysJoystickOptions; diff --git a/app/data/ct.release/.eslintrc.json b/app/data/ct.release/.eslintrc.json index ba4f0405c..8a5b17a2f 100644 --- a/app/data/ct.release/.eslintrc.json +++ b/app/data/ct.release/.eslintrc.json @@ -1,7 +1,12 @@ { "globals": { "ct": false, - "PIXI": false + "PIXI": false, + "Room": false, + "Background": false, + "Tileset": false, + "Copy": false, + "Camera": false }, "rules": { "prefer-destructuring": "off" diff --git a/app/data/ct.release/camera.js b/app/data/ct.release/camera.js new file mode 100644 index 000000000..b77a50943 --- /dev/null +++ b/app/data/ct.release/camera.js @@ -0,0 +1,322 @@ +/* eslint-disable no-unused-vars */ +/** + * This class represents a camera that is used by ct.js' cameras. + * Usually you won't create new instances of it, but if you need, you can substitute + * ct.camera with a new one. + * + * @extends {PIXI.DisplayObject} + * @class + * + * @property {number} x The real x-coordinate of the camera. It does not have a screen shake effect applied, as well as may differ from `targetX` if the camera is in transition. + * @property {number} y The real y-coordinate of the camera. It does not have a screen shake effect applied, as well as may differ from `targetY` if the camera is in transition. + * @property {number} width The width of the unscaled shown region. This is the base, unscaled value. Use ct.camera.scale.x to get a scaled version. To change this value, see `ct.width` property. + * @property {number} height The width of the unscaled shown region. This is the base, unscaled value. Use ct.camera.scale.y to get a scaled version. To change this value, see `ct.height` property. + * @property {number} targetX The x-coordinate of the target location. Moving it instead of just using the `x` parameter will trigger the drift effect. + * @property {number} targetY The y-coordinate of the target location. Moving it instead of just using the `y` parameter will trigger the drift effect. + * + * @property {Copy|false} follow If set, the camera will follow the given copy. + * @property {number|null} borderX Works if `follow` is set to a copy. Sets the frame inside which the copy will be kept, in game pixels. Can be set to `null` so the copy is set to the center of the screen. + * @property {number|null} borderY Works if `follow` is set to a copy. Sets the frame inside which the copy will be kept, in game pixels. Can be set to `null` so the copy is set to the center of the screen. + * @property {number} shiftX Displaces the camera horizontally but does not change x and y parameters. + * @property {number} shiftY Displaces the camera vertically but does not change x and y parameters. + * @property {number} drift Works if `follow` is set to a copy. If set to a value between 0 and 1, it will make camera movement smoother + * + * @property {number} shake The current power of a screen shake effect, relative to the screen's max side (100 is 100% of screen shake). If set to 0 or less, it, disables the effect. + * @property {number} shakePhase The current phase of screen shake oscillation. + * @property {number} shakeDecay The amount of `shake` units substracted in a second. Default is 5. + * @property {number} shakeFrequency The base frequency of the screen shake effect. Default is 50. + * @property {number} shakeX A multiplier applied to the horizontal screen shake effect. Default is 1. + * @property {number} shakeY A multiplier applied to the vertical screen shake effect. Default is 1. + * @property {number} shakeMax The maximum possible value for the `shake` property to protect players from losing their monitor, in `shake` units. Default is 10. + */ +class Camera extends PIXI.DisplayObject { + constructor(x, y, w, h) { + super(); + this.follow = this.rotate = false; + this.targetX = this.x = x; + this.targetY = this.y = y; + this.width = w || 1920; + this.height = h || 1080; + this.shiftX = this.shiftY = this.interpolatedShiftX = this.interpolatedShiftY = 0; + this.borderX = this.borderY = null; + this.drift = 0; + + this.shake = 0; + this.shakeDecay = 5; + this.shakeX = this.shakeY = 1; + this.shakeFrequency = 50; + this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; + this.shakeMax = 10; + + this.getBounds = this.getBoundingBox; + } + + /** + * Moves the camera to a new position. It will have a smooth transition + * if a `drift` parameter is set. + * @param {number} x New x coordinate + * @param {number} y New y coordinate + * @returns {void} + */ + moveTo(x, y) { + this.targetX = x; + this.targetY = y; + } + + /** + * Moves the camera to a new position. Ignores the `drift` value. + * @param {number} x New x coordinate + * @param {number} y New y coordinate + * @returns {void} + */ + teleportTo(x, y) { + this.targetX = this.x = x; + this.targetY = this.y = y; + this.shakePhase = this.shakePhaseX = this.shakePhaseY = 0; + this.interpolatedShiftX = this.shiftX; + this.interpolatedShiftY = this.shiftY; + } + + /** + * Updates the position of the camera + * @param {number} delta A delta value between the last two frames. This is usually ct.delta. + * @returns {void} + */ + update(delta) { + if (this.follow && this.follow.kill) { + this.follow = false; + } + + const sec = delta / (PIXI.Ticker.shared.maxFPS || 60); + this.shake -= sec * this.shakeDecay; + this.shake = Math.max(0, this.shake); + if (this.shakeMax) { + this.shake = Math.min(this.shake, this.shakeMax); + } + this.shakePhase += sec * this.shakeFrequency; + this.shakePhaseX += sec * this.shakeFrequency * (1 + Math.sin(this.shakePhase * 0.1489) * 0.25); // no logic in these constants + this.shakePhaseY += sec * this.shakeFrequency * (1 + Math.sin(this.shakePhase * 0.1734) * 0.25); // They are used to desync fluctuations and remove repetitive circular movements + + // The speed of drift movement + const speed = this.drift? Math.min(1, (1-this.drift)*delta) : 1; + + if (this.follow && ('x' in this.follow) && ('y' in this.follow)) { + const bx = this.borderX === null? this.width / 2 : Math.min(this.borderX, this.width / 2), + by = this.borderY === null? this.height / 2 : Math.min(this.borderY, this.height / 2); + const tl = this.uiToGameCoord(bx, by), + br = this.uiToGameCoord(this.width - bx, this.height - by); + if (this.follow.x < tl[0] - this.interpolatedShiftX) { + this.targetX = this.follow.x - bx + this.width / 2; + } else if (this.follow.x > br[0] - this.interpolatedShiftX) { + this.targetX = this.follow.x + bx - this.width / 2; + } + if (this.follow.y < tl[1] - this.interpolatedShiftY) { + this.targetY = this.follow.y - by + this.height / 2; + } else if (this.follow.y > br[1] - this.interpolatedShiftY) { + this.targetY = this.follow.y + by - this.height / 2; + } + } + + this.x = this.targetX * speed + this.x * (1-speed); + this.y = this.targetY * speed + this.y * (1-speed); + this.interpolatedShiftX = this.shiftX * speed + this.interpolatedShiftX * (1-speed); + this.interpolatedShiftY = this.shiftY * speed + this.interpolatedShiftY * (1-speed); + + this.x = this.x || 0; + this.y = this.y || 0; + } + + /** Returns the current camera position plus the screen shake effect. */ + get computedX() { + const dx = (Math.sin(this.shakePhaseX) + Math.sin(this.shakePhaseX * 3.1846)*0.25) / 1.25; + const x = this.x + dx * this.shake * Math.max(this.width, this.height) / 100 * this.shakeX; + return x + this.interpolatedShiftX; + } + /** Returns the current camera position plus the screen shake effect. */ + get computedY() { + const dy = (Math.sin(this.shakePhaseY) + Math.sin(this.shakePhaseY * 2.8948)*0.25) / 1.25; + const y = this.y + dy * this.shake * Math.max(this.width, this.height) / 100 * this.shakeY; + return y + this.interpolatedShiftY; + } + + /** + * Returns the position of the left edge where the visible rectangle ends, in game coordinates. + * This can be used for UI positioning in game coordinates. This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopLeftCorner` and `getBottomLeftCorner` methods. + * @returns {number} The location of the left edge. + * @readonly + */ + get left() { + return this.computedX + (this.width / 2) * this.scale.x; + } + /** + * Returns the position of the top edge where the visible rectangle ends, in game coordinates. + * This can be used for UI positioning in game coordinates. This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopLeftCorner` and `getTopRightCorner` methods. + * @returns {number} The location of the top edge. + * @readonly + */ + get top() { + return this.computedY - (this.height / 2) * this.scale.y; + } + /** + * Returns the position of the right edge where the visible rectangle ends, in game coordinates. + * This can be used for UI positioning in game coordinates. This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getTopRightCorner` and `getBottomRightCorner` methods. + * @returns {number} The location of the right edge. + * @readonly + */ + get right() { + return this.computedX + (this.width / 2) * this.scale.x; + } + /** + * Returns the position of the bottom edge where the visible rectangle ends, in game coordinates. + * This can be used for UI positioning in game coordinates. This does not count for rotations, though. + * For rotated and/or scaled viewports, see `getBottomLeftCorner` and `getBottomRightCorner` methods. + * @returns {number} The location of the bottom edge. + * @readonly + */ + get bottom() { + return this.computedY + (this.height / 2) * this.scale.y; + } + + /** + * Translates a point from UI space to game space. + * @param {number} x The x coordinate in UI space. + * @param {number} y The y coordinate in UI space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + uiToGameCoord(x, y) { + const modx = (x - this.width / 2) * this.scale.x, + mody = (y - this.height / 2) * this.scale.y; + const result = ct.u.rotate(modx, mody, this.rotation); + return [result[0] + this.computedX, result[1] + this.computedY]; + } + + /** + * Translates a point from game space to UI space. + * @param {number} x The x coordinate in game space. + * @param {number} y The y coordinate in game space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + gameToUiCoord(x, y) { + const relx = x - this.computedX, + rely = y - this.computedY; + const unrotated = ct.u.rotate(relx, rely, -this.rotation); + return [ + unrotated[0] / this.scale.x + this.width / 2, + unrotated[1] / this.scale.y + this.height / 2 + ]; + } + /** + * Gets the position of the top-left corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getTopLeftCorner() { + return this.uiToGameCoord(0, 0); + } + + /** + * Gets the position of the top-right corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getTopRightCorner() { + return this.uiToGameCoord(this.width, 0); + } + + /** + * Gets the position of the bottom-left corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getBottomLeftCorner() { + return this.uiToGameCoord(0, this.height); + } + + /** + * Gets the position of the bottom-right corner of the viewport in game coordinates. + * This is useful for positioning UI elements in game coordinates, especially with rotated viewports. + * @returns {Array} A pair of `x` and `y` coordinates. + */ + getBottomRightCorner() { + return this.uiToGameCoord(this.width, this.height); + } + + /** + * Returns the bounding box of the camera. + * Useful for rotated viewports when something needs to be reliably covered by a rectangle. + * @returns {PIXI.Rectangle} The bounding box of the camera. + */ + getBoundingBox() { + const bb = new PIXI.Bounds(); + const tl = this.getTopLeftCorner(), + tr = this.getTopRightCorner(), + bl = this.getBottomLeftCorner(), + br = this.getBottomRightCorner(); + bb.addPoint(new PIXI.Point(tl[0], tl[1])); + bb.addPoint(new PIXI.Point(tr[0], tr[1])); + bb.addPoint(new PIXI.Point(bl[0], bl[1])); + bb.addPoint(new PIXI.Point(br[0], br[1])); + return bb.getRectangle(); + } + + get rotation() { + return this.transform.rotation / Math.PI * -180; + } + /** + * The rotation angle of a camera. + * @param {number} value New rotation value + * @type {number} + */ + set rotation(value) { + this.transform.rotation = value * Math.PI / -180; + return value; + } + + /** + * Realigns all the copies in a room so that they distribute proportionally + * to a new camera size based on their `xstart` and `ystart` coordinates. + * Will throw an error if the given room is not in UI space (if `room.isUi` is not `true`). + * You can skip the realignment for some copies if you set their `skipRealign` parameter to `true`. + * @param {Room} room The room which copies will be realigned. + * @returns {void} + */ + realign(room) { + if (!room.isUi) { + throw new Error('[ct.camera] An attempt to realing a room that is not in UI space. The room in question is', room); + } + const w = (ct.rooms.templates[room.name].width || 1), + h = (ct.rooms.templates[room.name].height || 1); + for (const copy of room.children) { + if (!('xstart' in copy) || copy.skipRealign) { + continue; + } + copy.x = copy.xstart / w * this.width; + copy.y = copy.ystart / h * this.height; + } + } + /** + * This will align all non-UI layers in the game according to the camera's transforms. + * This is automatically called internally, and you will hardly ever use it. + * @returns {void} + */ + manageStage() { + const px = this.computedX, + py = this.computedY, + sx = 1 / (isNaN(this.scale.x)? 1 : this.scale.x), + sy = 1 / (isNaN(this.scale.y)? 1 : this.scale.y); + for (const item of ct.stage.children) { + if (!item.isUi && item.pivot) { + item.x = -this.width / 2; + item.y = -this.height / 2; + item.pivot.x = px; + item.pivot.y = py; + item.scale.x = sx; + item.scale.y = sy; + item.angle = -this.angle; + } + } + } +} diff --git a/app/data/ct.release/main.js b/app/data/ct.release/main.js index 1c38f6c7a..297e9c0f8 100644 --- a/app/data/ct.release/main.js +++ b/app/data/ct.release/main.js @@ -46,6 +46,11 @@ const ct = { * @type {number} */ delta: 1, + /** + * The camera that outputs its view to the renderer. + * @type {Camera} + */ + camera: null, /** * ct.js version in form of a string `X.X.X`. * @type {string} @@ -66,7 +71,7 @@ const ct = { * @type {number} */ set width(value) { - ct.viewWidth = ct.roomWidth = value; + ct.camera.width = ct.roomWidth = value; if (!ct.fittoscreen || ct.fittoscreen.mode === 'fastScale') { ct.pixiApp.renderer.resize(value, ct.height); } @@ -85,7 +90,7 @@ const ct = { * @type {number} */ set height(value) { - ct.viewHeight = ct.roomHeight = value; + ct.camera.height = ct.roomHeight = value; if (!ct.fittoscreen || ct.fittoscreen.mode === 'fastScale') { ct.pixiApp.renderer.resize(ct.width, value); } @@ -95,15 +100,21 @@ const ct = { return value; }, /** - * The width of the current view, in game units + * The width of the current view, in UI units * @type {number} + * @deprecated Since v1.3.0. See `ct.camera.width`. */ - viewWidth: null, + get viewWidth() { + return ct.camera.width; + }, /** - * The height of the current view, in game units + * The height of the current view, in UI units * @type {number} + * @deprecated Since v1.3.0. See `ct.camera.height`. */ - viewHeight: null + get viewHeight() { + return ct.camera.height; + } }; // eslint-disable-next-line no-console @@ -288,6 +299,24 @@ ct.u = { unlerp(a, b, val) { return (val - a) / (b - a); }, + /** + * Translates a point from UI space to game space. + * @param {number} x The x coordinate in UI space. + * @param {number} y The y coordinate in UI space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + uiToGameCoord(x, y) { + return ct.camera.uiToGameCoord(x, y); + }, + /** + * Translates a point from fame space to UI space. + * @param {number} x The x coordinate in game space. + * @param {number} y The y coordinate in game space. + * @returns {Array} A pair of new `x` and `y` coordinates. + */ + gameToUiCoord(x, y) { + return ct.camera.gameToUiCoord(x, y); + }, /** * Tests whether a given point is inside the given rectangle (it can be either a copy or an array) * @param {number} x The x coordinate of the point @@ -387,119 +416,104 @@ ct.u.ext(ct.u, {// make aliases extend: ct.u.ext }); -const removeKilledCopies = (array) => { - let j = 0; - for (let i = 0; i < array.length; i++) { - if (!array[i].kill) { - array[j++] = array[i]; +(() => { + const removeKilledCopies = (array) => { + let j = 0; + for (let i = 0; i < array.length; i++) { + if (!array[i].kill) { + array[j++] = array[i]; + } } - } + array.length = j; + return array; + }; + const killRecursive = copy => { + copy.kill = true; + ct.types.onDestroy.apply(copy); + copy.onDestroy.apply(copy); + for (const child of copy.children) { + if (child[copyTypeSymbol]) { + killRecursive(child); + } + } + }; + const manageCamera = () => { + if (ct.camera) { + ct.camera.update(ct.delta); + ct.camera.manageStage(); + } + }; - array.length = j; - return array; -}; -const killRecursive = copy => { - copy.kill = true; - ct.types.onDestroy.apply(copy); - copy.onDestroy.apply(copy); - for (const child of copy.children) { - if (child[copyTypeSymbol]) { - killRecursive(child); + ct.loop = function(delta) { + ct.delta = delta; + ct.inputs.updateActions(); + for (let i = 0, li = ct.stack.length; i < li; i++) { + ct.types.beforeStep.apply(ct.stack[i]); + ct.stack[i].onStep.apply(ct.stack[i]); + ct.types.afterStep.apply(ct.stack[i]); } - } -}; -ct.loop = function(delta) { - ct.delta = delta; - ct.inputs.updateActions(); - for (let i = 0, li = ct.stack.length; i < li; i++) { - ct.types.beforeStep.apply(ct.stack[i]); - ct.stack[i].onStep.apply(ct.stack[i]); - ct.types.afterStep.apply(ct.stack[i]); - } + // There may be a number of rooms stacked on top of each other. + // Loop through them and filter out everything that is not a room. + for (const item of ct.stage.children) { + if (!(item instanceof Room)) { + continue; + } + ct.rooms.beforeStep.apply(item); + item.onStep.apply(item); + ct.rooms.afterStep.apply(item); + } + // copies + for (let i = 0; i < ct.stack.length; i++) { + // eslint-disable-next-line no-underscore-dangle + if (ct.stack[i].kill && !ct.stack[i]._destroyed) { + killRecursive(ct.stack[i]); // This will also allow a parent to eject children to a new container before they are destroyed as well + ct.stack[i].destroy({children: true}); + } + } + for (const copy of ct.stack) { + // eslint-disable-next-line no-underscore-dangle + if (copy._destroyed) { + deadPool.push(copy); + } + } + removeKilledCopies(ct.stack); - ct.rooms.beforeStep.apply(ct.room); - ct.room.onStep.apply(ct.room); - ct.rooms.afterStep.apply(ct.room); - // copies - for (let i = 0; i < ct.stack.length; i++) { - // eslint-disable-next-line no-underscore-dangle - if (ct.stack[i].kill && !ct.stack[i]._destroyed) { - killRecursive(ct.stack[i]); // This will also allow a parent to eject children to a new container before they are destroyed as well - ct.stack[i].destroy({children: true}); + // ct.types.list[type: String] + for (const i in ct.types.list) { + removeKilledCopies(ct.types.list[i]); } - } - for (const copy of ct.stack) { - // eslint-disable-next-line no-underscore-dangle - if (copy._destroyed) { - deadPool.push(copy); + + for (const cont of ct.stage.children) { + cont.children.sort((a, b) => + ((a.depth || 0) - (b.depth || 0)) || ((a.uid || 0) - (b.uid || 0)) || 0 + ); } - } - removeKilledCopies(ct.stack); - // ct.types.list[type: String] - for (const i in ct.types.list) { - removeKilledCopies(ct.types.list[i]); - } + manageCamera(); - for (const cont of ct.stage.children) { - cont.children.sort((a, b) => - ((a.depth || 0) - (b.depth || 0)) || ((a.uid || 0) - (b.uid || 0)) || 0 - ); - } - const r = ct.room; - if (r.follow) { - const speed = Math.min(1, (1-r.followDrift)*ct.delta); - if (r.follow.kill) { - delete r.follow; - } else if (r.center) { - r.x += speed * (r.follow.x + r.followShiftX - r.x - ct.viewWidth / 2); - r.y += speed * (r.follow.y + r.followShiftY - r.y - ct.viewHeight / 2); - } else { - let cx = 0, - cy = 0, - w = 0, - h = 0; - w = Math.min(r.borderX, ct.viewWidth / 2); - h = Math.min(r.borderY, ct.viewHeight / 2); - if (r.follow.x + r.followShiftX - r.x < w) { - cx = r.follow.x + r.followShiftX - r.x - w; - } - if (r.follow.y + r.followShiftY - r.y < h) { - cy = r.follow.y + r.followShiftY - r.y - h; - } - if (r.follow.x + r.followShiftX - r.x > ct.viewWidth - w) { - cx = r.follow.x + r.followShiftX - r.x - ct.viewWidth + w; - } - if (r.follow.y + r.followShiftY - r.y > ct.viewHeight - h) { - cy = r.follow.y + r.followShiftY - r.y - ct.viewHeight + h; - } - r.x = Math.floor(r.x + speed * cx); - r.y = Math.floor(r.y + speed * cy); + for (let i = 0, li = ct.stack.length; i < li; i++) { + ct.types.beforeDraw.apply(ct.stack[i]); + ct.stack[i].onDraw.apply(ct.stack[i]); + ct.types.afterDraw.apply(ct.stack[i]); + ct.stack[i].xprev = ct.stack[i].x; + ct.stack[i].yprev = ct.stack[i].y; } - } - r.x = r.x || 0; - r.y = r.y || 0; - r.x = Math.round(r.x); - r.y = Math.round(r.y); - // console.log("loop") - for (let i = 0, li = ct.stack.length; i < li; i++) { - // console.log(ct.stack[i].type); - ct.types.beforeDraw.apply(ct.stack[i]); - ct.stack[i].onDraw.apply(ct.stack[i]); - ct.types.afterDraw.apply(ct.stack[i]); - ct.stack[i].xprev = ct.stack[i].x; - ct.stack[i].yprev = ct.stack[i].y; - } + for (const item of ct.stage.children) { + if (!(item instanceof Room)) { + continue; + } + ct.rooms.beforeDraw.apply(item); + item.onDraw.apply(item); + ct.rooms.afterDraw.apply(item); + } - ct.rooms.beforeDraw.apply(r); - ct.room.onDraw.apply(r); - ct.rooms.afterDraw.apply(r); + ct.main.fpstick++; + if (ct.rooms.switching) { + ct.rooms.forceSwitch(); + } + }; +})(); - ct.main.fpstick++; - if (ct.rooms.switching) { - ct.rooms.forceSwitch(); - } -}; /*%load%*/ diff --git a/app/data/ct.release/rooms.js b/app/data/ct.release/rooms.js index 551fc4593..7ee3fe596 100644 --- a/app/data/ct.release/rooms.js +++ b/app/data/ct.release/rooms.js @@ -1,175 +1,293 @@ -class Room extends PIXI.Container { - constructor(template) { - super(); - this.x = this.y = 0; - this.uid = 0; - this.follow = this.borderX = this.borderY = this.followShiftX = this.followShiftY = this.followDrift = 0; - this.tileLayers = []; - this.backgrounds = []; - if (!ct.room) { - ct.room = ct.rooms.current = this; - } - if (template) { - this.onCreate = template.onCreate; - this.onStep = template.onStep; - this.onDraw = template.onDraw; - this.onLeave = template.onLeave; - this.template = template; - this.name = template.name; - for (let i = 0, li = template.bgs.length; i < li; i++) { - const bg = new ct.types.Background(template.bgs[i].texture, null, template.bgs[i].depth, template.bgs[i].extends); - this.backgrounds.push(bg); - ct.stack.push(bg); - this.addChild(bg); - } - for (let i = 0, li = template.tiles.length; i < li; i++) { - const tl = ct.rooms.addTileLayer(template.tiles[i]); - this.tileLayers.push(tl); - this.addChild(tl); - } - for (let i = 0, li = template.objects.length; i < li; i++) { - ct.types.make(template.objects[i].type, template.objects[i].x, template.objects[i].y, { - tx: template.objects[i].tx, - ty: template.objects[i].ty - }, this); - } - } - return this; - } - get x () { - return -this.position.x; - } - set x (value) { - this.position.x = -value; - return value; - } - get y () { - return -this.position.y; - } - set y (value) { - this.position.y = -value; - return value; - } -} -(function () { - /* global deadPool */ - var nextRoom; - /** - * @namespace - */ - ct.rooms = { - templates: {}, - /** - * Creates and adds a background to the current room, at the given depth. - * @param {string} texture The name of the texture to use - * @param {number} depth The depth of the new background - * @returns {Background} The created background - */ - addBg(texture, depth) { - const bg = new ct.types.Background(texture, null, depth); - ct.room.addChild(bg); - return bg; - }, - /** - * Adds a new empty tile layer to the room, at the given depth - * @param {number} layer The depth of the layer - * @returns {Tileset} The created tile layer - */ - addTileLayer(layer) { - return new ct.types.Tileset(layer); - }, - /** - * Clears the current room - * @return {void} - */ - clear() { - ct.stage.children = []; - ct.stack = []; - for (var i in ct.types.list) { - ct.types.list[i] = []; - } - }, - /* - * Switches to the given room. Note that this transition happens at the end - * of the frame, so the name of a new room may be overridden. - */ - 'switch'(room) { - if (ct.rooms.templates[room]){ - nextRoom = room; - ct.rooms.switching = true; - } else { - console.error('[ct.rooms] The room "' + room + '" does not exist!'); - } - }, - switching: false, - /** - * Loads a given room and adds it to the stage. Useful for embedding prefabs and UI screens. - * @param {string} roomName The name of a room to add to the stage - * @returns {Room} The newly created room - */ - load(roomName) { - const room = new Room(ct.rooms.templates[roomName]); - ct.stage.addChild(ct.room); - return room; - }, - forceSwitch(roomName) { - if (nextRoom) { - roomName = nextRoom; - } - if (ct.room) { - ct.room.onLeave(); - ct.rooms.onLeave.apply(ct.room); - ct.room = void 0; - } - ct.rooms.clear(); - deadPool.length = 0; - var template = ct.rooms.templates[roomName]; - ct.viewWidth = ct.roomWidth = template.width; - ct.viewHeight = ct.roomHeight = template.height; - ct.pixiApp.renderer.resize(template.width, template.height); - ct.rooms.current = ct.room = new Room(template); - ct.room.onCreate(); - ct.rooms.onCreate.apply(ct.room); - /*%switch%*/ - ct.rooms.switching = false; - ct.stage.addChild(ct.room); - nextRoom = void 0; - }, - onCreate() { - /*%roomoncreate%*/ - }, - onLeave() { - /*%roomonleave%*/ - }, - /** - * The name of the starting room, as it was set in ct.IDE. - * @type {string} - */ - starting: '@startroom@', - /** - * The current room, same as `ct.room` - * @type {Room} - */ - current: null - }; -})(); -/** - * The current room - * @type {Room} - */ -ct.room = null; - -ct.rooms.beforeStep = function () { - /*%beforeroomstep%*/ -}; -ct.rooms.afterStep = function () { - /*%afterroomstep%*/ -}; -ct.rooms.beforeDraw = function () { - /*%beforeroomdraw%*/ -}; -ct.rooms.afterDraw = function () { - /*%afterroomdraw%*/ -}; - -/*@rooms@*/ +/** + * @typedef IRoomMergeResult + * + * @property {Array} copies + * @property {Array} tileLayers + * @property {Array} backgrounds + */ + +class Room extends PIXI.Container { + constructor(template) { + super(); + this.x = this.y = 0; + this.uid = 0; + this.tileLayers = []; + this.backgrounds = []; + if (!ct.room) { + ct.room = ct.rooms.current = this; + } + if (template) { + this.onCreate = template.onCreate; + this.onStep = template.onStep; + this.onDraw = template.onDraw; + this.onLeave = template.onLeave; + this.template = template; + this.name = template.name; + for (let i = 0, li = template.bgs.length; i < li; i++) { + const bg = new ct.types.Background(template.bgs[i].texture, null, template.bgs[i].depth, template.bgs[i].extends); + this.backgrounds.push(bg); + ct.stack.push(bg); + this.addChild(bg); + } + for (let i = 0, li = template.tiles.length; i < li; i++) { + const tl = ct.rooms.addTileLayer(template.tiles[i]); + this.tileLayers.push(tl); + this.addChild(tl); + } + for (let i = 0, li = template.objects.length; i < li; i++) { + ct.types.make(template.objects[i].type, template.objects[i].x, template.objects[i].y, { + tx: template.objects[i].tx, + ty: template.objects[i].ty + }, this); + } + } + return this; + } + get x () { + return -this.position.x; + } + set x (value) { + this.position.x = -value; + return value; + } + get y () { + return -this.position.y; + } + set y (value) { + this.position.y = -value; + return value; + } +} +(function () { + /* global deadPool */ + var nextRoom; + /** + * @namespace + */ + ct.rooms = { + templates: {}, + list: {}, + /** + * Creates and adds a background to the current room, at the given depth. + * @param {string} texture The name of the texture to use + * @param {number} depth The depth of the new background + * @returns {Background} The created background + */ + addBg(texture, depth) { + const bg = new ct.types.Background(texture, null, depth); + ct.room.addChild(bg); + return bg; + }, + /** + * Adds a new empty tile layer to the room, at the given depth + * @param {number} layer The depth of the layer + * @returns {Tileset} The created tile layer + */ + addTileLayer(layer) { + return new ct.types.Tileset(layer); + }, + /** + * Clears the current stage, removing all rooms with copies, tile layers, backgrounds, + * and other potential entities. + * @returns {void} + */ + clear() { + ct.stage.children = []; + ct.stack = []; + for (var i in ct.types.list) { + ct.types.list[i] = []; + } + ct.rooms.list = {}; + for (const name in ct.rooms.templates) { + ct.rooms.list[name] = []; + } + }, + /** + * This method safely removes a previously appended/prepended room from the stage. + * It will trigger "On Leave" for a room and "On Destroy" event for all the copies of the removed room. + * The room will also have `this.kill` set to `true` in its event, if it comes in handy. + * This method cannot remove `ct.room`, the main room. + * @param {Room} room The `room` argument must be a reference to the previously created room. + * @returns {void} + */ + remove(room) { + if (!(room instanceof Room)) { + if (typeof room === 'string') { + throw new Error('[ct.rooms] To remove a room, you should provide a reference to it (to an object), not its name. Provided value:', room); + } + throw new Error('[ct.rooms] An attempt to remove a room that is not actually a room! Provided value:', room); + } + const ind = ct.rooms.list[room.name]; + if (ind !== -1) { + ct.rooms.list[room.name].splice(ind, 1); + } else { + // eslint-disable-next-line no-console + console.warn('[ct.rooms] Removing a room that was not found in ct.rooms.list. This is strange…'); + } + room.kill = true; + ct.stage.removeChild(room); + for (const copy of room.children) { + copy.kill = true; + } + room.onLeave(); + ct.rooms.onLeave.apply(room); + }, + /* + * Switches to the given room. Note that this transition happens at the end + * of the frame, so the name of a new room may be overridden. + */ + 'switch'(roomName) { + if (ct.rooms.templates[roomName]) { + nextRoom = roomName; + ct.rooms.switching = true; + } else { + console.error('[ct.rooms] The room "' + roomName + '" does not exist!'); + } + }, + switching: false, + /** + * Creates a new room and adds it to the stage, separating its draw stack from existing ones. + * This room is added to `ct.stage` after all the other rooms. + * @param {string} roomName The name of the room to be appended + * @param {object} [exts] Any additional parameters applied to the new room. Useful for passing settings and data to new widgets and prefabs. + * @returns {Room} A newly created room + */ + append(roomName, exts) { + if (!(roomName in ct.rooms.templates)) { + console.error(`[ct.rooms] append failed: the room ${roomName} does not exist!`); + return false; + } + const room = new Room(ct.rooms.templates[roomName]); + if (exts) { + ct.u.ext(room, exts); + } + ct.stage.addChild(room); + room.onCreate(); + ct.rooms.onCreate.apply(room); + ct.rooms.list[roomName].push(room); + return room; + }, + /** + * Creates a new room and adds it to the stage, separating its draw stack from existing ones. + * This room is added to `ct.stage` before all the other rooms. + * @param {string} roomName The name of the room to be prepended + * @param {object} [exts] Any additional parameters applied to the new room. Useful for passing settings and data to new widgets and prefabs. + * @returns {Room} A newly created room + */ + prepend(roomName, exts) { + if (!(roomName in ct.rooms.templates)) { + console.error(`[ct.rooms] prepend failed: the room ${roomName} does not exist!`); + return false; + } + const room = new Room(ct.rooms.templates[roomName]); + if (exts) { + ct.u.ext(room, exts); + } + ct.stage.addChildAt(room, 0); + room.onCreate(); + ct.rooms.onCreate.apply(room); + ct.rooms.list[roomName].push(room); + return room; + }, + /** + * Merges a given room into the current one. Skips room's OnCreate event. + * + * @param {string} roomName The name of the room that needs to be merged + * @returns {IRoomMergeResult} Arrays of created copies, backgrounds, tile layers, + * added to the current room (`ct.room`). Note: it does not get updated, so beware of memory leaks + * if you keep a reference to this array for a long time! + */ + merge(roomName) { + if (!(roomName in ct.rooms.templates)) { + console.error(`[ct.rooms] merge failed: the room ${roomName} does not exist!`); + return false; + } + const generated = { + copies: [], + tileLayers: [], + backgrounds: [] + }; + const template = ct.rooms.templates[roomName]; + const target = ct.room; + for (const t of template.bgs) { + const bg = new ct.types.Background(t.texture, null, t.depth, t.extends); + target.backgrounds.push(bg); + ct.stack.push(bg); + target.addChild(bg); + generated.backgrounds.push(bg); + } + for (const t of template.tiles) { + const tl = ct.rooms.addTileLayer(t); + target.tileLayers.push(tl); + target.addChild(tl); + generated.tileLayers.push(tl); + } + for (const t of template.objects) { + const c = ct.types.make(t.type, t.x, t.y, { + tx: t.tx || 1, + ty: t.ty || 1 + }, target); + generated.copies.push(c); + } + return generated; + }, + forceSwitch(roomName) { + if (nextRoom) { + roomName = nextRoom; + } + if (ct.room) { + ct.room.onLeave(); + ct.rooms.onLeave.apply(ct.room); + ct.room = void 0; + } + ct.rooms.clear(); + deadPool.length = 0; + var template = ct.rooms.templates[roomName]; + ct.roomWidth = template.width; + ct.roomHeight = template.height; + ct.camera = new Camera(ct.roomWidth / 2, ct.roomHeight / 2, ct.roomWidth, ct.roomHeight); + ct.pixiApp.renderer.resize(template.width, template.height); + /*%beforeroomoncreate%*/ + ct.rooms.current = ct.room = new Room(template); + ct.stage.addChild(ct.room); + ct.room.onCreate(); + ct.rooms.onCreate.apply(ct.room); + ct.rooms.list[roomName].push(ct.room); + /*%switch%*/ + ct.camera.manageStage(); + ct.rooms.switching = false; + nextRoom = void 0; + }, + onCreate() { + /*%roomoncreate%*/ + }, + onLeave() { + /*%roomonleave%*/ + }, + /** + * The name of the starting room, as it was set in ct.IDE. + * @type {string} + */ + starting: '@startroom@' + }; +})(); +/** + * The current room + * @type {Room} + */ +ct.room = null; + +ct.rooms.beforeStep = function () { + /*%beforeroomstep%*/ +}; +ct.rooms.afterStep = function () { + /*%afterroomstep%*/ +}; +ct.rooms.beforeDraw = function () { + /*%beforeroomdraw%*/ +}; +ct.rooms.afterDraw = function () { + /*%afterroomdraw%*/ +}; + +/*@rooms@*/ diff --git a/app/data/ct.release/types.js b/app/data/ct.release/types.js index b2214ddf4..f5dc30f73 100644 --- a/app/data/ct.release/types.js +++ b/app/data/ct.release/types.js @@ -4,8 +4,8 @@ class Background extends PIXI.TilingSprite { constructor(bgName, frame, depth, exts) { exts = exts || {}; - var width = ct.viewWidth, - height = ct.viewHeight; + var width = ct.camera.width, + height = ct.camera.height; if (exts.repeat === 'no-repeat' || exts.repeat === 'repeat-x') { height = ct.res.getTexture(bgName, frame || 0).orig.height * (exts.scaleY || 1); } @@ -27,25 +27,32 @@ class Background extends PIXI.TilingSprite { if (this.scaleY) { this.tileScale.y = Number(this.scaleY); } + this.reposition(); } onStep() { this.shiftX += ct.delta * this.movementX; this.shiftY += ct.delta * this.movementY; } - onDraw() { + reposition() { + const cameraBounds = ct.camera.getBoundingBox(); + this.width = cameraBounds.width; + this.height = cameraBounds.height; if (this.repeat !== 'repeat-x' && this.repeat !== 'no-repeat') { - this.y = ct.room.y; + this.y = cameraBounds.y; this.tilePosition.y = -this.y*this.parallaxY + this.shiftY; } else { - this.y = this.shiftY + ct.room.y * (this.parallaxY - 1); + this.y = this.shiftY + cameraBounds.y * (this.parallaxY - 1); } if (this.repeat !== 'repeat-y' && this.repeat !== 'no-repeat') { - this.x = ct.room.x; + this.x = cameraBounds.x; this.tilePosition.x = -this.x*this.parallaxX + this.shiftX; } else { - this.x = this.shiftX + ct.room.x * (this.parallaxX - 1); + this.x = this.shiftX + cameraBounds.x * (this.parallaxX - 1); } } + onDraw() { + this.reposition(); + } static onCreate() { void 0; } @@ -293,6 +300,18 @@ const Copy = (function () { this.hspeed += spd * Math.cos(dir*Math.PI/-180); this.vspeed += spd * Math.sin(dir*Math.PI/-180); } + + /** + * Returns the room that owns the current copy + * @returns {Room} The room that owns the current copy + */ + getRoom() { + let parent = this.parent; + while (!(parent instanceof Room)) { + parent = parent.parent; + } + return parent; + } } return Copy; })(); diff --git a/projects/cameraTest.ict b/projects/cameraTest.ict new file mode 100644 index 000000000..88f6f0818 --- /dev/null +++ b/projects/cameraTest.ict @@ -0,0 +1,244 @@ +{ + "ctjsVersion": "1.2.1", + "notes": "/* empty */", + "libs": { + "place": { + "gridX": 512, + "gridY": 512 + }, + "fittoscreen": { + "mode": "scaleFit" + }, + "mouse": {}, + "keyboard": {}, + "sound.howler": {}, + "akatemplate": { + "csscss": "body {\n background: #000;\n}" + } + }, + "textures": [ + { + "name": "logo", + "untill": 0, + "grid": [ + 1, + 1 + ], + "axis": [ + 41, + 41 + ], + "marginx": 0, + "marginy": 0, + "imgWidth": 83, + "imgHeight": 83, + "width": 83, + "height": 83, + "offx": 0, + "offy": 0, + "origname": "if0f9ff12-35e9-485f-ad49-b87a493be343.png", + "source": "/home/comigo/Downloads/logo.png", + "shape": "rect", + "left": 41, + "right": 42, + "top": 41, + "bottom": 42, + "uid": "f0f9ff12-35e9-485f-ad49-b87a493be343", + "lastmod": 1580434023252 + } + ], + "skeletons": [], + "types": [ + { + "name": "Type_5f7fb46e93d4", + "depth": 0, + "oncreate": "", + "onstep": "this.move();", + "ondraw": "", + "ondestroy": "", + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4", + "texture": "f0f9ff12-35e9-485f-ad49-b87a493be343", + "extends": {}, + "lastmod": 1580434013170 + } + ], + "sounds": [], + "styles": [], + "rooms": [ + { + "name": "Room_cf2550d1386f", + "oncreate": "", + "onstep": "ct.camera.x += ct.actions.MoveX.value * 10 * ct.delta;\nct.camera.y += ct.actions.MoveY.value * 10 * ct.delta;\nct.camera.angle += ct.actions.Rotate.value * 3 * ct.delta;\nct.camera.scale.x *= 1 + ct.actions.Scale.value * 0.1;\nct.camera.scale.y = ct.camera.scale.x;", + "ondraw": "", + "onleave": "", + "width": 800, + "height": 600, + "backgrounds": [], + "copies": [ + { + "x": 64, + "y": 64, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 64, + "y": 512, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 704, + "y": 512, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 704, + "y": 64, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": 192, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 256, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 512, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": 448, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 960, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 1088, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": -64, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": -192, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": -64, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": -192, + "y": 320, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": 704, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + }, + { + "x": 384, + "y": 832, + "uid": "e6ca346a-5510-4035-a059-5f7fb46e93d4" + } + ], + "tiles": [ + { + "depth": -10, + "tiles": [] + } + ], + "uid": "66a9478e-66eb-43b4-a7b9-cf2550d1386f", + "thumbnail": "cf2550d1386f", + "gridX": 64, + "gridY": 64 + } + ], + "actions": [ + { + "name": "MoveX", + "methods": [ + { + "code": "keyboard.KeyD" + }, + { + "code": "keyboard.KeyA", + "multiplier": -1 + } + ] + }, + { + "name": "MoveY", + "methods": [ + { + "code": "keyboard.KeyW", + "multiplier": -1 + }, + { + "code": "keyboard.KeyS" + } + ] + }, + { + "name": "Rotate", + "methods": [ + { + "code": "mouse.Wheel" + } + ] + }, + { + "name": "Scale", + "methods": [ + { + "code": "keyboard.KeyQ", + "multiplier": -1 + }, + { + "code": "keyboard.KeyE" + } + ] + } + ], + "starting": 0, + "settings": { + "minifyhtmlcss": false, + "minifyjs": false, + "fps": 30, + "version": [ + 0, + 0, + 0 + ], + "versionPostfix": "", + "export": { + "windows64": true, + "windows32": true, + "linux64": true, + "linux32": true, + "mac64": true, + "debug": false + } + }, + "scripts": [], + "fonts": [] +} diff --git a/projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png b/projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png new file mode 100644 index 0000000000000000000000000000000000000000..176fd8188ec110119ac8782c74b0917d6d56989b GIT binary patch literal 3185 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 literal 0 HcmV?d00001 diff --git a/projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png_prev@2.png b/projects/cameraTest/img/if0f9ff12-35e9-485f-ad49-b87a493be343.png_prev@2.png new file mode 100644 index 0000000000000000000000000000000000000000..5f94a18c65028c4387bff72bc533a13ce06481f1 GIT binary patch literal 7983 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 literal 0 HcmV?d00001 diff --git a/projects/cameraTest/img/rcf2550d1386f.png b/projects/cameraTest/img/rcf2550d1386f.png new file mode 100644 index 0000000000000000000000000000000000000000..1b09cafe6a366a81f85b8157bc93174a2545129b GIT binary patch literal 7715 zcmXYWbyO7Z_y6p&$kIy+NGwY$2q-DFG)ULdAzjkau}HXtfJiD0BB6BW0t!fX3KF7( zNJ!^*-=E*_k2y2v%$)n2xzBU&D{hRorZOoJ0}%iKq^c?kx&Q#eJ%Rw}9o)^#tHd4v zn6_0FWcB?mcFi?>?}^raF5uy*?SD`eoKRYtN}~{}-v%f3x{Kg@Y=$+aN7cUt?4)+%aMM4OvBW1j$mMmIvM z8rk}=;Q$N{1L6UwKkmZXY}jlf0SIsp05h`z;C-WWx+gFIguR1C_d(HsBuYA(41xrz zK=2hoFdR+vAndeJ%k%}=GHmi8A(NlP>vS6ChtX+xAQ+Nt%(b#6=*+41M%Z6b{gqw~ z*_P_*i}h~bbJkpOT#R2;A`^PG$D`8KZO;mZ{a+!j{cpZv`cN>Cq&==d%NKvvQ^?28O6!|Ve@k0ZwH;Kvbhv*ZK`E0$=2zb{OF915?B42_(D7H7xZnOh%gRGwwYxZc4g#D(xwey8bqsSw^=ABS^T^Z%vj%4`nTi|E0XQQC(aEOnwjV4*^oKT zl#e8XfpRTf=Ix$Cn)v5z=R*~y?-gdMleiODtj>h*PhF6JtvwYnk5^J@P|ViO!K zS#qvJ0I_6e;P@FyiE6@DYjpyc!t zVjA6AK^U4-yZm88*|*A{3DHEv)7v~ytJisBrfUVFvIrql9W~tUbkLRTozEyZ} zvGs9OhrnQknvyLjaslz@+Woc^^QSL_uW(wzJU3V9?~YJ0%0PELgfl>pXK(F<2$EO$ z9M5h_1Rdq;qTZKr^%buIaX%(nd8lpd_6>&;BD7Bqb1NwmAe4=%Kg*|HKtS`f#l|yU zL)pcR*1gIqMaj3|YBN92P}us7&Tar=Rrpn|SdIe25}}m-hu3F__ig~ny!!K^UE^d% z>cBk41oL7pngIJ7mZSvnpVPiw0!<%4FuJVB-vniC?hHV?i z0)0hgcC*ioOcya^rN-`eiaGsfSlE!Zlpmc}v@{Ck6YS%sA~)NmbUpPorsci|5-69O zS@Z4T>T&MkdQK5lm)01xd-DbsW#LJytNcug_3yO*nO_b^Lqjlk#ywnNxoy@M=v$go z-WtZ+i^T4`aTS@xw_eeU5v)I?9?)usz;s{n@c39A}L&o6X317M1@z zK@VRRIQN*WNImjU7WgDHm94+PWUMW46idZKF`3A)_H)2p;CStziApl4IQ%3F0Y_iW zIZc0eu?)EiHRX*LDZjGI+HYX|@mC%=x~ zNS4!Xc;PM-{=k}hj|r?7BcoJUsV+$X#Y%YhqH7VbMCB4)AucGqT-XQ|8ccc`g<&j_ zD#MUqocYVEWa{y^?OSCq%XTFbb`N1_K2(}~0zP?+Zfd*`flw4G2A|!){=5ihMYzxGBCKmwODBArZy~5vP=LkFjO5Y6N~yca?(RmRk%q+0e~D|zR%cD z{vXUCYhymdaYPnzHW>dRM2{4*8N?J?n&i_80pKxxxe3GJqenqqHG$65x@#K3*?dG$ zEBU+fvYFnb9I zsfYb~QZklqRoG!S!A*HhPFR!Pd^NjNFsBJTE|-c3WhV9Jt%E|5R3n=c4e@Lu*&phi zKH4%q`OFuKm+1dRD^UGOpzv^Yl{FkqqFvVJAB~RzmSw7LJBy95#eGst!3R-? zuf;BoUAMfGPR-y%Z%O?72j|gKViM+Xt$)g9z!Z?qhX*#ImBl-*?+#P@Zn~3o{UBqi zkeRcP^Iu7EZ1e5iRRG*?!x6o+=|4Ltc{%&Wog{PGaXv$ODGida%i_r+TJt*)n0(!QM_}RKs&Z2o z;bI57tna`6h=Sd z?_G}0V_*VT$!acg=I|Y&fda_bb{M>P>077kK)EQDBbyJs>(uGsa@pp0os#ay3k~XX z1seb;oW>+X7P9$1#_k%e1w`2qToa%FJ0*C-BdTy2_y4>NW z6kDiQ~20S8i_h$&ne=GTTqkeeU)G@mY3)o3uo zki1dC9cJh1LT0L-RV*skf7XtBWIIqpv%01k`TGy7n>&~)uKyo<>QP8*1HVjNP5-R2?y@+QaqwlhV$I?oDis? zK$K7`nvw#I9g2+k+aP~%UJ3+UwXA=Pudn>h#WJE#RQLAJVnBoR)57GA54Sn568#`~ zX`x4W=IJ4c(&b&nsfOzb=f zbLDp8=T6=A0tcpMNfSZ@#P(ce%xtX))BQhzJp+M)E)t@nFn|~PiTo+h|3MV%&xR+hojs58!X@G6J(a( z8Fw{5&N%m^J%5A8N^CNH-!uTM5_?uw(Zr^;W!@qmaX0l7g4=gAFioNOAr zwiGK!6B1kg%_2A+!Tx5-&@v;T+=tqk@JKUe6$CoEef>8mWbllt0?j?gFk>6{!FL!j z+eLnCF%8bodiR2s5Vr|1CWm+^C{$9Pi2ZID1Cz#4FQ=dR)_$Pk zd~Q^y+fptG;?_4<>wCYtApEdD#oA`Z=LyXfyU+NpP>vCsnd(b_S%BkNoEx9KRppWl zTfs*^>wrKIcNr}VZKp)!iFyS*0*0-L!A9Bb`F3$I=sH^m{;iT?*@RtHn|1_nhof{&mKtZ&uiQfE{jA8A@pBApqpVqJS?)7MsL@^L#QvBAc6`2QxWLbj#)K^MV9*jUpet6x`JcBs)SVHb2fsmN2>#!|f z20!=P^Kw)?BXpB>MH+N9^Mj)pSH%>TVNxFosbLxL6-f{>6$vK||4-(q*T~E5{n!4S zHbO3#AS-Vhq!?YU%vG{r8uvg@Y>(>dWYYRa1q=)ybY^?IoJbP@(7p#yz_QjGXFLE5 zfbPtmvY~rK;SX zPg?Tui*e+mG2^>HRbswu44U2#fD+~t({QrMV&$x96M*FFDaBpH;JZ4TKG!5 zwh0?%U?TtzvaNg}0-QS2424Vpuqsqc79iwrwLT{gx&y|CuiOEBnFxJ$Zi)Ui4uN@a z@SP3yB<4S`8eql4l4cOHkAVe>oEOm` zyl`un$4elV{=NwajD#luP(Vb-2{N^PV6pql>GdXMBj8{_3NEQTzm#h7KNC9yy#%gu zC~{s)GfQhWNs8Ejj6nV(Ji!cqEm|wxVmo5v=^K@8?%-?yCnbLo6b#wYEemYX2ehN|PXNu?3G1BHhkr(>3J$-dl@mnDSbAln&Fgc6G{cpWSXv%UFNrkBFPCh$)% zoNst8+5nQ2az-^OZX^9$5#Es<$v2E}q5;7V94z#d+XJAHEUCA5Byc@~IqO>*6>xZC zgTMpTLXGOd7>66#<*?db^zxNFmH~{Bx#UpD9o1qm#Q`7N9q=z$RWz{X(6G9m%auqr zEE}GKMu$O-)DVGC@;f0cf87+4y-G#o33DdbTfGivAw0o_kU+!;2{hK%W-Cb7)4-ntkC-qgq0*E}<3mnLIycUGcX)nbTR2$myi{gO{O zUZ2!fw@Y21El|V#B3Sy8f_Z_rB;OSUL1_R7rk_d=N&jc)AnLR7!5=7qSbs^3dL zSQ$dq8rqORLxrD)n!Jam>>c#IdiTFWEpZsr)WsSVu8pGABorV?SVym zo1gV5=(GG9SbkAxY_NTrcJnoMOpiI0*RAq+Z@NsBx0KFZr81Y%H>Az=U*o`$vE0N# zK6SxL&fRvCeZlRq2irYG3myK~9!Z>)TR+rT(M^7|^I_`cOSw1tx?>BC?>z!?exc*u zM0u%)5sGY^K3N0|DfnIiQ#mnPiBL!Xs~!X5vZU{xVIT^F8}S1#Xx{&T;0+ZzeMxh@ zNvUo09sR7y=j6JqPg#{L0@tX;YlZ5frj;J>HQ!=GvYGWsbTZGX8PC3lU>{^!!? z%Ek}@s{(@j7$<-G>qm<54ZM&6b+sR^mhI2hBF0k$(qWC%U}&OAWMZE(o6$|W-NB|G zQFuElY}@?l3ljg<>XWxIm7ZC_>e;cWazK>?+=>l+Ff_GJeO0klx&4KIe7}!^`ZMdK zEtxI+9T%;MtQbOROe#TtIM9{>z9E}X&`siQS-+Xu#qNc>6@p8@P?0N{{CbpJ(JKqI zsN`q8g$^m5sa6KnTu+t}JoYY9@d*i?Xv&@08}eKUybfI$VEsI-*ckz&$&$awM#8l; z(RKtPClW^mno`+gYiGjedCrhLI^#apnonSD7N|OmI|9)EQ7zRz6Ds}gnTo=4j$cF1 zCf4AZ-V_wsMQQ0S9W4$-)g>yUggxqZ$ick_2eEmtHwM8I3@2DHK_fPs>4LXn0 zfxhUWWA-0Qf;v83Xj>A5XL<+X{oZ-vHLu;v2wEYkBE2}#y#vDr*S}{&&nXgviG#%H z2kC^M*gIg%Z@{eDua-n!KGzo^aIZXZ_qT$c5(;>h^{;W|M@hgYW6YeBX3~H#q}UoH z+5azHQAP=h^$|kR0@^d=knZ@-;8t&3GqFw$7p}-B1bb-MB;AElW6<@Sna-C(B=qJJPhhiEwA=$z8TIdg^U(cnu6{NlY6k8gPW-%I{a0 zk#P7xl(JYN^SIhTP!2dCpsq#hrXj}pVg0el3BA>p)iy%on0mQawF7kg?va50$907~ zf)-;`8WY_%9_TH!gpgM_@UDhm<;84>iEHKbEmFOeB2%xD{H~z14MaP#$`KDb7mXF8 z=r5o9N&3}nZKpD$r~mO999ZNfIur}X&W)e;v8RW<99yv5Z$FtHt3J*JYYVvPr7gyBceu2x3xdHMuM>Vi#k>= zeaNfi{T^bA2|TI%6Apy^jxc1)RVC#tvM6rJ0fm>&UU&9h3V9T3Hbg#D-Q=Fk4x`C5 zB9>Oljz%1w?)ENJ#H@FIJ3|6a?|S!eY$YerdOXi3vDCo-z+~56?=+5Ow|4&KcZO-QuJsT=5o(#fQYhkXQW*v)|Uh=p+hgQ#_Iff{w>N zd{1I)8Hwe-zTVZdpC_8x30vsM7LW(3gMlWpcvXF2NUzhL1$2D%v^M0jvDF@$H!Z_n zEW`O!lzzudSiZ;qkDQeceuf3?AoPkeFL~U=}QfTJ;k%BuZ<61Q{aep8ivG8bSAVWBqowaTM82DbR5P^>0Vm+gmQtVSb&~@ z__~5BS;FgTn#{;{WnxxZ^=&WIs*R`#UYyJ3QXJ>@@t`P>Gn!%^gUo15+!&VZ%ZB~W z9A4zUEVF&|6xfzVw$ld3k}65Om?%RcTSMV6i;icZ9gcMkEQ>Usc?uy(O+?VQTx-?6 z1vGId_7s@k%kS^A9w}nT&A#{X7rLxN1o~nMvZVtuy9z2gp+lx2u^yGZJ>gplj17+6 zVxW@n(ISU;d8|I)cZDn#z+^v5$iN5d5;R5L%kv$BUre*#*xys-f#fM-APzGJymlf?=E^9-b)Pq{kJ%Q~B**J957@yP^B9;7?avzU~Hww!=>9+stvcX-`o1?w5acbph#UFN%y1+da7G~%- zS)oO3620#tyz~8OyV5ra%R|niUFwp9*1Gy39|_?^FI)cTlrR06QF8yh^qIuRi#+TTCdwP2ogS>jXKN{z3jPNT8yP$Afa$85UVC7Yy z7FU75bxzZf%9)ALh}-P!@^+oEhW9to`}Mk}mkg53HGiu1(}>Ox#YvCCb-JNHnL4n8~Xt z)v)3P8qn{a*jI_s%-jr5c;ZvjTVnTsxLtSIIn_BfDzNPNGkO6}x;A$9SrHav&5rL> z>OPjf37z=OFg^d>7wT?yIAe~&k%ofArN)3Qhdu85J63#+i02Iz(tLqJh%%l3uS4IZ zrB<%tv;+_Fo^p(BFAt|=(^Au^g+q+wNmv^l-=LlMGu$L9XzauMUvi&*A@xG3BD_DV zlR}*>KV$)hIZcc#&Gb@G?+;m{sn>rS5KD#!?db+}r6#k@W&aHshS+2E5#VtSL31jd z#AqK1?GSXo2I;z~Ck1Xg2cVkCFdIfYYTSo7pM)v8=HbPl-2F19Y?tZGw4Ha)%Q||D zUbDJe7kqT8|BhSk&OUkiJT>>8=+bjRC9byE;|%;sd)*-Uh&VsG&X@Q5e%s2Lh@!(TvEQB$Ep&g$j=0sK`DasU7T literal 0 HcmV?d00001 diff --git a/projects/cameraTest/img/splash.png b/projects/cameraTest/img/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..1b09cafe6a366a81f85b8157bc93174a2545129b GIT binary patch literal 7715 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 literal 0 HcmV?d00001 diff --git a/src/examples/catformer.ict b/src/examples/catformer.ict index 115134c63..9b431c1d5 100644 --- a/src/examples/catformer.ict +++ b/src/examples/catformer.ict @@ -71,12 +71,12 @@ { "name": "Player", "depth": 0, - "oncreate": "this.animationSpeed = 15 / 60;\n\n// Traditional speed/dir movement system works badly with platformers.\n// We will set up our own variables and write own movement code.\n\nthis.maxspeed = 5; // this will be our max horizontal speed\nthis.acceleration = 1; // this is walking acceleration\nthis.jumpPower = 12; // this is our jumping velocity\nthis.gravity = 0.5;", + "oncreate": "this.animationSpeed = 15 / 60;\n\n// Traditional speed/dir movement system works badly with platformers.\n// We will set up our own variables and write own movement code.\n\nthis.maxspeed = 5; // this will be our max horizontal speed\nthis.acceleration = 1; // this is walking acceleration\nthis.jumpPower = 12; // this is our jumping velocity\nthis.gravity = 0.5;\n\n\n// Camera should follow the character\n\nct.camera.follow = this;\nct.camera.borderX = 400;\nct.camera.borderY = 200;\nct.camera.drift = 0.95;\n", "onstep": "// here and below we check for both collisions with tiles and copies.\n// -10 is the depth of the colliding tile layer. We ignore the decorative\n// layer at -20.\nvar onGround = ct.place.occupied(this, this.x, this.y + 1, 'Solid') ||\n ct.place.tile(this, this.x, this.y + 1, -10);\n\n// Move right or left\nif (ct.actions.Move.value > 0) {\n if (this.hspeed < this.maxspeed) {\n this.hspeed += this.acceleration;\n }\n if ((this.currentFrame > 10 || !this.playing) && onGround) {\n this.gotoAndPlay(0);\n }\n} else if (ct.actions.Move.value < 0) {\n if (this.hspeed > -this.maxspeed) {\n this.hspeed -= this.acceleration;\n }\n if ((this.currentFrame > 10 || !this.playing) && onGround) {\n this.gotoAndPlay(0);\n }\n} else { // Slow down if we are on the ground and we don't move.\n if (onGround) {\n this.speed = Math.max(0, this.speed - this.maxspeed);\n this.gotoAndStop(9);\n }\n}\n\n\n// If we are not on the ground, add gravity\nif (!onGround) {\n this.vspeed += this.gravity * ct.delta;\n}\n\n// Jumping\nif (ct.actions.Jump.down) {\n // Jump only if we stay on the ground\n if (onGround) {\n this.vspeed = -this.jumpPower;\n this.gotoAndStop(12);\n }\n}\n\n// Move pixel by pixel until we meet a colliding object next to us\n//\n// Premultiply the target speed with ct.delta to ensure the same movement\n// over different FPS.\n//\n// Math.sign returns -1 if a number is negative, 1 if positive and 0 if it is 0.\n// Math.abs turns everything into a positive number. \n// Combined, Math.abs and Math.sign allow to loop in any direction.\nfor (var i = 0; i < Math.abs(this.hspeed * ct.delta); i++) {\n if (ct.place.free(this, this.x + Math.sign(this.hspeed), this.y, 'Solid') &&\n !ct.place.tile(this, this.x + Math.sign(this.hspeed), this.y, -10)\n ) {\n this.x += Math.sign(this.hspeed);\n } else {\n this.hspeed = 0;\n break;\n }\n}\n\n// Do the same for vertical speed\nfor (var i = 0; i < Math.abs(this.vspeed * ct.delta); i++) {\n if (ct.place.free(this, this.x, this.y + Math.sign(this.vspeed), 'Solid') &&\n !ct.place.tile(this, this.x, this.y + Math.sign(this.vspeed), -10)\n ) {\n this.y += Math.sign(this.vspeed);\n } else {\n this.vspeed = 0;\n break;\n }\n}\n\n// Check if we collide with spikes\nif (ct.place.occupied(this, this.x, this.y, 'Enemy')) {\n ct.sound.spawn('BodyFall');\n // move to a 'death' screen\n ct.rooms.switch('DeadScreen');\n}", - "ondraw": "if (this.hspeed < 0) {\n this.scale.x = -1;\n} else if (this.hspeed > 0) {\n this.scale.x = 1;\n}\n\nct.room.followShiftX = this.scale.x * 150;", + "ondraw": "if (this.hspeed < 0) {\n this.scale.x = -1;\n} else if (this.hspeed > 0) {\n this.scale.x = 1;\n}\n\nct.camera.shiftX = this.scale.x * 150;", "ondestroy": "", "uid": "00664f77-21c6-4b06-b310-88640b1beaf9", - "lastmod": 1551429765916, + "lastmod": 1580611463284, "extends": { "ctype": "Player" }, @@ -86,11 +86,11 @@ "name": "Coin", "depth": 0, "oncreate": "", - "onstep": "if (ct.place.meet(this, this.x, this.y, 'Player')) {\n this.kill = true;\n ct.sound.spawn(ct.random.dice('Coin_01', 'Coin_02', 'Coin_03'));\n ct.room.coins ++;\n ct.room.coinsCounter.text = ct.room.coins;\n}", + "onstep": "if (ct.place.meet(this, this.x, this.y, 'Player')) {\n this.kill = true;\n ct.sound.spawn(ct.random.dice('Coin_01', 'Coin_02', 'Coin_03'));\n ct.room.coins ++;\n}", "ondraw": "", "ondestroy": "", "uid": "9402709c-5d0d-4198-9dce-877a5b7c77a2", - "lastmod": 1547470614124, + "lastmod": 1580524813404, "extends": {}, "texture": "62be2903-9782-49fc-b551-ef2e641091c9" }, @@ -98,11 +98,11 @@ "name": "Greed", "depth": 0, "oncreate": "", - "onstep": "ct.types.move(this);", + "onstep": "", "ondraw": "", "ondestroy": "", "uid": "2d29a0d5-d88d-493d-bece-8d52ed476273", - "lastmod": 1551429772275, + "lastmod": 1580611469943, "extends": {}, "texture": "e90eb23b-547a-4d96-a203-ffe63c00cf9b" }, @@ -110,11 +110,11 @@ "name": "Slime", "depth": 0, "oncreate": "this.speed = 1;\nthis.direction = 180;\n\nthis.animationSpeed = 4 / 60;\nthis.play();", - "onstep": "var freeBelow = !ct.place.tile(this, this.x + ct.u.ldx(70, this.dir), this.y + 35, -10) &&\n ct.place.free(this, this.x + ct.u.ldx(70, this.dir), this.y + 35, 'Solid');\n \nvar occupiedInFront = ct.place.tile(this, this.x + ct.u.ldx(10, this.dir), this.y, -10) || \n ct.place.occupied(this, this.x + ct.u.ldx(10, this.dir), this.y, 'Solid');\n\nif (freeBelow || occupiedInFront) {\n if (this.direction === 180) {\n this.direction = 0;\n this.scale.x = -1;\n } else {\n this.direction = 180;\n this.scale.x = 1;\n }\n}\n\nct.types.move(this);", + "onstep": "var freeBelow = !ct.place.tile(this, this.x + ct.u.ldx(70, this.direction), this.y + 35, -10) &&\n ct.place.free(this, this.x + ct.u.ldx(70, this.direction), this.y + 35, 'Solid');\n \nvar occupiedInFront = ct.place.tile(this, this.x + ct.u.ldx(10, this.direction), this.y, -10) || \n ct.place.occupied(this, this.x + ct.u.ldx(10, this.direction), this.y, 'Solid');\n\nif (freeBelow || occupiedInFront) {\n if (this.direction === 180) {\n this.direction = 0;\n this.scale.x = -1;\n } else {\n this.direction = 180;\n this.scale.x = 1;\n }\n}\n\nct.types.move(this);", "ondraw": "", "ondestroy": "", "uid": "1f91fd7d-2d6c-4542-aead-78b8010b835b", - "lastmod": 1551429013431, + "lastmod": 1580621292127, "extends": { "ctype": "Enemy" }, @@ -137,12 +137,24 @@ "depth": 100, "oncreate": "", "onstep": "if (ct.mouse.pressed) {\n if (ct.u.prect(ct.mouse.x, ct.mouse.y, this)) {\n ct.fittoscreen.toggleFullscreen();\n }\n}", - "ondraw": "this.x = ct.viewWidth + ct.room.x - 32;\nthis.y = ct.viewHeight + ct.room.y - 32;\nthis.gotoAndStop(ct.fittoscreen.getIsFullscreen()? 1 : 0);", + "ondraw": "this.gotoAndStop(ct.fittoscreen.getIsFullscreen()? 1 : 0);", "ondestroy": "", "uid": "8a4b8de4-7729-4936-bc7b-bc277a512975", - "lastmod": 1547470581588, + "lastmod": 1580612119334, "extends": {}, "texture": "d161a39a-203e-4099-99e2-41385d94f975" + }, + { + "name": "CoinWidget", + "depth": 0, + "oncreate": "this.coinsCounter = new PIXI.Text(this.coins, ct.styles.get('CoinText'));\nthis.coinsCounter.x = 40;\nthis.coinsCounter.anchor.y = 0.5;\nthis.addChild(this.coinsCounter);", + "onstep": "this.move();", + "ondraw": "if (this.coinsCounter.text != ct.room.coins + ' / 33') {\n this.coinsCounter.text = ct.room.coins + ' / 33';\n}", + "ondestroy": "", + "uid": "1f73eb4f-8795-4234-af61-2c44c3fba073", + "texture": "62be2903-9782-49fc-b551-ef2e641091c9", + "extends": {}, + "lastmod": 1580612116266 } ], "sounds": [ @@ -248,9 +260,9 @@ "rooms": [ { "name": "InGame", - "oncreate": "this.coins = 0;\n\nthis.follow = ct.types.list['Player'][0];\nthis.borderX = 400;\nthis.borderY = 300;\nthis.followDrift = 0.9;\n\nthis.coinsCounter = new PIXI.Text(this.coins, ct.styles.get('CoinText'));\nthis.coinsCounter.depth = 100;\nthis.coinsCounter.x = this.x + ct.viewWidth - 70;\nthis.coinsCounter.y = this.y + 35;\nthis.coinsCounter.anchor.y = 0.5;\nthis.addChild(this.coinsCounter);\n\nthis.coinIcon = new PIXI.Sprite(ct.res.getTexture('coinGold', 0));\nthis.coinIcon.x = this.x + ct.viewWidth - 100;\nthis.coinIcon.y = this.y + 35;\nthis.coinIcon.depth = 100;\nthis.addChild(this.coinIcon);", + "oncreate": "this.coins = 0;\n\nct.rooms.append('UI_Layer', {isUi: true});", "onstep": "if (this.coins >= 33) {\n ct.rooms.switch('RichScreen');\n}", - "ondraw": "this.coinsCounter.x = this.x + ct.viewWidth - 70;\nthis.coinsCounter.y = this.y + 35;\n\nthis.coinIcon.x = this.x + ct.viewWidth - 100;\nthis.coinIcon.y = this.y + 35;", + "ondraw": "", "onleave": "", "width": 1024, "height": 700, @@ -263,7 +275,7 @@ ], "uid": "b0f0ff4d-a155-4d68-a86e-5bb7ed8973ab", "grid": 35, - "lastmod": 1547471597586, + "lastmod": 1580620591256, "thumbnail": 1, "tiles": [ { @@ -1880,17 +1892,12 @@ "x": 245, "y": 770, "uid": "b7a528f8-655c-4920-900f-f191533f411d" - }, - { - "x": 595, - "y": 175, - "uid": "8a4b8de4-7729-4936-bc7b-bc277a512975" } ] }, { "name": "DeadScreen", - "oncreate": "ct.sound.spawn('Failure');\n\nthis.wait = 30;\nct.keyboard.clear();\n\nthis.ohNoLabel = new PIXI.Text('Oh no!', ct.styles.get('OhNo'));\nthis.ohNoLabel.x = ct.width / 2;\nthis.ohNoLabel.y = 540;\nthis.ohNoLabel.anchor.x = this.ohNoLabel.anchor.y = 0.5;\nthis.addChild(this.ohNoLabel);\n\nthis.subLabel = new PIXI.Text('You were too greeedy for that game!\\nPress any button to try again.', ct.styles.get('OhNo_Small'));\nthis.subLabel.x = ct.width / 2;\nthis.subLabel.y = 600;\nthis.subLabel.anchor.x = this.subLabel.anchor.y = 0.5;\nthis.addChild(this.subLabel);", + "oncreate": "ct.sound.spawn('Failure');\n\n// Create a delay of 0.5 seconds before this screen may be skipped. Also see the \"On Step\" event\nthis.wait = 30;\n// This clears keyboard inputs and ct.keyboard.lastKey\nct.keyboard.clear();\n\nthis.ohNoLabel = new PIXI.Text('Oh no!', ct.styles.get('OhNo'));\nthis.ohNoLabel.x = ct.camera.width / 2;\nthis.ohNoLabel.y = 540;\nthis.ohNoLabel.anchor.x = this.ohNoLabel.anchor.y = 0.5;\nthis.addChild(this.ohNoLabel);\n\nthis.subLabel = new PIXI.Text('You were too greeedy for that game!\\nPress any button to try again.', ct.styles.get('OhNo_Small'));\nthis.subLabel.x = ct.camera.width / 2;\nthis.subLabel.y = 600;\nthis.subLabel.anchor.x = this.subLabel.anchor.y = 0.5;\nthis.addChild(this.subLabel);", "onstep": "this.wait -= ct.delta;\nif (ct.keyboard.lastKey && this.wait < 0) {\n ct.rooms.switch('InGame');\n}", "ondraw": "", "onleave": "", @@ -1905,7 +1912,7 @@ ], "uid": "1ee751d0-24cc-43af-b91d-1a91a9aa48f5", "grid": 64, - "lastmod": 1547546306396, + "lastmod": 1580614967652, "thumbnail": 2, "gridX": 64, "gridY": 64, @@ -1922,8 +1929,8 @@ "uid": "2d29a0d5-d88d-493d-bece-8d52ed476273" }, { - "x": 384, - "y": 576, + "x": 992, + "y": 669, "uid": "8a4b8de4-7729-4936-bc7b-bc277a512975" } ] @@ -1945,7 +1952,7 @@ ], "uid": "1a5a64c6-5a4b-4f57-aa99-371ae3c158a3", "grid": 64, - "lastmod": 1551429777966, + "lastmod": 1580614969737, "thumbnail": 2, "gridX": 64, "gridY": 64, @@ -1967,6 +1974,39 @@ "uid": "8a4b8de4-7729-4936-bc7b-bc277a512975" } ] + }, + { + "name": "UI_Layer", + "oncreate": "", + "onstep": "", + "ondraw": "ct.camera.realign(this);", + "onleave": "", + "width": 1024, + "height": 720, + "backgrounds": [], + "copies": [ + { + "x": 992, + "y": 689, + "uid": "8a4b8de4-7729-4936-bc7b-bc277a512975" + }, + { + "x": 44, + "y": 46, + "uid": "1f73eb4f-8795-4234-af61-2c44c3fba073" + } + ], + "tiles": [ + { + "depth": -10, + "tiles": [] + } + ], + "uid": "cc861d50-35d6-4687-b789-5768eedfd438", + "thumbnail": "5768eedfd438", + "gridX": 64, + "gridY": 64, + "lastmod": 1580621041981 } ], "soundtick": 6, @@ -2003,7 +2043,7 @@ ], "startroom": "b0f0ff4d-a155-4d68-a86e-5bb7ed8973ab", "scripts": [], - "ctjsVersion": "1.0.0", + "ctjsVersion": "1.2.1", "fonts": [], "skeletons": [], "actions": [ diff --git a/src/node_requires/exporter.js b/src/node_requires/exporter.js index 93bcd313c..61c8fdc03 100644 --- a/src/node_requires/exporter.js +++ b/src/node_requires/exporter.js @@ -496,6 +496,7 @@ const runCtProject = async (project, projdir) => { afterdraw: '', afterstep: '', + beforeroomoncreate: '', roomoncreate: '', roomonleave: '', afterroomdraw: '', @@ -526,6 +527,7 @@ const runCtProject = async (project, projdir) => { /* Load source files in parallel */ const sources = {}; const sourcesList = [ + 'camera.js', 'ct.css', 'index.html', 'main.js', @@ -579,6 +581,7 @@ const runCtProject = async (project, projdir) => { .replace('@startroom@', startroom.name) .replace('/*@rooms@*/', roomsCode) .replace('/*%switch%*/', injects.switch) + .replace('/*%beforeroomoncreate%*/', injects.beforeroomoncreate) .replace('/*%roomoncreate%*/', injects.roomoncreate) .replace('/*%roomonleave%*/', injects.roomonleave); buffer += '\n'; @@ -629,6 +632,9 @@ const runCtProject = async (project, projdir) => { .replace('/*@types@*/', types); buffer += '\n'; + buffer += (await sources['camera.js']); + buffer += '\n'; + var sounds = stringifySounds(); buffer += (await sources['sound.js']) .replace('/*@sound@*/', sounds); diff --git a/src/styl/3rdParty/monacoEdits.styl b/src/styl/3rdParty/monacoEdits.styl index 5969292cd..637080713 100644 --- a/src/styl/3rdParty/monacoEdits.styl +++ b/src/styl/3rdParty/monacoEdits.styl @@ -22,4 +22,17 @@ .context-view.monaco-menu-container border-radius br - {bs} \ No newline at end of file + {bs} + +.monaco-scrollable-element + border-radius br +.monaco-menu .monaco-action-bar.vertical + .action-label + font-size 1rem + line-height 1.5 + &.separator + height 0 + margin-left 0 + margin-right 0 + .action-menu-item + height 2.5em \ No newline at end of file diff --git a/src/styl/tags/rooms/room-events-editor.styl b/src/styl/tags/rooms/room-events-editor.styl index c53caec0b..41a09b360 100644 --- a/src/styl/tags/rooms/room-events-editor.styl +++ b/src/styl/tags/rooms/room-events-editor.styl @@ -3,6 +3,7 @@ room-events-editor display flex flex-direction column z-index 11 + overflow hidden !important .tabs, button border-radius 0 button @@ -18,4 +19,5 @@ room-events-editor top 0 bottom 0 border-radius 0 - overflow visible \ No newline at end of file + overflow visible + box-sizing border-box \ No newline at end of file diff --git a/src/typedefs/ct.js/Room.d.ts b/src/typedefs/ct.js/Room.d.ts index 67fee59da..1e9fc4ede 100644 --- a/src/typedefs/ct.js/Room.d.ts +++ b/src/typedefs/ct.js/Room.d.ts @@ -33,19 +33,6 @@ declare class Room extends PIXI.Container { /** The vertical position of the camera */ y: number; - /** If set, the room's camera will follow the given copy */ - follow: Copy|null; - /** Works if `follow` is set to a copy. Sets the frame inside which the copy will be kept */ - borderX: number; - /** Works if `follow` is set to a copy. Sets the frame inside which the copy will be kept */ - borderY: number; - /** Works if `follow` is set to a copy. Displaces the camera horizontally, relative to the copy. */ - followShiftX: number; - /** Works if `follow` is set to a copy. Displaces the camera vertically, relative to the copy. */ - followShiftY: number; - /** Works if `follow` is set to a copy. If set to a value between 0 and 1, it will make camera movement smoother */ - followDrift: number; - tileLayers: Array backgrounds: Array