Example #1
0
        private void CreateSpriteRendererAndAnimator(
            out IComponentSpriteRenderer spriteRenderer,
            out ClientComponentSpriteSheetAnimator componentAnimatorFlash,
            byte atlasRow,
            IProtoItemWeaponRanged protoWeapon,
            TextureAtlasResource muzzleFlashTextureAtlas)
        {
            var animationFrameDurationSeconds = this.animationDuration
                                                / (double)muzzleFlashTextureAtlas.AtlasSize.ColumnsCount;

            // create sprite renderer
            spriteRenderer = Api.Client.Rendering.CreateSpriteRenderer(
                this.SceneObject,
                textureResource: TextureResource.NoTexture,
                // draw in the same layer as the skeleton renderer
                // (cannot do Default+1 here as it will produce wrong result over other objects)
                drawOrder: DrawOrder.Default,
                rotationAngleRad: 0,
                // align sprite by left side and centered vertical
                spritePivotPoint: (0, 0.5),
                scale: (float)this.description.TextureScale);

            // to ensure muzzleflash rendering over skeleton renderer we do this offset
            // TODO: find a better way of prioritizing rendering of muzzle flash over skeleton renderer
            spriteRenderer.DrawOrderOffsetY = -0.2;

            // create animator for sprite renderer
            componentAnimatorFlash = this.SceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();
            componentAnimatorFlash.Setup(
                spriteRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    muzzleFlashTextureAtlas,
                    onlySpecificRow: atlasRow),
                animationFrameDurationSeconds);
        }
Example #2
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            // setup animation
            var animationFrameDurationSeconds = 1 / 8.0;
            var clientState = data.ClientState;

            var textureAtlas = SharedIsAlternativeVariant(data.GameObject.TilePosition)
                                   ? this.textureAtlas1
                                   : this.textureAtlas2;

            data.GameObject
            .ClientSceneObject
            .AddComponent <ClientComponentSpriteSheetAnimator>()
            .Setup(
                clientState.Renderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(textureAtlas),
                isLooped: true,
                frameDurationSeconds: animationFrameDurationSeconds,
                randomizeInitialFrame: true);

            if (!data.GameObject.OccupiedTile.StaticObjects.Any(
                    o => o.ProtoStaticWorldObject is IProtoObjectExtractor))
            {
                // create sound emitter as there is no extractor
                clientState.SoundEmitter = Client.Audio.CreateSoundEmitter(
                    data.GameObject,
                    SoundResourceActive,
                    isLooped: true,
                    volume: 0.5f,
                    radius: 1.5f);
                clientState.SoundEmitter.CustomMaxDistance = 5;
            }
        }
Example #3
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            // setup animation
            var animationFrameDurationSeconds = 1 / 8.0;

            Client.Scene
            .GetSceneObject(data.GameObject)
            .AddComponent <ClientComponentSpriteSheetAnimator>()
            .Setup(
                data.ClientState.Renderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    (ITextureAtlasResource)this.DefaultTexture),
                frameDurationSeconds: animationFrameDurationSeconds,
                randomizeInitialFrame: true);

            if (!data.GameObject.OccupiedTile.StaticObjects.Any(
                    o => o.ProtoStaticWorldObject is IProtoObjectExtractor))
            {
                // create sound emitter as there is no extractor
                data.ClientState.SoundEmitter = Client.Audio.CreateSoundEmitter(
                    data.GameObject,
                    SoundResourceActive,
                    isLooped: true,
                    volume: 0.5f,
                    radius: 1.5f);
                data.ClientState.SoundEmitter.CustomMaxDistance = 5;
            }
        }
Example #4
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);
            data.ClientState.Renderer.DrawOrderOffsetY = 0.355;

            // add sprite sheet animation
            var sceneObject = data.GameObject.ClientSceneObject;

            sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>()
            .Setup(data.ClientState.Renderer,
                   ClientComponentSpriteSheetAnimator.CreateAnimationFrames(this.atlasTexture),
                   isLooped: true,
                   frameDurationSeconds: 3 / 60.0);

            // add light source at the firing fuse
            var lightSource = ClientLighting.CreateLightSourceSpot(
                sceneObject,
                LightColors.WoodFiring,
                size: (3, 3),
                logicalSize: 0,
                positionOffset: (0.7, 0.7));

            // add light flickering
            sceneObject.AddComponent <ClientComponentLightSourceEffectFlickering>()
            .Setup(lightSource,
                   flickeringPercents: 10,
                   flickeringChangePercentsPerSecond: 70);
        }
