最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

從零開始用Unity做一個(gè)海戰(zhàn)游戲(上)

2018-09-10 18:04 作者:皮皮關(guān)做游戲  | 我要投稿

作者:沈琰


前言

大家好。思索良久我決定鼓起勇氣開一個(gè)稍微大點(diǎn)的新坑。

初衷是因?yàn)楸救吮容^喜歡那種從無到有創(chuàng)造的樂趣,想做一個(gè)稍微完整點(diǎn)的小項(xiàng)目自娛自樂,也算是給自己一個(gè)小挑戰(zhàn)。所以這篇文章亦可看做是一個(gè)簡易的開發(fā)日志。

同時(shí)為了給喜歡游戲開發(fā),有志于投身于此的萌新們一些幫助,盡量不使用復(fù)雜的組件或插件,用代碼來實(shí)現(xiàn)需求。開發(fā)中遇到一些坑我也會盡力詳細(xì)的說明。目的是讓只要有一點(diǎn)C#編程基礎(chǔ)的萌新也能跟著完整的做出來。

不過就算是天馬行空的想法也得有個(gè)現(xiàn)實(shí)的參照,創(chuàng)世也得講個(gè)基本法吧?所以我也有個(gè)參考,原型來自于以前很喜歡玩的一款網(wǎng)頁小游戲《宇宙海賊王》:

游戲大體的玩法就是去扮演一個(gè)宇宙海賊,通過掠奪星球獲得更強(qiáng)的裝備直到挑戰(zhàn)海賊王。

游戲中的裝備系統(tǒng)非常豐富,多種流派之間的變化也讓游戲的可玩性極佳。

游戲地址:http://www.u77.com/game/1790

最終目標(biāo)就是在Unity上復(fù)刻一個(gè)類似的游戲出來,只不過題材從宇宙戰(zhàn)改成了海戰(zhàn),也許還會加入一些自己覺得好玩的點(diǎn)子進(jìn)去。



小船造起來

雖然想法很豐滿,但現(xiàn)實(shí)還是得從零開始一點(diǎn)一點(diǎn)的搭出來,就先從一艘簡單的小船開始。

為了對得起這個(gè)標(biāo)題所以咱干脆連素材也不要了,自己動手做。

只不過這可有些難為沒有美術(shù)細(xì)胞的我了,最初我想直接用Unity自帶的模型拼一個(gè)出來,結(jié)果是慘不忍睹,差點(diǎn)我就想棄坑了。

最后仔細(xì)想了下,如果建模這一步要自己來還得去求助一些工具。

但是面對繁雜的建模軟件頓時(shí)又生出一些無力感,這些軟件都需要花大量的時(shí)間和精力去學(xué)習(xí),對于只想簡簡單單做個(gè)小游戲的我而言頗有些本末倒置的感覺。

那么有沒有一款軟件既能做出還看得過去的模型,又簡單易用呢?有!

向大家安利一款功能強(qiáng)大且免費(fèi)開源的體素制作工具M(jìn)agicaVoxel,這里不詳細(xì)介紹軟件的使用方式了,因?yàn)槭钦娴暮唵我讓W(xué),就算沒有任何美術(shù)基礎(chǔ)也能輕松上手。

下載地址:ephtracy.github.io/

總之我花了幾個(gè)小時(shí)鼓搗了一通,總算拼出一艘船來。

出來吧,我的海軍夢幻號!

好吧,只能說勉強(qiáng)能看,可能有些美術(shù)出身的同學(xué)已經(jīng)忍不住要吐槽了,但不管怎么說總算比上面那個(gè)來的強(qiáng)。

在這里有個(gè)要注意的地方,在MagicaVoxel中的坐標(biāo)系的軸與Unity有些不一樣,對應(yīng)起來是Y->Z ,Z->Y,X軸則是一樣的。然后將文件導(dǎo)出為obj格式就可以拖到Unity里使用了。


二營長的意大利炮

可以看到特意在船上留了兩個(gè)炮座,所以下一步就是擼出一門炮來。這比起捏一艘船來可就簡單多了,只用注意一點(diǎn),把炮身和炮臺獨(dú)立分開來,至于為什么,容我先賣個(gè)關(guān)子。

然后回到Unity這里導(dǎo)入模型,拼裝在一起,如果你是真的分開來做炮塔和炮身,那在Unity里你要把兩者拼得嚴(yán)絲合縫會極其蛋疼。

這里說一個(gè)小技巧,先就把兩者一起畫出來保存一份,復(fù)制兩份在MagicaVoxel里分別扣去彼此。

