声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。

摘要

兔年到了,祝大家身体健,康万事顺利。本文内容作为兔年新春纪念页面,将使用 Three.js 及 其他前端开发知识,创建一个以兔子为主题的 3D 简单的趣味页面 Rabbit craft go。本文内容包括使用纯代码创建三维浮岛、小河、树木、兔子、胡萝卜以及兔子的运动交互、浮岛的动画效果等。本文包含的知识点相对比较简单,主要包括 使用 Three.js 网格立方体搭建三维卡通场景、键盘事件的监听与三维场景动画的结合等,如果仔细阅读并实践过本专栏《Three.js 进阶之旅》的话,非常容易掌握。

兔子造型来源于 Three.js开源论坛,页面整体造型灵感来源于《我的世界》,页面名称灵感来源于游戏《Lara Craft Go》。

效果

我们先来看看实现效果,页面加载完成后是一个游戏操作提示界面,可以通过键盘 空格键WASD 或方向键操作小兔子运动。点击开始按钮后,游戏提示界面消失,可以看到倒三角造型的天空浮岛及浮岛上方的树木 、河流 、桥 、胡萝卜 、兔子 等元素,接着摄像机镜头 自动拉近并聚焦到兔子上。

按照操作提示界面的按键,可以操作兔子进行前进、转向、跳跃等运动,当兔子的运动位置触碰到胡萝卜时,胡萝卜会消失同时兔子会进行跳跃运动。当兔子运动到小河或者超出浮岛范围时,兔子则会坠落到下方。

打开以下链接,在线预览效果,大屏访问效果更佳。

本专栏系列代码托管在 Github 仓库【threejs-odessey】后续所有目录也都将在此仓库中更新

代码仓库地址:git@github.com:dragonir/threejs-odessey.git

实现

文章篇幅有限,因此删减了三维模型的位置信息等细节调整代码,只提供构建三维模型的整体思路逻辑,想了解该部分内容的详细介绍可以阅读本专栏前几篇文章及阅读本文配套源码。现在,我们来看看整个页面的实现详细步骤:

页面结构

Rabbit Craft Go 页面的整体结构如下,其中 canvas.webgl 是用于渲染场景的容器、剩余标签都是一些装饰元素或提示语。

<canvas class="webgl"></canvas>
<div class="mask" id="mask">
<div class="box">
<div class="keyboard">
<div class="row"><span class="key">W/↑</span></div>
<div class="row"><span class="key">A/←</span><span class="key">S/↓</span><span class="key">D/→</span></div>
<div class="row"><span class="key space">space</span></div>
</div>
<p class="tips"><b>W</b>: 行走&emsp;<b>S</b>: 停止&emsp;<b>A</b>: 向左转&emsp;<b>D</b>: 向右转&emsp;<b>空格键</b>: 跳跃</p>
<p class="start"><button class="button" id="start_button">开始</button></p>
</div>
</div>
<a class='github' href='https://github.com/dragonir/threejs-odessey' target='_blank' rel='noreferrer'>
<span class='author'>three.js odessey</span>
</a>
<h1 class="title">RABBIT CRAFT GO!</h1>
<div class="banner"><i></i></div>

场景初始化

场景初始化过程中,我们引入必需的开发资源,并初始化渲染场景、相机、控制器、光照、页面缩放适配等。其中外部资源的引入,其中 OrbitControls 用于页面镜头缩放及移动控制;TWEENAnimations 用于生成镜头补间动画,也就是刚开始时浮岛由远及近的镜头切换动画中效果;IslandCarrotRabbitWaterfall 等是用来构建三维世界的类。为了使场景更加卡通化,使用了 THREE.sRGBEncoding 渲染效果。场景中添加了两种光源,其中 THREE.DirectionalLight 用来生成阴影效果。

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
import Animations from './environment/animation';
import Island from './environment/island';
import Carrot from './environment/carrot';
import Rabbit from './environment/rabbit';
import Waterfall from './environment/waterfall'; // 初始化渲染器
const canvas = document.querySelector('canvas.webgl');
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true
});
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
renderer.shadowMap.needsUpdate = true; // 初始化场景
const scene = new THREE.Scene(); // 初始化相机
const camera = new THREE.PerspectiveCamera(60, sizes.width / sizes.height, 1, 5000)
camera.position.set(-2000, -250, 2000); // 镜头控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enableDamping = true;
controls.enablePan = false;
controls.dampingFactor = 0.15; // 页面缩放事件监听
window.addEventListener('resize', () => {
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
// 更新渲染
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// 更新相机
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
}); // 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
scene.add(directionalLight);

