unity不规则unity3d 血条制作图形怎么处理

1068人阅读
Unity3D(1)
开门见山,NGUI中点击一个组件非常常用,一般来说UIButton也好,UISprite也好,都是规则的图形,用NGUI实现点击都很简单,方法也有很多。但是碰到特殊情况,比如制作地图板块点击,都是不规则的图形,就需要进行一下简单的设置。在网上查了一些大家的做法,大多是用添加Polygon Collider2D不规则碰撞盒的方式再配合NGUI发送射线来实现。不过之前的教程都是较早的NGUI版本,需要手动添加射线发送,现在我用的NGUI版本是3.9.8,已经包含了对2D碰撞盒的检测,下面简单给大家说下使用过程。
1、给一个不规则物体设置Polygon Collider2D
2、给这个物体添加UIButton组件(便于测试)
3、修改UICamera的Event Type为2D UI
4、测试下,运行后鼠标放到图片空白处无反应
放到中心区域后,触发UIButton组件颜色变化效果,证明触发碰撞盒。
最后:以上便是给不规则物体添加碰撞盒后用NGUI触发的方法。在NGUI的UICamera类下的Raycast方法中可以找到这段功能的写法,如果你的NGUI版本有这段判断或者在Unity编辑器下有这个选项那么都可以直接拿来用,然后在项目中用两个相机照规则物体和不规则物体就可以实现大部分想要的效果。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3351次
排名:千里之外unity3d利用网格去绘制血条实例图文教程
利用网格去绘制血条
血条肯定是一个矩形,网格是由一个一个三角形组成的,
矩形可以分成两个三角形。
创建一个空物体,添加以下脚本组件
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]public class MeshAndUV : MonoBehaviour{
private float size = 1;
void Awake()
mh = GetComponent&MeshFilter&().
rd = GetComponent&MeshRenderer&();
void Start()
//顶点数组
Vector3[] vertes
= new Vector3[]
new Vector3(-size, -size, 0),//第一个点
new Vector3(-size, size, 0), //第二个
new Vector3(size, size, 0), //第三个
new Vector3(size, -size, 0), //第四个
mh.vertices =
//顶点组成的三角形
mh.triangles = new[]
mh.RecalculateNormals();
运行下,就发现绘制出一个粉红色的矩形,为啥是粉红色,因为没材质啊
在scene视图下把ShadingMode改为Wireframe模式就可以看到两个三角形
轴点在中心,边长为2的矩形
然后在脚本上设置UV映射,加上贴图材质。
在设置三角形下面添加
//UV贴图的四个点,和顶点一一对应,左下角为(0,0),右上角为(1,1)
//如果顶点顺序没有跟UV对应,贴图就会出现问题Vector2[] uvs = new Vector2[]
new Vector2(0,0),//第一个点
new Vector2(0,1),//2
new Vector2(1,1),//3
new Vector2(1,0), //4
rd.material =
就可以把这张图绘制出来的
这张图包含了HP,MP,我们就要把它们区分开来。
封装成一个函数 void CreateBar(int barIndex)
修改UV映射
血条索引从下往上数,每个间隔0.25f
Vector2[] uvs = new Vector2[]
new Vector2(0, 0.25f * barIndex),//第一个点
new Vector2(0, 0.25f * (barIndex+1)),//2
new Vector2(1, 0.25f * (barIndex+1)),//3
new Vector2(1, 0.25f * barIndex), //4
在Start方法调用 CreateBar(0)
由于满血状态是全红的,所以在UV的x映射也要做下改变
Vector2[] uvs = new Vector2[]
new Vector2(0, 0.25f * barIndex),//第一个点
new Vector2(0, 0.25f * (barIndex+1)),//2
new Vector2(0.5f, 0.25f * (barIndex+1)),//3
new Vector2(0.5f, 0.25f * barIndex), //4
是不是有点像啦。只要改变下长宽比就好看啦。
函数添加多一个参数
void CreateBar(Vector2 size, int barIndex)
修改下顶点数组
Vector3[] vertes = new Vector3[]
new Vector3(-size.x, -size.y, 0),//第一个点
new Vector3(-size.x, size.y, 0), //第二个
new Vector3(size.x, size.y, 0), //第三个
new Vector3(size.x, -size.y, 0), //第四个
调用下函数
void Start()
CreateBar(new Vector2(1,0.25f),0 );
改变血条的值有点个办法,
第一个是改变Material的mainTextureOffset值
mat.mainTextureOffset = new Vector2(0.2f,0);
但是这样会令到所以使用者材质的物体贴图都会改变
第二个办法就是修改UV映射
void SetBarRate(float value)
value *= 0.5f;
Vector2[] uvs = new Vector2[]
new Vector2(value, 0.25f * barIndex),//第一个点
new Vector2(value, 0.25f * (barIndex+1)),//2
new Vector2(0.5f + value , 0.25f * (barIndex+1)),//3
new Vector2(0.5f + value, 0.25f * barIndex), //4
}//因为这张图一半是亮的,一半是暗的,暗的那部分代表失去的血量,所以value要乘以0.5;void Start()
CreateBar(new Vector2(1,0.25f),0 );
SetBarRate(0.9f);
做到这里就差不多完成了,利用网格去绘制血条。
using UnityEusing System.C [RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]public class MeshAndUV : MonoBehaviour{
private float rate = 0.5f;
private int barIndex = 0;
void Awake()
mh = GetComponent&MeshFilter&().
rd = GetComponent&MeshRenderer&();
void Start()
CreateBar(new Vector2(1,0.25f),0 );
SetBarRate(0.9f);
/// &summary&
/// 利用网格创建血条
/// &/summary&
/// &param name="size"&三角形大小&/param&
/// &param name="barIndex"&血条索引&/param&
void CreateBar(Vector2 size, int barIndex)
this.barIndex = barI
//顶点数组
Vector3[] vertes = new Vector3[]
new Vector3(-size.x, -size.y, 0),//第一个点
new Vector3(-size.x, size.y, 0), //第二个
new Vector3(size.x, size.y, 0), //第三个
new Vector3(size.x, -size.y, 0), //第四个
//给网格的顶点赋值
mh.vertices =
//顶点组成的三角形
mh.triangles = new[]
//UV贴图的四个点,和顶点一一对应,左下角为(0,0),右上角为(1,1)
//如果顶点顺序没有跟UV对应,贴图就会出现问题
Vector2[] uvs = new Vector2[]
new Vector2(0, 0.25f * barIndex),//第一个点
new Vector2(0, 0.25f * (barIndex+1)),//2
new Vector2(0.5f , 0.25f * (barIndex+1)),//3
new Vector2(0.5f , 0.25f * barIndex), //4
rd.material =
//法线重新
mh.RecalculateNormals();
/// &summary&
/// 设置血条比例
/// &/summary&
/// &param name="value"&血量失去的百分比&/param&
void SetBarRate(float value)
value *= 0.5f;
Vector2[] uvs = new Vector2[]
new Vector2(value, 0.25f * barIndex),//第一个点
new Vector2(value, 0.25f * (barIndex+1)),//2
new Vector2(0.5f + value , 0.25f * (barIndex+1)),//3
new Vector2(0.5f + value, 0.25f * barIndex), //4努力加载中,稍等...
暂无新消息
努力加载中,稍等...
已无更多消息...
这些人最近关注了你
努力加载中,稍等...
已无更多消息
努力加载中,稍等...
已无更多消息
& Unity 六边形地图系列(四) : 不规则性
Hex Map 4 Irregulatity
征集热心朋友翻译文章,奖励规则:每100汉字奖励10QB,30天内互动奖励0 - 50QB.
翻译请求:六边形地图系列的第4篇 为地图加入了一些不规则性
该文章来自用户转载
Hex Map 4 IrregulatitySample a noise texture.Perturb vertices.Keep cells flat.Subdivide cell edges.This tutorial is the fourth part of a series about . So far, our grid has been a strict honeycomb. In this installment, we'll introduce irregularities to make our map look more natural.No more regular hexagons.NoiseTo add irregularities, we need randomization. But not true randomness. We want things to stay consistent whenever we edit our map. Otherwise, things would jump around each time we made a change. So we need a form of reproducible pseudorandom noise.Perlin noise is a good candidate. It is reproducible at any point. When multiple frequences are combined, it also produces noise that can vary a lot over large distances, but stays fairly similar at small distances. This can produce relatively smooth distortions. Points that lie close together tend to stick together, instead of being distorted in opposite directions.We could generate Perlin noise programmatically. The
tutorial explains how to do that. But we could also sample from a pre-generated noise texture. The advantage of using a texture is that it's easier and much faster than computing multi-frequency Perlin noise. The downside is that the texture occupies more memory and only covers a small region of noise. So it needs to be a tiling texture, and has to be fairly large to make the tiling not that obvious.Noise TextureWe're going to use a texture, so you don't have to go through the
tutorial right now. That mean we need such a texture. Here's one.Tiling fractal Perlin noise texture.The above texture contains tiling multi-frequency Perlin noise. It is a grayscale image with an average value of 0.5, with extreme values approaching 0 and 1.But wait, this is only a single value per point. If we want a 3D distortion, we need at least three pseudorandom samples! So we need two additional textures, with different noise in each.We could do that, or we could store a different noise value in each of the color channels. That allows us to store up to four different noise patterns in a single texture. Here is such a texture.Four in one.How did you make that texture?Grab this texture and import it into your Unity project. Because we are going to sample the texture via code, it has to be readable. Switch the Texture Type to Advanced and switch on Read/Write Enabled. This will keep the texture data in memory, accessible via C# code. Make sure to set the Format to Automatic Truecolor, otherwise this won't work. We wouldn't want to destroy our noise pattern via texture compression anyway.We can disable Generate Mip Maps, because we don't need them. While we're at it, enable Bypass sRGB Sampling as well. We don't need this, but it is correct. It indicates that the texture does not contain color data in Gamma space. Imported noise texture.When does sRGB sampling matter?Sampling NoiseLet's add the noise sampling functionality to HexMetrics, so it can be used from anywhere. This means that HexMetrics much have a reference to the noise texture.public static
noiseSBecause it is not a component, we cannot assign our texture to it via the editor. We'll simply use HexGrid as an intermediary. As HexGrid is the first to act, it will be fine if we pass along the texture at the start of its
method.public
noiseSvoid
() {HexMetrics.noiseSource = noiseS… }However, this approach will not survive recompiles while in play mode. Static variables aren't serialized by Unity. To solve this, reassign the texture in the
event method as well. This method will get invoked after a recompile.void
() {HexMetrics.noiseSource = noiseS}Assigning the noise texture.Now that HexMetrics can access the texture, let's add a convenient noise sampling method to it. This method takes a world position and produce a 4D vector containing four noise samples.public static
SampleNoise ( position) {}The samples are produced by sampling the texture using bilinear filtering, using the X and Z world coordinates as UV coordinates. As our noise source is 2D, we ignore the third wold coordinate. If our noise source had been 3D, then we would've used the Y world coordinate too.We end up with a color, which can be cast to a 4D vector. This cast can be implicit, meaning that we can directly return the color without explicitly including ().public static
SampleNoise ( position) {return noiseSource.GetPixelBilinear(position.x, position.z);}How does bilinear filtering work?Perturbing VerticesWe distort our regular honeycomb grid by perturbing each vertex individually. So let's add a Perturb method to HexMesh to do this. It takes an unperturbed point and returns a perturbed one. To do so, it uses the unperturbed point to sample our noise. Perturb ( position) { sample = HexMetrics.SampleNoise(position);}Let's simply add the X, Y, and Z noise samples directly to the corresponding coordinates of the point and use that as the result. Perturb ( position) {
sample = HexMetrics.SampleNoise(position);position.x += sample.x;position.y += sample.y;position.z += sample.z;return}How could we quickly change HexMesh so all vertices are perturbed? By adjusting each vertex when it's added to the vertices list, in AddTriangle and AddQuad. So let's do that.void AddTriangle ( v1,
v3) { int vertexIndex = vertices.C
vertices.Add(Perturb(v1));
vertices.Add(Perturb(v2));
vertices.Add(Perturb(v3));
… } void AddQuad ( v1,
v4) { int vertexIndex = vertices.C
vertices.Add(Perturb(v1));
vertices.Add(Perturb(v2));
vertices.Add(Perturb(v3));
vertices.Add(Perturb(v4));
… }Are quads still flat after perturbing their vertices?Perturbed vertices, or not.It doesn't look like much changed, except that the cell labels appear to be missing. This happens because we're adding the noise samples to our points, and they are always positive. So all triangles end up above the labels, obscuring them. We have to center the adjustments so they can go in either direction. Change the range of the noise sample from 0–1 to -1–1. Perturb ( position) {
sample = HexMetrics.SampleNoise(position);
position.x += sample.x* 2f - 1f;
position.y += sample.y* 2f - 1f;
position.z += sample.z* 2f - 1f; return }Centered perturbation.Perturb StrengthIt is now clear that we have distorted the grid, but the effect is very subtle. There is at most a 1 unit adjustment in each dimension. So the theoretical maximum displacement is √3 ≈ 1.73 units, which would be extremely rare, if it happens at all. As our cells have an outer radius of ten units, the perturbations are relatively small.The solution is to add a strength setting to HexMetrics so we can scale the perturbations. Let's try a strength of 5. This has a theoretical maximum displacement of √75 ≈ 8.66 units, which should be much more obvious.public const float cellPerturbStrength = 5f;Apply the strength by multiplying it with the samples in HexMesh.Perturb. Perturb ( position) {
sample = HexMetrics.SampleNoise(position);
position.x +=(sample.x * 2f - 1f) * HexMetrics.cellPerturbS
position.y +=(sample.y * 2f - 1f) * HexMetrics.cellPerturbS
position.z +=(sample.z * 2f - 1f) * HexMetrics.cellPerturbS return }Increased strength.Noise ScaleWhile the grid looks fine before editing, things go wrong once terraces appear. Their vertices get distorted in wildly different directions, leading to a mess. This should not happen when using Perlin noise.The problem happens because we're directly using the world coordinates to sample the noise. This causes the texture to tile every unit, while our cells are much larger than that. Effectively, the texture gets sampled at arbitrary locations, destroying any coherence it has.10 by 10 grid lines overlapping the honeycomb.We have to scale the noise sampling so the texture covers a much larger area. Let's add this scale to HexMetrics and set it to 0.003, then scale the sample coordinates by that factor.public const float noiseScale = 0.003f;public static
SampleNoise ( position) { return noiseSource.GetPixelBilinear(
position.x* noiseScale,
position.z* noiseScale); }Suddenly, our texture covers 333 1/3
square units and its local coherence becomes obvious.Scaled noise.Our new scale also makes sure that the noise will take a while before it tiles. Actually, because the cells have an inner diameter of 10√3 units, it will effectively never exactly tile in the X dimension. However, because of the noise's local coherence, you can still detect repeating patterns at the larger scale, roughly every 20 cells, even if the details don't match. But that's only really obvious for an otherwise featureless map.Leveling Cell CentersPerturbing all vertices gives our map a more natural appearance, but there are some problems. Because the cells are now uneven, their labels intersect the mesh. And cracks appear in the mesh where terraces meet cliffs. We'll leave the cracks for later and focus on the cell surfaces.Less rigid, more issues.The simplest way to solve the intersection problem is to keep the cells centers flat. Just don't adjust the Y coordinate in HexMesh.Perturb. Perturb ( position) {
sample = HexMetrics.SampleNoise(position);
position.x += (sample.x * 2f - 1f) * HexMetrics.cellPerturbS
position.z += (sample.z * 2f - 1f) * HexMetrics.cellPerturbS return }Leveled cells.This change leaves all vertical positions unchanged, both for cell centers and for terrace steps. Note that this reduces the maximum displacement to √50 ≈ 7.07, in the XZ plane only.This is not a bad change, as it makes it a lot easier to identify individual cells and prevents terraces from becoming too messy. But some vertical perturbation would still be nice.Perturbing Cell ElevationInstead of applying a vertical perturbation per vertex, we could apply it per cell. That way each cell remains flat, but there is still variation between cells. It also makes sense to use a different scale for the elevation perturbation, so add one to HexMetrics. A strength of 1.5 units provides some subtle variation, which is roughly the height of a single terrace step.public const float elevationPerturbStrength = 1.5f;Adjust the HexCell.Elevation property so that it applies this perturbation to the cell's vertical position.public int Elevation { get { return
elevation =
position = transform.localP
position.y = value * HexMetrics.elevationSposition.y +=(HexMetrics.SampleNoise(position).y * 2f - 1f) *HexMetrics.elevationPerturbStransform.localPosition =
uiPosition = uiRect.localP
uiPosition.z =-position.y;
uiRect.localPosition = uiP
} }To make sure that the perturbation is applied immediately, we have to explicitly set each cell's elevation in HexGrid.CreateCell. Otherwise the grid would start out flat. Do this at the end, after the UI has been created.void CreateCell (int x, int z, int i) {
…cell.Elevation = 0;}Perturbed elevations, with cracks.Using the Same HeightsAt lot of cracks have appeared in the mesh, because we're not consistently using the same cell heights when triangulating the mesh. Let's add a convenient property to HexCell to retrieve its position, so we can use it everywhere.public
Position {get {return transform.localP}}Now we can use that property in HexMesh.Triangulate to determine the cell's center.void Triangulate (HexDirection direction, HexCell cell) {
center = cell.P
… }And we can use it in TriangulateConnection, when determining the vertical positions of the neighboring cells.void TriangulateConnection ( HexDirection direction, HexCell cell,
bridge = HexMetrics.GetBridge(direction);
v3.y = v4.y = neighbor.Position.y;
… HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (direction &= HexDirection.E && nextNeighbor != null) {
v5 = v2 + HexMetrics.GetBridge(direction.Next());
v5.y = nextNeighbor.Position.y;
} }Consistent use of cell elevation.Subdividing Cell EdgesWhile our cells are nicely varied, they are still obviously hexagons. This is not a problem per se, but we can do better than that.Clearly hexagonal cells.If we had more vertices, we would see more local variety. So let's split each cell edge into two parts, by introducing an edge vertex halfway between each pair of corners. This means that HexMesh.Triangulate has to add two instead of one triangle.void Triangulate (HexDirection direction, HexCell cell) {
center = cell.P
v1 = center + HexMetrics.GetFirstSolidCorner(direction);
v2 = center + HexMetrics.GetSecondSolidCorner(direction); e1 = .Lerp(v1, v2, 0.5f);AddTriangle(center, v1,e1);
AddTriangleColor(cell.color);AddTriangle(center, e1,v2);AddTriangleColor(cell.color);if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell, v1, v2);
} }Twelve sides instead of six.Doubling the vertices and triangles adds a bit more variety to our cell's edges. Let's make them even more rugged by tripling the vertices. e1 = .Lerp(v1, v2,1f / 3f); e2 = .Lerp(v1, v2, 2f / 3f);AddTriangle(center, v1, e1);
AddTriangleColor(cell.color);AddTriangle(center, e1, e2);AddTriangleColor(cell.color);AddTriangle(center,e2, v2);
AddTriangleColor(cell.color);18 Sides.Subdividing Edge ConnectionsOf course we also have to subdivide the edge connections. So pass the new edge vertices to TriangulateConnection.if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell, v1,e1, e2,v2);
}Add matching parameters to TriangulateConnection so it can work with the extra vertices.void TriangulateConnection ( HexDirection direction, HexCell cell,
e2, v2 ) { …}We also need to compute extra edge vertices for the neighboring cell. We can compute those after bridging to the other side. bridge = HexMetrics.GetBridge(direction);
v3.y = v4.y = neighbor.Position.y; e3 = .Lerp(v3, v4, 1f / 3f); e4 = .Lerp(v3, v4, 2f / 3f);Next, we have to adjust the triangulation of the edge. Ignoring terraced slopes for now, simply add three quads instead of one.if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(v1, v2, cell, v3, v4, neighbor);
AddQuad(v1,e1, v3,e3);AddQuadColor(cell.color, neighbor.color);AddQuad(e1, e2, e3, e4);AddQuadColor(cell.color, neighbor.color);AddQuad(e2, v2,e4, v4);
AddQuadColor(cell.color, neighbor.color);
}Subdivided connections.Bundling Edge VerticesAs we now need four vertices to describe an edge, it makes sense to bundle them. That's more convenient than dealing with four individual vertices. Create a simple EdgeVerticesstructure for this. It should contain four vertices, ordered clockwise along a cell's edge.using UnityEpublic struct EdgeVertices {public
v1, v2, v3, v4;}Doesn't it need to be serializable?Give it a convenient constructor method, which takes care of computing the intermediary edge points.public EdgeVertices ( corner1,
corner2) {v1 = corner1;v2 = .Lerp(corner1, corner2, 1f / 3f);v3 = .Lerp(corner1, corner2, 2f / 3f);v4 = corner2;}Now we can add a separate triangulate method to HexMesh for creating a triangle fan between a cell's center and one of its edges.void TriangulateEdgeFan ( center, EdgeVertices edge,
color) {AddTriangle(center, edge.v1, edge.v2);AddTriangleColor(color);AddTriangle(center, edge.v2, edge.v3);AddTriangleColor(color);AddTriangle(center, edge.v3, edge.v4);AddTriangleColor(color);}And a method for triangulating a strip of quads between two edges.void TriangulateEdgeStrip (EdgeVertices e1,
c1,EdgeVertices e2,
c2) {AddQuad(e1.v1, e1.v2, e2.v1, e2.v2);AddQuadColor(c1, c2);AddQuad(e1.v2, e1.v3, e2.v2, e2.v3);AddQuadColor(c1, c2);AddQuad(e1.v3, e1.v4, e2.v3, e2.v4);AddQuadColor(c1, c2);}This allows us to simplify the Triangulate method.void Triangulate (HexDirection direction, HexCell cell) {
center = cell.PEdgeVertices e = new EdgeVertices(center + HexMetrics.GetFirstSolidCorner(direction),center + HexMetrics.GetSecondSolidCorner(direction));TriangulateEdgeFan(center, e, cell.color);if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell,e);
} }On to TriangulateConnection. We can now use TriangulateEdgeStrip, but some other substitutions have to be made too. Where we first used v1, we should use e1.v1 instead. Likewise, v2 becomes e1.v4, v3 becomes e2.v1, and v4 becomes e2.v4.void TriangulateConnection ( HexDirection direction, HexCell cell,EdgeVertices e1) { HexCell neighbor = cell.GetNeighbor(direction); if (neighbor == null) { return;
bridge = HexMetrics.GetBridge(direction);bridge.y = neighbor.Position.y - cell.Position.y;EdgeVertices e2 = new EdgeVertices(e1.v1 + bridge,e1.v4 + bridge);if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(e1.v1,e1.v4, cell,e2.v1,e2.v4, neighbor);
} else {TriangulateEdgeStrip(e1, cell.color, e2, neighbor.color);} HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (direction &= HexDirection.E && nextNeighbor != null) {
v5 =e1.v4+ HexMetrics.GetBridge(direction.Next());
v5.y = nextNeighbor.Position.y; if (cell.Elevation &= neighbor.Elevation) { if (cell.Elevation &= nextNeighbor.Elevation) {
TriangulateCorner(e1.v4, cell,e2.v4, neighbor, v5, nextNeighbor
TriangulateCorner(
v5, nextNeighbor,e1.v4, cell,e2.v4, neighbor
} else if (neighbor.Elevation &= nextNeighbor.Elevation) {
TriangulateCorner(e2.v4, neighbor, v5, nextNeighbor,e1.v4, cell
TriangulateCorner(
v5, nextNeighbor,e1.v4, cell,e2.v4, neighbor
}Subdividing TerracesWe have to subdivide the terraces as well. So pass the edges to TriangulateEdgeTerraces.if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(e1, cell,e2, neighbor);
}Now we have to adjust TriangulateEdgeTerraces so it interpolates between edges, instead of pairs of vertices. Let's assume that EdgeVertices has a convenient static interpolation method for that. This allows us to simplify TriangulateEdgeTerraces, instead of making it more complex.void TriangulateEdgeTerraces ( EdgeVertices begin, HexCell beginCell, EdgeVertices end, HexCell endCell ) {EdgeVertices e2 = EdgeVertices.TerraceLerp(begin, end, 1); c2 = HexMetrics.TerraceLerp(beginCell.color, endCell.color, 1);TriangulateEdgeStrip(begin, beginCell.color, e2, c2);for (int i = 2; i & HexMetrics.terraceS i++) {EdgeVertices e1 = e2; c1 = c2;e2 = EdgeVertices.TerraceLerp(begin, end, i);c2 = HexMetrics.TerraceLerp(beginCell.color, endCell.color, i);TriangulateEdgeStrip(e1, c1, e2, c2);}TriangulateEdgeStrip(e2, c2, end, endCell.color);}The EdgeVertices.TerraceLerp method simply performs the terrace interpolation between all four pairs of two edge vertices.public static EdgeVertices TerraceLerp (EdgeVertices a, EdgeVertices b, int step){EdgeVerticesresult.v1 = HexMetrics.TerraceLerp(a.v1, b.v1, step);result.v2 = HexMetrics.TerraceLerp(a.v2, b.v2, step);result.v3 = HexMetrics.TerraceLerp(a.v3, b.v3, step);result.v4 = HexMetrics.TerraceLerp(a.v4, b.v4, step);return}Subdivided terraces.Reconnecting Cliffs and TerracesSo far, we have ignored those cracks where cliffs and terraces meet. It is time to deal with that issue. Let's consider the cliff-slope-slope (CSS) and slope-cliff-slope (SCS) cases first.Holes in the mesh.The problem occurs because the boundary vertices get perturbed. This means that they no longer lie exactly along the cliff's side, which produces a crack. Sometimes these holes aren't noticeable, and sometimes they are glaring.The solution is to not perturb the boundary vertex. This means that we need to control whether a point gets perturbed or not. The simplest approach is to create an AddTriangle alternative which does not perturb the vertices at all.void AddTriangleUnperturbed ( v1,
v3) {int vertexIndex = vertices.Cvertices.Add(v1);vertices.Add(v2);vertices.Add(v3);triangles.Add(vertexIndex);triangles.Add(vertexIndex + 1);triangles.Add(vertexIndex + 2);}Adjust TriangulateBoundaryTriangle so it uses this method. This means that is has to explicitly perturb all vertices, except the boundary vertex.void TriangulateBoundaryTriangle (
begin, HexCell beginCell,
left, HexCell leftCell,
boundaryColor ) {
v2 = HexMetrics.TerraceLerp(begin, left, 1);
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, 1);AddTriangleUnperturbed(Perturb(begin),Perturb(v2), boundary);
AddTriangleColor(beginCell.color, c2, boundaryColor); for (int i = 2; i & HexMetrics.terraceS i++) {
v2 = HexMetrics.TerraceLerp(begin, left, i);
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, i);AddTriangleUnperturbed(Perturb(v1),Perturb(v2), boundary);
AddTriangleColor(c1, c2, boundaryColor);
}AddTriangleUnperturbed(Perturb(v2),Perturb(left), boundary);
AddTriangleColor(c2, leftCell.color, boundaryColor); }Note that because we are not using v2 to derive any other point, it is possible to perturb it immediately. It is a simple optimization to make and it reduces code, so let's do it.void TriangulateBoundaryTriangle (
begin, HexCell beginCell,
left, HexCell leftCell,
boundaryColor ) {
v2 =Perturb(HexMetrics.TerraceLerp(begin, left, 1));
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, 1);
AddTriangleUnperturbed(Perturb(begin),v2, boundary);
AddTriangleColor(beginCell.color, c2, boundaryColor); for (int i = 2; i & HexMetrics.terraceS i++) {
v2 =Perturb(HexMetrics.TerraceLerp(begin, left, i));
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, i);
AddTriangleUnperturbed(v1,v2, boundary);
AddTriangleColor(c1, c2, boundaryColor);
AddTriangleUnperturbed(v2, Perturb(left), boundary);
AddTriangleColor(c2, leftCell.color, boundaryColor); }Unperturbed boundaries.This looks better, but we're not done yet. Inside the TriangulateCornerTerracesCliff method, the boundary point is found by interpolating between the left and right points. However, these points are not yet perturbed. To make the boundary point match the final cliff, we have to interpolate between the perturbed points. boundary = .Lerp(Perturb(begin),Perturb(right), b);The same is true for the TriangulateCornerCliffTerraces method. boundary = .Lerp(Perturb(begin),Perturb(left), b);No more holes.Double Cliffs and a SlopeThe remaining problem cases are those that feature two cliffs and a slope.Big hole caused by a single triangle.This problem is fixed by using manual perturbation for the single triangle in the else block at the end of TriangulateCornerTerracesCliff.else {AddTriangleUnperturbed(Perturb(left),Perturb(right), boundary);
AddTriangleColor(leftCell.color, rightCell.color, boundaryColor);
}And the same goes for TriangulateCornerCliffTerraces.else {AddTriangleUnperturbed(Perturb(left),Perturb(right), boundary);
AddTriangleColor(leftCell.color, rightCell.color, boundaryColor);
}The last cracks are gone.TweakingWe now have a fully correct perturbed mesh. Its exact appearance depends on the specific noise, its scale, and the perturbation strengths. In our case, the perturbation might be a bit too strong. While an irregular appearance is nice, we don't want the cells to deviate too much from the regular grid. After all, we still use that to determine which cell we're editing. And if the size of cells varies too much, it will be harder to fit content inside them later.Unperturbed vs. perturbed.A cell perturbation strength of 5 just seems a tad too much.Cell perturbation from 0 to 5.Let's reduce it to 4, to make it a bit more manageable without becoming too regular. That guarantees a maximum XZ displacement of √32 ≈ 5.66.public const float cellPerturbStrength =4f;Cell perturbation 4.Another value that we can tweak is the solid factor. If we increase it, the flat cell centers will become larger. This leaves more room for future content. Of course, they will also become more hexagonal.Solid factor from 0.75 to 0.95.A slight increase of the solid factor to 0.8 will make life a little easier for use later.public const float solidFactor =0.8f;Solid factor 0.8.Finally, the differences between elevation levels are a bit steep. That's handy when checking whether the mesh is generated correctly, but we're done with that. Let's reduce it to one unit per terrace step, so 3.public const float elevationStep =3f;Elevation step reduced to 3.We could also adjust the elevation perturb strength. But it's current set to 1.5, which is now equal to half an elevation step, which is fine.A smaller elevation step also makes it more practical to use all seven of our elevation levels. This allows us to add even more variety to our map.Using seven elevation levels.The next tutorial is .
Unity 六边形地图系列(四) : 不规则性
版权所有,禁止匿名转载;禁止商业使用;禁止个人使用。
翻译:王成林(麦克斯韦的麦斯威尔)
审校:黄秀美(厚德载物) 对噪音纹理进行取样微扰(perturb)顶点保持单元平直对单元的边进行细分 此教程为教程系列的第四篇。目前为止,我们的网格已经成为了一个严格的蜂巢状物体。在此基础上,我们引入不规则性来使我们的地图看上去更自然。不再规则的六边形 1 噪音为了加入一些不规则性,我们需要随机化处理。但不是真正的随机。我们要在编辑地图的时候让事物保持连贯,否则每次做出改变时地图上的东西都会跳来跳去。所以我们需要一种能重复产生的伪随机噪音。 柏林噪音(Perlin noise)是一个好的选择。它在任何时候都能重复产生。当多个频率被综合在一起的时候,它还会在大范围内产生差异很大、在小范围内产生差异很小的噪音。这样产生的扭曲相对平滑。距离非常近的点趋向于靠近彼此,而不是朝不同的方向扭曲。 我们可以使用程序产生柏林噪音。教程解释了方法。但是我们还可以从预先生成的噪音纹理中抽样。使用纹理的优势是它要比计算多频率的柏林噪音更方便更快捷。劣势就是纹理使用的内存更多而且只会覆盖一小范围的噪音。所以它需要是一个图块纹理(tiling texture),而且要很大才能让图块的纹理不那么明显。 1.1噪音纹理我们将要使用一个纹理,所以你现在不需要学习教程。这意味着我们需要有这样的纹理。比如这样的。柏林噪音纹理图块上面的纹理图块包括多个频率的柏林噪音。这是一个平均值为0.5、极值靠近0和1的灰度图像。 等一下,这只是每个点的单独数值。如果想要一个3D扭曲,我们需要至少三个伪随机抽样!所以我们需要两个额外的纹理,每个有不同的噪音。 我们可以那样做,或者我们可以在每个颜色频道中存储一个不同的噪音数值。这允许我们在一个单独的纹理中存储至多四种不同的噪音样式。这是一个这样的纹理。四合一
####参考信息################################################################如何制作这个纹理?我使用。这是我编写的一个Unity程序中的纹理编辑器。 ####参考信息################################################################ 下载这张纹理然后将它导入你的Unity工程中。因为我们要使用代码对纹理进行取样,它需要是可读的。将TextureType切换到Advanced然后切换到Read/Write Enabled。这会把纹理数据保存在内存中,可以使用C#代码进行处理。确认设置Format到Automatic Truecolor,否则这不会起作用。我们不希望由于纹理挤压破坏我们的噪音样式。 我们可以禁用Generate Mip Maps,因为我们不需要它们。既然已经来到这一步了,我们还可以开启Bypass SRGB Sampling。我们不需要这个,但是它是正确的。它表示纹理在Gamma空间中不包含颜色数据。导入的噪音纹理
####参考信息################################################################什么时候需要SRGB取样?如果我们需要在着色器中使用噪音纹理将会不一样。当使用线性渲染模式时,纹理取样自动将颜色数据从Gamma转换到线性颜色空间。这会使我们的噪音纹理产生不正确的结果,所以我们不想让它发生。####参考信息################################################################1.2 噪音取样让我们在HexMetrics中加入噪音取样功能,以便可以在任何地方使用它。这意味着HexMetrics有一个噪音纹理的引用。 publicstatic noiseS 因为它不是一个组件,我们不能将纹理通过编辑器分配给它。我们使用HexGrid作为一个中转介质。由于HexGrid是第一个有所行动的,如果我们可以在它的Awake方法开始的时候将纹理传递下去就好了。1234567public Texture2D noiseS void Awake () {
HexMetrics.noiseSource = noiseS
…} 但是,这个方法在游戏模式中不会成功重编译。静态变量不会被Unity序列化。要想解决这个问题,我们在OnEnable事件方法中重新分配纹理。这个方法在重编译后会被调用。123void OnEnable () {
HexMetrics.noiseSource = noiseS} 分配噪音纹理 由于HexMetrics可以处理纹理,我们在其中加入一个便捷的噪音取样方法。这个方法需要一个世界位置的输入参数,然后产生一个包含四个噪音取样的4D向量。12public static Vector4 SampleNoise (Vector3 position) {} 样本是使用双线性过滤对纹理取样产生的,它使用X和Z世界坐标作为UV坐标。由于我们的噪音来源是2D的,我们忽略了第三个世界坐标。如果我们的噪音来源是3D的,我们也会使用Y轴坐标。 最终我们得到了一个可以转换为4D向量的颜色。这个转换是隐式的,意味着我们可以直接返回颜色,不用使用(Vector4)进行显示转换。123public static Vector4 SampleNoise (Vector3 position) {
return noiseSource.GetPixelBilinear(position.x, position.z);} ####参考信息################################################################ 双线性过滤是如何工作的?查看教程,其中解释了UV坐标系和纹理过滤。 ####参考信息################################################################2 微扰顶点我们通过分别微扰规则蜂巢网格的每个顶点来对它进行扭曲。所以我们在HexMesh中加入一个Perturb方法来完成它。它的输入参数为一未微扰的点,返回一个微扰后的点。它使用未微扰的点来对我们的噪音进行取样。123Vector3 Perturb (Vector3 position) {
Vector4 sample = HexMetrics.SampleNoise(position);} 我们将X,Y,Z噪音样本直接加入到相应的坐标中,然后将它作为结果。1234567Vector3 Perturb (Vector3 position) {
Vector4 sample = HexMetrics.SampleNoise(position);
position.x += sample.x;
position.y += sample.y;
position.z += sample.z;
return } 我们如何快速地改变HexMesh以便所有的顶点都被微扰?在每个顶点被加入顶点列表中的时候,在AddTriangle和AddQuad中对它们进行调整。让我们这样做吧。12345678910111213141516void AddTriangle (Vector3 v1, Vector3 v2, Vector3 v3) {
int vertexIndex = vertices.C
vertices.Add(Perturb(v1));
vertices.Add(Perturb(v2));
vertices.Add(Perturb(v3));
…} void AddQuad (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4) {
int vertexIndex = vertices.C
vertices.Add(Perturb(v1));
vertices.Add(Perturb(v2));
vertices.Add(Perturb(v3));
vertices.Add(Perturb(v4));
…}####参考信息################################################################ 在微扰四边形的顶点后它们还是平的吗?它们极有可能不是。它们由两个三角形构成,不需要再对齐了。但是,因为那些三角形共用两个顶点,这些顶点的法线会变得平缓。这意味着你不会在两个三角形之间看到一个突兀的转变。如果扭曲幅度不是很大,你还是会看到平的四边形。####参考信息################################################################微扰后的顶点,跟没有被微扰过差不多 看上去没怎么改变,除了单元的标签似乎消失了。这是由于我们向点中加入了噪音样本,而它们总是正的。所以所有的三角形最终都位于标签上方,将它们隐藏了起来。我们需要调整居中这样它们可以去往任意方向了。将噪音样本的范围从0—1改为-1—1。1234567Vector3 Perturb (Vector3 position) {
Vector4 sample = HexMetrics.SampleNoise(position);
position.x += sample.x * 2f - 1f;
position.y += sample.y * 2f - 1f;
position.z += sample.z * 2f - 1f;
return } 居中的微扰 2.1 微扰强度我们现在已经扭曲了网格,但是效果很微弱。每个维度至多有一个单位的调整。所以理论最大位移是个单位,但这相当罕见,几乎不可能。由于我们单元的外径为10个单位,微扰相对很小。 解决方法是在HexMetrics中加入一个幅度设置以便我们可以调整微扰的幅度。让我们试试幅度为5。现在理论最大位移为个单位,应该更明显了。 publicconstfloat cellPerturbStrength =5f; 在HexMesh.Perturb中将样本乘以幅度。1234567Vector3 Perturb (Vector3 position) {
Vector4 sample = HexMetrics.SampleNoise(position);
position.x += (sample.x * 2f - 1f) * HexMetrics.cellPerturbS
position.y += (sample.y * 2f - 1f) * HexMetrics.cellPerturbS
position.z += (sample.z * 2f - 1f) * HexMetrics.cellPerturbS
return } 增加的幅度 2.2 噪音幅度虽然在编辑之前网格已经看上去不错了,但是阶地一出现就坏事了。它们的顶点被以不同的方向进行扭曲,导致一团糟。柏林噪音应该不是造成这个问题的原因。 这个问题发生的原因是我们直接使用世界坐标对噪音进行取样。这导致纹理使用图块覆盖每个单位,而我们的单元要更大些。实际上,纹理在任意位置被取样,破坏了它拥有的所有的连贯性。覆盖蜂巢的10*10网格线 我们需要调整噪音取样的大小以便纹理覆盖更大的区域。我们在HexMetrics中加入这个大小,然后将它设为0.003,然后使用这个参数调整样本坐标。12345678public const float noiseScale = 0.003f; public static Vector4 SampleNoise (Vector3 position) {
return noiseSource.GetPixelBilinear(
position.x * noiseScale,
position.z * noiseScale
);}突然我们的纹理覆盖333个方块单位,它的本地连贯性也变得明显。调整大小后的噪音 新的大小还确保了噪音在覆盖前有一段时间。事实上,由于单元的内径为10个单位,它永远也不会准确地覆盖在X轴上。但是,由于噪音的本地一致性,你仍会在更大尺度上检测到重复的样式,大约每20个单元,虽然细节有偏差。但是仅对于一张毫无特征的地图的确非常明显。 3单元中心对平微扰所有的顶点使我们的地图显得更自然,但是还有一些问题。由于单元现在不对称了,它们的标签和网格物体相交叉。阶地和陡坡相交处会发生裂痕。我们把裂痕留到后面处理,先集中注意力在单元表面上。网格少了,问题多了 解决相交问题的最简单的方法是保持单元中心的平直。不要在HexMesh.Perturb中调整Y坐标。
Perturb ( position) {
sample = HexMetrics.SampleNoise(position);
position.x += (sample.x * <span style='font-family:&微软雅黑&,&sans-serif&;color:#f - <span style='font-family:&微软雅黑&,&sans-serif&;color:#f) * HexMetrics.cellPerturbS
position.y += (sample.y * 2f – 1f) *HexMetrics.cellPerturbS//
position.y += (sample.y * 2f - 1f) * HexMetrics.cellPerturbS
position.z += (sample.z * <span style='font-family:&微软雅黑&,&sans-serif&;color:#f - <span style='font-family:&微软雅黑&,&sans-serif&;color:#f) * HexMetrics.cellPerturbS
} 划分了高度级别的单元 这个变化不改变单元中心和阶地台阶的垂直位置。注意这只在XZ平面中将最大位移减少到。 这个改变并不坏,因为我们可以更加容易地区分每个单元和阻止阶地混乱。但是一些垂直微扰也还是可以的。 3.1微扰单元高度我们对每个单元进行垂直微扰,而不是对每个顶点。这样的话每个单元都保持平直,但是单元之间还是有差别。使用不同的尺度对高度微扰也很合理,所以在HexMetrics中加入一个。一个1.5个单位的幅度会引起微弱的改变,差不多是一个阶地阶梯的高度了。 publicconstfloat elevationPerturbStrength =1.5f; 调整HexCell.Elevation性质以让它将这个微扰应用在单元的垂直位置上。123456789101112131415161718public int Elevation {
elevation =
Vector3 position = transform.localP
position.y = value * HexMetrics.elevationS
position.y +=
(HexMetrics.SampleNoise(position).y * 2f - 1f) *
HexMetrics.elevationPerturbS
transform.localPosition =
Vector3 uiPosition = uiRect.localP
uiPosition.z = -position.y;
uiRect.localPosition = uiP
}} 为了确认微扰被立即应用了,我们需要在HexGrid.CreateCell中显式地设置每个单元的高度。否则网格开始就会是平的。把这一步放在最后,在UI被创建之后。12345void CreateCell (int x, int z, int i) {
cell.Elevation = 0;}微扰后的高度,有裂痕 3.2使用同样的高度由于在三角化网格物体的时候我们没有连续地使用相同的单元高度,物体中出现了许多裂缝。我们在HexCell中加入一个方便的性质来得到它的位置以便在别处使用。12345public Vector3 Position {
return transform.localP
}} 现在我们使用HexMesh.Triangulate中的那个性质来确定单元的中心。1234void Triangulate (HexDirection direction, HexCell cell) {
Vector3 center = cell.P
…} 当要确定临近单元的垂直位置时我们可以在TriangulateConnection中使用它。1234567891011121314151617181920void TriangulateConnection (
HexDirection direction, HexCell cell, Vector3 v1, Vector3 v2) {
Vector3 bridge = HexMetrics.GetBridge(direction);
Vector3 v3 = v1 +
Vector3 v4 = v2 +
v3.y = v4.y = neighbor.Position.y;
HexCell nextNeighbor = cell.GetNeighbor(direction.Next());
if (direction &= HexDirection.E && nextNeighbor != null) {
Vector3 v5 = v2 + HexMetrics.GetBridge(direction.Next());
v5.y = nextNeighbor.Position.y;
}} 连贯地使用单元高度4 细分单元的边虽然我们为单元增添了很好的差异性,但是它们明显还是六边形。这本身不是个问题,但是我们可以做的更好。清晰的六边形单元 如果有更多的顶点,我们可以看到更多的本地变化。那么让我们通过在每对角的中间位置引入一个边上的顶点来将每个单元的边分为两个部分。这意味着HexMesh.Triangulate需要加入两个而不是一个三角。12345678910111213141516void Triangulate (HexDirection direction, HexCell cell) {
Vector3 center = cell.P
Vector3 v1 = center + HexMetrics.GetFirstSolidCorner(direction);
Vector3 v2 = center + HexMetrics.GetSecondSolidCorner(direction);
Vector3 e1 = Vector3.Lerp(v1, v2, 0.5f);
AddTriangle(center, v1, e1);
AddTriangleColor(cell.color);
AddTriangle(center, e1, v2);
AddTriangleColor(cell.color);
if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell, v1, v2);
}} 12条边而不是6条 将顶点和三角的数量翻倍为我们单元的边加入了一些多样性。我们可以再进一步将顶点数量乘3。123456789Vector3 e1 = Vector3.Lerp(v1, v2, 1f / 3f);Vector3 e2 = Vector3.Lerp(v1, v2, 2f / 3f); AddTriangle(center, v1, e1);AddTriangleColor(cell.color);AddTriangle(center, e1, e2);AddTriangleColor(cell.color);AddTriangle(center, e2, v2);AddTriangleColor(cell.color); 18条边 4.1 细分边部的连接当然我们还需要细分边部的连接。所以向TriangulateConnection传递一个新的边上顶点。123if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell, v1, e1, e2, v2);} 向TriangulateConnection中加入匹配的参数这样它可以处理额外的顶点。123456
void TriangulateConnection (
HexDirection direction, HexCell cell,
Vector3 v1, Vector3 e1, Vector3 e2, Vector3 v2
…} 我们还需要为邻居单元计算额外边上的顶点。我们可以在连接到另一边之后计算它们。1234567Vector3 bridge = HexMetrics.GetBridge(direction);Vector3 v3 = v1 +Vector3 v4 = v2 +v3.y = v4.y = neighbor.Position.y; Vector3 e3 = Vector3.Lerp(v3, v4, 1f / 3f);Vector3 e4 = Vector3.Lerp(v3, v4, 2f / 3f); 接下来,我们需要调整边的三角化。目前忽略插入了阶地的斜面,简单将三个四边形相加来代替一个。 1234567891011if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(v1, v2, cell, v3, v4, neighbor);}else {
AddQuad(v1, e1, v3, e3);
AddQuadColor(cell.color, neighbor.color);
AddQuad(e1, e2, e3, e4);
AddQuadColor(cell.color, neighbor.color);
AddQuad(e2, v2, e4, v4);
AddQuadColor(cell.color, neighbor.color);}被细分的连接 4.2 将边部顶点归拢(bundle)由于现在我们需要四个顶点来描述一条边,那么可以合理地将它们归拢在一起。这要比处理四个单独的顶点要容易得多。为此我们创建一个简单的EdgeVertices结构体。它应该包含四个顶点,顺序为沿单元的边顺时针方向。123456using UnityE public struct EdgeVertices {
public Vector3 v1, v2, v3, v4;}####参考信息################################################################ 它不需要被序列化吗?我们只有在三角化的时候使用这个结构。现在我们不会储存边上的顶点。所以不需要它序列化。 ####参考信息################################################################加入一个便捷的构造器方法来处理计算中转介质边缘的点。123456public EdgeVertices (Vector3 corner1, Vector3 corner2) {
v1 = corner1;
v2 = Vector3.Lerp(corner1, corner2, 1f / 3f);
v3 = Vector3.Lerp(corner1, corner2, 2f / 3f);
v4 = corner2;} 现在我们可以在HexMesh中加入一个单独的三角化方法以便在一个单元的中心和一条边之间创建一个三角扇形。12345678void TriangulateEdgeFan (Vector3 center, EdgeVertices edge, Color color) {
AddTriangle(center, edge.v1, edge.v2);
AddTriangleColor(color);
AddTriangle(center, edge.v2, edge.v3);
AddTriangleColor(color);
AddTriangle(center, edge.v3, edge.v4);
AddTriangleColor(color);} 以及一个对两条边之间一条四边形进行三角化的方法。1234567891011void TriangulateEdgeStrip (
EdgeVertices e1, Color c1,
EdgeVertices e2, Color c2) {
AddQuad(e1.v1, e1.v2, e2.v1, e2.v2);
AddQuadColor(c1, c2);
AddQuad(e1.v2, e1.v3, e2.v2, e2.v3);
AddQuadColor(c1, c2);
AddQuad(e1.v3, e1.v4, e2.v3, e2.v4);
AddQuadColor(c1, c2);} 这允许我们简化Triangulate方法。12345678910111213void Triangulate (HexDirection direction, HexCell cell) {
Vector3 center = cell.P
EdgeVertices e = new EdgeVertices(
center + HexMetrics.GetFirstSolidCorner(direction),
center + HexMetrics.GetSecondSolidCorner(direction)
TriangulateEdgeFan(center, e, cell.color);
if (direction &= HexDirection.SE) {
TriangulateConnection(direction, cell, e);
}} 转到TriangulateConnection。我们现在可以使用TriangulateEdgeStrip,但是还需要做一些其它的替换。在我们第一次使用v1的地方,我们现在应该使用e1.v1。同样,v2变成了e1.v4,v3变成了e2.v1,v4变成了e2.v4。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950void TriangulateConnection (
HexDirection direction, HexCell cell, EdgeVertices e1) {
HexCell neighbor = cell.GetNeighbor(direction);
if (neighbor == null) {
Vector3 bridge = HexMetrics.GetBridge(direction);
bridge.y = neighbor.Position.y - cell.Position.y;
EdgeVertices e2 = new EdgeVertices(
e1.v1 + bridge,
e1.v4 + bridge
if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(e1.v1, e1.v4, cell, e2.v1, e2.v4, neighbor);
TriangulateEdgeStrip(e1, cell.color, e2, neighbor.color);
HexCell nextNeighbor = cell.GetNeighbor(direction.Next());
if (direction &= HexDirection.E && nextNeighbor != null) {
Vector3 v5 = e1.v4 + HexMetrics.GetBridge(direction.Next());
v5.y = nextNeighbor.Position.y;
if (cell.Elevation &= neighbor.Elevation) {
if (cell.Elevation &= nextNeighbor.Elevation) {
TriangulateCorner(
e1.v4, cell, e2.v4, neighbor, v5, nextNeighbor
TriangulateCorner(
v5, nextNeighbor, e1.v4, cell, e2.v4, neighbor
else if (neighbor.Elevation &= nextNeighbor.Elevation) {
TriangulateCorner(
e2.v4, neighbor, v5, nextNeighbor, e1.v4, cell
TriangulateCorner(
v5, nextNeighbor, e1.v4, cell, e2.v4, neighbor
} 4.3 细分阶地我们还需要细分阶地。所以将边传入TriangulateEdgeTerraces。123if (cell.GetEdgeType(direction) == HexEdgeType.Slope) {
TriangulateEdgeTerraces(e1, cell, e2, neighbor);} 现在我们需要调整TriangulateEdgeTerraces以便它在边之间插值而不是在一对顶点之间插值。我们假设EdgeVertices有一个便捷的静态插值方法。这可以让我们简化TriangulateEdgeTerraces而不是让它更复杂。12345678910111213141516171819void TriangulateEdgeTerraces (
EdgeVertices begin, HexCell beginCell,
EdgeVertices end, HexCell endCell) {
EdgeVertices e2 = EdgeVertices.TerraceLerp(begin, end, 1);
Color c2 = HexMetrics.TerraceLerp(beginCell.color, endCell.color, 1);
TriangulateEdgeStrip(begin, beginCell.color, e2, c2);
for (int i = 2; i & HexMetrics.terraceS i++) {
EdgeVertices e1 = e2;
Color c1 = c2;
e2 = EdgeVertices.TerraceLerp(begin, end, i);
c2 = HexMetrics.TerraceLerp(beginCell.color, endCell.color, i);
TriangulateEdgeStrip(e1, c1, e2, c2);
TriangulateEdgeStrip(e2, c2, end, endCell.color);} EdgeVertices.TerraceLerp方法简单地在所有四对两个边上的顶点之间进行插值。12345678910public static EdgeVertices TerraceLerp (
EdgeVertices a, EdgeVertices b, int step){
result.v1 = HexMetrics.TerraceLerp(a.v1, b.v1, step);
result.v2 = HexMetrics.TerraceLerp(a.v2, b.v2, step);
result.v3 = HexMetrics.TerraceLerp(a.v3, b.v3, step);
result.v4 = HexMetrics.TerraceLerp(a.v4, b.v4, step);
return } 被细分的阶地 5 重新连接陡坡和阶地之前我们忽略了陡坡和阶地相交部分的裂缝。现在是时间处理这个问题了。让我们首先考虑陡-斜-斜(CSS)和斜-陡-斜(SCS)情况。 网格物体中的空洞 问题的发生是由于边界的顶点被微扰了。这意味着它们不再准确地沿着陡坡边,从而产生裂痕。有些时候这些空洞没那么明显,有时候它们很惹眼。 解决方法是不要微扰边界顶点。这意味着我们需要控制一个点是否需要被微扰。最简单的方法是创建一个根本不微扰顶点的AddTriangle备选方法。123456789void AddTriangleUnperturbed (Vector3 v1, Vector3 v2, Vector3 v3) {
int vertexIndex = vertices.C
vertices.Add(v1);
vertices.Add(v2);
vertices.Add(v3);
triangles.Add(vertexIndex);
triangles.Add(vertexIndex + 1);
triangles.Add(vertexIndex + 2);} 调整TriangulateBoundaryTriangle让它使用这个方法。这意味着它需要显式地微扰除了边界顶点的所有顶点。1234567891011121314151617181920212223void TriangulateBoundaryTriangle (
Vector3 begin, HexCell beginCell,
Vector3 left, HexCell leftCell,
Vector3 boundary, Color boundaryColor) {
Vector3 v2 = HexMetrics.TerraceLerp(begin, left, 1);
Color c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, 1);
AddTriangleUnperturbed(Perturb(begin), Perturb(v2), boundary);
AddTriangleColor(beginCell.color, c2, boundaryColor);
for (int i = 2; i & HexMetrics.terraceS i++) {
Vector3 v1 = v2;
Color c1 = c2;
v2 = HexMetrics.TerraceLerp(begin, left, i);
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, i);
AddTriangleUnperturbed(Perturb(v1), Perturb(v2), boundary);
AddTriangleColor(c1, c2, boundaryColor);
AddTriangleUnperturbed(Perturb(v2), Perturb(left), boundary);
AddTriangleColor(c2, leftCell.color, boundaryColor);} 注意由于我们没有使用v2来推导其它的点,我们可以立即微扰它。这是一个减少代码量的简单的优化,让我们这样做吧。1234567891011121314151617181920212223void TriangulateBoundaryTriangle (
Vector3 begin, HexCell beginCell,
Vector3 left, HexCell leftCell,
Vector3 boundary, Color boundaryColor) {
Vector3 v2 = Perturb(HexMetrics.TerraceLerp(begin, left, 1));
Color c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, 1);
AddTriangleUnperturbed(Perturb(begin), v2, boundary);
AddTriangleColor(beginCell.color, c2, boundaryColor);
for (int i = 2; i & HexMetrics.terraceS i++) {
Vector3 v1 = v2;
Color c1 = c2;
v2 = Perturb(HexMetrics.TerraceLerp(begin, left, i));
c2 = HexMetrics.TerraceLerp(beginCell.color, leftCell.color, i);
AddTriangleUnperturbed(v1, v2, boundary);
AddTriangleColor(c1, c2, boundaryColor);
AddTriangleUnperturbed(v2, Perturb(left), boundary);
AddTriangleColor(c2, leftCell.color, boundaryColor);} 未被微扰的边界 这看上去好多了,但是我们还没有完成。在TriangulateCornerTerracesCliff方法中,边界点是通过在左右点之间插值得到的。然而,这些点没有被微扰。为了让边界点与最后的陡坡相匹配,我们需要在微扰后的点之间做插值。
boundary = .Lerp(Perturb(begin), Perturb(right), b); 该方法同样适用于TriangulateCornerCliffTerraces。
boundary = .Lerp(Perturb(begin), Perturb(left), b); 没有任何空洞了 5.1 双陡坡和一个斜坡剩下的情况是那些有两个陡坡和一个斜坡的。一个三角形产生的大空洞 这个问题可以通过在TriangulateCornerTerracesCliff结尾处的else代码块中手动微扰单个三角进行修正。1234else {
AddTriangleUnperturbed(Perturb(left), Perturb(right), boundary);
AddTriangleColor(leftCell.color, rightCell.color, boundaryColor);} TriangulateCornerCliffTerraces也一样。1234else {
AddTriangleUnperturbed(Perturb(left), Perturb(right), boundary);
AddTriangleColor(leftCell.color, rightCell.color, boundaryColor);} 最后一个裂缝也没了
6 调整我们现在有一个完全正确的微扰后的网格物体。它的准确的外观取决于特定的噪音,它的大小,以及微扰幅度。在我们的情况中,微扰也许有点强了。虽然不规则的外表很好,但是我们不希望偏离正常单元太多。毕竟,我们仍然使用它来确定我们正在编辑的是哪个单元。如果单元的大小差异太大,那么以后将很难在它们内部加入一些内容。 未被微扰vs已被微扰 一个微扰强度为5的单元看上去太过了。 从0到5的单元微扰 让我们将它减为4,让它在不那么规则的同时更合理些。这保证了XZ的最大位移值是。 public const float cellPerturbStrength = 4f; 单元微扰为4 另外一个我们可以稍微调整的数值是纯色因子。如果我们增加它,平直的单元中心会变大。这为未来的内容提供了空间。当然,它们也会变得更像六边形。 从0.75到0.95的纯色因子 将纯色因子稍微增加到0.8会便于以后使用。 public const float solidFactor = 0.8f; 纯色因子为0.8 最后,不同高度之间的落差显得有些陡峭。对于检查网格物体是否正确生成有帮助,但是我们已经完成那一步了。让我们将它减为每个阶地阶梯1个单位,所以为3. public const float elevationStep = 3f;高度阶梯减小为3 我们还可以调整高度微扰的幅度。但是目前设为1.5,为高度阶梯的一半已经很好了。 一个稍小的高度阶梯会让我们更方便地使用所有七个高度级别。这允许我们为地图加入更多的变化。 使用七个高度级别
下一篇教程是。 【版权声明】原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。
下载次数:17
分类:程序新手圈
请勿发表无意义的内容请勿发表重复内容请勿发表交易类内容禁止发表广告宣传贴请使用文明用语其它
淫秽色情政治倾向人身攻击抄袭剽窃广告刷屏恶意挖坟冒充他人其它
登录后参与讨论。点击

我要回帖

更多关于 unity3d ugui制作血条 的文章

 

随机推荐