然后在Unity中把炮身設(shè)為炮塔的子物體,把炮身的坐標(biāo)置零就行了。

現(xiàn)在先不慌把炮擺到船上去,我們先把開炮的效果做出來。

首先炮得有開火的間隔,這對有一定基礎(chǔ)的同學(xué)來說并不難。聲明一個(gè)float變量作為間隔時(shí)間,在Update里減去每幀時(shí)間,一旦小于零就是可以發(fā)射了。這里換個(gè)看起來更簡單的方法,

在炮身上新建一個(gè)腳本:

public class weapons : MonoBehaviour

{

??? //開火間隔時(shí)間

??? public float FireFrequency = 0.4f;

??? //上一次開火時(shí)間

?? ?private float PrevFireTime = float.MinValue;

?

??? //如果上次開火的時(shí)間加上開火間隔時(shí)間小于當(dāng)前游戲進(jìn)行的時(shí)間,則炮的冷卻好了

??? private bool CanShoot

??? {

??????? get

??????? {

??????????? return PrevFireTime + FireFrequency < Time.time ? true : false;

??????? }

??? }

??? void Update()

? ??{

??????? KeyUpdate();

??? }

??? void Fire()

??? {

??????? Debug.Log("開火");

??? }

??? void KeyUpdate()

??? {

??????? if (Input.GetKey(KeyCode.Mouse0))

??????? {

??????????? if (CanShoot)

??????????? {

??????????????? Fire();

??????????????? PrevFireTime = Time.time;

??????????? }

??????? }

??? }

}

?


是不是有一種簡明扼要的感覺?如果能開炮那就開炮吧。簡單的使用了C#的屬性讓代碼的可讀性提高了不少。

然后現(xiàn)在回到之前賣的關(guān)子上來,炮身和炮管分離是為了做一個(gè)簡單的開炮動畫。

可能提到動畫大多數(shù)人想到的都是動作酷炫人形動畫,但明顯不能用在這里。

Unity里提供了一個(gè)非常實(shí)用的功能:AnimationCurve,作用是編輯一條曲線用在任何你能使用的地方。我們需要的是用這條曲線模擬大炮開炮時(shí)退膛的動畫。

先在腳本中新建一條動畫曲線,然后就可以在腳本的面板上編輯它了。

public AnimationCurve LerpCurve = new AnimationCurve();?


編輯界面有幾條預(yù)設(shè)的曲線,我們選取其中一條先緩后急的稍作修改。

這條曲線代表的是值隨著時(shí)間變化的關(guān)系,我們需要開炮的時(shí)候炮身急速后退,然后緩慢回膛的效果,因此拖動右邊的端點(diǎn)到(0.4,0),左邊到(0,-0.4)左右,然后在腳本里獲取炮身的模型,讓其坐標(biāo)的Z值隨著這條曲線變化。

void PositionUpdate()

??? {

??????? float t = Time.time - PrevFireTime;

??????? model.localPosition = Vector3.forward * LerpCurve.Evaluate(t);

??? }

?


如圖,大致就是我們所要的效果:

再添加一些開炮的粒子效果,讓表現(xiàn)力提高一點(diǎn)

可能會在后面的文章中介紹如何做一個(gè)簡易粒子特效,這里暫且跳過


炮塔旋轉(zhuǎn)控制

現(xiàn)在可以把炮搬到船上去啦。

首先在船的父節(jié)點(diǎn)上新建兩個(gè)子節(jié)點(diǎn)作為炮的錨點(diǎn),調(diào)整子節(jié)點(diǎn)的坐標(biāo)分別與船上炮座的底部中心點(diǎn)吻合,然后把炮塔設(shè)為錨點(diǎn)的子物體,坐標(biāo)歸零。

大家都見過真實(shí)的戰(zhàn)艦炮塔是怎么運(yùn)動的:

1.炮塔是有轉(zhuǎn)速的。

2.炮管是可以沿著X軸方向抬升下降的。

3.炮塔的轉(zhuǎn)動是有限制角度的。

我們先簡化一下難度,暫時(shí)不考慮炮管在X軸轉(zhuǎn)動的問題。所以現(xiàn)在要實(shí)現(xiàn)的功能就是在限制角度內(nèi)恒定速度旋轉(zhuǎn)的炮塔。

勻速旋轉(zhuǎn)這個(gè)問題不難,限制每幀的角速度就行,限制旋轉(zhuǎn)角度這個(gè)問題就稍微麻煩一些了。