创建浮岛

如以下 两幅图所示,整个浮岛造型是一个四棱椎,整体分为四部分,顶部是由地面和河流构成的四方体、底部三块是倒置的三角。生成这些三维模型的其实也并没有多少技巧,就像搭积木一样使用 Three.js 提供的立方体网格通过计算拼接到一起即可。类 Island 包含一个方法 generate 用于创建上述三维模型,并将所创建模型添加到三维分组 floorMesh 中用于外部调用,其中棱柱部分是通过 CylinderBufferGeometry 来实现的。

export default class Island {
constructor() {
this.floorMesh = new THREE.Group();
this.generate();
} generate() {
// 左侧地面
const leftFieldMat = new THREE.MeshToonMaterial({
color: 0x015521d,
side: THREE.DoubleSide,
});
const leftFieldGeom = new THREE.BoxBufferGeometry(800, 30, 1800);
this.leftFieldMesh = new THREE.Mesh(leftFieldGeom, leftFieldMat);
// 右侧地面
this.rightFieldMesh = this.leftFieldMesh.clone();
const mapCapMat = new THREE.MeshMatcapMaterial({
matcap: new THREE.TextureLoader().load('./images/matcap.png'),
side: THREE.DoubleSide
})
// 顶部棱柱
const topFieldGeom = new THREE.CylinderBufferGeometry(1200, 900, 200, 4, 4);
this.topFieldMesh = new THREE.Mesh(topFieldGeom, mapCapMat);
// 中间棱柱
const middleFieldGeom = new THREE.CylinderBufferGeometry(850, 600, 200, 4, 4);
this.middleFieldMesh = new THREE.Mesh(middleFieldGeom, mapCapMat);
// 底部棱锥
const bottomFieldGeom = new THREE.ConeBufferGeometry(550, 400, 4);
this.bottomFieldMesh = new THREE.Mesh(bottomFieldGeom, mapCapMat);
// 河面
const strGroundMat = new THREE.MeshLambertMaterial({
color: 0x75bd2d,
side: THREE.DoubleSide,
});
const strCroundGeom = new THREE.BoxBufferGeometry(205, 10, 1800);
this.strGroundMesh = new THREE.Mesh(strCroundGeom, strGroundMat); // 小河
const streamMat = new THREE.MeshLambertMaterial({
color: 0x0941ba,
side: THREE.DoubleSide,
});
const streamGeom = new THREE.BoxBufferGeometry(200, 16, 1800);
this.streamMesh = new THREE.Mesh(streamGeom, streamMat);
// ...
}
};

浮岛俯视图是一个正方形

浮岛侧视图是一个倒三角形

创建水流

接下来,我们为河流添加一个小瀑布,使场景动起来。流动的瀑布三维水滴 滴落效果的是通过创建多个限定范围内随机位置的 THREE.BoxBufferGeometry 来实现水滴模型,然后通过水滴的显示隐藏动画实现视觉上的水滴坠落效果。Waterfall 类用于创建单个水滴,它为水滴初始化随机位置和速度,并提供一个 update 方法用来更新它们。

