[링크] 유효성 검사를 한 번 더 - 이중 유효성 검사, INDIRIECT 함수
'Tip & Tech > Excel' 카테고리의 다른 글
| [Link] xlCompare as Excel Diff Tool! (0) | 2020.07.02 |
|---|---|
| [링크] 엑셀 강좌 사이트 (0) | 2018.10.30 |
| [링크] 입력할 데이터를 미리 정하기 - 유효성 검사 (0) | 2014.06.14 |
| 엑셀 틀고정, 첫행고정, 첫열고정 사용하기 (0) | 2014.03.14 |
| [Link] xlCompare as Excel Diff Tool! (0) | 2020.07.02 |
|---|---|
| [링크] 엑셀 강좌 사이트 (0) | 2018.10.30 |
| [링크] 입력할 데이터를 미리 정하기 - 유효성 검사 (0) | 2014.06.14 |
| 엑셀 틀고정, 첫행고정, 첫열고정 사용하기 (0) | 2014.03.14 |
[링크] https://github.com/pumperer/InfiniteList_NGUI
A component that runs on top of NGUI's UIScrollView & UITable classes for Unity3D (i.e. It requires NGUI & Unity3D) can be used with dynamic data.
Instead of instantiating a Prefab for each row in the list we are instantiating a fixed pool of objects that will be reused according to the scroll direction.
Best suited for Mobile (tested on both iOS & Android).
This demo package requires both Unity3D http://unity3d.com and NGUI http://www.tasharen.com/?page_id=140 to be installed. video: https://www.youtube.com/watch?v=5xFVJqzp0kY
To run the demo:
The demo package is released under the MIT License: http://opensource.org/licenses/MIT
Example of app using this component is Avatar Messenger for Android (Contacts list view) which is a free app on Google Play: https://play.google.com/store/apps/details?id=com.orange.labs.avachat
The main controller script that can be attached to a gameobject (e.g. the panel)
Some of the main methods included:
Initializes the list (also can be called to refresh the list with new data)
public void InitTableView(ArrayList inDataList, List<int> inSectionsIndices, int inStartIndex)
Parameters:
Refresh the list without changing the data (list start at startIndex value)
public void RefreshTableView()
Individual methods for changing the parameters if needed
public void SetStartIndex(int inStartIndex)
public void SetOriginalData(ArrayList inDataList)
public void SetSectionIndices(List<int> inSectionsIndices)
You can include section titles values.. or if you have more detailed sections seperators you can change the implementation of PopulateListSectionWithIndex
string GetTitleForSection(int i)
void PopulateListSectionWithIndex(Transform item, int index)
You can do your implementation of what to populate your row item with (in the demo we simply set a label to string value from our datalist array). Simply change InfiniteItemBehaviour (mentioned later below) to include more items as you want.
void PopulateListItemWithIndex(Transform item, int dataIndex)
Events that can be listened to.
public event InfiniteItemIsPressed InfiniteItemIsPressedEvent;
public event InfiniteItemIsClicked InfiniteItemIsClickedEvent;
Scripts attached to the row item prefab & section prefab (Note: the item prefab need to be tagged as "listItem" and the section prefab as "listSection")
Both checks for the visiblity of the item when the list start scrolling and notifiy the InfiniteListPopulator when the items becomes invisible. you can change use them as a template and include more labels, sprites or textures.
| [펌] Blur filter for UITexture in NGUI (0) | 2017.02.09 |
|---|---|
| [펌] NGUI 쉽게 말풍선 만들기 (0) | 2017.02.03 |
| NGUI CoverFlow(with Reflection Shader) (0) | 2016.10.11 |
| [Link] Unity-NGUIExtension (0) | 2016.10.10 |
| [펌] Smart Localization with NGUI (0) | 2016.09.26 |
For the moment, we have created a static scene with a player and some enemies. It’s a bit boring. Time to enhance our background and scene.
An effect that you find in every single 2D game for 15 years is “parallax scrolling”.
To make it short, the idea is to move the background layers at different speeds (i.e., the farther the layer is, the slower it moves). If done correctly, this gives an illusion of depth. It’s a cool, nice and easy-to-do effect.
Moreover, many shmups use a scrolling in one — or more — axis (except the original one, Space Invaders).
Let’s implement that in Unity.
Adding a scrolling axis need a bit of thinking on how we will make the game with this new aspect.
It’s good to think before coding. :)
We have a decision to take here :
The first choice is a no-brainer if you have a Perspective camera. The parallax is obvious: background elements have a higher depth. Thus, they are behind and seems to move slower.
But in a standard 2D game in Unity, we use an Orthographic camera. We don’t have depth at render.
About the camera: remember the “Projection” property of your camera game object. It’s set to Orthographic in our game.
Perspective means that the camera is a classic 3D camera, with depth management. Orthographic is a camera that renders everything at the same depth. It’s particularly useful for a GUI or a 2D game.
In order to add the parallax scrolling effect to our game, the solution is to mix both choices. We will have two scrollings:
Note: you may ask: “Why don’t we just set the camera as a child of the player object?”. Indeed, in Unity, if you set an object (camera or not) as a sub-child of a game object, this object will maintain its relative position to its parent. So if the camera is a child of the player and is centered on him, it will stay that way and will follow him exactly. It could be a solution, but this would not fit with our gameplay.
In a shmup, the camera restricts the player movement. If the camera moves along with the player for both horizontal and vertical axis, then the player is free to go where he wants. We DO want to keep the player inside a restricted area.
We would also recommend to always keep the camera independent in a 2D game. Even in a platformer, the camera isn’t strictly linked to the player: it follows him under some restrictions. Super Mario World has probably one the best camera possible for a platformer. You may have a look at how it is done.
Adding a scrolling to our game has consequences, especially concerning enemies. Currently, they are just moving and shooting as soon as the game starts. However, we want them to wait and be invincible until they spawn.
How do we spawn enemies? It depends on the game, definitely. You could define events that spawn enemies when they are triggered, spawn points, pre-determined positions, etc.
Here is what we will do: We position the Poulpies on the scene directly (by dragging the Prefab onto the scene). By default, they are static and invincibles until the camera reaches and activates them.
The nice idea here is that you can use the Unity editor to set the enemies. You read right: without doing anything, you already have a level editor.
Once again, it’s a choice, not science. ;)
Note: on a bigger project, you may need a dedicated level editor such as “Tiled” or a custom one you made. Your levels can be text files (plain text, XML, JSON, etc.) that you read in Unity for example.
First, we must define what our planes are and for each, if it’s a loop or not. A looping background will repeat over and over again during the level execution. E.g., it’s particularly useful for things like the sky.
Add a new layer to the scene for the background elements.
We are going to have:
| Layer | Loop |
|---|---|
| Background with the sky | Yes |
| Background (1st row of flying platforms) | No |
| Middleground (2nd row of flying platforms) | No |
| Foreground with players and enemies | No |
We could add as many layers of background objects as we want.
Careful with that axe, Eugene: if you add layers ahead of the foreground layer, be careful with the visibility. Many games do not use this technique because it reduces the clearness of the game, especially in a shmup where the gameplay elements need to be clearly visible.
Okay, we saw how implementing a parallax scrolling affects our game.
Did you know? “Scrolling shooters” is another name used for the shmups.
But enough thoughts, time to practice!
Unity has some parallax scrolling scripts in its standard packages (take a look at the 2D platformer demo on the Asset Store). You can of course use them, but we found it would be interesting to build one from scratch the first time.
Standard packages: these are practicals, but be careful to not abuse of them. Using standard packages can block your thoughts and will not make your game stand out of the crowd. They give a Unity feel to your gameplay.
Remember all the flash game clones?
We will start with the easy part: scrolling backgrounds without looping.
Remember the “MoveScript” we used before? The basis is the same: a speed and a direction applied over time.
Create a new “ScrollingScript” script:
using UnityEngine;
/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
/// <summary>
/// Scrolling speed
/// </summary>
public Vector2 speed = new Vector2(2, 2);
/// <summary>
/// Moving direction
/// </summary>
public Vector2 direction = new Vector2(-1, 0);
/// <summary>
/// Movement should be applied to camera
/// </summary>
public bool isLinkedToCamera = false;
void Update()
{
// Movement
Vector3 movement = new Vector3(
speed.x * direction.x,
speed.y * direction.y,
0);
movement *= Time.deltaTime;
transform.Translate(movement);
// Move the camera
if (isLinkedToCamera)
{
Camera.main.transform.Translate(movement);
}
}
}
Attach the script to these game objects with these values:
| Layer | Speed | Direction | Linked to Camera |
|---|---|---|---|
| Background | (1, 1) | (-1, 0, 0) | No |
| Background elements | (1.5, 1.5) | (-1, 0, 0) | No |
| Middleground | (2.5, 2.5) | (-1, 0, 0) | No |
| Foreground | (1, 1) | (1, 0, 0) | Yes |
For a convincing result, add elements to the scene:
Middleground.Foreground, far from the camera.The result:
Not bad! But we can see that enemies move and shoot when they are out of the camera, even before they spawn!
Moreover, they are never recycled when they pass the player (zoom out in the “Scene” view, and look at the left of the scene: the Poulpies are still moving).
Note: experiment with the values. :)
We’ll fix these problems later. First, we need to manage the infinite background (the sky).
In order to get an infinite background, we only need to watch the child which is at the left of the infinite layer.
When this object goes beyond the camera left edge, we move it to the right of the layer. Indefinitely.
For a layer filled with images, notice that you need a minimum size to cover the camera field, so we never see what’s behind. Here it’s 3 parts for the sky, but it’s completely arbitrary.
Find the correct balance between resource consumption and flexibility for your game.
In our case, the idea is that we will get all the children on the layer and check their renderer.
A note about using the renderer component: This method won’t work with invisible objects (e.g., the ones handling scripts). However, a use case when you need to do this on invisible objects is unlikely.
We will use an handy method to check whether an object’s renderer is visible by the camera. We’ve found it on the community wiki. It’s neither a class nor a script, but a C# class extension.
Extension: the C# language allows you to extend a class with extensions, without needing the base source code of the class.
Create a static method starting with a first parameter which looks like this: this Type currentInstance. The Type class will now have a new method available everywhere your own class is available.
Inside the extension method, you can refer to the current instance calling the method by using the currentInstance parameter instead of this.
Create a new C# file named “RendererExtensions.cs” and fill it with:
using UnityEngine;
public static class RendererExtensions
{
public static bool IsVisibleFrom(this Renderer renderer, Camera camera)
{
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(camera);
return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
}
}
Simple, isn’t it?
Namespaces: you might have already noted that Unity doesn’t add a namespace around a MonoBehaviour script when you create it from the “Project” view. And yet Unity does handle namespaces…
In this tutorial, we are not using namespaces at all. However, in your real project, you might consider to use them. If not, prefix your classes and behaviors to avoid a collision with a third-party library (like NGUI).
The real reason behind not using namespaces was that during the Unity 4 days (this tutorial was originally written for Unity 4.3), a namespace would prevent the use of default parameters. It’s not a problem anymore, so: use namespace!
We will call this method on the leftmost object of the infinite layer.
Observe the full “ScrollingScript” (explanations below):
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
/// <summary>
/// Parallax scrolling script that should be assigned to a layer
/// </summary>
public class ScrollingScript : MonoBehaviour
{
/// <summary>
/// Scrolling speed
/// </summary>
public Vector2 speed = new Vector2(10, 10);
/// <summary>
/// Moving direction
/// </summary>
public Vector2 direction = new Vector2(-1, 0);
/// <summary>
/// Movement should be applied to camera
/// </summary>
public bool isLinkedToCamera = false;
/// <summary>
/// 1 - Background is infinite
/// </summary>
public bool isLooping = false;
/// <summary>
/// 2 - List of children with a renderer.
/// </summary>
private List<SpriteRenderer> backgroundPart;
// 3 - Get all the children
void Start()
{
// For infinite background only
if (isLooping)
{
// Get all the children of the layer with a renderer
backgroundPart = new List<SpriteRenderer>();
for (int i = 0; i < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
SpriteRenderer r = child.GetComponent<SpriteRenderer>();
// Add only the visible children
if (r != null)
{
backgroundPart.Add(r);
}
}
// Sort by position.
// Note: Get the children from left to right.
// We would need to add a few conditions to handle
// all the possible scrolling directions.
backgroundPart = backgroundPart.OrderBy(
t => t.transform.position.x
).ToList();
}
}
void Update()
{
// Movement
Vector3 movement = new Vector3(
speed.x * direction.x,
speed.y * direction.y,
0);
movement *= Time.deltaTime;
transform.Translate(movement);
// Move the camera
if (isLinkedToCamera)
{
Camera.main.transform.Translate(movement);
}
// 4 - Loop
if (isLooping)
{
// Get the first object.
// The list is ordered from left (x position) to right.
SpriteRenderer firstChild = backgroundPart.FirstOrDefault();
if (firstChild != null)
{
// Check if the child is already (partly) before the camera.
// We test the position first because the IsVisibleFrom
// method is a bit heavier to execute.
if (firstChild.transform.position.x < Camera.main.transform.position.x)
{
// If the child is already on the left of the camera,
// we test if it's completely outside and needs to be
// recycled.
if (firstChild.IsVisibleFrom(Camera.main) == false)
{
// Get the last child position.
SpriteRenderer lastChild = backgroundPart.LastOrDefault();
Vector3 lastPosition = lastChild.transform.position;
Vector3 lastSize = (lastChild.bounds.max - lastChild.bounds.min);
// Set the position of the recyled one to be AFTER
// the last child.
// Note: Only work for horizontal scrolling currently.
firstChild.transform.position = new Vector3(lastPosition.x + lastSize.x, firstChild.transform.position.y, firstChild.transform.position.z);
// Set the recycled child to the last position
// of the backgroundPart list.
backgroundPart.Remove(firstChild);
backgroundPart.Add(firstChild);
}
}
}
}
}
}
(The numbers in the comments refer to the explanations below)
Start() method, we set the backgroundPart list with the children that have a renderer. Thanks to a bit of LINQ, we order them by their X position and put the leftmost at the first position of the array.Update() method, if the isLooping flag is set to true, we retrieve the first child stored in the backgroundPart list. We test if it’s completely outside the camera field. When it’s the case, we change its position to be after the last (rightmost) child. Finally, we put it at the last position of backgroundPartlist.Indeed, the backgroundPart is the exact representation of what is happening in the scene.
Remember to enable the “Is Looping” property of the “ScrollingScript” for the 0 - Background in the “Inspector” pane. Otherwise, it will (predictably enough) not work.
(Click on the image to see the animation)
Yes! We finally have a functional “parallax scrolling” implementation.
Note: why don’t we use the OnBecameVisible() and OnBecameInvisible() methods? Because they are broken.
The basic idea of these methods is to execute a fragment of code when the object is rendered (or vice-versa). They work like the Start() or Stop() methods (if you need one, simply add the method in the MonoBehaviour and Unity will use it).
The problem is that these methods are also called when rendered by the “Scene” view of the Unity editor. This means that we will not get the same behavior in the Unity editor and in a build (whatever the platform is). This is dangerous and absurd. We highly recommend to avoid these methods.
Let’s update our previous scripts.
We said earlier that enemies should be disabled until they are visible by the camera.
They should also be removed once they are completely off the screen.
We need to update “EnemyScript”, so it will:
(The numbers refer to the comments in the code)
using UnityEngine;
/// <summary>
/// Enemy generic behavior
/// </summary>
public class EnemyScript : MonoBehaviour
{
private bool hasSpawn;
private MoveScript moveScript;
private WeaponScript[] weapons;
private Collider2D coliderComponent;
private SpriteRenderer rendererComponent;
void Awake()
{
// Retrieve the weapon only once
weapons = GetComponentsInChildren<WeaponScript>();
// Retrieve scripts to disable when not spawn
moveScript = GetComponent<MoveScript>();
coliderComponent = GetComponent<Collider2D>();
rendererComponent = GetComponent<SpriteRenderer>();
}
// 1 - Disable everything
void Start()
{
hasSpawn = false;
// Disable everything
// -- collider
coliderComponent.enabled = false;
// -- Moving
moveScript.enabled = false;
// -- Shooting
foreach (WeaponScript weapon in weapons)
{
weapon.enabled = false;
}
}
void Update()
{
// 2 - Check if the enemy has spawned.
if (hasSpawn == false)
{
if (rendererComponent.IsVisibleFrom(Camera.main))
{
Spawn();
}
}
else
{
// Auto-fire
foreach (WeaponScript weapon in weapons)
{
if (weapon != null && weapon.enabled && weapon.CanAttack)
{
weapon.Attack(true);
}
}
// 4 - Out of the camera ? Destroy the game object.
if (rendererComponent.IsVisibleFrom(Camera.main) == false)
{
Destroy(gameObject);
}
}
}
// 3 - Activate itself.
private void Spawn()
{
hasSpawn = true;
// Enable everything
// -- Collider
coliderComponent.enabled = true;
// -- Moving
moveScript.enabled = true;
// -- Shooting
foreach (WeaponScript weapon in weapons)
{
weapon.enabled = true;
}
}
}
Start the game. Yes, there’s a bug.
Disabling the “MoveScript” as a negative effect: The player never reaches the enemies as they’re all moving with the Foreground layer scrolling:
Remember: we’ve added a “ScrollingScript” to this layer in order to move the camera along with the player.
But there is a simple solution: move the “ScrollingScript” from the Foregroundlayer to the player!
Why not after all? The only thing that is moving in this layer is him, and the script is not specific to a kind of object.
Push the “Play” button and observe: It works.
(Click on the image to see what happens)
You might have noticed that the player is not (yet) restricted to the camera area. “Play” the game, push the “Left Arrow” and watch him leaves the camera.
We have to fix that.
Open the “PlayerScript”, and add this at the end of the “Update()” method:
void Update()
{
// ...
// 6 - Make sure we are not outside the camera bounds
var dist = (transform.position - Camera.main.transform.position).z;
var leftBorder = Camera.main.ViewportToWorldPoint(
new Vector3(0, 0, dist)
).x;
var rightBorder = Camera.main.ViewportToWorldPoint(
new Vector3(1, 0, dist)
).x;
var topBorder = Camera.main.ViewportToWorldPoint(
new Vector3(0, 0, dist)
).y;
var bottomBorder = Camera.main.ViewportToWorldPoint(
new Vector3(0, 1, dist)
).y;
transform.position = new Vector3(
Mathf.Clamp(transform.position.x, leftBorder, rightBorder),
Mathf.Clamp(transform.position.y, topBorder, bottomBorder),
transform.position.z
);
// End of the update method
}
Nothing complicated, just verbose.
We get the camera edges and we make sure the player position (the center of the sprite) is inside the area borders.
Tweak the code to better understand what is happening.
We have a scrolling shooter!
We have just learned how to add a scrolling mechanism to our game, as well as a parallax effect for the background layers. However, the current code only works for right to left scrolling. But with your new knowledge, you should be able to enhance it and make it work for all scrolling directions (bonus: We did it as someone was stuck on the subject, click to see the code and an animation).
Still, the game really needs some tweaks to be playable. For example:
We will address these points in our upcoming chapter about gameplay tweaking (not released yet, unfortunately). For the moment, you can experiment. ;)
In the next chapter, we will focus our attention on how to make the game a bit more… flashy. With particles!
[출처] http://pixelnest.io/tutorials/2d-game-unity/parallax-scrolling/
| [펌] StreamingAssets 폴더 지정하기 (0) | 2016.11.10 |
|---|---|
| Invert ParticleEffect Velocity (0) | 2016.11.09 |
| [펌] Music player - (load sound files at runtime from directory, and play them) (1) | 2016.09.27 |
| [펌] OnApplicationFocus 와 OnApplicationPause 차이 (0) | 2016.09.22 |
| [펌] CUSTOM COROUTINES(Unity 5.3) (0) | 2016.07.21 |
필요한 경우가 아니면 GameObject를 비활성화 해라. 개체수를 감소시켜라.
필요한 경우가 아니면 Scripts를 비활성화 해라.
Find, FindByTag, GetComponent류의 사용을 자제해야 한다. 특히 Update, FixedUpdate같은 곳에서 지속적으로 사용하는 것은 퍼포먼스에 악영향이 있다. Awake, OnEnable에서 미리 찾아서 가지고 있는 것이 좋다.
Transform.position, gameObject.transform, gameObject.camera 등 유니티 엔진에서 C# 프로퍼티로 랩핑하여 제공하는 변수 타입에 접근시에 부하와 임시 객체가 생성된다. 캐싱하여 사용하는 것이 좋다.
태그 비교시에 if ( go.tag == “myBot” ) 구문 대신에 go.compareTag(“myBot”)을 사용하는 것이 임시 객체 생성을 회피하는 방법이다.
LINQ 명령어를 사용하는 경우 중간 버퍼가 할당된다. 이는 가비지로 남게 된다.
SendMessage류의 사용을 자제해야 한다. 직접 함수를 호출하는 경우보다 SendMessage류를 사용하는 경우 100배 느리다. 그리고 호출시에 함수 이름을 넣게 되는데 코드 난독화가 필요한 경우 장애요소가 된다.
foreach()를 사용할 경우 enumerator를 생성하기 위해서 힙 메모리를 할당한다. for(;;)문을 활용하는 것이 좋다. 한번 돌 때마다 24bit의 가비지를 남긴다.
[출처] http://www.antegg.com/wiki/doku.php?id=note:unity_3d:%EC%B5%9C%EC%A0%81%ED%99%94_tips
| [펌] Disable Screen Auto Rotation on Unity3D (0) | 2016.11.03 |
|---|---|
| [링크] Unity Technologies (0) | 2016.11.01 |
| [펌] 유니티 SendMessage 사용의 장점 (0) | 2016.10.26 |
| [Tip] Coroutine Optimization Technique (0) | 2016.10.07 |
| [펌] 에디터에서 게임 플레이를 할 때 저장되지 않은 Assets 저장하기 (0) | 2016.09.30 |
다른 오브젝트의 함수를 써야할 때 메시지 호출방법을 쓰는 것이 좋다고 합니다.
이득우 님의 발표자료에 따르면, (아래 화면 참조)
유니티 레퍼런스 홈페이지 예제를 보면,
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
void ApplyDamage(float damage) {
print(damage);
}
void Example() {
SendMessage("ApplyDamage", 5.0F);
}
}
위와 같은 방식으로 사용하고 있습니다. 파라미터 중 마지막 SendMessageOptions 은 말 그대로 옵션인데, SendMessageOptions.DontRequireReceiver 라고 쓸 경우에는 호출한 함수가 제대로 호출되었는지 여부는 신경쓰지 않고 호출한 것으로 역할이 끝납니다.
C# 에서는 콜백의 기능을 위해 delegate 를 사용할 수도 있습니다. 이와 관련해서 아래의 링크를 참고하세요.
http://unityindepth.tistory.com/entry/유니티-개발자로써-내가-배웠던-최고의-5가지
[출처] http://unitygame.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-SendMessage-%EC%82%AC%EC%9A%A9
[첨부]
| 덧붙이자면 SendMessage 도 생각보다 느리지 않다고 알고있습니다. 실시간으로 리플랙션을 통해 함수를 호출하는것이 아니라 엔진에서 맵형태로 관리하기때문에 스트링 비교하는 부하정도만 있는걸로 알고있습니다. |
[출처] http://www.gamecodi.com/board/zboard.php?id=GAMECODI_Talkdev&no=2070
| [링크] Unity Technologies (0) | 2016.11.01 |
|---|---|
| [펌] 유니티 최적화 Tips (0) | 2016.10.26 |
| [Tip] Coroutine Optimization Technique (0) | 2016.10.07 |
| [펌] 에디터에서 게임 플레이를 할 때 저장되지 않은 Assets 저장하기 (0) | 2016.09.30 |
| Latest Optimization Initiative for Unity Games (0) | 2016.09.18 |
| [링크] 고양이가 집사를 무는 이유? (0) | 2017.01.04 |
|---|---|
| [펌] 고양이 (0) | 2016.11.08 |
| [링크] 고양이를 처음 키우게 된 당신에게 (0) | 2016.10.24 |
| [링크] 고양이 간지럼 태우는 법을 아시나요? (0) | 2016.10.24 |
| [링크] 아기와 고양이, 사이좋게 잘 지내는 방법 (0) | 2016.10.24 |
| [펌] 고양이 (0) | 2016.11.08 |
|---|---|
| [링크] [BBC다큐] 고양이의 "야옹"과 "그르렁" (0) | 2016.10.24 |
| [링크] 고양이 간지럼 태우는 법을 아시나요? (0) | 2016.10.24 |
| [링크] 아기와 고양이, 사이좋게 잘 지내는 방법 (0) | 2016.10.24 |
| 노르웨이 숲고양이 성격과 키우는 법 (0) | 2014.11.25 |
[링크] http://catbook.kr/90
| [펌] 고양이 (0) | 2016.11.08 |
|---|---|
| [링크] [BBC다큐] 고양이의 "야옹"과 "그르렁" (0) | 2016.10.24 |
| [링크] 고양이를 처음 키우게 된 당신에게 (0) | 2016.10.24 |
| [링크] 아기와 고양이, 사이좋게 잘 지내는 방법 (0) | 2016.10.24 |
| 노르웨이 숲고양이 성격과 키우는 법 (0) | 2014.11.25 |
| [펌] 고양이 (0) | 2016.11.08 |
|---|---|
| [링크] [BBC다큐] 고양이의 "야옹"과 "그르렁" (0) | 2016.10.24 |
| [링크] 고양이를 처음 키우게 된 당신에게 (0) | 2016.10.24 |
| [링크] 고양이 간지럼 태우는 법을 아시나요? (0) | 2016.10.24 |
| 노르웨이 숲고양이 성격과 키우는 법 (0) | 2014.11.25 |
Have you ever found yourself wishing a built-in Unity class had some functionality that isn’t there? C#extension methods are the answer!
In this article, I’ll teach you how to use extension methods to add functionality to existing classes, no matter if they’re built-in Unity types, types defined in a third-party plugin, or even types defined in an Asset Store package which you could edit but you’re (rightly) worried about later package updates stomping your “patch”.
Seemingly obvious API omissions can be frustrating, but extension methods let you “fix” just about any API to your liking.
Hit the jump for all the details!
Imagine you need a way to set the layer of a GameObject and all its children, but there’s no GameObject.SetLayerRecursively() available. You could embed a loop in your code:
// Do some work.
// Set the layer of this GameObject and all its children.
gameObject.layer = someLayer;
foreach(Transform t in transform)
t.gameObject.layer = someLayer;
// Do some more work.This will work fine, although it’s not the cleanest thing in the world, and you’ll need to copy these lines of code around to every place where you want to do this operation. It would be better to encapsulate that code in a function, and make that function available to everyone via a “helper” class:
public class GameObjectHelper
{
public static void SetLayerRecursively(GameObject gameObject, int layer)
{
gameObject.layer = someLayer;
foreach(Transform t in transform)
t.gameObject.layer = someLayer;
}
}
public class Test : MonoBehaviour
{
void Start()
{
GameObjectHelper.SetLayerRecursively(gameObject, someLayer);
}
}This works fine too, but it can be easy to forget to use the helper class to invoke a function that feels like it belongs in GameObject itself. Wouldn’t it be nicer if you could just do this?
gameObject.SetLayerRecursively(someLayer);As it turns out, C# lets you make this happen!
Extension methods are declared just like regular functions, except that a) they must be static, b) they must be declared inside a non-generic, non-nested, static class, and c) they include a special syntax for their first argument, which is a reference to the calling object:
public static class GameObjectExtensions
{
public static void SetLayerRecursively(this GameObject gameObject, int layer)
{
// Implementation goes here
}
}Though declared as static, this is invoked as if it were an instance method:
myGameObject.SetLayerRecursively(someLayer);Note that when we call the method we actually omit the first argument and skip straight to the second. Take another look at the method declaration above. See how the first argument is declared using the “this” keyword? That’s what tells the compiler to infer that argument as the calling object; in this case, myGameObject.
That’s actually all there is to it. Extension methods are easy!
For what it’s worth, I like to organize my extension methods into classes named ClassNameExtensions. So I have GameObjectExtensions, TransformExtensions, ColorExtensions, and so on. There’s nothing that says you have to do this; I just like the organization. You could pack them all together into a single Extensions class if you prefer, or any other kind of organization you want. Just remember that extensions methods must be declared inside a non-generic, non-nested, static class; the compiler will complain otherwise.
Extension methods can add to an existing class, but they cannot override existing methods. For example, if you declared this:
public static class GameObjectExtensions
{
public static Component GetComponent(this GameObject gameObject, Type type)
{
Debug.LogError(“LOL, you got trolled”);
}
}…the compiler would basically ignore you. The rule for multiple method declarations using the same signature is: instance methods always take precedence over extension methods.
To extend a type, you’ll need make sure it’s in scope with the “using” directive. If you’re extending any built-in Unity type, you’re covered by virtue of the “using UnityEngine” that’s a standard entry in most Unity scripts. If you’re extending an editor type, you’ll need to add “using UnityEditor”, just like you would if you were calling that type. If you’re extending a type from a third-party plugin, you may need to import a namespace; check the plugin’s documentation (or source code, if you have it) for details.
I’ve already mentioned that extension methods must be declared inside a non-generic, non-nested, static class, which means you can’t just drop them in willy-nilly wherever you want. In practice, this “limitation” turns out to be a useful organizing device. You can certainly argue that this is virtually identical to the “helper class” example at the top of this article, and in terms of implementation effort that’s probably true; the difference is that with extension methods you get a cleaner, more normalized calling syntax. This really comes down to personal preference.
For the rest of this article, I’ll share some useful extension methods I’ve written over the past few months. Feel free to incorporate these into your own codebase and modify them at-will. (But don’t just copy-paste them into a text file and try to sell it on the Asset Store; that would make you a horrible person.)
I use the excellent 2D Toolkit for UI, and I use a two-camera setup: one camera for the scene, one camera for the UI. In order to make the UI camera render only UI objects, all the UI objects need to be on a UI layer. When I create UI objects from script I need to set that layer, and often I’m creating objects with children (like a button with a child sprite and a child text label).
It’d be nice to have a way to set the layer for this entire hierarchy with a single function call (you’ll recognize this as the example at the top of this article). Here’s what the call looks like:
myButton.gameObject.SetLayerRecursively(LayerMask.NameToLayer(“UI”));And here’s that extension method:
// Set the layer of this GameObject and all of its children.
public static void SetLayerRecursively(this GameObject gameObject, int layer)
{
gameObject.layer = layer;
foreach(Transform t in gameObject.transform)
t.gameObject.SetLayerRecursively(layer);
}While we’re doing things recursively, wouldn’t it be nice if we could enable/disable just the renderers, or just the colliders, for an entire hierarchy? This can be useful for UI, but it can also be useful in the scene. Imagine a complex hierarchy that defines a really fancy animated force field, and a game mechanic whereby you can switch your avatar’s polarity, allowing passage through force fields of a particular color. When you switch polarity, you’d loop through the matching force fields and disable their colliders, like this:
foreach(ForceField forceField in forceFields)
forceField.gameObject.SetCollisionRecursively(false);Here’s that extension method:
public static void SetCollisionRecursively(this GameObject gameObject, bool tf)
{
Collider[] colliders = gameObject.GetComponentsInChildren<Collider>();
foreach(Collider collider in colliders)
collider.enabled = tf;
}And the same principle applies for renderers:
public static void SetVisualRecursively(this GameObject gameObject, bool tf)
{
Renderer[] renderers = gameObject.GetComponentsInChildren<Renderer>();
foreach(Renderer renderer in renderers)
renderer.enabled = tf;
}It’s easy to search for child components using GameObject.GetComponentsInChildren(), but what if you have a have hierarchy in which you have lots of instances of a type, and you only want those instances which have a particular tag? In my case, I had a compound object with numerous renderers, and I needed to drive a material color on a subset of those renderers tagged as “Shield”.
This proved convenient:
m_renderers = gameObject.GetComponentsInChildrenWithTag<Renderer>("Shield");Here’s that extension method:
public static T[] GetComponentsInChildrenWithTag<T>(this GameObject gameObject, string tag)
where T: Component
{
List<T> results = new List<T>();
if(gameObject.CompareTag(tag))
results.Add(gameObject.GetComponent<T>());
foreach(Transform t in gameObject.transform)
results.AddRange(t.gameObject.GetComponentsInChildrenWithTag<T>(tag));
return results.ToArray();
}I noted earlier that extension methods must be declared inside non-generic classes. That doesn’t mean the extension method itself can’t be generic, however! When we call this method we replace T with the type we’re interested in — in the preceding call example, it was Renderer — and that type is used for each occurrence of T in the method implementation. The “where” keyword specifies that T must be of type Component or a type derived from Component (the compiler will throw an error otherwise).
See this MSDN article for more information about generics.
It’s all well and good to get components in children, but sometimes you need to search up. A common case I run into is when figuring out what to do with a collision result. I have a Player type, and its hierarchy is made up of several GameObjects which represent visuals, colliders, equipped items, and so on. Typically when I get a collision result on a player, the collider is bound to a child GameObject, so I can’t just do GetComponent
So now I do this:
Player player = collider.gameObject.GetComponentInParents<Player>();Here’s that extension method:
public static T GetComponentInParents<T>(this GameObject gameObject)
where T : Component
{
for(Transform t = gameObject.transform; t != null; t = t.parent)
{
T result = t.GetComponent<T>();
if(result != null)
return result;
}
return null;
}Just like Unity has both GameObject.GetComponentInChildren (singular) and GameObject.GetComponentsInChildren (plural), I also created a version that gets all components in parents:
public static T[] GetComponentsInParents<T>(this GameObject gameObject)
where T: Component
{
List<T> results = new List<T>();
for(Transform t = gameObject.transform; t != null; t = t.parent)
{
T result = t.GetComponent<T>();
if(result != null)
results.Add(result);
}
return results.ToArray();
}Note: it would be trivial to create a GetComponentsInParentsWithTag, but I haven’t run into a need for it yet. If you’d like to exercise your newfound knowledge of extension methods, this might be a good exercise. :)
Here’s one whose omission is particularly perplexing. You can get the layer a GameObject is on, which is useful for both rendering and collision purposes, but there’s no easy way to figure out the set of layers that GameObject can collide against.
I ran into this when implementing weapons. Projectile-based weapons are simple: they get a Rigidbody and a Collider of some sort, and the collision system handles everything for me. But a rail gun-like weapon is a different story: there’s no Rigidbody, just a script-invoked raytest. You can pass a collision mask — a bitfield — into a raytest, but what if you want the collision mask to be based on the weapon’s layer? It’d be nice to set some weapons to “Team1” and others to “Team2”, perhaps, and also to ensure your code doesn’t break if you change the collision matrix in the project’s Physics Settings.
Really, I wanted to just do this:
if(Physics.Raycast(startPosition, direction, out hitInfo, distance,
weapon.gameObject.GetCollisionMask())
)
{
// Handle a hit
}That raycast will only hit objects which the calling weapon is allowed to collide with, based on its layer and the project’s collision matrix.
Here’s that extension method:
public static int GetCollisionMask(this GameObject gameObject, int layer = -1)
{
if(layer == -1)
layer = gameObject.layer;
int mask = 0;
for(int i = 0; i < 32; i++)
mask |= (Physics.GetIgnoreLayerCollision(layer, i) ? 0 : 1) << i;
return mask;
}Note the optional “layer” argument. If omitted, it uses the layer of the calling GameObject, which is the most common/intuitive case (for me, at least). But you can specify a layer and it’ll hand you the collision mask for that layer instead.
I often find myself wanting to modulate the alpha value of a color without changing the color itself, for blinking/pulsing effects. Because Color is a struct, and structs in C# are immutable, you can’t simply assign color.a; you’ll get a compiler error. Fortunately, extension methods can extend structs as well as classes:
public static Color WithAlpha(this Color color, float alpha)
{
return new Color(color.r, color.g, color.b, alpha);
}This method makes modulating a color’s alpha clean and simple:
GUI.color = desiredColor.WithAlpha(currentAlpha);There really aren’t any. Extension methods are a compile-time device; once you reach runtime, they look and act just like any other method call. So it doesn’t matter whether you write an extension method or an explicit helper class; as long as the guts of the method are identical, both implementations should perform identically. You could theoretically squeeze a vanishingly tiny bit of performance out of simply inlining all the code, avoiding the overhead of any function call at all, but in this day and age that’s completely pointless to worry about unless you’re calling the function millions of times.
Extension methods are mainly useful as a way of cleaning up and normalizing your code. They have little or no effect on the behavior of that code.
I hope this article has been clear and useful. Extension methods are one of my favorite features of C#, because I’m a bit obsessive about having clean, easy-to-read code and they can really help make that happen. Of course, there’s more than one way to skin a cat, and I’m by no means suggesting that extension methods are the right or only way to do things. You can certainly argue that making an explicit helper class containing regular static functions — like GameObjectHelper.SetActiveRecursively() in the example at the top of this article — is just as good as an extension method-based implementation of the same; they’re six of one, half-dozen of another. I prefer the extension method approach because it feels like a natural and intuitive API extension, rather than a “bolt-on”, but that’s strictly a matter of personal preference.
Like every coding practice, extension methods are just one tool in the toolbox. But I encourage you to give them a shot!
(Oh and by the way: I'm available for contract work doing the sort of things you just read about. Hit me up if you're interested!)
[출처] http://www.third-helix.com/2013/09/30/adding-to-unitys-builtin-classes-using-extension-methods.html
| [펌] Find objects with DontDestroyOnLoad (0) | 2021.11.26 |
|---|---|
| [Unity] Play Streaming Music From Server (0) | 2018.02.06 |
| 일괄적으로 Texture Import Setting 변경 (0) | 2015.01.30 |
| Extension Methods (0) | 2014.08.18 |
| Nullable Types (0) | 2014.08.18 |