激情六月丁香婷婷|亚洲色图AV二区|丝袜AV日韩AV|久草视频在线分类|伊人九九精品视频|国产精品一级电影|久草视频在线99|在线看的av网址|伊人99精品无码|午夜无码视频在线

高校合作1:010-59833514 ?咨詢電話:400-810-1418 服務(wù)與監(jiān)督電話:400-810-1418轉(zhuǎn)接2

threejs開(kāi)發(fā)游戲(游戲編程)

發(fā)布時(shí)間:2023-11-27 18:11:30 瀏覽量:176次

?游戲編程

threejs開(kāi)發(fā)游戲(游戲編程)

此案例實(shí)現(xiàn)了人物跟隨著移動(dòng)操作桿進(jìn)行移動(dòng)并執(zhí)行跑步動(dòng)作,右邊的攻擊按鈕可以實(shí)現(xiàn)攻擊,短時(shí)間內(nèi)連按可以實(shí)現(xiàn)不同的攻擊動(dòng)作。

在線查看:
https://jxtreehouse.github.io/three.js-lessions/%E6%95%99%E7%A8%8B/examples/12_game_operation.html

源碼倉(cāng)庫(kù):
https://github.com/JXtreehouse/three.js-lessions/blob/gh-pages/%E6%95%99%E7%A8%8B/examples/12_game_operation.html

首先,我們需要把舞臺(tái)搭建出來(lái),先創(chuàng)建scene場(chǎng)景:

我們創(chuàng)建了場(chǎng)景,并設(shè)置了場(chǎng)景一個(gè)灰色的背景色。還設(shè)置了場(chǎng)景的霧化效果,這個(gè)霧的效果主要是針對(duì)于場(chǎng)景的相機(jī)的距離實(shí)現(xiàn)的,三個(gè)值分別是霧的顏色、霧的開(kāi)始距離、完全霧化距離相機(jī)的位置。

我們創(chuàng)建了一個(gè)與地面呈45度角并朝向原點(diǎn)的相機(jī):

我們創(chuàng)建了兩個(gè)燈光:

  • 照射全局的環(huán)境光 THREE.AmbientLight
  • 可以產(chǎn)生陰影的平衡光 THREE.DirectionalLight

我們使用平面幾何體創(chuàng)建了一個(gè)貼有草皮貼圖的材質(zhì)的模型:

到這里,場(chǎng)景、燈光、相機(jī)、舞臺(tái)都已經(jīng)備齊。接下來(lái)我們將請(qǐng)出我們主角naruto登場(chǎng)。

首先我們將模型導(dǎo)入到場(chǎng)景內(nèi),注意,案例中的模型比較大,加載和處理需要一定的時(shí)間,請(qǐng)小伙伴們耐心等待即可(實(shí)際案例里面可以加個(gè)loading動(dòng)畫):

var loader = new THREE.FBXLoader();
        loader.load("http://www.toutiao.com/js/models/fbx/Naruto.fbx", function (mesh) {
            scene.add(mesh);
        });

我們不單單只是將模型添加到場(chǎng)景,還對(duì)模型的陰影和位置做了一下調(diào)整:

調(diào)整模型的位置,站立在草地上面

設(shè)置燈光一直照射模型:


這個(gè)模型里面含有27個(gè)骨骼動(dòng)畫,我們可以通過(guò)設(shè)置不同的動(dòng)畫,來(lái)實(shí)現(xiàn)一整套的動(dòng)作來(lái)實(shí)現(xiàn)相應(yīng)的比如攻擊效果,移動(dòng)效果等。接下來(lái)我們通過(guò)模型的數(shù)據(jù)生成一下所需的動(dòng)畫:

模型加載成功后,我們需要讓模型執(zhí)行一個(gè)普通的站立效果:

我們主要添加了兩種操作:模型位置移動(dòng)操作和攻擊效果。