Example #5
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            // setup animation
            var animationFrameDurationSeconds = 1 / 8.0;

            Client.Scene
            .GetSceneObject(data.GameObject)
            .AddComponent <ClientComponentSpriteSheetAnimator>()
            .Setup(
                data.ClientState.Renderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    (ITextureAtlasResource)this.DefaultTexture),
                frameDurationSeconds: animationFrameDurationSeconds);

            // create sound emitter
            data.ClientState.SoundEmitter = Client.Audio.CreateSoundEmitter(
                data.GameObject,
                SoundResourceActive,
                isLooped: true,
                volume: 0.5f,
                radius: 1.5f);
            data.ClientState.SoundEmitter.CustomMaxDistance = 5;
        }
Example #6
0
        private void CreateSpriteRendererAndAnimator(
            out IComponentSpriteRenderer spriteRenderer,
            out ClientComponentSpriteSheetAnimator componentAnimatorFlash,
            byte atlasRow,
            TextureAtlasResource muzzleFlashTextureAtlas)
        {
            var animationFrameDurationSeconds = this.animationDuration
                                                / (double)muzzleFlashTextureAtlas.AtlasSize.ColumnsCount;

            // create sprite renderer
            spriteRenderer = Api.Client.Rendering.CreateSpriteRenderer(
                this.SceneObject,
                textureResource: TextureResource.NoTexture,
                // draw in the same layer as the skeleton renderer
                // (cannot do Default+1 here as it will produce wrong result over other objects)
                drawOrder: DrawOrder.Default,
                rotationAngleRad: 0,
                // align sprite by left side and centered vertical
                spritePivotPoint: (0, 0.5),
                scale: (float)this.description.TextureScale);

            // create animator for sprite renderer
            componentAnimatorFlash = this.SceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();
            componentAnimatorFlash.Setup(
                spriteRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    muzzleFlashTextureAtlas,
                    onlySpecificRow: atlasRow),
                isLooped: false,
                animationFrameDurationSeconds,
                isManualUpdate: true);
        }
Example #7
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            var renderer = data.ClientState.Renderer;

            renderer.DrawOrder = DrawOrder.Floor;

            var overlayRenderer = Client.Rendering.CreateSpriteRenderer(
                data.GameObject,
                TextureResource.NoTexture);

            this.ClientSetupRenderer(overlayRenderer);

            overlayRenderer.PositionOffset = renderer.PositionOffset
                                             + this.textureAltasAnimationDrawPositionWorldOffset;
            overlayRenderer.SpritePivotPoint = renderer.SpritePivotPoint;
            overlayRenderer.Scale            = renderer.Scale;
            overlayRenderer.DrawOrder        = renderer.DrawOrder + 1;

            Client.Scene
            .GetSceneObject(data.GameObject)
            .AddComponent <ClientComponentSpriteSheetBlendAnimator>()
            .Setup(
                overlayRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(this.textureAtlasAnimation),
                frameDurationSeconds: this.textureAtlasAnimationFrameDurationSeconds);
        }