export default class Waterfall {
constructor (scene) {
this.scene = scene;
this.drop = null;
this.generate();
}
generate () {
this.geometry = new THREE.BoxBufferGeometry(15, 50, 5);
this.material = new THREE.MeshLambertMaterial({ color: 0x0941ba });
this.drop = new THREE.Mesh(this.geometry, this.material);
this.drop.position.set((Math.random() - 0.5) * 200, -50, 900 + Math.random(1, 50) * 10);
this.scene.add(this.drop);
this.speed = 0;
this.lifespan = Math.random() * 50 + 50;
this.update = function() {
this.speed += 0.07;
this.lifespan--;
this.drop.position.x += (5 - this.drop.position.x) / 70;
this.drop.position.y -= this.speed;
};
}
};

完成水滴创建后,不要忘了需要在页面重绘动画 tick 方法中像这样更新已创建的水滴数组 drops,使其看起来生成向下流动坠落的效果。

for (var i = 0; i < drops.length; i++) {
drops[i].update();
if (drops[i].lifespan < 0) {
scene.remove(scene.getObjectById(drops[i].drop.id));
drops.splice(i, 1);
}
}

创建桥

在河流上方添加一个小木桥 ,这样小兔子就可以通过木桥在小河两边移动了。 类 Bridge 通过 generate 方法创建一个小木桥,并通过三维模型组 bridgeMesh 将其导出,我们可以在上面创建的 Island 类中使用它,将其添加到三维场景中。

export default class Bridge {
constructor() {
this.bridgeMesh = new THREE.Group();
this.generate();
}
generate() {
var woodMat = new THREE.MeshLambertMaterial({
color: 0x543b14,
side: THREE.DoubleSide
});
// 木头
for (var i = 0; i < 15; i++) {
var blockGeom = new THREE.BoxBufferGeometry(10, 3, 70);
var block = new THREE.Mesh(blockGeom, woodMat);
this.bridgeMesh.add(block);
}
// 桥尾
var geometry_rail_v = new THREE.BoxBufferGeometry(3, 20, 3);
var rail_1 = new THREE.Mesh(geometry_rail_v, woodMat);
var rail_2 = new THREE.Mesh(geometry_rail_v, woodMat);
var rail_3 = new THREE.Mesh(geometry_rail_v, woodMat);
var rail_4 = new THREE.Mesh(geometry_rail_v, woodMat);
// ...
}
}

创建树

从预览动图和页面可以看到,浮岛上共有两种树 ,绿色的高树和粉红色的矮树,树的实现也非常简单,是使用了两个 BoxBufferGeometry 拼接到一起。类 TreeLeafTree 分别用于生成这两种树木,接收参数 (x, y, z) 分别表示树木在场景中的位置信息。我们可以在 Island 辅导上添加一些树木,构成浮岛上的一片小森林。

export default class Tree {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.treeMesh = new THREE.Group();
this.generate();
}
generate() {
// 树干
var trunkMat = new THREE.MeshLambertMaterial({
color: 0x543b14,
side: THREE.DoubleSide
});
var trunkGeom = new THREE.BoxBufferGeometry(20, 200, 20);
this.trunkMesh = new THREE.Mesh(trunkGeom, trunkMat);
// 树叶
var leavesMat = new THREE.MeshLambertMaterial({
color: 0x016316,
side: THREE.DoubleSide
});
var leavesGeom = new THREE.BoxBufferGeometry(80, 400, 80);
this.leavesMesh = new THREE.Mesh(leavesGeom, leavesMat);
this.treeMesh.add(this.trunkMesh);
this.treeMesh.add(this.leavesMesh);
this.treeMesh.position.set(this.x, this.y, this.z);
// ...
}
}

矮树

export default class LeafTree {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.treeMesh = new THREE.Group();
this.generate();
}
generate() {
// ...
}
}

创建胡萝卜

接着,在地面上添加一些胡萝卜 。胡萝卜身体部分是通过四棱柱 CylinderBufferGeometry 实现的,然后通过 BoxBufferGeometry 立方体来实现胡萝卜的两片叶子。场景中可以通过 Carrot 类来添加胡萝卜,本页面示例中是通过循环调用添加了 20 个随机位置的胡萝卜。

