程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Django_3

編輯:Python

文章目錄

  • 游戲界面實現
    • 修改js模塊化
    • 增加界面樣式
    • 實現簡易游戲引擎
    • 創建人物
    • 創建火球類
    • 實現碰撞
    • 實現動態效果

游戲界面實現

修改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.fill;
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);
}
}
}
}


大致碰撞效果如上。


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved