• 運沙

Dev Notes Archive 2013-2015 From social media


[Unity3D]Procedural Map Generation Note #1:Room Random Generation part.1 (VR game project for my club)


Throughout the last few mouths I have been working on a VR game project for my club. Rouge like game, Sci-Fi style. One of the most difficult parts about developing this game is its feature of procedural map generation. With no doubt definitely challenging for me and it’s still under development.

The basic concept that I came up with is to start from single room random generation. Every room should be generated with a random yet somehow controllable size & a regular/irregular shape(T shape, L shape, cross, E shape etc.). Then by connecting rooms with proper passages, a procedural map generation shall be finished.

Generally speaking, the most annoying/ interesting question mark about this design is to ask what are the variables. What are the stuff that are truly being randomized & what are the stuff that are actually staying the same for the whole time.

Script Design

public struct Tile{ //1 for floor, 2 for wall, 3 for corner, 4 for door, 5 for inward corner, 6 for door, 7 //for window public int tileType; public float x; public float z; public int rotateAngle;

public Tile(int type, float posx, float posz, int rotateAng){ tileType = type; x = posx; z = posz; rotateAngle = rotateAng;

}

}

Because this is a tile-based game, I had to develop some ways to record & store the set of info that all types of tiles could have. Thus I came up with a struct that contains type, position, and angle that it’s facing in.

then,

public class Room{ public Vector3 centerPoint; public int lengthWithoutBound;//x public int widthWithoutBound;//z public List<Tile> wallTiles; public List<Tile> doorTiles; public List<Tile> floorTiles; public List<Tile> cornerTiles; public List<Tile> inwardTiles;

public Room(){} public Room(Vector3 center, int length, int width){ centerPoint = center; lengthWithoutBound = length; widthWithoutBound = width; floorTiles = new List<Tile>(); wallTiles = new List<Tile>(); doorTiles = new List<Tile>(); cornerTiles = new List<Tile>(); inwardTiles = new List<Tile>(); //

if(irregularShapeOrNot == false){ calculateTilePosition(); } }

//specific methods like calculateTilePosition() are not shown right here

}

This is a Room class, basically containing some essential info a rectangular room could have:

1. Vector3 centerPoint: position of our gameobject that is attached with our script.

2. int lengthWithoutBound; int widthWithoutBound; : its length & width

3. List<Tile>wallTiles, etc. : a list of the tiles that this room has.

4. at the end there is a check if this room is requested to be a irregular room. If so, it will go through irregular room generation mechanism. Otherwise it will go through the calculateTilePosition() method, which will calculate all positions of all the tiles this room contains.

[05/03/2015]Unity笔记[5]对于C# delegate在Unity中应用的研究(1)。|| Applying C# delegate into Unity. || ゲームエンジン:delegate の応用

下周期末了,想想也自己快是大三的人了。

这两天对于delegate在Unity中的使用方法进行了研究。

定义:简单的来说delegate就相当于对于function的pointer或者可以叫做一个container。普义上在c和c++中的pointer是用于指向object或者variable的,但delegate是用于来指代function而存在的。

Initialization Syntax:

[modifier] delegate [return type] [name](parameter type)

Example:

public delegate void rotateAndDo(bool value);

在unity中的作用:现在个人对它理解就是它在unity中能起到对于各种object的各种script的一种管理和统筹作用。如果能灵活运用delegate,那么可以摆脱许多不必要的coding,将在各种object里的众多作用不同的function进行分类处理和分类trigger。

拿一个场景举例子,假如玩家进入了某个迷宫里且触发了某个机关,这个机关的设计是它会激活这个level里所有的enemy对玩家进行反应。可能这些enemy里有弓兵,有melee,还有炮兵。可想而知,他们的反应行为是不同的,弓兵会放箭,melee会朝着玩家移动,炮兵会瞄准放炮,但是本质上他们被激活的条件其实是相同的,那就是玩家触动了机关。

再或者,例如2D地图中生成人物或者角色,每个角色生成的那一刻必须被赋予名字,头上的生命条和相应的互动。虽然各个角色可能细节上或有不同,但是实际上被生成时会有许多相同的步骤需要经历,赋予名字,和生命值等。

delegate在unity中的作用之一就是可以统筹这些有类似之处的trigger event到它的一个实例中,再在较少数的script里利用这个实例对于这些trigger event进行统一处理,而不是一个game object一个game object地设定trigger。

这样会大大减少代码上的负担,也提升了程序的条理和工整。

Personal Coding Illustration:

using UnityEngine; using System.Collections;

public class DelegateTest : MonoBehaviour {

public delegate void rotateAndDo(bool value); public static event rotateAndDo start; public static void readyToDo(bool value){ if (start != null) {

start(value); } } public void OnGUI(){ if (GUI.Button (new Rect (20, 20, 200, 30), “Activate”)) { readyToDo(true); } if (GUI.Button (new Rect (20, 60, 200, 30), “Deactivate”)) { readyToDo(false); } }

void Update(){ GameObject [] cubes = GameObject.FindGameObjectsWithTag (“Sphere”) as GameObject[]; foreach (GameObject n in cubes) { n.transform.Rotate(new Vector3(0f,-0.3f,0f)); }

}

}

注意在这个class中,我们创建了一个delegate的type 叫做 rotateAndDo, 返回值为空,parameter的type为bool。

紧接着,后面跟着的便是我们创建的这个delegate类type的一个静态的event实例,名叫start。创建这个start的意义就在于我们可以在其他的class里利用: DelegateTest.start += functionName 来把一些外界function去subscribe到这个实例中,进而通过在这个class中对这一个delegate的实例的操作来激活其中所指向的所有的function。

OnGUI中我们设定了两个button,一个叫activate,一个叫deactivate,当第一个被按了的时候,我们会call readyToDo 这个function,然后输入一个true的bool 值。相反如果第二个被按的话,我们会输入一个false。readyToDo 会先检查start中时候有subscriber, 如果start这个实例不为null,那么变向start所指向的所有function输送一个bool值。

值得注意的是,update中我们会找到所用被tag了sphere的object,对他们进行旋转以便演示。


我们利用三个球体来演示。从左边数,第一个球体被激活后会缩小。第二个会变色。第三个会移位。我这里只贴出第一个球体上的代码。

using UnityEngine; using System.Collections;

public class shrink : MonoBehaviour { public float targetScale = 0.1f; public float shrinkSpeed = 0.1f; private bool activated = false; private Vector3 originalPos; public void Awake(){ DelegateTest.start += this.shrinking; originalPos = transform.position; }

public void shrinking(bool value) { Debug.Log (“Enter shrinking”); activated = value;

} void Update(){

if (activated == true) { transform.localScale = Vector3.Lerp (transform.localScale, new Vector3 (targetScale, targetScale, targetScale), Time.deltaTime * shrinkSpeed); } else { transform.localScale = Vector3.Lerp (transform.localScale, new Vector3 (1, 1, 1), Time.deltaTime * shrinkSpeed); gameObject.GetComponent<Rigidbody> ().velocity = Vector3.zero; transform.position = Vector3.MoveTowards(transform.position,originalPos,15.0f*Time.deltaTime); } } }

粗体的是需要注意的地方。

我们这里设定了一个global boolean variable叫做 ”activiated“ ,在update中可以看到,只有activated为true时,物体才会进行缩小变形。

注意在Awake()中我们使用了“+=”对DelegateTest中的start静态变量进行了subscribe,subscribe的 local function名叫 shrinking.

下面,shrinking为一个return 值 为 void,parameter type为bool的function,这个是一定要与start的属性吻合的,否则无法subscribe。

shrinking中,我们对activated设定为输入的bool值,也就是start传送过来的bool的输入。

其实这里的编程部分可以简化为仅一行左右,也不需要特别设出一个activated这样的bool,但是为了更明显地表现出delegate对于function的作用所以这个多出了许多步骤。

