1
This repository has been archived on 2025-03-15. You can view files and clone it, but cannot push or open issues or pull requests.
2025-03-15 20:02:21 +01:00

270 lines
9.7 KiB
C#

// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
[RequireComponent(typeof(CanvasGroup))]
public class TiledPage : MonoBehaviour {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
/// Allows you to assign a custom set of tiles
/// To animate when this page is scrolling.
[SerializeField]
[Tooltip("The tiles to animate when scrolling.")]
private Transform[] tiles;
/// The RectTransform that tiles animate relative to.
/// The width and height of the layout transform will control
/// when the tiles start animating while scrolling.
[SerializeField]
[Tooltip("The RectTransform that tiles animate relative to.")]
private RectTransform layoutTransform;
/// Controls how much the tiles move when they are animating.
/// Set to 0 to turn off animation.
[SerializeField]
[Tooltip("Controls how much the tiles move when they are animating.")]
private float staggerAnimationIntensity = 0.5f;
public enum TileOrderBy {
Center,
LeftEdge,
LeftEdgeBySize,
RightEdge,
RightEdgeBySize
}
/// Controls the order that tiles move in when they are animating.
/// This is useful when a page has non-uniform tiles.
[SerializeField]
[Tooltip("Controls the order that tiles move in when they are animating.")]
private TileOrderBy tileOrderBy = TileOrderBy.Center;
/// The Key is an x position relative to the left side of the layoutTransform.
/// The value is a list of tiles that exist at that x position.
private List<List<Transform>> tilesByDistanceFromLeft;
/// When the distance between two tiles is within
/// TileGroupThreshold from eachother, they
/// Are considered within the same tile group
/// For animation purposes.
private const float kTileGroupThreshold = 5.0f;
/// Getter/Setter for tiles that ensures that the
/// layout cache is flushed when the tiles change.
public Transform[] Tiles {
get {
return tiles;
}
set {
tiles = value;
FlushLayoutCache();
}
}
/// <summary>
/// Call if the layout of tiles on this page has changed.
/// This will flush the cache to make sure the staggered
/// tiles animation plays correctly.
/// </summary>
public void FlushLayoutCache() {
tilesByDistanceFromLeft = null;
}
/// <summary>
/// Called by PagedScrollRect when scrolling occurs.
/// Do not call manually.
/// </summary>
/// <param name="scrollDistance">Signed scroll distance for this page.</param>
/// <param name="scrollSpacing">Spacing between pages.</param>
/// <param name="isInteractable">True is the PagedScrollRect is currently scrolling.</param>
public void ApplyScrollEffect(float scrollDistance, float scrollSpacing, bool isInteractable) {
if (Tiles == null) {
FlushLayoutCache();
return;
}
/// Organize the tiles by their x position
/// So that we can stagger them correctly.
CalculateTilesByDistance();
int iterateIndex;
int increment;
if (scrollDistance > 0) {
/// Scrolling Left
iterateIndex = 0;
increment = 1;
} else {
/// Scrolling Right
iterateIndex = tilesByDistanceFromLeft.Count - 1;
increment = -1;
}
float scrollMagnitude = Mathf.Abs(scrollDistance);
float ratioScrolled = scrollMagnitude / scrollSpacing;
int index = 0;
float directionCoeff = -increment;
bool updatedAnimatingTiles = false;
for (; iterateIndex >= 0 && iterateIndex < tilesByDistanceFromLeft.Count; iterateIndex += increment) {
List<Transform> tiles = tilesByDistanceFromLeft[iterateIndex];
float tileGroupRatio = (index + 1.0f) / tilesByDistanceFromLeft.Count;
float tileGroupInterval = scrollSpacing / tilesByDistanceFromLeft.Count;
tileGroupInterval *= staggerAnimationIntensity;
/// These tiles are currently animating based on the
/// Amount that the user has scrolled the scroll rect.
if (ratioScrolled < tileGroupRatio && !updatedAnimatingTiles) {
for (int i = 0; i < tiles.Count; i++) {
Transform tile = tiles[i];
float offset = tileGroupInterval * index;
float animatedXPos =
(scrollMagnitude * staggerAnimationIntensity * directionCoeff) - (offset * directionCoeff);
RectTransform cellRect = GetTileCell(tile);
Vector3 position = cellRect.TransformPoint(new Vector3(animatedXPos, 0.0f, 0.0f));
UpdateTile(tile, position, isInteractable);
}
updatedAnimatingTiles = true;
} else {
/// These tiles have not been animated yet,
/// Make sure their local position is reset.
if (updatedAnimatingTiles) {
for (int i = 0; i < tiles.Count; i++) {
Transform tile = tiles[i];
RectTransform cellRect = GetTileCell(tile);
Vector3 position = cellRect.TransformPoint(Vector3.zero);
UpdateTile(tile, position, isInteractable);
}
} else {
/// These tiles have already finished animating
/// Make sure they snap to their final position.
for (int i = 0; i < tiles.Count; i++) {
Transform tile = tiles[i];
RectTransform cellRect = GetTileCell(tile);
Vector3 position = cellRect.TransformPoint(new Vector3(tileGroupInterval * directionCoeff, 0.0f, 0.0f));
UpdateTile(tile, position, isInteractable);
}
}
}
index += 1;
}
}
private void UpdateTile(Transform tile, Vector3 position, bool isInteractable) {
// The Tile's cell is not necessarily the parent of the tile due
// to work arounds for the fact that depth sorting of Unity UI is tied to
// the objects location in the scene hierarchy instead of the z-position.
// As a result, the staggered position of the tile is passed into this function
// in world space. It must be translated back into the local space of the
// tile's parent to make sure that the the staggered animation is only overriding the
// x-axis of the tile in local space.
Vector3 animatedLocalPos = tile.parent.InverseTransformPoint(position);
Vector3 localPosition = tile.localPosition;
localPosition.x = animatedLocalPos.x;
tile.localPosition = localPosition;
BaseTile Tile = tile.GetComponent<BaseTile>();
if (Tile != null) {
Tile.IsInteractable = isInteractable;
}
}
private void CalculateTilesByDistance() {
/// Only do this if we haven't already calculated it.
if (tilesByDistanceFromLeft != null) {
return;
}
Canvas.ForceUpdateCanvases();
SortedDictionary<float, List<Transform>> tilesByDistance = new SortedDictionary<float, List<Transform>>();
// Ignore disabled tiles, otherwise this won't behave correctly when some tiles are disabled.
foreach (Transform tile in tiles) {
if (!tile.gameObject.activeInHierarchy) {
continue;
}
RectTransform cellRect = GetTileCell(tile);
RectTransform tileRect = tile.GetComponent<RectTransform>();
/// Find how far this cell is from the left side of the layout.
Vector3 tilePoint = GetTilePoint(tileRect);
Vector3 worldPoint = cellRect.TransformPoint(tilePoint);
Vector3 layoutPoint = layoutTransform.InverseTransformPoint(worldPoint);
float distanceFromLeft = layoutPoint.x - layoutTransform.rect.xMin;
/// Add the tile into the appropriate group based on it's x position.
List<Transform> tilesAtDistance;
if (tilesByDistance.TryGetValue(distanceFromLeft, out tilesAtDistance)) {
tilesAtDistance.Add(tile);
} else {
/// See if their is already a tile group that exists
/// Within range of the TileGroupThreshold.
tilesAtDistance = tilesByDistance.FirstOrDefault(
pair => {
float distance = Mathf.Abs(distanceFromLeft - pair.Key);
return distance < kTileGroupThreshold;
}).Value;
/// Found a tile group within range.
if (tilesAtDistance != null) {
tilesAtDistance.Add(tile);
} else {
tilesAtDistance = new List<Transform>();
tilesAtDistance.Add(tile);
tilesByDistance.Add(distanceFromLeft, tilesAtDistance);
}
}
}
tilesByDistanceFromLeft = tilesByDistance.Values.ToList();
}
private Vector3 GetTilePoint(RectTransform tileRect) {
switch (tileOrderBy) {
case TileOrderBy.Center:
return tileRect.rect.center;
case TileOrderBy.LeftEdge:
return tileRect.rect.min;
case TileOrderBy.LeftEdgeBySize:
return tileRect.rect.min - (tileRect.rect.size * 0.5f);
case TileOrderBy.RightEdge:
return tileRect.rect.max;
case TileOrderBy.RightEdgeBySize:
return tileRect.rect.max + (tileRect.rect.size * 0.5f);
default:
return Vector3.zero;
}
}
private RectTransform GetTileCell(Transform tile) {
RectTransform cellRect;
BaseTile Tile = tile.GetComponent<BaseTile>();
if (Tile != null) {
cellRect = Tile.Cell;
} else {
cellRect = tile.parent.GetComponent<RectTransform>();
}
return cellRect;
}
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
}