export default class Carrot {
constructor() {
this.carrotMesh = new THREE.Group();
this.generate();
}
generate() {
const carrotMat = new THREE.MeshLambertMaterial({
color: 0xd9721e
});
const leafMat = new THREE.MeshLambertMaterial({
color: 0x339e33
});
// 身体
const bodyGeom = new THREE.CylinderBufferGeometry(5, 3, 12, 4, 1);
this.body = new THREE.Mesh(bodyGeom, carrotMat);
// 叶子
const leafGeom = new THREE.BoxBufferGeometry(5, 10, 1, 1);
this.leaf1 = new THREE.Mesh(leafGeom, leafMat);
this.leaf2 = this.leaf1.clone();
// ...
this.carrotMesh.add(this.body);
this.carrotMesh.add(this.leaf1);
this.carrotMesh.add(this.leaf2);
}
};
for (let i = 0; i < 20; i++) {
carrot[i] = new Carrot();
scene.add(carrot[i].carrotMesh);
carrot[i].carrotMesh.position.set(-170 * Math.random() * 3 - 300, -12, 1400 * Math.random() * 1.2 - 900);
}

创建兔子

最后,来创建页面的主角兔子 。兔子全部都是由立方体 BoxBufferGeometry 搭建而成的,整体可以分解为头、眼睛、耳朵、鼻子、嘴、胡须、身体、尾巴、四肢等构成,构建兔子时的核心要素就是各个立方体位置和缩放比例的调整,需要具备一定的审美能力,当然本例中使用的兔子是在 Three.js 社区开源代码的基础上改造的

完成兔子的整体外形之后,我们通过 gsap 给兔子添加一些运动动画效果和方法以供外部调用,其中 blink() 方法用于眨眼、jump() 方法用于原地跳跃、nod() 方法用于点头、run() 方法用于奔跑、fall() 方法用于边界检测时检测到超出运动范围时使兔子坠落效果等。完成 Rabbit 类后,我们就可以在场景中初始化小兔子。

import { TweenMax, Power0, Power1, Power4, Elastic, Back } from 'gsap';

export default class Rabbit {
constructor() {
this.bodyInitPositions = [];
this.runningCycle = 0;
this.rabbitMesh = new THREE.Group();
this.bodyMesh = new THREE.Group();
this.headMesh = new THREE.Group();
this.generate();
}
generate() {
var bodyMat = new THREE.MeshLambertMaterial({
color: 0x5c6363
});
var tailMat = new THREE.MeshLambertMaterial({
color: 0xc2bebe
});
var nouseMat = new THREE.MeshLambertMaterial({
color: 0xed716d
});
// ...
var pawMat = new THREE.MeshLambertMaterial({
color: 0xbf6970
});
var bodyGeom = new THREE.BoxBufferGeometry(50, 50, 42, 1);
var headGeom = new THREE.BoxBufferGeometry(44, 44, 54, 1);
var earGeom = new THREE.BoxBufferGeometry(5, 60, 10, 1);
var eyeGeom = new THREE.BoxBufferGeometry(20, 20, 8, 1);
var irisGeom = new THREE.BoxBufferGeometry(8, 8, 8, 1);
var mouthGeom = new THREE.BoxBufferGeometry(8, 16, 4, 1);
var mustacheGeom = new THREE.BoxBufferGeometry(0.5, 1, 22, 1);
var spotGeom = new THREE.BoxBufferGeometry(1, 1, 1, 1);
var legGeom = new THREE.BoxBufferGeometry(33, 33, 10, 1);
var pawGeom = new THREE.BoxBufferGeometry(45, 10, 10, 1);
var pawFGeom = new THREE.BoxBufferGeometry(20, 20, 20, 1);
var tailGeom = new THREE.BoxBufferGeometry(20, 20, 20, 1);
var nouseGeom = new THREE.BoxBufferGeometry(20, 20, 15, 1);
var tailGeom = new THREE.BoxBufferGeometry(23, 23, 23, 1);
this.body = new THREE.Mesh(bodyGeom, bodyMat);
this.bodyMesh.add(this.body);
this.head = new THREE.Mesh(headGeom, bodyMat);
this.bodyMesh.add(this.legL);
this.headMesh.add(this.earR);
this.rabbitMesh.add(this.bodyMesh);
this.rabbitMesh.add(this.headMesh);
// ...
}
blink() {
var sp = 0.5 + Math.random();
if (Math.random() > 0.2)
TweenMax.to([this.eyeR.scale, this.eyeL.scale], sp / 8, {
y: 0,
ease: Power1.easeInOut,
yoyo: true,
repeat: 3
});
}
// 跳跃
jump() {
var speed = 10;
var totalSpeed = 10 / speed;
var jumpHeight = 150;
TweenMax.to(this.earL.rotation, totalSpeed / 2, {
z: "+=.3",
ease: Back.easeOut,
yoyo: true,
repeat: 1
});
TweenMax.to(this.earR.rotation, totalSpeed / 2, {
z: "-=.3",
ease: Back.easeOut,
yoyo: true,
repeat: 1
});
// ...
}
// 点头
nod() {}
// 奔跑
run() {}
// 移动
move() {}
// 坠落
fall() {}
// 动作销毁
killNod() {}
killJump() {}
killMove() {}
}