当我们按下左上角的activate时。start会将它之中的所有的subscriber function输入一个true 的 bool 值。对于第一个球体来说,它是缩小的一个trigger,但对于第二个球体来说,它便成为了变色的一个trigger。

如此我们可以知道,我们利用了delegate对这些trigger进行了一个统一的规定,那便是按下activate这个按钮。但这三个球体却通过这一个共同的激活展现了三种不同的变换形式。

回过头来说,在刚才所说的弓兵炮兵的例子里,我们也可以将如此的一个delegate应用到里面去进而达到机关的效果。

blog里有对这三个球体相应的演示视频。

欢迎评论。 Welcome to ask me anything in english.

[04/10/2015]Unity笔记[4]对于笔记2:2D手臂与鼠标位置交互的优化。(Perspective Camera, ScreenToWorldPoint)

当camera的镜头模式从orthographic 转换到 perspective mode时,原来在笔记二中记录的方式会被无效化。

原理研究:

在perspective的模式中,当使用 ScreenToWorldPoint(Input.mousePosition)时,z的值如果不进行特别设定,那么它便会决定相应的x与y的值。(具体可以参考图中两个红点的位置,可以看到当z 1-> 10时,x与y的值也相应变化了,就像是放大镜的原理,当把镜子从近处拉向远处时,放大镜中的物体的scale也会相应缩小。)

所以当z趋近于零时,input.mouseposition其实得到的是那个无限被缩小的mouse坐标,这里被判断成了camera的位置。

Code:

/*Vector3 difference = Camera.main.ScreenToWorldPoint (Input.mousePosition) - tranform.position; */

//这里使用一个farClipPlane 的function相当于去得到放大后的z值,然后再使用screenToWorldPoint就可以得到相应的正确的x和y

Vector3 pos = Input.mousePosition; pos.z = Camera.main.farClipPlane; pos = Camera.main.ScreenToWorldPoint (pos); Vector3 difference = pos - transform.position;

difference.Normalize (); float rotZ = Mathf.Atan2 (difference.y, difference.x) * Mathf.Rad2Deg; transform.rotation = Quaternion.Euler (0f, 0f, rotZ+180);

问题解决。

[02/07/2015] Blender学记笔记:Freestyle Rendering

Blender的Render功能太强大啦。

Blender里的Freestyle rendering可以被应用到许多物体与主题上去,今天学到的是类似与blueprints的一种表现手法。

想要完成这种rendering首先需要有一个3D 模型。

个人经过今天的实验觉得blueprints这种更适合于精密的,表面纹路繁复精致的物件而非人物上。

首先要在properties窗口的world一栏里将背景颜色设计好。对于blueprint的设定成浅蓝的很好看。到 render里在shading中将alpha调为sky

在3D view 里全选模型。利用ctrl+L link 所有的materials,然后回到properties的material 栏,将transparency里调为Z transparency alpha=0.00 或者用mask也行。

在render setting里激活freestyle,然后进入render layer里,之后基本所有的操作都会在这里进行。

这里非常重要的一个概念是line set。通过对不同的line set 进行不同的微调,rendering可以在一个物体上找到平衡。比如这里的outline line set使用了external contour去勾勒出物体的外部轮廓,而major line set却使用了silhouette, contour 和crease三种对物体边缘的理解方法去体现物体的表面。而且对于每个不同的line set 可以在freestyle line style里进行不同的设置,例如线条的大小粗细透明度,以达到最佳的效果。鉴于crease这种对于边缘的理解方式,在上面 freestyle栏里的crease angle可以调整。

Part2:设定一个360围绕你的物体旋转的摄像机。

很简单,在物体的原点设定一个empty物体,让它parent你的camera,随后对这个empty物体进行动画关键帧设定(i 键),加入的是rotation的关键帧,开始一个关键帧,结尾帧一个关键帧,记得在旋转的角度里将最后的调整为360度然后再放置帧。

最后效果:



[01/20/2015]Unity笔记[2]2D游戏中人物手臂与鼠标指针的交互