Example #8
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            var tilePosition = data.GameObject.TilePosition;
            var renderer     = data.ClientState.Renderer;

            if (ClientGroundExplosionAnimationHelper.IsGroundSpriteFlipped(tilePosition))
            {
                renderer.DrawMode = DrawMode.FlipHorizontally;
            }

            if (ClientGroundExplosionAnimationHelper.HasActiveExplosion(tilePosition))
            {
                // this is a fresh charred ground, animate the ground sprite
                var animationDuration      = ClientGroundExplosionAnimationHelper.ExplosionGroundDuration;
                var framesTextureResources = ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    ClientGroundExplosionAnimationHelper.ExplosionGroundTextureAtlas);
                var componentAnimator = renderer.SceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();
                componentAnimator.Setup(renderer,
                                        framesTextureResources,
                                        frameDurationSeconds: animationDuration / framesTextureResources.Length);

                componentAnimator.IsLooped = false;
                componentAnimator.Destroy(1.5 * animationDuration);
            }
        }
 public static void Setup(
     IComponentSpriteRenderer spriteRenderer,
     ITextureAtlasResource textureAtlas,
     double totalDuration)
 {
     Setup(spriteRenderer,
           ClientComponentSpriteSheetAnimator.CreateAnimationFrames(textureAtlas),
           totalDuration);
 }
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);
            var worldObject = data.GameObject;

            // create sound emitter
            data.ClientState.SoundEmitter = Client.Audio.CreateSoundEmitter(
                data.GameObject,
                SoundResourceActive,
                isLooped: true,
                volume: 0.5f,
                radius: 1.5f);
            data.ClientState.SoundEmitter.CustomMaxDistance = 4f;

            // add fan sprite renderers
            var randomizer = (int)(PositionHashHelper.GetHashUInt32(worldObject.TilePosition.X,
                                                                    worldObject.TilePosition.Y)
                                   % 128);

            AddFanRenderer((41 / 256.0, 253 / 256.0), frameOffset: randomizer, out var componentAnimator1);
            AddFanRenderer((137 / 256.0, 253 / 256.0), frameOffset: randomizer + 2, out var componentAnimator2);

            // sound and animation are played only if there is a power grid (a land claim area)
            var isActive = LandClaimSystem.SharedIsObjectInsideAnyArea(worldObject);

            componentAnimator1.IsEnabled
                    = componentAnimator2.IsEnabled
                    = data.ClientState.SoundEmitter.IsEnabled
                    = isActive;

            void AddFanRenderer(
                Vector2D vector2D,
                int frameOffset,
                out ClientComponentSpriteSheetAnimator componentAnimator)
            {
                var overlaySpriteRenderer = Client.Rendering.CreateSpriteRenderer(
                    worldObject,
                    positionOffset: vector2D,
                    spritePivotPoint: (0, 0),
                    drawOrder: data.ClientState.Renderer.DrawOrder);

                overlaySpriteRenderer.DrawOrderOffsetY = -vector2D.Y
                                                         - 0.01
                                                         + data.ClientState.Renderer.DrawOrderOffsetY;

                // add sprite sheet animation for fan sprite
                var sceneObject = worldObject.ClientSceneObject;

                componentAnimator = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();
                componentAnimator.Setup(overlaySpriteRenderer,
                                        ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                                            this.textureAtlasActive),
                                        isLooped: true,
                                        frameDurationSeconds: 5 / 60.0,
                                        initialFrameOffset: frameOffset);
            }
        }
Example #11
0
        protected virtual void ClientSetupExtractorActiveAnimation(
            IStaticWorldObject worldObject,
            ObjectManufacturerPublicState serverPublicState,
            ITextureAtlasResource textureAtlasResource,
            Vector2D positionOffset,
            double frameDurationSeconds,
            bool autoInverseAnimation      = false,
            bool randomizeInitialFrame     = false,
            bool playAnimationSounds       = true,
            OnRefreshActiveState onRefresh = null)
        {
            var clientState = worldObject.GetClientState <StaticObjectClientState>();
            var sceneObject = worldObject.ClientSceneObject;

            var overlayRenderer = Client.Rendering.CreateSpriteRenderer(
                sceneObject,
                TextureResource.NoTexture,
                DrawOrder.Default,
                positionOffset: positionOffset,
                spritePivotPoint: Vector2D.Zero);

            overlayRenderer.DrawOrderOffsetY = -positionOffset.Y - 0.01;

            var spriteSheetAnimator = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();

            spriteSheetAnimator.Setup(
                overlayRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    textureAtlasResource,
                    autoInverse: autoInverseAnimation),
                isLooped: true,
                frameDurationSeconds: frameDurationSeconds,
                randomizeInitialFrame: randomizeInitialFrame);

            // we play Active sound for pumping on both up and down position of the oil pump
            var componentActiveState = sceneObject.AddComponent <ClientComponentOilPumpActiveState>();

            componentActiveState.Setup(overlayRenderer, spriteSheetAnimator, worldObject, playAnimationSounds);

            serverPublicState.ClientSubscribe(
                s => s.IsActive,
                callback: RefreshActiveState,
                subscriptionOwner: clientState);

            spriteSheetAnimator.IsEnabled = overlayRenderer.IsEnabled = false;

            RefreshActiveState(serverPublicState.IsActive);

            void RefreshActiveState(bool isActive)
            {
                // ReSharper disable once PossibleNullReferenceException
                componentActiveState.IsActive = isActive;
                onRefresh?.Invoke(isActive);
            }
        }
        protected void ClientSetupManufacturerActiveAnimation(
            IStaticWorldObject worldObject,
            ObjectManufacturerPublicState serverPublicState,
            ITextureAtlasResource textureAtlasResource,
            Vector2D positionOffset,
            double frameDurationSeconds,
            double drawOrderOffsetY        = 0,
            bool autoInverseAnimation      = false,
            OnRefreshActiveState onRefresh = null)
        {
            var clientState = worldObject.GetClientState <StaticObjectClientState>();

            var sceneObject = Client.Scene.GetSceneObject(worldObject);

            var overlayRenderer = Client.Rendering.CreateSpriteRenderer(
                sceneObject,
                TextureResource.NoTexture,
                DrawOrder.Default,
                positionOffset: positionOffset,
                spritePivotPoint: Vector2D.Zero);

            overlayRenderer.DrawOrderOffsetY = drawOrderOffsetY - positionOffset.Y - 0.01;

            var spriteSheetAnimator = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();

            spriteSheetAnimator.Setup(
                overlayRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    textureAtlasResource,
                    autoInverse: autoInverseAnimation),
                frameDurationSeconds: frameDurationSeconds);

            var soundEmitterActiveState = this.ClientCreateActiveStateSoundEmitterComponent(worldObject, sceneObject);

            serverPublicState.ClientSubscribe(
                s => s.IsManufacturingActive,
                callback: RefreshActiveState,
                subscriptionOwner: clientState);

            RefreshActiveState(serverPublicState.IsManufacturingActive);

            void RefreshActiveState(bool isActive)
            {
                overlayRenderer.IsEnabled     = isActive;
                spriteSheetAnimator.IsEnabled = isActive;
                if (soundEmitterActiveState != null)
                {
                    soundEmitterActiveState.IsEnabled = isActive;
                }

                onRefresh?.Invoke(isActive);
            }
        }