操作按鈕為了方便,直接使用的dom標(biāo)簽?zāi)M出來(lái)的。 模型位置移動(dòng)操作中,我們需要模型的位置的變動(dòng)和模型的朝向以及修改站立動(dòng)畫和奔跑動(dòng)畫的切換。 攻擊效果則是實(shí)現(xiàn)攻擊并且根據(jù)點(diǎn)擊速度實(shí)現(xiàn)一整套的攻擊動(dòng)作切換。

在實(shí)現(xiàn)位置移動(dòng)效果中,我們?yōu)榘粹o綁定了三個(gè)事件:鼠標(biāo)按下,鼠標(biāo)移動(dòng),鼠標(biāo)抬起。 在鼠標(biāo)按下時(shí),我們獲取到了當(dāng)前操作圓盤的中心點(diǎn)的位置,讓模型進(jìn)入跑步動(dòng)畫,綁定了鼠標(biāo)的移動(dòng)和抬起事件。重要的是更新模型的移動(dòng)方向和移動(dòng)速度。


上面的dop類是封裝的一個(gè)兼容多端的事件庫(kù),github地址:
https://github.com/johnson2heng/dop 在鼠標(biāo)移動(dòng)回調(diào)事件中,我們更新模型的移動(dòng)方向和移動(dòng)速度。

function move(event) {
    getRadian(event);
}

最后在鼠標(biāo)抬起事件中,我們解綁事件,將按鍵復(fù)原,并停止掉模型的移動(dòng)狀態(tài),將模型動(dòng)畫恢復(fù)到站立狀態(tài)。

function up() {
    doc.remove("move", move);
    doc.remove("up", up);

    //按鈕復(fù)原
    bar.style.marginTop = 0;
    barWrap.style.transform = `translate(-50%, -50%) rotate(0deg)`;
    bar.style.transform = `translate(-50%, -50%) rotate(0deg)`;

    //設(shè)置移動(dòng)距離為零
    characterMove(new THREE.Vector2(), 0);

    //鼠標(biāo)抬起切換站立狀態(tài)
    state.skills === 0 && gui["action" + 24]();
}

三個(gè)事件綁定完成后,我們需要將在回調(diào)中獲得的值求出當(dāng)前的偏轉(zhuǎn)方向和移動(dòng)速度: 首先我們獲取一下當(dāng)前鼠標(biāo)的位置:

if (media === "pc") {
    mouse.x = event.clientX;
    mouse.y = event.clientY;
}
else {
    mouse.x = event.touches[0].clientX;
    mouse.y = event.touches[0].clientY;
}

根據(jù)位置求出距離操作圓盤中心的位置,并保證最大值也不會(huì)超出圓盤的半徑:

let distance = center.distanceTo(mouse);
distance >= parseFloat(dop.getFinalStyle(control, "width")) / 2 && (distance = parseFloat(dop.getFinalStyle(control, "width")) / 2);

計(jì)算出來(lái)當(dāng)前位置和中心的夾角,并修改dom的位置:


//計(jì)算兩點(diǎn)之間的夾角
mouse.x = mouse.x - center.x;
mouse.y = mouse.y - center.y;

//修改操作桿的css樣式
bar.style.marginTop = `-${distance}px`;
bar.style.transform = `translate(-50%, -50%) rotate(-${(mouse.angle() / Math.PI * 180 + 90) % 360}deg)`;
barWrap.style.transform = `translate(-50%, -50%) rotate(${(mouse.angle() / Math.PI * 180 + 90) % 360}deg)`;

函數(shù)的最后,則調(diào)用的characterMove方法,將按鈕數(shù)據(jù)轉(zhuǎn)換成為模型實(shí)際需要移動(dòng)的距離。

//修改當(dāng)前的移動(dòng)方向和移動(dòng)速度
characterMove(mouse.normalize(), distance / (parseFloat(dop.getFinalStyle(control, "width")) / 2));

