浅谈STG游戏的开发(4月8日更新,已补全内容)

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

PS:从根本上讲,弹幕游戏本就归属于STG,或者说也仅仅是种STG罢了。因此,本文也可以视作在介绍LGame中任意STG类游戏的基本开发。

通常我们所谓的弹幕,词源来自英文的【barrage】,本来是指英国陆军在1915年一战期间,采取的特种战术名称。该战术宗旨为,无差别的不间断发射弹药,组成上天下地的半圆形交织火力网,消灭攻击范围内可能存在的一切敌人。(不过因为弹药消耗过巨,兼之敌我不分,此战术在一战后就不再使用。小弟额外一提,目前各国研发的【金属风暴】系统其实都和它有异曲同工之妙,不过定点性更强了,因此也有人管该系统叫【弹幕墙】)


但具体到中文的【弹幕】二字,则和【达人】【素颜】之类一样,都源于东瀛日本的平假名名词(当然,因为日本文化源自汉唐,所以也可以视为汉文化的回流~)。因此,目前市面上比较流行的具体弹幕类游戏,如东方系列,如虫姬系列,如式神之城系列,又如三国战记系列,都是日式弹幕游戏,他们大多都具有一定程度上的AVG式剧情交互,并且单纯的射击而已。

而一般来说,我们开发游戏的最终目地,是让游戏者玩的开心高兴,并非打击他们的游戏乐趣,更不是想要彻底杀死他们(^^)。

所以在开发弹幕类游戏时,我们就不能,也不可能像军用的【barrage】系统一样真不给人半点生机;相反的,我们还要让玩家【有机可乘】,能够不被那么轻易的消灭掉才行。故而绝大多数的弹幕类游戏,都结合有“射击”与“闪避”两大游戏要素。即要让玩家“在敌人放出的大量子弹(弹幕)的细小空隙间闪避”,“并且能够予以敌人猛烈的还击”,这样才能给以玩家弹幕时的独特快感。

因此,对绝大多数的弹幕游戏而言,通常会有以下共性存在:


1、敌人的子弹速度比普通的射击游戏的慢很多。

2、大量的敌弹会以一定的算法有规则地射出,往往在画面上排出几何形状,必定有空隙存在。

3、敌弹的攻击判定和自机的被击中判定比眼见的小很多,不那么容易射中我方目标。

4、有可能突然减慢,或突然增加自机速度,使精密的避弹更容易操作。

当然,弹幕类游戏中敌方的子弹,毕竟会比普通的射击游戏密集许多,所以不论敌弹判定还是自机被击中判定时有多“放水”,弹幕游戏的难度,也大多会比一般的射击类游戏更高些。

至于小弟下面例举的具体弹幕示例源码,则直接采用LGame自带的STG扩展包开发,所以在默认状态下已经支持了触屏与键盘操作(只要继承了STGHero类,我们的主角机就能够完成相关操作),以及基本的角色碰撞检查。所以,大家并不需要再关注什么额外的代码设定,仅仅理解下所谓弹幕,也不过是一堆形状在屏幕上做自定义碰撞,就足够了。


在STG扩展包中,继承了Screen类的STGScreen,用以显示游戏画面。而STGObject这个对象,则代表了全部的屏幕中机体(包括子弹,敌人,我方,物品等),至于区别它们关系的,则是预先定义好的,位于STGScreen类中的9种弹幕对象属性,即:

//主角public static final int HERO = 0;//主角子弹public static final int HERO_SHOT = 1;//敌人public static final int ENEMY = 2;//敌人子弹public static final int ENEMY_SHOT = 3;//该物体无法命中目标(纯漂过,不和任何对象发生作用)public static final int NO_HIT = 4;//物品public static final int ITEM = 5;//必须取得的物品(也就是任务物品,预留区域)public static final int GET_ITEM = 6;//自杀(与此物体碰撞即宣布游戏失败,预留区域)public static final int SUICIDE = 7;//全部命中,不分敌我public static final int ALL_HIT = 8;


我们可以通过变更STGObject类的attribute参数,修正当前角色的作用。而STGScreen对象,则可以在所有的STGObject对象中调取stg变量获得,该对象对应着作为主窗体的STGScreen,我们可以通过该对象为中介,获得多个子类间的相互合作与调配。

下面小弟将例举一些实际的代码例子。

请注意,所有的角色类都是STGObject对象的衍生。所谓敌机,我方机体,或者弹药等等,不过是继承了STGObject类的对象,设定了不同的attribute参数而已。所以从本质上看,他们都是一种东西。

