从零开始实现放置游戏(八)——实现挂机战斗(6)代码重构

  前几张,我们主要实现了升级经验、人物等级属性、地图、地图怪物,这四种配置的增删查改以及Excel导入功能。我们主要以地图怪物为例,因此在文章末尾提供的源代码中只实现了地图怪物这部分的逻辑功能。

  如果你照猫画虎,把4种配置功能的逻辑全部实现的话,就会发现,增删查改的代码基本相同,除了SQL语句和模型对象不同,其他地方变化不大。

  本章我们利用泛型模板,对整个系统就行重构。在重构结束后,你就会发现写代码简直就是TMD艺术

后端重构

idlewow-core

  我们从最底层开始,首先重构位于core模块中的数据访问层。目前看来,基本上所有的模型对象,都应包含增删查改、批量添加、列表查询这些基本方法。那我们把这些方法抽象到一个单独的Mapper和Manager里。

  新建com.idlewow.common包,再该包下新建接口类BaseMapper:

package com.idlewow.common;

import com.idlewow.common.model.QueryParam;

import java.util.List;

public interface BaseMapper<T> {
    /**
     * 添加记录
     * @param t
     */
    int insert(T t);

    /**
     * 批量添加记录
     * @param list
     * @return
     */
    int batchInsert(List<T> list);

    /**
     * 更新记录
     * @param t
     */
    int update(T t);

    /**
     * 删除记录
     * @param id
     */
    int delete(String id);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    T find(String id);

    /**
     * 根据条件查询总数
     * @param queryParam
     * @return
     */
    int count(QueryParam queryParam);

    /**
     * 根据条件查询列表
     * @param queryParam
     * @return
     */
    List<T> list(QueryParam queryParam);
}

BaseMapper.java

  再在该包下,新建一个抽象类BaseManager,代码如下:

package com.idlewow.common;

import com.idlewow.common.model.PageList;
import com.idlewow.common.model.QueryParam;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public abstract class BaseManager<T> {
    @Autowired
    public BaseMapper<T> baseMapper;

    public void insert(T t) {
        int effected = baseMapper.insert(t);
        if (effected == 0) {
            throw new RuntimeException("sql effected 0 rows");
        }
    }

    public void batchInsert(List<T> list) {
        int splitSize = 100;
        int index = 0;
        int total = list.size();
        while (index <= total) {
            int end = index + splitSize;
            if (end > total) {
                end = total;
            }

            List<T> sublist = list.subList(index, end);
            int effected = baseMapper.batchInsert(sublist);
            if (effected == 0) {
                throw new RuntimeException("sql effected 0 rows");
            }

            index += splitSize;
        }
    }

    public void update(T t) {
        int effected = baseMapper.update(t);
        if (effected == 0) {
            throw new RuntimeException("sql effected 0 rows");
        }
    }

    public void delete(String id) {
        int effected = baseMapper.delete(id);
        if (effected == 0) {
            throw new RuntimeException("sql effected 0 rows");
        }
    }

    public T find(String id) {
        T t = (T)baseMapper.find(id);
        return t;
    }

    public PageList<T> list(QueryParam queryParam) {
        PageList<T> pageList = new PageList<>();
        int count = baseMapper.count(queryParam);
        List<T> list = baseMapper.list(queryParam);
        pageList.setTotalCount(count);
        pageList.setData(list);
        pageList.setPageParam(queryParam.getPageParam());
        return pageList;
    }
}

BaseManager.java

  还是以地图怪物为例,我们重构MapMobMapper和MapMobManager,只要让他们继承BaseMapper和BaseManager即可,代码如下:

package com.idlewow.mob.mapper;

import com.idlewow.common.BaseMapper;
import com.idlewow.mob.model.MapMob;

public interface MapMobMapper extends BaseMapper<MapMob> {
}

MapMobMapper.java

package com.idlewow.mob.manager;

import com.idlewow.common.BaseManager;
import com.idlewow.mob.model.MapMob;
import org.springframework.stereotype.Component;

@Component
public class MapMobManager extends BaseManager<MapMob> {
}

MapMobManager.java

  重构后的Mapper和Manager直接继承基类的增删查改方法,无需在各个业务中一遍又一遍的书写重复代码。

