WillKen's Blog.

3D游戏编程与设计-游戏对象与图形基础

Word count: 2.3kReading time: 13 min
2019/10/07 Share

游戏对象与图形基础

基本操作演练

  • 下载 Fantasy Skybox FREE, 构建自己的游戏场景

    (因为Fantasy Skybox FREE 与本地Unity5.0.0版本不兼容,所以下载了Skybox Series Free)

  • 写一个简单的总结,总结游戏对象的使用

在Asset store中下载相应的天空盒。下载结束后,将其import进我们的项目。

接着可以创建matrial,自己贴上六面的天空盒,当然也可以使用预置的scene直接观察效果。

编程实践

  • 牧师与魔鬼 动作分离版
    • 【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束

  由于第一版本中,控制器会负责几乎所有的工作,是我们的项目结构显得非常臃肿没有将不同动作的逻辑区分开。在第二版本,实现了动作分离。项目地址

  • 首先还是通过FirstController进行一些初始化工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mydirector;
using Mycontroller;

public class FirstController : MonoBehaviour, SceneController, UserAction{

private Vector3 river = new Vector3(6,-0.5F,0);
UserInterface userGUI;
public CoastController fromCoast;
public CoastController toCoast;
public BoatController boat;
private Mycontroller.CharacterController[] characters;
public Move actionManager;
Judge judge;
void Awake() {
Director director = Director.getInstace ();
director.current = this;
userGUI = gameObject.AddComponent <UserInterface>() as UserInterface;
characters = new Mycontroller.CharacterController[6];
loadResources ();
actionManager = gameObject.AddComponent<Move> ()as Move;
}

public void loadResources() {
GameObject Water = Instantiate (Resources.Load ("Prefab/water", typeof(GameObject)),river,Quaternion.identity,null) as GameObject;

Water.name = "Water";
fromCoast = new CoastController ("from");
toCoast = new CoastController ("to");
boat = new BoatController ();
judge = new Judge (fromCoast, toCoast, boat);
for (int i = 0; i < 3; i++) {
Mycontroller.CharacterController cha = new Mycontroller.CharacterController ("priest");
cha.setName("priest" + i);
cha.setPos (fromCoast.getEmptyPos ());
cha.getOnCoast (fromCoast);
fromCoast.getOnCoast (cha);
characters [i] = cha;
}

for (int i = 0; i < 3; i++) {
Mycontroller.CharacterController cha = new Mycontroller.CharacterController ("devil");
cha.setName("devil" + i);
cha.setPos (fromCoast.getEmptyPos());
cha.getOnCoast (fromCoast);
fromCoast.getOnCoast (cha);
characters [i+3] = cha;
}
}

public void moveBoat() {
if (boat.is_empty ())
return;
actionManager.moveBoat (boat.getGameobj (), boat.Move_to (), boat.speed);
}

public void moveCharacter(Mycontroller.CharacterController characterCtrl) {
if (characterCtrl.Is_On_Boat ()) {
CoastController whichCoast;
if (boat.get_is_from () == -1) {
whichCoast = toCoast;
} else {
whichCoast = fromCoast;
}

boat.GetOffBoat (characterCtrl.getName());

Vector3 end_pos = whichCoast.getEmptyPos();
Vector3 mid_pos = new Vector3 (characterCtrl.getGameobj ().transform.position.x,end_pos.y, end_pos.z);
actionManager.moveCharacter(characterCtrl.getGameobj(),mid_pos,end_pos,characterCtrl.move_speed);
characterCtrl.getOnCoast (whichCoast);
whichCoast.getOnCoast (characterCtrl);

} else {
CoastController whichCoast = characterCtrl.getCoastController ();

if (boat.getEmptyIndex () == -1) {
return;
}

if (whichCoast.get_is_from () != boat.get_is_from ())
return;

whichCoast.getOffCoast(characterCtrl.getName());

Vector3 end_pos = boat.getEmptyPosition();
Vector3 mid_pos = new Vector3 (end_pos.x,characterCtrl.getGameobj().transform.position.y,end_pos.z);
actionManager.moveCharacter(characterCtrl.getGameobj(),mid_pos,end_pos,characterCtrl.move_speed);

characterCtrl.getOnBoat (boat);
boat.GetOnBoat (characterCtrl);
}
}
public void restart() {
boat.reset ();
fromCoast.reset ();
toCoast.reset ();
for (int i = 0; i < characters.Length; i++) {
characters [i].reset ();
}
}
void Update () {
userGUI.status = judge.check ();
}
}
  • ActionBase中实现了动作分离的相关定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Myaction{
public class CCMoveToAction : SSAction {
public Vector3 target;
public float speed;

public static CCMoveToAction GetSSAction(Vector3 target, float speed) {
CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}

public override void Update () {
this.transform.position = Vector3.MoveTowards(this.transform.position,target,speed);
if(this.transform.position == target) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
}

public override void Start() {}
}

public class CCSequenceAction : SSAction, ISSActionCallback {
public List<SSAction> sequence;
public int repeat = -1;
public int start = 0;

public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence) {
CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}

public override void Update() {
if (sequence.Count == 0) return;
if (start < sequence.Count)
sequence[start].Update();
}

public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null) {
source.destroy = false;
this.start++;
if (this.start >= sequence.Count) {
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
else {
sequence[start].Start();
}
}
}