Example #13
0
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);

            var worldObject = data.GameObject;
            var publicState = data.PublicState;
            var renderer    = data.ClientState.Renderer;
            var sceneObject = worldObject.ClientSceneObject;

            PowerGridSystem.ClientInitializeConsumerOrProducer(worldObject);
            var soundEmitter = Client.Audio.CreateSoundEmitter(
                worldObject,
                SoundResourceActive,
                is3D: true,
                volume: 1.0f,
                isLooped: true);

            soundEmitter.CustomMinDistance = 3.0f;
            soundEmitter.CustomMaxDistance = 6.0f;

            // add sprite sheet animation for fan sprite
            var componentAnimator = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();

            componentAnimator.Setup(renderer,
                                    ClientComponentSpriteSheetAnimator.CreateAnimationFrames(this.textureAtlasActive)
                                    .Skip(1)
                                    .ToArray(),
                                    isLooped: true,
                                    frameDurationSeconds: 6 / 60.0,
                                    randomizeInitialFrame: true);

            publicState.ClientSubscribe(
                _ => _.ElectricityConsumerState,
                _ => RefreshActiveState(),
                data.ClientState);

            RefreshActiveState();

            void RefreshActiveState()
            {
                var isActive = publicState.ElectricityConsumerState
                               == ElectricityConsumerState.PowerOnActive;

                componentAnimator.IsEnabled = isActive;
                soundEmitter.IsEnabled      = isActive;

                if (!isActive)
                {
                    // reset to default texture
                    renderer.TextureResource = this.DefaultTexture;
                }
            }
        }