接下來(lái)我們查看一下characterMove方法,在這個(gè)方法中,我們計(jì)算出了模型每一幀需要移動(dòng)的距離。這里有一個(gè)問(wèn)題,我們所謂的操作桿向前讓模型移動(dòng)前方,其實(shí)是相機(jī)朝向的前方。所以我們需要先求出相機(jī)的前方矢量,再通過(guò)相機(jī)的前方矢量為基礎(chǔ),計(jì)算出來(lái)模型實(shí)際方向。 我們首先聲明了兩個(gè)變量,一個(gè)是旋轉(zhuǎn)矩陣,另一個(gè)是移動(dòng)矢量:

let direction = new THREE.Matrix4(); //當(dāng)前移動(dòng)的旋轉(zhuǎn)矩陣
let move = new THREE.Vector3(); //當(dāng)前位置移動(dòng)的距離

在characterMove函數(shù)內(nèi),我們根據(jù)相機(jī)的四元數(shù)獲得了旋轉(zhuǎn)矩陣:

/重置矩陣
direction.identity();

//通過(guò)相機(jī)的四元數(shù)獲取到相機(jī)的旋轉(zhuǎn)矩陣
let quaternion = camera.quaternion;
direction.makeRotationFromQuaternion(quaternion);

然后通過(guò)旋轉(zhuǎn)矩陣和當(dāng)前的操作桿的方向通過(guò)相乘計(jì)算出來(lái)實(shí)際模型移動(dòng)的方向:


//獲取到操作桿的移動(dòng)方向
move.x = vector.x;
move.y = 0;
move.z = vector.y;

//通過(guò)相機(jī)方向和操作桿獲得最終角色的移動(dòng)方向
move.applyMatrix4(direction);
move.normalize();

最后,通過(guò)比例和方向得出當(dāng)前模型每一幀移動(dòng)的距離,因?yàn)槲覀儾恍枰薷哪P蛓軸,所以實(shí)際上也只是修改兩個(gè)軸的位置:

move.x = move.x * ratio * 10;
move.z = move.z * ratio * 10;

我們獲取到了模型的每一幀移動(dòng)的距離,還需要在幀循環(huán)中調(diào)用:

//如果模型添加成功,則每幀都移動(dòng)角色位置
if (naruto) {
    //獲取當(dāng)前位置
    position.x += move.x;
    position.z += move.z;

    //修改模型位置
    naruto.position.x = position.x;
    naruto.position.z = position.z;

    //修改平衡光的位置
    light.position.x = position.x;
    light.position.z = position.z + 100;

    //修改相機(jī)位置
    camera.position.x = position.x;
    camera.position.z = position.z - 800;
}


當(dāng)前的模型,燈光,和相機(jī)都會(huì)跟隨移動(dòng),實(shí)現(xiàn)了,我們上面動(dòng)圖中的模型移動(dòng)的效果。

在實(shí)現(xiàn)攻擊效果時(shí),我沒(méi)有只是簡(jiǎn)單的實(shí)現(xiàn)一個(gè)普通的攻擊,而是直接實(shí)現(xiàn)一套連招。 這一套連招是通過(guò)五個(gè)動(dòng)作組成,在執(zhí)行一個(gè)攻擊動(dòng)畫時(shí)如果再次點(diǎn)擊了攻擊按鈕,執(zhí)行完這個(gè)攻擊動(dòng)畫將不會(huì)切換到站立動(dòng)畫,而是直接切換到連招的下一個(gè)攻擊動(dòng)畫中。 只要連續(xù)點(diǎn)按攻擊按鈕,模型將完成一整套的動(dòng)作。實(shí)現(xiàn)這個(gè)效果,我們只是使用了一個(gè)簡(jiǎn)單的定時(shí)器即可實(shí)現(xiàn),接下來(lái)我們通過(guò)代碼了解一下實(shí)現(xiàn)過(guò)程。

