From 70660a60f15f38de56faf4de75216453606693a0 Mon Sep 17 00:00:00 2001 From: e Date: Sun, 1 Oct 2023 12:54:16 -0300 Subject: [PATCH] update to 0.3.0 version --- README.BBCode | 39 +-- package.json | 2 +- rellax.asc | 819 ++++++++++++++++++++++++++++++++++++++------------ rellax.ash | 61 +++- rellax.scm | Bin 17642 -> 35027 bytes rellax.xml | 2 +- 6 files changed, 701 insertions(+), 222 deletions(-) diff --git a/README.BBCode b/README.BBCode index c435044..edd30dc 100644 --- a/README.BBCode +++ b/README.BBCode @@ -1,40 +1,42 @@ -[size=14pt][b]rellax[/b][/size] [color=gray][b] version 0.2.1[/b][/color] +[size=14pt][b]rellax[/b][/size] [color=gray][b]version 0.3.0[/b][/color] -[url=https://github.com/ericoporto/rellax/releases/download/0.2.1/rellax.scm]Get Latest Release [b]rellax.scm[/b][/url] | [url=https://github.com/ericoporto/rellax]GitHub Repo[/url] | [url=https://github.com/ericoporto/rellax/releases/download/0.2.1/rellax_demo_windows.zip]Demo Windows[/url] | [url=https://github.com/ericoporto/rellax/releases/download/0.2.1/rellax_demo_linux.tar.gz]Demo Linux[/url] | [url=https://github.com/ericoporto/rellax/archive/master.zip] Download project .zip [/url] +[url="https://github.com/ericoporto/rellax/releases/download/0.3.0/rellax.scm"]Get Latest Release [b]rellax.scm[/b][/url] | [url="https://github.com/ericoporto/rellax"]GitHub Repo[/url] | [url="https://github.com/ericoporto/rellax/releases/download/0.3.0/rellax_demo_windows.zip"]Demo Windows[/url] | [url="https://github.com/ericoporto/rellax/releases/download/0.3.0/rellax_demo_linux.tar.gz"]Demo Linux[/url] | [url="https://github.com/ericoporto/rellax/archive/master.zip"]Download project .zip [/url] Rellax while the camera tracks with cool parallax -[url=https://imgur.com/nJGnylM][img width=640 height=360]https://raw.githubusercontent.com/ericoporto/rellax/master/rellax_demo.gif[/img][/url] +[url="https://imgur.com/nJGnylM"][img width=640 height=360]https://raw.githubusercontent.com/ericoporto/rellax/master/rellax_demo.gif[/img][/url] [i]This module uses the camera and Viewport API from [b]Adventure Game Studio 3.5.0[/b].[/i] Demo game uses keyboard arrows control, up arrow jumps. WASD should also work. [hr] - [i][b]Usage[/b][/i] +[i][b]Usage[/b][/i] Before starting, you must create the following Custom Properties in AGS Editor, for usage with Objects. Just click on [b][tt]Properties [...][/tt][/b] and on the [b][tt]Edit Custom Properties[/tt][/b] screen, click on [b][tt]Edit Schema ...[/tt][/b] button, and add the two properties below: - PxPos: [list] +[size=14pt]PxPos[/size]: +[list] [li][b][tt]Name[/tt]:[/b] [tt]PxPos[/tt][/li] [li][b][tt]Description[/tt]:[/b] [tt]Object's horizontal parallax[/tt][/li] [li][b][tt]Type[/tt]:[/b] [tt]Number[/tt][/li] [li][b][tt]Default Value[/tt]:[/b] [tt]0[/tt][/li] [/list] - PyPos: [list] +[size=14pt]PyPos[/size]: +[list] [li][b][tt]Name[/tt]:[/b] [tt]PyPos[/tt][/li] [li][b][tt]Description[/tt]:[/b] [tt]Object's vertical parallax[/tt][/li] [li][b][tt]Type[/tt]:[/b] [tt]Number[/tt][/li] [li][b][tt]Default Value[/tt]:[/b] [tt]0[/tt][/li] [/list] -The number defined on [tt]Px[/tt] or [tt]Py[/tt] will be divided by 100 and used to increase the scrolling. +[u]The number defined on [tt]Px[/tt] or [tt]Py[/tt] will be divided by 100 and used to increase the scrolling[/u]. An object with [tt]Px[/tt] and [tt]Py[/tt] 0 is scrolled normally, an object with [tt]Px[/tt] and [tt]Py[/tt] 100 will be fixed on the screen despite camera movement. Objects with negative [tt]Px[/tt] and [tt]Py[/tt] are usually at the front, and positive values are usually at the back. [hr] - [i][b]Script API[/b][/i] +[i][b]Script API[/b][/i] [b][tt]static attribute Character* TargetCharacter[/tt][/b] The character being tracked by the Game.Camera. @@ -46,13 +48,19 @@ Gets/sets whether Parallax is on or off. Gets/sets whether Smooth Camera tracking is on or off. [b][tt]static attribute bool AdjustCameraOnRoomLoad[/tt][/b] -Gets/sets whether to quickly adjust camera to target on room before fade in, when Smooth Camera is on. +Gets/sets whether to instantly adjust camera to target on room before fade in, when Smooth Camera is on. Default is true. + +[b][tt]static attribute RellaxTweenEasingType EasingType[/tt][/b] +gets/sets the camera tween type to use when the character is stopped. + +[b][tt]static attribute float TweenDuration[/tt][/b] +gets/sets the camera tween duration once the character is stopped. [b][tt]static attribute int CameraOffsetX[/tt][/b] -Gets/sets the camera horizontal offset. +Gets/sets the camera horizontal offset. It's applied without smoothing. [b][tt]static attribute int CameraOffsetY[/tt][/b] -Gets/sets the camera vertical offset. +Gets/sets the camera vertical offset. It's applied without smoothing. [b][tt]static attribute int CameraLookAheadX[/tt][/b] Gets/sets the camera horizontal lookahead offset. This is an additional offset that is added in the direction the target character is facing (only 4 direction support now). @@ -60,9 +68,6 @@ Gets/sets the camera horizontal lookahead offset. This is an additional offset t [b][tt]static attribute int CameraLookAheadY[/tt][/b] Gets/sets the camera vertical lookahead offset. This is an additional offset that is added in the direction the target character is facing (only 4 direction support now). -[b][tt]static attribute int StandstillCameraDelayY[/tt][/b] -Gets/sets number of frames to wait before adjusting the Y axis quicker after the player is still. - [b][tt]static attribute int CameraLerpFactorX[/tt][/b] Gets/sets the factor the camera should use when interpolating in the X axis. @@ -76,7 +81,7 @@ Gets/sets the camera window width that is centered on the target lookahead point Gets/sets the camera window height that is centered on the target lookahead point, when the target is outside of the window, the camera moves to keep it inside. [hr] - [i][b]License[/b][/i] -This module is created by eri0o is provided with MIT License, see [url=https://github.com/ericoporto/rellax/blob/master/LICENSE]LICENSE[/url] for more details. +[i][b]License[/b][/i] +This module is created by eri0o is provided with MIT License, see [url="https://github.com/ericoporto/rellax/blob/master/LICENSE"]LICENSE[/url] for more details. The code on this module is based on the code of Smooth Scrolling + Parallax Module from Alasdair Beckett, which bases on code from Steve McCrea. -The demo game uses CC0 (Public Domain) art provided by [url=https://opengameart.org/users/jetrel]jetrel[/url]. \ No newline at end of file +The demo game uses CC0 (Public Domain) art provided by [url="https://opengameart.org/users/jetrel"]jetrel[/url]. \ No newline at end of file diff --git a/package.json b/package.json index 369e6b9..b9af18e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,6 @@ "name": "rellax", "text": "Rellax while the Camera tracks with cool parallax.", "forum": "https://www.adventuregamestudio.co.uk/forums/index.php?topic=57489.0", - "version": "0.2.2", + "version": "0.3.0", "author": "eri0o" } diff --git a/rellax.asc b/rellax.asc index 8d02374..56a8ec6 100644 --- a/rellax.asc +++ b/rellax.asc @@ -1,5 +1,5 @@ // Rellax -// 0.1.4 +// 0.3.0 // A module to provide smooth scrolling and parallax! // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // Before starting, you must create the following Custom Properties @@ -48,115 +48,313 @@ // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. - #define MAX_PARALLAX_OBJS 39 -Character *_TargetCharacter; +#define RLX_HALF_PI 1.570796327 +#define RLX_DOUBLE_PI 6.283185307 + +// Custom properties for objects +// PxPos: horizontal parallax +// PyPos: vertical parallax + +managed struct PointF { + float x; + float y; + static import PointF* New(float x, float y); + import void Set(PointF* pf); +}; + +static PointF* PointF::New(float x, float y) { + PointF* p = new PointF; + p.x = x; + p.y = y; + return p; +} + +void PointF::Set(PointF* pf) { + this.x = pf.x; + this.y = pf.y; +} -Object *_pxo[MAX_PARALLAX_OBJS]; +// Target character for camera tracking +Character* _TargetCharacter; +PointF* _TargetPos; + +// Parallax objects +Object* _pxo[MAX_PARALLAX_OBJS]; int _pxoRoomStartX[MAX_PARALLAX_OBJS]; int _pxoRoomStartY[MAX_PARALLAX_OBJS]; int _pxoOriginX[MAX_PARALLAX_OBJS]; int _pxoOriginY[MAX_PARALLAX_OBJS]; int _pxo_count; +// Camera window dimensions int _cam_window_w, _cam_window_h; + +// Scrolling float _scroll_x, _scroll_y; -float _next_cam_x, _next_cam_y; +PointF* _next_cam_pos; + +// Tween Stuff +PointF* _tween_cam_origin; +float _cam_tween_elapsed; +float _cam_tween_duration; +RellaxTweenEasingType _cam_tween_type; + +// Previous character position int _prev_c_x, _prev_c_y; +int _target_stop_counter; +bool _is_target_stopped; + +// Camera offsets int _off_x, _off_y; +int _off_applied_x, _off_applied_y; + +// smoothed offset int _look_ahead_x; int _look_ahead_y; -int _standstill_ticks_y; + +// Interpolation factors float _cam_lerp_factor_x; float _cam_lerp_factor_y; -float _y_multiplier; -float _cam_x, _cam_y; +// Current camera position +PointF* _cam_pos; +// Partial character height int _partial_c_height; -int _count_still_ticks; +// Flags bool _is_doRoomSetup; +bool _DebugShow; bool _SmoothCamEnabled = true; bool _ParallaxEnabled = true; bool _AdjustCameraOnRoomLoad = true; -DynamicSprite* debug_spr; -Overlay* ovr; + +// Debugging objects +DynamicSprite* _debug_spr; +Overlay* _debug_ovr; + +// Linear interpolation float _Lerp(float from, float to, float t) { return (from + (to - from) * t); } -int _ClampInt(int value, int min, int max) { - if (value > max) return max; - else if (value < min) return min; - return value; +PointF* _LerpPF(PointF* from, PointF* to, float t) { + return PointF.New((from.x + (to.x - from.x) * t), (from.y + (to.y - from.y) * t)); } -Point* _doCameraTracking() -{ - if(_prev_c_x == _TargetCharacter.x && _prev_c_y == _TargetCharacter.y) - _count_still_ticks++; - else - _count_still_ticks = 0; +PointF* _LerpPFEx(PointF* from, PointF* to, float tx, float ty) { + return PointF.New((from.x + (to.x - from.x) * tx), (from.y + (to.y - from.y) * ty)); +} - Point* p = new Point; - if(_TargetCharacter.x-Game.Camera.Width/2-Game.Camera.X<=-_cam_window_w/2 || - _TargetCharacter.x-Game.Camera.Width/2-Game.Camera.X>_cam_window_w/2 || - _TargetCharacter.y-Game.Camera.Height/2-Game.Camera.Y<=-_cam_window_h/2 || - _TargetCharacter.y-Game.Camera.Height/2-Game.Camera.Y>_cam_window_h/2 || - (_count_still_ticks > 5 || _standstill_ticks_y == 0)){ - - int x_focus=0,y_focus=0; - if(_TargetCharacter.Loop == 2) x_focus = _look_ahead_x; // right - else if(_TargetCharacter.Loop == 1) x_focus = -_look_ahead_x; // left - else if(_TargetCharacter.Loop == 0) y_focus = _look_ahead_y; // down - else if(_TargetCharacter.Loop == 3) y_focus = -_look_ahead_y; // up - - if(_standstill_ticks_y > 0) { - if(_count_still_ticks <= _standstill_ticks_y) _y_multiplier = IntToFloat(_count_still_ticks)*0.138 ; - if(_count_still_ticks > _standstill_ticks_y) { - _y_multiplier = 5.0; - _count_still_ticks = _standstill_ticks_y + 6; - } - } else { - _y_multiplier = 1.0; - } - - p.x = _ClampInt(_TargetCharacter.x + _off_x + x_focus - Game.Camera.Width/2, - 0, Room.Width-Game.Camera.Width); - p.y = _ClampInt(_TargetCharacter.y + _off_y + y_focus - _partial_c_height - Game.Camera.Height/2, - 0, Room.Height-Game.Camera.Height); - } else { - p.x = Game.Camera.X; - p.y = Game.Camera.Y; +float _p, _s; + +float _rellax_tween_EaseLinear(float t,float d) { + return t / d; +} + +float _rellax_tween_EaseInSine(float t,float b,float c,float d) { + return -c * Maths.Cos((t/d) * RLX_HALF_PI) + c + b; +} +float _rellax_tween_EaseOutSine(float t,float b,float c,float d) { + return c * Maths.Sin((t/d) * RLX_HALF_PI) + b; +} +float _rellax_tween_EaseInOutSine(float t,float b,float c,float d) { + return (-c*0.5) * (Maths.Cos(Maths.Pi*(t/d)) -1.0) + b; +} + +float _rellax_tween_EaseInPower(float t,float b,float c,float d,float exponent) { + t = t / d; + return c*Maths.RaiseToPower(t, exponent) + b; +} +float _rellax_tween_EaseOutPower(float t,float b,float c,float d,float exponent) { + _s = 1.0; + if (FloatToInt(exponent, eRoundDown) % 2 == 0) { + c = -c; + _s = -_s; } + t = (t / d) - 1.0; + return c*(Maths.RaiseToPower(t, exponent) + _s) + b; +} +float _rellax_tween_EaseInOutPower(float t,float b,float c,float d,float exponent) { + t = t / (d*0.5); + if (t < 1.0) return (c*0.5)*Maths.RaiseToPower(t, exponent) + b; + _s = 2.0; + if (FloatToInt(exponent, eRoundDown) % 2 == 0) { + c = -c; + _s = -2.0; + } + return (c*0.5)*(Maths.RaiseToPower(t - 2.0, exponent) + _s) + b; +} - _prev_c_x = _TargetCharacter.x; - _prev_c_y = _TargetCharacter.y; - return p; +float _rellax_tween_EaseInQuad(float t,float b,float c,float d) { + t = (t / d); + return c*t*t + b; +} +float _rellax_tween_EaseOutQuad(float t,float b,float c,float d) { + t = (t / d); + return -c*t*(t-2.0) + b; +} +float _rellax_tween_EaseInOutQuad(float t,float b,float c,float d) { + t = t / (d*0.5); + if (t < 1.0) return (c*0.5)*t*t + b; + t = t - 1.0; + return -(c*0.5)*(t*(t-2.0) - 1.0) + b; } -void _quickAdjustToTarget() -{ - Point* p = _doCameraTracking(); - - _next_cam_x = IntToFloat(p.x); - _next_cam_y = IntToFloat(p.y); - _cam_x = _next_cam_x; - _cam_y = _next_cam_y; - - Game.Camera.SetAt(p.x, p.y); +float _rellax_tween_EaseInExpo(float t,float b,float c,float d) { + if (t == 0.0) return b; + return c * Maths.RaiseToPower(2.0, 10.0 * (t/d - 1.0)) + b; +} +float _rellax_tween_EaseOutExpo(float t,float b,float c,float d) { + if (t == d) return b + c; + return c * (-Maths.RaiseToPower(2.0, -10.0 * (t/d)) + 1.0) + b; +} +float _rellax_tween_EaseInOutExpo(float t,float b,float c,float d) { + if (t == 0.0) return b; + if (t == d) return b + c; + t = t / (d*0.5); + if (t < 1.0) return (c*0.5) * Maths.RaiseToPower(2.0, 10.0 * (t - 1.0)) + b; + t = t - 1.0; + return (c*0.5) * (-Maths.RaiseToPower(2.0, -10.0 * t) + 2.0) + b; } -void _updateCameras() -{ - _next_cam_x = IntToFloat(Game.Camera.X); - _next_cam_y = IntToFloat(Game.Camera.Y); - _cam_x = _next_cam_x; - _cam_y = _next_cam_y; +float _rellax_tween_EaseInCirc(float t,float b,float c,float d) { + t = t / d; + return -c * (Maths.Sqrt(1.0 - t*t) - 1.0) + b; +} +float _rellax_tween_EaseOutCirc(float t,float b,float c,float d) { + t = t / d - 1.0; + return c * Maths.Sqrt(1.0 - t*t) + b; +} +float _rellax_tween_EaseInOutCirc(float t,float b,float c,float d) { + t = t / (d*0.5); + if (t < 1.0) return -(c*0.5) * (Maths.Sqrt(1.0 - t*t) - 1.0) + b; + t = t - 2.0; + return (c*0.5) * (Maths.Sqrt(1.0 - t*t) + 1.0) + b; +} + +float _rellax_tween_EaseInBack(float t,float b,float c,float d) { + _s = 1.70158; + t = (t / d); + return c*t*t*((_s+1.0)*t - _s) + b; +} +float _rellax_tween_EaseOutBack(float t,float b,float c,float d) { + _s = 1.70158; + t = (t / d) - 1.0; + return c*(t*t*((_s+1.0)*t + _s) + 1.0) + b; +} +float _rellax_tween_EaseInOutBack(float t,float b,float c,float d) { + _s = 1.70158; + t = t / (d / 2.0); + _s = _s * 1.525; + if (t < 1.0) return (c/2.0)*(t*t*((_s+1.0)*t - _s)) + b; + t = t - 2.0; + return (c/2.0)*(t*t*((_s+1.0)*t + _s) + 2.0) + b; +} + +float _rellax_tween_EaseOutBounce(float t,float b,float c,float d) { + t = t / d; + if (t < (1.0 / 2.75)) return c*(7.5625*t*t) + b; + else if (t < (2.0 / 2.75)) { + t = t - (1.5 / 2.75); + return c*(7.5625*t*t + 0.75) + b; + } + else if (t < (2.5 / 2.75)) { + t = t - (2.25 / 2.75); + return c*(7.5625*t*t + 0.9375) + b; + } + t = t - (2.625 / 2.75); + return c*(7.5625*t*t + 0.984375) + b; +} +float _rellax_tween_EaseInBounce(float t,float b,float c,float d) { + return c - _rellax_tween_EaseOutBounce(d - t, 0.0, c, d) + b; +} +float _rellax_tween_EaseInOutBounce(float t,float b,float c,float d) { + if (t < (d / 2.0)) return _rellax_tween_EaseInBounce(t * 2.0, 0.0, c, d) * 0.5 + b; + return (_rellax_tween_EaseOutBounce((t * 2.0) - d, 0.0, c, d) * 0.5) + (c*0.5) + b; +} + +float _rellax_tween_EaseInElastic(float t,float b,float c,float d) { + if (t == 0.0) return b; + t = t / d; + if (t == 1.0) return b + c; + _p = d * 0.3; + _s = _p / 4.0; + t = t - 1.0; + return -(c*Maths.RaiseToPower(2.0, 10.0*t) * Maths.Sin(((t*d - _s)*RLX_DOUBLE_PI) / _p)) + b; +} +float _rellax_tween_EaseOutElastic(float t,float b,float c,float d) { + if (t == 0.0) return b; + t = t / d; + if (t == 1.0) return b + c; + _p = d * 0.3; + _s = _p / 4.0; + return ((c*Maths.RaiseToPower(2.0, -10.0*t)) * Maths.Sin(((t*d - _s)*RLX_DOUBLE_PI / _p)) + c + b); +} +float _rellax_tween_EaseInOutElastic(float t,float b,float c,float d) { + if (t == 0.0) return b; + t = t / (d * 0.5); + if (t == 2.0) return b + c; + _p = d * (0.3 * 1.5); + _s = _p / 4.0; + if (t < 1.0) { + t = t - 1.0; + return -0.5*(c*Maths.RaiseToPower(2.0, 10.0*t) * Maths.Sin(((t*d - _s)*RLX_DOUBLE_PI) / _p)) + b; + } + t = t - 1.0; + return c*Maths.RaiseToPower(2.0, -10.0*t) * Maths.Sin(((t*d - _s)*RLX_DOUBLE_PI) / _p)*0.5 + c + b; +} + +float _rellax_tween_GetValue(float elapsed, float duration, RellaxTweenEasingType easingType) { + if (easingType == eRellaxEaseLinearTween) return _rellax_tween_EaseLinear(elapsed, duration); + if (easingType == eRellaxEaseInSineTween) return _rellax_tween_EaseInSine(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutSineTween) return _rellax_tween_EaseOutSine(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutSineTween) return _rellax_tween_EaseInOutSine(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInCubicTween) return _rellax_tween_EaseInPower(elapsed, 0.0, 1.0, duration, 3.0); + if (easingType == eRellaxEaseOutCubicTween) return _rellax_tween_EaseOutPower(elapsed, 0.0, 1.0, duration, 3.0); + if (easingType == eRellaxEaseInOutCubicTween) return _rellax_tween_EaseInOutPower(elapsed, 0.0, 1.0, duration, 3.0); + if (easingType == eRellaxEaseInQuartTween) return _rellax_tween_EaseInPower(elapsed, 0.0, 1.0, duration, 4.0); + if (easingType == eRellaxEaseOutQuartTween) return _rellax_tween_EaseOutPower(elapsed, 0.0, 1.0, duration, 4.0); + if (easingType == eRellaxEaseInOutQuartTween) return _rellax_tween_EaseInOutPower(elapsed, 0.0, 1.0, duration, 4.0); + if (easingType == eRellaxEaseInQuintTween) return _rellax_tween_EaseInPower(elapsed, 0.0, 1.0, duration, 5.0); + if (easingType == eRellaxEaseOutQuintTween) return _rellax_tween_EaseOutPower(elapsed, 0.0, 1.0, duration, 5.0); + if (easingType == eRellaxEaseInOutQuintTween) return _rellax_tween_EaseInOutPower(elapsed, 0.0, 1.0, duration, 5.0); + if (easingType == eRellaxEaseInQuadTween) return _rellax_tween_EaseInQuad(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutQuadTween) return _rellax_tween_EaseOutQuad(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutQuadTween) return _rellax_tween_EaseInOutQuad(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInExpoTween) return _rellax_tween_EaseInExpo(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutExpoTween) return _rellax_tween_EaseOutExpo(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutExpoTween) return _rellax_tween_EaseInOutExpo(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInCircTween) return _rellax_tween_EaseInCirc(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutCircTween) return _rellax_tween_EaseOutCirc(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutCircTween) return _rellax_tween_EaseInOutCirc(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInBackTween) return _rellax_tween_EaseInBack(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutBackTween) return _rellax_tween_EaseOutBack(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutBackTween) return _rellax_tween_EaseInOutBack(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInElasticTween) return _rellax_tween_EaseInElastic(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutElasticTween) return _rellax_tween_EaseOutElastic(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutElasticTween) return _rellax_tween_EaseInOutElastic(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInBounceTween) return _rellax_tween_EaseInBounce(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseOutBounceTween) return _rellax_tween_EaseOutBounce(elapsed, 0.0, 1.0, duration); + if (easingType == eRellaxEaseInOutBounceTween) return _rellax_tween_EaseInOutBounce(elapsed, 0.0, 1.0, duration); + return _rellax_tween_EaseLinear(elapsed, duration); +} + +// Clamp integer value between min and max +int _ClampInt(int value, int min, int max) { + if (value > max) return max; + else if (value < min) return min; + return value; +} + +float _ClampFloat(float value, float min, float max) { + if (value > max) return max; + else if (value < min) return min; + return value; } void _drawClearRectangle(DrawingSurface* surf, int x1, int y1, int x2, int y2) @@ -167,45 +365,63 @@ void _drawClearRectangle(DrawingSurface* surf, int x1, int y1, int x2, int y2) surf.DrawLine(x1, y1, x1, y2); } -void _drawDebugOverlay() +// -- HELPER FUNCTIONS -- + +Point* _get_camera_position() { - Point* p , tp; - if(debug_spr != null) { - debug_spr.Delete(); - debug_spr = null; - } - - debug_spr = DynamicSprite.Create(Screen.Width, Screen.Height, true); - DrawingSurface* surf = debug_spr.GetDrawingSurface(); - surf.Clear(); - surf.DrawingColor = 63811; // red - p = Screen.Viewport.RoomToScreenPoint(Game.Camera.Width/2+Game.Camera.X, Game.Camera.Height/2+Game.Camera.Y, false); - tp = Screen.Viewport.RoomToScreenPoint(_TargetCharacter.x, _TargetCharacter.y, false); - _drawClearRectangle(surf, - p.x - _cam_window_w/2, p.y - _cam_window_h/2, - p.x + _cam_window_w/2, p.y + _cam_window_h/2); - - - if(_TargetCharacter.Loop == 2) surf.DrawLine(tp.x, tp.y, tp.x + _look_ahead_x, tp.y); // right - else if(_TargetCharacter.Loop == 1) surf.DrawLine(tp.x, tp.y, tp.x - _look_ahead_x, tp.y); // left - else if(_TargetCharacter.Loop == 0) surf.DrawLine(tp.x, tp.y, tp.x, tp.y + _look_ahead_y); // down - else if(_TargetCharacter.Loop == 3) surf.DrawLine(tp.x, tp.y, tp.x, tp.y - _look_ahead_y); // up - - surf.DrawString(20, 20, eFontNormal, "Standstill: %d", _count_still_ticks); - - surf.Release(); - - if(ovr != null) { - ovr.Remove(); + Point* p = new Point; + p.x = Game.Camera.X - _off_applied_x; + p.y = Game.Camera.Y - _off_applied_y; + return p; +} + +void _set_camera_position(int x, int y) +{ +// Game.Camera.SetAt(x, y); +// return; + Game.Camera.SetAt(x+_off_x, y+_off_y); + _off_applied_x = _ClampInt(x+_off_x, 0, Room.Width - Game.Camera.Width) -x; + _off_applied_y = _ClampInt(y+_off_y, 0, Room.Height - Game.Camera.Height) -y; +} + +void _enable_parallax(bool enable) { + _ParallaxEnabled = enable; +} + +void _update_target_height() +{ + ViewFrame* c_vf = Game.GetViewFrame(_TargetCharacter.NormalView, 0, 0); + float scaling = IntToFloat(_TargetCharacter.Scaling)/100.00; + _partial_c_height = FloatToInt((IntToFloat(Game.SpriteHeight[c_vf.Graphic])*scaling)/3.0); +} + +void _set_targetcharacter(Character* target) { + if(target.Room <= 0 && player.Room != target.Room) { + if(_DebugShow) AbortGame("Target character must be at the same room as the player character"); + return; } - ovr = Overlay.CreateGraphical(0, 0, debug_spr.Graphic, true); + _TargetCharacter = target; + _update_target_height(); +} + +void _update_target_pos() +{ + _TargetPos.x = IntToFloat(_TargetCharacter.x); + _TargetPos.y = IntToFloat(_TargetCharacter.y) + IntToFloat(_partial_c_height); } +void _updateCameras() +{ + Point* game_camera = _get_camera_position(); + _next_cam_pos.x = IntToFloat(game_camera.x); + _next_cam_pos.y = IntToFloat(game_camera.y); + _cam_pos.Set(_next_cam_pos); +} void doObjectParallax(){ - int camx = FloatToInt(_next_cam_x); - int camy = FloatToInt(_next_cam_y); + int camx = FloatToInt(_next_cam_pos.x); + int camy = FloatToInt(_next_cam_pos.y); for(int i=0; i<_pxo_count; i++){ if(_pxo[i].GetProperty("PxPos") !=0 || _pxo[i].GetProperty("PyPos") != 0) { @@ -218,10 +434,6 @@ void doObjectParallax(){ } } -void _enable_parallax(bool enable) { - _ParallaxEnabled = enable; -} - void _enable_smoothcam(bool enable) { if(enable == true){ doObjectParallax(); @@ -231,138 +443,373 @@ void _enable_smoothcam(bool enable) { _SmoothCamEnabled = enable; } -void _set_targetcharacter(Character* target) { - _TargetCharacter = target; +// -- MOST RELEVANT FUNCTIONS -- + +void _rellax_defaults() +{ + _off_applied_x = 0; + _off_applied_y = 0; + _off_x = 0; + _off_y = 0; + _cam_window_w = 64; + _cam_window_h = 40; + _look_ahead_x = 20; + _look_ahead_y = 0; + _cam_tween_type = eRellaxEaseOutBackTween; + _cam_tween_elapsed = 0.0; + _cam_tween_duration = 1.0; + _cam_lerp_factor_x = 0.01; + _cam_lerp_factor_y = 0.01; + _set_targetcharacter(player); + _enable_parallax(true); + _enable_smoothcam(true); + _AdjustCameraOnRoomLoad = true; } -// ---- Rellax API ------------------------------------------------------------ -void set_TargetCharacter(static Rellax, Character* target) +PointF* _getTargetFocus() +{ + Point* focus = new Point; + + switch(_TargetCharacter.Loop) + { + case 0: focus.y = _look_ahead_y; break; // down + case 1: focus.x = -_look_ahead_x; break; // left + case 2: focus.x = _look_ahead_x; break; // right + case 3: focus.y = -_look_ahead_y; break; // up + case 4: if(_TargetCharacter.DiagonalLoops) {focus.y = 2*_look_ahead_y/3; focus.x = 2*_look_ahead_x/3;} break; // down-right + case 5: if(_TargetCharacter.DiagonalLoops) {focus.y = -2*_look_ahead_y/3; focus.x = 2*_look_ahead_x/3;} break; // up-right + case 6: if(_TargetCharacter.DiagonalLoops) {focus.y = 2*_look_ahead_y/3; focus.x = -2*_look_ahead_x/3;} break; // down-left + case 7: if(_TargetCharacter.DiagonalLoops) {focus.y = -2*_look_ahead_y/3; focus.x = -2*_look_ahead_x/3;} break; // up-left + } + + PointF* focusf = PointF.New(IntToFloat(focus.x), IntToFloat(focus.y)); + + return focusf; +} + +// Perform camera tracking, returning a desired camera position +PointF* _doCameraTracking() +{ + PointF* focus = _getTargetFocus(); + PointF* cam_target = PointF.New(_TargetPos.x + focus.x, _TargetPos.y + focus.y); + Point* game_camera = _get_camera_position(); + + int cam_window_check_x = FloatToInt(cam_target.x)-Game.Camera.Width/2-game_camera.x; + int cam_window_check_y = FloatToInt(cam_target.y)-Game.Camera.Height/2-game_camera.y; + bool is_outside_cam_window = (cam_window_check_x <= -_cam_window_w/2) || (cam_window_check_x > _cam_window_w/2) || + (cam_window_check_y <= -_cam_window_h/2) || (cam_window_check_y > _cam_window_h/2); + + if(is_outside_cam_window){ + cam_target.x = _ClampFloat(cam_target.x - IntToFloat(Game.Camera.Width/2), + 0.0, IntToFloat(Room.Width-Game.Camera.Width)); + cam_target.y = _ClampFloat(cam_target.y - IntToFloat(Game.Camera.Height/2), + 0.0, IntToFloat(Room.Height-Game.Camera.Height)); + } else { + // the camera is inside the window, this means we don't want it to move + cam_target.x = IntToFloat(game_camera.x); + cam_target.y = IntToFloat(game_camera.y); + } + + int c_new_x = FloatToInt(cam_target.x); + int c_new_y = FloatToInt(cam_target.y); + if((_prev_c_x == c_new_x) && (_prev_c_y == c_new_y)) { + _target_stop_counter++; + } else { + _target_stop_counter = 0; + } + _is_target_stopped = _target_stop_counter > 2; + _prev_c_x = c_new_x; + _prev_c_y = c_new_y; + return cam_target; +} + +// debug helper function +void _drawDebugOverlay() +{ + if(_debug_spr == null) + return; + + DrawingSurface* surf = _debug_spr.GetDrawingSurface(); + surf.Clear(); + + // Calculate screen coordinates for camera and target character positions, in rellax terms + Point* cam_pos = Screen.Viewport.RoomToScreenPoint(Game.Camera.Width/2+Game.Camera.X, Game.Camera.Height/2+Game.Camera.Y, false); + Point* target_posi = Screen.Viewport.RoomToScreenPoint(_TargetCharacter.x, _TargetCharacter.y, false); + PointF* target_pos = PointF.New(IntToFloat(target_posi.x), IntToFloat(target_posi.y)); + + surf.DrawingColor = 63811; // red + + // Draw rectangle of the camera window + _drawClearRectangle(surf, + cam_pos.x - _cam_window_w/2, cam_pos.y - _cam_window_h/2, + cam_pos.x + _cam_window_w/2, cam_pos.y + _cam_window_h/2); + + // Draw line in direction that target character is facing, length matches look ahead + PointF* focus = _getTargetFocus(); + surf.DrawLine(FloatToInt(target_pos.x), FloatToInt(target_pos.y), FloatToInt(target_pos.x + focus.x), FloatToInt(target_pos.y + focus.y)); + + + // let's draw screen limits + surf.DrawingColor = 63935; // pink + + Point* top_left_limit = Screen.Viewport.RoomToScreenPoint(Game.Camera.Width/2, Game.Camera.Height/2, false); + Point* bottom_righht_limit = Screen.Viewport.RoomToScreenPoint(Room.Width - Game.Camera.Width/2, Room.Width - Game.Camera.Height/2, false); + _drawClearRectangle(surf, + top_left_limit.x, top_left_limit.y, + bottom_righht_limit.x, bottom_righht_limit.y); + + // let's make sure the actual camera is positioned where we think it should be + surf.DrawingColor = 65506; // yellow + Point* next_posi = Screen.Viewport.RoomToScreenPoint(FloatToInt(_next_cam_pos.x), FloatToInt(_next_cam_pos.y), false); + + surf.DrawLine(next_posi.x -2, next_posi.y -2, next_posi.x +2 , next_posi.y + 2); + + surf.DrawLine(next_posi.x, next_posi.y -4, next_posi.x, next_posi.y + 4); + surf.DrawLine(next_posi.x-4, next_posi.y, next_posi.x+4, next_posi.y); + surf.DrawString(5, 5, eFontNormal, "_next_cam_pos (%.2f, %.2f)", _next_cam_pos.x, _next_cam_pos.y); + + surf.Release(); + + if (_debug_ovr != null) { + _debug_ovr.Remove(); + _debug_ovr = null; + } + + _debug_ovr = Overlay.CreateGraphical(0, 0, _debug_spr.Graphic, true); + #ifdef SCRIPT_API_v360 + _debug_ovr.ZOrder = 9999999; + #endif +} + +void _quickAdjustToTarget() +{ + PointF* cam_target = _doCameraTracking(); + _next_cam_pos.Set(cam_target); + _cam_pos.Set(_next_cam_pos); + _set_camera_position(FloatToInt(cam_target.x), FloatToInt(cam_target.y)); +} + +void doSmoothCameraTracking() +{ + PointF* cam_target = _doCameraTracking(); + Point* game_camera = _get_camera_position(); + + if(_is_target_stopped ) { + if(_tween_cam_origin == null) { + _tween_cam_origin = PointF.New(IntToFloat(game_camera.x), IntToFloat(game_camera.y)); + _cam_tween_elapsed = 0.0; + } else { + if(_cam_tween_elapsed < _cam_tween_duration) { + _cam_tween_elapsed += 1.0/IntToFloat(GetGameSpeed()) ; + } else { + _cam_tween_elapsed = _cam_tween_duration; + } + } + } + else { + _tween_cam_origin = null; + } + + if(FloatToInt(cam_target.x) != game_camera.x || FloatToInt(cam_target.y) != game_camera.y) + { + if(_tween_cam_origin != null) + { + if(_cam_tween_elapsed < _cam_tween_duration) + { + float tween_factor = _rellax_tween_GetValue(_cam_tween_elapsed, _cam_tween_duration, _cam_tween_type); + _next_cam_pos = _LerpPF(_tween_cam_origin, cam_target, tween_factor); + } else { + _next_cam_pos.x = cam_target.x; + _next_cam_pos.y = cam_target.y; + } + } + else + { + if(_cam_lerp_factor_x > 0.04 && _cam_lerp_factor_y > 0.04) + { + _next_cam_pos = _LerpPFEx(_cam_pos, cam_target, _cam_lerp_factor_x, _cam_lerp_factor_y); + } + else + { + float d_x = _ClampFloat( Maths.Sqrt((_cam_pos.x-cam_target.x)*(_cam_pos.x-cam_target.x)/4.0), 1.0, 8.0); + float d_y = _ClampFloat( Maths.Sqrt((_cam_pos.y-cam_target.y)*(_cam_pos.y-cam_target.y)/4.0), 1.0, 8.0); + float clfx = 0.4/d_x; + float clfy = 0.4/d_y; + + _next_cam_pos = _LerpPFEx(_cam_pos, cam_target, clfx, clfy); + } + } + } +} + +void _enable_debug_overlay(bool enable) { + if (!enable && _debug_ovr != null) { + _debug_ovr.Remove(); + _debug_ovr = null; + } + + if (_debug_spr != null) { + _debug_spr.Delete(); + _debug_spr = null; + } + + if(enable) { + _debug_spr = DynamicSprite.Create(Screen.Width, Screen.Height, true); + } + + _DebugShow = enable; +} + +// ---- Rellax API ------------------------------------------------------------ + +void set_TargetCharacter(this Rellax*, Character* target) +{ _set_targetcharacter(target); } -Character* get_TargetCharacter(static Rellax) +Character* get_TargetCharacter(this Rellax*) { return _TargetCharacter; } -void set_EnableParallax(static Rellax, bool enable) +void set_EasingType(this Rellax*, RellaxTweenEasingType value) +{ + _cam_tween_type = value; +} + +RellaxTweenEasingType get_EasingType(this Rellax*) +{ + return _cam_tween_type; +} + +void set_TweenDuration(this Rellax*, float value) +{ + _cam_tween_duration = value; +} + +float get_TweenDuration(this Rellax*) +{ + return _cam_tween_duration; +} + +void set_EnableParallax(this Rellax*, bool enable) { _enable_parallax(enable); } -bool get_EnableParallax(static Rellax) +bool get_EnableParallax(this Rellax*) { return _ParallaxEnabled; } -void set_EnableSmoothCam(static Rellax, bool enable) +void set_DebugShow(this Rellax*, bool enable) +{ + _enable_debug_overlay(enable); +} + +bool get_DebugShow(this Rellax*) +{ + return _DebugShow; +} + +void set_EnableSmoothCam(this Rellax*, bool enable) { _enable_smoothcam(enable); } -bool get_EnableSmoothCam(static Rellax) +bool get_EnableSmoothCam(this Rellax*) { return _SmoothCamEnabled; } -void set_AdjustCameraOnRoomLoad(static Rellax, bool enable) +void set_AdjustCameraOnRoomLoad(this Rellax*, bool enable) { _AdjustCameraOnRoomLoad = enable; } -bool get_AdjustCameraOnRoomLoad(static Rellax) +bool get_AdjustCameraOnRoomLoad(this Rellax*) { return _AdjustCameraOnRoomLoad; } -void set_CameraOffsetX(static Rellax, int offset_x) +void set_CameraOffsetX(this Rellax*, int offset_x) { _off_x = offset_x; } -int get_CameraOffsetX(static Rellax) +int get_CameraOffsetX(this Rellax*) { return _off_x; } -void set_CameraOffsetY(static Rellax, int offset_y) +void set_CameraOffsetY(this Rellax*, int offset_y) { _off_y = offset_y; } -int get_CameraOffsetY(static Rellax) +int get_CameraOffsetY(this Rellax*) { return _off_y; } -void set_CameraLookAheadX(static Rellax, int look_ahead_x) +void set_CameraLookAheadX(this Rellax*, int look_ahead_x) { _look_ahead_x = look_ahead_x; } -int get_CameraLookAheadX(static Rellax) +int get_CameraLookAheadX(this Rellax*) { return _look_ahead_x; } -void set_CameraLookAheadY(static Rellax, int look_ahead_y) +void set_CameraLookAheadY(this Rellax*, int look_ahead_y) { _look_ahead_y = look_ahead_y; } -int get_CameraLookAheadY(static Rellax) +int get_CameraLookAheadY(this Rellax*) { return _look_ahead_y; } -void set_StandstillCameraDelayY(static Rellax, int value) -{ - _standstill_ticks_y = value; -} - -int get_StandstillCameraDelayY(static Rellax) -{ - return _standstill_ticks_y; -} - -void set_CameraLerpFactorX(static Rellax, float value) +void set_CameraLerpFactorX(this Rellax*, float value) { _cam_lerp_factor_x = value; } -float get_CameraLerpFactorX(static Rellax) +float get_CameraLerpFactorX(this Rellax*) { return _cam_lerp_factor_x; } -void set_CameraLerpFactorY(static Rellax, float value) +void set_CameraLerpFactorY(this Rellax*, float value) { _cam_lerp_factor_y = value; } -float get_CameraLerpFactorY(static Rellax) +float get_CameraLerpFactorY(this Rellax*) { return _cam_lerp_factor_y; } -void set_CameraWindowWidth(static Rellax, int value) +void set_CameraWindowWidth(this Rellax*, int value) { _cam_window_w = value; } -int get_CameraWindowWidth(static Rellax) +int get_CameraWindowWidth(this Rellax*) { return _cam_window_w; } -void set_CameraWindowHeight(static Rellax, int value) +void set_CameraWindowHeight(this Rellax*, int value) { _cam_window_h = value; } -int get_CameraWindowHeight(static Rellax) +int get_CameraWindowHeight(this Rellax*) { return _cam_window_h; } @@ -379,74 +826,64 @@ void doSetOrigins () for(int i=0; i>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> // Before starting, you must create the following Custom Properties @@ -49,34 +49,72 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. + +enum RellaxTweenEasingType { + eRellaxEaseLinearTween, + eRellaxEaseInSineTween, + eRellaxEaseOutSineTween, + eRellaxEaseInOutSineTween, + eRellaxEaseInQuadTween, + eRellaxEaseOutQuadTween, + eRellaxEaseInOutQuadTween, + eRellaxEaseInCubicTween, + eRellaxEaseOutCubicTween, + eRellaxEaseInOutCubicTween, + eRellaxEaseInQuartTween, + eRellaxEaseOutQuartTween, + eRellaxEaseInOutQuartTween, + eRellaxEaseInQuintTween, + eRellaxEaseOutQuintTween, + eRellaxEaseInOutQuintTween, + eRellaxEaseInCircTween, + eRellaxEaseOutCircTween, + eRellaxEaseInOutCircTween, + eRellaxEaseInExpoTween, + eRellaxEaseOutExpoTween, + eRellaxEaseInOutExpoTween, + eRellaxEaseInBackTween, + eRellaxEaseOutBackTween, + eRellaxEaseInOutBackTween, + eRellaxEaseInElasticTween, + eRellaxEaseOutElasticTween, + eRellaxEaseInOutElasticTween, + eRellaxEaseInBounceTween, + eRellaxEaseOutBounceTween, + eRellaxEaseInOutBounceTween +}; + struct Rellax { /// The character being tracked by the Game.Camera. import static attribute Character* TargetCharacter; + /// gets/sets the camera tween type to use when the character is stopped + import static attribute RellaxTweenEasingType EasingType; + + /// gets/sets the camera tween duration once the character is stopped + import static attribute float TweenDuration; + /// gets/sets whether Parallax is on or off. import static attribute bool EnableParallax; /// gets/sets whether Smooth Camera tracking is on or off. import static attribute bool EnableSmoothCam; - + /// if Smooth Camera is on, gets/sets whether to instantly adjust camera to target on room before fade in. import static attribute bool AdjustCameraOnRoomLoad; - - /// gets/sets camera horizontal offset + + /// gets/sets camera horizontal offset, it's applied in the next frame as soon as possible import static attribute int CameraOffsetX; - /// gets/sets camera vertical offset + /// gets/sets camera vertical offset, it's applied in the next frame as soon as possible import static attribute int CameraOffsetY; - + /// gets/sets camera horizontal lookahead offset import static attribute int CameraLookAheadX; /// gets/sets camera vertical lookahead offset import static attribute int CameraLookAheadY; - - /// gets/sets number of frames to wait before adjusting the Y axis quicker after the player is still - import static attribute int StandstillCameraDelayY; - + /// gets/sets the factore the camera should use when interpolating in the X axis import static attribute float CameraLerpFactorX; @@ -88,4 +126,7 @@ struct Rellax { /// gets/sets the camera window height that is centered on the player, when the target is outside of the window, the camera moves to keep it inside import static attribute int CameraWindowHeight; + + /// gets/sets whether Debug overlay is shown is on or off. + import static attribute bool DebugShow; }; diff --git a/rellax.scm b/rellax.scm index 449a70c334c8249f4a6bd64a32e728e2d11067a8..f816e8f8b4cfb7c8184ee1347befab98294333d6 100644 GIT binary patch literal 35027 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 diff --git a/rellax.xml b/rellax.xml index 5a2fad1..8f7dd7f 100644 --- a/rellax.xml +++ b/rellax.xml @@ -3,6 +3,6 @@ rellax Rellax while the Camera tracks with cool parallax. eri0o - 0.2.2 + 0.3.0 1909973127