设定一个主角类:
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.action.map.Config;import org.loon.framework.javase.game.core.graphics.LColor;import org.loon.framework.javase.game.stg.STGHero;import org.loon.framework.javase.game.stg.STGScreen;public class Hero1 extends STGHero {    public Hero1(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        // 设定主角生命值(被击中60次后死亡)        this.setHP(60);        // 设定主角魔法值        this.setMP(60);        // 设定自身动画(第一项参数为动画顺序,第二项参数为对应的图像索引)        this.setPlaneBitmap(0, 0);        // 如果设定有多个setPlaneBitmap,可开启此函数,以完成动画播放        // setPlaneAnime(true);        // 设定动画延迟        // setPlaneAnimeDelay(delay);        // 设定自身位置        this.setLocation(x, y);        // 旋转图像为指定角度        // setPlaneAngle(90);        // 变更图像为指定色彩        // setPlaneBitmapColor(LColor.red);        // 变更图像大小        // setPlaneSize(w, h);        // 显示图像        this.setPlaneView(true);        // 设定子弹用类        this.setHeroShot("Shot1");        // 设定自身受伤用类        // this.setDamagedEffect("D1");        this.setHitW(32);        this.setHitH(32);    }    public void onShot() {    }    public void onDamaged() {        this.setPlaneBitmapColor(LColor.red);    }    public void onMove() {        this.setPlaneBitmapColor(LColor.white);        // stg对象即当前的当前STGScreen,所有子类都可以调取到这个对象。通过此对象为中介,        // 我们获得STGScreen状态,也可以 获得多个子类间的相互合作与调配。        // 根据角色所朝向的方向,变更角色图        switch (stg.getHeroTouch().getDirection()) {        case Config.LEFT:        case Config.TLEFT:            setPlaneBitmap(0, 1);            break;        case Config.RIGHT:        case Config.TRIGHT:            setPlaneBitmap(0, 2);            break;        default:            setPlaneBitmap(0, 0);            break;        }    }}

设定对应的主角机子弹类:
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGScreen;import org.loon.framework.javase.game.stg.shot.HeroShot;public class Shot1 extends HeroShot {    public Shot1(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        //下列两参数为命中点偏移        hitX = hitY = 2;        //设定角色图像索引        setPlaneBitmap(0, 3);        setLocation(x + 14, y);        //设定角色大小(如不设定,直接视为图像大小)        setHitW(15);        setHitH(15);    }}

设定一个最基本的敌人类(直接继承现有的敌兵类):
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGScreen;import org.loon.framework.javase.game.stg.enemy.EnemyOne;public class MoveEnemy extends EnemyOne {    public MoveEnemy(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        //使用图像索引5(对应图像的注入顺序)        setPlaneBitmap(0, 5);        //坐标位于脚本导入的坐标        setLocation(x, y);    }    public void onExplosion() {            }}

然后以最简单的方式,进行如下脚本操作:
//设定反射用包package org.loon.framework.javase.game.stg.test//加载主角类leader Hero1 166 266//加载敌人enemy MoveEnemy 55 20//延迟20豪秒进行下一步操作sleep 20enemy MoveEnemy 75 20sleep 20enemy MoveEnemy 85 20sleep 20

屏幕上就会得到这样的显示,这时已经可以进行最基本的游戏了。


事实上,如果以STG包开发弹幕游戏,那么我们真正需要关心的,仅仅是子弹、我方机体、敌方机体的行走算法,也就是如何让它们以尽量绚丽多彩移动的形式展现在用户眼前,而无需介怀其他什么。比如,大家可能都感觉到上图中敌人的直线运动太单调了,那么下面我们设定一个新类,并命名为BeeEnemy,然后做如下设定。
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGObject;import org.loon.framework.javase.game.stg.STGScreen;import org.loon.framework.javase.game.stg.enemy.EnemyOne;import org.loon.framework.javase.game.utils.MathUtils;public class BeeEnemy extends EnemyOne {    public BeeEnemy(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        this.setPlaneBitmap(0, 8);        this.setPlaneBitmap(1, 9);        this.setPlaneBitmap(2, 10);        this.setPlaneBitmap(3, 11);        this.setPlaneBitmap(4, 12);        this.setPlaneAnime(true);        this.setLocation(x, y);        //死亡延迟时间为0,即命中足够次数后立刻消失        this.setDieSleep(0);        //移动速度3        this.speed = 3;        //命中三次后,敌人消失        this.hitPoint = 3;    }    public float distance(float x1, float y1, float x2, float y2) {        x1 -= x2;        y1 -= y2;        return MathUtils.sqrt(x1 * x1 + y1 * y1);    }    private int c;    public void update() {        super.update();        if (getY() >= 50) {            if (c == 0) {                for (int i = 0; i < 360; i += 30) {                    float rad = 2 * MathUtils.PI * ((float) i / 360);                    STGObject bow = newPlane("BeeShot", getX() + 18,                            getY() + 32, targetPlnNo);                    bow.offsetX = MathUtils.cos(rad);                    bow.offsetY = MathUtils.sin(rad);                }            }            ++c;            c %= 150;        }    }    //如果敌人角色死后,将自动执行此函数    public void onExplosion() {    }}

再给它添加一种专用子弹。
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.core.LSystem;import org.loon.framework.javase.game.stg.STGObject;import org.loon.framework.javase.game.stg.STGScreen;//请注意,该类直接继承的STGObjectpublic class BeeShot extends STGObject {    public BeeShot(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        //设定对象属性为敌方子弹        this.attribute = STGScreen.ENEMY_SHOT;        //图像索引为7        setPlaneBitmap(0, 7);        setLocation(x, y);        hitX = hitY = 1;    }    public void update() {        //每次移动时,按照偏移值的数值进行操作        move(offsetX, offsetY);        //如果角色被命中(就子弹来讲,也意味着命中目标),或者超出屏幕        if (hitFlag || !LSystem.screenRect.contains(getX(), getY())) {            //删除当前角色            delete();        }    }}

然后我们修改脚本,多增加一些操作:
//设定反射用包package org.loon.framework.javase.game.stg.test//加载主角类leader Hero1 166 266//加载敌人enemy MoveEnemy 55 20//延迟20豪秒进行下一步操作sleep 20enemy MoveEnemy 75 20sleep 20enemy MoveEnemy 85 20sleep 20begin actionsleep 15enemy BeeEnemy 57 0sleep 15enemy BeeEnemy 59 0sleep 50enemy BeeEnemy 155 0sleep 50enemy BeeEnemy 155 0sleep 50enemy BeeEnemy 155 0sleep 50enemy BeeEnemy 155 0sleep 50enemy BeeEnemy 155 0sleep 50enemy BeeEnemy 155 0endcall actioncall action

就会得到如下图所示的游戏效果,子弹呈圆形喷射而出。



最后,我们还可以添加新类作为Boss:


package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGScreen;import org.loon.framework.javase.game.stg.enemy.EnemyMidle;public class Boss1 extends EnemyMidle {    public Boss1(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        setPlaneBitmap(0, 6);        setLocation((getScreenWidth() - getWidth()) / 2, y);        setView(true);        setHitPoint(60);    }    public void onExplosion() {    }    public void onEffectOne() {    }    int count;    public void onEffectTwo() {        count++;        if (count % 5 == 0) {            addClass("BossShot1", getX() + 32, getY() + 90, super.targetPlnNo);        }        if (count % 6 == 0) {            addClass("BossShot1", getX() + 45, getY() + 90, super.targetPlnNo);        }        if (count % 10 == 0) {            addClass("BossShot2", getX() + 32, getY() + 90, super.targetPlnNo);        }        if (count > 20){            count = 0;        }    }}

然后为它添加两种弹药,一种是自定义的,一种是继承自默认子弹类的:
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGObject;import org.loon.framework.javase.game.stg.STGScreen;public class BossShot1 extends STGObject {    public BossShot1(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        super.attribute = STGScreen.ENEMY_SHOT;        setPlaneBitmap(0, 7);        setLocation(x, y);        hitX = hitY = 1;    }    public void update() {        move(0, 12);        if (getY() > stg.getHeight()) {            delete();        }    }}
package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.stg.STGScreen;import org.loon.framework.javase.game.stg.shot.MoonShot;public class BossShot2 extends MoonShot{    public BossShot2(STGScreen stg, int no, float x, float y, int tpno) {        super(stg, no, x, y, tpno);        setPlaneBitmap(0, 13);        setPlaneBitmap(1, 14);        setPlaneBitmap(2, 15);        setPlaneBitmap(3, 16);        setPlaneAnime(true);    }}

这时在屏幕上我们就可以和Boss开打了,效果图如下所示:


package org.loon.framework.javase.game.stg.test;import org.loon.framework.javase.game.GameScene;import org.loon.framework.javase.game.core.graphics.LColor;import org.loon.framework.javase.game.core.graphics.component.LButton;import org.loon.framework.javase.game.core.graphics.opengl.GLEx;import org.loon.framework.javase.game.core.input.LInputFactory.Key;import org.loon.framework.javase.game.core.input.LTouch;import org.loon.framework.javase.game.core.input.LTransition;import org.loon.framework.javase.game.stg.STGScreen;public class Test extends STGScreen {    public LTransition onTransition() {        return LTransition.newEmpty();    }    public Test(String path) {        // 需要读取的脚本文件        super(path);    }    public void loadDrawable(DrawableVisit bitmap) {        // 注入图像到STGScreen(内部会形成单幅纹理,ID即插入顺序)        bitmap.add("assets/hero0.png");        bitmap.add("assets/hero1.png");        bitmap.add("assets/hero2.png");        bitmap.add("assets/shot.png");        bitmap.add("assets/boom.png");        bitmap.add("assets/ghost.png");        bitmap.add("assets/boss.png");        bitmap.add("assets/greenfire.png");        bitmap.add("assets/bee.png", 48, 48);        bitmap.add("assets/moon1.png");        bitmap.add("assets/moon2.png");        bitmap.add("assets/moon3.png");        bitmap.add("assets/moon4.png");        // 设定背景为星空图(绘制产生)        setStarModeBackground(LColor.white);        // 设定滚屏背景图片        // setScrollModeBackground("assets/background.png");        // 设定无背景(无设定时默认为此)        // setNotBackground();    }    /**     * 游戏脚本监听(返回true时强制中断脚本,也可于此自定义游戏脚本)     */    public boolean onCommandAction(String cmd) {        return false;    }    /**     * 指定的图像ID监听(用于渲染指定ID对应的图像)     */    public boolean onDrawPlane(GLEx g, int id) {        return false;    }    /**     * 游戏主循环(位于循环线程中)     */    public void onGameLoop() {    }    /**     * 当脚本读取完毕时,将触发此函数     */    public void onCommandAchieve() {    }    /**     * 当主角死亡时     */    public void onHeroDeath() {        System.out.println("over");    }    /**     * 当敌兵被清空时     */    public void onEnemyClear() {    }    public void onLoading() {        LButton btn = new LButton("assets/button.png") {            public void downClick() {                setKeyDown(Key.ENTER);            }            public void upClick() {                setKeyUp(Key.ENTER);            }        };        bottomOn(btn);        btn.setLocation(getWidth() - btn.getHeight() - 25, btn.getY() - 25);        add(btn);        // 禁止此按钮影响STG触屏事件        addTouchLimit(btn);    }    public void onDown(LTouch e) {    }    public void onDrag(LTouch e) {    }    public void onMove(LTouch e) {    }    public void onUp(LTouch e) {    }    public void update(long elapsedTime) {    }    public static void main(String[] args) {        GameScene game = new GameScene("弹幕测试", 320, 480);        game.setShowFPS(true);        game.setShowLogo(false);        game.setScreen(new Test("assets/stage1.txt"));        game.showScreen();    }}

而且同样的代码放到Android版中照旧通行,效果如下图所示(横屏竖屏也无所谓):



小弟在SVN的更新中发了两个版本(直接下LGame-0.3.3-Beta即可,解包可见),一个标准Java的,一个Android的,源码基本一致,所以不再赘述。(话说小弟C#版也写了,不过都发比较占空间,等发LGame正式版时再说了……)

另外,使用STG包之所以在图像资源使用上稍微麻烦一点,是因为它们在内部都被LGame压成了单独的纹理,以保证弹幕速度。等后期小弟提供IDE时,配置上就没这么繁琐了。(本来就有两种图像资源加载方式,一种是直接填文件名,一种是读xml配置,,只不过后者暂无工具不太好做~)

http://loon-simple.googlecode.com/svn/trunk
_____________

说点题外话,小弟发现国漫还是很有希望的,比如小弟刚看了两集《圣斗士星矢Ω》,就感觉这货已经越来越接近国漫……前两天看完《屌丝女士》系列再次剧荒,目前值得期待的,就剩下《神秘博士》与《SPEC 天》了……



           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

猜你喜欢

转载自blog.csdn.net/aabbyyz/article/details/84038798