public override void Start() {
foreach (SSAction action in sequence) {
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}

private void OnDestroy() {

}
}



public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;

protected SSAction() { }

public virtual void Start()
{
throw new System.NotImplementedException();
}

public virtual void Update()
{
throw new System.NotImplementedException();
}
}



public enum SSActionEventType : int { Started, Completed }

public interface ISSActionCallback
{
void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null);
}



public class SSActionManager : MonoBehaviour ,ISSActionCallback{

private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();
private List <SSAction> waitingAdd = new List <SSAction> ();
private List <int> waitingDelete = new List <int> ();

protected void Update () {
foreach (SSAction ac in waitingAdd)
actions [ac.GetInstanceID ()] = ac;
waitingAdd.Clear ();

foreach (KeyValuePair <int,SSAction> kv in actions) {
SSAction ac = kv.Value;
if (ac.destroy) {
waitingDelete.Add (ac.GetInstanceID ());
} else if (ac.enable) {
ac.Update ();
}
}

foreach (int key in waitingDelete) {
SSAction ac = actions [key];
actions.Remove (key);
DestroyObject (ac);
}
waitingDelete.Clear ();
}

public void RunAction (GameObject gameObject, SSAction action, ISSActionCallback manager) {
action.gameobject = gameObject;
action.transform = gameObject.transform;
action.callback = manager;
waitingAdd.Add (action);
action.Start ();
}

protected void Start () {
}

public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Completed,
int intParam = 0, string strParam = null, Object objectParam = null)
{
}
}

}
  • ControllerBase中包含不同对象的控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mydirector;

namespace Mycontroller{

//Boat
public class BoatController {
private GameObject boat;
private Vector3 From = new Vector3 (4,0,0);
private Vector3 To = new Vector3 (8, 0, 0);
private Vector3[] from_positions;
private Vector3[] to_positions;
int is_from;
CharacterController[] characters = new CharacterController[2];
private Move move;
public float speed = 30;

public BoatController() {
is_from = 1;

from_positions = new Vector3[] { new Vector3 (4.5F,0.5F,0), new Vector3 (3.5F,0.5F,0) };
to_positions = new Vector3[] { new Vector3 (8.5F, 0.5F, 0), new Vector3 (7.5F, 0.5F, 0) };

boat = Object.Instantiate (Resources.Load ("Prefab/boat", typeof(GameObject)), From, Quaternion.identity, null) as GameObject;
boat.name = "boat";

move = boat.AddComponent (typeof(Move)) as Move;
boat.AddComponent (typeof(UserClick));
}
public bool is_empty(){
for (int i = 0; i < characters.Length; i++) {
if (characters [i] != null) {
return false;
}
}
return true;
}

public Vector3 Move_to() {
if (is_from == -1) {
is_from = 1;
return From;
} else {
is_from = -1;
return To;
}
}

public int getEmptyIndex() {
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null) {
return i;
}
}
return -1;
}

public Vector3 getEmptyPosition() {
Vector3 pos;
int emptyIndex = -1;
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null) {
emptyIndex = i;
}
}
if (is_from == -1) {
pos = to_positions[emptyIndex];
} else {
pos = from_positions[emptyIndex];
}
return pos;
}

public void GetOnBoat(CharacterController characterCtrl) {
int index = -1;
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null) {
index = i;

}
}
characters [index] = characterCtrl;
}

public CharacterController GetOffBoat(string characters_name) {
for (int i = 0; i < characters.Length; i++) {
if (characters [i] != null && characters [i].getName () == characters_name) {
CharacterController cc = characters [i];
characters [i] = null;
return cc;
}
}
return null;
}

public GameObject getGameobj() {
return boat;
}

public int get_is_from() {
return is_from;
}

public int[] getCharacterNum() {
int[] count = {0, 0};
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null)
continue;
if (!characters [i].Is_Devil ()) {
count[0]++;
} else {
count[1]++;
}
}
return count;
}

public void reset() {
if (is_from == -1) {
Move_to ();
}
boat.transform.position = From;
characters = new CharacterController[2];
}
}