idlewow-rms

  在rms模块中,主要对controller中的重复代码进行重构。在前几章,我们抽象出过一个BaseController,在里面实现了一些最基础的方法。这里,我们再抽象出一个CrudController来实现数据的增删查该;在CrudController的基础上,再抽象出一个ExcelController来实现Excel的批量导入。

  在com.idlewow.rms.controlelr包下新建抽象类CrudContoller,代码如下:

package com.idlewow.rms.controller;

import com.idlewow.common.BaseManager;
import com.idlewow.common.model.BaseModel;
import com.idlewow.common.model.CommonResult;
import com.idlewow.common.model.PageList;
import com.idlewow.common.model.QueryParam;
import com.idlewow.util.validation.ValidateGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

public abstract class CrudController<T extends BaseModel, Q extends QueryParam> extends BaseController {
    private final String path = this.getClass().getAnnotation(RequestMapping.class).value()[0];

    @Autowired
    BaseManager<T> baseManager;

    @RequestMapping("/list")
    public Object list() {
        return this.path + "/list";
    }

    @ResponseBody
    @RequestMapping(value = "/list", method = RequestMethod.POST)
    public Object list(@RequestParam(value = "page", defaultValue = "1") int pageIndex, @RequestParam(value = "limit", defaultValue = "10") int pageSize, Q q) {
        q.setPage(pageIndex, pageSize);
        PageList<T> pageList = baseManager.list(q);
        return this.parseTable(pageList);
    }

    @RequestMapping("/add")
    public Object add() {
        return this.path + "/add";
    }

    @ResponseBody
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public Object add(@RequestBody T t) {
        try {
            CommonResult commonResult = this.validate(t, ValidateGroup.Create.class);
            if (!commonResult.isSuccess())
                return commonResult;

            t.setCreateUser(this.currentUserName());
            baseManager.insert(t);
            return CommonResult.success();
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            return CommonResult.fail();
        }
    }

    @RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public Object edit(@PathVariable String id, Model model) {
        T t = baseManager.find(id);
        model.addAttribute(t);
        return this.path + "/edit";
    }

    @ResponseBody
    @RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
    public Object edit(@PathVariable String id, @RequestBody T t) {
        try {
            if (!id.equals(t.getId())) {
                return CommonResult.fail("id不一致");
            }

            CommonResult commonResult = this.validate(t, ValidateGroup.Update.class);
            if (!commonResult.isSuccess())
                return commonResult;

            t.setUpdateUser(this.currentUserName());
            baseManager.update(t);
            return CommonResult.success();
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            return CommonResult.fail();
        }
    }

    @ResponseBody
    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
    public Object delete(@PathVariable String id) {
        try {
            baseManager.delete(id);
            return CommonResult.success();
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            return CommonResult.fail();
        }
    }
}

CrudController.java

  这个CrudController起到了一个模板的作用,可以说非常的精髓。首先,利用泛型约束,解决了不同业务数据模型、查询参数不同的问题。然后,通过反射获取不同业务controller的Url映射,解决了不同业务跳转页面路径不同的问题。具体业务的controller直接继承此类,无需再写任何代码,即可实现增删查改。

  下面我们再实现Excel导入功能的模板类,在该包内新建一个抽象类ExcelController,代码如下:

package com.idlewow.rms.controller;

import com.idlewow.common.model.BaseModel;
import com.idlewow.common.model.CommonResult;
import com.idlewow.common.model.QueryParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.Iterator;
import java.util.List;

public abstract class ExcelController<T extends BaseModel, Q extends QueryParam> extends CrudController<T, Q> {
    @ResponseBody
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Object importExcel(HttpServletRequest request) {
        try {
            ServletContext servletContext = request.getServletContext();
            String uploadPath = servletContext.getRealPath("/upload");
            File dir = new File(uploadPath);
            if (!dir.exists()) {
                dir.mkdir();
            }

            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(servletContext);
            if (multipartResolver.isMultipart(request)) {
                MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
                Iterator<String> iter = multiRequest.getFileNames();
                while (iter.hasNext()) {
                    MultipartFile file = multiRequest.getFile(iter.next());
                    if (file.getSize() > 0) {
                        String fileName = file.getOriginalFilename();
                        String extension = fileName.substring(fileName.lastIndexOf("."));
                        if (!extension.toLowerCase().equals(".xls") && !extension.toLowerCase().equals(".xlsx")) {
                            throw new Exception("不支持的文档格式!请上传.xls或.xlsx格式的文档!");
                        }

                        String destFileName = fileName + "_" + System.currentTimeMillis() + extension;
                        File destFile = new File(uploadPath, destFileName);
                        file.transferTo(destFile);
                        List<T> dataList = this.loadExcelData(destFile.getPath());
                        this.saveExcelData(dataList);
                        if (destFile.exists() && !destFile.delete()) {
                            logger.error("删除临时文件失败!" + destFile.getAbsolutePath());
                        }
                    }
                }
            }

            return CommonResult.success();
        } catch (Exception ex) {
            logger.error(ex.getMessage(), ex);
            return CommonResult.fail();
        }
    }