让人物的手臂随着鼠标指针的方向进行旋转,如针对射击游戏,可以通过以下script完成。

using UnityEngine; using System.Collections;

public class armRo : MonoBehaviour {

// Update is called once per frame void Update () {

//计算的是鼠标到人物的距离 Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;

//将vector的方向保留,normalize它的长度 difference.Normalize ();

//这个利用unity中的mathf数学函数求出vector相对于z的角度,rad2deg是指将rad转化成deg 单位以便feed到euler函数中。

float rotZ = Mathf.Atan2 (difference.y, difference.x) * Mathf.Rad2Deg;

transform.rotation = Quaternion.Euler (0f, 0f, rotZ);

} }


[01/20/2015]Unity笔记[1]:激光设计

将box-collider之类的套进一个空的game object A里。

A有两个script做支持,

一个是control script,用于旋转主体和控制激光角度。

using UnityEngine; using System.Collections;

public class controll : MonoBehaviour {

//三个变量

public int speed; public float friction; public float lerp;

//quaternion变量用于控制旋转

private float xD; private float yD; private Quaternion fromRotation; private Quaternion toRotation;

// Use this for initialization void Start () { } // Update is called once per frame void Update () {

// if 检查是否是输入了鼠标右击 if (Input.GetMouseButton (1)) {

xD -= Input.GetAxis(“Mouse X”)*speed*friction;

//eruler function用于return 一个 quaternion变量,eruler(z,x,y),各角度旋转zxy。

fromRotation = transform.rotation;

toRotation = Quaternion.Euler(0,xD,0);

//public static Quaternion Lerp(Quaternion from, Quaternion to, float t)

transform.rotation = Quaternion.Lerp(fromRotation,toRotation,Time.deltaTime * lerp);

}

} }

另一个是 weapon script, 用于与被照射到的物体产生交互。

public class weapon : MonoBehaviour {

//这个barrel变量是之后要import laser object 的

public GameObject Barrel;

// Use this for initialization void Start () { } // Update is called once per frame void Update () {

//判断是否输入了空格键 if (Input.GetKeyDown (KeyCode.Space)) { Fire (); }

} void Fire(){

//raycasthit类变量,基本上是用于储存被射物体的。Physics.raycast会返回bool值如果barrel照射到了物体。物体的信息会在hit里被存储。可以通过".collider"等进行访问。

RaycastHit hit;

if (Physics.Raycast (Barrel.transform.position, Barrel.transform.forward, out hit)) { if(hit.collider.tag == “Enemy”) {

print (“Hit : ” + hit.collider.gameObject.name); }

} }

}

下面是laser object的设计。

先parent 一个实体,之下建一个空object,加入line renderer,设定材料和参数,之后写进laser script。(laser在之后要被拖进weapon script的barrel 栏里)。

Laser script:

public class laser : MonoBehaviour {

//private设定,因为这个变量将在Start()里被赋值而非外部赋值

private LineRenderer lr;

// Use this for initialization void Start () {

//getcomponent函数,最简单的通过辨识linerenderer的存在直接将laser存到lr里

lr = GetComponent<LineRenderer>();

} // Update is called once per frame void Update () {

//同 weapon script

RaycastHit ra;

if (Physics.Raycast (lr.transform.position, lr.transform.forward, out ra)) {

//laser效果的本质,之所以其遇到物体能被截断是因为下面的setposition函数重新调整了linerenderer末端的位置,setposition(1,new Vector3)中第一个parameter是表示末端,若是零则是先端,第二个parameter是新的位置坐标。

if(ra.collider){ lr.SetPosition(1, new Vector3(0,0,ra.distance)); } }else{ lr.SetPosition(1,new Vector3(0,0,500));

} } }


​運沙 / Wei / Dh722
1995
普度大学计算机科学系 / Purdue Univ. CS / パデュー大学 コンピュータ科学
南加州大学计算机游戏研发 / Univ of Southern California CS Game Dev / 南カリフォルニア大学 コンピュータ科学 ゲーム開発
I make my own toys.