王者荣耀英雄选择案例
一、目标与说明
- 还是注重逻辑分析,图片素材为游戏截屏抠图而成,不涉及王者炫酷的动画效果。
- 运用面向对象的思想,在这里我会分析王者荣耀运用了哪些对象,然后抽离里面的功能和方法、抽离成类、研究每个类之间的逻辑关系、把每个类模块化。
- 最终实现进入登录界面完成登陆、选择英雄、选择皮肤、释放技能等一系列功能。
- 为什么要用面向对象来做?王者荣耀上百个英雄,每个英雄又对应各自的皮肤和技能。按照面向对象思想,根据项目的特点,把类与类之间的逻辑关系写好,实现模块化功能,到时候上百个英雄只需要直接对类进行实例化就可以完成功能,代码更加易于管理和高效。
- 实现功能并不难,怎么划分对象、怎么划分功能、搞清楚对象与对象之间的关系是关键点。
二、实现效果
王者荣耀英雄选择案例效果图
三、写之前先分析
- 写之前先分析一波:假设我已经知道面向对象的思想、知道什么是对象、类、原型、继承等,现在有了这样一个需求,我该怎么把思维从面向过程转变为面向对象呢?
- 可以按照以下步骤:
- 根据需求研究对象,比如我今天就写两个英雄,不需要研究一大堆东西,一切从需求出发就行。要考虑有哪些对象?对象有哪些属性和方法?有什么特性?
- 抽象成类,提升复用性。如果是一个类,它就不止一个对象,可能有成百上千个对象,抽离这些对象中的一些共性的东西成类。如果某个对象有特殊的功能的话,就没必要抽离成类。
- 上一步研究完对象的共性,已经抽离成类了,这一步可以根据需求研究类的共性问题,即抽离类成为基类(父类),这时候就可以考虑继承的问题。但是是根据需求来,如果需求简单,就没必要这么做。
- 研究类之间的逻辑关系,研究这个类需要那个类,这个类调用那个类等等。
- 在王者荣耀这个案例中,我列出来的对象有:亚瑟、鲁班、玩家、技能、皮肤、游戏管理类game
- 文件目录如下图:
四、类
1. 玩家类
import Yase from './yase.js';
import Luban from './luban/js'
export default class Player{
constructor(username){
this.name = username;
this.heroes = [new Yase,new Luban];
}
}
- 玩家就是用户,在这个案例中只涉及到了用户名和玩家拥有的英雄,所以只有两个属性name和heroes
- 真正的游戏还有很多属性,例如等级啊、积分啊、钻石啊、点券等等
- 这个界面引入了两个类,亚瑟类和鲁班类,是属于玩家的英雄,这里就可以显现面向对象的好处了,如果玩家还有其他英雄如李白,只需要引入李白类,并在heroes里面进行实例化即可。
2. 亚瑟类
//亚瑟类
import Hero from './hero.js'
import S1 from './skills/yase/s1.js'
import S2 from './skills/yase/s2.js'
import S3 from './skills/yase/s3.js'
import Skin1 from './skins/yase/skin1.js';
import Skin2 from './skins/yase/skin2.js';
import Skin3 from './skins/yase/skin3.js';
export default class Yase extends Hero{
constructor(){
let opts = {
name: "亚瑟",
ico: "./sources/heros/yase1.png",
skills: [new S1(),new S2,new S3],
skins: [new Skin1,new Skin2,new Skin3]
}
super(opts);
// this.name = "亚瑟";
// this.ico = "./sources/heros/yase1.png";
// this.skills = [new S1(),new S2,new S3];
// this.skins = [new Skin1,new Skin2,new Skin3];
}
}
- 亚瑟类包含四个属性,分别是英雄名、英雄图标、技能和皮肤
- 技能被抽离成类,引入了三个技能S1,S2,S3,并实例化
- 皮肤也被抽离成类,引入了三个皮肤Skin1,Skin2,Skin3,并实例化
- 因为亚瑟、鲁班都是英雄,我在这里将亚瑟类和鲁班类的共性抽离成基类,即英雄类,所以这里引入了Hero类
- 在这个案例中其实没必要抽离基类,下面注释是没有抽离的写法,是否抽离应该按实际需求来
3. 鲁班类
//鲁班类
import Hero from './hero.js'
import S1 from './skills/luban/s1.js'
import S2 from './skills/luban/s2.js'
import S3 from './skills/luban/s3.js'
import Skin1 from './skins/luban/skin1.js'
import Skin2 from './skins/luban/skin2.js'
import Skin3 from './skins/luban/skin3.js'
export default class Luban extends Hero{
constructor(){
let opts = {
name : "鲁班",
ico : "./sources/heros/luban1.png",
skills : [new S1(),new S2(),new S3()],
skins : [new Skin1,new Skin2, new Skin3],
}
super(opts);
}
}
- 说明和亚瑟类相似,这里不赘述
4.英雄基类
// 英雄基类;
import GameEvent from './GameEvent.js';
export default class Hero extends GameEvent{
constructor({name,ico,skills,skins}){
super();
this.name = name;
this.ico = ico;
this.skills = skills;
this.skins = skins;
// 绑定自定义事件
this.addEvent("myinit",this.init);
}
init(){
console.log("初始化了");
}
}
- 抽离出一些英雄的共性,例如name、ico、skills、skins
- 进行了一个初始化实现(功能很简单哈哈哈,只打印了一下初始化了)
- 这里的addEvent方法是继承的父类GameEvent里面的添加事件的方法
- 父类GameEvent就是用来管理一些事件的添加移除的,这里不过多赘述,详细代码可以下载源码查看
5. 技能类和皮肤类
- 逻辑比较简单我就以图片形式呈现啦
- 技能类包含了两个属性 name 和 ico,以及一个释放技能 release 方法
- 皮肤类就简简单单包含了三个属性 name 、ico、 img;注意,ico是小图标;img是大图片
6. 总结
- 类与类之间的关系就说到这里啦,看似抽离了这么多类很麻烦,但其实一旦英雄数量多起来,玩家多起来,就会显现出这样做的好处,逻辑更清晰、更易于管理,哪里出现了bug也可以第一时间发现
- 所以大家还是慢慢学会转变自己的思想,从面向过程到面向对象
五、页面结构和逻辑
1. 页面元素
<body>
<!-- 登录页面 -->
<div class="login">
<input class="username" /> <button class="sub">登录</button>
<img src="./sources/login.JPG" alt="">
</div>
<!-- 游戏选择页面 -->
<div class="game">
<button class="heroBtn">英雄</button>
<div class="heroContainer">
<div class="heroView">
<div class="heroItem">
<img src="./sources/heros/yase1.png" />
<span>亚瑟</span>
</div>
<div class="heroItem">
<img src="./sources/heros/yase1.png" />
<span>亚瑟</span>
</div>
</div>
</div>
<button class="skinBtn">皮肤</button>
<div class="skinContainer">
<div class="skinView">
<!-- <div class="skinItem">
<img src="./sources/heros/yase1.png" />
<span>经典</span>
</div>
<div class="skinItem">
<img src="./sources/heros/yase2.png" />
<span>死亡骑士</span>
</div> -->
</div>
</div>
<div class="skinShow">
<img src="./sources/skins/301660.png" alt="">
</div>
<div class="userView">
<div class="heroShow">
<!-- <img src="./sources/heros/yase1.png" /> -->
</div>
<span class="chioseusername">张三</span>
</div>
<div class="skillsView">
<!-- <img src="./sources/skills/11210.png" />
<img src="./sources/skills/11220.png" />
<img src="./sources/skills/11230.png" /> -->
</div>
<div>
</div>
<img src="./sources/chiose.JPG" />
</div>
<script type="module" src="./index.js"></script>
</body>
- 主要是一些 div 结构,注意 class 类名要起的很规范,这样写逻辑操作DOM结构的时候才不会乱套
- 代码倒数第二行采用模块化思想,导入 index.js 业务逻辑
2. Style样式
<style>
body {
margin: 0;
padding: 0;
}
.username {
position: absolute;
left: 347px;
top: 250px;
border-radius: 5px;
height: 24px;
}
button {
background: transparent;
outline: none;
border: none;
color: white;
}
.heroShow{
width: 50px;
height: 50px;
}
.sub {
position: absolute;
left: 351px;
top: 288px;
width: 126px;
color: white;
font-weight: bold;
font-size: 18px;
}
.geme {
position: relative;
}
.heroBtn {
position: absolute;
left: 45px;
top: 8px;
font-size: 14px;
}
.skinBtn {
position: absolute;
top: 8px;
left: 105px;
font-size: 14px;
}
.userView{
position: absolute;
/* width: 80px; */
top: 34px;
left: 720px;
}
.userView img{
width: 40px;
height: 40px;
}
.chioseusername {
display: block;
color: rgb(255, 215, 0);
text-align: center;
font-size: 12px;
font-weight: bold;
}
.skillsView {
position: absolute;
display: flex;
flex-direction: column;
left: 660px;
top: 200px;
}
.skillsView img {
width: 40px;
height: 40px;
margin-bottom: 10px;
}
.heroView{
width: 120px;
position:absolute;
margin-top: 40px;
margin-left: 20px;
display: flex;
flex-wrap: wrap;
}
.skinView{
width: 130px;
position:absolute;
margin-top: 40px;
margin-left: 20px;
display: flex;
flex-wrap: wrap;
}
.heroView img {
width: 50px;
height: 50px;
}
.skinView img{
width: 50px;
height: 50px;
}
.heroItem{
margin-left: 10px;
margin-top: 10px;
display: flex;
flex-direction: column;
color: gray;
align-items: center;
font-size: 12px;
}
.skinItem{
margin-left: 10px;
margin-top: 10px;
display: flex;
flex-direction: column;
color: gray;
align-items: center;
font-size: 12px;
}
.skinShow{
position:absolute;
left:320px;
top: 40px;
}
.login {
display: block;
}
.game{
display: none;
}
.heroContainer{
display: block;
}
.skinContainer{
display: none;
}
</style>
- 我这里直接写进 style 标签里了,其实更好的做法应该是写css文件,然后引入进来
3. 逻辑操作
import Game from './game/game.js';
// 对象: 鲁班、亚瑟、玩家、技能、游戏管理类;
let game = new Game();
function decorator(fn1,fn2,skill,...arg){
fn1.call(skill);
fn2(...arg);
}
function hurt(num){
console.log("造成"+num+"点伤害");
}
document.querySelector(".sub").onclick = function(){
let username = document.querySelector(".username").value;
// console.log(username);
game.login(username);
console.log(game);
// 隐藏login 显示选择;
document.querySelector(".login").style.display = "none";
document.querySelector(".game").style.display = "block";
// 修改名称
document.querySelector(".chioseusername").innerHTML = username;
renderHeroes(game.player.heroes);
}
function renderHeroes(heroes){
console.log(heroes);
document.querySelector(".heroView").innerHTML = "";
heroes.forEach(hero=>{
let heroItem = document.createElement("div");
heroItem.classList.add("heroItem");
heroItem.innerHTML = `<img src="${hero.ico}" />
<span>${hero.name}</span>`;
document.querySelector(".heroView").appendChild(heroItem);
heroItem.onclick = function(){
document.querySelector(".heroShow").innerHTML = `<img src="${hero.ico}" />`
// 渲染技能
renderSkills(hero.skills);
// 渲染皮肤;
renderSkins(hero.skins);
}
})
}
function renderSkills(skills){
document.querySelector(".skillsView").innerHTML = "";
skills.forEach(skill=>{
let img = document.createElement("img");
img.src = skill.ico;
document.querySelector(".skillsView").appendChild(img);
img.onclick = function(){
decorator(skill.release,hurt,skill,100);
}
})
}
// 渲染皮肤
function renderSkins(skins){
document.querySelector(".skinView").innerHTML="";
document.querySelector(".skinShow").innerHTML = `<img src="${skins[0].img}" />`;
skins.forEach(skin=>{
let skinDiv = document.createElement("div");
skinDiv.classList.add("skinItem");
skinDiv.innerHTML = `<img src="${skin.ico}" />
<span>${skin.name}</span>`;
document.querySelector(".skinView").appendChild(skinDiv);
skinDiv.onclick = function(){
document.querySelector(".skinShow").innerHTML = `<img src="${skin.img}" />`;
}
})
}
// 切换 英雄 及皮肤;
document.querySelector(".heroBtn").onclick = function(){
document.querySelector(".heroContainer").style.display = "block";
document.querySelector(".skinContainer").style.display = "none";
}
document.querySelector(".skinBtn").onclick = function(){
document.querySelector(".heroContainer").style.display = "none";
document.querySelector(".skinContainer").style.display = "block";
}
- 是按照之前讲的数据驱动的思想,所以这里一般都放一些渲染视图的逻辑。
- 具体都是一些DOM结构的基本操作,如果使用 vue 或者 react 开发的话,这些逻辑将会更简单。
六、 总结
- 这个案例能很好的体现面向对象的思想。其实无论是面向对象还是面向过程都有一定的好处,只是要根据实际需求来定。
- 巩固自己的同时也希望可以帮到大家~