在實(shí)現(xiàn)動(dòng)畫前,先設(shè)置一個(gè)連招的數(shù)組,將需要的動(dòng)作添加到數(shù)組當(dāng)中。我這里添加了五個(gè)手部攻擊的效果:

let attackList = [12, 13, 14, 15, 16]; //連招的循序
let attackCombo = false; //是否連招,接下一個(gè)攻擊

我們還設(shè)置了attackCombo設(shè)置當(dāng)前是否可以連招的變量,這個(gè)變量state.skills值不為0時(shí),將變?yōu)閠rue。定時(shí)器每一次更新的時(shí)候,將判斷attackCombo是否為true,在為true的狀態(tài)下,將執(zhí)行連招的下一個(gè)動(dòng)作。否則,將停止連招。

//attackIndex 等于0,當(dāng)前不處于攻擊狀態(tài)  不等于,當(dāng)前處于攻擊狀態(tài)
if(state.skills === 0){
    state.skills++;
    gui["action" + attackList[state.skills-1]]();
    attackInterval = setInterval(function () {
        if(attackCombo){
            //如果設(shè)置了連招,上一個(gè)攻擊動(dòng)作完成后,進(jìn)行下一個(gè)攻擊動(dòng)作
            state.skills++;
            //如果整套攻擊動(dòng)作已經(jīng)執(zhí)行完成,則清除定時(shí)器
            if(state.skills-1 >= attackList.length){
                closeAttack();
                return;
            }

            //進(jìn)行下一個(gè)動(dòng)作
            gui["action" + attackList[state.skills-1]]();

            attackCombo = false;
        }
        else{
            closeAttack();
        }
    }, naruto.animations[attackList[state.skills-1]].duration*1000);
}
else{
    attackCombo = true;
}

在關(guān)閉掉攻擊動(dòng)畫的函數(shù)內(nèi),我們首先將state.skills設(shè)置為0,然后恢復(fù)到移動(dòng)或者站立動(dòng)畫,最后清除掉定時(shí)器:

threejs開(kāi)發(fā)游戲(游戲編程)


//關(guān)閉攻擊狀態(tài)
function closeAttack() {
    state.skills = 0;
    //根據(jù)狀態(tài)設(shè)置是移動(dòng)狀態(tài)還是站立狀態(tài)
    state.move ? gui["action" + 3]() :gui["action" + 24](); //回到站立狀態(tài)
    clearInterval(attackInterval);
}

通過(guò)很簡(jiǎn)單的一些代碼,我們就實(shí)現(xiàn)了一個(gè)復(fù)雜的連招效果。是不是很有成就感,這就是在最前面看到的那個(gè)操作gif的效果的案例

  • OrbitControls.js: 允許我們使用鼠標(biāo)或觸摸屏瀏覽操作3D場(chǎng)景

Fog類定義的是線性霧,霧的密度是隨著距離線性增大的,即場(chǎng)景中物體霧化效果隨著隨距離線性變化。

構(gòu)造函數(shù)霧Fog(color,near,far)的三個(gè)參數(shù)分別對(duì)應(yīng)霧對(duì)象Fog的三個(gè)屬性.color、.near和.far。

.color屬性表示霧的顏色,比如設(shè)置為紅色,場(chǎng)景中遠(yuǎn)處物體為黑色,場(chǎng)景中最近處距離物體是自身顏色,最遠(yuǎn)和最近之間的物體顏色是物體本身顏色和霧顏色的混合效果。

// 改變霧的顏色為白色
scene.fog.color.set(0xffffff)

.near屬性值表示應(yīng)用霧化效果的最小距離,距離活動(dòng)攝像機(jī)長(zhǎng)度小于.near的物體將不會(huì)被霧所影響

.far屬性值表示應(yīng)用霧化效果的最大距離,距離活動(dòng)攝像機(jī)長(zhǎng)度大于.far的物體將不會(huì)被霧所影響

透視投影照相機(jī)(Perspective Camera)的構(gòu)造函數(shù)是:

