【菜菜丸的菜鸟教程】动手做个生命游戏 体验无穷演化奥妙(2)
四、脚本编写(一)创建“细胞”首先,让我们来创建生命游戏的基本单位——“细胞”。在Hierarchy窗口中单击右键>2D Object>Sprites>Square,将其重命名为Tile。接下来,在Project窗口中新建文件夹Scripts,用来存放游戏脚本。在Scripts文件夹中新建C#脚本,重命名为Tile,将这个脚本挂到刚创建的2D物体Tile上。再在Project窗口
·
四、脚本编写
(一)创建“细胞”
首先,让我们来创建生命游戏的基本单位——“细胞”。
在Hierarchy窗口中单击右键>2D Object>Sprites>Square,将其重命名为Tile。
接下来,在Project窗口中新建文件夹Scripts,用来存放游戏脚本。在Scripts文件夹中新建C#脚本,重命名为Tile,将这个脚本挂到刚创建的2D物体Tile上。
再在Project窗口中新建文件夹Prefabs,将挂好脚本的2D物体Tile拖入这个文件夹,存为预制体。删掉Hierarchy窗口中的2D物体Tile。
(二)生成网格背景
首先,在Hierachy窗口中单击右键>Create Empty,创建一个空白的新物体,重命名为Grid Manager,然后在Scripts文件夹中新建脚本GridManager,把这个脚本挂在Grid Manager上。
接下来我们开始写GridManager脚本。我们已经有了构成网格的“细胞”,只需通过嵌套for循环就可以生成整张网格了。
using
System
.
Collections
;
using
System
.
Collections
.
Generic
;
using
UnityEngine
;
public
class
GridManager
:
MonoBehaviour
{
public
int
width
,
height
;
[
SerializeField
]
private
Tile
tilePrefab
;
[
SerializeField
]
private
Transform
cam
;
public
void
Start
(
)
{
GenerateGrid
(
)
;
}
void
GenerateGrid
(
)
{
for
(
int
x
=
0
;
x
<
width
;
x
++
)
{
for
(
int
y
=
0
;
y
<
height
;
y
++
)
{
var
spawnedTile
=
Instantiate
(
tilePrefab
,
new
Vector3
(
x
,
y
)
,
Quaternion
.
identity
)
;
spawnedTile
.
name
=
$"Tile
{
x
}
{
y
}
"
;
}
}
cam
.
transform
.
position
=
new
Vector3
(
(
float
)
width
/
2
-
0.5f
,
(
float
)
height
/
2
+
1.8f
,
-
10
)
;
}
}
回到Unity窗口,找到Grid Manager上挂载的GridManager脚本组件,将Main Camera拖入Cam栏位,将Tile预制体拖入Tile Prefab栏位,并设置表格的长(Width)宽(Height)。示例中的Width为60,Height为34,Main Camera的Size为20。这些参数,包括脚本中对摄像机位置的参数设置,都可以根据你的需要来调整,直至获得满意效果。
现在我们有了一张“网格”,但这张网格看上去就像一块白板,没有任何分界线。这是因为所有格子都是相同颜色。为了解决这个问题,我们要让格子的颜色交错,方便后面的互动操作和显示。
在脚本Tile中,我们让每个格子根据一个布尔值isOffset来确定自身的颜色。(这里我使用了三目运算符,如果你还不熟悉它的用法,请参考官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/conditional-operator)
using
System
.
Collections
;
using
System
.
Collections
.
Generic
;
using
UnityEngine
;
public
class
Tile
:
MonoBehaviour
{
[
SerializeField
]
private
Color
baseColor
,
offsetColor
;
[
SerializeField
]
private
SpriteRenderer
tileRenderer
;
public
void
Init
(
bool
isOffset
)
{
tileRenderer
.
color
=
isOffset
?
offsetColor
:
baseColor
;
}
}
这个布尔值是由格子所在位置决定的。之前生成网格时,我们用格子的坐标x和y给它们起了名字,所以我们回到GridManager脚本中,在这里给isOffset赋值。
void
GenerateGrid
(
)
{
for
(
int
x
=
0
;
x
<
width
;
x
++
)
{
for
(
int
y
=
0
;
y
<
height
;
y
++
)
{
var
spawnedTile
=
Instantiate
(
tilePrefab
,
new
Vector3
(
x
,
y
)
,
Quaternion
.
identity
)
;
spawnedTile
.
name
=
$"Tile
{
x
}
{
y
}
"
;
var
isOffset
=
(
x
%
2
==
0
&&
y
%
2
!=
0
)
||
(
x
%
2
!=
0
&&
y
%
2
==
0
)
;
spawnedTile
.
Init
(
isOffset
)
;
}
}
cam
.
transform
.
position
=
new
Vector3
(
(
float
)
width
/
2
-
0.5f
,
(
float
)
height
/
2
+
1.8f
,
-
10
)
;
}
当x和y一奇一偶时,isOffset为true,否则为false。
回到Unity窗口,打开Tile预制体,给Base Color、Offset Color和Tile Renderer赋值。注意:(1)Base Color和Offset Color最好是两个相近的颜色。(2)给Tile Renderer赋值时,把Tile预制体上的Sprite Renderer拖进去即可。
现在运行游戏,应该能出现一张漂亮的网格了!
(三)点击鼠标,点亮格子
生命游戏在开始演化之前,需要玩家设定一个初始图案。这个图案是通过鼠标点击格子“画”出来的。此外,我们还希望鼠标悬停在某个格子但尚未点击时,格子也会点亮,并在鼠标离开格子后不再点亮。这样更方便我们判断鼠标当前所在位置。
为了呈现出点亮格子的效果,我们先打开Tile预制体添加一个子物体:单击右键>2D Object>Sprites>Square,将其重命名为Highlight。然后,将Highlight的颜色Alpha值调为120。
调整之后,取消勾选Highlight前面的对勾,使之隐藏。
接下来打开Tile脚本,输入以下语句:
public
bool
isClicked
=
false
;
public
GameObject
highlight
;
void
OnMouseEnter
(
)
{
highlight
.
SetActive
(
true
)
;
}
void
OnMouseExit
(
)
{
if
(
isClicked
==
false
)
{
highlight
.
SetActive
(
false
)
;
}
}
void
OnMouseDown
(
)
{
isClicked
=
!
isClicked
;
if
(
isClicked
==
true
)
{
highlight
.
SetActive
(
true
)
;
}
else
{
highlight
.
SetActive
(
false
)
;
}
}
回到Unity窗口,打开Tile预制体,将Hierarchy窗口中Tile的子物体Highlight拖入Tile脚本组件的Highlight栏位。
现在,运行游戏,点击鼠标,将可以点亮格子,画出你想要的图案了。
(四)获取周边格子的信息
一般来说,网格中任意一格周边会有八个格子(如下图),每个格子周边格子的状态决定了这个格子下一轮的状态,根据这些状态,画布上将出现新的“图案”。
在Tile脚本中添加以下内容:
public
GameObject
GridManager
;
public
int
nameX
;
public
int
nameY
;
int
gridCountX
;
int
gridCountY
;
public
List
<
GameObject
>
tempCells
=
new
List
<
GameObject
>
(
)
;
void
Start
(
)
{
gridCountX
=
GridManager
.
GetComponent
<
GridManager
>
(
)
.
width
;
gridCountY
=
GridManager
.
GetComponent
<
GridManager
>
(
)
.
height
;
for
(
int
i
=
-
1
;
i
<=
1
;
i
++
)
{
for
(
int
j
=
-
1
;
j
<=
1
;
j
++
)
{
int
tempX
=
nameX
+
i
;
int
tempY
=
nameY
+
j
;
if
(
tempX
<
gridCountX
&&
tempX
>=
0
&&
tempY
<
gridCountY
&&
tempY
>=
0
&&
(
tempX
!=
nameX
|
tempY
!=
nameY
)
)
{
tempCells
.
Add
(
GameObject
.
Find
(
$"Tile
{
tempX
}
{
tempY
}
"
)
)
;
}
}
}
}
为了获得周边格子的信息,我们创建了一个列表来储存它们。
主体是通过双层for循环实现的,稍微有点难度的是最里层的if语句。加入列表的格子需要满足以下条件:
(1)tempX(格子的x值)>=0
(2)tempY(格子的y值)>=0
(3)tempX<整个网格的长度
(4)tempY<整个网格的宽度
(5)tempX != nameX或tempY != nameY
前四步保证只有在网格中的格子才会加入列表,这样在网格边缘点击格子时,不会将数值溢出网格长宽的格子错误加入列表。
第五步剔除了点击的格子本身,最后列表中只保留点击的格子周围的格子。
之后,我们要把每个格子的nameX和nameY赋值,可以从生成格子的函数中取得这些值。回到GridManager脚本中,将GenerateGrid函数修改为:
void
GenerateGrid
(
)
{
for
(
int
x
=
0
;
x
<
width
;
x
++
)
{
for
(
int
y
=
0
;
y
<
height
;
y
++
)
{
var
spawnedTile
=
Instantiate
(
tilePrefab
,
new
Vector3
(
x
,
y
)
,
Quaternion
.
identity
)
;
spawnedTile
.
name
=
$"Tile
{
x
}
{
y
}
"
;
GameObject
.
Find
(
$"Tile
{
x
}
{
y
}
"
)
.
GetComponent
<
Tile
>
(
)
.
nameX
=
x
;
GameObject
.
Find
(
$"Tile
{
x
}
{
y
}
"
)
.
GetComponent
<
Tile
>
(
)
.
nameY
=
y
;
var
isOffset
=
(
x
%
2
==
0
&&
y
%
2
!=
0
)
||
(
x
%
2
!=
0
&&
y
%
2
==
0
)
;
spawnedTile
.
Init
(
isOffset
)
;
}
}
cam
.
transform
.
position
=
new
Vector3
(
(
float
)
width
/
2
-
0.5f
,
(
float
)
height
/
2
+
1.8f
,
-
10
)
;
}
最后,回到Unity窗口中,将Grid Manager拖入Tile脚本组件中的Grid Manager栏位。
(五)实现生命游戏规则
规则本身的编程并不难,但要注意,生命游戏是
逐代
演化的,也就是每过一段时间变化一次,所以要在整个游戏过程中规律地重复执行生命游戏规则。之前我尝试过使用协程,但是出现了一些问题,所以我改变了写法,用计时器方法来实现。
开始前,先获得周边格子中被点亮的格子个数,然后应用规则。
首先,在Tile脚本中声明一个代表被点亮格子个数的整型变量。
public
int
aliveCellCount
;
接下来,在GridManager脚本中加入以下内容:
float
passedTime
=
0
;
public
float
targetTime
=
0.5f
;
GameObject
[
]
tiles
;
List
<
GameObject
>
temps
;
public
void
Start
(
)
{
tiles
=
GameObject
.
FindGameObjectsWithTag
(
"Tile"
)
;
}
void
Repeat
(
)
{
if
(
passedTime
>
targetTime
)
{
for
(
int
i
=
0
;
i
<
tiles
.
Length
;
i
++
)
{
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
=
0
;
temps
=
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
tempCells
;
foreach
(
GameObject
tile
in
temps
)
{
if
(
tile
.
transform
.
GetChild
(
0
)
.
gameObject
.
activeInHierarchy
==
true
)
{
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
++
;
}
}
}
StartRules
(
)
;
passedTime
=
0
;
}
passedTime
+=
Time
.
deltaTime
;
}
void
StartRules
(
)
{
for
(
int
i
=
0
;
i
<
tiles
.
Length
;
i
++
)
{
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
==
3
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
true
)
;
}
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
==
2
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
activeSelf
)
;
}
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
!=
3
&&
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
!=
2
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
false
)
;
}
}
}
这里还要注意的是,由于脚本中我们使用了寻找标签的方法,所以要将Tile预制体的标签设为Tile。
(六)开始和重启游戏
之前,我们已经做好了游戏的开始按钮和清除按钮。现在,我们要分别写两个按钮事件。当按下开始按钮后,将开始“生命演化”,并禁用鼠标点击。当按下清除按钮时,将清空画布,回到初始状态。
首先,在GridManager脚本中加入以下内容:
public
bool
hasStarted
;
public
void
Start
(
)
{
hasStarted
=
false
;
}
void
Update
(
)
{
if
(
hasStarted
==
true
)
{
Repeat
(
)
;
}
}
public
void
StartButton
(
)
{
hasStarted
=
true
;
}
public
void
ClearButton
(
)
{
hasStarted
=
false
;
foreach
(
GameObject
tile
in
tiles
)
{
tile
.
GetComponent
<
Tile
>
(
)
.
isClicked
=
false
;
tile
.
GetComponent
<
Tile
>
(
)
.
highlight
.
SetActive
(
false
)
;
}
}
在Tile脚本中也要加入开始游戏后禁用鼠标点击这个条件。三个鼠标事件都要修改:
void
OnMouseEnter
(
)
{
if
(
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
highlight
.
SetActive
(
true
)
;
}
}
void
OnMouseExit
(
)
{
if
(
isClicked
==
false
&&
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
highlight
.
SetActive
(
false
)
;
}
}
void
OnMouseDown
(
)
{
if
(
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
isClicked
=
!
isClicked
;
if
(
isClicked
==
true
)
{
highlight
.
SetActive
(
true
)
;
}
else
{
highlight
.
SetActive
(
false
)
;
}
}
}
最后,回到Unity窗口中,在Start Button和Clear Button中把Grid Manager和对应的按钮事件拖入On Click()的相应栏位。
至此,一个简单的生命游戏就做完啦。点亮专属于你的图案,欣赏它美妙的演化过程吧!
完整脚本:
GridManager
using
System
.
Collections
;
using
System
.
Collections
.
Generic
;
using
UnityEngine
;
public
class
GridManager
:
MonoBehaviour
{
public
int
width
,
height
;
float
passedTime
=
0
;
public
float
targetTime
=
0.5f
;
public
bool
hasStarted
;
GameObject
[
]
tiles
;
List
<
GameObject
>
temps
;
[
SerializeField
]
private
Tile
tilePrefab
;
[
SerializeField
]
private
Transform
cam
;
public
void
Start
(
)
{
hasStarted
=
false
;
GenerateGrid
(
)
;
tiles
=
GameObject
.
FindGameObjectsWithTag
(
"Tile"
)
;
}
void
Update
(
)
{
if
(
hasStarted
==
true
)
{
Repeat
(
)
;
}
}
public
void
StartButton
(
)
{
hasStarted
=
true
;
}
public
void
ClearButton
(
)
{
hasStarted
=
false
;
foreach
(
GameObject
tile
in
tiles
)
{
tile
.
GetComponent
<
Tile
>
(
)
.
isClicked
=
false
;
tile
.
GetComponent
<
Tile
>
(
)
.
highlight
.
SetActive
(
false
)
;
}
}
void
Repeat
(
)
{
if
(
passedTime
>
targetTime
)
{
for
(
int
i
=
0
;
i
<
tiles
.
Length
;
i
++
)
{
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
=
0
;
temps
=
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
tempCells
;
foreach
(
GameObject
tile
in
temps
)
{
if
(
tile
.
transform
.
GetChild
(
0
)
.
gameObject
.
activeInHierarchy
==
true
)
{
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
++
;
}
}
}
StartRules
(
)
;
passedTime
=
0
;
}
passedTime
+=
Time
.
deltaTime
;
}
void
StartRules
(
)
{
for
(
int
i
=
0
;
i
<
tiles
.
Length
;
i
++
)
{
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
==
3
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
true
)
;
}
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
==
2
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
activeSelf
)
;
}
if
(
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
!=
3
&&
tiles
[
i
]
.
GetComponent
<
Tile
>
(
)
.
aliveCellCount
!=
2
)
{
tiles
[
i
]
.
transform
.
GetChild
(
0
)
.
gameObject
.
SetActive
(
false
)
;
}
}
}
void
GenerateGrid
(
)
{
for
(
int
x
=
0
;
x
<
width
;
x
++
)
{
for
(
int
y
=
0
;
y
<
height
;
y
++
)
{
var
spawnedTile
=
Instantiate
(
tilePrefab
,
new
Vector3
(
x
,
y
)
,
Quaternion
.
identity
)
;
spawnedTile
.
name
=
$"Tile
{
x
}
{
y
}
"
;
GameObject
.
Find
(
$"Tile
{
x
}
{
y
}
"
)
.
GetComponent
<
Tile
>
(
)
.
nameX
=
x
;
GameObject
.
Find
(
$"Tile
{
x
}
{
y
}
"
)
.
GetComponent
<
Tile
>
(
)
.
nameY
=
y
;
var
isOffset
=
(
x
%
2
==
0
&&
y
%
2
!=
0
)
||
(
x
%
2
!=
0
&&
y
%
2
==
0
)
;
spawnedTile
.
Init
(
isOffset
)
;
}
}
cam
.
transform
.
position
=
new
Vector3
(
(
float
)
width
/
2
-
0.5f
,
(
float
)
height
/
2
+
1.8f
,
-
10
)
;
}
}
Tile
using
System
.
Collections
;
using
System
.
Collections
.
Generic
;
using
UnityEngine
;
public
class
Tile
:
MonoBehaviour
{
[
SerializeField
]
private
Color
baseColor
,
offsetColor
;
[
SerializeField
]
private
SpriteRenderer
tileRenderer
;
public
GameObject
highlight
;
public
GameObject
GridManager
;
public
bool
isClicked
=
false
;
public
int
nameX
;
public
int
nameY
;
int
gridCountX
;
int
gridCountY
;
public
int
aliveCellCount
;
public
List
<
GameObject
>
tempCells
=
new
List
<
GameObject
>
(
)
;
void
Start
(
)
{
gridCountX
=
GridManager
.
GetComponent
<
GridManager
>
(
)
.
width
;
gridCountY
=
GridManager
.
GetComponent
<
GridManager
>
(
)
.
height
;
for
(
int
i
=
-
1
;
i
<=
1
;
i
++
)
{
for
(
int
j
=
-
1
;
j
<=
1
;
j
++
)
{
int
tempX
=
nameX
+
i
;
int
tempY
=
nameY
+
j
;
if
(
tempX
<
gridCountX
&&
tempX
>=
0
&&
tempY
<
gridCountY
&&
tempY
>=
0
&&
(
tempX
!=
nameX
|
tempY
!=
nameY
)
)
{
tempCells
.
Add
(
GameObject
.
Find
(
$"Tile
{
tempX
}
{
tempY
}
"
)
)
;
}
}
}
}
public
void
Init
(
bool
isOffset
)
{
tileRenderer
.
color
=
isOffset
?
offsetColor
:
baseColor
;
}
void
OnMouseEnter
(
)
{
if
(
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
highlight
.
SetActive
(
true
)
;
}
}
void
OnMouseExit
(
)
{
if
(
isClicked
==
false
&&
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
highlight
.
SetActive
(
false
)
;
}
}
void
OnMouseDown
(
)
{
if
(
GameObject
.
Find
(
"Grid Manager"
)
.
GetComponent
<
GridManager
>
(
)
.
hasStarted
==
false
)
{
isClicked
=
!
isClicked
;
if
(
isClicked
==
true
)
{
highlight
.
SetActive
(
true
)
;
}
else
{
highlight
.
SetActive
(
false
)
;
}
}
}
}
游戏演示视频:
更多推荐
已为社区贡献712条内容
所有评论(0)