    protected abstract List<T> loadExcelData(String excelPath) throws Exception;

    protected void saveExcelData(List<T> dataList) {
        this.baseManager.batchInsert(dataList);
    }
}

ExcelController.java

  在这个类中,我们将Excel导入功能分解成3个方法。importExcel,对应前端点击事件,保存上传的临时文件;saveExcelData,保存解析出的数据,即调用mapper的批量添加方法;这两个方法都是通用的,直接在ExcelController中实现即可。只有loadExcelData,解析Excel数据这个方法,不同业务的实现不同,我们把它定义成抽象方法,等待各个业务自己实现。

  好了,基类已经定义好了,我们让MapMobController继承ExcelController即可,代码如下:

package com.idlewow.rms.controller;

import com.idlewow.common.constant.DataDict;
import com.idlewow.map.model.WowMap;
import com.idlewow.mob.model.MapMob;
import com.idlewow.query.model.MapMobQueryParam;
import com.idlewow.util.poi.PoiUtil;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.ehcache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

@Controller
@RequestMapping("/manage/map_mob")
public class MapMobController extends ExcelController<MapMob, MapMobQueryParam> {
    @Autowired
    protected Cache mapCache;

    protected List<MapMob> loadExcelData(String excelPath) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(excelPath);
        XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
        Sheet sheet = workbook.getSheet("怪物");
        List<MapMob> mapMobList = new ArrayList<>();
        // 处理当前页,循环读取每一行
        String createUser = this.currentUserName();
        for (int rowNum = 2; rowNum <= sheet.getLastRowNum(); rowNum++) {
            XSSFRow row = (XSSFRow) sheet.getRow(rowNum);
            String mapName = PoiUtil.getCellValue(row.getCell(1));
            String mobName = PoiUtil.getCellValue(row.getCell(2));
            String faction = PoiUtil.getCellValue(row.getCell(3));
            String mobClass = PoiUtil.getCellValue(row.getCell(4));
            String mobType = PoiUtil.getCellValue(row.getCell(5));
            Integer level = Integer.valueOf(PoiUtil.getCellValue(row.getCell(6)));
            Integer hp = Integer.valueOf(PoiUtil.getCellValue(row.getCell(7)));
            Integer damage = Integer.valueOf(PoiUtil.getCellValue(row.getCell(8)));
            Integer amour = Integer.valueOf(PoiUtil.getCellValue(row.getCell(9)));
            WowMap wowMap = (WowMap)mapCache.get(mapName);
            MapMob mapMob = new MapMob();
            mapMob.setMapId(wowMap.getId());
            mapMob.setMapName(wowMap.getName());
            mapMob.setName(mobName);
            mapMob.setFaction(DataDict.Faction.getByDesc(faction).getCode());
            mapMob.setMobClass(DataDict.MobClass.getByDesc(mobClass).getCode());
            mapMob.setMobType(DataDict.MobType.getByDesc(mobType).getCode());
            mapMob.setLevel(level);
            mapMob.setHp(hp);
            mapMob.setDamage(damage);
            mapMob.setAmour(amour);
            mapMob.setCreateUser(createUser);
            mapMobList.add(mapMob);
        }

        fileInputStream.close();
        return mapMobList;
    }
}

MapMobController.java

  现在业务Controller里,只有一个独立实现的loadExcelData方法,再也不用重复书写增删查改了。(注意:这个类里多了个缓存对象mapCache,是我用来缓存地图数据的。pom中添加了对应包,具体可在源码中查看。用法比较简单,可搜EhCache。)