THREE.PerspectiveCamera(fov, aspect, near, far)

透視圖中,灰色的部分是視景體,是可能被渲染的物體所在的區(qū)域。fov是視景體豎直方向上的張角(是角度制而非弧度制),如側(cè)視圖所示。

aspect等于width / height,是照相機(jī)水平方向和豎直方向長(zhǎng)度的比值,通常設(shè)為Canvas的橫縱比例。

near和far分別是照相機(jī)到視景體最近、最遠(yuǎn)的距離,均為正值,且far應(yīng)大于near。

環(huán)境光是指場(chǎng)景整體的光照效果,是由于場(chǎng)景內(nèi)若干光源的多次反射形成的亮度一致的效果,通常用來(lái)為整個(gè)場(chǎng)景指定一個(gè)基礎(chǔ)亮度。因此,環(huán)境光沒(méi)有明確的光源位置,在各處形成的亮度也是一致的。

在設(shè)置環(huán)境光時(shí),只需要指定光的顏色:

THREE.AmbientLight(hex)

其中,hex是十六進(jìn)制的RGB顏色信息,如紅色表示為0xff0000。

創(chuàng)建環(huán)境光并將其添加到場(chǎng)景中的完整做法是:

var light = new THREE.AmbientLight(0xffffff);
scene.add(light);

如果此時(shí)場(chǎng)景中沒(méi)有物體,只添加了這個(gè)環(huán)境光,那么渲染的結(jié)果仍然是一片黑

環(huán)境光通常使用白色或者灰色,作為整體光照的基礎(chǔ)。

##TextureLoader

通過(guò)紋理貼圖加載器TextureLoader的load()方法加載一張圖片可以返回一個(gè)紋理對(duì)象Texture,紋理對(duì)象Texture可以作為模型材質(zhì)顏色貼圖.map屬性的值。

材質(zhì)的顏色貼圖屬性.map設(shè)置后,模型會(huì)從紋理貼圖上采集像素值,這時(shí)候一般來(lái)說(shuō)不需要在設(shè)置材質(zhì)顏色.color。.map貼圖之所以稱之為顏色貼圖就是因?yàn)榫W(wǎng)格模型會(huì)獲得顏色貼圖的顏色值RGB。

three.js有官方的fbx插件,可以直接將模型加載至網(wǎng)頁(yè),并且支持動(dòng)畫數(shù)據(jù),代碼量也是最少的。 但是,該格式存在很大弊端:插件對(duì)文件格式的規(guī)范很嚴(yán)格,換言之,插件支持性不太好。從網(wǎng)上下載的fbx動(dòng)畫,十有八九會(huì)加載失敗。

首先需要引入FBXLoader.js插件,如果報(bào)錯(cuò) “Error: THREE.FBXLoader: External library Inflate.min.js required, obtain or import from
https://github.com/imaya/zlib.js”,則還需引入inflate.min.js文件。

我們可以看一個(gè)簡(jiǎn)單案例

https://wow.techbrood.com/fiddle/55419

The Making of “The Aviator”: Animating a Basic 3D Scene with Three.js : 使用three.js設(shè)計(jì)游戲的學(xué)習(xí)心得與知識(shí)分享

Three.js Making a Game

16 Three.js 游戲操作案例

Joystick, gamepad or 3D mouse support in Three.js

yoannmoinet/nipplejs

https://css-tricks.com/how-to-make-a-smartphone-controlled-3d-web-game/

ADDING SUPPORT FOR VR INPUTS WITH WEBXR AND THREE.JS

threejs開(kāi)發(fā)游戲(游戲編程)

熱門課程推薦

熱門資訊

請(qǐng)綁定手機(jī)號(hào)

x

同學(xué)您好!

您已成功報(bào)名0元試學(xué)活動(dòng),老師會(huì)在第一時(shí)間與您取得聯(lián)系,請(qǐng)保持電話暢通!
確定