Example #14
0
        protected override void ClientSetupSkeletonAnimation(
            bool isActive,
            IItem item,
            ICharacter character,
            IComponentSkeleton skeletonRenderer,
            List <IClientComponent> skeletonComponents)
        {
            base.ClientSetupSkeletonAnimation(isActive, item, character, skeletonRenderer, skeletonComponents);

            if (!isActive)
            {
                return;
            }

            // create fire sprite renderer
            var sceneObject  = skeletonRenderer.SceneObject;
            var fireRenderer = Client.Rendering.CreateSpriteRenderer(
                sceneObject,
                TextureResource.NoTexture,
                // draw origin is calculated so that the center
                // of the flame sprite will always stay on the end of the torch item
                spritePivotPoint: (0.5, 0.277),
                // please note: the torch X axis ("Weapon" slot) is oriented up because torch is held this way!
                positionOffset: (0.35, 0),
                scale: 0.9);

            var fireAnimator = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();

            fireAnimator.Setup(
                fireRenderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(TextureAtlasFire),
                isLooped: true,
                frameDurationSeconds: 1 / 15.0,
                randomizeInitialFrame: true);

            var slotNameClone = "Weapon_Torch";

            skeletonRenderer.CloneSlot("Weapon", slotNameClone);
            skeletonRenderer.SetAttachmentRenderer(slotNameClone,
                                                   "WeaponMelee",
                                                   fireRenderer,
                                                   applyBoneRotation: false);

            skeletonRenderer.SetAttachment(slotNameClone, "WeaponMelee");

            skeletonComponents.Add(fireRenderer);
            skeletonComponents.Add(fireAnimator);
        }
        public WeaponHitSparksPreset Add(
            ObjectMaterial material,
            double texturePivotY,
            TextureAtlasResource texture,
            Color?lightColor                   = null,
            bool useScreenBlending             = false,
            bool allowRandomizedHitPointOffset = true)
        {
            var frames = ClientComponentSpriteSheetAnimator.CreateAnimationFrames(texture);

            this.HitSparksPreset[material] = new HitSparksEntry(frames,
                                                                lightColor,
                                                                useScreenBlending,
                                                                texturePivotY,
                                                                allowRandomizedHitPointOffset);
            return(this);
        }
Example #16
0
            public void Setup(
                IComponentSpriteRenderer rocketRenderer,
                IComponentSpriteRenderer mast1Renderer,
                IComponentSpriteRenderer mast2Renderer,
                double timePassedSinceLaunch)
            {
                this.rocketRenderer = rocketRenderer;
                this.time           = timePassedSinceLaunch - RocketLaunchAnimationStartDelay;
                this.mast1Renderer  = mast1Renderer;
                this.mast2Renderer  = mast2Renderer;

                this.defaultRocketRendererPositionOffset   = rocketRenderer.PositionOffset;
                this.defaultRocketRendererDrawOrderOffsetY = rocketRenderer.DrawOrderOffsetY;

                this.defaultMast1RendererPositionOffset = mast1Renderer.PositionOffset;
                this.defaultMast2RendererPositionOffset = mast2Renderer.PositionOffset;

                var sceneObject = rocketRenderer.SceneObject;

                this.fireAnimators  = new ClientComponentSpriteSheetAnimator[RocketFireOffsets.Length];
                this.lightRenderers = new BaseClientComponentLightSource[RocketFireOffsets.Length];
                for (var index = 0; index < this.fireAnimators.Length; index++)
                {
                    var fireRenderer = Client.Rendering.CreateSpriteRenderer(sceneObject,
                                                                             TextureResourceRocketFireAnimation);

                    this.fireAnimators[index] = sceneObject.AddComponent <ClientComponentSpriteSheetAnimator>();
                    this.fireAnimators[index].Setup(fireRenderer,
                                                    ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                                                        TextureResourceRocketFireAnimation),
                                                    isLooped: true,
                                                    frameDurationSeconds: 1 / 30.0,
                                                    randomizeInitialFrame: true);

                    var lightRender = ClientLighting.CreateLightSourceSpot(sceneObject,
                                                                           EngineLightSourceConfig);
                    this.lightRenderers[index] = lightRender;
                }

                // toggle rocket renderer to ensure it's drawn on front
                rocketRenderer.IsEnabled = false;
                rocketRenderer.IsEnabled = true;

                this.Update(0);
            }
        public WeaponHitSparksPreset SetDefault(
            double texturePivotY,
            TextureAtlasResource texture,
            Color?lightColor                   = null,
            bool useScreenBlending             = false,
            bool allowRandomizedHitPointOffset = true)
        {
            var frames = ClientComponentSpriteSheetAnimator.CreateAnimationFrames(texture);

            foreach (var soundMaterial in AllSoundMaterials)
            {
                this.HitSparksPreset[soundMaterial] = new HitSparksEntry(frames,
                                                                         lightColor,
                                                                         useScreenBlending,
                                                                         texturePivotY,
                                                                         allowRandomizedHitPointOffset);
            }

            return(this);
        }
        protected override void ClientInitialize(ClientInitializeData data)
        {
            base.ClientInitialize(data);
            data.ClientState.Renderer.DrawOrderOffsetY = 0.355;

            // add sprite sheet animation
            var sceneObject = Client.Scene.GetSceneObject(data.GameObject);

            sceneObject
            .AddComponent <ClientComponentSpriteSheetAnimator>()
            .Setup(data.ClientState.Renderer,
                   ClientComponentSpriteSheetAnimator.CreateAnimationFrames(this.atlasTexture),
                   frameDurationSeconds: 0.15f);

            // add light source at the firing fuse
            ClientLighting.CreateLightSourceSpot(
                sceneObject,
                Color.FromRgb(0xFF, 0x55, 0x22),
                size: (3, 3),
                positionOffset: (0.4, 0.4));
        }