将兔子添加到场景中。

添加动画和操作

为了使兔子可以运动和可交互,我们通过监听键盘按键的方式来调用兔子类内置的对应动画方法,兔子的方向转动可以通过修改兔子的旋转属性 rotation 来实现。

// 兔子控制
const rabbitControl = {
tureLeft: () => {
rabbit && (rabbit.rabbitMesh.rotation.y -= Math.PI / 2);
},
turnRight: () => {
rabbit && (rabbit.rabbitMesh.rotation.y += Math.PI / 2);
},
stopMove: () => {
rabbitMoving = false;
rabbit.killMove();
rabbit.nod();
},
} // 键盘监听
document.addEventListener('keydown', e => {
if (e && e.keyCode) {
switch(e.keyCode) {
// 左
case 65:
case 37:
rabbitControl.tureLeft();
break;
// 右
case 68:
case 39:
rabbitControl.turnRight();
break;
// 前
case 87:
case 38:
rabbitMoving = true;
break;
// 空格键
case 32:
!rabbitJumping && rabbit.jump() && (rabbitJumping = true);
break;
default:
break;
}
}
}); document.addEventListener('keyup', e => {
if (e && e.keyCode) {
switch(e.keyCode) {
case 83:
case 40:
case 87:
case 38:
rabbitMoving = false;
rabbit.killMove();
rabbit.nod();
break;
case 32:
setTimeout(() => {
rabbitJumping = false;
}, 800);
break;
}
}
});

为了使场景更加真实和趣味,我们可以添加一些边界检测方法,当兔子位置处于非可运动区域如小河、浮岛之外等区域时,可以调用兔子的 fall(),方法使其坠落。当检测到兔子的位置和胡萝卜的位置重叠时,给兔子添加了一个 jump() 跳跃动作并使检测到的这个胡萝卜从场景中移除。

const checkCollision = () => {
for (let i = 0; i < 20; i++) {
let rabbCarr = rabbit.rabbitMesh.position.clone().sub(carrot[i].carrotMesh.position.clone());
if (rabbCarr.length() <= 20) {
rabbit.jump();
scene.remove(carrot[i].carrotMesh);
rabbCarr = null;
}
}
// 检查是否是地面的边界
var rabbFloor = island.floorMesh.position.clone().sub(rabbit.rabbitMesh.position.clone());
if (
rabbFloor.x <= -900 ||
rabbFloor.x >= 900 ||
rabbFloor.z <= -900 ||
rabbFloor.z >= 900
) {
rabbit.fall();
}
// 小河检测
var rabbStream = rabbit.rabbitMesh.position.clone().sub(island.streamMesh.position.clone());
if (
(rabbStream.x >= -97 &&
rabbStream.x <= 97 &&
rabbStream.z >= -900 &&
rabbStream.z <= 688) ||
(rabbStream.x >= -97 && rabbStream.x <= 97 && rabbStream.z >= 712)
) {
rabbit.fall();
}
}

