這是你永遠抵達不到的真實——用Unity做一個JOJOJump

作者:Yumir
嗨大家好我是yumir。
經(jīng)過上一次2D像素游戲“Seed”的洗禮我決定休息一下?lián)Q個題材,做一個3D游戲就不會出現(xiàn)自己畫素材畫哭的情況啦!于是決定實現(xiàn)《jetpack jump》中的跳躍,算是物理系統(tǒng)小練習,大家可以自己搜索安裝包試玩,不過不要過渡沉迷,會影響你打代碼的~( ̄▽ ̄)~*

但是直接使用unity的免費素材來做游戲,又比較沒意思,畢竟unity娘谷歌娘什么的,除了又好看又方便之外還有什么優(yōu)點嗎,沒有。我們怎么能滿足于這種唾手可得的資源呢,我們要去別的地方找更有意思的東西(其實就是想用自己喜歡的動畫片的模型)。

這次的模型是從:https://www.spriters-resource.com/上下載的,主要是因為我在別的網(wǎng)站都沒有找到JOJO的模型,同學們?nèi)绻袆e的資源來源請務(wù)必在評論區(qū)分享。
其實找模型有一個方法是通過找MMD作品看有沒有好看的想要的模型,以前聽大佬說MMD都是要求要把使用的資源鏈接列在簡介里面的(除非資源是自己制作并且不愿意共享的),再看看MMD轉(zhuǎn)Unity的插件教程,就可以讓可愛的MMD角色跑起來了。
但是我找JOJO的MMD完全沒有看到鏈接,也許都是自己建模的?或者是我太菜了不會找,我本來心儀的是Q版豆豆眼的那個JOJO,就是這個:

但是想想這么可愛的豆豆眼在地上瘋狂摩擦的樣子實在是不忍心,所以我決定還是讓Dio在地上摩擦吧,于是今天做的是DiavoloJump!JOJOJump?永遠抵達不到。
其實我也想過吉良吉影在不能回頭的巷子里Jump,但是吧...

這就不是我短時間內(nèi)能解決的問題了。
然后我掏出了老板的模型。

突然卡茲。
我們喬斯達家族世世代代都是紳士,絕不會這樣就放棄!

然后我下載了blender,同時找到一個可以從unity導出在unity中修改過的模型的插件,但是導出來之后模型的uv就莫得了,對比發(fā)現(xiàn)模型導入到blender中修改再導出,該有的材質(zhì)都有,所以最后是在blender里面幫BOSS把一頭秀發(fā)修剪整齊了。