Example #19
0
        private void ClientSetupDoor(ClientInitializeData data)
        {
            var sceneObject      = data.GameObject.ClientSceneObject;
            var publicState      = data.PublicState;
            var clientState      = data.ClientState;
            var isHorizontalDoor = publicState.IsHorizontalDoor;
            var isOpened         = publicState.IsOpened;

            var atlas = isHorizontalDoor
                            ? this.AtlasTextureHorizontal
                            : this.AtlasTextureVertical;
            var renderer = Client.Rendering.CreateSpriteRenderer(
                sceneObject,
                atlas.Chunk(0, 0),
                drawOrder: DrawOrder.Default);

            renderer.PositionOffset = (0,
                                       isHorizontalDoor
                                           ? DrawWorldOffsetYHorizontalDoor
                                           : DrawWorldOffsetYVerticalDoor);
            renderer.DrawOrderOffsetY = isHorizontalDoor
                                            ? WallPatterns.DrawOffsetNormal - DrawWorldOffsetYHorizontalDoor
                                            : this.DoorSizeTiles + 0.1 - DrawWorldOffsetYVerticalDoor;

            var spriteSheetAnimator = sceneObject.AddComponent <ClientComponentDoorSpriteSheetAnimator>();
            var atlasColumnsCount   = atlas.AtlasSize.ColumnsCount;

            clientState.Renderer?.Destroy();
            clientState.Renderer = renderer;
            clientState.SpriteAnimator?.Destroy();
            clientState.SpriteAnimator = spriteSheetAnimator;

            clientState.DoorBaseRenderer?.Destroy();
            clientState.DoorBaseRenderer = null;

            clientState.DoorVerticalFrontPartRenderer?.Destroy();
            clientState.DoorVerticalFrontPartRenderer = null;

            if (isHorizontalDoor)
            {
                // add extra sprite renderer for horizontal door - door base
                clientState.DoorBaseRenderer = Client.Rendering.CreateSpriteRenderer(
                    sceneObject,
                    this.TextureBaseHorizontal,
                    drawOrder: DrawOrder.Floor + 1,
                    positionOffset: (0, DrawWorldOffsetYHorizontalDoor),
                    spritePivotPoint: (0, 0));
            }
            else
            {
                // add extra sprite renderer for vertical door - door base
                clientState.DoorBaseRenderer = Client.Rendering.CreateSpriteRenderer(
                    sceneObject,
                    atlas.Chunk((byte)(atlasColumnsCount - 1), 0),
                    drawOrder: DrawOrder.Floor + 1,
                    positionOffset: (0, DrawWorldOffsetYVerticalDoor));

                // add extra sprite renderer for vertical door - side door front sprite
                clientState.DoorVerticalFrontPartRenderer = Client.Rendering.CreateSpriteRenderer(
                    sceneObject,
                    atlas.Chunk((byte)(atlasColumnsCount - 2), 0),
                    drawOrder: DrawOrder.Default,
                    positionOffset:
                    (0, DrawWorldOffsetYVerticalDoor));
            }

            var framesCount = isHorizontalDoor
                                  ? atlasColumnsCount
                                  : atlasColumnsCount - 2;

            spriteSheetAnimator.Setup(
                renderer,
                ClientComponentSpriteSheetAnimator.CreateAnimationFrames(
                    atlas,
                    columns: (byte)framesCount),
                frameDurationSeconds: this.DoorOpenCloseAnimationDuration / (double)framesCount);

            // refresh opened/closed state
            clientState.IsOpened = isOpened;
            // re-create physics
            this.SharedCreatePhysics(data.GameObject);
            spriteSheetAnimator.SetCurrentFrame(isOpened ? spriteSheetAnimator.FramesTextureResources.Length : 0);
        }