页面装饰

最后,我们来制作一个其实页面,中间部分是键盘操作说明,底部是一些装饰文案图片,操作提示下方是一个开始按钮,我们给这个按钮添加一个通过 TWEEN.js 实现的镜头补间动画效果,当点击按钮时,页面首先显示的是倒置三角造型的浮岛,然后镜头慢慢方法拉近,显示出兔子运动的区域。本页面为了使其看起来更加符合游戏主题,标题文案使用了一种像素化的字体

const startButton = document.getElementById('start_button');
const mask = document.getElementById('mask');
startButton.addEventListener('click', () => {
mask.style.display = 'none';
Animations.animateCamera(camera, controls, { x: 50, y: 120, z: 1000 }, { x: 0, y: 0, z: 0 }, 3600, () => {});
});

源码地址:https://github.com/dragonir/threejs-odessey

总结

本文中主要包含的知识点包括:

  • 使用 Three.js 网格立方体搭建三维卡通场景
  • 键盘事件的监听与三维场景动画的结合

想了解其他前端知识或其他未在本文中详细描述的Web 3D开发技术相关知识,可阅读我往期的文章。如果有疑问可以在评论中留言,如果觉得文章对你有帮助,不要忘了一键三连哦

附录

参考

本文作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/17064580.html

Three.js 进阶之旅:新春特典-Rabbit craft go 🐇的更多相关文章

  1. 我的Android进阶之旅------>解决Jackson等第三方转换Json的开发包在开启混淆后转换的实体类数据都是null的bug

    1.错误描述 今天测试人员提了一个bug,说使用我们的app出现了闪退的bug,后来通过debug断点调试,发现我们的app转换服务器发送过来的json数据后,都是为null.而之前已经提测快一个月的 ...

  2. 2. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无从下手 ...

  3. 【 D3.js 进阶系列 】 进阶总结

    进阶系列的文章从去年10月开始写的,晃眼又是4个多月了,想在年前总结一下. 首先恭祝大家新年快乐.今年是羊年吧.前段时间和朋友聊天,聊到十二生肖里为什么没猫,我张口就道:不是因为十二生肖开会的时候猫迟 ...

  4. web前端开发分享-css,js进阶篇

    一,css进阶篇: 等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践 经验, 简单的想法:把qq首页全屏另存为jpg然后通过ps工具切图结合css转换成html,有无 从 ...

  5. [置顶] 我的Android进阶之旅------>介绍一款集录制与剪辑为一体的屏幕GIF 动画制作工具 GifCam

    由于上一篇文章:我的Android进阶之旅------>Android之动画之Frame Animation实例 中展示的是Frame动画效果,但是之前我是将图片截取下来,不好说明确切的动画过程 ...

  6. 我的Android进阶之旅------&gt; Android在TextView中显示图片方法

    面试题:请说出Android SDK支持哪些方式显示富文本信息(不同颜色.大小.并包括图像的文本信息).并简要说明实现方法. 答案:Android SDK支持例如以下显示富文本信息的方式. 1.使用T ...

  7. JAVA进阶之旅(二)——认识Class类,反射的概念,Constructor,Field,Method,反射Main方法,数组的反射和实践

    JAVA进阶之旅(二)--认识Class类,反射的概念,Constructor,Field,Method,反射Main方法,数组的反射和实践 我们继续聊JAVA,这次比较有意思,那就是反射了 一.认识 ...

  8. JAVA进阶之旅(一)——增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法

    JAVA进阶之旅(一)--增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法 学完我们的java之旅,其实收获还是很多的,但是依然还有很 ...

  9. JS 进阶知识点及常考面试题

    将会学习到一些原理相关的知识,不会解释涉及到的知识点的作用及用法,如果大家对于这些内容还不怎么熟悉,推荐先去学习相关的知识点内容再来学习原理知识. 手写 call.apply 及 bind 函数 涉及 ...

  10. 【我的Android进阶之旅】推荐一款视频转换GIF图片格式的转换工具(Video to GIF)

    一.背景 最近想把一些Android Demo的运行效果图获取下来,但是一直使用真机进行调试,在电脑上不好截取一段gif动画.而之前使用模拟器的时候可以使用 GifCam 工具进行屏幕动画截取.Gif ...