之所以使用的是blender主要是因為。
(#`O′)他免費??!
感覺只要是建模軟件應(yīng)該都能做這一步,但是你要我說是不是肯定都一樣我當然是不知道的,畢竟只是一個普普通通的上班族而已。
因為快捷鍵不一樣磨合時間也比較長,所以有別的建模軟件經(jīng)驗的小伙伴不必強行下載。這個模型腳底下還有上下牙和舌頭,不知道到底是什么游戲,才需要把牙齒做出來,難道是要玩櫻桃嗎。

所以為了保護同學們的鍵盤我把我改好的模型上傳到了網(wǎng)盤:
https://pan.baidu.com/s/1-JaKrhSOu_Fr48xBRbugZw#list/path=%2F
將模型上傳到Mixamo綁骨再下載下來,我們的主要素材就完成啦!什么你還不知道Mixamo是什么?這么好用的網(wǎng)站快保存下來??!
https://www.mixamo.com/#/?page=1&type=Character
人物的動畫是在unity官方下載的,里面的動畫正好夠用,再在官方商店隨便找了兩個差不多的場景包再差不多的搭一個場景做個差不多的天空盒我們的前期準備就完成啦。

如果同學們想讓游戲時間變得更長的話需要更多的場景素材,itch上不止有免費的游戲還有很多免費的素材,需要的同學可以自己去看看,非常不錯。
在畫天空盒的時候發(fā)現(xiàn)天空盒的顏色也是會反射到場景中的(這不是當然的嗎!)于是干脆把天空調(diào)成了奇妙的綠色,天空盒的制作方法如下:
1、新建一個材質(zhì)球,設(shè)置Shader為Skybox,這里我設(shè)置為Skybox/Panoramic,我比較喜歡這個效果,也可以做六面體。

2、打開ps做一個漸變顏色圖片給該天空盒賦值。
3、點擊window→rendering→lightingsetting,將skybox material設(shè)置為剛才新建的天空盒。
場景的搭建其實是很費時費力的,所以大概搭了個夠用的,就是下面這個丑丑的場景↓

沒關(guān)系,畢竟我做的是DiavoloJump嗒!再下載一個緋紅之王套一個打籃球的動作,Boss的“噴射背包”也有了,那么可以開始我們的大工程啦~
------------------不介紹實現(xiàn)功能就開始薅代碼的經(jīng)驗分享都是耍流氓------------------
這次做的游戲的玩法就是點擊屏幕跳躍,但是如果一個游戲隨便跳那就沒有意義了,所以是有條件限制的跳,因為是在電腦實現(xiàn)功能所以我做的是按下鼠標左鍵跳躍,內(nèi)容如下:
1、游戲中BOSS一直向前奔跑,按下鼠標左鍵跳躍。
2、如果第一跳的位置正好在起跑線,可以雙倍跳躍。
3、如果在落地的瞬間按下鼠標左鍵,可以雙倍跳躍。
4、第三跳是噴射跳躍,在空中可以有若干次加速機會。
5、如果超過起跑線一段時間沒有跳躍,游戲失敗。
6、如果落地一段時間沒有跳躍,游戲失敗。
7、Boss永遠抵達不到終點。
畫了一張圖來表示整個游戲流程,不過這次我只做了結(jié)算之前的跳躍,會在文末給感興趣的同學補充一些結(jié)算狀態(tài)的思路。

為了可以一直看到Boss的背影我們需要做一個攝像機跟隨,在制作這部分功能的時候考慮到游戲中會有不同的攝像機角度,所以新建了一個空物體先把當前的跟隨角度存儲起來,后期需要新的角度的時候只需要增加新的空物體和邏輯就可以了。
? ??public?float?smooth;????????????????// 平滑度????public?Transform?cameraFPTran;??//人物活動時攝像機跟隨位置????public?Transform?cameraStartTran;??//攝像機初始位置
????private?GameObject?player;????????// 目標????private?Vector3?targetPosition;????????// 攝像機移動目標????private?bool?isStart?=?true;
????void?Start()
????{
????????player?=?GameObject.FindWithTag("Player");
????????cameraFPOffset?=?cameraFPTran.position?-?player.transform.position;
????????//攝像機放在初始位置,置標志位為f????????jetSlider.SetActive(false);
????}
????void?LateUpdate()
????{
????????if?(isStart)
????????{
????????????CameraFollow(cameraFPOffset);
????????}
????}
????void?CameraFollow(Vector3?follow)
????{
????????targetPosition?=?player.transform.position?+?follow;
????????transform.position?=?Vector3.Lerp(transform.position,?targetPosition,?Time.deltaTime?*?smooth);
????????transform.LookAt(player.transform);
????}
?
在實現(xiàn)跳躍之前我們需要讓模型在游戲場景中往前跑,首先新建Boss的游戲物體,然后進行以下操作:
1、在Boss身上添加碰撞體、剛體以及動畫三個組件,新建一個角色控制腳本。
2、把動畫的“Apply Root Motion”取消選中。
3、將剛體的旋轉(zhuǎn)角度鎖定。

因為我把跑道搭在Z軸上,所以Boss只需要一直在Z軸上面跑就行了,同時我希望Boss在起跑的時候有加速度的過程,于是我做了一個動畫混合樹,將走路和跑步的動畫混合在一起。

嘗試過幾種移動方法之后發(fā)現(xiàn)使用“AddForce”進行移動的效果是最好的。在移動的過程中我們需要控制Boss的移動速度(人類的力量是有極限的)以及懸空的時候不會繼續(xù)往前加速,所以我們需要對Boss的速度和是否跳躍進行監(jiān)控,我是這樣實現(xiàn)的:
? ? private void FixedUpdate()
????{
????????if (!isJump && playerRigdbody.velocity.z < speed)
????????{
????????????playerRigdbody.AddForce(Vector3.forward * speed);
????????}
????????playerAnimator.SetFloat("Blend", playerRigdbody.velocity.z / speed);
????}
?
接下來就要實現(xiàn)跳躍功能了,跳躍有:起跳、空中、下落這三個狀態(tài),加上本身有一個奔跑狀態(tài),而起跳到空中這段動作是不需要條件轉(zhuǎn)換的,我的動畫狀態(tài)機是這樣搭建的。

其中指向“Run”的轉(zhuǎn)換條件是“run=true”,指向“JumpStart”的轉(zhuǎn)換條件是“jump=true”指向“JumpEnd”的轉(zhuǎn)換條件是“down=true”,“JumpStart”的動畫播放結(jié)束自動跳轉(zhuǎn)到“JumpMidAir”。
因為有概率出現(xiàn)明明“run”為True但是卻沒有從“JumpEnd”跳轉(zhuǎn)到“Run”的情況,我又在“JumpEnd”和“JumpStart”之間增加了一條線。在腳本中定義一個枚舉類型區(qū)分三個狀態(tài)就能輕松控制動畫的切換了,因為代碼太簡單就不浪費篇幅了。
畫面表現(xiàn)搞定之后就是腳本控制游戲物體移動了,同樣的,用AddForce給一個向上的力就可以讓Boss上天,但是Boss可以一直上天嗎,Boss不行。
于是乎我需要讓他只能在腳踩地面的時候上天,這個判斷我是通過射線實現(xiàn)的:

解決了Boss上天的問題我開始思考每一次跳躍之間的差別,整個游戲只有按下鼠標左鍵一個操作,但是每個階段的操作都是不一樣的,可以看出來最大的問題是最后一條需要出現(xiàn)反饋噴射進度的UI,而且是在起跳后一段時間才出現(xiàn)噴射進度條。
我的解決方法利用委托,定義一個沒有返回類型沒有參數(shù)的委托變量,在游戲中每幀調(diào)用,在不同狀態(tài)中更改改委托的內(nèi)容,這樣一來我就可以在游戲的不同時期做不同的事了。
而后我又定義了一個int類型的數(shù)用來判斷Boss處于第幾個階段(狀態(tài)),通過Switch語句控制不同階段的操作,這個方法其實并不好,但是在學習的過程中我們需要靈活運用各種技巧,這個游戲并不會進行后期修改所以可以這樣控制。
以第一階段為例,第一階段所做的事情是:起跑&第一次跳躍
我設(shè)置的起跑線是Z=15,當玩家超過起跑線一段距離(我設(shè)置的值是Z大于等于18)時玩家死亡,如果在起跑線浮動范圍里面起跳則將“jumpSpeed”翻倍否則不變,起跳時設(shè)置動畫參數(shù)并將表示階段(第幾跳)的變量自增,這樣在下一幀執(zhí)行的便是下一個狀態(tài)的方法。
?
if (isGround)
{
????isJump = false;
????if (Input.GetMouseButtonDown(0) && playerTransform.position.z < 18.0f)
????{
????????if (playerTransform.position.z > 14.5f && playerTransform.position.z < 15.5f)//最佳
????????{
????????????jumpSpeed *= 2;
?????????}
?????????SetPlayerAnima(PlayerStateType.Jump);
?????????playerRigdbody.AddForce((Vector3.up) * jumpSpeed, ForceMode.Impulse);
?????????jumpNum++;
?????????jumpTime = 0;
?????????isJump = true;
????}
????if (playerTransform.position.z >= 18.0f)
????{
?????????playerMove = PlayerDie;
????}
}
?
第二跳第三跳大同小異,與第一跳不同的地方是第一跳用距離判斷是否加倍,后面的跳躍則是用落地時間,所以當Boss落地之后就要開始計時,在起跳的時候?qū)⒂嫊r器清零。
if (isGround)
{
???jumpTime += Time.deltaTime;
???if (jumpTime > timing){ playerMove = PlayerDie; }
???else if (Input.GetMouseButtonDown(0) && jumpTime <= timing)
???{
????????if (jumpTime <= bestTiming){ jumpSpeed *= 2; }
????????Jump();
???}
}
?
需要注意的是如何控制動畫的播放,當Boss快要落地的時候要切換為“Down”動畫,也就是我需要知道他是否下墜,下墜時離地面多高,我通過記錄Boss的Y軸數(shù)值并比較上一幀和當前幀的Y值大小來判斷:
if (lastPosY > playerTransform.position.y && playerTransform.position.y < 2)
{
????SetPlayerAnima(PlayerStateType.Down);
}
lastPosY = playerTransform.position.y;
?

