Django_3

游戏界面实现

修改js模块化

game/templates/multiends/web.html
在这里插入图片描述
并且删除<head></head>里面的AcGame
game/static/js/src/zbase.js
class前加上export即可完成,再重新打包

增加界面样式

game/static/css/game.css

.ac-game-playground{                                              
   width:100%;                                                   
   height:100%;                                                  
   user-select:none;                                             
  }                       

在game/static/js/src/playground中先存下界面的长和宽

this.width=this.$playground.width();                       this.weight=this.$playground.height();  

实现简易游戏引擎

在game/static/js/src/playground创建ac_game_object文件夹,并在文件夹内创建zbase.js

let AC_GAME_OBJECTS=[];                                                                                                
 class AcGameObject{                                              
constructor(){                                                   
 AC_GAME_OBJECTS.push(this);                                                                                            
  this.has_called_start=false;  //是否执行过start函数       
  this.timedelta=0;  //当前帧距离上一帧的时间间隔           
   }                                                                                                                               
     start(){  //只会在第一帧执行  
              }                                                                                                                                 
               update(){  //每一帧会执行一次        
                  }                                                                                                                              
                   on_destory(){  //被删除之前执行一次            
               }                                                                                                                               
                destory(){  //删掉该物体                         
                                                                                                 
                                                                                                  
this.on_destory();             
       for(let i=0;i<AC_GAME_OBJECTS.length;i++){             
       if(AC_GAME_OBJECTS[i] === this){                       
         AC_GAME_OBJECTS.splice(i,1);           
                            break;                               
                       }              
                  }                                
            }                     
        }                                                                                                                                                                                                 
 let AC_GAME_ANIMATION=function(timestamp){                                                                                    
 for(let i = 0;i<AC_GAME_OBJECTS.length;i++){                    
         let obj=AC_GAME_OBJECTS[i];         
  if(!obj.has_called_start){                                 
   obj.start();                                               
    obj.has_called_start=true;                              
     }else{                                                         
        obj.timedelta=timestamp-last_timestamp;                
        obj.update();                                                
         }                                                          
       }                                                              
   last_timestamp=timestamp;                                                                                                  requestAnimationFrame(AC_GAME_ANIMATION);                  
    }                                                                                                                                                                                     requestAnimationFrame(AC_GAME_ANIMATION);    

在game/static/js/src/playground/创建game_map文件夹,并在文件夹内创建zbase.js

       class GameMap extends AcGameObject{
  2     constructor(playground){
  3         super();
  4         this.playground =playground;
  5         this.$canvas=$(`<canvas></canvas>`);
  6         this.ctx=this.$canvas[0].getContext('2d');
  7         this.ctx.canvas.width=this.playground.width;
  8         this.ctx.canvas.height=this.playground.height;
  9         this.playground.$playground.append(this.$canvas);
 10     }
 11 
 12     start(){
 13     }
 14 
 15     update(){
 16         this.render();
 17     }
 18 
 19     render(){
 20         this.ctx.fillStyle="rgba(0,0,0,0.2)";
 21         this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
 22     }
 23 }                                               

创建人物