我們先假設(shè)把炮塔限制在正前方60度內(nèi)轉(zhuǎn)動。

我一開始的想法是計(jì)算炮塔當(dāng)前朝向角度,如果超過限制角的一半則停止轉(zhuǎn)動。

想法是沒錯(cuò)的,但是有一個(gè)問題。在Unity里判斷當(dāng)前的歐拉角會有角度正負(fù)的區(qū)別,比如一個(gè)物體沿Y軸選轉(zhuǎn)1度和旋轉(zhuǎn)-359度,最后的朝向是一樣的,但是旋轉(zhuǎn)方向不同。這會讓代碼里角度判定條件出錯(cuò)。

所以換了個(gè)思路來解決這個(gè)問題。

public class TurretsContorl : MonoBehaviour

{

??? [Range(30, 330)]

??? public float LimitAngle = 60f;

?

??? public int RotateSpeed = 180;

?

??? public Camera cam;

?

??? void Update()

??? {

??????? RotateUpdate();

??? }

?

??? void RotateUpdate()

??? {

??????? Vector3 limit_dir = GetMouseDir_limit(LimitAngle);

?

??????? Quaternion rotate = Quaternion.RotateTowards(transform.rotation,

???????????????????????????????????????????????????? Quaternion.LookRotation(limit_dir, transform.up),

?????????????????????????????????? ?????????????????RotateSpeed * Time.deltaTime);

??????? transform.rotation = rotate;

??? }

?

??? //獲取限制角度內(nèi)的方向

??? Vector3 GetMouseDir_limit(float limit_angle)

??? {

??????? Ray ray = cam.ScreenPointToRay(Input.mousePosition);

??????? RaycastHit hit;

?

????? ??if (Physics.Raycast(ray, out hit))

??????? {

??????????? Vector3 hitpos = new Vector3(hit.point.x, transform.position.y, hit.point.z);

??????????? Vector3 dir = (hitpos - transform.position).normalized;

??????????? Debug.DrawLine(hitpos, transform.position, Color.blue, 0.5f);

?

??????????? //如果鼠標(biāo)指向的方向超過了限制角的一半,返回限制角與父節(jié)點(diǎn)Z方向的乘積

??????????? if (Vector3.Angle(transform.parent.forward, dir) > limit_angle / 2)

??????????? {

??????????????? bool in_my_right = Vector3.Cross(transform.parent.forward, dir).y > 0;

??????????????? Quaternion q = Quaternion.AngleAxis((in_my_right ? limit_angle : -limit_angle) / 2, transform.parent.up);

??????????????? Debug.DrawRay(transform.position, q * transform.parent.forward * 5, Color.red, 0.5f);

??????????????? return q * transform.parent.forward;

??????????? }

??????????? else

??????????? {

??????????????? Debug.DrawRay(transform.position, (hitpos - transform.position).normalized * 5, Color.green, 0.5f);

??????????????? return (hitpos - transform.position).normalized;

??? ????????}

??????? }

??????? else

??????? {

??????????? return transform.parent.forward;

??????? }

??? }

}

?


直接在獲取目標(biāo)向量時(shí)就計(jì)算好,在旋轉(zhuǎn)時(shí)直接按恒定速度轉(zhuǎn)就可以了。

藍(lán)線表示鼠標(biāo)到炮塔的向量,黃線限制角內(nèi)的返回的目標(biāo)向量,紅線為超過限制角的目標(biāo)向量??梢钥吹酱笾路衔覀兊南敕?但是還沒有完全解決,當(dāng)把限制角加到一個(gè)大于180度的角度時(shí)出現(xiàn)了新的問題。

輔助線告訴我們獲取的目標(biāo)向量沒有問題,問題出在了 Quaternion.LookRotation()這個(gè)函數(shù)上。計(jì)算時(shí)返回的是向量的兩個(gè)夾角中較小的那個(gè)角度。但在我們的需求中,這塊不能轉(zhuǎn)向的區(qū)域是“禁區(qū)”,所以得換一個(gè)方法來解決。

還是用圖來幫助我們進(jìn)行思考,假設(shè)現(xiàn)在的限制角是約300度:

由前面的結(jié)果可知當(dāng)限制角在正前方180度(綠色區(qū)域)時(shí)沒有問題,而無論是目標(biāo)向量還是炮塔的當(dāng)前朝向落在藍(lán)色區(qū)域中時(shí),我們就需要特殊處理一下了。