//coat
public class CoastController{
private GameObject coast;
private Vector3 From = new Vector3 (0, 0, 0);
private Vector3 To = new Vector3(12,0,0);
private Vector3[] positions;
private int is_from;
private CharacterController[] characters;
public CoastController(string Where){
positions = new Vector3[] {new Vector3 (2.5F,1.25F,0),new Vector3 (1.5F,1.25F,0), new Vector3 (0.5F,1.25F,0),new Vector3 (-0.5F,1.25F,0),new Vector3 (-1.5F,1.25F,0),new Vector3 (-2.5F,1.25F,0)
};
characters = new CharacterController[6];
if (Where == "from") {
coast = Object.Instantiate (Resources.Load ("Prefab/stone", typeof(GameObject)), From, Quaternion.identity, null) as GameObject;
coast.name = "from";
is_from = 1;
} else if(Where == "to"){
coast = Object.Instantiate (Resources.Load ("Prefab/stone", typeof(GameObject)), To, Quaternion.identity, null) as GameObject;
coast.name = "to";
is_from = -1;
}
}
public Vector3 getEmptyPos(){
int index = -1;
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null) {
index = i;
break;
}
}
Vector3 pos = positions [index];

if(is_from==-1)
pos.x +=12;

return pos;
}
public void getOnCoast(CharacterController character_ctrl){
int index = -1;
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null) {
index = i;
break;
}
}
characters [index] = character_ctrl;
}

public CharacterController getOffCoast(string character_name){
for (int i = 0; i < characters.Length; i++) {
if (characters [i] != null && characters [i].getName () == character_name) {
CharacterController cc = characters [i];
characters [i] = null;
return cc;
}
}
return null;
}
public int get_is_from(){
return is_from;
}
public int[] get_character_num(){
int[] num = { 0, 0 };
for (int i = 0; i < characters.Length; i++) {
if (characters [i] == null)
continue;
if (!characters[i].Is_Devil ()) {
num [0]++;
} else {
num [1]++;
}
}
return num;
}
public void reset(){
characters = new CharacterController[6];
}
}


//character
public class CharacterController{
private GameObject character;
private Move move;
private UserClick click;
private bool is_devil;
private bool is_on_boat = false;
private CoastController coast;
public float move_speed = 30;
// Use this for initialization
public CharacterController(string name){
if(name == "priest"){
character = Object.Instantiate(Resources.Load("Prefab/priest",typeof(GameObject)),Vector3.zero,Quaternion.identity,null) as GameObject;
is_devil = false;
}else if(name == "devil"){
character = Object.Instantiate(Resources.Load("Prefab/devil",typeof(GameObject)),Vector3.zero,Quaternion.identity,null) as GameObject;
is_devil = true;
}
move = character.AddComponent(typeof(Move))as Move;
click = character.AddComponent(typeof(UserClick))as UserClick;
click.setController(this);
}
public void setName(string _name){
character.name = _name;
}
public string getName(){
return character.name;
}
public bool Is_Devil(){
return is_devil;
}
public bool Is_On_Boat(){
return is_on_boat;
}
public void setPos(Vector3 pos){
character.transform.position = pos;
}
public Vector3 getPos(){
return character.transform.position;
}

public GameObject getGameobj(){
return character;
}


public CoastController getCoastController() {
return coast;
}
public void getOnBoat(BoatController boatCtrl) {
coast = null;
character.transform.parent = boatCtrl.getGameobj().transform;
is_on_boat = true;
}

public void getOnCoast(CoastController coastCtrl) {
coast = coastCtrl;
character.transform.parent = null;
is_on_boat = false;
}
public void reset() {
coast = (Director.getInstace ().current as FirstController).fromCoast;
getOnCoast (coast);
setPos (coast.getEmptyPos());
coast.getOnCoast (this);
}

}
}
  • Judge中是游戏结束的判断。FirstController中Update一直对其调用,以实现“当游戏达到结束条件时,通知场景控制器游戏结束”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mycontroller;

public class Judge : MonoBehaviour {
private CoastController From;
private CoastController To;
private BoatController Boat;
public Judge(CoastController from_coast,CoastController to_coast,BoatController boat)
{
this.From = from_coast;
this.Boat = boat;
this.To = to_coast;
}
public int check()
{
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;

int[] fromCount = From.get_character_num ();
from_priest += fromCount[0];
from_devil += fromCount[1];

int[] toCount = To.get_character_num ();
to_priest += toCount[0];
to_devil += toCount[1];

if (to_priest + to_devil == 6)
return 2;

int[] boatCount = Boat.getCharacterNum ();
if (Boat.get_is_from () == -1) {
to_priest += boatCount[0];
to_devil += boatCount[1];
} else {
from_priest += boatCount[0];
from_devil += boatCount[1];
}
if (from_priest < from_devil && from_priest > 0) {
return 1;
}
if (to_priest < to_devil && to_priest > 0) {
return 1;
}
return 0;
}
}

材料与渲染联系

  • 从 Unity 5 开始,使用新的 Standard Shader 作为自然场景的渲染。

    以Transparency为例

    首先,展示正常的模型

    在Inspector中选择static; Rendering Model 选择Transparent

    在Albedo中调整A通道的值实现透明效果

CATALOG
  1. 1. 游戏对象与图形基础
    1. 1.1. 基本操作演练
    2. 1.2. 编程实践
    3. 1.3. 材料与渲染联系