class Player extends AcGameObject{                                                                                               
  2     constructor(playground,x,y,radius,color,speed,is_me){                                                                       
  3         super();                                                                                                                     
  4         this.playground=playground;                                                                                            
  5         this.ctx=this.playground.game_map.ctx;                                                                                   
  6         this.x=x;                                                                                                               
  7         this.y=y;     
  			this.vx=0;
  			this.vy=0;
  			this.move_length=0;                                                                                                              
  8         this.radius=radius;                                                                                                   
  9         this.color=color;                                                                                              
 10         this.speed=speed;                                                                            
 11         this.is_me=is_me;                                                                                         
 12         this.eps=0.1;                                                                                                
 13     }                                                                                                         
 16                                                                                                                       
 17     start(){                                                                                                            
 18     if(this.is_me){                                                                                                   
 19         this.add_listening_events();                                                                              
 20         }                                                                                                      
 21     }                                                                                                      
 22     add_listening_events(){                                                                                 
 23         let outer=this;                                                                                         
 24   this.playground.game_map.$canvas.on("contextmenu",function(){                                                                                                                              
 25             return false;                                                                                                    
 26      });                                                                                                                          
 27        this.playground.game_map.$canvas.mousedown(function(e){                                                             
 28             if(e.which === 3){   
   29                 outer.move_to(e.clientX,e.clientY);                                                                            
 30             }                                                                                                                  
 31         });                                                                                                                    
 32     }                                                                                                                          
 33                                                                                                                                  
 34     get_dist(x1,y1,x2,y2){                                                                                                       
 35         let dx=x1-x2;                                                                                                                 
 36         let dy=y1-y2;                                                                                                              anvas
 37         return Math.sqrt(dx*dx+dy*dy);                                                                                             
 38                                                                                                                                    
 39     }                                                                                                                               
 40                                                                                                                                        
 41        
 42     move_to(tx,ty){                                                                                                                 
 43         console.log("move to",this.x,this.y,tx,t                                                                                      
    y);                                                                                                                                  
 44         this.move_length=this.get_dist(this.x,th                                                                                    
    is.y,tx,ty);                                                                                                                     
 45         let angle=Math.atan2(ty-this.y,tx-this.x);         
 46         this.vx=Math.cos(angle);                                                                                                    
 47         this.vy=Math.sin(angle);                                                                                                    
 48         console.log("angle",this.move_length,this.vx,this.vy);                                                                                                                         
 49     }  
 50                                                                                                                             
 51     update(){                                                                                                                
 52         if(this.move_length<this.eps){                                             
 53             this.move_length=0;                                                
 54             this.vx=this.vy=0;                                                
 55         }else{                                                       
 56             let moved=Math.min(this.move_length,this.speed*this.timedelta/1000);                                         
 57             //console.log(this.angle,this.move_length,this.speed,this.timedelta/1000);                                                                                       
 58             this.x+=this.vx*moved;                                                                                          
 59             this.y+=this.vy*moved;                                                                                              
 60             this.move_length-=moved;      
 61                                                                                                                            
 62         }                                                                                                                   
 63     this.render();                                                      
 64     }                                                                                                                                              
 22     render(){                                                                                                                         
 23     this.ctx.beginPath();                                                                                                             
 24     this.ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,false);                                                                       
 25     this.ctx.fillStyle=this.color;                                                                                                   
 26     this.ctx.fill();  

在AcGamePlayground中加入:
在这里插入图片描述

创建火球类

 1 class FireBall extends AcGameObject{
 2     constructor(playground,player,x,y,radius,vx,vy,color,speed,move_length){
 3         super();
 4         this.playground=playground;
 5         this.player=player;
 6         this.ctx=this.playground.game_map.ctx;
 7         this.x=x;
 8         this.y=y; 
 9         this.vx=vx;
10         this.vy=vy;
11         this.color=color;
12         this.speed=speed;
13         this.move_length=move_length;
14         this.eps=0.1;
15     }
16     start(){
17     }   
18     update(){
19         if(this.move_length<this.eps){                                                           
20             this.destroy();
21             return false;
22         }
23         let moved=Math.min(this.move_length,this.speed*this.timedelta/1000);
24         this.x+=this.vx*moved;
25         this.y+=this.vy*moved;
26         this.move_length-=moved;
27 
28         this.render();
29 
30     }
31 
32     render(){         
33         this.ctx.beginPath();
34         this.ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,false);
35         this.ctx.fillStyle=this.color;
36         this.ctx.fill();
37     }
38 }      

在player中实现发射火球

constructor(...)
{
            ...
    this.cur_skill = null; // 当前选中的技能
            ...
}

add_listening_events()
{
    ...

    this.playground.game_map.$canvas.mousedown(function(e){
        ...
        else if (ee === 1)
        {
            if (outer.cur_skill === "fireball") // 当前技能是火球就发射
            {
                outer.shoot_fireball(e.clientX, e.clientY);
                return false;
            }
            outer.cur_skill = null; // 点击之后就得清空
        }
    });

    ...

    $(window).keydown(function(e){
        if (!outer.is_alive) return false;
        let ee = e.which;
        if (ee === 81) // Q的keycode是81,其他keycode可以自行查阅
        {
            outer.cur_skill = "fireball"; // 技能选为fireball
            return false;
        }    
    });

    ...
}