随机推荐

  1. SpringBoot(三) - Ribbon客户端负载均衡,Zuul网关,Config配置中心

    1.Ribbon客户端负载均衡 1.1 依赖 1.2 配置信息 # feign默认加载了ribbon负载均衡,默认负载均衡机制是:轮询 # 负载均衡机制是添加在消费端(客户端)的,如果改为随机,指定服 ...

  2. Java 多线程写zip文件遇到的错误 write beyond end of stream!

    最近在写一个大量小文件直接压缩到一个zip的需求,由于zip中的entry每一个都是独立的,不需要追加写入,也就是一个entry文件,写一个内容, 因此直接使用了多线程来处理,结果就翻车了,代码给出了 ...

  3. Oracle生成awr报告操作步骤介绍

    AWR全称Automatic Workload Repository,自动负载信息库,是Oracle 10g版本后推出的一种性能收集和分析工具,提供了一个时间段内整个系统的报表数据.通过AWR报告,可 ...

  4. Java开发学习(四十一)----MyBatisPlus标准数据层(增删查改分页)开发

    一.标准CRUD使用 对于标准的CRUD功能都有哪些以及MyBatisPlus都提供了哪些方法可以使用呢? 我们先来看张图: 1.1 环境准备 这里用的环境就是Java开发学习(四十)----MyBa ...

  5. 微信小程序的学习(一)

    一.小程序简介 1.小程序与普通网页开发的区别 运行环境不同 网页运行在浏览器环境中 小程序运行在微信环境中 API不同 小程序无法调用浏览器中的DOM和BOM的API 但是小程序可以调用微信环境提供 ...

  6. zabbix-钉钉报警部署

    zabbix-钉钉报警部署 1. 流程说明 申请钉钉机器人 获取Webhook配置安全设置 获取钉钉号 使用脚本(shell/python)调用钉钉接口: python 输入收件人 信息 配置发件人 ...

  7. civil3d安装教程2022序列号和密钥

    Civil3D2021 WIN10 64位安装步骤:1.先使用"百度网盘客户端"下载C3D21_CN_x64软件安装包到电脑磁盘里,并右击进行解压,安装前先断网,然后找到Autod ...

  8. Go语言核心36讲31

    我们在前两篇文章中讨论了互斥锁.读写锁以及基于它们的条件变量,先来总结一下. 互斥锁是一个很有用的同步工具,它可以保证每一时刻进入临界区的goroutine只有一个.读写锁对共享资源的写操作和读操作则 ...

  9. 谈软件-专家谈C/C++重构的操作与思路

    1.Refactoring: 对软件内部结构的一种调整,目的是不该被软件的可观察行为的前提上,提高其可理解性,降低其修改成本. 2.代码坏味道 2.1.不易复用 2.2.不易理解 2.3.存在冗余 3 ...

  10. PHY驱动调试之 ---PHY设备驱动(三)

    1. 前言 内核版本:linux 4.9.225,以freescale为例.(部分内容待修改和补充,不一定准确) 2. 概述 上一篇文章讲了控制器的驱动使用的是platform总线的连接方式,本节要讲 ...