Boss的第三次起跳需要延遲調(diào)用,我是用協(xié)程來實現(xiàn)的,在協(xié)程中對委托進行新的賦值操作,并且解開對Boss剛體的旋轉(zhuǎn)鎖定,以及一系列該有的操作。
IEnumerator Jet()
{
????yield return new WaitForSeconds(1);
????playerRigdbody.freezeRotation = false;
????GameManager.instance.jetSlider.SetActive(true);
????jetValue = jetTimes;
????playerMove = PlayerMove02;
}
?
噴射進度條是我自己用PS做的,用了一個和游戲場景比較搭的配色,在場景中新建兩個Image,將方格圖作為漸變色圖的子物體,因為Unity中的UI物體越在列表的下方則越靠近屏幕,自然可以將方格圖顯示在漸變色的前方。
將漸變色圖的參數(shù)設(shè)置如下,并持有這個游戲物體,我是在GameManager里持有的。

在Boss的控制腳本中“PlayerMove02”中的設(shè)置更新該image的Amount并在每次推進中給Boss一個往前的力。當Boss落地的時候?qū)赢嫊和2シ牛[藏緋紅之王,游戲到這里就結(jié)束了。
if (isGround)
{
????playerAnimator.speed = 0;
????kingCrimson.SetActive(false);
}
else if (Input.GetMouseButton(0) && jetValue > 0)
{
???--jetValue;
???GameManager.instance.SetJetSlider(jetValue / jetTimes);
???playerRigdbody.AddForce(Vector3.forward * jetSpeed, ForceMode.Impulse);
}
最后可以通過剛體的IsSleeping判斷Boss是不是已經(jīng)停下最后的滑行,在結(jié)束的時候彈出游戲結(jié)束的各種效果,游戲中的里程可以通過3D Object→3D Text來實現(xiàn)顯示,有更多問題歡迎在評論區(qū)留言,或者到群里勾搭~
Github:https://github.com/peiyl/JOJOJump

有知友看了上一篇文章成功實現(xiàn)了自己的小項目,在原游戲的基礎(chǔ)上做了擴展,雖然我也不是真大佬但是還是感覺很Diohh。



歡迎加入游戲開發(fā)群歡樂攪基:869551769
有意向參與線下游戲開發(fā)學習的讀者可戳這里進一步了解:http://levelpp.com/