具體思路是當(dāng)目標(biāo)向量不在綠色區(qū)域時(shí),先判斷炮塔的當(dāng)前朝向轉(zhuǎn)向目標(biāo)向量的方向。如果這個(gè)旋轉(zhuǎn)方向朝向紅色區(qū)域,再判斷朝向與限制角邊界的夾角和朝向與目標(biāo)向量夾角之間的大小關(guān)系,可得知旋轉(zhuǎn)的路徑是否會經(jīng)過“禁區(qū)”。一旦條件成立,取反當(dāng)前的轉(zhuǎn)軸與角度。換成代碼表示如下:

void RotateUpdate()

??? {

??????? Vector3 aixs;

??????? float angle;

?

??????? Vector3 limit_dir = GetMouseDir_limit(LimitAngle);

?

??????? //目標(biāo)向量與基準(zhǔn)Z軸正方向的左右關(guān)系

??????? bool tar_is_right = Vector3.Cross(limit_dir, transform.parent.forward).y > 0;

??????? //炮塔當(dāng)前朝向與目標(biāo)向量之間的左右關(guān)系

??????? bool cur_is_right = Vector3.Cross(transform.forward, limit_dir).y > 0;

??????? //當(dāng)前朝向與基準(zhǔn)正方向的左右關(guān)系

??????? bool tra_is_right = Vector3.Cross(transform.forward, transform.parent.forward).y > 0;

?

??????? //如果目標(biāo)向量在正180度之外,作特殊處理

??????? if (Vector3.Dot(limit_dir, transform.parent.forward) <= 0)

??????? {

??????????? //當(dāng)前朝向的邊界向量

??????????? Vector3 edge = Quaternion.AngleAxis(LimitAngle / 2, tra_is_right ? transform.parent.up : -transform.parent.up) * transform.parent.forward;

??????????? //朝向與邊界的夾角

??????????? float edge_angle = Vector3.Angle(transform.forward, edge);

??????????? //轉(zhuǎn)向可能經(jīng)過基準(zhǔn)負(fù)方向且目標(biāo)與邊界的夾角小于朝向與邊界的夾角,則角度和軸作取反處理

??????????? if (((tar_is_right && cur_is_right) || (!tar_is_right && !cur_is_right)) && Vector3.Angle(limit_dir, edge) < edge_angle)

??????????? {

??????????????? angle = 360 - Vector3.Angle(transform.forward, -limit_dir);

??????????????? aixs = Vector3.Cross(transform.forward, -limit_dir);

??????????????

??????????? }

??????????? else

??????????? {

??????????????? angle = Vector3.Angle(transform.forward, limit_dir);?????????

??????????????? aixs = Vector3.Cross(transform.forward, limit_dir);??????????

??????????? }????

??????? }

??????? else

??????? {

??????????? angle = Vector3.Angle(transform.forward, limit_dir);

??????????? aixs = Vector3.Cross(transform.forward, limit_dir);

?????

??????? }

??????? transform.Rotate(aixs, Mathf.Min(RotateSpeed * Time.deltaTime, angle));

??? }

?

最終效果如我們所愿:

結(jié)束

在這期文章中我們把船的基本結(jié)構(gòu)建立起來了,可以說開了個(gè)還算不錯(cuò)的頭。

可能文章在解釋遇到問題的時(shí)候稍顯拖沓,但我始終覺得在開發(fā)時(shí)遇到和解決問題的過程才是最有價(jià)值的,因此使用這種有點(diǎn)記流水賬的方式說明問題。同時(shí)限于自身的水平,難免會有疏漏和不足,也歡迎大家指正。

感謝觀看到此,下期再見。(如果沒有太監(jiān)掉的話...)

本期工程地址:github.com/tank1018702/

游戲開發(fā)攪基QQ群:869551769?

從零開始用Unity做一個(gè)海戰(zhàn)游戲(上)的評論 (共 條)

分享到微博請遵守國家法律
肇东市| 呼伦贝尔市| 万源市| 凤阳县| 林口县| 赤城县| 宿松县| 闵行区| 获嘉县| 抚顺市| 顺义区| 聂拉木县| 江川县| 大关县| 滁州市| 龙南县| 马关县| 应用必备| 哈巴河县| 桐梓县| 读书| 大石桥市| 岳西县| 滦平县| 镇巴县| 筠连县| 九江市| 通道| 柳林县| 泉州市| 广元市| 黄山市| 甘洛县| 东港市| 新疆| 桑植县| 屏东市| 哈密市| 宣汉县| 共和县| 秀山|