前端重构

  除了后端代码冗余外,其实很容易发现,前端的代码重复部分也很多,尤其是写ajax请求的部分,其实每次变化的只有url地址,或者请求参数等。我们直接在js里定义一个类来实现增删查改等这些ajax请求的通用部分。

  在/webapp/js/helper.js中,定义一个类CRUD,代码如下:

…………
…………

var CRUD = function () {
};

CRUD.prototype = {
    list: function (cols, url) {
        var table = layui.table;
        table.render({
            elem: ‘#datatable‘
            , url: url
            , method: ‘post‘
            , cellMinWidth: 80
            , cols: cols
            , page: {
                layout: [‘limit‘, ‘count‘, ‘prev‘, ‘page‘, ‘next‘, ‘skip‘] //自定义分页布局
                , limits: [10, 20, 30, 40, 50]
                , groups: 3 //只显示 1 个连续页码
                , first: ‘首页‘
                , last: ‘尾页‘
            }
        });
    },
    upload: function (url, extension) {
        layui.upload.render({
            elem: ‘#btnSelectFile‘,
            url: url,
            accept: ‘file‘,
            exts: extension,
            auto: false,
            bindAction: ‘#btnImport‘,
            done: function (result) {
                if (result.code === 1) {
                    layer.alert(result.message, {icon: 6},
                        function () {
                            layui.layer.closeAll();
                            layui.table.reload(‘datatable‘);
                        });
                } else {
                    layer.alert(result.message, {icon: 5});
                }
            }
        });
    },
    search: function (data) {
        var table = layui.table;
        table.reload(‘datatable‘, {
            where: data,
            page: {
                curr: 1
            }
        });
    },
    add: function (url) {
        var form = layui.form;
        form.on(‘submit(add)‘,
            function (data) {
                $.ajax({
                    url: url,
                    type: ‘post‘,
                    contentType: "application/json; charset=utf-8",
                    data: JSON.stringify(data.field),
                    success: function (result) {
                        if (result.code === 1) {
                            layer.alert(result.message, {icon: 6},
                                function () {
                                    xadmin.close();
                                    xadmin.father_reload();
                                });
                        } else {
                            layer.alert(result.message, {icon: 5});
                        }
                    },
                    error: function () {
                        layer.alert("请求失败", {icon: 5});
                    }
                });
            });
    },
    edit: function (url) {
        var form = layui.form;
        form.on(‘submit(edit)‘,
            function (data) {
                $.ajax({
                    url: url + ‘/‘ + data.field.id,
                    type: ‘post‘,
                    contentType: "application/json; charset=utf-8",
                    data: JSON.stringify(data.field),
                    success: function (result) {
                        if (result.code === 1) {
                            layer.alert(result.message, {icon: 6},
                                function () {
                                    xadmin.close();
                                    xadmin.father_reload();
                                });
                        } else {
                            layer.alert(result.message, {icon: 5});
                        }
                    },
                    error: function () {
                        layer.alert("请求失败", {icon: 5});
                    }
                });
            });
    },
    remove: function (obj, url) {
        layer.confirm(‘确认要删除吗?‘, function () {
            $.ajax({
                url: url,
                type: ‘post‘,
                success: function (result) {
                    if (result.code === 1) {
                        $(obj).parents("tr").remove();
                        layer.msg(‘删除成功‘, {icon: 1, time: 1000});
                    } else {
                        layer.alert("删除失败", {icon: 5});
                    }
                },
                error: function () {
                    layer.alert("请求失败", {icon: 5});
                }
            });
        });
    }
};

window.crud = new CRUD();

…………
…………

Helper.js

  然后,在/webapp/js/wow/map_mob中,修改add.js, edit.js 和 list.js 如下:

layui.use([‘form‘, ‘layer‘],
    function () {
        var form = layui.form;
        form.verify({});
        crud.add(‘/manage/map_mob/add‘);
    });

add.js

layui.use([‘form‘, ‘layer‘],
    function () {
        var form = layui.form;
        form.render();
        form.verify({});
        crud.edit(‘/manage/map_mob/edit/‘);
    });

edit.js