shoot_fireball(tx, ty)
{
    console.log(tx, ty); // 测试用
    // 以下部分在测试成功之后再写入
    let x = this.x, y = this.y;
    let radius = this.playground.height * 0.01; // 半径
    let color = "orange"; // 颜色
    let damage = this.playground.height * 0.01; // 伤害值

    let angle = Math.atan2(ty - this.y, tx - this.x); // 角度
    let vx = Math.cos(angle), vy = Math.sin(angle); // 方向
    let speed = this.playground.height * 0.5; // 速度
    let move_dist = this.playground.height * 1; // 射程

    new FireBall(this.playground, this, x, y, radius, color, damage, vx, vy, speed, move_dist);

随机生成其他敌人

constructor()
{
    ...

    for (let i = 0; i < 5; ++ i)//随机生成5个敌人
    {
        this.players.push(new Player(this, this.width / 2, this.height / 2, this.height * 0.05, GET_RANDOM_COLOR(), false, this.height * 0.15));    
    }
}

修改player:

update()
{
    this.update_AI();

    ...
}

update_AI()
{
    if (this.is_me) return false; // 如果这不是一个机器人就直接退出

    this.update_AI_move();
}

update_AI_move()
{
    if (this.move_length < EPS) // 如果停下来就随机选个地方走向那边
    {
        let tx = Math.random() * this.playground.width;
        let ty = Math.random() * this.playground.height;

        this.move_to(tx, ty);
    }
}

实现碰撞

let is_collision = function(obj1, obj2) // 这是一个全局函数,代表两个物体之间是否碰撞
{
    return GET_DIST(obj1.x, obj1.y, obj2.x, obj2.y) < obj1.radius + obj2.radius; // 很简单的两圆相交条件
}

is_satisfy_collision(obj) // 真的碰撞的条件
{
    if (this === obj) return false; // 自身不会被攻击
    if (this.player === obj) return false; // 发射源不会被攻击
    return IS_COLLISION(this, obj); // 距离是否满足
}

hit(obj) // 碰撞
{
    obj.is_attacked(this); // obj被this攻击了
    this.is_attacked(obj); // this被obj攻击了
}

is_attacked(obj) // 被伤害
{
    this.is_attacked_concrete(0, 0); // 具体被伤害多少,火球不需要关注伤害值和血量,因为碰到后就直接消失
}

is_attacked_concrete(angle, damage) // 具体被伤害
{
    this.destroy(); // 直接消失
}

update()
{
    this.update_attack();
    ...
}

update_attack()
{
    for (let i = 0; i < AC_GAME_OBJECTS.length; ++ i)
    {
        let obj = AC_GAME_OBJECTS[i];
        if (this.is_satisfy_collision(obj)) // 如果真的碰撞了(这样可以保证碰撞条件可以自行定义,以后会很好维护)
        {
            this.hit(this, obj); // 两个物体碰撞了
            break; // 火球,只能碰到一个物体
        }
    }
}


is_attacked(obj)
{
    let angle = Math.atan2(this.y - obj.y, this.x - obj.x); // 角度
    let damage = obj.damage; // 伤害
    // 注意,这里被伤害之后的表现,就是什么方向碰撞就是什么伤害,简单的向量方向计算
    this.is_attacked_concrete(angle, damage);
}

is_attacked_concrete(angle, damage) // 被具体伤害
{
    this.radius -= damage; // 这里半径就是血量
    this.friction_damage = 0.8; // 击退移动摩擦力

    if (this.is_died()) return false; // 已经去世了吗

    this.x_damage = Math.cos(angle);
    this.y_damage = Math.sin(angle); // (x_damage, y_damage)是伤害向量的方向向量
    this.speed_damage = damage * 100; // 击退速度
}

is_died()
{
    if (this.radius < EPS * 10) // 少于这个数表示已经去世
    {
        this.destroy(); // 去世
        return true;
    }
    return false;
}

update_move()
{
    if (this.speed_damage && this.speed_damage > EPS) // 如果此时在被击退的状态,就不能自己动
    {
        this.vx = this.vy = 0; // 不能自己动
        this.move_length = 0; // 不能自己动
        this.x += this.x_damage * this.speed_damage * this.timedelta / 1000; // 被击退的移动
        this.y += this.y_damage * this.speed_damage * this.timedelta / 1000; // 被击退的移动
        this.speed_damage *= this.friction_damage; // 摩擦力,表现出一个被击退越来越慢的效果
    }
    ...
}


实现动态效果

// 这里面很多过程都是前面写过的,借这个机会努力回想一下。

class Particle extends AcGameObject
{
    constructor(playground, x, y, radius, color, vx, vy, speed)
    {
        super();
        this.playground = playground;
        this.ctx = this.playground.game_map.ctx;
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.color = color;

        this.vx = vx;
        this.vy = vy;
        this.speed = speed;
    }

    render()
    {
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        this.fillStyle = this.color;
        this.fill();
    }

    start()
    {
        this.friction_speed = 0.8;
        this.friction_radius = 0.8;
    }

    update()
    {
        this.update_move();
        this.render();
    }

    update_move()
    {
        if (this.speed < EPS * 10 || this.radius < EPS * 10)
        {
            this.destroy();
            return false;
        }

        this.x += this.vx * this.speed * this.timedelta / 1000;
        this.y += this.vy * this.speed * this.timedelta / 1000;

        this.speed *= this.friction_speed;
        this.radius *= this.friction_radius;
    }

}

修改后的Player:

class Player extends AcGameObject
{
    constructor(playground, x, y, radius, color, is_me, speed)
    {
        super(true);

        this.playground = playground; // 所属playground
        this.ctx = this.playground.game_map.ctx; // 操作的画笔

        this.x = x;  // 坐标
        this.y = y; // 坐标
        this.radius = radius; // 半径
        this.color = color; // 颜色
        this.is_me = is_me; // 玩家类型

        this.speed = speed; // 速度
        this.is_alive = true; // 是否存活

        this.eps = 0.1; // 精度,这里建议定义为全局变量,EPS = 0.1,在这个教程里以后都这么用。

        this.cur_skill = null; // 当前选中的技能
    }

    add_listening_events()
    {
        let outer = this; // 设置正确的this指针,因为接下来的后面的function内的this不是对象本身的this
        this.playground.game_map.$canvas.on("contextmenu", function(){ // 关闭画布上的鼠标监听右键
            return false;
        });

        this.playground.game_map.$canvas.mousedown(function(e){ // 鼠标监听
            if (!this.is_alive) return false;

            let ee = e.which; // e.which就是点击的键对应的值
            if (ee === 3) // 右键
            {
                outer.move_to(e.clientX, e.clientY); // e.clientX是鼠标的x坐标,e.clientY同理
            }
            else if (ee === 1)
            {
                if (outer.cur_skill === "fireball")
                {
                    outer.shoot_fireball(e.clientX, e.clientY);
                    return false;
                }
                outer.cur_skill = null; // 点击之后就得清空
            }
        });

        $(window).keydown(function(e){
            if (!this.is_alive) return false;
            let ee = e.which;
            if (ee === 81) // Q的keycode是81,其他keycode可以自行查阅
            {
                outer.cur_skill = "fireball"; // 技能选为fireball
                return false;
            }    
        });
    }

    render()
    {
        // 画圆的方法,请照抄,深入了解同样自行查阅菜鸟教程
        this.ctx.beginPath();
        this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
        this.ctx.fillStyle = this.color;
        this.ctx.fill();
    }

    move_to(tx, ty)
    {
        this.move_length = GET_DIST(this.x, this.y, tx, ty); // 跟目的地的距离
        let dx = tx - this.x, dy = ty - this.y; 
        let angle = Math.atan2(dy, dx); // 计算角度,这里Math.atan2(y, x)相当于求arctan(y / x);

        this.vx = Math.cos(angle); // vx是这个速度(单位向量)的x上的速度(学过向量的都明白)
        this.vy = Math.sin(angle); // vy是这个速度的y上的速度
    }

    shoot_fireball(tx, ty)
    {
        console.log(tx, ty); // 测试用
        // 以下部分在测试成功之后再写入
        let x = this.x, y = this.y;
        let radius = this.playground.height * 0.01; // 半径
        let color = "orange"; // 颜色
        let damage = this.playground.height * 0.01; // 伤害值

        let angle = Math.atan2(ty - this.y, tx - this.x); // 角度
        let vx = Math.cos(angle), vy = Math.sin(angle); // 方向
        let speed = this.playground.height * 0.5; // 速度
        let move_dist = this.playground.height * 1; // 射程

        new FireBall(this.playground, this, x, y, radius, color, damage, vx, vy, speed, move_dist);
    }

    is_attacked(obj)
    {
        let angle = Math.atan2(this.y - obj.y, this.x - obj.x); // 角度
        let damage = obj.damage; // 伤害
        // 注意,这里被伤害之后的表现,就是什么方向碰撞就是什么伤害,简单的向量方向计算   
        this.is_attacked_concrete(angle, damage);
    }

    is_attacked_concrete(angle, damage) // 被具体伤害
    {
        this.explode_particle();

        this.radius -= damage; // 这里半径就是血量
        this.friction_damage = 0.8; // 击退移动摩擦力

        if (this.is_died()) return false; // 已经去世了吗

        this.x_damage = Math.cos(angle);
        this.y_damage = Math.sin(angle); // (x_damage, y_damage)是伤害向量的方向向量
        this.speed_damage = damage * 100; // 击退速度
    }

    explode_particle()
    {
        for (let i = 0; i < 10 + Math.random() * 5; ++ i) // 粒子数
        {
            let x = this.x, y = this.y;
            let radius = this.radius / 3;
            let angle = Math.PI * 2 * Math.random(); // 随机方向
            let vx = Math.cos(angle), vy = Math.sin(angle);
            let color = this.color;
            let speed = this.speed * 10;

            new Particle(this.playground, x, y, radius, color, vx, vy, speed); // 创建粒子对象
        }
    }

    is_died()
    {
        if (this.radius < EPS * 10) // 少于这个数表示已经去世
        {
            this.destroy(); //消失
            return true;
        }
        return false;
    }

    start()
    {
        this.start_add_listening_events();
        this.cold_time = 5;
    }

    start_add_listening_evnet()
    {
        if (this.is_me)
        {
            this.add_listening_evnets();
        }
    }

    update()
    {
        this.update_AI();
        this.update_move(); // 更新移动
        this.render(); // 同样要一直画一直画(yxc:“人不吃饭会死,物体不一直画会消失。”)
    }

    update_AI()
    {
        if (this.is_me) return false; // 如果这不是一个机器人就直接退出

        this.update_AI_move();

        if (!this.update_AI_cold_time()) return false; // 还没走完冷静期,就不能放技能
        this.update_AI_shoot_fireball(); // 发射火球
    }

    update_AI_move()
    {
        if (this.move_length < EPS) // 如果停下来就随机选个地方走向那边
        {
            let tx = Math.random() * this.playground.width;
            let ty = Math.random() * this.playground.height;

            this.move_to(tx, ty);
        }
    }

    update_AI_cold_time() // 冷静期
    {
        if (this.cold_time > 0) // 如果处于冷静期,就不能放技能,返回false
        {
            this.cold_time -= this.timedelta / 1000; // 冷静期流逝
            return false;
        }
        return true; // 过了冷静期,可以放技能了,返回true
    }

    update_AI_shoot_fireball()
    {
        if (Math.random() < 1 / 300.0) // 每隔一定时间发射一次
        {
            let player = this.playground.players[0]; // 这个可以设置为随机,自行实现
            this.shoot_fireball(player.x, player.y); // 发射火球
        }
    }

    update_move() // 将移动单独写为一个过程
    {
        if (this.speed_damage && this.speed_damage > EPS) // 如果此时在被击退的状态,就不能自己动
        {
            this.vx = this.vy = 0; // 不能自己动
            this.move_length = 0; // 不能自己动
            this.x += this.x_damage * this.speed_damage * this.timedelta / 1000; // 被击退的移动
            this.y += this.y_damage * this.speed_damage * this.timedelta / 1000; // 被击退的移动
            this.speed_damage *= this.friction_damage; // 摩擦力,表现出一个被击退越来越慢的效果
        }

        if (this.move_length < EPS) // 移动距离没了(小于精度)
        {
            this.move_length = 0; // 全都停下了
            this.vx = this.vy = 0;
        }
        else // 否则继续移动
        {
            let moved = Math.min(this.move_length, this.speed * this.timedelta / 1000); // 每个时间微分里该走的距离
            // 注意:this.timedelta 的单位是毫秒,所以要 / 1000 转换单位为秒
            this.x += this.vx * moved; // 移动
            this.y += this.vy * moved; // 移动
        }
    }

    on_destroy() // 死之前在this.players数组里面删掉这个player
    {
        this.is_alive = false;
        for (let i = 0; i < this.playground.players.length; ++ i)
        {
            let player = this.playground.players[i];
            if (this === player)
            {
                this.playground.players.splice(i, 1);
            }
        }
    }
}

在这里插入图片描述
大致碰撞效果如上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值