layui.use([‘upload‘, ‘table‘, ‘form‘], function () {
    var cols = [[
        {field: ‘id‘, width: 50, title: ‘id‘}
        , {field: ‘name‘, title: ‘怪物名称‘}
        , {field: ‘mapName‘, title: ‘地图名称‘}
        , {
            field: ‘faction‘, title: ‘阵营‘, templet: function (d) {
                return enumUtil.faction(d.faction);
            }
        }
        , {
            field: ‘mobClass‘, title: ‘怪物种类‘, templet: function (d) {
                return enumUtil.mobClass(d.mobClass);
            }
        }
        , {
            field: ‘mobType‘, title: ‘怪物类型‘, templet: function (d) {
                return enumUtil.mobType(d.mobType);
            }
        }
        , {field: ‘level‘, title: ‘等级‘}
        , {field: ‘hp‘, title: ‘生命值‘}
        , {field: ‘damage‘, title: ‘伤害‘}
        , {field: ‘amour‘, title: ‘护甲‘}
        , {
            title: ‘操作‘, width: 150, templet: function (d) {
                return ‘<button class="layui-btn layui-btn-xs"  onclick="xadmin.open(\‘编辑怪物\‘,\‘edit/‘ + d.id + ‘\‘, 500, 500)" type="button"><i class="layui-icon"></i>编辑</button>‘ +
                    ‘<button class="layui-btn-danger layui-btn layui-btn-xs"  onclick="remove(this, \‘‘ + d.id + ‘\‘)" type="button"><i class="layui-icon"></i>删除</button>‘;
            }
        }
    ]];

    crud.list(cols, ‘/manage/map_mob/list‘);
    crud.upload(‘/manage/map_mob/importExcel‘, ‘xls|xlsx‘);
});

function search() {
    var data = {
        name: $(‘input[name="name"]‘).val(),
        levelStart: $(‘input[name="levelStart"]‘).val(),
        levelEnd: $(‘input[name="levelEnd"]‘).val(),
        faction: $(‘select[name="faction"]‘).val(),
        mobClass: $(‘select[name="mobClass"]‘).val(),
        mobType: $(‘select[name="mobType"]‘).val()
    };

    crud.search(data);
}

function reset(){
    $(‘#queryForm‘).reset();
}

function remove(obj, id) {
    crud.remove(obj, ‘/manage/map_mob/delete/‘ + id);
}

list.js

  可以看到,重构后的js,和后端一样,简洁多了。

运行效果

  这里,由于改动了core模块,需要先对项目编译打包。再运行rms模块,即可正常启动项目。启动后,效果和之前一样,只是代码变得简洁多了,这里就不再截图了。

小结

  本章终于把冗余的代码进行了重构,整个代码瞬间提升了几层逼格,变得干净多了。

  源码下载地址:https://idlestudio.ctfile.com/fs/14960372-386521083

  说明:我的代码风格就是不套用设计模式,在不确定最终效果时,不做过多提前设计,先实现了再说,实现了再慢慢重构,还有就是几乎不注释。

  最近因为有其他的事情,所以停更了一周。写到这里其实发现这一大章题目似乎叫RMS系统的初步实现更好。

  写代码和写文章其实差别还有点大。自己写代码的时候,想到哪写到哪,各个模块并行随缘开发。但写文章就必须按一定顺序来,否则容易让人困惑。所以文章里的代码都是单独拉一个分支重新整理的。

  后面希望自己能坚持下去,一周更一次,用一两年把主线更完。

原文地址:https://www.cnblogs.com/lyosaki88/p/11152608.html

时间: 2024-10-10 07:58:31

从零开始实现放置游戏(八)——实现挂机战斗(6)代码重构的相关文章

从零开始实现放置游戏(十二)——实现战斗挂机(3)数据字典和缓存改造

上一章,我们添加了游戏的主界面和注册登录功能.由于距离上上篇间隔较长,可能有些内容想些的后来就忘了.同时,逻辑也不复杂,所以描述比较粗略. 现在随着模块的增加,整个架构也暴露出一些问题.本章我们将对整个系统进行大规模重构. 比如,之前为了快速开发,rms模块,我们采用了直接访问数据库的方式,对于rms模块本身来说,没有什么问题. 但是,在game模块中,对于频繁访问的.不经常改变的数据或接口,希望采用缓存的方式,将数据缓存起来,减少后端压力,同时加快响应速度,从而提升体验. 之前rms模块中尝试

从零开始实现放置游戏(六)——实现挂机战斗(4)导入Excel数值配置

前面我们已经实现了在后台管理系统中,对配置数据的增删查改.但每次添加只能添加一条数据,实际生产中,大量数据通过手工一条一条添加不太现实.本章我们就实现通过Excel导入配置数据的功能.这里我们还是以地图数据为例,其他配置项可参照此例. 涉及的功能点主要有对office文档的编程.文件上传功能.流程图大致如下: 一.添加依赖项 解析office文档推荐使用免费的开源组件POI,已经可以满足80%的功能需求.上传文件需要依赖commons-fileupload包.我们在pom中添加下列代码: <!-

从零开始实现放置游戏(九)——实现后台管理系统(7)地图选择控件

前面做了地图怪物的添加,删除,查询等功能.但添加怪物的时候,需要选择怪物所在地图.前几张的源代码中,我忘了把这部分改回去,所以如果想要成功添加,需要自己改一下html界面,手动填写怪物所在地图的ID.然而,我们配置的时候,地图ID并不是固定的,而是数据库自增的.所以这里最好做成一个弹窗,点击后弹出一个地图列表,让我们手动选择怪物所在地图. 本章我们就实现这样一个弹窗控件,实现对地图的选择.后面如果有选择怪物,选择装备等需求,都可照猫画虎.整个过程的流程大致如下: 实现步骤 首先,我们给弹出的地图

GameBuilder开发游戏应用系列之100行代码实现贪吃蛇

在线预览:http://osgames.duapp.com/apprun.html?appid=osgames1-801422234293697 在线编辑:http://osgames.duapp.com/gamebuilder.php?appid=osgames1-801422234293697 微信扫描: 运行截图: 除了重力感应游戏,GameBuilder开发传统的游戏也毫不逊色,作为一个怀旧的人,总是对这类游戏情有独钟. 贪吃蛇主要靠一个[UICanvas]来实现,前面一片博客GameB

GameBuilder开发游戏应用系列之70行代码实现套圆环

在线预览:http://osgames.duapp.com/apprun.html?appid=osgames1-551421721381542 在线编辑:http://osgames.duapp.com/gamebuilder.php?appid=osgames1-551421721381542 微信扫描: 运行截图: 套圆环是火舞上的一款非常火爆的游戏,它是FlappyBird的变种,也是重力感应类的游戏,不同的是碰撞检测从FlappyBird的钢管,变成中间的一根绳子. 这根绳子在Game

Cocos2D iOS之旅:如何写一个敲地鼠游戏(八):为动画建立属性列表

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流之用,请勿进行商业用途.同时,转载时不要移除本申明.如产生任何纠纷,均与本博客所有人.发表该翻译稿之人无任何关系.谢谢合作! 在之前的博文中,我们创建了游戏的基础 - 小可爱的地鼠从洞中呼之欲出.我们花了大量的时间思考关于如何组织素材以及坐标置位,以让我们的游戏在iphone,ipad以及高清屏上看

(NO.00005)iOS实现炸弹人游戏(八):游戏主角(一)

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 最近一直在做另一个RPG游戏,所以本系列迟迟没有更新,上一篇博文的地址在: (NO.00005)iOS实现炸弹人游戏(七):游戏数据的序列化表示 本篇接着上篇介绍炸弹人游戏中的游戏主角的基本构成,游戏主角自然是我们的炸弹人了. 因为主角会在游戏场景中各个方向行走,所以我们需要先准备其对应的图片资源: 如各位所见除了普通的移动动作,我还选择了主角被人道毁灭和无敌

C#程序员的春天之从零开始学习unity3D游戏开发入门教程一(前言)

我们不在“拖控件”,我们也有春天 序言:之前断断续续谢了一点unity的教程,但是不全面.这里表示歉意.今天准备重新做一套.今天也是换了新工作,也是新的开始.也希望以此来激励自己把这个教程一直写下去.想要学习unity的同学可以加这个群(326535328),可能会在qq课堂在线讲解一些相关知识. 乱扯一下:为什么说这时C#程序员的春天呢?在大多数java或者其他程序员的眼里,我们.net平台的程序员同胞都背负着”拖控件“的这么一个标签.某种程度上讲,.net的快速入门的精髓就是拖控件.然而外人

Java从零开始学二十八(Math类和Random类)

一.Math概述 提供了常用的数学运算方法和两个静态常量E(自然对数的底数)和PI(圆周率) 二.常用方法 package com.pb.demo1; public class MathTest { public static void main(String[] args) { System.out.println("求平方根:" + Math.sqrt(9.0)); System.out.println("求两数的最大值:" + Math.max(10,30))